mirror-Windows-Sandbox-Tools/Installer Scripts/Install-Winget.ps1

160 lines
5.9 KiB
PowerShell

# This script will install Winget from within the Windows Sandbox
# It fetches the necessary files and dependencies from Microsoft's winget-cli, and installs them
# Author: ThioJoe
# Repo Url: https://github.com/ThioJoe/Windows-Sandbox-Tools
# Last Updated: August 4, 2025
param(
[switch]$removeMsStoreAsSource = $false # If switch is included, it will remove the 'msstore' source after installing winget, which doesn't work with Sandbox, unless the Microsoft Store is also installed
)
function Get-LatestRelease {
param(
[string]$repoOwner = 'microsoft',
[string]$repoName = 'winget-cli'
)
try {
$releasesUrl = "https://api.github.com/repos/$repoOwner/$repoName/releases"
$releases = Invoke-RestMethod -Uri $releasesUrl -UseBasicParsing
} catch {
Write-Error "Failed to fetch releases from GitHub API: $($_.Exception.Message)"
return $null
}
if (-not $releases) { Write-Error "No releases found for $repoOwner/$repoName."; return $null; }
# Pick the top entry once sorted by published_at descending
$latestRelease = $releases | Sort-Object -Property published_at -Descending | Select-Object -First 1
return $latestRelease
}
function Get-AssetUrl {
param(
[Parameter(Mandatory=$true)]
$release,
[Parameter(Mandatory=$true)]
[string]$assetName
)
if ($release.assets -and $release.assets.Count -gt 0) {
$asset = $release.assets | Where-Object { $_.name -eq $assetName }
if ($asset) {
return $asset.browser_download_url
}
}
return $null
}
function Install-WingetDependencies {
param([string]$depsFolder)
# Look for DesktopAppInstaller_Dependencies.json to determine explicit install order
$jsonFile = Join-Path $depsFolder "DesktopAppInstaller_Dependencies.json"
if (Test-Path $jsonFile) {
Write-Host "Installing dependencies based on DesktopAppInstaller_Dependencies.json"
$jsonContent = Get-Content $jsonFile -Raw | ConvertFrom-Json
$dependencies = $jsonContent.Dependencies
foreach ($dep in $dependencies) {
# For example: "Microsoft.VCLibs.140.00.UWPDesktop" + "14.0.33728.0"
$matchingFiles = Get-ChildItem -Path $depsFolder -Filter *.appx -Recurse |
Where-Object { $_.Name -like "*$($dep.Name)*" -and $_.Name -like "*$($dep.Version)*" }
foreach ($file in $matchingFiles) {
Write-Host "Installing dependency: $($file.Name)"
Add-AppxPackage -Path $file.FullName
}
}
}
else {
# If the JSON doesn't exist, install all .appx in the folder
Write-Warning "No DesktopAppInstaller_Dependencies.json found, installing all .appx in $depsFolder"
foreach ($appxFile in Get-ChildItem $depsFolder -Filter *.appx -Recurse) {
Write-Host "Installing: $($appxFile.Name)"
Add-AppxPackage -Path $appxFile.FullName
}
}
}
# Prevents progress bar from showing (often speeds downloads)
$ProgressPreference = 'SilentlyContinue'
$downloadPath = Join-Path $env:USERPROFILE "Downloads"
$latestRelease = Get-LatestRelease
if (-not $latestRelease) { Write-Error "Could not retrieve the latest release. Exiting."; return; }
$latestTag = $latestRelease.tag_name
Write-Host "Latest winget version tag is: $latestTag"
# Download the MSIX bundle
$msixName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"
$msixUrl = Get-AssetUrl -release $latestRelease -assetName $msixName
if (-not $msixUrl) { Write-Error "Could not find $msixName in the latest release assets."; return; }
Write-Host "Downloading $msixName..."
$msixPath = Join-Path $downloadPath $msixName
Invoke-WebRequest -Uri $msixUrl -OutFile $msixPath
# Figure out the OS architecture using environment variable
$procArch = $env:PROCESSOR_ARCHITECTURE
switch -Wildcard ($procArch) {
"AMD64" { $arch = "x64" }
"x86" { $arch = "x86" }
"*ARM64*" { $arch = "arm64" }
"*ARM*" { $arch = "arm" }
default {
$arch = "x64"
Write-Warning "Unrecognized architecture: $procArch. Defaulting to x64."
}
}
# Download the dependencies zip
$depsZipName = "DesktopAppInstaller_Dependencies.zip"
$depsZipUrl = Get-AssetUrl -release $latestRelease -assetName $depsZipName
# We'll expand to a base 'Dependencies' folder
$topDepsFolder = Join-Path $downloadPath "Dependencies"
# Then pick the sub-folder for the architecture
$depsFolder = Join-Path $topDepsFolder $arch
if ($depsZipUrl) {
Write-Host "Downloading $depsZipName..."
$depsZipPath = Join-Path $downloadPath $depsZipName
Invoke-WebRequest -Uri $depsZipUrl -OutFile $depsZipPath
# Remove existing Dependencies folder and expand the zip
if (Test-Path $topDepsFolder) { Remove-Item -Path $topDepsFolder -Recurse -Force }
Expand-Archive -LiteralPath $depsZipPath -DestinationPath $topDepsFolder -Force
}
else { Write-Warning "No $depsZipName found in $latestTag, skipping dependency download."; }
# Restore progress preference
$ProgressPreference = 'Continue'
# If dependencies exist for this architecture, install them
if (Test-Path $depsFolder) {
Install-WingetDependencies -depsFolder $depsFolder
} else {
Write-Warning "No architecture-specific dependencies found at $depsFolder"
}
# Finally, install the winget MSIX bundle
Write-Host "Installing $msixName..."
Add-AppxPackage -Path $msixPath
# Remove msstore source if set to do so
if ($removeMsStoreAsSource.IsPresent) {
Write-Host "Attempting to remove 'msstore' source from winget..."
try {
winget source remove -n msstore --ignore-warnings
} catch {
Write-Warning "An error occurred while trying to execute 'winget source remove msstore': $($_.Exception.Message)"
}
} else {
# Automatically accept source agreements to avoid prompts. Mostly applies to msstore.
winget list --accept-source-agreements | Out-Null
}