Merge branch 'ThioJoe:main' into patch-1
commit
20dfc1dcec
|
|
@ -1,39 +0,0 @@
|
|||
# URLs for the latest Visual C++ Redistributables
|
||||
$urls = @(
|
||||
"https://aka.ms/vs/17/release/vc_redist.x86.exe",
|
||||
"https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
)
|
||||
if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') {
|
||||
$urls += "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
|
||||
}
|
||||
|
||||
# Directory to save the downloads
|
||||
$downloadPath = "$env:TEMP"
|
||||
|
||||
# To improve download performance, the progress bar is suppressed. [2, 6]
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
foreach ($url in $urls) {
|
||||
$fileName = $url.Split('/')[-1]
|
||||
$filePath = Join-Path $downloadPath $fileName
|
||||
|
||||
Write-Host "Downloading $fileName..."
|
||||
# Download the file without a progress bar [1, 4]
|
||||
Invoke-WebRequest -Uri $url -OutFile $filePath
|
||||
|
||||
if (Test-Path $filePath) {
|
||||
Write-Host "Installing $fileName..."
|
||||
# Silently install the redistributable and wait for it to complete [3, 5, 9]
|
||||
Start-Process -FilePath $filePath -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Write-Host "$fileName has been installed."
|
||||
# Optional: Remove the installer after installation
|
||||
# Remove-Item -Path $filePath
|
||||
} else {
|
||||
Write-Host "Error: Failed to download $fileName."
|
||||
}
|
||||
}
|
||||
|
||||
# Restore the default progress preference
|
||||
$ProgressPreference = 'Continue'
|
||||
|
||||
Write-Host "Script execution finished."
|
||||
|
|
@ -4,17 +4,40 @@
|
|||
|
||||
# Author: ThioJoe
|
||||
# Repo Url: https://github.com/ThioJoe/Windows-Sandbox-Tools
|
||||
# Last Updated: August 10, 2025
|
||||
# Last Updated: February 12, 2026
|
||||
|
||||
param(
|
||||
# Optional switch to output the generated XML files to the working directory
|
||||
[switch]$debugSaveFiles,
|
||||
|
||||
# Optional switch to skip the installation of Microsoft Store, but still download the files
|
||||
[switch]$noInstall,
|
||||
|
||||
# Optional switch to skip the download and install, but still show the packages found
|
||||
[switch]$noDownload
|
||||
[switch]$noDownload,
|
||||
|
||||
# Optional path to a local directory containing the installation files. If provided, the download steps will be skipped.
|
||||
# - Just copy the entire "MSStore Install" folder with the files the script normally downloads, and put it in your mounted folder to avoid having to re-download it.
|
||||
# - You can find the "MSStore Install" folder in the "Downloads" 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-Microsoft-Store.ps1
|
||||
#
|
||||
# Install from existing files instead of downloading:
|
||||
# .\Install-Microsoft-Store.ps1 -ExistingInstallerFilesPath "C:\Users\WDAGUtilityAccount\Desktop\HostShared\MSStore Install"
|
||||
#
|
||||
# Download only (Don't Install):
|
||||
# .\Install-Microsoft-Store.ps1 -noInstall
|
||||
#
|
||||
# Debug mode (Save SOAP XML logs to disk):
|
||||
# .\Install-Microsoft-Store.ps1 -debugSaveFiles
|
||||
|
||||
# =======================================================
|
||||
|
||||
# --- Configuration ---
|
||||
# Note: These defaults should work for the regular current build of Microsoft Store, but I haven't tested using any of the other values. So fetching insider builds of MS Store (if any) might not work.
|
||||
$flightRing = "Retail" # Apparently accepts 'Retail', 'Internal', and 'External'
|
||||
|
|
@ -43,21 +66,26 @@ $subfolderName = "MSStore Install"
|
|||
# Category ID for the Microsoft Store app package
|
||||
$storeCategoryId = "64293252-5926-453c-9494-2d4021f1c78d"
|
||||
|
||||
# Combine them to create the full working directory path
|
||||
$workingDir = Join-Path -Path $userDownloadsFolder -ChildPath $subfolderName
|
||||
$LogDirectory = Join-Path -Path $workingDir -ChildPath "Logs"
|
||||
|
||||
# Create the directory if it doesn't exist
|
||||
if (-not (Test-Path -Path $workingDir)) {
|
||||
New-Item -Path $workingDir -ItemType Directory -Force | Out-Null
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
If ($debugSaveFiles) {
|
||||
# Create a subdirectory for logs if it doesn't exist
|
||||
if (-not (Test-Path -Path $LogDirectory)) {
|
||||
New-Item -Path $LogDirectory -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
Write-Host "All files (logs, downloads) will be saved to: '$LogDirectory'" -ForegroundColor Yellow
|
||||
Write-Host "All files (logs, downloads) will be saved to: '$workingDir'" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# --- XML Templates ---
|
||||
|
|
@ -185,204 +213,206 @@ $headers = @{ "Content-Type" = "application/soap+xml; charset=utf-8" }
|
|||
$baseUri = "https://fe3.delivery.mp.microsoft.com/ClientWebService/client.asmx"
|
||||
|
||||
try {
|
||||
# Step 1: Get Cookie
|
||||
Write-Host "Step 1: Getting authentication cookie..."
|
||||
$cookieRequestPayload = $cookieXmlTemplate
|
||||
If ($debugSaveFiles) { $cookieRequestPayload | Set-Content -Path (Join-Path $LogDirectory "01_Step1_Request.xml") }
|
||||
|
||||
$cookieResponse = Invoke-WebRequest -Uri $baseUri -Method Post -Body $cookieRequestPayload -Headers $headers -UseBasicParsing
|
||||
If ($debugSaveFiles) { $cookieResponse.Content | Set-Content -Path (Join-Path $LogDirectory "01_Step1_Response.xml"); Write-Host " -> Saved request and response logs for Step 1." }
|
||||
|
||||
$cookieResponseXml = [xml]$cookieResponse.Content
|
||||
$encryptedCookieData = $cookieResponseXml.Envelope.Body.GetCookieResponse.GetCookieResult.EncryptedData
|
||||
Write-Host "Success. Cookie received." -ForegroundColor Green
|
||||
|
||||
# Step 2: Get File List
|
||||
Write-Host "Step 2: Getting file list..."
|
||||
$fileListRequestPayload = $fileListXmlTemplate -f $encryptedCookieData, $storeCategoryId, $currentBranch, $flightRing, $flightingBranchName
|
||||
If ($debugSaveFiles) { [System.IO.File]::WriteAllText((Join-Path $LogDirectory "02_Step2_Request_AUTOMATED.xml"), $fileListRequestPayload, [System.Text.UTF8Encoding]::new($false)) }
|
||||
|
||||
$fileListResponse = Invoke-WebRequest -Uri $baseUri -Method Post -Body $fileListRequestPayload -Headers $headers -UseBasicParsing
|
||||
If ($debugSaveFiles) { $fileListResponse.Content | Set-Content -Path (Join-Path $LogDirectory "02_Step2_Response_SUCCESS.xml") }
|
||||
|
||||
# The response contains XML fragments that are HTML-encoded. We must decode this before treating it as XML.
|
||||
Add-Type -AssemblyName System.Web
|
||||
$decodedContent = [System.Web.HttpUtility]::HtmlDecode($fileListResponse.Content)
|
||||
$fileListResponseXml = [xml]$decodedContent
|
||||
Write-Host "Successfully received and DECODED Step 2 response." -ForegroundColor Green
|
||||
|
||||
$fileIdentityMap = @{}
|
||||
|
||||
# Get the two main lists of updates from the now correctly-decoded response
|
||||
$newUpdates = $fileListResponseXml.Envelope.Body.SyncUpdatesResponse.SyncUpdatesResult.NewUpdates.UpdateInfo
|
||||
$allExtendedUpdates = $fileListResponseXml.Envelope.Body.SyncUpdatesResponse.SyncUpdatesResult.ExtendedUpdateInfo.Updates.Update
|
||||
|
||||
Write-Host "--- Correlating Update Information ---" -ForegroundColor Magenta
|
||||
|
||||
# Filter the 'NewUpdates' list to only include items that are actual downloadable files.
|
||||
# These are identified by the presence of the <SecuredFragment> tag inside their inner XML.
|
||||
$downloadableUpdates = $newUpdates | Where-Object { $_.Xml.Properties.SecuredFragment }
|
||||
|
||||
Write-Host "Found $($downloadableUpdates.Count) potentially downloadable packages." -ForegroundColor Cyan
|
||||
|
||||
# Now, process each downloadable update
|
||||
foreach ($update in $downloadableUpdates) {
|
||||
$lookupId = $update.ID
|
||||
if (-not $ExistingInstallerFilesPath) {
|
||||
# Step 1: Get Cookie
|
||||
Write-Host "Step 1: Getting authentication cookie..."
|
||||
$cookieRequestPayload = $cookieXmlTemplate
|
||||
If ($debugSaveFiles) { $cookieRequestPayload | Set-Content -Path (Join-Path $LogDirectory "01_Step1_Request.xml") }
|
||||
|
||||
# Find the matching entry in the 'ExtendedUpdateInfo' list using the same numeric ID.
|
||||
$extendedInfo = $allExtendedUpdates | Where-Object { $_.ID -eq $lookupId } | Select-Object -First 1
|
||||
$cookieResponse = Invoke-WebRequest -Uri $baseUri -Method Post -Body $cookieRequestPayload -Headers $headers -UseBasicParsing
|
||||
If ($debugSaveFiles) { $cookieResponse.Content | Set-Content -Path (Join-Path $LogDirectory "01_Step1_Response.xml"); Write-Host " -> Saved request and response logs for Step 1." }
|
||||
|
||||
$cookieResponseXml = [xml]$cookieResponse.Content
|
||||
$encryptedCookieData = $cookieResponseXml.Envelope.Body.GetCookieResponse.GetCookieResult.EncryptedData
|
||||
Write-Host "Success. Cookie received." -ForegroundColor Green
|
||||
|
||||
# Step 2: Get File List
|
||||
Write-Host "Step 2: Getting file list..."
|
||||
$fileListRequestPayload = $fileListXmlTemplate -f $encryptedCookieData, $storeCategoryId, $currentBranch, $flightRing, $flightingBranchName
|
||||
If ($debugSaveFiles) { [System.IO.File]::WriteAllText((Join-Path $LogDirectory "02_Step2_Request_AUTOMATED.xml"), $fileListRequestPayload, [System.Text.UTF8Encoding]::new($false)) }
|
||||
|
||||
$fileListResponse = Invoke-WebRequest -Uri $baseUri -Method Post -Body $fileListRequestPayload -Headers $headers -UseBasicParsing
|
||||
If ($debugSaveFiles) { $fileListResponse.Content | Set-Content -Path (Join-Path $LogDirectory "02_Step2_Response_SUCCESS.xml") }
|
||||
|
||||
# The response contains XML fragments that are HTML-encoded. We must decode this before treating it as XML.
|
||||
Add-Type -AssemblyName System.Web
|
||||
$decodedContent = [System.Web.HttpUtility]::HtmlDecode($fileListResponse.Content)
|
||||
$fileListResponseXml = [xml]$decodedContent
|
||||
Write-Host "Successfully received and DECODED Step 2 response." -ForegroundColor Green
|
||||
|
||||
if (-not $extendedInfo) {
|
||||
Write-Warning "Could not find matching ExtendedInfo for downloadable update ID $lookupId. Skipping."
|
||||
continue
|
||||
}
|
||||
$fileIdentityMap = @{}
|
||||
|
||||
# From the extended info, get the actual package file and ignore the metadata .cab files.
|
||||
$fileNode = $extendedInfo.Xml.Files.File | Where-Object { $_.FileName -and $_.FileName -notlike "Abm_*" } | Select-Object -First 1
|
||||
# Get the two main lists of updates from the now correctly-decoded response
|
||||
$newUpdates = $fileListResponseXml.Envelope.Body.SyncUpdatesResponse.SyncUpdatesResult.NewUpdates.UpdateInfo
|
||||
$allExtendedUpdates = $fileListResponseXml.Envelope.Body.SyncUpdatesResponse.SyncUpdatesResult.ExtendedUpdateInfo.Updates.Update
|
||||
|
||||
if (-not $fileNode) {
|
||||
Write-Warning "Found matching ExtendedInfo for ID $lookupId, but it contains no valid file node. Skipping."
|
||||
continue
|
||||
}
|
||||
Write-Host "--- Correlating Update Information ---" -ForegroundColor Magenta
|
||||
|
||||
# Additional parsing
|
||||
$fileName = $fileNode.FileName
|
||||
$updateGuid = $update.Xml.UpdateIdentity.UpdateID
|
||||
$revNum = $update.Xml.UpdateIdentity.RevisionNumber
|
||||
$fullIdentifier = $fileNode.GetAttribute("InstallerSpecificIdentifier")
|
||||
# Filter the 'NewUpdates' list to only include items that are actual downloadable files.
|
||||
# These are identified by the presence of the <SecuredFragment> tag inside their inner XML.
|
||||
$downloadableUpdates = $newUpdates | Where-Object { $_.Xml.Properties.SecuredFragment }
|
||||
|
||||
# Define the regex based on the official package identity structure.
|
||||
# <Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>
|
||||
$regex = "^(?<Name>.+?)_(?<Version>\d+\.\d+\.\d+\.\d+)_(?<Architecture>[a-zA-Z0-9]+)_(?<ResourceId>.*?)_(?<PublisherId>[a-hjkmnp-tv-z0-9]{13})$"
|
||||
|
||||
$packageInfo = [PSCustomObject]@{
|
||||
FullName = $fullIdentifier
|
||||
FileName = $fileName
|
||||
UpdateID = $updateGuid
|
||||
RevisionNumber = $revNum
|
||||
}
|
||||
Write-Host "Found $($downloadableUpdates.Count) potentially downloadable packages." -ForegroundColor Cyan
|
||||
|
||||
if ($fullIdentifier -match $regex) {
|
||||
# If the regex matches, populate the object with the named capture groups
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PackageName" -Value $matches.Name
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Version" -Value $matches.Version
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Architecture" -Value $matches.Architecture
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "ResourceId" -Value $matches.ResourceId
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PublisherId" -Value $matches.PublisherId
|
||||
} else {
|
||||
# Fallback for any identifiers that don't match the pattern
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PackageName" -Value "Unknown (Parsing Failed)"
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Architecture" -Value "unknown"
|
||||
}
|
||||
|
||||
# Use the full, unique identifier as the key in the map
|
||||
$fileIdentityMap[$fullIdentifier] = $packageInfo
|
||||
|
||||
Write-Host " -> CORRELATED: '$($packageInfo.PackageName)' ($($packageInfo.Architecture))" -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host "--- Correlation Complete ---" -ForegroundColor Magenta
|
||||
Write-Host "Found and prepared $($fileIdentityMap.Count) downloadable files." -ForegroundColor Green
|
||||
|
||||
|
||||
# --- Step 3: Filter, Get URLs, and Download ---
|
||||
try {
|
||||
# Get the current system's processor architecture and map it to the script's naming convention
|
||||
$systemArch = switch ($env:PROCESSOR_ARCHITECTURE) {
|
||||
"AMD64" { "x64" }
|
||||
"ARM64" { "arm64" }
|
||||
"x86" { "x86" }
|
||||
default { "unknown" }
|
||||
}
|
||||
|
||||
if ($systemArch -eq "unknown") {
|
||||
throw "Could not determine system architecture from '$($env:PROCESSOR_ARCHITECTURE)'."
|
||||
}
|
||||
Write-Host "Step 3: Filtering packages for your system architecture ('$systemArch')..." -ForegroundColor Magenta
|
||||
|
||||
# --- Filter the packages ---
|
||||
|
||||
# 1. Isolate the Microsoft.WindowsStore packages and find the latest version
|
||||
$latestStorePackage = $fileIdentityMap.Values |
|
||||
Where-Object { $_.PackageName -eq 'Microsoft.WindowsStore' } |
|
||||
Sort-Object { [version]$_.Version } -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
# 2. Get all other dependencies that match the system architecture (or are neutral)
|
||||
$filteredDependencies = $fileIdentityMap.Values |
|
||||
Where-Object {
|
||||
($_.PackageName -ne 'Microsoft.WindowsStore') -and
|
||||
( ($_.Architecture -eq $systemArch) -or ($_.Architecture -eq 'neutral') )
|
||||
}
|
||||
|
||||
# 3. Combine the lists for the final download queue
|
||||
$packagesToDownload = @()
|
||||
if ($latestStorePackage) {
|
||||
$packagesToDownload += $latestStorePackage
|
||||
Write-Host " -> Found latest Store package: $($latestStorePackage.FullName)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning "Could not find any Microsoft.WindowsStore package."
|
||||
}
|
||||
|
||||
$packagesToDownload += $filteredDependencies
|
||||
Write-Host " -> Found $($filteredDependencies.Count) dependencies for '$systemArch' architecture." -ForegroundColor Green
|
||||
Write-Host "Total files to download: $($packagesToDownload.Count)" -ForegroundColor Cyan
|
||||
Write-Host "------------------------------------------------------------"
|
||||
|
||||
|
||||
# --- Loop through the filtered list, get URLs, and download ---
|
||||
Write-Host "Step 4: Fetching URLs and downloading files..." -ForegroundColor Magenta
|
||||
|
||||
$originalPref = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
foreach ($package in $packagesToDownload) {
|
||||
Write-Host "Processing: $($package.FullName)"
|
||||
|
||||
# Get the download URL for this specific package
|
||||
$fileUrlRequestPayload = $fileUrlXmlTemplate -f $encryptedCookieData, $package.UpdateID, $package.RevisionNumber, $currentBranch, $flightRing, $flightingBranchName
|
||||
$fileUrlResponse = Invoke-WebRequest -Uri "$baseUri/secured" -Method Post -Body $fileUrlRequestPayload -Headers $headers -UseBasicParsing
|
||||
$fileUrlResponseXml = [xml]$fileUrlResponse.Content
|
||||
|
||||
$fileLocations = $fileUrlResponseXml.Envelope.Body.GetExtendedUpdateInfo2Response.GetExtendedUpdateInfo2Result.FileLocations.FileLocation
|
||||
$baseFileName = [System.IO.Path]::GetFileNameWithoutExtension($package.FileName)
|
||||
$downloadUrl = ($fileLocations | Where-Object { $_.Url -like "*$baseFileName*" }).Url
|
||||
|
||||
if (-not $downloadUrl) {
|
||||
Write-Warning " -> Could not retrieve download URL for $($package.FileName). Skipping."
|
||||
continue
|
||||
}
|
||||
if ($noDownload) {
|
||||
Write-Host " -> Skipping download for $($package.FullName) because of -noDownload switch." -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
|
||||
# Download the file
|
||||
# Construct a more descriptive filename using the package's full name and its original extension
|
||||
$fileExtension = [System.IO.Path]::GetExtension($package.FileName)
|
||||
$newFileName = "$($package.FullName)$($fileExtension)"
|
||||
$filePath = Join-Path $workingDir $newFileName
|
||||
# Now, process each downloadable update
|
||||
foreach ($update in $downloadableUpdates) {
|
||||
$lookupId = $update.ID
|
||||
|
||||
Write-Host " -> Downloading from: $downloadUrl" -ForegroundColor Gray
|
||||
Write-Host " -> Saving to: $filePath"
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $filePath -UseBasicParsing
|
||||
Write-Host " -> SUCCESS: Download complete." -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Error " -> FAILED to download $($newFileName). Error: $($_.Exception.Message)"
|
||||
# Find the matching entry in the 'ExtendedUpdateInfo' list using the same numeric ID.
|
||||
$extendedInfo = $allExtendedUpdates | Where-Object { $_.ID -eq $lookupId } | Select-Object -First 1
|
||||
|
||||
if (-not $extendedInfo) {
|
||||
Write-Warning "Could not find matching ExtendedInfo for downloadable update ID $lookupId. Skipping."
|
||||
continue
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
# From the extended info, get the actual package file and ignore the metadata .cab files.
|
||||
$fileNode = $extendedInfo.Xml.Files.File | Where-Object { $_.FileName -and $_.FileName -notlike "Abm_*" } | Select-Object -First 1
|
||||
|
||||
if (-not $fileNode) {
|
||||
Write-Warning "Found matching ExtendedInfo for ID $lookupId, but it contains no valid file node. Skipping."
|
||||
continue
|
||||
}
|
||||
|
||||
# Additional parsing
|
||||
$fileName = $fileNode.FileName
|
||||
$updateGuid = $update.Xml.UpdateIdentity.UpdateID
|
||||
$revNum = $update.Xml.UpdateIdentity.RevisionNumber
|
||||
$fullIdentifier = $fileNode.GetAttribute("InstallerSpecificIdentifier")
|
||||
|
||||
# Define the regex based on the official package identity structure.
|
||||
# <Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>
|
||||
$regex = "^(?<Name>.+?)_(?<Version>\d+\.\d+\.\d+\.\d+)_(?<Architecture>[a-zA-Z0-9]+)_(?<ResourceId>.*?)_(?<PublisherId>[a-hjkmnp-tv-z0-9]{13})$"
|
||||
|
||||
$packageInfo = [PSCustomObject]@{
|
||||
FullName = $fullIdentifier
|
||||
FileName = $fileName
|
||||
UpdateID = $updateGuid
|
||||
RevisionNumber = $revNum
|
||||
}
|
||||
|
||||
if ($fullIdentifier -match $regex) {
|
||||
# If the regex matches, populate the object with the named capture groups
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PackageName" -Value $matches.Name
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Version" -Value $matches.Version
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Architecture" -Value $matches.Architecture
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "ResourceId" -Value $matches.ResourceId
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PublisherId" -Value $matches.PublisherId
|
||||
} else {
|
||||
# Fallback for any identifiers that don't match the pattern
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "PackageName" -Value "Unknown (Parsing Failed)"
|
||||
$packageInfo | Add-Member -MemberType NoteProperty -Name "Architecture" -Value "unknown"
|
||||
}
|
||||
|
||||
# Use the full, unique identifier as the key in the map
|
||||
$fileIdentityMap[$fullIdentifier] = $packageInfo
|
||||
|
||||
Write-Host " -> CORRELATED: '$($packageInfo.PackageName)' ($($packageInfo.Architecture))" -ForegroundColor Green
|
||||
}
|
||||
|
||||
$ProgressPreference = $originalPref
|
||||
|
||||
Write-Host "------------------------------------------------------------"
|
||||
Write-Host "Finished downloading packages to: $workingDir" -ForegroundColor Green
|
||||
Write-Host "--- Correlation Complete ---" -ForegroundColor Magenta
|
||||
Write-Host "Found and prepared $($fileIdentityMap.Count) downloadable files." -ForegroundColor Green
|
||||
|
||||
} catch {
|
||||
Write-Host "An error occurred during the filtering or downloading phase:" -ForegroundColor Red
|
||||
Write-Host $_.Exception.ToString()
|
||||
|
||||
# --- Step 3: Filter, Get URLs, and Download ---
|
||||
try {
|
||||
# Get the current system's processor architecture and map it to the script's naming convention
|
||||
$systemArch = switch ($env:PROCESSOR_ARCHITECTURE) {
|
||||
"AMD64" { "x64" }
|
||||
"ARM64" { "arm64" }
|
||||
"x86" { "x86" }
|
||||
default { "unknown" }
|
||||
}
|
||||
|
||||
if ($systemArch -eq "unknown") {
|
||||
throw "Could not determine system architecture from '$($env:PROCESSOR_ARCHITECTURE)'."
|
||||
}
|
||||
Write-Host "Step 3: Filtering packages for your system architecture ('$systemArch')..." -ForegroundColor Magenta
|
||||
|
||||
# --- Filter the packages ---
|
||||
|
||||
# 1. Isolate the Microsoft.WindowsStore packages and find the latest version
|
||||
$latestStorePackage = $fileIdentityMap.Values |
|
||||
Where-Object { $_.PackageName -eq 'Microsoft.WindowsStore' } |
|
||||
Sort-Object { [version]$_.Version } -Descending |
|
||||
Select-Object -First 1
|
||||
|
||||
# 2. Get all other dependencies that match the system architecture (or are neutral)
|
||||
$filteredDependencies = $fileIdentityMap.Values |
|
||||
Where-Object {
|
||||
($_.PackageName -ne 'Microsoft.WindowsStore') -and
|
||||
( ($_.Architecture -eq $systemArch) -or ($_.Architecture -eq 'neutral') )
|
||||
}
|
||||
|
||||
# 3. Combine the lists for the final download queue
|
||||
$packagesToDownload = @()
|
||||
if ($latestStorePackage) {
|
||||
$packagesToDownload += $latestStorePackage
|
||||
Write-Host " -> Found latest Store package: $($latestStorePackage.FullName)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning "Could not find any Microsoft.WindowsStore package."
|
||||
}
|
||||
|
||||
$packagesToDownload += $filteredDependencies
|
||||
Write-Host " -> Found $($filteredDependencies.Count) dependencies for '$systemArch' architecture." -ForegroundColor Green
|
||||
Write-Host "Total files to download: $($packagesToDownload.Count)" -ForegroundColor Cyan
|
||||
Write-Host "------------------------------------------------------------"
|
||||
|
||||
|
||||
# --- Loop through the filtered list, get URLs, and download ---
|
||||
Write-Host "Step 4: Fetching URLs and downloading files..." -ForegroundColor Magenta
|
||||
|
||||
$originalPref = $ProgressPreference
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
foreach ($package in $packagesToDownload) {
|
||||
Write-Host "Processing: $($package.FullName)"
|
||||
|
||||
# Get the download URL for this specific package
|
||||
$fileUrlRequestPayload = $fileUrlXmlTemplate -f $encryptedCookieData, $package.UpdateID, $package.RevisionNumber, $currentBranch, $flightRing, $flightingBranchName
|
||||
$fileUrlResponse = Invoke-WebRequest -Uri "$baseUri/secured" -Method Post -Body $fileUrlRequestPayload -Headers $headers -UseBasicParsing
|
||||
$fileUrlResponseXml = [xml]$fileUrlResponse.Content
|
||||
|
||||
$fileLocations = $fileUrlResponseXml.Envelope.Body.GetExtendedUpdateInfo2Response.GetExtendedUpdateInfo2Result.FileLocations.FileLocation
|
||||
$baseFileName = [System.IO.Path]::GetFileNameWithoutExtension($package.FileName)
|
||||
$downloadUrl = ($fileLocations | Where-Object { $_.Url -like "*$baseFileName*" }).Url
|
||||
|
||||
if (-not $downloadUrl) {
|
||||
Write-Warning " -> Could not retrieve download URL for $($package.FileName). Skipping."
|
||||
continue
|
||||
}
|
||||
if ($noDownload) {
|
||||
Write-Host " -> Skipping download for $($package.FullName) because of -noDownload switch." -ForegroundColor Yellow
|
||||
continue
|
||||
}
|
||||
|
||||
# Download the file
|
||||
# Construct a more descriptive filename using the package's full name and its original extension
|
||||
$fileExtension = [System.IO.Path]::GetExtension($package.FileName)
|
||||
$newFileName = "$($package.FullName)$($fileExtension)"
|
||||
$filePath = Join-Path $workingDir $newFileName
|
||||
|
||||
Write-Host " -> Downloading from: $downloadUrl" -ForegroundColor Gray
|
||||
Write-Host " -> Saving to: $filePath"
|
||||
|
||||
try {
|
||||
Invoke-WebRequest -Uri $downloadUrl -OutFile $filePath -UseBasicParsing
|
||||
Write-Host " -> SUCCESS: Download complete." -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Error " -> FAILED to download $($newFileName). Error: $($_.Exception.Message)"
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
$ProgressPreference = $originalPref
|
||||
|
||||
Write-Host "------------------------------------------------------------"
|
||||
Write-Host "Finished downloading packages to: $workingDir" -ForegroundColor Green
|
||||
|
||||
} catch {
|
||||
Write-Host "An error occurred during the filtering or downloading phase:" -ForegroundColor Red
|
||||
Write-Host $_.Exception.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
If ($noDownload) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
# Downloads the latest VC Redistributables from Microsoft
|
||||
|
||||
param(
|
||||
# Optional path to a local directory containing the installation files. If provided, the download steps will be skipped.
|
||||
# - Just copy the entire "VCRedist Install" folder with the files the script normally downloads, and put it in your mounted folder to avoid having to re-download it.
|
||||
# - Make sure to use the mounted path from the perspective of within the sandbox.
|
||||
[string]$ExistingInstallerFilesPath,
|
||||
|
||||
# If set, forces using the installer files from ExistingInstallerFilesPath even if a new version exists. Still warns about there being a new version.
|
||||
[switch]$ForceCachedFilesOnly,
|
||||
|
||||
# If set, implies ForceCachedFilesOnly and uses cached installer files only, but does not even bother checking for a latest version.
|
||||
[switch]$NoCheckLatestVersion
|
||||
)
|
||||
|
||||
# --- Parameter Usage Examples ---
|
||||
# Standard run (Download & Install):
|
||||
# .\Install-VC-Redist.ps1
|
||||
#
|
||||
# Install from existing files instead of downloading:
|
||||
# .\Install-VC-Redist.ps1 -ExistingInstallerFilesPath "C:\Users\WDAGUtilityAccount\Desktop\HostShared\VCRedist Install"
|
||||
#
|
||||
# Use cached files only, even if outdated (still warns about new versions):
|
||||
# .\Install-VC-Redist.ps1 -ExistingInstallerFilesPath "C:\Users\WDAGUtilityAccount\Desktop\HostShared\VCRedist Install" -ForceCachedFilesOnly
|
||||
#
|
||||
# Use cached files only, skip all version checks:
|
||||
# .\Install-VC-Redist.ps1 -ExistingInstallerFilesPath "C:\Users\WDAGUtilityAccount\Desktop\HostShared\VCRedist Install" -NoCheckLatestVersion
|
||||
|
||||
# =======================================================
|
||||
|
||||
# Validate parameter combinations
|
||||
if ($NoCheckLatestVersion) {
|
||||
$ForceCachedFilesOnly = [switch]::new($true)
|
||||
}
|
||||
if ($ForceCachedFilesOnly -and [string]::IsNullOrWhiteSpace($ExistingInstallerFilesPath)) {
|
||||
Write-Host "Error: -ForceCachedFilesOnly requires -ExistingInstallerFilesPath to be specified." -ForegroundColor Red
|
||||
Read-Host "Press Enter to exit"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# URLs for the latest Visual C++ Redistributables
|
||||
$urls = @(
|
||||
"https://aka.ms/vs/17/release/vc_redist.x86.exe",
|
||||
"https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
)
|
||||
if ($env:PROCESSOR_ARCHITECTURE -eq 'ARM64') {
|
||||
$urls += "https://aka.ms/vs/17/release/vc_redist.arm64.exe"
|
||||
}
|
||||
|
||||
# Directory to save the downloads. This will save it into the user "Downloads" folder.
|
||||
$folderName = "VCRedist Install"
|
||||
$userDownloadsFolder = (New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path
|
||||
$downloadPath = Join-Path -Path $userDownloadsFolder -ChildPath $folderName
|
||||
|
||||
# To improve download performance, the progress bar is suppressed.
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
# Load stored version hashes from the pre-existing installer cache (read-only mount)
|
||||
$storedHashes = @{}
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExistingInstallerFilesPath)) {
|
||||
$hashFilePath = Join-Path $ExistingInstallerFilesPath "versions.txt"
|
||||
if (Test-Path $hashFilePath) {
|
||||
Get-Content $hashFilePath | ForEach-Object {
|
||||
$parts = $_ -split '=', 2
|
||||
if ($parts.Count -eq 2) { $storedHashes[$parts[0]] = $parts[1] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Track current remote hashes to save at the end
|
||||
$currentHashes = @{}
|
||||
$pauseAtEnd = $false
|
||||
|
||||
foreach ($url in $urls) {
|
||||
$fileName = $url.Split('/')[-1]
|
||||
$downloadFilePath = Join-Path $downloadPath $fileName
|
||||
$installerToRun = $null
|
||||
$updateDetected = $false
|
||||
$remoteHash = $null
|
||||
|
||||
Write-Host "`nChecking $fileName..."
|
||||
|
||||
if (-not $NoCheckLatestVersion) {
|
||||
try {
|
||||
# Get the final redirected URL to extract the version hash
|
||||
$headResponse = Invoke-WebRequest -Uri $url -Method Head -UseBasicParsing -ErrorAction Stop
|
||||
if ($headResponse.BaseResponse.ResponseUri) {
|
||||
# PowerShell 5.1
|
||||
$finalUrl = $headResponse.BaseResponse.ResponseUri.AbsoluteUri
|
||||
} else {
|
||||
# PowerShell 7+
|
||||
$finalUrl = $headResponse.BaseResponse.RequestMessage.RequestUri.AbsoluteUri
|
||||
}
|
||||
$remoteHash = $finalUrl.Split('/')[-2]
|
||||
$currentHashes[$fileName] = $remoteHash
|
||||
Write-Host "Remote hash: $remoteHash"
|
||||
}
|
||||
catch {
|
||||
Write-Host "Warning: Could not retrieve remote info for $fileName." -ForegroundColor Yellow
|
||||
Write-Host "Error Info: $_" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Check if a pre-existing cached installer can be used
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExistingInstallerFilesPath)) {
|
||||
$cachedFilePath = Join-Path $ExistingInstallerFilesPath $fileName
|
||||
if (Test-Path $cachedFilePath) {
|
||||
if ($null -ne $remoteHash -and $storedHashes.ContainsKey($fileName) -and $storedHashes[$fileName] -eq $remoteHash) {
|
||||
Write-Host "Cached version is up to date. Using $cachedFilePath"
|
||||
$installerToRun = $cachedFilePath
|
||||
} elseif ($ForceCachedFilesOnly) {
|
||||
if ($null -ne $remoteHash) {
|
||||
Write-Host "WARNING: Cached installer is out of date, but using it anyway (-ForceCachedFilesOnly)." -ForegroundColor Yellow
|
||||
Write-Host "Please update your cache at: $ExistingInstallerFilesPath" -ForegroundColor Yellow
|
||||
$pauseAtEnd = $true
|
||||
} else {
|
||||
Write-Host "Using cached file (version check skipped). Using $cachedFilePath"
|
||||
}
|
||||
$installerToRun = $cachedFilePath
|
||||
} else {
|
||||
Write-Host "Newer version detected. Cached installer is out of date." -ForegroundColor Yellow
|
||||
$updateDetected = $true
|
||||
$pauseAtEnd = $true
|
||||
}
|
||||
} elseif ($ForceCachedFilesOnly) {
|
||||
Write-Host "Error: Cached file not found at $cachedFilePath and -ForceCachedFilesOnly is set." -ForegroundColor Red
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
# Download if no valid cache was found or an update is needed
|
||||
if ($null -eq $installerToRun) {
|
||||
if ($ForceCachedFilesOnly) {
|
||||
Write-Host "Error: No cached installer available for $fileName and -ForceCachedFilesOnly is set. Skipping." -ForegroundColor Red
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Host "Downloading $fileName..."
|
||||
|
||||
# Create the directory if it doesn't exist
|
||||
if (-not (Test-Path -Path $downloadPath)) {
|
||||
New-Item -Path $downloadPath -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
|
||||
Invoke-WebRequest -Uri $url -OutFile $downloadFilePath -UseBasicParsing
|
||||
$installerToRun = $downloadFilePath
|
||||
|
||||
if ($updateDetected) {
|
||||
Write-Host "ACTION REQUIRED: A new version of $fileName was downloaded." -ForegroundColor Yellow
|
||||
Write-Host "Please update your cache at: $ExistingInstallerFilesPath" -ForegroundColor Yellow
|
||||
Write-Host "Then update versions.txt with: $fileName=$remoteHash" -ForegroundColor Yellow
|
||||
$pauseAtEnd = $true
|
||||
}
|
||||
}
|
||||
|
||||
if (Test-Path $installerToRun) {
|
||||
Write-Host "Installing $fileName from $installerToRun..."
|
||||
# Silently install the redistributable and wait for it to complete
|
||||
Start-Process -FilePath $installerToRun -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Write-Host "$fileName has been installed."
|
||||
|
||||
# Optional: Remove the downloaded installer if it was downloaded to TEMP
|
||||
if ($installerToRun -eq $downloadFilePath) {
|
||||
# Remove-Item -Path $downloadFilePath
|
||||
}
|
||||
} else {
|
||||
Write-Host "Error: Failed to locate installer for $fileName."
|
||||
}
|
||||
}
|
||||
|
||||
# Save versions.txt to the download folder so it can be copied alongside the installers
|
||||
if ($currentHashes.Count -gt 0) {
|
||||
if (-not (Test-Path -Path $downloadPath)) {
|
||||
New-Item -Path $downloadPath -ItemType Directory -Force | Out-Null
|
||||
}
|
||||
$versionsFilePath = Join-Path $downloadPath "versions.txt"
|
||||
$currentHashes.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" } | Set-Content $versionsFilePath
|
||||
}
|
||||
|
||||
# Restore the default progress preference
|
||||
$ProgressPreference = 'Continue'
|
||||
|
||||
Write-Host "`nScript execution finished."
|
||||
if ($pauseAtEnd) {
|
||||
Read-Host "`nPress Enter to exit"
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -78,9 +78,9 @@ reg add "HKEY_CLASSES_ROOT\.ps1\ShellNew" /v "ItemName" /t REG_SZ /d "script" /f
|
|||
|
||||
# NotePad Tip: Go to C:\Windows on your main computer and copy Notepad.exe, then copy notepad.exe.mui from your main language folder, such as C:\Windows\en-US
|
||||
# Important: Notepad.exe.mui can't simply go next to notepad.exe. You need to actually create the language folder (like en-US) again next to notepad.exe and put it in that. Otherwise notepad won't run.
|
||||
$notepadPath = "C:\Users\WDAGUtilityAccount\Desktop\HostShared\notepad.exe"
|
||||
$notepadPath = "C:\Users\WDAGUtilityAccount\Desktop\HostShared\Apps\notepad.exe"
|
||||
# For Notepad++, use the portable version
|
||||
$notepadPlusPlusPath = "C:\Users\WDAGUtilityAccount\Desktop\HostShared\Notepad++\Notepad++.exe"
|
||||
$notepadPlusPlusPath = "C:\Users\WDAGUtilityAccount\Desktop\HostShared\Apps\Notepad++\Notepad++.exe"
|
||||
|
||||
# Check if the Notepad and Notepad++ paths exist, if not, set them to null
|
||||
If (!(Test-Path $notepadPath)) { $notepadPath = $null; Write-Host "Notepad not found, context menu options will not be added." }
|
||||
|
|
|
|||
Loading…
Reference in New Issue