|
#Requires -Version 5 |
|
#Requires -RunAsAdministrator |
|
|
|
Param( |
|
[switch] $PreRelease |
|
) |
|
|
|
|
|
Function Enable-WindowsSandbox { |
|
if (!(Get-Command 'WindowsSandbox.exe' -ErrorAction 'SilentlyContinue')) { |
|
try { |
|
Enable-WindowsOptionalFeature -FeatureName 'Containers-DisposableClientVM' -All -Online |
|
Write-Output 'Windows must be restarted to finish enabling Windows Sandbox' |
|
} catch { |
|
Write-Error 'Windows Sandbox cannot be enabled on your machine. Please ensure your system meets the prerequisites shown at https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview.' |
|
exit 1 |
|
} |
|
} else { |
|
Write-Output 'WindowsSandbox is already installed' |
|
} |
|
} |
|
|
|
Function Close-WindowsSandbox { |
|
$_Sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue |
|
if ($_Sandbox) { |
|
Write-Output 'Windows Sandbox must be closed to modify the base image' |
|
Write-Output 'Closing Windows Sandbox' |
|
$_Sandbox | Stop-Process |
|
Start-Sleep -Seconds 5 |
|
} else { |
|
Write-Output 'Windows Sandbox is not running. Installation will proceed' |
|
} |
|
} |
|
|
|
Function Set-CMServiceStatus() { |
|
Param( |
|
[boolean] $Enabled |
|
) |
|
$_ContainerManagerService = Get-Service -Name 'CmService' |
|
switch ($Enabled) { |
|
$true { |
|
if ($_ContainerManagerService.Status -eq 'Stopped') { |
|
Write-Output 'Starting Container Manager Service' |
|
$_ContainerManagerService | Start-Service |
|
} else { |
|
Write-Output 'Container Manager Service is running' |
|
} |
|
} |
|
$false { |
|
if ($_ContainerManagerService.Status -ne 'Stopped') { |
|
Write-Output 'Stopping Container Manager Service' |
|
$_ContainerManagerService | Stop-Service -Force |
|
} else { |
|
Write-Output 'Container Manager Service is stopped' |
|
} |
|
} |
|
default { |
|
Write-Error "Unknown parameter value - Set-CMServiceStatus $Enabled" |
|
exit 1 |
|
} |
|
} |
|
} |
|
|
|
Function Mount-SandboxImage { |
|
Write-Output 'Attempting to find the virtual hard disk' |
|
$script:BaseImageFolder = Resolve-Path("$ENV:PROGRAMDATA\Microsoft\Windows\Containers\BaseImages") -ErrorAction 'SilentlyContinue' |
|
if ($null -eq $BaseImageFolder) { |
|
Write-Error 'Could not locate BaseImages Folder' |
|
exit 1 |
|
} |
|
$script:GUIDFolder = @((Get-ChildItem $BaseImageFolder -Directory).Where({ $_.Name -match '[a-z0-9]{8}(-[a-z0-9]{4}){3}-[a-z0-9]{12}' }))[0] |
|
if ($null -eq $GUIDFolder) { |
|
Write-Error 'Could not locate Sandbox GUID Folder' |
|
exit 1 |
|
} |
|
$script:BaseLayerVHDX = @((Get-ChildItem $GUIDFolder.FullName -File).Where({ $_.FullName -cmatch 'BaseLayer\.vhdx$' }))[0] |
|
if ($null -eq $BaseLayerVHDX) { |
|
Write-Error 'Could not locate Sandbox Virtual Hard Disk' |
|
exit 1 |
|
} |
|
Write-Output 'Virtual hard disk found. Attempting to mount the disk' |
|
Mount-VHD $BaseLayerVHDX.FullName |
|
$script:VHDXDriveLetter = (Get-VHD $BaseLayerVHDX.FullName | Get-Disk | Get-Partition | Get-Volume).DriveLetter |
|
Write-Output "Virtual hard disk mounted at $VHDXDriveLetter`:\" |
|
} |
|
|
|
Function Dismount-SandboxImage { |
|
Write-Output 'Dismounting the virtual hard disk' |
|
Dismount-VHD $BaseLayerVHDX.FullName |
|
} |
|
|
|
Function Mount-SandboxRegistry { |
|
$_NTUserDatFile = Resolve-Path "$VHDXDriveLetter`:\Files\Users\WDAGUtilityAccount\ntuser.dat" |
|
if ($null -eq $_NTUserDatFile) { |
|
Write-Error 'Unable to locate sandbox user registry' |
|
exit 1 |
|
} |
|
Write-Output 'Loading Sandbox User Registry' |
|
$_GUID = (New-Guid).ToString() |
|
Write-Output "Assigning Temporary User GUID - $_GUID" |
|
$script:SandboxRegKey = "HKEY_Users\$_GUID" |
|
REG LOAD $SandboxRegKey $_NTUserDatFile.Path |
|
Write-Output 'Sandbox User Registry Loaded!' |
|
} |
|
|
|
Function Dismount-SandboxRegistry { |
|
Write-Output 'Unloading Sandbox User Registry' |
|
$null = REG UNLOAD $SandboxRegKey |
|
} |
|
|
|
Function Publish-BinaryFiles { |
|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
|
$WebClient = New-Object System.Net.WebClient |
|
$_WindowsPath = Resolve-Path "$VHDXDriveLetter`:\Files\Windows" |
|
Write-Output 'Fetching Latest Winget CLI Release' |
|
$_LatestUrl = ((Invoke-WebRequest 'https://api.github.com/repos/microsoft/winget-cli/releases' -UseBasicParsing | ConvertFrom-Json).Where({ $_.prerelease -eq $PreRelease }) | Select-Object -First 1).assets.Where({ $_.name -match '^Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle$' }).browser_download_url |
|
Write-Output 'Downloading Winget to Sandbox' |
|
$WebClient.DownloadFile($_LatestUrl, $(Join-Path -Path $_WindowsPath -ChildPath 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle')) |
|
Write-Output 'Downloading VCLibs to Sandbox' |
|
$WebClient.DownloadFile('https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx', $(Join-Path -Path $_WindowsPath -ChildPath 'Microsoft.VCLibs.x64.14.00.Desktop.appx')) |
|
Write-Output 'Fetching Microsoft UI Nuget Package' |
|
$_UILibsZipPath = Join-Path -Path $env:TEMP -ChildPath 'Microsoft.UI.Xaml.2.7.zip' |
|
$WebClient.DownloadFile('https://www.nuget.org/api/v2/package/Microsoft.UI.Xaml/2.7.0', $_UILibsZipPath) |
|
Write-Output 'Extracting Microsoft UI Appx to Sandbox' |
|
Expand-Archive -Path $_UILibsZipPath -DestinationPath $(Join-Path -Path $env:TEMP -ChildPath 'Microsoft.UI.Xaml.2.7') -Force |
|
$_UILibsAppxPath = Join-Path -Path $env:TEMP -ChildPath 'Microsoft.UI.Xaml.2.7\tools\AppX\x64\Release\Microsoft.UI.Xaml.2.7.appx' |
|
Copy-Item -Path $_UILibsAppxPath -Destination $(Join-Path -Path $_WindowsPath -ChildPath 'Microsoft.UI.Xaml.2.7.appx') |
|
|
|
Write-Output 'Writing registry key to install Winget' |
|
$_RunKey = Get-Item Registry::$SandboxRegKey\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -ErrorAction 'SilentlyContinue' |
|
if ($null -eq $_RunKey) { |
|
$_RunKey = New-Item Registry::$SandboxRegKey\SOFTWARE\Microsoft\Windows\CurrentVersion\Run -Force |
|
} |
|
$null = New-ItemProperty -Path $_RunKey.PSPath -Name 'InstallWingetAndDependencies' -Value 'powershell.exe "Add-AppxPackage C:\Windows\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle -DependencyPath "C:\Windows\Microsoft.UI.Xaml.2.7.appx","C:\Windows\Microsoft.VCLibs.x64.14.00.Desktop.appx""' -PropertyType 'String' -Force |
|
$_RunKey.Close() |
|
|
|
Write-Output 'Writing registry key to enable powershell script execution' |
|
$_PowershellKey = Get-Item Registry::$SandboxRegKey\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell -ErrorAction 'SilentlyContinue' |
|
if ($null -eq $_PowershellKey) { |
|
$_PowershellKey = New-Item Registry::$SandboxRegKey\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell -Force |
|
} |
|
$null = New-ItemProperty -Path $_PowershellKey.PSPath -Name 'ExecutionPolicy' -Value 'RemoteSigned' -PropertyType 'String' -Force |
|
$null = New-ItemProperty -Path $_PowershellKey.PSPath -Name 'Path' -Value 'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe' -PropertyType 'String' -Force |
|
$_PowershellKey.Close() |
|
[GC]::Collect() |
|
Write-Output 'Setup complete' |
|
} |
|
|
|
Enable-WindowsSandbox |
|
Close-WindowsSandbox |
|
Set-CMServiceStatus -Enabled $false |
|
Mount-SandboxImage |
|
Mount-SandboxRegistry |
|
Publish-BinaryFiles |
|
Dismount-SandboxRegistry |
|
Dismount-SandboxImage |
|
Set-CMServiceStatus -Enabled $true |
It may be important to mention that the entirety (?) of the Hyper-V feature needs to be installed for this script to work. That was my experience on Windows 10.