<# .SYNOPSIS Install wslx -- the Ubuntu WSL machine manager -- and uv, if it is missing. .DESCRIPTION One typeable line that takes a bare Windows machine to a working `wslx`: iex (irm https://get.optersoft.com/wsl.ps1) The paren form rather than `irm ... | iex` because `|` is unmapped on a SPICE console and on several non-US keyboard layouts, and this script is often the first thing typed into a fresh VM by hand. It installs uv (Astral's Python installer) only if uv is not already there, then installs wslx as a uv tool. Both land in %USERPROFILE%\.local\bin, which is added to PATH for the current session immediately and to future shells via `uv tool update-shell` -- so `wslx` works in this window without reopening it. Idempotent: run it again to upgrade wslx to the latest release. .NOTES wslx itself needs a WSL that supports systemd, because it seeds every distribution with cloud-init and cloud-init runs under systemd. The version of wsl.exe shipped in the Windows image predates that, so this script checks and says so rather than letting the failure surface later as a distribution with no `box` user. See the WSL check at the end. #> [CmdletBinding()] param( # Skip the WSL health check (the install itself does not need WSL). [switch] $NoWslCheck ) $ErrorActionPreference = 'Stop' # PowerShell 5.1 defaults to TLS 1.0, which astral.sh and GitHub refuse. [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # The uv installer draws a progress bar that is very slow over a remote shell. $ProgressPreference = 'SilentlyContinue' function Write-Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } function Write-Ok($msg) { Write-Host " ok $msg" -ForegroundColor Green } function Write-Skip($msg) { Write-Host " -- $msg" -ForegroundColor DarkGray } function Write-Warn($msg) { Write-Host " !! $msg" -ForegroundColor Yellow } function Invoke-Native { <# Run a native executable and return its exit code. PowerShell 5.1 with $ErrorActionPreference='Stop' turns a single byte on a native command's stderr into a terminating NativeCommandError. uv writes ordinary progress there ("Executable directory ... is already in PATH"), and the in-box wsl.exe answers `--version` with its help text there, so both would abort this script despite succeeding. The exit code is the only reliable signal. #> param( [Parameter(Mandatory)][string] $Exe, [string[]] $Arguments = @(), [switch] $Quiet ) $previous = $ErrorActionPreference $ErrorActionPreference = 'Continue' try { if ($Quiet) { & $Exe @Arguments 2>&1 | Out-Null } else { & $Exe @Arguments 2>&1 | ForEach-Object { Write-Host " $_" } } return $LASTEXITCODE } finally { $ErrorActionPreference = $previous } } $binDir = Join-Path $env:USERPROFILE '.local\bin' function Use-LocalBin { <# Put %USERPROFILE%\.local\bin first for this session. The uv installer tells you to restart your shell; we cannot, because the next thing this script does is call uv. #> if ($env:PATH -notlike "*$binDir*") { $env:PATH = "$binDir;$env:PATH" } } # ---------------------------------------------------------------- uv -------- Write-Step 'uv' Use-LocalBin if (Get-Command uv -ErrorAction SilentlyContinue) { Write-Skip "already installed -- $(uv --version)" } else { iex (irm https://astral.sh/uv/install.ps1) Use-LocalBin if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { throw "uv installed but is not on PATH -- expected it in $binDir" } Write-Ok (uv --version) } # -------------------------------------------------------------- wslx -------- Write-Step 'wslx' if (Get-Command wslx -ErrorAction SilentlyContinue) { $code = Invoke-Native uv @('tool', 'upgrade', 'wslx') } else { $code = Invoke-Native uv @('tool', 'install', 'wslx') } if ($code -ne 0) { throw "uv could not install wslx (exit $code)" } # Persist the PATH entry so new shells find wslx too. Harmless if already done. Invoke-Native uv @('tool', 'update-shell') -Quiet | Out-Null Use-LocalBin if (-not (Get-Command wslx -ErrorAction SilentlyContinue)) { throw "wslx installed but is not on PATH -- expected it in $binDir" } Write-Ok (wslx --version) # --------------------------------------------------------------- WSL -------- if (-not $NoWslCheck) { Write-Step 'WSL' if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) { Write-Warn 'wsl.exe not found. Enable WSL before using wslx:' Write-Host ' wsl --install' -ForegroundColor DarkGray } else { # `wsl --version` exists only in the modern (MSI/Store) build. The # in-box wsl.exe answers it with its help text and a non-zero exit, # and that build has no systemd -- so cloud-init never runs and every # distribution wslx creates comes out with no box user, the wrong # hostname and no sudo rule. $env:WSL_UTF8 = '1' if ((Invoke-Native wsl.exe @('--version') -Quiet) -ne 0) { Write-Warn 'This is the in-box WSL, which has no systemd support.' Write-Warn 'wslx needs systemd for cloud-init, so seeding will not work.' Write-Host ' wsl --update' -ForegroundColor DarkGray Write-Host ' # if that fails, install the MSI:' -ForegroundColor DarkGray Write-Host ' # https://github.com/microsoft/WSL/releases/latest' -ForegroundColor DarkGray } else { Write-Ok ((wsl.exe --version) -split "`n" | Select-Object -First 1).Trim() } } } Write-Host '' Write-Host 'Done. Try:' -ForegroundColor Cyan Write-Host ' wslx connect alfa --new' -ForegroundColor DarkGray