diff --git a/Installer Scripts/Install-Winget.ps1 b/Installer Scripts/Install-Winget.ps1 index 8124c02..89090ea 100644 --- a/Installer Scripts/Install-Winget.ps1 +++ b/Installer Scripts/Install-Winget.ps1 @@ -3,11 +3,24 @@ # Author: ThioJoe # Repo Url: https://github.com/ThioJoe/Windows-Sandbox-Tools -# Last Updated: August 4, 2025 +# Last Updated: February 12, 2026 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 - ) + # 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 + [switch]$removeMsStoreAsSource = $false, + + # Optional path to a local directory containing the installation files. If provided, the download steps will be skipped. + # - Just copy the entire "Winget Install" folder with the files the script normally downloads, and put it in your mounted folder. + # - Make sure to use the mounted path from the perspective of within the sandbox. + [string]$ExistingInstallerFilesPath +) + +# --- Parameter Usage Examples --- +# Standard run (Download & Install): +# .\Install-Winget.ps1 +# +# Install from existing files instead of downloading: +# .\Install-Winget.ps1 -ExistingInstallerFilesPath "C:\Users\WDAGUtilityAccount\Desktop\HostShared\Winget Install" function Get-LatestRelease { param( @@ -77,25 +90,33 @@ function Install-WingetDependencies { } } +# --- Define Working Directory --- +$userDownloadsFolder = (New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path +$subfolderName = "Winget Install" +$msixName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" + +if ($ExistingInstallerFilesPath) { + if (Test-Path -Path $ExistingInstallerFilesPath) { + $workingDir = $ExistingInstallerFilesPath + Write-Host "Using local source path: $workingDir" -ForegroundColor Yellow + } else { + Write-Error "The specified local source path does not exist: $ExistingInstallerFilesPath" + return + } +} else { + # Combine them to create the full working directory path + $workingDir = Join-Path -Path $userDownloadsFolder -ChildPath $subfolderName + + # Create the directory if it doesn't exist + if (-not (Test-Path -Path $workingDir)) { + New-Item -Path $workingDir -ItemType Directory -Force | Out-Null + } +} + # 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 - +# --- Determine Architecture --- # Figure out the OS architecture using environment variable $procArch = $env:PROCESSOR_ARCHITECTURE switch -Wildcard ($procArch) { @@ -109,49 +130,85 @@ switch -Wildcard ($procArch) { } } -# Download the dependencies zip -$depsZipName = "DesktopAppInstaller_Dependencies.zip" -$depsZipUrl = Get-AssetUrl -release $latestRelease -assetName $depsZipName +# --- Download Steps (Skipped if using existing files) --- +if (-not $ExistingInstallerFilesPath) { + $latestRelease = Get-LatestRelease + if (-not $latestRelease) { Write-Error "Could not retrieve the latest release. Exiting."; return; } -# 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 + $latestTag = $latestRelease.tag_name + Write-Host "Latest winget version tag is: $latestTag" -if ($depsZipUrl) { - Write-Host "Downloading $depsZipName..." - $depsZipPath = Join-Path $downloadPath $depsZipName - Invoke-WebRequest -Uri $depsZipUrl -OutFile $depsZipPath + # Download the MSIX bundle + $msixUrl = Get-AssetUrl -release $latestRelease -assetName $msixName + if (-not $msixUrl) { Write-Error "Could not find $msixName in the latest release assets."; return; } - # Remove existing Dependencies folder and expand the zip - if (Test-Path $topDepsFolder) { Remove-Item -Path $topDepsFolder -Recurse -Force } - - # Use Expand-Archive cmdlet by default because it's safe for constrained language mode. Fall back to .NET assembly if it fails. - try { - Expand-Archive -LiteralPath $depsZipPath -DestinationPath $topDepsFolder -Force -ErrorAction Stop - } - catch { - Write-Warning "Standard extraction failed, attempting .NET fallback. The error was: $($_.Exception.Message)" - # Fallback using .NET System.IO.Compression (Fixes issues in non-EN Windows Sandbox) - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::ExtractToDirectory($depsZipPath, $topDepsFolder) - } -} -else { Write-Warning "No $depsZipName found in $latestTag, skipping dependency download."; } + Write-Host "Downloading $msixName..." + $msixPath = Join-Path $workingDir $msixName + Invoke-WebRequest -Uri $msixUrl -OutFile $msixPath + + # 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 $workingDir "Dependencies" + + if ($depsZipUrl) { + Write-Host "Downloading $depsZipName..." + $depsZipPath = Join-Path $workingDir $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 } + + # Use Expand-Archive cmdlet by default because it's safe for constrained language mode. Fall back to .NET assembly if it fails. + try { + Expand-Archive -LiteralPath $depsZipPath -DestinationPath $topDepsFolder -Force -ErrorAction Stop + } + catch { + Write-Warning "Standard extraction failed, attempting .NET fallback. The error was: $($_.Exception.Message)" + # Fallback using .NET System.IO.Compression (Fixes issues in non-EN Windows Sandbox) + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::ExtractToDirectory($depsZipPath, $topDepsFolder) + } + + # Cleanup the zip file + if (Test-Path $depsZipPath) { + Remove-Item -Path $depsZipPath -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 +# --- Installation Steps --- + +# Define paths based on working directory +$msixPath = Join-Path $workingDir $msixName +$topDepsFolder = Join-Path $workingDir "Dependencies" +$depsFolder = Join-Path $topDepsFolder $arch + +# Install Dependencies if (Test-Path $depsFolder) { Install-WingetDependencies -depsFolder $depsFolder } else { - Write-Warning "No architecture-specific dependencies found at $depsFolder" + if ($ExistingInstallerFilesPath) { + Write-Error "Dependencies folder not found at: $depsFolder`nEnsure the 'Dependencies' folder is present in your source directory." + } else { + Write-Warning "No architecture-specific dependencies found at $depsFolder" + } } -# Finally, install the winget MSIX bundle -Write-Host "Installing $msixName..." -Add-AppxPackage -Path $msixPath +# Install Winget MSIX bundle +if (Test-Path $msixPath) { + Write-Host "Installing $msixName..." + Add-AppxPackage -Path $msixPath +} else { + Write-Error "Winget package not found at: $msixPath" + return +} # Remove msstore source if set to do so if ($removeMsStoreAsSource.IsPresent) { @@ -164,4 +221,4 @@ if ($removeMsStoreAsSource.IsPresent) { } else { # Automatically accept source agreements to avoid prompts. Mostly applies to msstore. winget list --accept-source-agreements | Out-Null -} +} \ No newline at end of file