Initial Commit

pull/2/head
ThioJoe 2025-08-03 20:15:47 -07:00
commit 20104565c3
No known key found for this signature in database
GPG Key ID: 2E328FE64CC3898C
9 changed files with 805 additions and 0 deletions

View File

@ -0,0 +1,484 @@
# 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 = @"
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetCookie</a:Action>
<a:MessageID>urn:uuid:$(New-Guid)</a:MessageID>
<a:To s:mustUnderstand="1">https://fe3.delivery.mp.microsoft.com/ClientWebService/client.asmx</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<wuws:WindowsUpdateTicketsToken wsu:id="ClientMSA" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wuws="http://schemas.microsoft.com/msus/2014/10/WindowsUpdateAuthorization">
<TicketType Name="MSA" Version="1.0" Policy="MBI_SSL"><user></user></TicketType>
</wuws:WindowsUpdateTicketsToken>
</o:Security>
</s:Header>
<s:Body><GetCookie xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService" /></s:Body>
</s:Envelope>
"@
# 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 = @"
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/SyncUpdates</a:Action>
<a:MessageID>urn:uuid:$(New-Guid)</a:MessageID>
<a:To s:mustUnderstand="1">https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<Timestamp xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<Created>$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"))</Created>
<Expires>$((Get-Date).AddMinutes(5).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"))</Expires>
</Timestamp>
<wuws:WindowsUpdateTicketsToken wsu:id="ClientMSA" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wuws="http://schemas.microsoft.com/msus/2014/10/WindowsUpdateAuthorization">
<TicketType Name="MSA" Version="1.0" Policy="MBI_SSL">
<user/>
</TicketType>
</wuws:WindowsUpdateTicketsToken>
</o:Security>
</s:Header>
<s:Body>
<SyncUpdates xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
<cookie>
<Expiration>$((Get-Date).AddYears(10).ToUniversalTime().ToString('u').Replace(' ','T'))</Expiration>
<EncryptedData>{0}</EncryptedData>
</cookie>
<parameters>
<ExpressQuery>false</ExpressQuery>
<InstalledNonLeafUpdateIDs>
<int>1</int><int>2</int><int>3</int><int>11</int><int>19</int><int>2359974</int><int>5169044</int>
<int>8788830</int><int>23110993</int><int>23110994</int><int>54341900</int><int>59830006</int><int>59830007</int>
<int>59830008</int><int>60484010</int><int>62450018</int><int>62450019</int><int>62450020</int><int>98959022</int>
<int>98959023</int><int>98959024</int><int>98959025</int><int>98959026</int><int>104433538</int><int>129905029</int>
<int>130040031</int><int>132387090</int><int>132393049</int><int>133399034</int><int>138537048</int><int>140377312</int>
<int>143747671</int><int>158941041</int><int>158941042</int><int>158941043</int><int>158941044</int><int>159123858</int>
<int>159130928</int><int>164836897</int><int>164847386</int><int>164848327</int><int>164852241</int><int>164852246</int>
<int>164852253</int>
</InstalledNonLeafUpdateIDs>
<SkipSoftwareSync>false</SkipSoftwareSync>
<NeedTwoGroupOutOfScopeUpdates>false</NeedTwoGroupOutOfScopeUpdates>
<FilterAppCategoryIds>
<CategoryIdentifier>
<Id>{1}</Id>
</CategoryIdentifier>
</FilterAppCategoryIds>
<TreatAppCategoryIdsAsInstalled>true</TreatAppCategoryIdsAsInstalled>
<AlsoPerformRegularSync>false</AlsoPerformRegularSync>
<ComputerSpec/>
<ExtendedUpdateInfoParameters>
<XmlUpdateFragmentTypes>
<XmlUpdateFragmentType>Extended</XmlUpdateFragmentType>
</XmlUpdateFragmentTypes>
<Locales>
<string>en-US</string>
<string>en</string>
</Locales>
</ExtendedUpdateInfoParameters>
<ClientPreferredLanguages>
<string>en-US</string>
</ClientPreferredLanguages>
<ProductsParameters>
<SyncCurrentVersionOnly>false</SyncCurrentVersionOnly>
<DeviceAttributes>E:BranchReadinessLevel=CB&amp;CurrentBranch=rs_prerelease&amp;OEMModel=Virtual%20Machine&amp;FlightRing=Retail&amp;AttrDataVer=321&amp;InstallLanguage=en-US&amp;OSUILocale=en-US&amp;InstallationType=Client&amp;FlightingBranchName=&amp;OSSkuId=48&amp;App=WU_STORE&amp;ProcessorManufacturer=GenuineIntel&amp;OEMName_Uncleaned=Microsoft%20Corporation&amp;AppVer=1407.2503.28012.0&amp;OSArchitecture=AMD64&amp;IsFlightingEnabled=1&amp;TelemetryLevel=1&amp;DefaultUserRegion=39070&amp;WuClientVer=1310.2503.26012.0&amp;OSVersion=10.0.26100.3915&amp;DeviceFamily=Windows.Desktop</DeviceAttributes>
<CallerAttributes>Interactive=1;IsSeeker=1;</CallerAttributes>
<Products/>
</ProductsParameters>
</parameters>
</SyncUpdates>
</s:Body>
</s:Envelope>
"@
# 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 = @"
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
<a:Action s:mustUnderstand="1">http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/GetExtendedUpdateInfo2</a:Action>
<a:MessageID>urn:uuid:$(New-Guid)</a:MessageID>
<a:To s:mustUnderstand="1">https://fe3cr.delivery.mp.microsoft.com/ClientWebService/client.asmx/secured</a:To>
<o:Security s:mustUnderstand="1" xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0" xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<u:Created>$((Get-Date).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"))</u:Created>
<u:Expires>$((Get-Date).AddMinutes(5).ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'"))</u:Expires>
</u:Timestamp>
<wuws:WindowsUpdateTicketsToken wsu:id="ClientMSA" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wuws="http://schemas.microsoft.com/msus/2014/10/WindowsUpdateAuthorization">
<TicketType Name="MSA" Version="1.0" Policy="MBI_SSL"><user>{0}</user></TicketType>
</wuws:WindowsUpdateTicketsToken>
</o:Security>
</s:Header>
<s:Body>
<GetExtendedUpdateInfo2 xmlns="http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService">
<updateIDs><UpdateIdentity><UpdateID>{1}</UpdateID><RevisionNumber>{2}</RevisionNumber></UpdateIdentity></updateIDs>
<infoTypes><XmlUpdateFragmentType>FileUrl</XmlUpdateFragmentType></infoTypes>
<DeviceAttributes>E:BranchReadinessLevel=CB&amp;CurrentBranch=rs_prerelease&amp;OEMModel=Virtual%20Machine&amp;FlightRing={3}&amp;AttrDataVer=321&amp;InstallLanguage=en-US&amp;OSUILocale=en-US&amp;InstallationType=Client&amp;FlightingBranchName=&amp;OSSkuId=48&amp;App=WU_STORE&amp;ProcessorManufacturer=GenuineIntel&amp;OEMName_Uncleaned=Microsoft%20Corporation&amp;AppVer=1407.2503.28012.0&amp;OSArchitecture=AMD64&amp;IsFlightingEnabled=1&amp;TelemetryLevel=1&amp;DefaultUserRegion=39070&amp;WuClientVer=1310.2503.26012.0&amp;OSVersion=10.0.26100.3915&amp;DeviceFamily=Windows.Desktop</DeviceAttributes>
</GetExtendedUpdateInfo2>
</s:Body>
</s:Envelope>
"@
# --- 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 <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
# 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.
# <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
}
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()
}
}

View File

@ -0,0 +1,156 @@
# 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
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)"
}
}

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 ThioJoe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Windows-Sandbox-Tools
Various useful scripts for use within Windows Sandbox

View File

@ -0,0 +1,45 @@
<Configuration>
<!-- Docs say this may affect ability to copy and paste, but that still seems to work -->
<ProtectedClient>Enable</ProtectedClient>
<Networking>Enable</Networking>
<MemoryInMB>12288</MemoryInMB> <!-- Adjust allocated memory as desired. Default is only 4GB -->
<!-- Options for improved security -->
<VideoInput>Disable</VideoInput>
<AudioInput>Disable</AudioInput>
<PrinterRedirection>Disabled</PrinterRedirection>
<vGPU>Disable</vGPU>
<!-- Enables or disables sharing of the host clipboard with the sandbox. (Disable / Default) -->
<ClipboardRedirection>Enable</ClipboardRedirection>
<!-- Allow read only access to folder on host -->
<MappedFolders>
<!-- Create a read-only folder mapped to a folder on host -->
<MappedFolder>
<!-- Update the HostFolder path to the one on your real computer that will be shared with the Sandbox -->
<HostFolder>B:\Caches\Sandbox_Share</HostFolder>
<SandboxFolder>C:\Users\WDAGUtilityAccount\Desktop\HostShared</SandboxFolder>
<ReadOnly>true</ReadOnly>
</MappedFolder>
</MappedFolders>
<!-- Run the powershell script from the mapped folder -->
<LogonCommand>
<Command>powershell -executionpolicy Bypass -command "start powershell {-file C:\Users\WDAGUtilityAccount\Desktop\HostShared\SandboxStartup.ps1}"</Command>
<!-- Use this one below instead if you want the powershell window to not close after running the script -->
<!-- <Command>powershell -executionpolicy Bypass -command "start powershell {-noexit -file C:\Users\WDAGUtilityAccount\HostShared\SandboxStartup.ps1}"</Command> -->
</LogonCommand>
</Configuration>
<!-- Other Settings Examples
See: https://learn.microsoft.com/en-us/windows/security/application-security/application-isolation/windows-sandbox/windows-sandbox-configure-using-wsb-file
Anything outside <configuration> tags don't apply
More secure but no clipboard sharing
<ProtectedClient>Disable</ProtectedClient>
-->

View File

@ -0,0 +1,9 @@
<Configuration>
<Networking>Enable</Networking>
<LogonCommand>
<Command>netsh interface ipv4 set dnsservers "Ethernet" static 8.8.8.8 primary</Command>
</LogonCommand>
</Configuration>

View File

@ -0,0 +1,50 @@
# Change context menu to old style
reg.exe add "HKCU\Software\Classes\CLSID\{86ca1aa0-34aa-4e8b-a509-50c905bae2a2}\InprocServer32" /f /ve
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Start_ShowClassicMode" /t REG_DWORD /d 1 /f
# Show file extensions
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "HideFileExt" /t REG_DWORD /d 0 /f
# Show hidden files
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced" /v "Hidden" /t REG_DWORD /d 1 /f
# ---- Add 'Edit With Notepad' and 'Open Notepad' to context menu -------
# Set the path to your specific Notepad executable which you'll put in the shared folder, since notepad isn't included in the sandbox for some reason
# 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"
reg add "HKEY_CLASSES_ROOT\*\shell\Edit with Notepad" /f
reg add "HKEY_CLASSES_ROOT\*\shell\Edit with Notepad" /v "Icon" /t REG_SZ /d "$notepadPath,0" /f
reg add "HKEY_CLASSES_ROOT\*\shell\Edit with Notepad\command" /ve /d "`"$notepadPath`" `"%1`"" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Notepad" /ve /d "Open Notepad" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Notepad" /v "Icon" /t REG_SZ /d "$notepadPath,0" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Notepad\command" /ve /d "`"$notepadPath`"" /f
# ---- Add 'Open PowerShell Here' and 'Open CMD Here' to context menu -------
$powershellPath = "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
$cmdPath = "C:\Windows\System32\cmd.exe"
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\MyPowerShell" /ve /d "Open PowerShell Here" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\MyPowerShell" /v "Icon" /t REG_SZ /d "$powershellPath,0" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\MyPowerShell\command" /ve /d "powershell.exe -noexit -command Set-Location -literalPath '%V'" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Mycmd" /ve /d "Open CMD Here" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Mycmd" /v "Icon" /t REG_SZ /d "$cmdPath,0" /f
reg add "HKEY_CLASSES_ROOT\Directory\Background\shell\Mycmd\command" /ve /d "cmd.exe /s /k cd /d `"\`"%V`"\`"" /f
# ---- Set .txt files to open with Notepad, Create txt option in Context Menu 'New' list
cmd /c assoc .txt=txtfile
cmd /c ftype txtfile=`"$notepadPath`" "%1"
reg add "HKEY_CLASSES_ROOT\txtfile" /ve /d "Text Document" /f
reg add "HKEY_CLASSES_ROOT\.txt\ShellNew" /f
reg add "HKEY_CLASSES_ROOT\.txt\ShellNew" /v "NullFile" /t REG_SZ /d "" /f
reg add "HKEY_CLASSES_ROOT\.txt\ShellNew" /v "ItemName" /t REG_SZ /d "New Text Document" /f
# -------------------------------------------------------------------------
# Restart Explorer so changes take effect
Stop-Process -Name explorer -Force
# Open an explorer window to the host-shared folder
Start-Process explorer.exe C:\Users\WDAGUtilityAccount\Desktop\HostShared
# Change execution policy for powershell to allow running scripts
Set-ExecutionPolicy -ExecutionPolicy unrestricted -Scope LocalMachine
# Uncomment to pause after running
#Read-Host "Pause"

View File

@ -0,0 +1,28 @@
# Enable Dark Mode for Apps
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "AppsUseLightTheme" -Value 0
# Enable Dark Mode for System
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "SystemUsesLightTheme" -Value 0
# Set the Wallpaper
$wallpaperPath = "C:\Windows\Web\Wallpaper\Windows\img19.jpg"
$code = @'
using System.Runtime.InteropServices;
public class Wallpaper {
[DllImport("user32.dll", CharSet=CharSet.Auto)]
public static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
}
'@
Add-Type $code
$SPI_SETDESKWALLPAPER = 0x0014
$UPDATE_INI_FILE = 0x01
$SEND_CHANGE = 0x02
[Wallpaper]::SystemParametersInfo($SPI_SETDESKWALLPAPER, 0, $wallpaperPath, ($UPDATE_INI_FILE -bor $SEND_CHANGE))
# Restart Explorer to apply changes
Write-Host "Restarting Explorer..."
Stop-Process -Name explorer -Force
Start-Process explorer
Write-Host "Dark mode enabled and wallpaper updated successfully! Explorer has been restarted."

View File

@ -0,0 +1,10 @@
# Enable Light Mode for Apps
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "AppsUseLightTheme" -Value 1
# Enable Light Mode for System
Set-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize" -Name "SystemUsesLightTheme" -Value 1
# Restart Explorer to apply changes
Write-Host "Restarting Explorer..."
Stop-Process -Name explorer -Force
Start-Process explorer
Write-Host "Light mode enabled! Explorer has been restarted."