How do I install an app from Windows Store using Powershell

34,462

Solution 1

store.rg-adguard.net is a GUI for generating direct download links to store apps. Peeking at the source of that page, we can piggyback off them to download the content directly, but using PackageFamilyName, rather than Name (in your example it would be Microsoft.HEVCVideoExtension_8wekyb3d8bbwe).

function Download-AppxPackage {
[CmdletBinding()]
param (
  [string]$PackageFamilyName,
  [string]$Path
)
   
  process {
    $WebResponse = Invoke-WebRequest -Method 'POST' -Uri 'https://store.rg-adguard.net/api/GetFiles' -Body "type=PackageFamilyName&url=$PackageFamilyName&ring=Retail" -ContentType 'application/x-www-form-urlencoded'
    $LinksMatch = $WebResponse.Links | where {$_ -like '*_x64*.appx*'} | Select-String -Pattern '(?<=a href=").+(?=" r)'
    $DownloadLinks = $LinksMatch.matches.value 

    for ($i = 1; $i -le $DownloadLinks.Count; $i++) {
      Invoke-WebRequest -Uri $DownloadLinks[$i-1] -OutFile "$Path\$PackageFamilyName($i).appx"   
    }
  }
}

This is limited to the x64 version, and the path must point to a folder. It will download the package and its dependencies and save them all as PackagefamilyName(n).appx

Solution 2

function Download-AppxPackage {
[CmdletBinding()]
param (
  [string]$Uri,
  [string]$Path = "."
)
   
  process {
    echo ""
    $StopWatch = [system.diagnostics.stopwatch]::startnew()
    $Path = (Resolve-Path $Path).Path
    #Get Urls to download
    Write-Host -ForegroundColor Yellow "Processing $Uri"
    $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 {$_ -like '*.appx*'} | where {$_ -like '*_neutral_*' -or $_ -like "*_"+$env:PROCESSOR_ARCHITECTURE.Replace("AMD","X").Replace("IA","X")+"_*"} | Select-String -Pattern '(?<=a href=").+(?=" r)').matches.value
    $Files = ($WebResponse.Links | where {$_ -like '*.appx*'} | where {$_ -like '*_neutral_*' -or $_ -like "*_"+$env:PROCESSOR_ARCHITECTURE.Replace("AMD","X").Replace("IA","X")+"_*"} | where {$_ } | Select-String -Pattern '(?<=noreferrer">).+(?=</a>)').matches.value
    #Create array of links and filenames
    $DownloadLinks = @()
    for($i = 0;$i -lt $LinksMatch.Count; $i++){
        $Array += ,@($LinksMatch[$i],$Files[$i])
    }
    #Sort by filename descending
    $Array = $Array | sort-object @{Expression={$_[1]}; Descending=$True}
    $LastFile = "temp123"
    for($i = 0;$i -lt $LinksMatch.Count; $i++){
        $CurrentFile = $Array[$i][1]
        $CurrentUrl = $Array[$i][0]
        #Find first number index of current and last processed filename
        if ($CurrentFile -match "(?<number>\d)"){}
        $FileIndex = $CurrentFile.indexof($Matches.number)
        if ($LastFile -match "(?<number>\d)"){}
        $LastFileIndex = $LastFile.indexof($Matches.number)

        #If current filename product not equal to last filename product
        if (($CurrentFile.SubString(0,$FileIndex-1)) -ne ($LastFile.SubString(0,$LastFileIndex-1))) {
            #If file not already downloaded, add to the download queue
            if (-Not (Test-Path "$Path\$CurrentFile")) {
                "Downloading $Path\$CurrentFile"
                $FilePath = "$Path\$CurrentFile"
                $FileRequest = Invoke-WebRequest -Uri $CurrentUrl -UseBasicParsing #-Method Head
                [System.IO.File]::WriteAllBytes($FilePath, $FileRequest.content)
            }
        #Delete file outdated and already exist
        }elseif ((Test-Path "$Path\$CurrentFile")) {
            Remove-Item "$Path\$CurrentFile"
            "Removing $Path\$CurrentFile"
        }
        $LastFile = $CurrentFile
    }
    "Time to process: "+$StopWatch.ElapsedMilliseconds
  }
}


if (-Not (Test-Path "C:\Support\Store")) {
    Write-Host -ForegroundColor Green "Creating directory C:\Support\Store"
    New-Item -ItemType Directory -Force -Path "C:\Support\Store"
}

Download-AppxPackage "https://www.microsoft.com/p/snip-sketch/9mz95kl8mr0l" "C:\Support\Store"

Modified the script so that it delete old versions and only download latest.

Much thanks to Yorai Levi for the original script!

Solution 3

Building further on AJ's Answer. Download any app from windows app store with powershell just supply a url from the store and a path for saving.

Usage:

Download-AppxPackage "https://www.microsoft.com/p/dynamic-theme/9nblggh1zbkw" "$ENV:USERPROFILE\Desktop"
C:\Users\user\Desktop\55888ChristopheLavalle.DynamicTheme_1.4.30233.0_neutral_~_jdggxwd41xcr0.AppxBundle
C:\Users\user\Desktop\55888ChristopheLavalle.DynamicTheme_1.4.30234.0_neutral_~_jdggxwd41xcr0.AppxBundle
C:\Users\user\Desktop\Microsoft.NET.Native.Framework.1.7_1.7.27413.0_x64__8wekyb3d8bbwe.Appx
C:\Users\user\Desktop\Microsoft.NET.Native.Runtime.1.7_1.7.27422.0_x64__8wekyb3d8bbwe.Appx
C:\Users\user\Desktop\Microsoft.Services.Store.Engagement_10.0.19011.0_x64__8wekyb3d8bbwe.Appx
C:\Users\user\Desktop\Microsoft.VCLibs.140.00_14.0.29231.0_x64__8wekyb3d8bbwe.App

Code:

function Download-AppxPackage {
[CmdletBinding()]
param (
  [string]$Uri,
  [string]$Path = "."
)
   
  process {
    $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 {$_ -like '*.appx*'} | where {$_ -like '*_neutral_*' -or $_ -like "*_"+$env:PROCESSOR_ARCHITECTURE.Replace("AMD","X").Replace("IA","X")+"_*"} | Select-String -Pattern '(?<=a href=").+(?=" r)'
    $DownloadLinks = $LinksMatch.matches.value 

    function 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){
            $i = 0;
            $item = (Get-Item $Path)
            while(Test-Path $newPath){
                $i += 1;
                $newPath = Join-Path $item.DirectoryName ($item.BaseName+"($i)"+$item.Extension)
            }
        }
        return $newPath
    }
    #Download Urls
    foreach($url in $DownloadLinks){
        $FileRequest = Invoke-WebRequest -Uri $url -UseBasicParsing #-Method Head
        $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)
        echo $FilePath
    }
  }
}

This correctly downloads neutral and x64 packages but untested for arm and 32bit systems. the path must point to a folder. It will download the package and its dependencies and save them all as their original file names while avoiding name collisions like chrome.

Share:
34,462
David Wilson
Author by

David Wilson

Updated on September 18, 2022

Comments

  • David Wilson
    David Wilson over 1 year

    I know if I have .appx package file, I can install it via powershell with the Add-AppxPackage cmdlet. However, I simply want to download & install Microsoft Store packages by name.

    I don't want to have to go to the Microsoft Store page, start fiddler, start the download, capture the .appx file URL and then manually download it so that I can use Add-AppxPackage. (See how Windows OS Hub did that here)

    That could be fun - but it will be flaky. I need a robust scriptable method for managing Windows Store apps.

    (There are a few software packages that are only accessible via Microsoft Store. Everything else I can get via Chocolatey or direct msi download.)

    One example that I can't yet script is installation of the HEIF Image Extensions (needed to view the image format from iPhones: *.HEIC format.

    Once I install this from the Windows Store, it shows up with Get-AppxPackage

    PS C:\Tools> Get-AppxPackage | Where-Object {$_.Name -eq "Microsoft.HEVCVideoExtension" }
    
    
    Name              : Microsoft.HEVCVideoExtension
    Publisher         : CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US
    Architecture      : X64
    ResourceId        :
    Version           : 1.0.31053.0
    PackageFullName   : Microsoft.HEVCVideoExtension_1.0.31053.0_x64__8wekyb3d8bbwe
    InstallLocation   : C:\Program Files\WindowsApps\Microsoft.HEVCVideoExtension_1.0.31053.0_x64__8wekyb3d8bbwe
    IsFramework       : False
    PackageFamilyName : Microsoft.HEVCVideoExtension_8wekyb3d8bbwe
    PublisherId       : 8wekyb3d8bbwe
    IsResourcePackage : False
    IsBundle          : False
    IsDevelopmentMode : False
    NonRemovable      : False
    Dependencies      : {Microsoft.VCLibs.140.00_14.0.27810.0_x64__8wekyb3d8bbwe}
    IsPartiallyStaged : False
    SignatureKind     : Store
    Status            : Ok
    

    What I want is the cmdlet: Download-AppxPackage so that I can do:

    Download-AppxPackage -Name "Microsoft.HEVCVideoExtension"
    

    Does anyone know how I can do this?

  • David Wilson
    David Wilson almost 4 years
    Thanks - this is a good improvement. Not ideal as: a) I have to manualy install to find out the PackageFamilyName - and it may change. And: b) I don't know who "rg-adguard.net" are. I'll accept this as answer as probably best we can do until MS give me the cmdlet I want. They may want to divert users to the store to see ads etc.
  • DeerSpotter
    DeerSpotter almost 3 years
    as of: 06/25/2021 this is broken. Download-AppxPackage no longer works
  • DeerSpotter
    DeerSpotter almost 3 years
    as of: 06/25/2021 this is broken. Download-AppxPackage no longer works
  • DeerSpotter
    DeerSpotter almost 3 years
    as of: 06/25/2021 this is broken. Download-AppxPackage no longer works
  • Yorai Levi
    Yorai Levi almost 3 years
    hey @DeerSpotter what package did you try to download? my example still runs on my system
  • André
    André almost 3 years
    Thanks, it works. I didn't understand though why this fn downloads multiple versions of the same package. E.g. I tried with microsoft.com/pt-br/p/windbg/9pgjgd53tn86 and it downloaded both Microsoft.WinDbg_1.2104.13002.0_neutral__8wekyb3d8bbwe.Appx and Microsoft.WinDbg_1.2103.1004.0_neutral__8wekyb3d8bbwe.Appx
  • Yorai Levi
    Yorai Levi almost 3 years
    @André this script doesn't attempt to version check it just performs the download, you are welcome to share an adaptation that version checks~ I am sure it will be helpful for everyone
  • Mavaddat Javid
    Mavaddat Javid over 2 years