-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create SandboxTest script #827
Changes from all commits
f7ec04d
da8548d
e8ca1c4
ee50cb8
eea0fe4
8efaeac
8f90038
ef769c5
22c6233
42e8651
d72fcac
c3a12a0
c825b3e
be375f9
f799acb
7074857
b20c3f2
5c9d1d9
799bdde
8cb5a7a
73fc9a0
e875fa9
f72b42a
3693035
3060469
c3cccd8
b1319ef
22d04a4
7228aa7
553a646
dd7408a
d438e94
a85396a
b607189
cb06ad1
498374a
76b44aa
25df74c
ac75e09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,284 @@ | ||
# Parse arguments | ||
|
||
Param( | ||
[Parameter(Position = 0, HelpMessage = "The Manifest to install in the Sandbox.")] | ||
[String] $Manifest, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be declared mandatory? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The purpose of the script is to be flexible. Some times you just want to open sandbox, have winget installed, the current folder mounted and mess with it freely, this allows: > .\Tools\SandboxTest.ps1 # works
> .\Tools\SandboxTest.ps1 -Script { winget install git; Update-Environment; git --version } # also works, no manifest There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, I didn't realize that was an intended purpose, makes sense |
||
[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)) { | ||
felipecrs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Write-Host '--> Validating Manifest' | ||
|
||
if (-Not (Test-Path -Path $Manifest)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this should have -PathType Container now too? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depends on the other conversation also. |
||
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 | ||
felipecrs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
$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 = @{ | ||
felipecrs marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aside from making this mandatory in the params, I'd check this once up top and fail out if it's whitespace, then you don't have to keep checking it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also depends on the other conversation, I guess. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving manifest optional is fine |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept passing the manifest version file itself and validation kept failing, took me a while to realize this parameter expects the manifest version directory. I'd clarify that here and in the param help
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@palenshus Well, my goal was to support the two kinds of manifests to keep backwards compatibility (it was files previously). Since you said you're not from the winget-cli team, it may be better to ask them about it.
@denelon, any suggestion?
If yet we decide to keep accepting both files and folders, the help can be surely enhanced to clarify that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you have a singleton manifest you can point to the file, but the directory option works for both cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I was only trying it on multi-file schemas. I'd just make it take a directory in both cases for consistency. Winget will validate the directory and ensure that if the manifestType is singleton, then there's only the one file in there