tasks/70-WslDistros.ps1

@{
    Id          = 'WslDistros'
    DisplayName = 'WSL distros'
    Description = 'Installs configured WSL distros and relocates each to WslHome via `wsl --manage --move`'
    Action      = {
        [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
        param(
            [Parameter(Mandatory)]
            [System.Collections.IDictionary] $Paths
        )

        Write-Header -Name 'WSL Distros'

        if (-not (Get-Command wsl -ErrorAction SilentlyContinue)) {
            Write-Status -Level Skip -Message ' [SKIP] WSL is not installed.'
            return
        }

        # Force UTF-8 output from wsl.exe so parsing isn't broken by UTF-16LE.
        # Restore the previous value (or remove it) on the way out so the env
        # var doesn't leak into the parent shell.
        $previousWslUtf8 = $env:WSL_UTF8
        $env:WSL_UTF8 = '1'

        try {
            # `wsl --manage --move` was introduced in WSL 2.4.4 (Nov 2024). Fail
            # cleanly on older versions instead of producing a confusing error.
            $wslHelp = wsl --help 2>&1 | Out-String
            if ($wslHelp -notmatch '--manage') {
                Write-Status -Level Skip -Message ' [SKIP] WSL --manage is not supported (requires WSL 2.4.4+). Run `wsl --update` and retry.'
                return
            }

            # Docker Desktop manages its own WSL distros; relocating them via
            # `wsl --manage --move` will break Docker. Use the "Disk image location"
            # setting in Docker Desktop's Resources -> Advanced UI instead.
            $excluded = @('docker-desktop', 'docker-desktop-data')

            # --- Step 1: install configured distros that aren't installed yet ---
            $configured = @(Get-WslDistrosConfig)
            if ($configured.Count -gt 0) {
                $existing = @(wsl -l -q |
                    Where-Object { $_ -and $_.Trim() } |
                    ForEach-Object { $_.Trim() })

                foreach ($name in $configured) {
                    if ($name -in $existing) { continue }
                    if ($PSCmdlet.ShouldProcess($name, 'wsl --install -d')) {
                        Write-Status -Level Info -Message " [INFO] Installing WSL distro '$name'..."
                        wsl --install -d $name --no-launch 2>&1 | Format-ToolOutput
                        if ($LASTEXITCODE -ne 0) {
                            throw ("wsl --install -d {0} failed with exit code {1}." -f $name, $LASTEXITCODE)
                        }
                        Write-Status -Level Info -Message " [INFO] $name installed."
                    }
                }
            }

            # --- Step 2: relocate every installed distro to the configured target ---
            # Re-query so any distro installed in Step 1 is included.
            $distros = wsl -l -q |
                Where-Object { $_ -and $_.Trim() } |
                ForEach-Object { $_.Trim() }

            if (-not $distros) {
                Write-Status -Level Skip -Message ' [SKIP] No WSL distros found.'
                return
            }

            $toMigrate = foreach ($name in $distros) {
                if ($name -in $excluded) {
                    Write-Status -Level Skip -Message " [SKIP] $name (managed by Docker Desktop; relocate via Docker settings instead)"
                    continue
                }

                $targetDir = Join-Path $Paths.WslHome $name
                if (Test-Path -Path $targetDir -PathType Container) {
                    Write-Status -Level Info -Message " [INFO] $name already exists at $targetDir"
                    continue
                }
                [PSCustomObject]@{ Name = $name; Target = $targetDir }
            }

            if (-not $toMigrate) { return }

            if ($PSCmdlet.ShouldProcess('WSL', 'Shut down before relocating distros')) {
                Write-Status -Level Info -Message ' [INFO] Shutting down WSL before migrating...'
                wsl --shutdown 2>&1 | Format-ToolOutput
            }

            foreach ($d in $toMigrate) {
                if ($PSCmdlet.ShouldProcess($d.Name, "Relocate to $($d.Target)")) {
                    Write-Status -Level Info -Message " [INFO] Relocating $($d.Name) to $($d.Target)..."
                    wsl --manage $d.Name --move "$($d.Target)\" 2>&1 | Format-ToolOutput
                    Write-Status -Level Info -Message " [INFO] $($d.Name) migrated."
                }
            }
        }
        finally {
            if ($null -eq $previousWslUtf8) {
                Remove-Item Env:\WSL_UTF8 -ErrorAction SilentlyContinue
            }
            else {
                $env:WSL_UTF8 = $previousWslUtf8
            }
        }
    }
}