Repository navigation
Expand file tree
/
Copy pathwsl.ps1
More file actions
320 lines (282 loc) · 13.8 KB
/
Copy pathwsl.ps1
File metadata and controls
320 lines (282 loc) · 13.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
<#
.SYNOPSIS
Installs WSL and Ubuntu with reboot support, or removes them during cleanup.
#>
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
$Script:DevConfigWslDistributionName = 'Ubuntu'
$Script:DevConfigWslFirstRunSetting = @{
Name = 'WslFirstRun'
KeyPath = 'HKCU\Software\Microsoft\Windows\CurrentVersion\Lxss'
ValueName = 'OOBEComplete'
Value = 1
}
# This CBS key signals component servicing pending restart; app installer restart flags are ignored.
function Test-DevConfigServicingRebootPending {
return (Test-Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending')
}
# WSL output is redirected and bounded; exit codes are used because message text is localized.
function Get-DevConfigWslExitCode {
param(
[Parameter(Mandatory)] [string[]] $Arguments,
[int] $TimeoutSeconds = 120,
[switch] $NoInput
)
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
return $null
}
$stdout = [System.IO.Path]::GetTempFileName()
$stderr = [System.IO.Path]::GetTempFileName()
$stdin = $null
try {
if ($NoInput) { $stdin = [System.IO.Path]::GetTempFileName() }
return Invoke-DevConfigProcess -FilePath 'wsl.exe' -Arguments $Arguments -TimeoutSeconds $TimeoutSeconds `
-NoNewWindow -RedirectStandardOutput $stdout -RedirectStandardError $stderr -RedirectStandardInput $stdin
} catch {
Write-Verbose "wsl $($Arguments -join ' ') could not run: $($_.Exception.Message)"
return $null
} finally {
Remove-Item -LiteralPath $stdout, $stderr -Force -ErrorAction SilentlyContinue
if ($stdin) { Remove-Item -LiteralPath $stdin -Force -ErrorAction SilentlyContinue }
}
}
# The current WSL package supports --version; the inbox WSL returns a nonzero exit code.
function Test-DevConfigWslRuntimeCurrent {
return ((Get-DevConfigWslExitCode -Arguments @('--version') -NoInput) -eq 0)
}
function Test-DevConfigWslFeaturesActive {
$service = Get-CimInstance -ClassName Win32_Service -Filter "Name='vmcompute'"
return ($null -ne $service)
}
function Test-DevConfigWslComponentsReady {
return (Test-DevConfigWslFeaturesActive) -and (Test-DevConfigWslRuntimeCurrent)
}
# The Store update is tried first; --web-download provides the same package when Store access is unavailable.
function Update-DevConfigWslRuntime {
Write-Host ' This machine has the older WSL that ships inside Windows; a distro needs the current one.' -ForegroundColor DarkGray
Write-Host ' Updating WSL (wsl --update)...' -ForegroundColor DarkCyan
foreach ($arguments in @(@('--update'), @('--update', '--web-download'))) {
$exitCode = Get-DevConfigWslExitCode -Arguments $arguments -TimeoutSeconds 900
if ($exitCode -eq 0 -and (Test-DevConfigWslRuntimeCurrent)) {
return $true
}
Write-Verbose "wsl $($arguments -join ' ') returned $exitCode"
}
Write-Host ' WSL could not be updated here.' -ForegroundColor Yellow
return $false
}
function Install-DevConfigWslComponents {
try {
Invoke-DevConfigRetry -Name 'wsl --install --no-distribution' -MaxAttempts 2 -ScriptBlock {
Write-Host 'Installing WSL platform components (wsl --install --no-distribution)...'
Write-Host '(A separate WSL window may pop up briefly -- that is normal. This can take a few minutes.)' -ForegroundColor DarkGray
# No -NoNewWindow: wsl's install bootstrap needs a real console to run against.
$exitCode = Invoke-DevConfigProcess -FilePath 'wsl.exe' -Arguments @('--install', '--no-distribution') -TimeoutSeconds 900
if ($exitCode -eq 3010 -or $exitCode -eq 1641) {
Write-Host 'WSL components installed; a reboot is required to activate them.'
$Script:DevConfigWslRestartSignalled = $true
} elseif ($exitCode -ne 0) {
throw "wsl --install --no-distribution failed with exit code $exitCode"
}
}
} catch {
# Direct feature enablement can still prepare WSL when wsl --install is unavailable.
Write-Host " WSL's own installer could not run here ($($_.Exception.Message))." -ForegroundColor Yellow
Write-Host ' Turning on the WSL Windows features directly instead.' -ForegroundColor Yellow
Enable-DevConfigWslFeatures
}
# Enabling features may leave only the inbox WSL; updating ensures the current WSL package is present.
if (-not (Test-DevConfigWslRuntimeCurrent)) {
Update-DevConfigWslRuntime | Out-Null
}
}
# dism.exe provides stable exit codes and avoids the Windows PowerShell compatibility layer.
function Enable-DevConfigWslFeatures {
foreach ($feature in @('VirtualMachinePlatform', 'Microsoft-Windows-Subsystem-Linux')) {
Write-Host " Turning on the $feature Windows feature..." -ForegroundColor DarkCyan
$exitCode = Invoke-DevConfigProcess -FilePath 'dism.exe' -NoNewWindow -TimeoutSeconds 1200 -Arguments @(
'/online', '/enable-feature', "/featurename:$feature", '/all', '/norestart', '/quiet'
)
# 3010 is "enabled, restart required", which is the expected outcome here.
if ($exitCode -eq 3010) {
$Script:DevConfigWslRestartSignalled = $true
} elseif ($exitCode -ne 0) {
throw "Could not turn on the $feature Windows feature (dism exit code $exitCode)."
}
}
}
function Test-DevConfigUbuntuInstalled {
# Without wsl.exe, Ubuntu is treated as not installed rather than as an error.
if (-not (Get-Command wsl.exe -ErrorAction SilentlyContinue)) {
return $false
}
$env:WSL_UTF8 = '1'
$out = [System.IO.Path]::GetTempFileName()
$err = [System.IO.Path]::GetTempFileName()
$stdin = $null
try {
# Closed stdin prevents a missing WSL runtime from waiting at its installation prompt.
$stdin = [System.IO.Path]::GetTempFileName()
# This query is bounded and redirected so a nonresponsive listing is treated as not installed.
$exitCode = Invoke-DevConfigProcess -FilePath 'wsl.exe' -Arguments @('--list', '--quiet') `
-NoNewWindow -TimeoutSeconds 120 -RedirectStandardOutput $out -RedirectStandardError $err -RedirectStandardInput $stdin
if ($exitCode -ne 0) {
return $false
}
$distros = @(Get-Content -LiteralPath $out -Encoding UTF8 |
ForEach-Object { ($_ -replace "`0", '').Trim() } |
Where-Object { $_ })
# Match Ubuntu specifically, including versioned registrations such as Ubuntu-24.04.
return @($distros | Where-Object { $_ -like "$($Script:DevConfigWslDistributionName)*" }).Count -gt 0
} catch {
Write-Verbose "Could not list WSL distros: $($_.Exception.Message)"
return $false
} finally {
Remove-Item -LiteralPath $out, $err -Force -ErrorAction SilentlyContinue
if ($stdin) { Remove-Item -LiteralPath $stdin -Force -ErrorAction SilentlyContinue }
}
}
# A --no-launch install can complete before wsl --list shows the distro, so the listing is retried.
function Wait-DevConfigUbuntuVisible {
for ($attempt = 1; $attempt -le 10; $attempt++) {
if (Test-DevConfigUbuntuInstalled) {
return $true
}
if ($attempt -eq 1) {
Write-Host ' (Waiting for WSL to list the new distro...)' -ForegroundColor DarkGray
}
Start-Sleep -Seconds 3
}
return $false
}
# Success requires both a zero exit code and Ubuntu appearing in wsl --list afterward.
function Install-DevConfigUbuntuVia {
param(
[Parameter(Mandatory)] [string[]] $Arguments,
[int] $MaxAttempts = 3
)
try {
Invoke-DevConfigWslUbuntuInstall -Arguments $Arguments -MaxAttempts $MaxAttempts
} catch {
Write-Host " That route did not work ($($_.Exception.Message))." -ForegroundColor Yellow
return $false
}
return (Wait-DevConfigUbuntuVisible)
}
function Install-DevConfigUbuntu {
# A distro install requires active platform components, so fail early when they are not active.
if (-not (Test-DevConfigWslComponentsReady)) {
throw "WSL isn't active on this machine, so Ubuntu can't be installed yet (see the note above)."
}
# Suppresses the "Welcome to WSL" first-run GUI.
$setting = $Script:DevConfigWslFirstRunSetting
Set-DevConfigRegistryValue -KeyPath $setting.KeyPath -ValueName $setting.ValueName -Value $setting.Value
if (Install-DevConfigUbuntuVia -Arguments @('--install', '-d', $Script:DevConfigWslDistributionName, '--no-launch') -MaxAttempts 2) {
return
}
# The web-download path does not depend on Store access or Store registration timing.
Write-Host ' The Store copy of Ubuntu did not take. Downloading Ubuntu from the web instead.' -ForegroundColor Yellow
if (Install-DevConfigUbuntuVia -Arguments @('--install', '-d', $Script:DevConfigWslDistributionName, '--no-launch', '--web-download')) {
return
}
Set-DevConfigStepUnverified -Reason "Ubuntu did not finish installing. Everything else is set up -- run this again, or install Ubuntu from the Start menu."
}
function Invoke-DevConfigWslUbuntuInstall {
param(
[Parameter(Mandatory)] [string[]] $Arguments,
[int] $MaxAttempts = 3
)
Invoke-DevConfigRetry -Name "wsl $($Arguments -join ' ')" -MaxAttempts $MaxAttempts -ScriptBlock {
Write-Host "Downloading and installing Ubuntu (wsl $($Arguments -join ' '))..."
Write-Host '(A separate WSL window may pop up briefly -- that is normal. This can take a few minutes.)' -ForegroundColor DarkGray
$exitCode = Invoke-DevConfigProcess -FilePath 'wsl.exe' -Arguments $Arguments -TimeoutSeconds 1200
if ($exitCode -ne 0) {
throw "wsl $($Arguments -join ' ') failed with exit code $exitCode"
}
}
}
$Script:DevConfigWslInactiveMessage = @'
WSL's platform components are installed but still not usable after a restart, so restarting
again would not help. The usual cause is virtualization being turned off: enable it in the
BIOS/UEFI, or turn on nested virtualization if this is a virtual machine. If virtualization is
already on, this machine could not reach the WSL download. Either way, run this script again
once that is sorted.
'@
function Install-DevConfigWslPlatform {
param(
[Parameter(Mandatory)] [string] $OrchestratorPath
)
$Script:DevConfigWslRestartSignalled = $false
Install-DevConfigWslComponents
# Skip restart only when no servicing restart is pending and the WSL platform is active.
if (-not $Script:DevConfigWslRestartSignalled -and
-not (Test-DevConfigServicingRebootPending) -and
(Test-DevConfigWslComponentsReady)) {
return
}
# After one resume, stop instead of repeating restarts if the platform is still inactive.
if ($Script:DevConfigResumed) {
throw $Script:DevConfigWslInactiveMessage
}
Suspend-DevConfigForReboot -ScriptPath $OrchestratorPath
}
function Test-DevConfigWslDistributionRegistered {
$root = Convert-DevConfigRegistryPath -KeyPath $Script:DevConfigWslFirstRunSetting.KeyPath
if (-not (Test-Path -LiteralPath $root)) {
return $false
}
foreach ($key in Get-ChildItem -LiteralPath $root) {
$properties = Get-ItemProperty -LiteralPath $key.PSPath
$name = $properties.PSObject.Properties['DistributionName']
if ($name -and $name.Value -eq $Script:DevConfigWslDistributionName) {
return $true
}
}
return $false
}
function Test-DevConfigWslPackageInstalled {
$executable = Join-Path $env:ProgramFiles 'WSL\wsl.exe'
return (Test-Path -LiteralPath $executable) -or
@(Get-AppxPackage -AllUsers -Name 'MicrosoftCorporationII.WindowsSubsystemForLinux' -ErrorAction Stop).Count -gt 0
}
function Remove-DevConfigWsl {
if (Test-DevConfigWslDistributionRegistered) {
Invoke-DevConfigCleanupCommand -FilePath 'wsl.exe' -Arguments @('--unregister', $Script:DevConfigWslDistributionName) | Out-Null
}
if (Test-DevConfigWslPackageInstalled) {
# WSL can terminate itself during removal; the step still verifies that the package is gone.
Invoke-DevConfigCleanupCommand -FilePath 'wsl.exe' -Arguments @('--uninstall') -SuccessCodes @(0, 1, 3010) | Out-Null
for ($attempt = 0; $attempt -lt 15; $attempt++) {
if (-not (Test-DevConfigWslPackageInstalled)) {
return
}
Start-Sleep -Seconds 2
}
}
}
function Invoke-WslPhase {
param(
[Parameter(Mandatory)] [string] $OrchestratorPath
)
if ($Script:DevConfigAction -eq 'Uninstall') {
$steps = @(
New-DevConfigRegistryStep -Setting $Script:DevConfigWslFirstRunSetting -Reset
New-DevConfigStep -Name 'WslCleanup' -Description "Delete $Script:DevConfigWslDistributionName and its data, then uninstall WSL" -BestEffort `
-Check { -not (Test-DevConfigWslDistributionRegistered) -and -not (Test-DevConfigWslPackageInstalled) } `
-Apply { Remove-DevConfigWsl }
)
Invoke-DevConfigSteps -Steps $steps
return
}
# ArgumentList binds the path at call time; BestEffort preserves prior phases if WSL cannot start.
$steps = @(
New-DevConfigStep -Name 'WslComponents' -Description 'Install WSL platform components' -BestEffort `
-Check { Test-DevConfigWslComponentsReady } `
-Apply { param($OrchestratorPath) Install-DevConfigWslPlatform -OrchestratorPath $OrchestratorPath } `
-ArgumentList @($OrchestratorPath)
New-DevConfigStep -Name 'WslUbuntu' -Description 'Install the default Ubuntu distro' -BestEffort `
-Check { Test-DevConfigUbuntuInstalled } `
-Apply { Install-DevConfigUbuntu }
)
Invoke-DevConfigSteps -Steps $steps
}