* 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
|