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>
This commit is contained in:
Felipe Santos
2021-05-03 15:36:36 -03:00
committed by GitHub
parent b694d2ea48
commit 9619e1aa83
2 changed files with 294 additions and 0 deletions

View File

@@ -35,10 +35,20 @@ Although the Windows Package Manager YAML Generator can create YAML files with m
## Test your manifest ## Test your manifest
Now that you have authored your manifest, you should make sure it works as expected. Now that you have authored your manifest, you should make sure it works as expected.
### Locally
1) Verify the syntax. You can do that by typing the following command: `winget validate <manifest>` 1) Verify the syntax. You can do that by typing the following command: `winget validate <manifest>`
2) Test the install. You can do that by installing the manifest: `winget install -m <manifest>` 2) Test the install. You can do that by installing the manifest: `winget install -m <manifest>`
For more details, see [packages](https://docs.microsoft.com/windows/package-manager/package). For more details, see [packages](https://docs.microsoft.com/windows/package-manager/package).
### In Windows Sandbox
You can use the [`Tools\SandboxTest.ps1`](Tools/SandboxTest.ps1) script for testing a manifest installation in [Windows Sandbox](https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview). The manifest will be also validated.
Just provide the path to manifest as parameter:
```powershell
.\Tools\SandboxTest.ps1 <path-to-manifest>
```
## Submit your PR ## Submit your PR
With the manifest verified, you will need to submit a PR. Your manifest should be located in the folder path matching `manifests\<first lower case letter of publisher>\<publisher>\<package>\<version>.yaml` With the manifest verified, you will need to submit a PR. Your manifest should be located in the folder path matching `manifests\<first lower case letter of publisher>\<publisher>\<package>\<version>.yaml`

284
Tools/SandboxTest.ps1 Normal file
View File

@@ -0,0 +1,284 @@
# 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