Public/Assert-WslHasBash.ps1
|
function Assert-WslHasBash { <# .SYNOPSIS Ensures the targeted WSL distro has bash installed and on PATH. .DESCRIPTION Assert-Wsl2Ready proves WSL2 itself is available and at least one distro is registered, but does not look inside the distro. Some distros - notably the `docker-desktop` distro that Docker Desktop installs and silently sets as the WSL default - ship a busybox/dash userland with no `bash` at all. Any caller that execs a `#!/usr/bin/env bash` script against such a distro fails mid-run with `env: can't execute 'bash': No such file or directory` or `/bin/sh: bash: not found`, leaving the operator to reverse-engineer that a Docker install moved the default. Assert-WslHasBash closes that gap. It runs a one-line probe inside the targeted distro and throws a `WslMissingBash:`- prefixed error when bash is absent, mirroring the `Wsl2NotReady:` contract from Assert-Wsl2Ready so callers can catch one prefix and surface a clear remediation hint. .PARAMETER DistroName Name of the WSL distro to probe (`wsl --list --quiet` lists the available names). When omitted, the system default distro is used - same target a bare `wsl --` invocation would hit. Pass the name explicitly when the caller will go on to use `wsl -d <DistroName> --` and wants the same verification scope. .EXAMPLE try { Assert-Wsl2Ready Assert-WslHasBash # ... wsl -- bash-using work ... } catch { if ($_.Exception.Message -match '^WslMissingBash: ') { Write-Host ( $_.Exception.Message -replace '^WslMissingBash: ','' ) -ForegroundColor Yellow exit 1 } throw } #> [CmdletBinding()] param( [string] $DistroName ) # Probe shape: `command -v bash` is a POSIX-portable PATH lookup # that works in busybox sh as well as bash. It prints the resolved # path (which we discard) and exits 0 on hit, non-zero on miss. # `/bin/sh -c` is reached via wsl's exec - even docker-desktop has # /bin/sh - so the probe itself does not depend on bash. $wslArgs = @() if ($DistroName) { $wslArgs += @('-d', $DistroName) } $wslArgs += @('--', '/bin/sh', '-c', 'command -v bash') # Stderr folded into stdout via 2>&1 so a transient wsl error (e.g. # distro stopped, not registered) surfaces in the thrown message # rather than the operator's console only. Native invocation; # $LASTEXITCODE is the source of truth - $? alone misreports on # native commands. $probeOutput = & wsl @wslArgs 2>&1 $probeExit = $LASTEXITCODE if ($probeExit -eq 0) { return } $targetLabel = if ($DistroName) { "distro '$DistroName'" } else { 'the default WSL distro' } # Detection hint: docker-desktop's name is well-known and the # docker-install root cause is so common that calling it out # up-front saves the operator a diagnostic round trip. $hint = if (-not $DistroName -or $DistroName -eq 'docker-desktop') { " If 'wsl --list --verbose' shows 'docker-desktop' as the default, " + "install a real Linux distro (e.g. 'wsl --install -d Ubuntu-24.04') and " + "set it as default via 'wsl --set-default Ubuntu-24.04'." } else { " Install bash inside '$DistroName' (e.g. 'wsl -d $DistroName -- apt-get install -y bash' " + "for Debian/Ubuntu) or point downstream callers at a different distro." } throw ( "WslMissingBash: bash was not found on PATH inside $targetLabel " + "(exit ${probeExit}: $($probeOutput -join ' ')).${hint}" ) } |