Files
winget-pkgs/Tools/SandboxTest.ps1
Felipe Santos 9619e1aa83 Create SandboxTest script (#827)
* 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>
2021-05-03 11:36:36 -07:00

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