Last active
October 12, 2023 17:00
-
-
Save mavaddat/26146d111abf62f6160b1bd02a392ba8 to your computer and use it in GitHub Desktop.
This correctly downloads neutral and x64 packages but untested for arm and 32bit systems. The path must point to a folder.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Usage (for one URI): | |
<# | |
Import-Module -Name Invoke-DownloadAppxPackage.ps1 | |
$URI = 'https://www.microsoft.com/store/productId/9P6RC76MSMMJ' # From Windows Store 'share' | |
if( Get-Command -Name Get-AppxPackageDownload -CommandType Function ) { | |
Get-AppxPackageDownload -Uri $URI -Path $env:TEMP # Use -Force to skip confirmation | |
} else { | |
Write-Host 'Get-AppxPackageDownload function not found' | |
} | |
#> | |
# Usage (for multiple URIs): | |
<# | |
Import-Module -Name Invoke-DownloadAppxPackage.ps1 | |
$URIs = @('https://www.microsoft.com/store/productId/9P6RC76MSMMJ', 'https://www.microsoft.com/store/productId/9N0866FS04W8', 'https://www.microsoft.com/store/productId/9NH2GPH4JZS4') | |
if( Get-Command -Name Get-AppxPackageDownload -CommandType Function ) { | |
$URIs | ForEach-Object { | |
Get-AppxPackageDownload -Uri $_ -Path $env:TEMP -Force # Use -Force to skip confirmation | |
} | |
} else { | |
Write-Host 'Get-AppxPackageDownload function not found' | |
} | |
#> | |
function Invoke-DownloadAppxPackage | |
{ | |
[CmdletBinding(SupportsShouldProcess)] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline)] | |
[ValidateScript({ [uri]::TryCreate($_, [UriKind]::Absolute, [ref]$null) })] | |
[string]$Uri, | |
[ValidateScript({ Test-Path -Path $_ -PathType Container })] | |
[string]$OutputDir = $env:TEMP | |
) | |
process | |
{ | |
if ($WhatIfPreference) | |
{ | |
$OutputDir = $env:TEMP | |
} | |
else | |
{ | |
$OutputDir = (Resolve-Path -Path $OutputDir).Path | |
} | |
#Get Urls to download | |
$WebResponse = Invoke-WebRequest -Uri 'https://store.rg-adguard.net/api/GetFiles' -Method Post -Body "type=url&url=$Uri&ring=Retail" -ContentType 'application/x-www-form-urlencoded' | |
$LinksMatch = $WebResponse.Links | Where-Object { $_ -like '*.appx*' } | Where-Object { $_ -like '*_neutral_*' -or $_ -like '*_' + $env:PROCESSOR_ARCHITECTURE.Replace('AMD', 'X').Replace('IA', 'X') + '_*' } | Select-String -Pattern '(?<=a href=").+(?=" r)' | Select-Object -ExpandProperty Matches | |
$DownloadLinks = $LinksMatch.Value | |
function private:Resolve-NameConflict | |
{ | |
#Accepts Path to a FILE and changes it so there are no name conflicts | |
param( | |
[string]$OutputDir | |
) | |
$newPath = $OutputDir | |
if (Test-Path $OutputDir -PathType Leaf) | |
{ | |
$i = 0 | |
$item = (Get-Item $OutputDir) | |
while ( (Test-Path $newPath -PathType Leaf) -and ($i -lt [int]([math]::Sqrt([int]::MaxValue)))) | |
{ | |
$i += 1 | |
$newPath = Join-Path $item.DirectoryName ($item.BaseName + "($i)" + $item.Extension) | |
} | |
} | |
return $newPath | |
} | |
$InstallQueue = [System.Collections.Generic.Queue[string]]::new(($DownloadLinks.Count)) | |
#Download Urls | |
foreach ($url in $DownloadLinks) | |
{ | |
$FileRequest = $null | |
try | |
{ | |
$FileRequest = Invoke-WebRequest -Uri $url -ErrorAction SilentlyContinue | |
} | |
catch | |
{ | |
Write-Warning "Failed to download '$url' - $_" | |
} | |
$AppxFileName = ($FileRequest.Headers['Content-Disposition'] | Select-String -Pattern '(?<=filename=).+').Matches.Value | |
$AppxFilePath = Join-Path -Path $OutputDir -ChildPath $AppxFileName | |
$AppxFilePath = Resolve-NameConflict -OutputDir $OutputDir | |
try{ | |
[System.IO.File]::WriteAllBytes($AppxFilePath, $FileRequest.Content) | Out-Null | |
} | |
catch{ | |
Write-Warning "Failed to write to '$AppxFilePath' - $_" | |
} | |
if ( -not $ConfirmPreference -or $PSCmdlet.ShouldProcess($AppxFileName, 'Add Appx Package')) | |
{ | |
try { Add-AppxPackage -Path $AppxFilePath -ErrorAction SilentlyContinue } catch { $InstallQueue.EnQueue($AppxFilePath) } | |
} | |
} | |
while ($InstallQueue.Count -gt 0) | |
{ | |
Write-Verbose "Retrying Add-AppxPackage on '$($InstallQueue.Peek())'" | |
Add-AppxPackage -Path ($InstallQueue.DeQueue()) | |
} | |
} | |
} |
Ah good to know, gonna test the new script now. I feel bit safe in first checking all the downloaded packages before installing them manually. Maybe you can add a parameter to skip download like say Download-AppxPackage -Uri "$URL" -OutputDir"$PATH" -skipinstall
i tried tinkering around with the script to only have it download the packages
function Invoke-DownloadAppxPackage
{
[CmdletBinding(SupportsShouldProcess)]
param (
[Parameter(Mandatory, ValueFromPipeline)]
[ValidateScript({ [uri]::TryCreate($_, [UriKind]::Absolute, [ref]$null) })]
[string]$Uri,
[ValidateScript({ Test-Path -Path $_ -PathType Container })]
[string]$OutputDir = $env:TEMP
)
process
{
if ($WhatIfPreference)
{
$OutputDir = $env:TEMP
}
else
{
$OutputDir = (Resolve-Path -Path $OutputDir).Path
}
# Get Urls to download
$WebResponse = Invoke-WebRequest -Uri 'https://store.rg-adguard.net/api/GetFiles' -Method Post -Body "type=url&url=$Uri&ring=Retail" -ContentType 'application/x-www-form-urlencoded'
$LinksMatch = $WebResponse.Links | Where-Object { $_ -like '*.appx*' } | Where-Object { $_ -like '*_neutral_*' -or $_ -like '*_' + $env:PROCESSOR_ARCHITECTURE.Replace('AMD', 'X').Replace('IA', 'X') + '_*' } | Select-String -Pattern '(?<=a href=").+(?=" r)' | Select-Object -ExpandProperty Matches
$DownloadLinks = $LinksMatch.Value
function private:Resolve-NameConflict
{
# Accepts Path to a FILE and changes it so there are no name conflicts
param(
[string]$OutputDir
)
$newPath = $OutputDir
if (Test-Path $OutputDir -PathType Leaf)
{
$i = 0
$item = (Get-Item $OutputDir)
while ((Test-Path $newPath -PathType Leaf) -and ($i -lt [int]([math]::Sqrt([int]::MaxValue))))
{
$i += 1
$newPath = Join-Path $item.DirectoryName ($item.BaseName + "($i)" + $item.Extension)
}
}
return $newPath
}
# Download Urls
foreach ($url in $DownloadLinks)
{
$FileRequest = $null
try
{
$FileRequest = Invoke-WebRequest -Uri $url -ErrorAction SilentlyContinue
}
catch
{
Write-Warning "Failed to download '$url' - $_"
}
$AppxFileName = ($FileRequest.Headers['Content-Disposition'] | Select-String -Pattern '(?<=filename=).+').Matches.Value
$AppxFilePath = Join-Path -Path $OutputDir -ChildPath $AppxFileName
$AppxFilePath = Resolve-NameConflict -OutputDir $OutputDir
try
{
[System.IO.File]::WriteAllBytes($AppxFilePath, $FileRequest.Content) | Out-Null
}
catch
{
Write-Warning "Failed to write to '$AppxFilePath' - $_"
}
}
}
}
Invoke-DownloadAppxPackage -Uri 'https://www.microsoft.com/store/productId/9NKSQGP7F2NH' -OutputDir "$env:USERPROFILE\Downloads"
i am getting these errors
PowerShell 7.3.7
WARNING: Failed to write to 'C:\Users\Administrator\Downloads' - Exception calling "WriteAllBytes" with "2" argument(s): "Access to the path 'C:\Users\Administrator\Downloads' is denied."
i didn't got these errors in the earlier version of the script
i think have found the issue, although i have no idea how do i fix it
$AppxFileName = ($FileRequest.Headers['Content-Disposition'] | Select-String -Pattern '(?<=filename=).+').Matches.Value
$AppxFileName in your script is not storing any value whatsoever
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Actually, I tested it more carefully and the issue was the MS Store was refusing cross-origin requests. I was wrong that my version of the script required basic parsing — it actually doesn't require any parsing. Links are provided by
Invoke-WebRequest
without parsing.Also, I don't see what you changed in the version you shared in your latest comment (except for whitespace)!