# VirtualBox automatic port forward

The reason why this is in its own section it's because it's generic enough to be followed for any distro, as the changes are minimal.

Make sure you have installed the VirtualBox Guest Additions, that's a requirement. Same goes for PowerShell v7.

First, make sure your user has permissions to run /usr/bin/netstat and /usr/bin/service (if on a systemd-based distro) as root. Otherwise, you'll need to use sudo and visudo and make some changes in the /etc/sudoers file, of course, replacing user with your Linux username:

user  ALL=(ALL) NOPASSWD: /usr/bin/netstat, /usr/sbin/service

Create a PowerShell script somewhere in your computer. I've named it C:\_scripts\VBoxWSLSVC.ps1. Leave it empty for now. First, let's make a shortcut for it:

  1. Use your Windows Start Menu to look out for the PowerShell shortcut. Copy it on your desktop or something, or create a new shortcut by looking for pwsh.exe. It could be found at C:\Program Files\PowerShell\7. Right-click it and choose "Create shortcut". If it asks to create it on the desktop, click yes.

  2. Right-click the shortcut

  3. Click Properties.

  4. Change Target to look like this:

    "C:\Program Files\PowerShell\7\pwsh.exe" "C:\_scripts\VBoxWSLSVC.ps1"
  5. Click on Compatibility tab

  6. Enable "Run this program as administrator"

  7. Click OK

Now, put this as the content for your PowerShell script and edit the first lines accordingly. When you fire up your VM, you run the new PowerShell shortcut you just created (so this script runs as administrator) and it'll be watching the VM for new ports opened every 5 seconds, and apply the corresponding NAT port forwarding rules. Automagically!

$VMNAME = "WSL2"
$USER = "user"
$PASS = "pass"
$VBOXMANAGE = "C:\Program Files\Oracle\VirtualBox\VBoxManage.exe"

$restartCounter = 0
while($true)
{
    ##############################################################################################################################
    # Get existing rules
    ##############################################################################################################################
    $existingRules = @()
    $existingRulesOutput = & $VBOXMANAGE showvminfo $VMNAME
    $existingRulesMatches = [regex]::Matches($existingRulesOutput,'NIC \d Rule\(\d*?\):.*?\[(.*?)-(.*?)\]');
    foreach($ruleMatch in $existingRulesMatches)
    {
        $port = $ruleMatch.Groups[2].Value
        $proto = $ruleMatch.Groups[1].Value
        $existingRules += [PSCustomObject]@{ "Port"=$port; "Protocol"=$proto }
    }

    ##############################################################################################################################
    # Get currently open ports
    ##############################################################################################################################
    $newPorts = @()
    $netstatOutput = & $VBOXMANAGE guestcontrol $VMNAME run --username $USER --password $PASS "/usr/bin/sudo" -- "/usr/bin/netstat" "-ant"
    $portMatches = [regex]::matches($netstatOutput,'([a-z]{1,}?)\s*\d*\s*\d*\s*\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\:(\d{1,5})\s*0\.0\.0\.0');
    foreach ($portMatch in $portMatches)
    {
        $port = $portMatch.Groups[2].Value;
        $proto =  $portMatch.Groups[1].Value;
        if ($port -eq 53) { continue; }
        if ($port -eq 41339) { continue; }
        $newPorts += [PSCustomObject]@{ "Port"=$port; "Protocol"=$proto }
    }

    ##############################################################################################################################
    # Prepare new rules
    ##############################################################################################################################
    $newRules = @{}
    $currentRules = @{}

    foreach($rule in $newPorts) {
        $port = $rule.Port
        $proto = $rule.Protocol
        $newRules["$proto-$port"] = $true
    }

    foreach($rule in $existingRules) {
        $port = $rule.Port
        $proto = $rule.Protocol
        $currentRules["$proto-$port"] = $true
    }

    ##############################################################################################################################
    # Remove unused ports
    ##############################################################################################################################
    foreach($key in $currentRules.Keys)
    {
        $rule = $newRules[$key]
        $split = $key.Split('-')
        $proto = $split[0]
        $port = $split[1]
        if($rule -eq $null) {
            & $VBOXMANAGE controlvm $VMNAME natpf1 delete "[$key]"
            Write-Host  "Port $port ($proto) closed" -ForegroundColor Red
        }
    }

    ##############################################################################################################################
    # Add new ports
    ##############################################################################################################################
    foreach($key in $newRules.Keys)
    {
        $split = $key.Split('-')
        $proto = $split[0]
        $port = $split[1]
        $rule = $currentRules[$key]
        if($rule -eq $null) {
            & $VBOXMANAGE controlvm $VMNAME natpf1 "[$key],$proto,,${port},,${port}"
            Write-Host "Port $port ($proto) opened" -ForegroundColor Green
        }
    }

    echo ...
    Start-Sleep -s 5
    $restartCounter = $restartCounter + 1
    if ($restartCounter -eq 6 || $restartCounter -gt 6) {
        $restartCounter = 0
        echo Restart
        & $VBOXMANAGE guestcontrol $VMNAME run --username $USER --password $PASS --wait-stdout --wait-stderr --ignore-operhaned-processes --timeout 10000 "/usr/bin/sudo" -- "/usr/sbin/service" "vboxadd-service" "restart"
        Start-Sleep -s 1
        & $VBOXMANAGE guestcontrol $VMNAME run --username $USER --password $PASS --wait-stdout --wait-stderr --ignore-operhaned-processes --timeout 10000 "/usr/bin/sudo" -- "/usr/sbin/service" "vboxadd" "restart"
    }
}