Skip to content

Instantly share code, notes, and snippets.

@Trenly
Last active March 13, 2024 09:04
Show Gist options
  • Star 17 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Trenly/3e8ba9a9498c6cc12a9bb25e4179a98c to your computer and use it in GitHub Desktop.
Save Trenly/3e8ba9a9498c6cc12a9bb25e4179a98c to your computer and use it in GitHub Desktop.
Install Winget to the Windows Sandbox Base Image

This powershell script modifies the Base Image, or the Virtual Hard Disk, which the Windows Sandbox launches upon startup. It will copy the required files to the sandbox and add a registry key which will install them upon startup. By default the script will install the latest stable release of Winget. You can specify to use the latest pre-release with the -PreRelease switch.

When a new version of Winget is released, run this script again to update the installation in the sandbox to the latest version

#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 'Container Manager Service'
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
} 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
@doggy8088
Copy link

The $_ContainerManagerService = Get-Service -Name 'Container Manager Service' must change to $_ContainerManagerService = Get-Service -Name 'CmService' in my Windows 10 PowerShell environment.

@doggy8088
Copy link

May I ask what's the Temporary User for?

@isaacrlevin
Copy link

Tried this and get

Could not locate Sandbox Virtual Hard Disk

Looking at the script, it is looking for a vhdx file. But there isn't one in my case. My BaseContainer folder has directories.

@elisimpson
Copy link

The $_ContainerManagerService = Get-Service -Name 'Container Manager Service' must change to $_ContainerManagerService = Get-Service -Name 'CmService' in my Windows 10 PowerShell environment.

Indeed, it looks like 'CmService' is the 'name' and 'Container Manager Service' is the 'description'

@Dragon1573
Copy link

Use the fork from @doggy8088 , but there're still errors on my Windows 11 device.

PS D:\Download\Aria2> gsudo .\WingetSandbox.ps1
WindowsSandbox is already installed
Windows Sandbox must be closed to modify the base image
Closing Windows Sandbox
Stopping Container Manager Service
Stop-Service : 无法停止服务“容器管理器服务 (CmService)”,因为具有依赖它的服务。只有在设置了 Force 标志时才能停止该服务。
所在位置 D:\Download\Aria2\WingetSandbox.ps1:52 字符: 45
+                 $_ContainerManagerService | Stop-Service
+                                             ~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (System.ServiceProcess.ServiceController:ServiceController) [Stop-Service],ServiceCommandException
    + FullyQualifiedErrorId : ServiceHasDependentServices,Microsoft.PowerShell.Commands.StopServiceCommand

Attempting to find the virtual hard disk
Mount-SandboxImage : Could not locate Sandbox Virtual Hard Disk
所在位置 D:\Download\Aria2\WingetSandbox.ps1:152 字符: 1
+ Mount-SandboxImage
+ ~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Mount-SandboxImage

@doggy8088
Copy link

@Dragon1573 I changed a bit. Add -Force to the Stop-Service cmdlet. Please try again!

@Dragon1573
Copy link

The first error is fixed, but the script still unable to locate the VHD of Sandbox ...

Repository  λ gsudo .\WingetSandbox.ps1
WindowsSandbox is already installed
Windows Sandbox must be closed to modify the base image
Closing Windows Sandbox
Stopping Container Manager Service
Attempting to find the virtual hard disk
Write-Error: D:\Repository\WingetSandbox.ps1:152
Line |
 152 |  Mount-SandboxImage
     |  ~~~~~~~~~~~~~~~~~~
     | Could not locate Sandbox Virtual Hard Disk

@Slluxx
Copy link

Slluxx commented Feb 18, 2023

does not work.
probably needs hyper-v services installed for Mount-VHD but even if, it "cant resolve computer 'WIN-2OJA0BNFHE0'".

@brenocrs
Copy link

brenocrs commented Mar 1, 2023

HI all

@Slluxx , i was able to use the script after activating the powershell hyper-v management usgin the command bellow

Enable-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Management-PowerShell' -All -Online

@Slluxx and @Dragon1573 , Could you guys test the script after activate this feature ? this will not activate hyper-v as far as i know, it will only activate the script fot mount-vhd and etc...

@Trenly if this solv the problem, could you implement in the code ?

@Dragon1573
Copy link

I was able to use the script after activating the PowerShell Hyper-V management using the command bellow

Enable-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Management-PowerShell' -All -Online

@brenocrs I'm unable to run the above command, but I can't figure out what was wrong …

Environment

  • Windows 11 Professional 22H2 (22621.1265)
  • Windows Feature Experience Pack 1000.22638.1000.0
  • gsudo v2.0.4 (Branch.tags-v2.0.4.Sha.506efa024af0cef6e4b0cfec42e0c8c5d0b1472c)
  • Powershell v7.3.3

Run logs

PS> sudo Enable-WindowsOptionalFeature -FeatureName 'Microsoft-Hyper-V-Management-PowerShell' -All -Online
Enable-WindowsOptionalFeature: 没有注册类

Windows Functions

As the following screenshot, [Hyper-V] > [Hyper-V Management Tools] > [Hyper-V Modules for Windows Powershell] is enabled.

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment