# This script will install the Microsoft Store into Windows Sandbox # It uses the Windows Update API to fetch the necessary installation files directly from Microsoft # Unlike many similar scripts, it uses NO dependencies or third party APIs # Author: ThioJoe # Repo Url: https://github.com/ThioJoe/Windows-Sandbox-Tools # --- Configuration --- # Category ID for the Microsoft Store app package $storeCategoryId = "64293252-5926-453c-9494-2d4021f1c78d" # Flight Ring - Use "Retail" for the public version. # Note: Values other than retail are not properly set up yet in this script $flightRing = "Retail" # Other Known options: # "RP" (ReleasePreview Branch) # "WIS" (Beta Branch) # "WIF" (Dev Branch) # Other Possible Options, Untested: # "Canary" # "MSIT" (Internal) # --- Define Working Directory --- # Get the path to the user's personal Downloads folder in a reliable way $userDownloadsFolder = (New-Object -ComObject Shell.Application).Namespace('shell:Downloads').Self.Path # Define the subfolder name for all our files $subfolderName = "MSStore Install" # 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 } Write-Host "All files (logs, downloads) will be saved to: '$workingDir'" -ForegroundColor Yellow # --- XML Templates --- # Step 1: GetCookie request body. # See: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/36a5d99a-a3ca-439d-bcc5-7325ff6b91e2 $cookieXmlTemplate = @" http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie urn:uuid:$(New-Guid) https://fe3.delivery.mp.microsoft.com/ClientWebService/client.asmx "@ # Step 2: SyncUpdates request body. Based on intercepted XML request using Microsoft Store. # Info about attributes found here: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/6b654980-ae63-4b0d-9fae-2abb516af894 $fileListXmlTemplate = @" http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/SyncUpdates urn:uuid:$(New-Guid) https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx $((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'")) $((Get-Date).AddMinutes(5).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'")) $((Get-Date).AddYears(10).ToUniversalTime().ToString('u').Replace(' ','T')) {0} false 123111923599745169044 87888302311099323110994543419005983000659830007 598300086048401062450018624500196245002098959022 98959023989590249895902598959026104433538129905029 130040031132387090132393049133399034138537048140377312 143747671158941041158941042158941043158941044159123858 159130928164836897164847386164848327164852241164852246 164852253 false false {1} true false Extended en-US en en-US false E:BranchReadinessLevel=CB&CurrentBranch=rs_prerelease&OEMModel=Virtual%20Machine&FlightRing=Retail&AttrDataVer=321&InstallLanguage=en-US&OSUILocale=en-US&InstallationType=Client&FlightingBranchName=&OSSkuId=48&App=WU_STORE&ProcessorManufacturer=GenuineIntel&OEMName_Uncleaned=Microsoft%20Corporation&AppVer=1407.2503.28012.0&OSArchitecture=AMD64&IsFlightingEnabled=1&TelemetryLevel=1&DefaultUserRegion=39070&WuClientVer=1310.2503.26012.0&OSVersion=10.0.26100.3915&DeviceFamily=Windows.Desktop Interactive=1;IsSeeker=1; "@ # Step 3: GetExtendedUpdateInfo2 - After getting the list of matched files (app version and dependencies), this lets us get the actual download URLs # See: https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wusp/2f66a682-164f-47ec-968e-e43c0a85dc21 $fileUrlXmlTemplate = @" http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetExtendedUpdateInfo2 urn:uuid:$(New-Guid) https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx/secured $((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'")) $((Get-Date).AddMinutes(5).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'")) {0} {1}{2} FileUrl E:BranchReadinessLevel=CB&CurrentBranch=rs_prerelease&OEMModel=Virtual%20Machine&FlightRing={3}&AttrDataVer=321&InstallLanguage=en-US&OSUILocale=en-US&InstallationType=Client&FlightingBranchName=&OSSkuId=48&App=WU_STORE&ProcessorManufacturer=GenuineIntel&OEMName_Uncleaned=Microsoft%20Corporation&AppVer=1407.2503.28012.0&OSArchitecture=AMD64&IsFlightingEnabled=1&TelemetryLevel=1&DefaultUserRegion=39070&WuClientVer=1310.2503.26012.0&OSVersion=10.0.26100.3915&DeviceFamily=Windows.Desktop "@ # --- Script Execution --- $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 #$cookieRequestPayload | Set-Content -Path (Join-Path $LogDirectory "01_Step1_Request.xml") $cookieResponse = Invoke-WebRequest -Uri $baseUri -Method Post -Body $cookieRequestPayload -Headers $headers -UseBasicParsing #$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, $flightRing $tempRequestFile = Join-Path $workingDir "02_Step2_Request_AUTOMATED.xml" [System.IO.File]::WriteAllText($tempRequestFile, $fileListRequestPayload, [System.Text.UTF8Encoding]::new($false)) $fileListResponse = Invoke-WebRequest -Uri $baseUri -Method Post -InFile $tempRequestFile -Headers $headers -UseBasicParsing #$fileListResponse.Content | Set-Content -Path (Join-Path $LogDirectory "02_Step2_Response_SUCCESS.xml") Write-Host " -> Saved request and response logs for Step 2." # 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 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 # 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 } # 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. # ____ $regex = "^(?.+?)_(?\d+\.\d+\.\d+\.\d+)_(?[a-zA-Z0-9]+)_(?.*?)_(?[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 } 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, $flightRing $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 } # 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 "Process complete. All files are in the '$downloadDir' folder." -ForegroundColor Green } catch { Write-Host "An error occurred during the filtering or downloading phase:" -ForegroundColor Red Write-Host $_.Exception.ToString() } # --- Step 5: Install Downloaded Packages --- Write-Host "------------------------------------------------------------" Write-Host "Step 5: Installing packages..." -ForegroundColor Magenta Write-Host "This step requires Administrator privileges." # 1. Check for Administrator rights if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Write-Error "Installation failed. Please re-run the script with 'Run as Administrator'." # Add a pause so the user can see the message before the window closes. Read-Host "Press Enter to exit" exit } # 2. Define the installation order for dependencies based on their base names # The order here is critical for dependencies. $dependencyInstallOrder = @( 'Microsoft.VCLibs', 'Microsoft.NET.Native.Framework', 'Microsoft.NET.Native.Runtime', 'Microsoft.UI.Xaml' ) # 3. Get all downloaded package files and separate the main app from dependencies try { $allDownloadedFiles = Get-ChildItem -Path $workingDir -File | Where-Object { $_.Extension -in '.appx', '.msix', '.appxbundle', '.msixbundle' } $storePackageFile = $allDownloadedFiles | Where-Object { $_.Name -like 'Microsoft.WindowsStore*' } | Select-Object -First 1 $dependencyFiles = $allDownloadedFiles | Where-Object { $_.Name -notlike 'Microsoft.WindowsStore*' } if (-not $dependencyFiles -and -not $storePackageFile) { Write-Warning "No package files found in '$downloadDir' to install." return # Exits this part of the script gracefully } # 4. Install dependencies in the correct, predefined order Write-Host "Installing dependencies..." foreach ($baseName in $dependencyInstallOrder) { # Find all packages that start with the current base name (e.g., 'Microsoft.VCLibs*') # Sorting by name ensures a consistent order if multiple versions exist $packagesInGroup = $dependencyFiles | Where-Object { $_.Name -like "$baseName*" } | Sort-Object Name foreach ($package in $packagesInGroup) { Write-Host " -> Installing $($package.Name)" try { Add-AppxPackage -Path $package.FullName Write-Host " SUCCESS." -ForegroundColor Green } catch { Write-Error " FAILED to install $($package.Name). Error: $($_.Exception.Message)" } } } # 5. Install the main Microsoft Store package last if ($storePackageFile) { Write-Host "Installing the main application..." Write-Host " -> Installing $($storePackageFile.Name)" try { Add-AppxPackage -Path $storePackageFile.FullName Write-Host " SUCCESS: Microsoft Store has been installed/updated." -ForegroundColor Green } catch { Write-Error " FAILED to install $($storePackageFile.Name). Error: $($_.Exception.Message)" } } else { Write-Warning "Microsoft Store package was not found in the download folder." } Write-Host "------------------------------------------------------------" Write-Host "Installation process finished." -ForegroundColor Cyan } catch { Write-Error "A critical error occurred during the installation phase: $($_.Exception.Message)" } # --- Set Region to US so the store will work. Default 'World' region does not work. --- try { # Define the path to the registry key $geoKeyPath = "HKCU:\Control Panel\International\Geo" # Check if the 'Geo' key exists. If not, create it. # The -Force switch ensures that parent keys ('International') are also created if they are missing. if (-not (Test-Path $geoKeyPath)) { Write-Host " -> Registry key not found. Creating: $geoKeyPath" New-Item -Path $geoKeyPath -Force | Out-Null } # Set the 'Nation' value. This is equivalent to: reg add ... /v Nation ... Set-ItemProperty -Path $geoKeyPath -Name "Nation" -Value "244" Write-Host " -> Set 'Nation' value to '244'." # Set the 'Name' value. This is equivalent to: reg add ... /v Name ... Set-ItemProperty -Path $geoKeyPath -Name "Name" -Value "US" Write-Host " -> Set 'Name' value to 'US'." Write-Host " -> Registry configuration complete." -ForegroundColor Green } catch { Write-Error "FAILED to configure registry settings. Error: $($_.Exception.Message)" } } catch { Write-Host "An error occurred:" -ForegroundColor Red if ($_.Exception.Response) { $statusCode = $_.Exception.Response.StatusCode.value__ $statusDescription = $_.Exception.Response.StatusDescription $errorLogPath = Join-Path $LogDirectory "ERROR_Response.txt" try { $stream = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($stream) $responseBody = $reader.ReadToEnd() #$responseBody | Set-Content -Path $errorLogPath } catch { "Could not read error response body." | Set-Content -Path $errorLogPath } Write-Host "Status Code: $statusCode" Write-Host "Status Description: $statusDescription" Write-Host "Server Response saved to '$errorLogPath'" } else { Write-Host $_.Exception.ToString() } }