* Create SandboxTest script * Add missing appx dependencies * Clear temp folder * Validate manifest before pushing to sandbox * Fix invalid character in Sandbox.ps1 The dash character in -ExecutionPolicy was a special character which broke the command. * Download dependencies on the fly * remove vscode launch file * Add newline in gitignore * Fix when there is no temp folder * Add Test in Sandbox to README * Add some comments and enhance some messages * Properly accept mandatory parameter * Check if Windows Sandbox exist or not * Fix Sandbox install snippet and typo * Fix wrong conflict resolution in README * Upgrade WinGet to v0.1.41821-preview * Rewrite the whole script Adding some cool features such as: - Specify folder to mount (default to current) - Make Manifest optional, so we can use the script to just start the Sandbox and install WinGet - Add optional Script parameter, to specify which script block to run after all the steps inside the Sandbox - Automatically refresh environment variables after installing the Manifest, and leave the function Update-Environment available for the users - Use a temporary folder on system's temp directory, thus deleting the .gitignore - Make Powershell open maximized and without splash messages - Improve overall UX both in local machine and in the Sandbox - Close Sandbox automatically if needed - Upgrade WinGet to latest version Most of the refactor came from my PR at https://github.com/chocolatey-community/chocolatey-coreteampackages/pull/1512 * Add tip for Update-Environment * Fix relative paths on map folder * Copy the manifest instead of installing from the source * Fix call without manifest param and enhance logs * Use VCLibs UWPDesktop from official source and update winget * Add some missing new lines * Fetch latest WinGet release automatically * Add -SkipManifestValidation switch and fix checksum * Fix TLS issues, handle PowerShell 7, remove unneeded VCLibs dep and fix manifest as folder * Restore file/folder check for manifest * Change checksum output filename * Update Update-EnvironmentVariables function Co-authored-by: chausner <chausner@users.noreply.github.com>
		
			
				
	
	
		
			285 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PowerShell
		
	
	
	
	
	
			
		
		
	
	
			285 lines
		
	
	
		
			8.0 KiB
		
	
	
	
		
			PowerShell
		
	
	
	
	
	
# Parse arguments
 | 
						|
 | 
						|
Param(
 | 
						|
  [Parameter(Position = 0, HelpMessage = "The Manifest to install in the Sandbox.")]
 | 
						|
  [String] $Manifest,
 | 
						|
  [Parameter(Position = 1, HelpMessage = "The script to run in the Sandbox.")]
 | 
						|
  [ScriptBlock] $Script,
 | 
						|
  [Parameter(HelpMessage = "The folder to map in the Sandbox.")]
 | 
						|
  [String] $MapFolder = $pwd,
 | 
						|
  [switch] $SkipManifestValidation
 | 
						|
)
 | 
						|
 | 
						|
$ErrorActionPreference = "Stop"
 | 
						|
 | 
						|
$mapFolder = (Resolve-Path -Path $MapFolder).Path
 | 
						|
 | 
						|
if (-Not (Test-Path -Path $mapFolder -PathType Container)) {
 | 
						|
  Write-Error -Category InvalidArgument -Message 'The provided MapFolder is not a folder.'
 | 
						|
}
 | 
						|
 | 
						|
# Validate manifest file
 | 
						|
 | 
						|
if (-Not $SkipManifestValidation -And -Not [String]::IsNullOrWhiteSpace($Manifest)) {
 | 
						|
  Write-Host '--> Validating Manifest'
 | 
						|
 | 
						|
  if (-Not (Test-Path -Path $Manifest)) {
 | 
						|
    throw 'The Manifest does not exist.'
 | 
						|
  }
 | 
						|
 | 
						|
  winget.exe validate $Manifest
 | 
						|
  if (-Not $?) {
 | 
						|
    throw 'Manifest validation failed.'
 | 
						|
  }
 | 
						|
 | 
						|
  Write-Host
 | 
						|
}
 | 
						|
 | 
						|
# Check if Windows Sandbox is enabled
 | 
						|
 | 
						|
if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) {
 | 
						|
  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'
 | 
						|
'@
 | 
						|
}
 | 
						|
 | 
						|
# Close Windows Sandbox
 | 
						|
 | 
						|
$sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue
 | 
						|
if ($sandbox) {
 | 
						|
  Write-Host '--> Closing Windows Sandbox'
 | 
						|
 | 
						|
  $sandbox | Stop-Process
 | 
						|
  Start-Sleep -Seconds 5
 | 
						|
 | 
						|
  Write-Host
 | 
						|
}
 | 
						|
Remove-Variable sandbox
 | 
						|
 | 
						|
# Initialize Temp Folder
 | 
						|
 | 
						|
$tempFolderName = 'SandboxTest'
 | 
						|
$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName
 | 
						|
 | 
						|
New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null
 | 
						|
 | 
						|
# Set dependencies
 | 
						|
 | 
						|
$apiLatestUrl = 'https://api.github.com/repos/microsoft/winget-cli/releases/latest'
 | 
						|
 | 
						|
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
 | 
						|
$WebClient = New-Object System.Net.WebClient
 | 
						|
 | 
						|
function Get-LatestUrl {
 | 
						|
  ((Invoke-WebRequest $apiLatestUrl -UseBasicParsing | ConvertFrom-Json).assets | Where-Object { $_.name -match '^Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.appxbundle$' }).browser_download_url
 | 
						|
}
 | 
						|
 | 
						|
function Get-LatestHash {
 | 
						|
  $shaUrl = ((Invoke-WebRequest $apiLatestUrl -UseBasicParsing | ConvertFrom-Json).assets | Where-Object { $_.name -match '^Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.txt$' }).browser_download_url
 | 
						|
 | 
						|
  $shaFile = Join-Path -Path $tempFolder -ChildPath 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.txt'
 | 
						|
  $WebClient.DownloadFile($shaUrl, $shaFile)
 | 
						|
 | 
						|
  Get-Content $shaFile
 | 
						|
}
 | 
						|
 | 
						|
# Hide the progress bar of Invoke-WebRequest
 | 
						|
$oldProgressPreference = $ProgressPreference
 | 
						|
$ProgressPreference = 'SilentlyContinue'
 | 
						|
 | 
						|
$desktopAppInstaller = @{
 | 
						|
  fileName = 'Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.appxbundle'
 | 
						|
  url      = $(Get-LatestUrl)
 | 
						|
  hash     = $(Get-LatestHash)
 | 
						|
}
 | 
						|
 | 
						|
$ProgressPreference = $oldProgressPreference
 | 
						|
 | 
						|
$vcLibsUwp = @{
 | 
						|
  fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx'
 | 
						|
  url      = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx'
 | 
						|
  hash     = '6602159c341bafea747d0edf15669ac72df8817299fbfaa90469909e06794256'
 | 
						|
}
 | 
						|
 | 
						|
$dependencies = @($desktopAppInstaller, $vcLibsUwp)
 | 
						|
 | 
						|
# Clean temp directory
 | 
						|
 | 
						|
Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse
 | 
						|
 | 
						|
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
 | 
						|
  Copy-Item -Path $Manifest -Recurse -Destination $tempFolder
 | 
						|
}
 | 
						|
 | 
						|
# Download dependencies
 | 
						|
 | 
						|
Write-Host '--> Checking dependencies'
 | 
						|
 | 
						|
$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop'
 | 
						|
 | 
						|
foreach ($dependency in $dependencies) {
 | 
						|
  $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName
 | 
						|
  $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -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)) {
 | 
						|
    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.'
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Write-Host
 | 
						|
 | 
						|
# Create Bootstrap script
 | 
						|
 | 
						|
# See: https://stackoverflow.com/a/22670892/12156188
 | 
						|
$bootstrapPs1Content = @'
 | 
						|
function Update-EnvironmentVariables {
 | 
						|
  foreach($level in "Machine","User") {
 | 
						|
    [Environment]::GetEnvironmentVariables($level).GetEnumerator() | % {
 | 
						|
        # For Path variables, append the new values, if they're not already in there
 | 
						|
        if($_.Name -match 'Path$') {
 | 
						|
          $_.Value = ($((Get-Content "Env:$($_.Name)") + ";$($_.Value)") -split ';' | Select -unique) -join ';'
 | 
						|
        }
 | 
						|
        $_
 | 
						|
    } | Set-Content -Path { "Env:$($_.Name)" }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
'@
 | 
						|
 | 
						|
$bootstrapPs1Content += @"
 | 
						|
Write-Host @'
 | 
						|
--> Installing WinGet
 | 
						|
 | 
						|
'@
 | 
						|
Add-AppxPackage -Path '$($desktopAppInstaller.pathInSandbox)' -DependencyPath '$($vcLibsUwp.pathInSandbox)'
 | 
						|
 | 
						|
Write-Host @'
 | 
						|
 | 
						|
Tip: you can type 'Update-EnvironmentVariables' to update your environment variables, such as after installing a new software.
 | 
						|
 | 
						|
'@
 | 
						|
 | 
						|
 | 
						|
"@
 | 
						|
 | 
						|
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
 | 
						|
  $manifestFileName = Split-Path $Manifest -Leaf
 | 
						|
  $manifestPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $manifestFileName)
 | 
						|
 | 
						|
  $bootstrapPs1Content += @"
 | 
						|
Write-Host @'
 | 
						|
 | 
						|
--> Installing the Manifest $manifestFileName
 | 
						|
 | 
						|
'@
 | 
						|
winget install -m '$manifestPathInSandbox'
 | 
						|
 | 
						|
Write-Host @'
 | 
						|
 | 
						|
--> Refreshing environment variables
 | 
						|
'@
 | 
						|
Update-EnvironmentVariables
 | 
						|
 | 
						|
 | 
						|
"@
 | 
						|
}
 | 
						|
 | 
						|
if (-Not [String]::IsNullOrWhiteSpace($Script)) {
 | 
						|
  $bootstrapPs1Content += @"
 | 
						|
Write-Host @'
 | 
						|
 | 
						|
--> Running the following script:
 | 
						|
 | 
						|
{
 | 
						|
$Script
 | 
						|
}
 | 
						|
 | 
						|
'@
 | 
						|
 | 
						|
$Script
 | 
						|
 | 
						|
 | 
						|
"@
 | 
						|
}
 | 
						|
 | 
						|
$bootstrapPs1Content += @"
 | 
						|
Write-Host
 | 
						|
"@
 | 
						|
 | 
						|
$bootstrapPs1FileName = 'Bootstrap.ps1'
 | 
						|
$bootstrapPs1Content | Out-File (Join-Path -Path $tempFolder -ChildPath $bootstrapPs1FileName)
 | 
						|
 | 
						|
# Create Wsb file
 | 
						|
 | 
						|
$bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName)
 | 
						|
$mapFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Split-Path -Path $mapFolder -Leaf)
 | 
						|
 | 
						|
$sandboxTestWsbContent = @"
 | 
						|
<Configuration>
 | 
						|
  <MappedFolders>
 | 
						|
    <MappedFolder>
 | 
						|
      <HostFolder>$tempFolder</HostFolder>
 | 
						|
      <ReadOnly>true</ReadOnly>
 | 
						|
    </MappedFolder>
 | 
						|
    <MappedFolder>
 | 
						|
      <HostFolder>$mapFolder</HostFolder>
 | 
						|
    </MappedFolder>
 | 
						|
  </MappedFolders>
 | 
						|
  <LogonCommand>
 | 
						|
  <Command>PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$mapFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox'</Command>
 | 
						|
  </LogonCommand>
 | 
						|
</Configuration>
 | 
						|
"@
 | 
						|
 | 
						|
$sandboxTestWsbFileName = 'SandboxTest.wsb'
 | 
						|
$sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName
 | 
						|
$sandboxTestWsbContent | Out-File $sandboxTestWsbFile
 | 
						|
 | 
						|
Write-Host @"
 | 
						|
--> Starting Windows Sandbox, and:
 | 
						|
    - Mounting the following directories:
 | 
						|
      - $tempFolder as read-only
 | 
						|
      - $mapFolder as read-and-write
 | 
						|
    - Installing WinGet
 | 
						|
"@
 | 
						|
 | 
						|
if (-Not [String]::IsNullOrWhiteSpace($Manifest)) {
 | 
						|
  Write-Host @"
 | 
						|
    - Installing the Manifest $manifestFileName
 | 
						|
    - Refreshing environment variables
 | 
						|
"@
 | 
						|
}
 | 
						|
 | 
						|
if (-Not [String]::IsNullOrWhiteSpace($Script)) {
 | 
						|
  Write-Host @"
 | 
						|
    - Running the following script:
 | 
						|
 | 
						|
{
 | 
						|
$Script
 | 
						|
}
 | 
						|
"@
 | 
						|
}
 | 
						|
 | 
						|
Write-Host
 | 
						|
 | 
						|
WindowsSandbox $SandboxTestWsbFile
 |