Script for installing WSL on Windows 10 AME
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

549 lines
19 KiB

2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. # Created for Windows 10 AME version 20H2
  2. # Script version: 1.0.0
  3. # Author: Logan Darklock <logandarklock+ame@gmail.com> (no spam please)
  4. # https://stackoverflow.com/a/34559554/
  5. function New-TemporaryDirectory {
  6. $parent = [System.IO.Path]::GetTempPath()
  7. $name = [System.IO.Path]::GetRandomFileName()
  8. New-Item -ItemType Directory -Path (Join-Path $parent $name)
  9. }
  10. Workflow Install-WSL {
  11. [CmdletBinding(DefaultParameterSetName='Installation')]
  12. param(
  13. [Parameter(Mandatory=$True,ParameterSetName='Installation',Position=0)]
  14. [ValidateSet(
  15. 'wslubuntu2004',
  16. 'wslubuntu2004arm',
  17. 'wsl-ubuntu-1804',
  18. 'wsl-ubuntu-1804-arm',
  19. 'wsl-ubuntu-1604',
  20. 'wsl-debian-gnulinux',
  21. 'wsl-kali-linux-new',
  22. 'wsl-opensuse-42',
  23. 'wsl-sles-12'
  24. )]
  25. [string]$LinuxDistribution,
  26. [Parameter(Mandatory=$False,ParameterSetName='Installation')]
  27. [switch]$FeatureInstalled,
  28. [Parameter(Mandatory=$False,ParameterSetName='Installation')]
  29. [switch]$OmitWindowsTerminal,
  30. [Parameter(Mandatory=$True,ParameterSetName='Cancelation')]
  31. [switch]$Cancel,
  32. [Parameter(Mandatory=$False,ParameterSetName='WindowsTerminal')]
  33. [switch]$InstallWindowsTerminal
  34. )
  35. # The task scheduler is unreliable in AME
  36. $ShortcutPath = Join-Path $env:AppData 'Microsoft\Windows\Start Menu\Programs\Startup\Install WSL.lnk'
  37. if ($Cancel) {
  38. $Removed = Remove-Item -LiteralPath $ShortcutPath -ErrorAction SilentlyContinue
  39. $Removed = Get-Job -Command 'Install-WSL' | Where-Object {$_.State -eq 'Suspended'} | Remove-Job -Force
  40. Write-Information 'All pending WSL installations have been canceled.'
  41. return 'done'
  42. } elseif ($InstallWindowsTerminal) {
  43. InlineScript {
  44. $ExecutionPolicy = Get-ExecutionPolicy -Scope Process
  45. Set-ExecutionPolicy RemoteSigned -Scope Process
  46. Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
  47. Set-ExecutionPolicy $ExecutionPolicy -Scope Process
  48. scoop bucket add extras
  49. scoop install windows-terminal
  50. }
  51. return 'done'
  52. }
  53. # establish directory for WSL installations
  54. $AppDataFolder = Join-Path $env:LocalAppData 'WSL'
  55. $DistrosFolder = New-Item -ItemType Directory -Force -Path $AppDataFolder
  56. $DistroFolder = Join-Path $DistrosFolder $LinuxDistribution
  57. if (Test-Path -Path $DistroFolder -PathType Container) {
  58. return Write-Error 'Cannot install a distro twice! This will waste your internet data. Uninstall the existing version first.' -Category ResourceExists
  59. }
  60. Write-Information 'Creating startup item'
  61. InlineScript {
  62. $shell = New-Object -ComObject ('WScript.Shell')
  63. $shortcut = $shell.CreateShortcut($Using:ShortcutPath)
  64. $shortcut.TargetPath = 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe'
  65. $shortcut.Arguments = "-WindowStyle Normal -NoLogo -NoProfile -Command `"& { Write-Output \`"Resuming installation...\`"; Get-Job -Command `'Install-WSL`' | Resume-Job | Receive-Job -Wait -InformationAction Continue; pause; exit }`""
  66. $shortcut.Save()
  67. }
  68. Write-Information ''
  69. Write-Information 'There will be a "Windows PowerShell" shortcut in your startup items until this'
  70. Write-Information 'script is complete. Please do not be alarmed, it will remove itself once the'
  71. Write-Information 'installation is complete.'
  72. Write-Information ''
  73. Write-Information 'Ensuring required features are enabled...'
  74. # using a named pipe to communicate between elevated process and not elevated one
  75. if ($FeatureInstalled) {
  76. $RestartNeeded = $False
  77. } else {
  78. try {
  79. # For various reasons this needs to be duplicated twice.
  80. # I hate it as much as you, but for some reason I can't put it in a function
  81. # It just refuses to work when I try to call it in the loop below
  82. $RestartNeeded = InlineScript {
  83. $PipeName = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 12 |%{[char]$_})
  84. $Enabled = Start-Process powershell -ArgumentList "`
  85. `$Enabled = Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart -WarningAction SilentlyContinue`
  86. `$RestartNeeded = `$Enabled.RestartNeeded`
  87. `
  88. `$pipe = New-Object System.IO.Pipes.NamedPipeServerStream `'$PipeName`',`'Out`'`
  89. `$pipe.WaitForConnection()`
  90. `$sw = New-Object System.IO.StreamWriter `$pipe`
  91. `$sw.AutoFlush = `$True`
  92. `$sw.WriteLine([string]`$RestartNeeded)`
  93. `$sw.Dispose()`
  94. `$pipe.Dispose()`
  95. " -Verb RunAs -WindowStyle Hidden -ErrorAction Stop
  96. $pipe = New-Object System.IO.Pipes.NamedPipeClientStream '.',$Using:PipeName,'In'
  97. $pipe.Connect()
  98. $sr = New-Object System.IO.StreamReader $pipe
  99. $data = $sr.ReadLine()
  100. $sr.Dispose()
  101. $pipe.Dispose()
  102. $data -eq [string]$True
  103. } -ErrorAction Stop
  104. } catch {
  105. return Write-Error 'Please accept the UAC prompt so that the WSL feature can be installed, or specify the -FeatureInstalled flag to skip'
  106. }
  107. }
  108. if ($RestartNeeded) {
  109. # TODO detect if we're already waiting for a reboot specifically
  110. # Maybe this can be done by checking for the scheduled task instead?
  111. # This feels messy which is why it's disabled, and it would also detect
  112. # the currently running task
  113. # Future Logan from the future!: I think the shortcut is more easily
  114. # detected, but there are reasons you might want to run this more than
  115. # once in a row. For example if you are installing multiple distros
  116. # Should work okay...
  117. Write-Information 'Please restart your computer to continue the installation'
  118. 'restart-needed'
  119. Suspend-Workflow
  120. # Wait for a logon where the feature is installed. This will be after at
  121. # least 1 reboot, but for various reasons (grumble grumble...) it might
  122. # be later. Every Suspend-Workflow is virtually guaranteed to be resumed
  123. # by a logon, or a manual resume (which is harmless in this case).
  124. $waiting = $True
  125. while ($waiting) {
  126. if ($FeatureInstalled) {
  127. $RestartNeeded = $False
  128. } else {
  129. try {
  130. $RestartNeeded = InlineScript {
  131. $PipeName = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 12 |%{[char]$_})
  132. $Enabled = Start-Process powershell -ArgumentList "`
  133. `$Enabled = Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux -NoRestart -WarningAction SilentlyContinue`
  134. `$RestartNeeded = `$Enabled.RestartNeeded`
  135. `
  136. `$pipe = New-Object System.IO.Pipes.NamedPipeServerStream `'$PipeName`',`'Out`'`
  137. `$pipe.WaitForConnection()`
  138. `$sw = New-Object System.IO.StreamWriter `$pipe`
  139. `$sw.AutoFlush = `$True`
  140. `$sw.WriteLine([string]`$RestartNeeded)`
  141. `$sw.Dispose()`
  142. `$pipe.Dispose()`
  143. " -Verb RunAs -WindowStyle Hidden -ErrorAction Stop
  144. $pipe = New-Object System.IO.Pipes.NamedPipeClientStream '.',$Using:PipeName,'In'
  145. $pipe.Connect()
  146. $sr = New-Object System.IO.StreamReader $pipe
  147. $data = $sr.ReadLine()
  148. $sr.Dispose()
  149. $pipe.Dispose()
  150. $data -eq [string]$True
  151. } -ErrorAction Stop
  152. } catch {
  153. # I decided that this is not always true and it would be
  154. # rude to assume that. So I give the user a choice and allow
  155. # them to continue without UAC
  156. ## The user accepted the UAC prompt the first time, so they
  157. ## can do it again. They cannot specify the -FeatureInstalled
  158. ## flag at this point, unfortunately.
  159. #Write-Output 'Please accept the UAC prompt to continue installation.'
  160. # Try to get input from the user as a fallback
  161. $response = InlineScript {
  162. [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
  163. [System.Windows.Forms.Messagebox]::Show("Admin access is required to check the status of the WSL feature. If you can no longer grant admin access via UAC:`n`nIs the WSL feature installed and enabled?", 'WSL Installer', [System.Windows.Forms.MessageBoxButtons]::YesNo)
  164. }
  165. $RestartNeeded = $response -eq 7 # 7 is DialogResult.No
  166. }
  167. }
  168. if ($RestartNeeded) {
  169. Write-Information 'Looks like the WSL component is still not installed.'
  170. 'still-waiting'
  171. Suspend-Workflow
  172. } else {
  173. $waiting = $False
  174. }
  175. }
  176. }
  177. Write-Information "`n`n`n`n`n`n`n"
  178. Write-Information 'It will take a few minutes to download the distribution. Most WSL distros are'
  179. Write-Information 'at or around 200 MB in size. Depending on your internet connection, you could be'
  180. Write-Information 'staring at this screen for 10 minutes. Sit back and relax, grab a cup of tea...'
  181. Write-Information ''
  182. $retrying = $True
  183. while ($retrying) {
  184. $tempFile = InlineScript { New-TemporaryFile }
  185. Remove-Item -LiteralPath $tempFile
  186. $tempFile = $tempFile.FullName -replace '$','.zip'
  187. try {
  188. Write-Information "Attempting to download distribution to $tempFile..."
  189. $data = InlineScript {
  190. $PipeName = -join (((48..57)+(65..90)+(97..122)) * 80 |Get-Random -Count 12 |%{[char]$_})
  191. Start-Process powershell -ArgumentList "`
  192. Try {`
  193. Invoke-WebRequest -Uri `"https://aka.ms/$Using:LinuxDistribution`" -OutFile `"$Using:tempFile`" -ErrorAction Stop -UseBasicParsing`
  194. `$Result = 'Success'`
  195. } Catch {`
  196. `$Result = `"Failed to download file: `$(`$PSItem.Message)`"`
  197. }`
  198. `
  199. `$pipe = New-Object System.IO.Pipes.NamedPipeServerStream `'$PipeName`',`'Out`'`
  200. `$pipe.WaitForConnection()`
  201. `$sw = New-Object System.IO.StreamWriter `$pipe`
  202. `$sw.AutoFlush = `$True`
  203. `$sw.WriteLine([string]`$Result)`
  204. `$sw.Dispose()`
  205. `$pipe.Dispose()`
  206. " -WindowStyle Hidden -ErrorAction Stop
  207. $pipe = New-Object System.IO.Pipes.NamedPipeClientStream '.',$PipeName,'In'
  208. $pipe.Connect()
  209. $sr = New-Object System.IO.StreamReader $pipe
  210. $data = $sr.ReadLine()
  211. $sr.Dispose()
  212. $pipe.Dispose()
  213. $data
  214. } -ErrorAction Stop
  215. if ($data -ne 'Success') {
  216. Write-Error $data -ErrorAction Stop
  217. }
  218. $retrying = $False
  219. Write-Information 'Done!'
  220. } catch {
  221. Remove-Item -LiteralPath $tempFile -ErrorAction SilentlyContinue
  222. # PSItem is contextual and can't be read from the InlineScript
  223. $theError = $PSItem
  224. Write-Information "Error: $theError"
  225. $response = InlineScript {
  226. [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
  227. [System.Windows.Forms.Messagebox]::Show("The WSL package '$Using:LinuxDistribution' could not be downloaded from Microsoft's servers.`n`nError: $Using:theError`n`nYou may abort the install, and restart it at any time using the wizard. Clicking Ignore will cause a retry the next time you log in.", 'Could not download WSL package', [System.Windows.Forms.MessageBoxButtons]::AbortRetryIgnore)
  228. }
  229. if ($response -eq 3) { # Abort
  230. Write-Information 'Aborting'
  231. $retrying = $False
  232. Write-Information 'Removing startup item...'
  233. Remove-Item -LiteralPath $ShortcutPath -ErrorAction SilentlyContinue
  234. return 'aborted'
  235. } elseif ($response -eq 5) { # Ignore
  236. Write-Information 'Ignoring'
  237. 'still-waiting'
  238. Suspend-Workflow # Wait for next logon
  239. }
  240. Write-Information 'Retrying'
  241. # If retry just loop again /shrug
  242. }
  243. }
  244. Write-Information 'Removing startup item...'
  245. Remove-Item -LiteralPath $ShortcutPath -ErrorAction SilentlyContinue
  246. $tempDir = New-TemporaryDirectory
  247. Expand-Archive -LiteralPath $tempFile -DestinationPath $tempDir -ErrorAction Stop
  248. Remove-Item -LiteralPath $tempFile -ErrorAction SilentlyContinue
  249. Write-Information 'Distribution bundle extracted'
  250. $theDir = $tempDir
  251. $Executable = Get-ChildItem $tempDir | Where-Object {$_.Name -match '.exe$'} | Select-Object -First 1
  252. if ($Executable -eq $null) {
  253. $Package = Get-ChildItem $tempDir | Where-Object {$_.Name -match '_x64.appx$'} | Select-Object -First 1
  254. if ($Package -eq $null) {
  255. return Write-Error 'Could not find the package containing the installer :(' -Category NotImplemented
  256. }
  257. $Package = Rename-Item -LiteralPath ($Package.FullName) -NewName ($Package.Name -replace '.appx$','.zip') -PassThru
  258. Write-Information "Distribution package: $($Package.Name)"
  259. $InnerPackageTemp = New-TemporaryDirectory
  260. Expand-Archive -LiteralPath $Package -DestinationPath $InnerPackageTemp
  261. Remove-Item -LiteralPath $tempDir -Recurse
  262. $Executable = Get-ChildItem $InnerPackageTemp | Where-Object {$_.Name -match '.exe$'} | Select-Object -First 1
  263. $theDir = $InnerPackageTemp
  264. if ($Executable -eq $null) {
  265. return Write-Error 'Could not find an executable inside the x64 package :(' -Category NotImplemented
  266. }
  267. } else {
  268. Write-Information 'Root package contains the installer'
  269. }
  270. # this is going to have to stick around forever if the wsl install is going to stay intact
  271. $theDir = Move-Item -LiteralPath $theDir -Destination $DistroFolder -PassThru
  272. $Executable = Get-ChildItem $theDir | Where-Object {$_.Name -match '.exe$'} | Select-Object -First 1
  273. Write-Information "Executing installer: $($Executable.Name)"
  274. InlineScript { wsl --set-default-version 1 }
  275. Start-Process -FilePath ($Executable.FullName) -Wait
  276. if (!$OmitWindowsTerminal) {
  277. Write-Information 'Installing Windows Terminal...'
  278. InlineScript {
  279. $ExecutionPolicy = Get-ExecutionPolicy -Scope Process
  280. Set-ExecutionPolicy RemoteSigned -Scope Process
  281. Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh')
  282. Set-ExecutionPolicy $ExecutionPolicy -Scope Process
  283. scoop bucket add extras
  284. scoop install windows-terminal
  285. }
  286. }
  287. Write-Information 'Everything should be in order now. Enjoy!'
  288. # We done
  289. return 'done'
  290. }
  291. function Install-WSLInteractive {
  292. $Distros = @(
  293. [PSCustomObject]@{Slug = 'wslubuntu2004'; Name = 'Ubuntu 20.04'; Arch = 'x64'}
  294. [PSCustomObject]@{Slug = 'wsl-ubuntu-1804'; Name = 'Ubuntu 18.04'; Arch = 'x64'}
  295. [PSCustomObject]@{Slug = 'wsl-ubuntu-1604'; Name = 'Ubuntu 16.04'; Arch = 'x64'}
  296. [PSCustomObject]@{Slug = 'wsl-debian-gnulinux'; Name = 'Debian Stable'; Arch = 'x64'}
  297. [PSCustomObject]@{Slug = 'wsl-kali-linux-new'; Name = 'Kali Linux'; Arch = 'x64'}
  298. [PSCustomObject]@{Slug = 'wsl-opensuse-42'; Name = 'OpenSUSE 4.2'; Arch = 'x64'}
  299. [PSCustomObject]@{Slug = 'wsl-sles-12'; Name = 'SLES 12'; Arch = 'x64'}
  300. )
  301. $Menu = 'main'
  302. if ([Security.Principal.WindowsIdentity]::GetCurrent().Groups -contains 'S-1-5-32-544') {
  303. $Menu = 'admin'
  304. }
  305. while ($Menu -ne 'exit') {
  306. Clear-Host
  307. # 80 chars: ' '
  308. Write-Host ' :: WSL INSTALL SCRIPT FOR WINDOWS 10 AME v1.0.0'
  309. Write-Host ''
  310. Write-Host ' This script will help you install Windows Subsystem for Linux on your'
  311. Write-Host ' ameliorated installation of Windows 10'
  312. Write-Host ''
  313. Write-Host ' :: NOTE: Tested on Windows 10 1909, and Windows 10 AME 20H2'
  314. switch ($menu) {
  315. 'main' {
  316. Write-Host ''
  317. Write-Host ' :: Please enter a number from 1-3 to select an option from the list below'
  318. Write-Host ''
  319. Write-Host ' 1) Install a new WSL distro'
  320. Write-Host ' 2) Cancel a pending WSL installation'
  321. Write-Host ' 3) Exit'
  322. Write-Host ''
  323. Write-Host ' >> ' -NoNewLine
  324. $Input = $Host.UI.ReadLine()
  325. switch ($Input) {
  326. '1' {
  327. $Menu = 'select-distro'
  328. }
  329. '2' {
  330. $Menu = 'cancel'
  331. }
  332. '3' {
  333. $Menu = 'exit'
  334. }
  335. default {
  336. Write-Host ''
  337. Write-Host ' !! Invalid option selected' -ForegroundColor red
  338. Write-Host ''
  339. Write-Host ' Press enter to continue...' -NoNewLine
  340. $Host.UI.ReadLine()
  341. }
  342. }
  343. }
  344. 'select-distro' {
  345. Write-Host ''
  346. Write-Host ' :: Please enter a number from the list to select a distro to install'
  347. Write-Host ''
  348. $Max = 1
  349. $Distros | ForEach-Object {
  350. Add-Member -InputObject $_ -NotePropertyName Option -NotePropertyValue ([string]$Max) -Force
  351. Write-Host " $Max) $($_.Name)"
  352. $Max += 1
  353. }
  354. Write-Host " $Max) Return to main menu"
  355. Write-Host ''
  356. Write-Host ' >> ' -NoNewLine
  357. $Input = $Host.UI.ReadLine()
  358. if ($Input -eq ([string]$Max)) {
  359. $Menu = 'main'
  360. } else {
  361. $Distro = $Distros | Where-Object -Property Option -eq -Value $Input
  362. if ($Distro -eq $null) {
  363. Write-Host ''
  364. Write-Host ' !! Invalid option selected' -ForegroundColor Red
  365. Write-Host ''
  366. Write-Host ' Press enter to continue...' -NoNewLine
  367. $Host.UI.ReadLine()
  368. } else {
  369. $Menu = 'install-distro-confirm'
  370. }
  371. }
  372. }
  373. 'install-distro-confirm' {
  374. Write-Host ''
  375. Write-Host " :: WARNING: Are you sure you want to install $($Distro.Name)? (yes/no) " -NoNewLine
  376. $Input = $Host.UI.ReadLine()
  377. switch ($Input) {
  378. 'yes' {
  379. $Menu = 'install-distro'
  380. }
  381. 'no' {
  382. $Menu = 'select-distro'
  383. }
  384. default {
  385. Write-Host ''
  386. Write-Host ' !! Invalid input' -ForegroundColor Red
  387. Write-Host ''
  388. Write-Host ' Press enter to continue...' -NoNewLine
  389. $Host.UI.ReadLine()
  390. $Menu = 'select-distro'
  391. }
  392. }
  393. }
  394. 'install-distro' {
  395. Write-Host ''
  396. Write-Host "Installing $($Distro.Name)..."
  397. try {
  398. $Menu = ('result-' + (Install-WSL -LinuxDistribution ($Distro.Slug) -InformationAction Continue -ErrorAction Stop | Select-Object -First 1 -Wait))
  399. } catch {
  400. Write-Host ''
  401. Write-Host ' !! An error occurred during the installation' -ForegroundColor Red
  402. Write-Host " !! The error is: $PSItem" -ForegroundColor Red
  403. Write-Host ''
  404. Write-Host ' Your chosen distro could not be installed.'
  405. Write-Host ''
  406. Write-Host ' Press enter to continue...' -NoNewLine
  407. $Host.UI.ReadLine()
  408. $Menu = 'select-distro'
  409. }
  410. }
  411. 'cancel' {
  412. Write-Host ''
  413. Write-Host ' :: WARNING: Are you sure you want to cancel all pending installs? (yes/no) ' -NoNewLine
  414. $Input = $Host.UI.ReadLine()
  415. switch ($Input) {
  416. 'yes' {
  417. Write-Host ''
  418. Install-WSL -Cancel
  419. }
  420. 'no' {
  421. Write-Host ''
  422. Write-Host ' Returning to main menu.'
  423. }
  424. default {
  425. Write-Host ''
  426. Write-Host ' !! Invalid input' -ForegroundColor Red
  427. }
  428. }
  429. Write-Host ''
  430. Write-Host ' Press enter to continue...' -NoNewLine
  431. $Host.UI.ReadLine()
  432. $Menu = 'main'
  433. }
  434. 'admin' {
  435. Write-Host ''
  436. Write-Host ' !! This script should NOT be run as Administrator' -ForegroundColor Red
  437. Write-Host ' !! Please close this window and run the script normally' -ForegroundColor Red
  438. Write-Host ''
  439. Write-Host ' Press enter to continue...' -NoNewLine
  440. $Host.UI.ReadLine()
  441. $Menu = 'exit'
  442. }
  443. 'result-restart-needed' {
  444. Clear-Host
  445. Write-Host ' !! WSL installation will resume once you restart Windows'
  446. Write-Host ''
  447. Write-Host ' Please ensure you stay connected to the Internet.'
  448. Write-Host ''
  449. Write-Host ' Press enter to continue...' -NoNewLine
  450. $Host.UI.ReadLine()
  451. $Menu = 'exit'
  452. }
  453. 'result-done' {
  454. Clear-Host
  455. Write-Host ' :: Installation done!'
  456. Write-Host ''
  457. Write-Host ' The WSL feature was already installed and enabled on your system, so we were'
  458. Write-Host ' able to install your distro right away.'
  459. Write-Host ''
  460. Write-Host ' Enjoy!'
  461. Write-Host ''
  462. Write-Host ' Press enter to continue...' -NoNewLine
  463. $Host.UI.ReadLine()
  464. $Menu = 'exit'
  465. }
  466. default {
  467. Write-Host ''
  468. Write-Host " !! Invalid menu encountered ($Menu). Exiting" -ForegroundColor Red
  469. Write-Host ' !! THIS IS A BUG, PLEASE REPORT IT TO THE AME DEVS' -ForegroundColor Red
  470. $Menu = 'exit'
  471. }
  472. }
  473. }
  474. }