Install-WindowsStoreMinimal.ps1
|
<#PSScriptInfo
.VERSION 1.0.1 .GUID 94b0cf8f-12aa-4c53-ac47-ffff60587fe2 .AUTHOR Joel055 .TAGS WindowsStore MicrosoftStore AppX MSIX Restore .LICENSEURI https://github.com/Joel055/Install-WindowsStoreMinimal/blob/master/LICENSE .PROJECTURI https://github.com/Joel055/Install-WindowsStoreMinimal .RELEASENOTES - Added browser-like rg-adguard request handling to reduce Cloudflare/API blocking issues. - Added Microsoft package signature validation before installation. - Added optional Winget/App Installer support via -IncludeWinget. - Added Microsoft.WindowsAppRuntime.1.8 dependency for newer App Installer versions. - Improved installed-version checks to avoid installing older packages if the local version is somehow newer. - Broadened package matching for AppX/MSIX bundle changes. .DESCRIPTION Installs the minimal latest Microsoft Store dependency set required for Store and optionally winget functionality on Windows, without installing the full Store app suite. Resolves official Store packages via rg-adguard.net. #> #Requires -RunAsAdministrator <# .SYNOPSIS Installs the minimal set of Microsoft Store dependencies required for Microsoft Store functionality, with optional WinGet/App Installer support. Resolves official Store packages via rg-adguard.net and installs them offline using Add-AppxPackage. .NOTES Requires administrative privileges. For x64 installations only. May set the Microsoft Account Sign-in Assistant (wlidsvc) service to Manual if disabled. rg-adguard.net is not affiliated with Microsoft and is used at your own risk. .PARAMETER IncludeWinget Includes WinGet/App Installer support as well. .PARAMETER KeepDownloads Prevents deletion of downloaded package files (subsequent runs will reset the folder). .PARAMETER Force Bypasses the initial Store-installed check and queues packages even when the same version is already installed. Newer local package versions are still skipped. .EXAMPLE Install-WindowsStoreMinimal .EXAMPLE .\Install-WindowsStoreMinimal.ps1 -IncludeWinget -KeepDownloads #> [CmdletBinding()] param( [switch]$IncludeWinget, [switch]$KeepDownloads, [switch]$Force ) $bitsSvc = "BITS" $msSigninSvc = "wlidsvc" $downloadPath = "$env:TEMP\WindowsStoreMinimal" $packageTable = @( [pscustomobject]@{ Target = "Store" FamilyBase = "Microsoft.WindowsStore" Publisher = "8wekyb3d8bbwe" IgnoreVer = $false Patterns = @( "Microsoft\.VCLibs\.[^A-Za-z]*x64.*\.appx" "Microsoft\.UI\.Xaml\..*x64.*\.appx" "Microsoft\.NET\.Native\.Framework\..*x64.*\.appx" "Microsoft\.NET\.Native\.Runtime\..*x64.*\.appx" "Microsoft\.WindowsStore.*neutral.*\.(appxbundle|msixbundle)" ) } [pscustomobject]@{ Target = "Store" FamilyBase = "Microsoft.StorePurchaseApp" Publisher = "8wekyb3d8bbwe" IgnoreVer = $false Patterns = @("Microsoft\.StorePurchaseApp.*neutral.*\.(appxbundle|msixbundle)") } [pscustomobject]@{ Target = "Winget" FamilyBase = "Microsoft.WindowsAppRuntime.1.8" Publisher = "8wekyb3d8bbwe" IgnoreVer = $false Patterns = @("Microsoft\.WindowsAppRuntime\.1\.8.*(x64|neutral).*\.(appx|msix)") } [pscustomobject]@{ Target = "Winget" FamilyBase = "Microsoft.DesktopAppInstaller" Publisher = "8wekyb3d8bbwe" IgnoreVer = $true # DesktopAppInstaller's bundle version always mismatches local install (manifest ver), skip version check Patterns = @("Microsoft\.DesktopAppInstaller.*neutral.*\.(appxbundle|msixbundle)") } ) $packageTable = $packageTable | Where-Object { $_.Target -eq "Store" -or ($IncludeWinget -and $_.Target -eq "Winget") } function Invoke-RgAdguardRequest { param( $FamilyBase, $Publisher ) $baseUrl = "https://store.rg-adguard.net" $apiUrl = "$baseUrl/api/GetFiles" $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession $basicHeaders = @{ "User-Agent" = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" "Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" "Accept-Language" = "en-US,en;q=0.9" "Upgrade-Insecure-Requests" = "1" } try { Invoke-WebRequest ` -Uri $baseUrl ` -Method GET ` -Headers $basicHeaders ` -WebSession $session ` -UseBasicParsing ` -ErrorAction Stop | Out-Null } catch [System.Net.WebException] { throw "Cannot reach store.rg-adguard.net over HTTPS. Internet access, DNS, proxy, or firewall may be blocking the connection. Original error: $($_.Exception.Message)" } catch { throw "Failed to open rg-adguard front page. Original error: $($_.Exception.Message)" } $postHeaders = @{ "User-Agent" = $basicHeaders["User-Agent"] "Accept" = "*/*" "Accept-Language" = "en-US,en;q=0.9" "Content-Type" = "application/x-www-form-urlencoded; charset=UTF-8" "X-Requested-With" = "XMLHttpRequest" "Origin" = $baseUrl "Referer" = "$baseUrl/" } $body = @{ type = "PackageFamilyName" url = "$($FamilyBase)_$($Publisher)" ring = "Retail" lang = "en-US" } try { $response = Invoke-WebRequest ` -Uri $apiUrl ` -Method POST ` -Body $body ` -Headers $postHeaders ` -WebSession $session ` -UseBasicParsing ` -ErrorAction Stop } catch { throw "Cannot reach store.rg-adguard.net over HTTPS. Internet access, DNS, proxy, or firewall may be blocking the connection. Original error: $($_.Exception.Message)" } if ( $response.Content -match 'Just a moment' -or $response.Content -match 'Enable JavaScript and cookies' -or $response.Content -match '/cdn-cgi/challenge-platform/' -or $response.Content -match 'cf_chl' -or $response.Content -match 'cf_clearance' ) { throw "rg-adguard returned a Cloudflare challenge instead of package metadata. The endpoint is currently blocking scripted access." } return $response } function Get-AdguardLinks { param( $PackageTable ) $packageURLs = @() foreach ($pkg in $PackageTable) { Write-Host "Checking: $($pkg.FamilyBase)" -ForegroundColor Yellow $response = Invoke-RgAdguardRequest -FamilyBase $pkg.FamilyBase -Publisher $pkg.Publisher foreach ($pattern in $pkg.Patterns) { $versions = $response.Links | Where-Object { $_.outerHTML -match $pattern } | ForEach-Object { if ($_.outerHTML -match '">(.*?)(?=_(x64|neutral)).*(\.[^.]+)</') { $name = $matches[1] $arch = $matches[2] $ext = $matches[3] # .appx / .appxbundle / .msixbundle etc if ($arch -eq 'neutral') { $arch = 'x64' } # Normalize version strings to a numeric value for comparison; Store package versions are inconsistent. [long]$ver = $name -replace "[^0-9]", "" [PSCustomObject]@{ Name = $name Architecture = $arch Extension = $ext Version = $ver URL = $_.href } } } # Fail fast at first sign of dependency-mismatch, avoids partial installations. if (-not $versions) { throw "Dependency resolution failed for $($pkg.FamilyBase) (pattern '$pattern'). Store dependency metadata has changed; this script is out of date." } $maxLen = ($versions.Version | ForEach-Object { $_.ToString().Length } | Measure-Object -Maximum).Maximum $latest = $versions | Sort-Object @{Expression={ $_.Version.ToString().PadRight($maxLen,'0') }} -Descending | Select-Object -First 1 Write-Host " Package : " -NoNewline Write-Host "$($latest.Name)" -ForegroundColor Cyan $installed = $null $local = $null if ($pkg.IgnoreVer) { $local = Get-AppxPackage -AllUsers -Name $pkg.FamilyBase -ErrorAction SilentlyContinue | Select-Object -First 1 if ($local -and -not $Force) { $installed = $local } } else { if ($latest.Name -match '^(?<PkgName>.+)_(?<PkgVer>\d+\.\d+\.\d+\.\d+)$') { $onlineName = $matches.PkgName $onlineVer = [version]$matches.PkgVer $local = Get-AppxPackage -AllUsers -Name $onlineName -ErrorAction SilentlyContinue | Sort-Object @{ Expression = { [version]$_.Version } } -Descending | Select-Object -First 1 if ($local) { $localVer = [version]$local.Version if ( ($localVer -gt $onlineVer) -or ($localVer -eq $onlineVer -and -not $Force) ) { $installed = $local } } } } if ($installed) { Write-host " Already installed or newer, skipping`n" -ForegroundColor DarkGray continue } else { Write-Host " Queued for download/install`n" -ForegroundColor Green } $packageURLs += $latest } } $packageURLs } function Test-PackageSignature { param( $Path ) $sig = Get-AuthenticodeSignature -LiteralPath $Path if ($sig.Status -ne "Valid") { throw "Invalid signature on '$Path'. Status: $($sig.Status)" } $expectedSubject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" if ($sig.SignerCertificate.Subject -ne $expectedSubject) { throw "Unexpected signer on '$Path'. Signer: $($sig.SignerCertificate.Subject)" } } # Avoid running the script unnecessarily $isStoreInstalled = Get-AppxPackage -AllUsers -Name "Microsoft.WindowsStore" -ErrorAction SilentlyContinue $isWingetInstalled = Get-AppxPackage -AllUsers -Name "Microsoft.DesktopAppInstaller" -ErrorAction SilentlyContinue if (-not $Force) { if (-not $IncludeWinget -and $isStoreInstalled) { Write-Warning "Microsoft Store is already installed. Use -Force to re-check packages and reinstall matching versions." return } if ($IncludeWinget -and $isStoreInstalled -and $isWingetInstalled) { Write-Warning "Microsoft Store and WinGet/App Installer are already installed. Use -Force to re-check packages and reinstall matching versions." return } } if ((Get-Service -Name $bitsSvc).StartType -eq "Disabled") { throw "BITS service is disabled but required for dependency downloads." } # Make sure MS sign-in assistant service is enabled, needed for Store auth if ((Get-Service -Name $msSigninSvc).StartType -eq "Disabled") { Set-Service -Name $msSigninSvc -StartupType Manual | Out-Null Write-Warning "Startup-type of service `"$msSigninSvc`" set to Manual, from Disabled`n" Start-Service -Name $msSigninSvc -ErrorAction Stop | Out-Null } $packages = Get-AdguardLinks -PackageTable $packageTable if (-not $packages) { Write-Host "All dependencies are already up to date." -ForegroundColor Yellow return } if (-not (Test-Path -Path $downloadPath)) { New-Item -ItemType Directory -Force -Path $downloadPath | Out-Null } else { # Remove old files from previous runs Get-ChildItem -Path $downloadPath -Recurse | Remove-Item -Force -Recurse } foreach ($pkt in $packages) { Write-Host "Processing: $($pkt.Name)" -ForegroundColor Yellow $outFile = Join-Path $downloadPath ($pkt.Name + $pkt.Extension) Write-Host " Downloading..." -ForegroundColor Cyan Start-BitsTransfer -Source $pkt.URL -Destination $outFile -ErrorAction Stop Write-Host " Verifying signature..." -ForegroundColor Cyan Test-PackageSignature -Path $outFile Write-Host " Installing...`n" -ForegroundColor Cyan Add-AppxPackage -Path $outFile -ForceApplicationShutdown -ErrorAction Stop } if (-not $KeepDownloads) { Remove-Item -Path $downloadPath -Recurse -Force } else { Write-Host "Downloaded package-files can be found at: `"$($downloadPath)`"`n" } Write-Host "Done!`n" -ForegroundColor Green |