<# .SYNOPSIS Toggle a Windows-side TCP bridge so Maestro (running in WSL) can reach the Android emulators' adb ports. .DESCRIPTION Android emulators on the Windows host bind their adb ports (5555, 5557) to 127.0.0.1 only. Maestro's bundled `dadb` connects directly to that port — not via the adb server — so `ADB_SERVER_SOCKET` is irrelevant. To run `maestro test` from inside WSL, we expose those ports on the WSL-facing interface and let the matching WSL-side relay (.dev/wsl_tcp_relay.py) bridge 127.0.0.1 in WSL → on Windows. Heads-up: idle `netsh portproxy` entries with no live target have been reported to occasionally pin CPU on iphlpsvc. Keep these rules OFF when you're not running Maestro from WSL — that is the whole point of this script. .PARAMETER Action up Add port-proxy entries + firewall rules. down Remove them. status Print current portproxy + matching firewall rules. .PARAMETER ListenAddress Optional. Defaults to the IPv4 of the `vEthernet (WSL)` adapter — the IP that WSL2 sees as its default gateway. Override only if your WSL adapter is named differently. .PARAMETER Ports Optional. Defaults to 5554,5555,5556,5557 — the console + adb ports for emulator-5554 (5554 console, 5555 adb) and emulator-5556 (5556 console, 5557 adb). Maestro / dadb need both: the console port for authentication and the adb port for the actual protocol. .EXAMPLE # From an elevated PowerShell, before `maestro test ...` in WSL: .\wsl_emulator_bridge.ps1 up .EXAMPLE # When done testing — tear it back down to avoid the CPU-pin risk: .\wsl_emulator_bridge.ps1 down .EXAMPLE .\wsl_emulator_bridge.ps1 status #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateSet('up', 'down', 'status')] [string]$Action, [string]$ListenAddress, [int[]]$Ports = @(5554, 5555, 5556, 5557) ) $ErrorActionPreference = 'Stop' $FirewallRulePrefix = 'WSL adb bridge' function Assert-Admin { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { throw 'This script must be run from an elevated PowerShell (Run as administrator).' } } function Resolve-WslGatewayIp { param([string]$Override) if ($Override) { return $Override } # WSL2 always creates a `vEthernet (WSL)` adapter on the Windows host. # Its IPv4 address is what WSL2 sees as `default via ...` in `ip route`. $candidates = Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue | Where-Object { $_.InterfaceAlias -like 'vEthernet (WSL*)' -and $_.PrefixOrigin -ne 'WellKnown' } | Sort-Object -Property InterfaceMetric if (-not $candidates) { throw "Couldn't find a `vEthernet (WSL)` adapter. Pass -ListenAddress explicitly." } return $candidates[0].IPAddress } function Get-PortProxyEntries { $out = & netsh interface portproxy show v4tov4 2>&1 if ($LASTEXITCODE -ne 0) { throw "netsh interface portproxy show v4tov4 failed: $out" } return $out } function Invoke-PortProxyUp { param([string]$Addr, [int]$Port) & netsh interface portproxy add v4tov4 ` listenaddress=$Addr listenport=$Port ` connectaddress=127.0.0.1 connectport=$Port | Out-Null if ($LASTEXITCODE -ne 0) { throw "Failed to add portproxy for $Addr`:$Port (exit $LASTEXITCODE)" } $ruleName = "$FirewallRulePrefix $Port" $existing = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue if (-not $existing) { New-NetFirewallRule -DisplayName $ruleName ` -Direction Inbound -Action Allow -Protocol TCP ` -LocalPort $Port -LocalAddress $Addr | Out-Null } Write-Host " + $Addr`:$Port -> 127.0.0.1:$Port (firewall: $ruleName)" -ForegroundColor Green } function Invoke-PortProxyDown { param([string]$Addr, [int]$Port) & netsh interface portproxy delete v4tov4 ` listenaddress=$Addr listenport=$Port 2>$null | Out-Null $ruleName = "$FirewallRulePrefix $Port" $rule = Get-NetFirewallRule -DisplayName $ruleName -ErrorAction SilentlyContinue if ($rule) { Remove-NetFirewallRule -DisplayName $ruleName } Write-Host " - $Addr`:$Port removed" -ForegroundColor Yellow } switch ($Action) { 'up' { Assert-Admin $addr = Resolve-WslGatewayIp -Override $ListenAddress Write-Host "Bringing WSL adb bridge UP on $addr ..." -ForegroundColor Cyan foreach ($p in $Ports) { Invoke-PortProxyUp -Addr $addr -Port $p } Write-Host '' Write-Host 'Remember to take it DOWN when you are done:' -ForegroundColor DarkGray Write-Host ' .\wsl_emulator_bridge.ps1 down' -ForegroundColor DarkGray } 'down' { Assert-Admin # If user didn't pass -ListenAddress, try to clean up whichever address # is currently bound. Walk the existing portproxy entries. $entries = Get-PortProxyEntries $matched = @() foreach ($p in $Ports) { $regex = "^\s*(\d+\.\d+\.\d+\.\d+)\s+$p\s+" foreach ($line in $entries) { if ($line -match $regex) { $matched += [pscustomobject]@{ Addr = $matches[1]; Port = $p } } } } if (-not $matched -and $ListenAddress) { foreach ($p in $Ports) { $matched += [pscustomobject]@{ Addr = $ListenAddress; Port = $p } } } if (-not $matched) { Write-Host 'No matching portproxy entries found. Nothing to remove.' -ForegroundColor DarkGray break } Write-Host 'Tearing WSL adb bridge DOWN ...' -ForegroundColor Cyan foreach ($m in $matched) { Invoke-PortProxyDown -Addr $m.Addr -Port $m.Port } } 'status' { Write-Host 'Active portproxy entries:' -ForegroundColor Cyan Get-PortProxyEntries | ForEach-Object { Write-Host " $_" } Write-Host '' Write-Host 'Matching firewall rules:' -ForegroundColor Cyan Get-NetFirewallRule -DisplayName "$FirewallRulePrefix *" -ErrorAction SilentlyContinue | Select-Object DisplayName, Enabled, Direction, Action | Format-Table -AutoSize | Out-String | Write-Host } }