-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMove-WSL2NewDrive.ps1
286 lines (215 loc) · 8.75 KB
/
Move-WSL2NewDrive.ps1
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
# Move-WSL2NewDrive.ps1
<#
This assumes that there is only a single VHDX file in the distro's BasePath.
Based on work by sonook @Giuthub
https://github.com/MicrosoftDocs/WSL/issues/412#issuecomment-828924500
#>
[CmdletBinding()]
param ()
function Add-YesNoPrompt
{
[CmdletBinding()]
param (
[Parameter()]
[string]
$Title,
[Parameter()]
[string]
$Question,
[Parameter()]
[ValidateRange(0,1)]
[int]
$Default = 0
)
$choices = '&Yes', '&No'
$decision = $Host.UI.PromptForChoice($title, $question, $choices, $Default)
if ($decision -eq 0)
{
return $true
}
else
{
return $false
}
}
$wsl2RegRoot = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Lxss\"
# get the distros from the registry to save time on string parsing of wsl
$distros = Get-ChildItem $wsl2RegRoot -EA SilentlyContinue | ForEach-Object { Get-ItemProperty $_.PSPath } | Where-Object Version -eq 2
$locSpace = ($distros.DistributionName | ForEach-Object { $_.Length } | Measure-Object -Maximum | ForEach-Object Maximum) + 3
Write-Host -ForegroundColor Yellow "Which distro do you want to move?"
if ($distros.Count -gt 0)
{
1..($distros.Count) | ForEach-Object {
$tmpDistro = $distros[($_ - 1)]
Write-Host "$_`t$(($tmpDistro.DistributionName).PadRight($locSpace))(Location: $($tmpDistro.BasePath))"
}
}
else
{
Write-Host "No WSL2 distros were found."
}
do
{
try
{
[int]$selection = Read-Host "Chose wisely" -EA Stop
}
catch
{
Write-Warning "You have chosen poorly. The selection must be an integer, 1 thru $($distros.Count). [1]`n"
$selection = $null
}
if ($selection -and ($selection -lt 1 -or $selection -gt $distros.Count))
{
Write-Warning "You have chosen poorly. The selection must be an integer, 1 thru $($distros.Count). [2]`n"
$selection = $null
}
} until ($selection)
# save the selected distro
$selDistro = $distros[$selection - 1]
do
{
$path = Read-Host "Enter the full parent path to the new distro (the path used will be: <path you enter>\<distro-name>)"
if (-NOT (Test-Path "$path" -IsValid))
{
Write-Warning "'$path' is an invalid path. Please try again."
$path = $null
}
} until ($path)
$destPath = "$path\$($selDistro.DistributionName)"
$title = @"
Moving distro $($selDistro.DistributionName). Please save and close all work in WSL2 before proceeding, as a WSL shutdown is required to move a distro.
Current Path: $($selDistro.BasePath)
New Path: $destPath
"@
$question = "Would you like to proceed (Y/n)?"
$go = Add-YesNoPrompt $title $question
if ($go)
{
Write-Verbose "Stopping wsl."
wsl --shutdown
$stopTime = Get-Date
Write-Verbose "Create the destination dir, $destPath."
try
{
$null = mkdir "$destPath" -Force -EA Stop
}
catch
{
return (Write-Error "Failed to create the destination directory, $destPath`: $_" -EA Stop)
}
# copy the file first, not a move
$vhdx = Get-ChildItem "$($selDistro.BasePath)" -Filter "*.vhdx"
if ($vhdx)
{
Write-Verbose "Copying $($vhdx.FullName) to $destPath."
$null = Copy-Item "$($vhdx.FullName)" "$destPath" -Force
}
else
{
return (Write-Error "Failed to find the distro's VHDX file in $($selDistro.BasePath)" -EA Stop)
}
# save and update the registery
reg.exe export "$(($selDistro.PSPath).Split(':')[-1])" "$destPath\pre-move-export.reg" /y > $null
try
{
Set-ItemProperty $selDistro.PSPath -Name BasePath -Value $destPath -EA Stop
}
catch
{
return (Write-Error "Failed to update the distro path in the registry: $_" -EA Stop)
}
# start the distro to make sure it works
$cmdArgs = "/k wsl.exe -d $($selDistro.DistributionName)"
Start-Process cmd -ArgumentList $cmdArgs
$sw = [system.diagnostics.stopwatch]::StartNew()
$distroRunning = $false
do
{
# wait 1 second for the state to change
Start-Sleep 1
# is the distro running?
# wsl.exe output does some sort of strange encoding that pwsh doesn't like...there has to be a better way to do this
# strip out all the zeroes
$raw = [System.Text.Encoding]::Unicode.GetBytes((wsl -l -v)) | Where-Object { $_ -ne 0 }
# convert the result into UTF8
$text = [System.Text.Encoding]::UTF8.GetString($raw)
# get the index of the distro in the output + (length of the distro name)
$tIdx = $text.IndexOf("$($selDistro.DistributionName)") + ($selDistro.DistributionName.Length)
Write-Verbose "tIdx: $tIdx"
# now look for the index of the next number, which will be the WSL version number
$c = $tIdx
$verIdx = -1
Write-Verbose "text substring: $($text.Substring($tIdx))"
foreach ($char in $text.Substring($tIdx).ToCharArray())
{
Write-Debug "pipe: $_"
if ($char -match "\d")
{
Write-Verbose "match at: $c"
$verIdx = $c
break
}
else
{
$c++
}
}
Write-Verbose "verIdx: $verIdx"
# get all the non-whitespace characters between tIdx and verIdx, join them into a word
$status = ($text.Substring($tIdx, ($verIdx - $tIdx)).ToCharArray() | Where-Object {$_ -match "\w"}) -join ""
Write-Verbose "status: $status"
if ($status -eq "Running")
{
$distroRunning = $true
}
} until ($sw.Elapsed.TotalSeconds -gt 60 -or $distroRunning)
$sw.Stop()
if ($distroRunning -eq $false)
{
# write warning and do not delete anything
Write-Warning @"
Either the distro did not start in the new location or the script couldn't determine if it did. The VHDX file in the original location has NOT been deleted, just in case.
Use "wsl -l -v" to manually check the status of the $($selDistro.DistributionName). If the status is Running, then you should be okay to manually delete the old VHDX file.
The original VHDX file is in:
$($selDistro.BasePath)
How do you know for absolute certain that it worked?
Go to the original and new locations ($destPath) and compare the Date Modified time on the VHDX files. The time on the new file should have a recent Date Modified time.
The old VHDX file should not have a Date Modified time much newer than around $($stopTime.ToShortDateString()) $($stopTime.ToShortTimeString())`. I recorded when the script stopped wsl, just in case.
If something really weird happened you can revert to the original location by merging $destPath\pre-move-export.reg (double-click) back into the registry. This will reset the location back to the original path.
"@
}
else
{
# compare Dat Modified (LastWriteTime) on old and new files to make sure the new file was used post-migration
$oldTime = Get-Item "$($vhdx.FullName)" | ForEach-Object LastWriteTime
$newTime = Get-ChildItem "$destPath" -Filter "*.vhdx" | ForEach-Object LastWriteTime
# assuming there is just one VHDX file in the dir
if ($newTime -gt $oldTime)
{
$title = @"
It's cleanup time!
The original file now appears out of date, based on the last write times of the old and new VHDX files. Which means it should be safe to delete the old VHDX file and free up some space.
Do you want to be 100% certain first!
Go to the original and new locations and compare the Date Modified time on the VHDX files. The time on the new file should have a newer Date Modified time.
The old VHDX file should not have a Date Modified time much newer than around $($stopTime.ToShortDateString()) $($stopTime.ToLongTimeString())`. That is roughly the time when the script stopped wsl.
The original VHDX file is in:
$($selDistro.BasePath)
The new VHDX file is in:
$destPath
You can use "wsl --shutdown" followed by relaunching the WSL2 distro to force a change of the Date Modified time. The new file time should change, the old file should remain the same.
If that's what happened then it should be safe to delete the old file.
"@
$byebye = Add-YesNoPrompt -Title $title -Question "Remove the original file?"
if ($byebye)
{
# delete the original file
$null = $vhdx | Remove-Item -Force
}
}
}
}
else
{
Write-Host "User terminated."
}