Update Windows "host" File With WSL IP On Start Up

I have been able to get what I wanted thanks to help from this answer, but I did make some modifications.

Basically, I created an update_wsl_ip_to_domain.ps1 file and saved to this PATH C:\Scripts\update_wsl_ip_to_domain.ps1, then I added the following scripts

#
# Powershell script for adding/removing/showing entries to the hosts file.
#
# Known limitations:
# - does not handle entries with comments afterwards ("<ip>    <host>    # comment")
#

$file = "C:\Windows\System32\drivers\etc\hosts"
$wsl_ip = (wsl -d debian hostname -I).trim()
$data = @('add','localwsl.com',$wsl_ip)

function add-host([string]$filename, [string]$hostname, [string]$ip) {
    remove-host $filename $hostname
    $ip + "`t`t" + $hostname | Out-File -encoding ASCII -append $filename
}

function remove-host([string]$filename, [string]$hostname) {
    $c = Get-Content $filename
    $newLines = @()

    foreach ($line in $c) {
        $bits = [regex]::Split($line, "\t+")
        if ($bits.count -eq 2) {
            if ($bits[1] -ne $hostname) {
                $newLines += $line
            }
        } else {
            $newLines += $line
        }
    }

    # Write file
    Clear-Content $filename
    foreach ($line in $newLines) {
        $line | Out-File -encoding ASCII -append $filename
    }
}

function print-hosts([string]$filename) {
    $c = Get-Content $filename

    foreach ($line in $c) {
        $bits = [regex]::Split($line, "\t+")
        if ($bits.count -eq 2) {
            Write-Host $bits[0] `t`t $bits[1]
        }
    }
}

try {
    if ($data[0] -eq "add") {

        if ($data.count -lt 3) {
            throw "Not enough arguments for add."
        } else {
            add-host $file $data[1] $data[2]
        }

    } elseif ($data[0] -eq "remove") {

        if ($data.count -lt 2) {
            throw "Not enough arguments for remove."
        } else {
            remove-host $file $data[1]
        }

    } elseif ($data[0] -eq "show") {
        print-hosts $file
    } else {
        throw "Invalid operation '" + $data[0] + "' - must be one of 'add', 'remove', 'show'."
    }
} catch  {
    Write-Host $error[0]
    Write-Host "`nUsage: hosts add <ip> <hostname>`n       hosts remove <hostname>`n       hosts show"
}

Next, I created a separate update_wsl_ip_to_domain.cmd file within the same directory as the ps1 file and I added the following commands.

PowerShell Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
Powershell -File C:\Scripts\update_wsl_ip_to_domain.ps1
PowerShell Set-ExecutionPolicy Restricted

So in order to get the update_wsl_ip_to_domain.cmd file to run on startup, I created a shortcut to this directory C:\Users\<user_name>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\ and gave it admin privileges.

Finally, I also added another shortcut of the update_wsl_ip_to_domain.cmd file on my desktop with admin privileges also, which I run manually, because for some reason the previous one doesn't always run on startup.

UPDATES

On my Debian WSL distro, I thought of running apache on port 80 and nginx on port 81.

So in order to give them a static IP and a domain name I've edited my update_wsl_ip_to_domain.ps1 file and added the following code at the bottom.

#GIVING WSL NGINX A STATIC IP. IT RUNS AT PORT 81 ON MY DEBIAN
netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.65.43.21 connectport=81 connectaddress=$wsl_ip

#GIVING WSL APACHE  A STATIC IP. IT RUNS AT PORT 80 ON MY DEBIAN
netsh interface portproxy add v4tov4 listenport=80 listenaddress=127.65.43.22 connectport=80 connectaddress=$wsl_ip

Then I made the following entries to my hosts file on Windows 10.

127.65.43.21        localwslnginx.com

127.65.43.22        localwslapache.com

I believe that there is a lot of potential to this, I just might be able to run multiple apps on different ports with my apache or nginx.

I will make updates as I go on.

UPDATES 2 (28th, December 2021)

I have been able to run multiple apps over wsl with each of them having their unique domain names. So here's what my hosts file on windows looks like:

127.65.43.21        localwslnginx.com

127.65.43.22        localwslapache.com

# Laravel Apps
127.65.43.22        evangrest.test

127.65.43.22        firstbarcodes.test

They must bear the same static IP with the localwslapache.com or localwslnginx.com for apache and nginx respectively.

For apache, I used the following config to get my laravel app running:

<VirtualHost *:80>
    DocumentRoot "/data/www/firstbarcodes/public"
    DirectoryIndex "index.php"
    ServerName firstbarcodes.test
    ServerAlias firstbarcodes.test    

    <Directory "/data/www/firstbarcodes">
       Options Indexes FollowSymLinks
       AllowOverride None
       Require all granted
    </Directory>
   
    <Directory "/data/www/firstbarcodes/public">
      Options Indexes FollowSymLinks MultiViews ExecCGI
      AllowOverride All
      Order allow,deny
      Allow from all
      Require all granted
    </Directory>
    
    #Alias /js /data/www/firstbarcodes/public/js
    #<Directory /data/www/firstbarcodes/public/js>
    #  Options Indexes FollowSymLinks MultiViews ExecCGI
    #  AllowOverride All
    #  Order allow,deny
    #  Allow from all
    #  Require all granted
    #</Directory>
</VirtualHost>

You might also need to run this command sudo a2enmod rewrite for apache

I guess this way, I have been able to somehow create a hack for having a static IP for all my apache and nginx apps over WSL.

What I would love to do next is to get my WSL Debian to auto-start my apache and mariadb on windows startup.


I also had the same problem and came with my own solution. Here it is. It lacks adding static IPs with netsh, however, has some other features :)


I have several WSL distros.

For each of them, I create a file named MYWSLDISTRONAME-hostnames.txt in a scripts directory.
This file contains only domain names:

app.ds.local
pma.ds.local
sandbox.ds.local
test.ds.local

At the beginning, I add these domain names to the end of the hosts file, just once, like

127.0.0.1 app.ds.local
127.0.0.1 pma.ds.local
127.0.0.1 sandbox.ds.local
127.0.0.1 test.ds.local

If I have to add another domain name for my distro, I stop the WSL, and then just add
another.ds.local line to MYWSLDISTRONAME-hostnames.txt and
127.0.0.1 another.ds.local to hosts

Every time I need to start my WSL distro, I run the following script MYWSLDISTRONAME-start.ps1. What it does:

  • Starts the /root/start_services.sh script inside the distro that starts the necessary services etc (thus solving a problem with lack of systemd)
  • Gets the IP address of running WSL distro
  • Replaces IP with the new one in hosts for each domain name from MYWSLDISTRONAME-hostnames.txt. All this stuff is done with the temporary hosts file.
  • Checks if the real hosts file requires any modifications. If yes, a separate pwsh is started that requests admin privileges. If not, the script just displays a note.
$runningDirectory = Split-Path -Parent -Path $MyInvocation.MyCommand.Definition

$hostsFile        = "c:\windows\system32\drivers\etc\hosts"
$wslHostnamesFile = "$runningDirectory\MYWSLDISTRONAME-hostnames.txt"
$tmpFile          = "$runningDirectory\.new-hostnames.txt"
$distroName       = "MYWSLDISTRONAME"


# Start services inside the WSL
wsl -d $distroName -e /root/start_services.sh

# Get IP address of WSL distro
$wslIpAddr = wsl -d $distroName -- ip addr
$match = [System.Text.RegularExpressions.Regex]::Match($wslIpAddr, "(?<ip>172\.[\d\.]*)\/")
$ip = $match.Groups["ip"]

Write-Host "$distroName is available at $ip"


# read hosts file and change the IPs
$host_file_contents = Get-Content $hostsFile -Encoding UTF8 -Raw
$hostnames = Get-Content $wslHostnamesFile -Encoding UTF8
foreach ($hostname in $hostnames) {
    $host_file_contents = [System.Text.RegularExpressions.Regex]::Replace($host_file_contents, "(\b(?:(?:2(?:[0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9])\.){3}(?:(?:2([0-4][0-9]|5[0-5])|[0-1]?[0-9]?[0-9]))\b)\s+$hostname", "$ip`t$hostname") 
}

# Save hosts file
$host_file_contents | Set-Content -Path $tmpFile -Encoding UTF8 

# Compare two files to avoid unncecessary popups
$result = Compare-Object -ReferenceObject ((Get-Content $hostsFile).trim() -ne '') -DifferenceObject ((Get-Content $tmpFile).trim() -ne '')
if ($result)
{
    # Prepare and execute encoded command (note that we use 'pwsh' instead of 'powershell')
    $command = "Get-Content -Path ""$tmpFile"" | Set-Content -Path ""$hostsFile"""
    Write-Host "Updating hosts file with: $command"
    $encodedCmd = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
    Start-Process -FilePath pwsh.exe -Verb RunAs -ArgumentList "-encodedcommand $encodedCmd"
}
else 
{
    Write-Host "Hosts file is the same, modification is unnecessary"
}

Start-Sleep -Seconds 5

# Remove temporary file
Remove-Item $tmpFile

NOTE: I really like your idea with netsh and probably will add it to my scripts.

And, for completeness, I also have MYWSLDISTRONAME-stop.ps1:

wsl.exe --shutdown -d MYWSLDISTRONAME

I also have .lnk's for both .ps1 files with nice and distinguishing icons to easily identify each distro :).
They can be started without admin privileges, the scripts will request them if necessary.