Automating the Windows Sandbox

Overview

Windows Sandbox has the following properties:

  • Part of Windows - everything required for this feature ships with Windows 10 Pro and Enterprise. No need to download a VHD!
  • Pristine - every time Windows Sandbox runs, it’s as clean as a brand-new installation of Windows
  • Disposable - nothing persists on the device; everything is discarded after you close the application
  • Secure - uses hardware-based virtualization for kernel isolation, which relies on the Microsoft’s hypervisor to run a separate kernel which isolates Windows Sandbox from the host
  • Efficient - uses integrated kernel scheduler, smart memory management, and virtual GPU

Windows Sandbox has the following pre-requisites.

  • Windows 10 Pro or Enterprise build 18305 or later
  • AMD64 architecture
  • Virtualization capabilities enabled in BIOS
  • At least 4GB of RAM (8GB recommended)
  • At least 1 GB of free disk space (SSD recommended)
  • At least 2 CPU cores (4 cores with hyper-threading recommended)

Note: Before proceeding, you need to enable virtualization, as follows.

  • If you are using a physical machine, ensure virtualization capabilities are enabled in the BIOS.
  • If you are using a virtual machine, enable nested virtualization with this PowerShell cmdlet:
    Set-VMProcessor -VMName <VMName> -ExposeVirtualizationExtensions $true
    

Enable Sandbox

To Enable Windows 10 Sandbox with PowerShell run the following command in an elevated PS and reboot the machine afterwards:

Enable-WindowsOptionalFeature -FeatureName "Containers-DisposableClientVM" -All -Online

The sandbox can be uninstalled with:

Disable-WindowsOptionalFeature -FeatureName "Containers-DisposableClientVM" -Online

Sandbox automation script

# Parse Arguments

Param(
  [Parameter(Mandatory, HelpMessage = "The path for the Manifest.")]
  [String] $Manifest
)

if (-not (Test-Path -Path $Manifest -PathType Leaf)) {
  throw 'The Manifest file does not exist.'
}

# Validate manifest file
# We can't rely on status code until https://github.com/microsoft/winget-cli/issues/312 is solved
$validationResult = winget.exe validate $Manifest
if ($validationResult -like '*Manifest validation failed.*') {
  throw 'Manifest validation failed.'
}

# Check if Windows Sandbox is enabled

if (-Not (Test-Path "$env:windir\System32\WindowsSandbox.exe")) {
  Write-Error -Category NotInstalled -Message @'
Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details:    
https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview
  
You can run the following command in an elevated PowerShell for enabling Windows Sandbox:
Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM'
'@ -ErrorAction Stop
}

# Set dependencies

$desktopAppInstaller = @{
  fileName = 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.appxbundle'
  url      = 'https://github.com/microsoft/winget-cli/releases/download/v0.1.4331-preview/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.appxbundle'
  hash     = 'e1b5aa89c7354dd39db38c2ac33f0455c4529eb4cf77f023028d955122ea8377'
}

$vcLibs = @{
  fileName = 'Microsoft.VCLibs.140.00_14.0.27810.0_x64__8wekyb3d8bbwe.Appx'
  url      = 'https://github.com/felipecassiors/winget-pkgs/raw/da8548d90369eb8f69a4738dc1474caaffb58e12/Tools/SandboxTest_Temp/Microsoft.VCLibs.140.00_14.0.27810.0_x64__8wekyb3d8bbwe.Appx'
  hash     = 'fe660c46a3ff8462d9574902e735687e92eeb835f75ec462a41ef76b54ef13ed'
}

$vcLibsUwp = @{
  fileName = 'Microsoft.VCLibs.140.00.UWPDesktop_14.0.27810.0_x64__8wekyb3d8bbwe.Appx'
  url      = 'https://raw.githubusercontent.com/felipecassiors/winget-pkgs/da8548d90369eb8f69a4738dc1474caaffb58e12/Tools/SandboxTest_Temp/Microsoft.VCLibs.140.00.UWPDesktop_14.0.27810.0_x64__8wekyb3d8bbwe.Appx'
  hash     = '66de9fde9d2ebf18893a890987f35d2d145c18cc5ee0e8ecaa09477dcc13b16b'
}

$dependencies = @($desktopAppInstaller, $vcLibs, $vcLibsUwp)

# Initialize Temp Folder

$tempFolder = Join-Path -Path $PSScriptRoot -ChildPath 'SandboxTest_Temp'

New-Item $tempFolder -ItemType Directory -ea 0 | Out-Null

Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force

Copy-Item -Path $Manifest -Destination $tempFolder

# Download dependencies

$WebClient = New-Object System.Net.WebClient

foreach ($dependency in $dependencies) {
  $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName

  # Only download if the file does not exist, or its hash does not match.
  if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(get-filehash $dependency.file).Hash)) {
    # This downloads the file
    Write-Host "Downloading $($dependency.url) ..."
    try { 
      $WebClient.DownloadFile($dependency.url, $dependency.file) 
    } 
    catch {
      throw "Error downloading $($dependency.url) ."
    }
    if (-not ($dependency.hash -eq $(get-filehash $dependency.file).Hash)) {
      throw 'Hashes do not match, try gain.'
    }
  }
}

# Create Bootstrap script

$manifestFileName = Split-Path $Manifest -Leaf

$bootstrapPs1Content = @"
Set-PSDebug -Trace 1

Add-AppxPackage -Path '$($desktopAppInstaller.fileName)' -DependencyPath '$($vcLibs.fileName)','$($vcLibsUwp.fileName)'

winget install -m '$manifestFileName'
"@

$bootstrapPs1FileName = 'Bootstrap.ps1'
$bootstrapPs1Content | Out-File (Join-Path -Path $tempFolder -ChildPath $bootstrapPs1FileName)

# Create Wsb file

$tempFolderInSandbox = Join-Path -Path 'C:\Users\WDAGUtilityAccount\Desktop' -ChildPath (Split-Path $tempFolder -Leaf)

$sandboxTestWsbContent = @"
<Configuration>
  <MappedFolders>
    <MappedFolder>
      <HostFolder>$tempFolder</HostFolder>
      <ReadOnly>true</ReadOnly>
    </MappedFolder>
  </MappedFolders>
  <LogonCommand>
  <Command>PowerShell Start-Process PowerShell -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -File $bootstrapPs1FileName'</Command>
  </LogonCommand>
</Configuration>
"@

$sandboxTestWsbFileName = 'SandboxTest.wsb'
$sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName
$sandboxTestWsbContent | Out-File $sandboxTestWsbFile

Write-Host 'Starting Windows Sandbox and trying to install the manifest file.'

WindowsSandbox $SandboxTestWsbFile