From 20104565c3dfb3ca41d9dcadd892165e23d1e4db Mon Sep 17 00:00:00 2001
From: ThioJoe <12518330+ThioJoe@users.noreply.github.com>
Date: Sun, 3 Aug 2025 20:15:47 -0700
Subject: [PATCH] Initial Commit
---
Installer Scripts/Install-Microsoft-Store.ps1 | 484 ++++++++++++++++++
Installer Scripts/Install-Winget.ps1 | 156 ++++++
LICENSE | 21 +
README.md | 2 +
Sandbox Configurations/MyDefaultSandbox.wsb | 45 ++
Sandbox Configurations/UseGoogleDNS.wsb | 9 +
Startup Scripts/SandboxStartup.ps1 | 50 ++
Theme Scripts/Set Theme Dark Mode.ps1 | 28 +
Theme Scripts/Set Theme Light Mode.ps1 | 10 +
9 files changed, 805 insertions(+)
create mode 100644 Installer Scripts/Install-Microsoft-Store.ps1
create mode 100644 Installer Scripts/Install-Winget.ps1
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 Sandbox Configurations/MyDefaultSandbox.wsb
create mode 100644 Sandbox Configurations/UseGoogleDNS.wsb
create mode 100644 Startup Scripts/SandboxStartup.ps1
create mode 100644 Theme Scripts/Set Theme Dark Mode.ps1
create mode 100644 Theme Scripts/Set Theme Light Mode.ps1
diff --git a/Installer Scripts/Install-Microsoft-Store.ps1 b/Installer Scripts/Install-Microsoft-Store.ps1
new file mode 100644
index 0000000..1723049
--- /dev/null
+++ b/Installer Scripts/Install-Microsoft-Store.ps1
@@ -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 = @"
+
+
+ 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()
+ }
+}
diff --git a/Installer Scripts/Install-Winget.ps1 b/Installer Scripts/Install-Winget.ps1
new file mode 100644
index 0000000..dd16923
--- /dev/null
+++ b/Installer Scripts/Install-Winget.ps1
@@ -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)"
+ }
+}
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..210b917
--- /dev/null
+++ b/LICENSE
@@ -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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..179c75b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# Windows-Sandbox-Tools
+Various useful scripts for use within Windows Sandbox
diff --git a/Sandbox Configurations/MyDefaultSandbox.wsb b/Sandbox Configurations/MyDefaultSandbox.wsb
new file mode 100644
index 0000000..99b3e7a
--- /dev/null
+++ b/Sandbox Configurations/MyDefaultSandbox.wsb
@@ -0,0 +1,45 @@
+
+
+
+ Enable
+
+ Enable
+ 12288
+
+
+ Disable
+ Disable
+ Disabled
+ Disable
+
+
+ Enable
+
+
+
+
+
+
+ B:\Caches\Sandbox_Share
+ C:\Users\WDAGUtilityAccount\Desktop\HostShared
+ true
+
+
+
+
+
+ powershell -executionpolicy Bypass -command "start powershell {-file C:\Users\WDAGUtilityAccount\Desktop\HostShared\SandboxStartup.ps1}"
+
+
+
+
+
+
+
+
diff --git a/Sandbox Configurations/UseGoogleDNS.wsb b/Sandbox Configurations/UseGoogleDNS.wsb
new file mode 100644
index 0000000..44681e7
--- /dev/null
+++ b/Sandbox Configurations/UseGoogleDNS.wsb
@@ -0,0 +1,9 @@
+
+
+ Enable
+
+
+ netsh interface ipv4 set dnsservers "Ethernet" static 8.8.8.8 primary
+
+
+
diff --git a/Startup Scripts/SandboxStartup.ps1 b/Startup Scripts/SandboxStartup.ps1
new file mode 100644
index 0000000..a9ea86f
--- /dev/null
+++ b/Startup Scripts/SandboxStartup.ps1
@@ -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"
diff --git a/Theme Scripts/Set Theme Dark Mode.ps1 b/Theme Scripts/Set Theme Dark Mode.ps1
new file mode 100644
index 0000000..6109ad5
--- /dev/null
+++ b/Theme Scripts/Set Theme Dark Mode.ps1
@@ -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."
diff --git a/Theme Scripts/Set Theme Light Mode.ps1 b/Theme Scripts/Set Theme Light Mode.ps1
new file mode 100644
index 0000000..9dc546e
--- /dev/null
+++ b/Theme Scripts/Set Theme Light Mode.ps1
@@ -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."