-
-
Save mavaddat/26146d111abf62f6160b1bd02a392ba8 to your computer and use it in GitHub Desktop.
# 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()) | |
} | |
} | |
} |
Is it possible that you can write a script for batch downloading appx files?
Here is a simple approach using the above Get-AppxPackageDownload
function:
# Example apps to install
$URIs = @('https://www.microsoft.com/store/productId/9P6RC76MSMMJ', 'https://www.microsoft.com/store/productId/9N0866FS04W8', 'https://www.microsoft.com/store/productId/9NH2GPH4JZS4')
$URIs | Foreach-Object {
Get-AppxPackageDownload -Uri $_ -Path $env:TEMP
}
i tried the script but its ending instantly without doing anything, not sure if i am missing something here ?
I believe it is because you are using PowerShell 6+, yes? This script currently only works on PowerShell 5.1. Please give me a few minutes and I will update it to work on PSEdition
Core.
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.
There were some syntax errors in the script. Also -Force command is not valid for AppxPackage command. After some fiddling around, i have fixed the issue. But a script working properly on PSEdition Core would be much more ideal considering how the the Desktop edition is kind of depreciated. i am dropping the script which worked perfectly fine on PS 5.1 if it helps in any way. Gonna switch to the Core edition script as you drop it. Thanks for making this, its pretty useful, wasted so many hours on this before i finally got to this script :)
function Download-AppxPackage {
[CmdletBinding(SupportsShouldProcess=$true)]
param (
[Parameter(Mandatory=$true,ValueFromPipeline)]
[ValidateScript({[uri]::TryCreate($_, [UriKind]::Absolute, [ref]$null)})]
[string]$Uri,
[ValidateScript({Test-Path -Path $_ -PathType Container})]
[string]$Path = $env:TEMP
)
process {
#Requires -PSEdition Desktop # Basic parsing unavailable in Core
if ($WhatIfPreference) {
$Path = $env:TEMP
} else {
$Path = (Resolve-Path $Path).Path
}
# Get Urls to download
$WebResponse = Invoke-WebRequest -UseBasicParsing -Method 'POST' -Uri 'https://store.rg-adguard.net/api/GetFiles' -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)'
$DownloadLinks = $LinksMatch.matches.value
function private:Resolve-NameConflict {
# Accepts Path to a FILE and changes it so there are no name conflicts
param(
[string]$Path
)
$newPath = $Path
if (Test-Path $Path -PathType Leaf) {
$i = 0;
$item = (Get-Item $Path)
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 -UseBasicParsing -ErrorAction SilentlyContinue
} catch {
continue
}
$FileName = ($FileRequest.Headers["Content-Disposition"] | Select-String -Pattern '(?<=filename=).+').matches.value
$FilePath = Join-Path $Path $FileName
$FilePath = Resolve-NameConflict($FilePath)
[System.IO.File]::WriteAllBytes($FilePath, $FileRequest.content) | Out-Null
if (($ConfirmPreference -eq $false) -or $PSCmdlet.ShouldProcess($FileName, "Add Appx Package")) {
try {
Add-AppxPackage -Path $FilePath -ErrorAction SilentlyContinue
} catch {
$installQueue.Enqueue($FilePath)
}
}
}
while ($InstallQueue.Count -gt 0) {
Write-Verbose "Retrying Add-AppxPackage on '$($InstallQueue.Peek())'"
Add-AppxPackage -Path ($InstallQueue.Dequeue())
}
}
}
$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 Download-AppxPackage -CommandType Function) {
$URIs | ForEach-Object {
Download-AppxPackage -Uri $_ -Path "$env:USERPROFILE\Downloads"
}
}
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)!
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
Is it possible that you can write a script for batch downloading appx files?