PowerShell 下载 Microsoft Store 的 UWP 安装包

PowerShell 下载 Microsoft Store 的 UWP 安装包

由于本人有自动下载 Microsoft Store 上的 UWP 安装包的需求,故写了一个 PowerShell 脚本,方便以后使用。

这边我们使用 https://store.rg-adguard.net/ 这个网站提供的 API,注意在实际使用中可能需要遵守网站的 robots.txt 协议。

获取 APP 信息

首先,我们需要知道这个 App 的相关信息,并且能够在 https://store.rg-adguard.net/ 中找到。

此网站提供了四种请求类型来指定到一个 APP:

  1. URL (Link)
    Sample Data: https://www.microsoft.com/store/productId/9NSWSBXN8K03
  2. ProductId
    Sample Data: 9NKSQGP7F2NH
  3. PackageFamilyName
    Sample Data: Microsoft.WindowsStore_8wekyb3d8bbwe
  4. CategoryID
    Sample Data: d58c3a5f-ca63-4435-842c-7814b5ff91b7

我个人是比较推荐用第3种PackageFamilyName的,因为搜索起来比较方便,以下脚本均是用此方式。

使用 Invoke-WebRequest 请求软件下载链接信息

使用 PowerShell 的 Invoke-WebRequest,发送一个 POST 请求到 https://store.rg-adguard.net/api/GetFiles 接口,请求参数包括:

  • type:请求类型,这里为’PackageFamilyName’
  • url:应用ID,这里为’Microsoft.WindowsStore_8wekyb3d8bbwe’
  • ring:渠道,这里为’RP’
  • lang:语言,这里为’zh-CN’

这些参数与网页前端看到的相对应。

1
2
3
4
5
6
7
8
9
$obj = Invoke-WebRequest -Uri "https://store.rg-adguard.net/api/GetFiles" `
-Method "POST" `
-ContentType "application/x-www-form-urlencoded" `
-Body @{
type = 'PackageFamilyName'
url = 'Microsoft.WindowsStore_8wekyb3d8bbwe'
ring = 'RP'
lang = 'zh-CN'
}

我们测试一下返回的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
PS > $obj

StatusCode : 200
StatusDescription : OK
Content : <b>CategoryID:</b> <i>64293252-5926-453c-9494-2d4021f1c78d</i><center><script async src="//pagead2.googlesyndication.com/pagead/js/adsbygo
ogle.js"></script>
<!-- Microsoft Store #2 -->
<ins class="ads
RawContent : HTTP/1.1 200 OK
Date: Thu, 11 Apr 2024 16:48:02 GMT
Transfer-Encoding: chunked
Connection: keep-alive
X-Powered-By: PHP/7.4.33
Strict-Transport-Security: max-age=31536000;
CF-Cache-Status: DYNAM
Headers : {[Date, System.String[]], [Transfer-Encoding, System.String[]], [Connection, System.String[]], [X-Powered-By, System.String[]]…}
Images : 省略
InputFields : {}
Links : 省略
RawContentLength : 29436
RelationLink : {}

返回的内容不是 JSON 格式,而是一个 HTML 页面。

幸运的是,PowerShell 可以直接通过 $obj.Links 自动爬取 HTML 页面中的链接,然后通过 $obj.Links.href 获取下载链接。

1
2
3
4
5
6
7
8
9
10
11
12
PS > $obj.Links

outerHTML
---------
<a href="http://dl.delivery.mp.microsoft.com/filestreamingservice/files/xxxxxx" rel="noreferrer">Microsoft.NET.Native.Framework…
...
<a href="http://dl.delivery.mp.microsoft.com/filestreamingservice/files/xxxxxx" rel="noreferrer">Microsoft.WindowsStore_22403.1

PS > $obj.Links.href
http://dl.delivery.mp.microsoft.com/filestreamingservice/files/xxxxxx
http://tlu.dl.delivery.mp.microsoft.com/filestreamingservice/files/xxxxxx
...

不过 .href 输出的链接并包含文件名,我们还需对文件名和下载文件进行匹配。

匹配文件名和下载文件

此站的 API 会返回依赖的文件,这个对于用户是好事,可以避免缺少依赖导致无法安装,我们安装前面肯定是要先安装依赖再装软件的。

不过这里的问题在于如何把这些文件全部都下载下来,且保证文件和文件名对应,且部分文件是我们不需要的,这要求我们对脚本进行自定义,通过各种匹配机制,如 -like-match-contains 等,对内容进行一个筛选。且 PowerShell 解析的 HTML 不支持读取 innerText,即 <a> 标签的文本内容,这给我们准确匹配并输出信息带来不便。

下面的例子我们以下载运行库 Microsoft.VCLibs.140.00 最新版本为例。

首先还是请求接口。

1
2
3
4
5
6
7
8
9
$obj = Invoke-WebRequest -Uri "https://store.rg-adguard.net/api/GetFiles" `
-Method "POST" `
-ContentType "application/x-www-form-urlencoded" `
-Body @{
type = 'PackageFamilyName'
url = 'Microsoft.VCLibs.140.00_8wekyb3d8bbwe'
ring = 'RP'
lang = 'zh-CN'
}

然后执行 $obj.Links.outerHTML,我们可以看到文件名了。

这个 API 返回的安装包列表存在大量信息需要我们过滤,比如说系统架构,假设我们是 x64 的系统,那边需要匹配到的不只是 x64 的,还有 neutral 的通用架构,所以我们再匹配前必须了解尽可能详细的信息。

另外,我们要下载的主程序安装包也会返回多个,根据经验,有些时候返回的是 .eappxbundle 格式的,这种是给 Xbox 的,安装包是加密了的,Windows 上用不了;有时还会返回多个格式的版本,比如说 .msixbundle 新版本 和 .appxbundle 老版本。

有些时候同样是 .appxbundle,但是会返回多个版本号,最新版本的不兼容老系统:有些是 Windows 11 专用的,不能给 Windows 10 装。

不过这些都是能够根据实际情况定制脚本解决的。另外我相信 Github、PSGallary 上肯定有现成的模块,那种绝对比我们自己写的好很多。

实际情况我要同时下载 x64x86 两个平台,我们这边发现有很多包是用不上的,如 arm64arm,另外包的格式为 .appx,我们需要过滤掉 .BlockMap

这边使用 -match 正则表达式匹配:

1
2
3
4
5
6
7
8
9
10
foreach ($link in $obj.Links) {
if ($link.outerHTML -match '(?<=<a\b[^>]*>).*?(?=</a>)') {
$linkText = $Matches[0]
if ($linkText -match '(x86|x64).*\.appx\b') {
Write-Output " $linkText : $($link.href)"
# Invoke-WebRequest -Uri $link.href -OutFile "$linkText"
}
}
}

到这里我们就可以看到安装包已经被正确解析并过滤出来了,这样我们就可以将 Write-Output 改为 Invoke-WebRequest 下载。

完整脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function Download-Appx($Name) {
$obj = Invoke-WebRequest -Uri "https://store.rg-adguard.net/api/GetFiles" `
-Method "POST" `
-ContentType "application/x-www-form-urlencoded" `
-Body @{
type = 'PackageFamilyName'
url = $Name
ring = 'RP'
lang = 'zh-CN'
}

foreach ($link in $obj.Links) {
if ($link.outerHTML -match '(?<=<a\b[^>]*>).*?(?=</a>)') {
$linkText = $Matches[0]
if ($linkText -match '(x86|x64|neutral).*\.(appx|appxbundle|msixbundle)\b') {
Write-Debug "$linkText : $($link.href)"
if (Test-Path -Path $linkText) {
Write-Warning "Already exists, skiping $linkText"
} else {
Invoke-WebRequest -Uri $link.href -OutFile $linkText
}
}
}
}
}

Download-Appx 'Microsoft.VCLibs.140.00_8wekyb3d8bbwe'
# 这里我们拓展了一下,再下几个
Download-Appx 'Microsoft.AV1VideoExtension_8wekyb3d8bbwe'
Download-Appx 'Microsoft.HEIFImageExtension_8wekyb3d8bbwe'
Download-Appx 'Microsoft.MPEG2VideoExtension_8wekyb3d8bbwe'
Download-Appx 'Microsoft.RawImageExtension_8wekyb3d8bbwe'
Download-Appx 'Microsoft.VP9VideoExtensions_8wekyb3d8bbwe'
Download-Appx 'Microsoft.WebMediaExtensions_8wekyb3d8bbwe'
Download-Appx 'Microsoft.WebpImageExtension_8wekyb3d8bbwe'