6

I'm using WSL2 on Windows 10.

My distro is Debian.

I wanted to set a static IP for my distro, but I found out that it can't be done for WSL from this answer.

However I figured out that there could be some other possibilities.

Is it possible to have a Powershell script that updates the etc hosts on Windows 10 with the new WSL IP pointing to a custom domain name like 172.18.225.26 dev.local on start up? And without leaving any prev WSL configs on the host file.

My host file currently looks like this:

# Copyright (c) 1993-2009 Microsoft Corp.
#
# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
#
# This file contains the mappings of IP addresses to host names. Each
# entry should be kept on an individual line. The IP address should
# be placed in the first column followed by the corresponding host name.
# The IP address and the host name should be separated by at least one
# space.
#
# Additionally, comments (such as these) may be inserted on individual
# lines or following the machine name denoted by a '#' symbol.
#
# For example:
#
#      102.54.94.97     rhino.acme.com          # source server
#       38.25.63.10     x.acme.com              # x client host

# localhost name resolution is handled within DNS itself.
#   127.0.0.1       localhost
#   ::1             localhost
# Added by Docker Desktop
192.168.8.101 host.docker.internal
192.168.8.101 gateway.docker.internal
# To allow the same kube context to work on the host and the container:
127.0.0.1 kubernetes.docker.internal
# End of section


# Added by Custom Script For WSL Distro
172.18.225.26 dev.local
#End of section

I have no knowledge on Powershell scripting, I would really appreciate the help.

3
  • 1
    Create a file named hosts but stage it somewhere else with the needed values in the file there, and then as a startup script, you can copy it to Windows\System32\drivers\etc and overwrite the file there. You can google and play with copy-item which is trivial to figure out but use the -force parameter with it. Nov 5, 2021 at 12:25
  • @VomitIT-ChunkyMessStyle Thanks, this gave me clues to resolving my issue.
    – Ikechukwu
    Nov 6, 2021 at 11:55
  • 1
    Good job, I'm glad to hear you got a working solution! Nov 6, 2021 at 14:31

3 Answers 3

4

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.

1

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|192\.168)\.[\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.


EDIT: changed (?<ip>172\.[\d\.]*)\/ to (?<ip>(172|192\.168)\.[\d\.]*)\/ because it seems that WSL2 have started issuing IPs from that range too (?). [Edited on 17 Feb 2022 ]

1

With Windows 11 and current wsl2, I found that mDNS seems to work fine, and you won't need to script your hosts file. My WSL2 distro is ubuntu 22.04.

You can use mDNS to advertise your wsl hosts' hostname with the .local suffix. If you assign a hostname to your wsl2 environment in /etc/wsl.conf, like this:

[network]
hostname=projects

Your windows host should be able to resolve the name projects.local.

from the WSL terminal, you can check your wsl is advertising it's IP address with something like:

$ avahi-resolve -n "$(hostname).local"
projects.local  172.17.0.1

PS: I found out that as long as I enable packet forwarding in Windows (as shown here), I can even reach my wsl host from a HyperV VM connected to the default vSwitch, using the <wsl hostname>.local name.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .