replace backup appx packages with download appx

This commit is contained in:
zoicware
2025-10-05 18:51:40 -04:00
committed by GitHub
parent 47f49fe6fa
commit 8f002d86f6

View File

@@ -111,214 +111,6 @@ function Run-Trusted([String]$command, $psversion) {
} }
# function from: https://github.com/the-loan-wolf/Appx-Backup/blob/master/Appx-Backup.ps1
function Backup-Appx {
param (
[Parameter(Mandatory = $True)]
[string] $WSAppPath,
[Parameter(Mandatory = $True)]
[string] $WSAppOutputPath
)
function Get-FileFromWeb {
param (
# Parameter help description
[Parameter(Mandatory)]
[string]$URL,
# Parameter help description
[Parameter(Mandatory)]
[string]$File
)
Begin {
}
Process {
try {
$storeEAP = $ErrorActionPreference
$ErrorActionPreference = 'Stop'
# invoke request
$request = [System.Net.HttpWebRequest]::Create($URL)
$response = $request.GetResponse()
if ($response.StatusCode -eq 401 -or $response.StatusCode -eq 403 -or $response.StatusCode -eq 404) {
throw "Remote file either doesn't exist, is unauthorized, or is forbidden for '$URL'."
}
if ($File -match '^\.\\') {
$File = Join-Path (Get-Location -PSProvider 'FileSystem') ($File -Split '^\.')[1]
}
if ($File -and !(Split-Path $File)) {
$File = Join-Path (Get-Location -PSProvider 'FileSystem') $File
}
if ($File) {
$fileDirectory = $([System.IO.Path]::GetDirectoryName($File))
if (!(Test-Path($fileDirectory))) {
[System.IO.Directory]::CreateDirectory($fileDirectory) | Out-Null
}
}
[long]$fullSize = $response.ContentLength
$fullSizeMB = $fullSize / 1024 / 1024
# define buffer
[byte[]]$buffer = new-object byte[] 1048576
[long]$total = [long]$count = 0
# create reader / writer
$reader = $response.GetResponseStream()
$writer = new-object System.IO.FileStream $File, 'Create'
# start download
$finalBarCount = 0 #show final bar only one time
do {
$count = $reader.Read($buffer, 0, $buffer.Length)
$writer.Write($buffer, 0, $count)
$total += $count
$totalMB = $total / 1024 / 1024
if ($fullSize -gt 0) {
#Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix 'MB'
}
if ($total -eq $fullSize -and $count -eq 0 -and $finalBarCount -eq 0) {
#Show-Progress -TotalValue $fullSizeMB -CurrentValue $totalMB -ProgressText "Downloading $($File.Name)" -ValueSuffix 'MB' -Complete
$finalBarCount++
}
} while ($count -gt 0)
}
catch {
$ExeptionMsg = $_.Exception.Message
Write-Host "Download breaks with error : $ExeptionMsg"
}
finally {
# cleanup
if ($reader) { $reader.Close() }
if ($writer) { $writer.Flush(); $writer.Close() }
$ErrorActionPreference = $storeEAP
[GC]::Collect()
}
}
}
function Run-Process {
Param ($p, $a)
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = $p
$pinfo.Arguments = $a
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
$output = $p.StandardOutput.ReadToEnd()
$output += $p.StandardError.ReadToEnd()
$p.WaitForExit()
return $output
}
#tools path
$WSTools = "$env:TEMP\AppxBackupTools-master\tool\x64"
if (!(Test-Path $WSTools)) {
Get-FileFromWeb -URL 'https://github.com/zoicware/AppxBackupTools/archive/refs/heads/master.zip' -File "$env:TEMP\BackupTools.zip"
$ProgressPreference = 'SilentlyContinue'
Expand-Archive "$env:TEMP\BackupTools.zip" -DestinationPath $env:TEMP
}
$WSAppXmlFile = 'AppxManifest.xml'
# read manifest
$FileExists = Test-Path "$WSAppPath\$WSAppXmlFile"
if ($FileExists -eq $False) {
#temp: debug
Write-Status -msg 'ERROR: Windows Store manifest not found.'
}
[xml]$manifest = Get-Content "$WSAppPath\$WSAppXmlFile"
$WSAppName = $manifest.Package.Identity.Name
$WSAppPublisher = $manifest.Package.Identity.Publisher
# prepare
$WSAppFileName = Get-Item $WSAppPath | Select-Object basename
$WSAppFileName = $WSAppFileName.BaseName
if (Test-Path "$WSAppOutputPath\$WSAppFileName.appx") {
Remove-Item "$WSAppOutputPath\$WSAppFileName.appx"
}
$proc = "$WSTools\MakeAppx.exe"
$args = "pack /d ""$WSAppPath"" /p ""$WSAppOutputPath\$WSAppFileName.appx"" /l"
$output = Run-Process $proc $args
if ($output -inotlike '*succeeded*') {
Write-host ' ERROR: Appx creation failed!'
Write-host " proc = $proc"
Write-host " args = $args"
Write-host (' ' + $output)
# Exit
}
if (Test-Path "$WSAppOutputPath\$WSAppFileName.pvk") {
Remove-Item "$WSAppOutputPath\$WSAppFileName.pvk"
}
if (Test-Path "$WSAppOutputPath\$WSAppFileName.cer") {
Remove-Item "$WSAppOutputPath\$WSAppFileName.cer"
}
$proc = "$WSTools\MakeCert.exe"
$args = "-n ""$WSAppPublisher"" -r -a sha256 -len 2048 -cy end -h 0 -eku 1.3.6.1.5.5.7.3.3 -b 01/01/2000 -pe -sv ""$WSAppOutputPath\$WSAppFileName.pvk"" ""$WSAppOutputPath\$WSAppFileName.cer"""
$output = Run-Process $proc $args
if ($output -inotlike '*succeeded*') {
Write-host 'ERROR: Certificate creation failed!'
Write-host "proc = $proc"
Write-host "args = $args"
Write-host (' ' + $output)
# Exit
}
if (Test-Path "$WSAppOutputPath\$WSAppFileName.pfx") {
Remove-Item "$WSAppOutputPath\$WSAppFileName.pfx"
}
$proc = "$WSTools\Pvk2Pfx.exe"
$args = "-pvk ""$WSAppOutputPath\$WSAppFileName.pvk"" -spc ""$WSAppOutputPath\$WSAppFileName.cer"" -pfx ""$WSAppOutputPath\$WSAppFileName.pfx"""
$output = Run-Process $proc $args
if ($output.Length -gt 0) {
Write-host ' ERROR: Certificate conversion to pfx failed!'
Write-host " proc = $proc"
Write-host " args = $args"
Write-host (' ' + $output)
# Exit
}
$proc = "$WSTools\SignTool.exe"
$args = "sign -fd SHA256 -a -f ""$WSAppOutputPath\$WSAppFileName.pfx"" ""$WSAppOutputPath\$WSAppFileName.appx"""
$output = Run-Process $proc $args
if ($output -inotlike '*successfully signed*') {
Write-host 'ERROR: Package signing failed!'
Write-host $output.Length
Write-host "proc = $proc"
Write-host "args = $args"
Write-host (' ' + $output)
# Exit
}
Remove-Item "$WSAppOutputPath\$WSAppFileName.pvk"
Remove-Item "$WSAppOutputPath\$WSAppFileName.pfx"
}
function Write-Status { function Write-Status {
param( param(
@@ -562,29 +354,205 @@ function Disable-Copilot-Policies {
function Remove-AI-Appx-Packages { function Remove-AI-Appx-Packages {
#function from: https://github.com/Andrew-J-Larson/OS-Scripts/blob/main/Windows/Wrapper-Functions/Download-AppxPackage-Function.ps1
function Download-AppxPackage {
param(
# there has to be an alternative, as sometimes the API fails on PackageFamilyName
[string]$PackageFamilyName,
[string]$ProductId,
[string]$outputDir
)
if (-Not ($PackageFamilyName -Or $ProductId)) {
# can't do anything without at least one
Write-Error 'Missing either PackageFamilyName or ProductId.'
return $null
}
try {
$UserAgent = [Microsoft.PowerShell.Commands.PSUserAgent]::Chrome # needed as sometimes the API will block things when it knows requests are coming from PowerShell
}
catch {
#ignore error
}
$DownloadedFiles = @()
$errored = $false
$allFilesDownloaded = $true
$apiUrl = 'https://store.rg-adguard.net/api/GetFiles'
$versionRing = 'Retail'
$architecture = switch ($env:PROCESSOR_ARCHITECTURE) {
'x86' { 'x86' }
{ @('x64', 'amd64') -contains $_ } { 'x64' }
'arm' { 'arm' }
'arm64' { 'arm64' }
default { 'neutral' } # should never get here
}
if (Test-Path $outputDir -PathType Container) {
New-Item -Path "$outputDir\$PackageFamilyName" -ItemType Directory -Force | Out-Null
$downloadFolder = "$outputDir\$PackageFamilyName"
}
else {
$downloadFolder = Join-Path $env:TEMP $PackageFamilyName
if (!(Test-Path $downloadFolder -PathType Container)) {
New-Item $downloadFolder -ItemType Directory -Force | Out-Null
}
}
$body = @{
type = if ($ProductId) { 'ProductId' } else { 'PackageFamilyName' }
url = if ($ProductId) { $ProductId } else { $PackageFamilyName }
ring = $versionRing
lang = 'en-US'
}
# required due to the api being protected behind Cloudflare now
if (-Not $apiWebSession) {
$global:apiWebSession = $null
$apiHostname = (($apiUrl.split('/'))[0..2]) -Join '/'
Invoke-WebRequest -Uri $apiHostname -UserAgent $UserAgent -SessionVariable $apiWebSession -UseBasicParsing
}
$raw = $null
try {
$raw = Invoke-RestMethod -Method Post -Uri $apiUrl -ContentType 'application/x-www-form-urlencoded' -Body $body -UserAgent $UserAgent -WebSession $apiWebSession
}
catch {
$errorMsg = 'An error occurred: ' + $_
Write-Host $errorMsg
$errored = $true
return $false
}
# hashtable of packages by $name
# > values = hashtables of packages by $version
# > values = arrays of packages as objects (containing: url, filename, name, version, arch, publisherId, type)
[Collections.Generic.Dictionary[string, Collections.Generic.Dictionary[string, array]]] $packageList = @{}
# populate $packageList
$patternUrlAndText = '<tr style.*<a href=\"(?<url>.*)"\s.*>(?<text>.*\.(app|msi)x.*)<\/a>'
$raw | Select-String $patternUrlAndText -AllMatches | ForEach-Object { $_.Matches } | ForEach-Object {
$url = ($_.Groups['url']).Value
$text = ($_.Groups['text']).Value
$textSplitUnderscore = $text.split('_')
$name = $textSplitUnderscore.split('_')[0]
$version = $textSplitUnderscore.split('_')[1]
$arch = ($textSplitUnderscore.split('_')[2]).ToLower()
$publisherId = ($textSplitUnderscore.split('_')[4]).split('.')[0]
$textSplitPeriod = $text.split('.')
$type = ($textSplitPeriod[$textSplitPeriod.length - 1]).ToLower()
# create $name hash key hashtable, if it doesn't already exist
if (!($packageList.keys -match ('^' + [Regex]::escape($name) + '$'))) {
$packageList["$name"] = @{}
}
# create $version hash key array, if it doesn't already exist
if (!(($packageList["$name"]).keys -match ('^' + [Regex]::escape($version) + '$'))) {
($packageList["$name"])["$version"] = @()
}
# add package to the array in the hashtable
($packageList["$name"])["$version"] += @{
url = $url
filename = $text
name = $name
version = $version
arch = $arch
publisherId = $publisherId
type = $type
}
}
# an array of packages as objects, meant to only contain one of each $name
$latestPackages = @()
# grabs the most updated package for $name and puts it into $latestPackages
$packageList.GetEnumerator() | ForEach-Object { ($_.value).GetEnumerator() | Select-Object -Last 1 } | ForEach-Object {
$packagesByType = $_.value
$msixbundle = ($packagesByType | Where-Object { $_.type -match '^msixbundle$' })
$appxbundle = ($packagesByType | Where-Object { $_.type -match '^appxbundle$' })
$msix = ($packagesByType | Where-Object { ($_.type -match '^msix$') -And ($_.arch -match ('^' + [Regex]::Escape($architecture) + '$')) })
$appx = ($packagesByType | Where-Object { ($_.type -match '^appx$') -And ($_.arch -match ('^' + [Regex]::Escape($architecture) + '$')) })
if ($msixbundle) { $latestPackages += $msixbundle }
elseif ($appxbundle) { $latestPackages += $appxbundle }
elseif ($msix) { $latestPackages += $msix }
elseif ($appx) { $latestPackages += $appx }
}
# download packages
$latestPackages | ForEach-Object {
$url = $_.url
$filename = $_.filename
# TODO: may need to include detection in the future of expired package download URLs..... in the case that downloads take over 10 minutes to complete
$downloadFile = Join-Path $downloadFolder $filename
# If file already exists, ask to replace it
if (Test-Path $downloadFile) {
Write-Host "`"${filename}`" already exists at `"${downloadFile}`"."
$confirmation = ''
while (!(($confirmation -eq 'Y') -Or ($confirmation -eq 'N'))) {
$confirmation = Read-Host "`nWould you like to re-download and overwrite the file at `"${downloadFile}`" (Y/N)?"
$confirmation = $confirmation.ToUpper()
}
if ($confirmation -eq 'Y') {
Remove-Item -Path $downloadFile -Force
}
else {
$DownloadedFiles += $downloadFile
}
}
if (!(Test-Path $downloadFile)) {
# Write-Host "Attempting download of `"${filename}`" to `"${downloadFile}`" . . ."
$fileDownloaded = $null
$PreviousProgressPreference = $ProgressPreference
$ProgressPreference = 'SilentlyContinue' # avoids slow download when using Invoke-WebRequest
try {
Invoke-WebRequest -Uri $url -OutFile $downloadFile
$fileDownloaded = $?
}
catch {
$ProgressPreference = $PreviousProgressPreference # return ProgressPreference back to normal
$errorMsg = 'An error occurred: ' + $_
Write-Host $errorMsg
$errored = $true
break $false
}
$ProgressPreference = $PreviousProgressPreference # return ProgressPreference back to normal
if ($fileDownloaded) { $DownloadedFiles += $downloadFile }
else { $allFilesDownloaded = $false }
}
}
if ($errored) { Write-Host 'Completed with some errors.' }
if (-Not $allFilesDownloaded) { Write-Host 'Warning: Not all packages could be downloaded.' }
return $DownloadedFiles
}
if ($revert) { if ($revert) {
#install backedup appx packages
#download appx packages from store
$appxBackup = "$env:USERPROFILE\RemoveWindowsAI\Backup\AppxBackup" $appxBackup = "$env:USERPROFILE\RemoveWindowsAI\Backup\AppxBackup"
if (Test-Path $appxBackup) { if (Test-Path $appxBackup) {
$files = Get-ChildItem $appxBackup $familyNames = Get-Content "$appxBackup\PackageFamilyNames.txt"
Write-Status -msg 'Installing Appx Packages...' foreach ($package in $familyNames) {
foreach ($file in $files) { Write-Status -msg "Attempting to Download $package..."
if ($file.FullName -like '*.cer') { $downloadedFiles = Download-AppxPackage -PackageFamilyName $package -outputDir $appxBackup
#install certs $bundle = $downloadedFiles | Where-Object { $_ -match '\.appxbundle$' -or $_ -match '\.msixbundle$' } | Select-Object -First 1
Import-Certificate -FilePath $file.FullName -CertStoreLocation Cert:\LocalMachine\Root | Out-Null if ($bundle) {
} Write-Status -msg "Installing $package..."
} Add-AppPackage $bundle
#install the packages
$ProgressPreference = 'SilentlyContinue'
foreach ($file in $files) {
if ($file.FullName -like '*.appx') {
Add-AppPackage -Path $file.FullName
} }
} }
#cleanup
Remove-Item "$appxBackup\*" -Recurse -Force -ErrorAction SilentlyContinue
} }
else { else {
Write-Status -msg 'Unable to Find AppxBackup in User Directory!' -errorOutput $true Write-Status -msg 'Unable to Find AppxBackup in User Directory!' -errorOutput $true
} }
} }
else { else {
@@ -635,23 +603,20 @@ function Remove-AI-Appx-Packages {
) )
if ($backup) { if ($backup) {
#backup appx packages before removing them
#create file with package family names for reverting
$appxBackup = "$env:USERPROFILE\RemoveWindowsAI\Backup\AppxBackup" $appxBackup = "$env:USERPROFILE\RemoveWindowsAI\Backup\AppxBackup"
if (!(Test-Path $appxBackup)) { if (!(Test-Path $appxBackup)) {
New-Item $appxBackup -ItemType Directory -Force | Out-Null New-Item $appxBackup -ItemType Directory -Force | Out-Null
} }
Write-Status -msg 'Backing Up AI Appx Packages...' $backuppath = New-Item $appxBackup -Name 'PackageFamilyNames.txt' -ItemType File -Force
#aix and core ai can not be backed up
$packagesToBackup = get-appxpackage -AllUsers | Where-Object { $aipackages -contains $_.Name } | Where-Object { $_.Name -ne 'MicrosoftWindows.Client.AIX' -and $_.Name -ne 'MicrosoftWindows.Client.CoreAI' } $familyNames = get-appxpackage -allusers | Where-Object { $aipackages -contains $_.Name }
[System.Windows.MessageBox]::Show('Please Select [NONE] On The Following Windows' , 'INFO', [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information) *>$null foreach ($familyName in $familyNames) {
foreach ($package in $packagesToBackup) { Add-Content -Path $backuppath.FullName -Value $familyName.PackageFamilyName
Backup-Appx -WSAppPath $package.InstallLocation -WSAppOutputPath $appxBackup
} }
#cleanup tools
Remove-Item "$env:TEMP\AppxBackupTools-master\tool\x64" -Force -Recurse -ErrorAction SilentlyContinue
Remove-Item "$env:TEMP\BackupTools.zip" -Force -ErrorAction SilentlyContinue
} }
$code = @' $code = @'
@@ -700,7 +665,6 @@ foreach ($choice in $aipackages) {
$PackageName = $appx.PackageName $PackageName = $appx.PackageName
$PackageFamilyName = ($appxpackage | Where-Object { $_.Name -eq $appx.DisplayName }).PackageFamilyName $PackageFamilyName = ($appxpackage | Where-Object { $_.Name -eq $appx.DisplayName }).PackageFamilyName
New-Item "$store\Deprovisioned\$PackageFamilyName" -force New-Item "$store\Deprovisioned\$PackageFamilyName" -force
Set-NonRemovableAppsPolicy -Online -PackageFamilyName $PackageFamilyName -NonRemovable 0 Set-NonRemovableAppsPolicy -Online -PackageFamilyName $PackageFamilyName -NonRemovable 0
@@ -715,7 +679,6 @@ foreach ($choice in $aipackages) {
$PackageFullName = $appx.PackageFullName $PackageFullName = $appx.PackageFullName
$PackageFamilyName = $appx.PackageFamilyName $PackageFamilyName = $appx.PackageFamilyName
New-Item "$store\Deprovisioned\$PackageFamilyName" -force New-Item "$store\Deprovisioned\$PackageFamilyName" -force
Set-NonRemovableAppsPolicy -Online -PackageFamilyName $PackageFamilyName -NonRemovable 0 Set-NonRemovableAppsPolicy -Online -PackageFamilyName $PackageFamilyName -NonRemovable 0
#remove inbox apps #remove inbox apps