跳到主要内容

WinGet 进阶

基础篇解决的是「用 winget 装软件」。进阶篇解决的是这些:

  • 把它塞进脚本跑(CI、计划任务、新机器初始化)
  • 管住它的行为(源、作用域、默认版本、代理、实验功能)
  • 出问题能自己查(日志、错误码、verbose)
  • 仓库里搜不到你要的软件时,自己写一个 manifest 提交上去

目录​

  1. 源管理
  2. 脚本化:四个必备参数与幂等
  3. 安装位置、作用域与安装器类型
  4. 版本锁定与机器迁移
  5. settings.json:把 winget 调成你想要的样子
  6. 代理与网络
  7. 日志与错误码
  8. 批量、CI 与 WinGet Configuration
  9. 自己写 manifest 并提交到 winget-pkgs
  10. 与本项目配合
  11. 排错清单

1. 源管理​

winget source list

输出里的 Explicit 一列是重点:false 表示这个源会参与所有命令的搜索;true 表示只有显式 --source 指定它才会被搜索。winget-font 默认是 true,所以字体要单独装:

winget search --source winget-font --tag monospace

增删改查​

winget source list # 看所有源
winget source list --name winget # 看某个源的详情
winget source update # 更新所有源的索引
winget source update --name winget # 只更新一个
winget source add --name mycorp `
--arg https://packages.mycorp.com/winget-source/ `
--type Microsoft.Rest `
--header "X-Api-Key: 你的密钥" `
--data-directory D:\winget-cache
winget source edit --name winget-font --explicit false # 改成参与全局搜索
winget source remove --name mycorp
winget source reset # 把默认源恢复出厂

参数说明​

参数作用
--arg源的地址(RST 源的 REST 端点)
--type源类型:Microsoft.Rest(REST API)或 Microsoft.LocalFolder(本地文件夹)
--header自定义 HTTP 头,私有源鉴权用
--data-directory本地缓存目录,留空则不缓存,每次联网查
--explicit是否显式源

私有源的三种做法​

做法适用注意
Microsoft.LocalFolder + 本地目录公司内网、离线环境、只用几个内部软件目录结构必须和 winget-pkgs 一致
自建 REST 源团队共享、要支持多客户端得自己实现 REST 接口,工作量大
直接放一个本地 manifest只有一个软件要装用 winget install --manifest,见第 9 节

本地目录源长这样(把 winget-pkgs 的某个子目录拷出来就能用):

winget source add --name internal --arg D:\winget-local --type Microsoft.LocalFolder
winget search --source internal foo

源出问题了怎么修​

winget source update # 先试这个
winget source reset # 还不行就恢复默认源

0x8A15000A(索引损坏)、0x8A150009(索引版本比客户端新)这类错误,升级「应用安装程序」比删缓存更有效——前者是客户端太旧读不了新索引。

源的优先级(实验功能)​

开启后,多源有同名包时,优先级高的源排在搜索结果前面、并优先被安装:

"experimentalFeatures": {
"sourcePriority": true
}

配合 winget source edit --name winget --priority 10。

这是实验性功能,行为可能随版本变化。别把它当成生产环境的依赖。


2. 脚本化:四个必备参数与幂等​

在脚本和 CI 里跑 winget,默认是不安全的,因为它会停下来问你问题。一个无人值守的安装命令至少要加四个参数:

winget install --id Microsoft.VisualStudioCode -e `
--silent `
--accept-package-agreements `
--accept-source-agreements `
--disable-interactivity
参数关掉了什么
--silent(-h)安装器弹出的所有 UI(进度条、结束提示)
--accept-package-agreements「是否同意本软件许可协议」
--accept-source-agreements「是否同意本源的使用条款」
--disable-interactivitywinget 自己的一切提问(多结果时列清单、是否重启等)

为什么 -e(--exact)不能省​

不加 -e 时 winget 做的是子串匹配,命中多个就会列出来让你选。在交互式环境下这没问题(你能选),在 CI 里就是直接失败或装错东西。

用 --id X -e 把目标钉死,行为就确定了。

幂等:让脚本可以反复跑​

# 已装了就跳过(哪怕版本旧)
winget install --id Python.Python.3.12 -e --no-upgrade `
--silent --accept-package-agreements --disable-interactivity
想要的语义怎么写
没装就装,装了就跳过--no-upgrade
一定要装最新版upgrade(配合 --force 可覆盖失败)
强制重装同版本--allow-reinstall
出错也继续检查 $LASTEXITCODE,别让脚本被中断
顺带升所有winget upgrade --all --disable-interactivity

退出码​

winget 用退出码汇报结果,脚本里必须看:

退出码含义
0成功
0x8A150014没找到匹配的包(ID 写错 / 源里没有)
0x8A150016匹配到多个包,无法确定目标
0x8A150010没有适用于当前系统的安装器
0x8A150011安装包哈希不匹配
0x8A150002命令行参数不合法
0x8A150005收到取消信号(Ctrl+C)

注意退出码在 PowerShell 里是 16 进制十进制数,$LASTEXITCODE 拿到的是有符号十进制,0x8A150014 是负数。要判断就直接用具体的 exit 值比较,最稳的方式是:

winget install --id Foo.Bar -e --silent --disable-interactivity
switch ($LASTEXITCODE) {
0 { Write-Host 'OK' }
-1978335212 { Write-Warning '没找到这个包,检查 ID' } # 0x8A150014
-1978335210 { Write-Warning '匹配到多个包' } # 0x8A150016
default { Write-Error "winget 失败: $LASTEXITCODE" }
}

用 winget error <code> 查表比背数字靠谱得多:

winget error 0x8A150014

一个真正能用的批量安装脚本​

#requires -Version 5.1
<#
.SYNOPSIS
用 winget 批量安装/补装软件,可重复执行。
.NOTES
跑法:powershell -ExecutionPolicy Bypass -File .\setup.ps1
#>
[CmdletBinding()]
param(
[switch]$Upgrade # 存在时改为「全部升级」
)

$ErrorActionPreference = 'Continue'

# 脚本里统一用这些参数
$wingetCommon = @(
'--exact'
'--silent'
'--accept-package-agreements'
'--accept-source-agreements'
'--disable-interactivity'
'--nowarn'
)

$packages = [ordered]@{
'Microsoft.VisualStudioCode' = '代码编辑器'
'Git.Git' = 'Git'
'GitHub.cli' = 'GitHub CLI'
'Microsoft.PowerShell' = 'PowerShell 7'
'Python.Python.3.12' = 'Python 3.12'
'NodeJS.LTS' = 'Node.js LTS'
'7zip.7zip' = '7-Zip'
'Microsoft.WindowsTerminal' = 'Windows Terminal'
'Notepad++.Notepad++' = 'Notepad++'
'Google.Chrome' = 'Chrome'
}

if ($Upgrade) {
Write-Host '==> 升级所有已安装软件' -ForegroundColor Cyan
winget upgrade --all @wingetCommon
exit $LASTEXITCODE
}

# 先同步一次源索引,避免装到过期列表
winget source update --accept-source-agreements

$failed = [System.Collections.Generic.List[string]]::new()

foreach ($id in $packages.Keys) {
$label = $packages[$id]
Write-Host "==> [$label] $id" -ForegroundColor Cyan

# --no-upgrade:已装则跳过,让脚本可以重复执行
winget install --id $id @wingetCommon --no-upgrade

if ($LASTEXITCODE -ne 0) {
$reason = switch ($LASTEXITCODE) {
-1978335212 { '源里没有这个包(检查 ID 拼写)' }
-1978335210 { '匹配到多个包,需要 -e 之外再限定源' }
-1978335216 { '没有适用于本机的安装器(架构不对?)' }
-1978335215 { '安装包哈希不匹配,先别用 --ignore-security-hash' }
default { "退出码 $LASTEXITCODE" }
}
Write-Warning " 失败:$label —— $reason"
$failed.Add("$id ($label)")
}
}

Write-Host ''
if ($failed.Count -gt 0) {
Write-Host "以下 ${($failed.Count)} 个包没装上:" -ForegroundColor Red
$failed | ForEach-Object { Write-Host " - $_" -ForegroundColor Red }
exit 1
}

Write-Host '全部安装完成。' -ForegroundColor Green

几个值得抄走的写法:

  • [ordered]@{} 保存「ID → 中文名」,日志可读性大幅提升
  • 单个包失败不中断整批(不设 $ErrorActionPreference = 'Stop',并且逐个检查退出码)
  • 用 switch 把退出码翻译成中文,最后汇总成一份「失败清单」而不是第一个错误就退出
  • 先 source update 再装,避免用过期索引解析出一个已经下线的版本

3. 安装位置、作用域与安装器类型​

--scope:装给「我」还是「所有人」​

winget install --id Microsoft.PowerToys -e --scope user # 只装当前用户
winget install --id Microsoft.PowerToys -e --scope machine # 全机器(要 UAC)

规则是硬性的:

manifest 里 Scope 声明的安装器,不支持 --location; manifest 里支持 --location 的安装器(通常 portable / zip),只支持 user 作用域。

所以 --scope machine 和 --location 基本是互斥的。另外 user 作用域要求「以用户身份运行 winget」——在管理员 PowerShell 里 --scope user 可能直接失败。

--location 的前置条件​

winget install --id Microsoft.PowerToys -e -l "D:\Tools\PowerToys"

只有 manifest 里 InstallerSwitches: Custom: InstallLocation: true 的包才支持 -l。 想知道支不支持:

winget show --id Microsoft.PowerToys

输出里会明确写是否支持自定义安装位置。社区里大多数包都不支持,这也是 Scoop 更适合装「绿色工具」的原因。

--installer-type:手动指定安装器类型​

同一个包在 manifest 里可能同时提供 msi、exe、zip 几种安装器,winget 会按优先级自动挑:

winget install --id Some.App -e --installer-type zip

可选值:appx、burn、exe、font、inno、msi、msix、msstore、nullsoft、portable、wix、zip。

它的主要用途是排错:如果默认选的安装器在你机器上有问题,换一个类型常常能解决。

--architecture​

winget install --id Python.Python.3.12 -e -a x64

值:x86、x64、arm、arm64。不写就取当前系统架构。如果你要在一台 x64 机器上装 x86 版,必须显式指定,否则会装错。


4. 版本锁定与机器迁移​

export / import:把「我装了什么」导出来​

winget export -o D:\backup\apps.json
winget export -o D:\backup\apps.json --include-versions # 连版本号一起导出

生成的文件长这样(schema 在 winget-cli 仓库的 schemas/JSON/packages):

{
"Sources": [
{
"Source": "winget",
"Arg": "https://cdn.winget.microsoft.com/cache",
"Type": "Microsoft.Rest"
}
],
"Packages": [
{ "PackageIdentifier": "Git.Git" },
{ "PackageIdentifier": "Microsoft.VisualStudioCode", "Version": "1.95.0" }
]
}

导入到新机器:

winget import -i D:\backup\apps.json

import 的行为要点:

  • 不加 --include-versions 导出的文件,import 时装的是最新版,不是导出时的版本
  • 装了 ID 相同的更高版本时,默认会跳过并警告,不会降级
  • 源里已经没有的包会跳过并警告——导出文件不保证 100% 可导入,尤其是在包被下架或改名之后

想「无条件装成指定版本」:

winget import -i D:\backup\apps.json --ignore-versions # 忽略锁定版本,全部用最新

--ignore-versions 和「降级」是两件事:它是不要版本号,不是「装旧版」。要降级只能用 install --version。

pin:把这个包钉住,不要随便升​

winget pin add --id Microsoft.PowerToys -e # 轻度:挡住 upgrade --all
winget pin add --id Microsoft.PowerToys -e --blocking # 重度:连 upgrade <包> 也挡
winget pin add --id Microsoft.PowerToys -e --version 0.70.* # 门控:锁版本/版本区间
winget pin list
winget pin remove --id Microsoft.PowerToys -e
winget pin reset # 预览会被清掉的 pin
winget pin reset --force # 真的清掉

三种 pin 的区别(这是官方定义):

类型怎么加效果
Pinning默认被 upgrade --all 排除,但 winget upgrade <包> 仍可升;upgrade --all --include-pinned 可以带上它
Blocking--blockingupgrade --all 和 upgrade <包> 都挡,必须先解除 pin(--force 可强破)
Gating--version 1.2.*锁死版本或版本区间;1.2.* 表示 1.2.0 到 1.2.x 都算合法

pin 只约束 winget。软件自己的自动更新(VS Code、Chrome 都有)不受 pin 影响,仍会自己升上去。

什么时候该用哪个​

场景做法
团队要「统一环境」export --include-versions + 版本控制的 JSON,提交进仓库
某个库在旧版本才正常pin add --version <版本> 锁住,别用 --blocking(太狠)
生产/测试机pin add --blocking,防止任何人误升

5. settings.json:把 winget 调成你想要的样子​

winget settings

会直接用系统默认的 JSON 编辑器打开配置文件。

文件位置​

%LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json

如果你用的是自己编译的非商店版 winget,路径是 %LOCALAPPDATA%\Microsoft\WinGet\Settings\settings.json。

一份可直接使用的配置​

{
"$schema": "https://aka.ms/winget-settings.schema.json",

"source": {
"autoUpdateIntervalInMinutes": 60
},

"installBehavior": {
"preferences": {
"scope": "user",
"locale": ["zh-CN", "en-US"]
}
},

"visual": {
"progressBar": "rainbow"
},

"network": {
"downloader": "do"
},

"experimentalFeatures": {
"installPortability": true
},

"telemetry": {
"disable": true
}
}

第一行的 $schema 一定要留着——有它的话 VS Code 才会给你自动补全和类型检查。

常用配置项​

路径作用说明
source.autoUpdateIntervalInMinutes源索引自动更新间隔0 = 关闭;只在用到该源时才检查
installBehavior.preferences.scope默认作用域user / machine,省得每次敲 --scope
installBehavior.preferences.locale偏好语言列表装多语言安装器时优先选哪个
installBehavior.preferences.architecture偏好架构同上
installBehavior.preferences.installerType偏好安装器类型配合 --installer-type 使用
visual.progressBar进度条样式accent(默认)/ rainbow / retro / sixel / disabled
visual.enableSixels六种颜色真彩色输出终端支持时好看
network.downloader下载器实现default / wininet / do(Delivery Optimization)
telemetry.disable关闭遥测尊重系统隐私设置;也可在此显式关闭
experimentalFeatures实验功能开关见下

实验性功能​

winget features list # 看当前版本有哪些实验功能
# 逐个开关(需要管理员)
winget settings --enable LocalManifestFiles
winget settings --disable LocalManifestFiles

几个实用的:

功能作用
LocalManifestFiles允许 winget install --manifest / show --manifest 装本地 manifest(第 9 节要用)
directMSI直接处理 MSI,减少对 msiexec 的依赖
resume某些命令在重启后继续执行
fontswinget font list 之类的字体相关能力
sourcePriority源优先级
installPortability便携版相关的安装行为

实验功能会随版本增删,不要把生产环境的可用性押在实验功能上。

downloader 选哪个​

值特点
default让 winget 自己判断
wininet走系统 WinINet,代理行为和浏览器一致,适合公司环境
do走 Delivery Optimization,局域网内多台机器能共享下载同一个安装包

内网批量装机用 do 能省不少带宽。


6. 代理与网络​

winget install --id Foo.Bar -e --proxy http://127.0.0.1:7890
winget install --id Foo.Bar -e --no-proxy # 本次强制不走代理

--proxy 只影响这一次执行,不会写进配置。

三层代理,依次排查​

# 1. winget 自己能不能出去
winget source update

# 2. 系统代理有没有生效
netsh winhttp show proxy

# 3. 域名能不能解析 / 连通
Test-NetConnection cdn.winget.microsoft.com -Port 443

netsh winhttp 显示「Direct access」而浏览器能上网,说明系统 WinINet 代理没配,此时 winget(默认走 WinINet)就上不去。解决方案:

  • 在「设置 → 网络和 Internet → 代理」里配系统代理(会同步到 WinINet);
  • 或者在 settings.json 里把 network.downloader 改成 do(不走 WinINet);
  • 或者干脆每次带 --proxy。

常见网络报错​

现象原因
0x801901a0HTTP 2.0 响应头不合法,典型是中间设备(代理 / 防火墙)篡改
0x80d03002证书链验证失败:代理做了 SSL 中间人、证书没装全、或开了 __ 拦截
0x80070490安装组件本身出问题(依赖 / 系统文件)

这几个是 winget 官方 troubleshooting 文档点名的常见错误。

winget download 做离线​

# 联网机器下好
winget download --id Microsoft.VisualStudioCode -d D:\offline
winget download --id Microsoft.VisualStudioCode -d D:\offline --architecture x64

# 内网机器离线装
winget install --id Microsoft.VisualStudioCode -e `
--manifest D:\offline\Microsoft.VisualStudioCode.yaml

离线安装走的是 --manifest:从 download 出来的包会带一个 .yaml,里面已经填好了 SHA256,所以离线安装依然能通过哈希校验。这正是 download 存在的意义。


7. 日志与错误码​

找日志​

winget --logs # 打开日志目录

路径:

%LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir\

每次执行生成一个 时间戳-命令名-进程ID.log。报 bug 给别人时,把这个文件贴上去,里面什么都有。

两种日志级别​

winget install --id Foo.Bar -e --verbose # 本次生成详细日志
winget install --id Foo.Bar -e -o D:\a.log # 本次写到指定文件

详细日志里会记录源返回的原始响应、哈希计算过程、安装器退出码——排查「为什么匹配到 0 个包」这类问题时非常关键。

错误码速查​

用 winget error <hex> 可以随时查,下面是常用的一部分:

错误码符号含义
0x8A150001INTERNAL_ERRORwinget 内部错误,通常要升级客户端
0x8A150002INVALID_CLI_ARGUMENTS命令行参数不合法
0x8A150003COMMAND_FAILED命令执行失败
0x8A150004MANIFEST_FAILED打开/解析 manifest 失败
0x8A150005CTRL_SIGNAL_RECEIVED被 Ctrl+C 取消
0x8A150006SHELLEXEC_INSTALL_FAILED调用安装器失败
0x8A150007UNSUPPORTED_MANIFESTVERSIONmanifest 版本比你的客户端新 → 升级「应用安装程序」
0x8A150008DOWNLOAD_FAILED下载安装包失败
0x8A150009CANNOT_WRITE_TO_UPLEVEL_INDEX索引 schema 比客户端新,同上:升级客户端
0x8A15000AINDEX_INTEGRITY_COMPROMISED本地索引损坏
0x8A15000BSOURCES_INVALID源配置损坏
0x8A15000CSOURCE_NAME_ALREADY_EXISTS源名重复
0x8A15000DINVALID_SOURCE_TYPE源类型不合法
0x8A15000FSOURCE_DATA_MISSING源数据缺失
0x8A150010NO_APPLICABLE_INSTALLER没有适用于本机的安装器(架构/系统不匹配)
0x8A150011INSTALLER_HASH_MISMATCH安装包哈希与 manifest 不符
0x8A150012SOURCE_NAME_DOES_NOT_EXIST源不存在
0x8A150013SOURCE_ARG_ALREADY_EXISTS同一个地址已被另一个名字占用
0x8A150014NO_APPLICATIONS_FOUND没找到匹配的包
0x8A150015NO_SOURCES_DEFINED没有任何源
0x8A150016MULTIPLE_APPLICATIONS_FOUND匹配到多个包
0x8A150017NO_MANIFEST_FOUND找不到 manifest
0x8A15001CMSSTORE_APP_BLOCKED_BY_POLICY商店被组策略挡了
0x8A15001DEXPERIMENTAL_FEATURE_DISABLED实验功能没开
0x8A15C005CONFIG_ERROR_SET_APPLY_FAILEDwinget configure 应用失败
0x8A15C006CONFIG_ERROR_DUPLICATE_IDENTIFIER配置文件里有重复 ID

hash 不匹配(0x8A150011)要认真对待:它意味着「你下到的安装包和 manifest 里声明的那个不是同一个文件」。常见原因是软件官网换了安装包但没换版本号、下载被 CDN 截断、或者中间人攻击。

处理顺序:① 加 --verbose 看详细日志;② 换个网络/清 CDN 缓存重试;③ 确认软件官网是不是重新上传过安装包;④ 只有在人工确认过安装包来源可信时,才考虑 --ignore-security-hash,并且永远不要在生产/CI 里长期开着。

非 winget 的错误码​

winget 也会转发 MSI 和 MSIX 的原始错误码,用 winget error 一样能查:

  • 0x80070005:权限不足(需要管理员)
  • 0x80073D02:安装源被打开,程序在使用中 → 关掉那个软件再装
  • 0x80070490:组件未正确配置
  • 0x8A150109 相关场景:需要重启才能继续

8. 批量、CI 与 WinGet Configuration​

需求 → 工具​

需求用什么
一台机器装十来个软件winget install A B C(一次多 ID)
迁移整机软件清单export / import
团队统一开发环境(含系统设置)winget configure
定时升级所有软件计划任务 + winget upgrade --all
企业批量管控Intune / GPO 推 winget configure
反复测试 manifestWindows Sandbox + SandboxTest.ps1

计划任务:每天自动升级​

# 每天凌晨 3 点升级全部已装软件
$action = New-ScheduledTaskAction -Execute 'winget.exe' `
-Argument 'upgrade --all --silent --accept-package-agreements --accept-source-agreements --disable-interactivity'
$trigger = New-ScheduledTaskTrigger -Daily -At 3am
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest

Register-ScheduledTask -TaskName 'winget-daily-upgrade' `
-Action $action -Trigger $trigger -Principal $principal `
-Description '每日自动升级所有 winget 管理的软件'

用 SYSTEM 跑是为了拿到最高权限(省掉 UAC),代价是装的是全机器范围。个人机器上更建议用 --scope user 的配置。

WinGet Configuration:winget configure​

import 只能管「装哪些包」;configure 能把软件、版本、系统设置、注册表、服务一起声明,是真正的「声明式环境搭建」。需要 winget ≥ v1.6.2631。

文件长什么样​

文件名约定用 .winget 扩展名;放在 Git 项目里推荐放 ./.config/configuration.winget。

# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/0.2
properties:
assertions:
- resource: Microsoft.Windows.Developer/OsVersion
directives:
description: 校验系统版本下限
allowPrerelease: true
settings:
MinVersion: '10.0.22000'
resources:
- resource: Microsoft.Windows.Settings/WindowsSettings
directives:
description: 打开开发者模式
allowPrerelease: true
securityContext: elevated
settings:
DeveloperMode: true
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: vsCode
directives:
description: 安装 VS Code
securityContext: elevated
settings:
id: Microsoft.VisualStudioCode
source: winget
- resource: Microsoft.WinGet.DSC/WinGetPackage
id: node
dependsOn:
- vsCode
directives:
description: 安装 Node.js LTS
settings:
id: NodeJS.LTS
source: winget
Pin: true
configurationVersion: 0.2.0

结构说明​

节点作用
properties.assertions前置断言。断言失败 → 依赖它的资源被跳过(不算整体失败)
properties.resources要做的事,每条对应一个 PowerShell DSC 资源
resource格式固定为 模块名/DSC资源名,winget 会自动从 PowerShell Gallery 下载该模块
directives.description干什么用的(日志里能看到)
directives.securityContext: elevated需要管理员。一次配置只会弹一次 UAC,winget 会起两个进程分别跑提权 / 非提权的部分
directives.allowPrerelease允许装预发布版模块
settings传给 DSC 资源的参数
id给这条资源起个名字,供 dependsOn 引用
dependsOn依赖。顺序不重要——DSC 是声明式的,会自动算执行顺序,部分任务还能并行

WinGetPackage 这个资源常用 settings:id、source、Pin、Scope、Type、Version、Architecture、SecurityHash、Dependencies。

运行与排查​

winget configure show -f .config\configuration.winget # 看这个文件要干什么(★ 先看再跑)
winget configure validate -f .config\configuration.winget
winget configure list # 历史上应用过哪些配置
winget configure test -f .config\configuration.winget # 对比当前状态和目标状态
winget configure export -o out.winget # 把当前状态导出成配置文件

show 一定要在 configure 之前跑一次。configure 能执行任意 PowerShell DSC 资源(理论上能做任何事),微软官方明确警告过:不要运行你审阅过、且确认可信的配置文件。

${WinGetConfigRoot} 变量指向运行 configure 时的工作目录,配置里引用同目录的文件时用它,就不用写死绝对路径:

settings:
vsConfigFile: '${WinGetConfigRoot}\..\.vsconfig'

和 Scoop / Chocolatey 共存​

没有理由只用一种:

工具装什么
winget桌面软件、官方安装器、MSIX 商店应用、字体
Scoop命令行工具、便携版、免管理员、用户级隔离
Chocolatey企业管控、需要商业功能(付费版)时的老牌方案

冲突的常见场景只有一个:同一个软件两个包管理器都装了。winget list 和 winget upgrade 看到的是「已经装了所以跳过」,真正的问题在卸载时(两边的卸载记录可能对不上)。做法是固定用一个工具管一个软件。


9. 自己写 manifest 并提交到 winget-pkgs​

当 winget search 搜不到你要的软件时,正确的做法不是「放弃」,而是给它写一个 manifest 提交到社区仓库。这对全世界的用户都有用,也是 winget 社区最主要的贡献方式。

manifest 是什么​

一个 YAML 文件,描述「这个软件是什么、下载地址、哈希是多少、怎么静默安装、怎么卸载」。

winget-pkgs 仓库里长这样:

manifests/
└── m/ ← 发布者首字母的小写
└── Microsoft/ ← 发布者
└── Edge/ ← 软件名
└── 130.0.2849.80/ ← 版本号
├── Microsoft.Edge.installer.yaml
├── Microsoft.Edge.locale.en-US.yaml (如有)
└── Microsoft.Edge.license.yaml (如有)

路径规则:manifests/<发布者首字母小写>/<发布者>/<软件>/<版本>/ 一个 PR 只能提一个版本的一个包。 顺带改 README、拼写、工具脚本的,要单独开一个 PR。

一份真实的 manifest​

这是 Edge 130 的 installer manifest(原文,未改动):

# Created using wingetcreate 1.6.5.0
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.6.0.schema.json

PackageIdentifier: Microsoft.Edge
PackageVersion: 130.0.2849.80
InstallerLocale: en-US
Platform:
- Windows.Desktop
InstallerType: msi
Scope: machine
InstallModes:
- interactive
- silent
- silentWithProgress
UpgradeBehavior: install
Commands:
- msedge
Protocols:
- http
- https
FileExtensions:
- crx
- htm
- html
- pdf
- url
AppsAndFeaturesEntries:
- UpgradeCode: '{883C2625-37F7-357F-A0F4-DFAF391B2B9C}'
Installers:
- Architecture: x86
InstallerUrl: https://msedge.sf.dl.delivery.mp.microsoft.com/filestreamingservice/files/5eafcbf7-.../MicrosoftEdgeEnterpriseX86.msi
InstallerSha256: 6027D2AAD7BDBB0515CAF68230C942FFF732A433271AEBBD7F6414560331A320
ProductCode: '{5077DDBB-F9B5-32C6-92DB-F1FFDAF82CE5}'
- Architecture: x64
InstallerUrl: https://.../MicrosoftEdgeEnterpriseX64.msi
InstallerSha256: 84BA997B57BB7B4EE4222816D2202D090F8002874A3FBA6EF54F11A74CFF1BD4
ProductCode: '{414F64AB-22EF-39CD-A991-5BA905E30C30}'
ManifestType: installer
ManifestVersion: 1.6.0

顶层字段​

字段必填说明
PackageIdentifier✅就是 ID,格式 发布者.软件名,全局唯一
PackageVersion✅版本号,必须和下载文件里的实际版本一致
Publisher发布者官网
PublisherUrl发布者主页
InstallerType✅安装器类型,见下表
Scopeuser / machine,决定能不能用 --location
Platform✅Windows.Desktop / Windows.Universal
InstallerLocale只在需要区分语言包时写(多语言安装器)
InstallModesinteractive / silent / silentWithProgress
InstallerSwitches静默参数怎么传
UpgradeBehaviorinstall(直接覆盖装)/ uninstallPrevious / installThenUninstall
Commands装完能敲的命令名,决定 winget search --command 能不能搜到
FileExtensions / Protocols注册的文件类型 / 协议
AppsAndFeaturesEntries卸载、检测版本、升级全靠它
Dependencies依赖的 Windows 版本 / 功能
ManifestType / ManifestVersion✅固定 installer + 当前 schema 版本

InstallerType 与静默参数对照​

InstallerType装什么静默参数写法(InstallerSwitches)
msiMSI 包Silent: /quiet,SilentWithProgress: /quiet /norestart
wixWiX 打包的 exe/quiet、/quiet /norestart
burn引导程序(bootstrapper)/quiet、/quiet /norestart
innoInno Setup 打的 exe/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART
nullsoftNSIS 打的 exe/S
exe其他 exe必须自己写死参数;Silent / SilentWithProgress 两套
msix / appx商店格式通常无需参数
msstore商店源包传 Store 产品 ID
zip压缩包需要 Archive.NestedInstallerFiles 指定里面哪个文件
portable绿色版同 zip,加 Archive.ArchiveBinaries 生成 shim
font字体—

inno 和 nullsoft 是最有价值的两条:因为 winget 能「认识」这两种安装器,你甚至不用写任何静默参数,winget 也会自动加上正确的那一套。这就是为什么同一个软件,「有人写了 manifest」和「没写」体验差距巨大。

Archive 相关(用于 zip / portable):

Archive:
ArchiveBinaries: rg.exe # 生成一个 shim,让 `rg` 命令到处都能用
NestedInstallerFiles:
- RelativeFilePath: rg.exe # 装完后主程序位置

AppsAndFeaturesEntries:别偷懒​

这个字段决定了三件事:能不能被 winget list 认出来、能不能干净地卸载、升级时怎么判断版本。

常见组合:

AppsAndFeaturesEntries:
- DisplayName: Some App 1.0
DisplayVersion: 1.0.0
ProductCode: '{GUID-OR-MSI-CODE}' # MSI 类必填
UpgradeCode: '{GUID-OR-MSI-CODE}' # 决定「这是同一个软件的新版本」
InstallerType: exe
DisplayIcon: SomeApp.exe
Publisher: Some Publisher
UninstallString: '"C:\Program Files\SomeApp\unins000.exe" /SILENT'
QuietUninstallString: '"C:\Program Files\SomeApp\unins000.exe" /VERYSILENT'

UpgradeCode 写错是 PR 被退回的高频原因——它必须和注册表里真实的一致。

怎么拿到这些值​

需要的信息怎么查
安装包 SHA256winget hash --file .\setup.exe
静默参数查该安装器类型的官方文档(Inno / NSIS / WiX 官网)
ProductCode / UpgradeCode装完之后从「程序和功能」→ 右键属性 / 或用 Get-ItemProperty 读卸载注册表项
版本号文件属性 → 详细信息,或安装后从注册表读 DisplayVersion

工具:WinGetCreate​

手写 YAML 很累,官方有工具:

winget install wingetcreate

WinGetCreate 的常用命令:

wingetcreate sources # 装它的依赖(winget-cli、YamlCreate 等)
wingetcreate new C:\Downloads\setup.exe # 扫安装包,生成 manifest 骨架
wingetcreate update Publisher AppName # 为已有包生成「新版本」的 manifest
wingetcreate show Publisher AppName # 导出仓库里现有的 manifest
wingetcreate export C:\Downloads\app.zip # 导出成你写的格式
wingetcreate submit .\manifests\... # 直接帮你开 PR(需要 GitHub 认证)

社区还有个非常好用的 GUI 工具 Komac,可以点点选选生成 manifest,也能直接提 PR。

建议流程:wingetcreate new 生成骨架 → 手工补 AppsAndFeaturesEntries 和静默参数 → 本地验证 → 提交。

本地验证​

前置条件:本地 manifest 功能默认是关闭的,需要在管理员 PowerShell 里开一次:

winget settings --enable LocalManifestFiles
# 1. 语法 + schema 校验
winget validate --manifest .\Microsoft.SomeApp.installer.yaml

# 2. 看 winget 自己怎么理解这个 manifest
winget show --manifest .\Microsoft.SomeApp.installer.yaml

# 3. 真装一次
winget install --manifest .\Microsoft.SomeApp.installer.yaml

强烈建议开详细日志:

winget install --manifest .\manifest.yaml --verbose

validate 只检查格式和schema,它不检查你的静默参数对不对、不检查 URL 还能不能下载。所以第 3 步的实装测试才是真正的验证。

在 Windows Sandbox 里测​

仓库自带一个测试脚本,在一次性的干净环境里跑安装,不污染你的机器:

git clone https://github.com/microsoft/winget-pkgs
cd winget-pkgs
.\Tools\SandboxTest.ps1 <manifest 路径>

它会开一个 Windows Sandbox、装 winget、校验 manifest、尝试安装、再把结果贴回来。提交 PR 之前跑一次,能挡掉绝大多数「在我机器上好好的」。

提交 PR​

  1. Fork microsoft/winget-pkgs
  2. 按目录结构放文件:manifests/<首字母小写>/<发布者>/<软件>/<版本>/
  3. 一个 PR 只放一个版本的一个包(其他改动另开 PR)
  4. 提交前确认:仓库里还没有这个软件的 manifest、也没有人在开着的 PR
  5. 开 PR,描述里写清来源、下载地址、静默参数、测试情况

校验流程​

GitHub 上的机器人(BOT)和核心团队会打 label 来标记进度。校验失败时 BOT 会指出问题并把 PR 退回给你。流程里有两个时间点值得注意:

5 天没有任何活动 → 打上 No-Recent-Activity 标签;再过 3 天仍无活动 → PR 被自动关闭。

常见被退回的原因​

问题说明
同一个 PR 提了多个版本规则硬性要求一 PR 一版本
顺带改了非 manifest 文件README / 工具脚本的改动必须单独提 PR
InstallerSha256 算错用 winget hash 重算,别手抄
静默参数没验证必须实装测试过(--silent 真的不弹窗)
UpgradeCode / ProductCode 不对与注册表实际值不一致
Scope 与 --location 兼容性问题见第 3 节的规则
软件没有官网 / 域名可疑仓库有政策限制
安装包是「每次下载都不一样」有些站的安装器是动态生成的,需要先下载下来再上传或换源
复用他人 manifest同一个包的 manifest 只能有一个来源

有价值的可选字段(如 Publisher、Tags、License、ReleaseNotes、图标等)受仓库政策限制,其中一部分需要**「已验证开发者」流程才能提交。所以新 PR 老实写必填字段就行**,别为了好看去硬填。

提新版本​

软件更新后,给同一个包加一个新版本目录:

manifests/m/Microsoft/Edge/131.0.0.0/Microsoft.Edge.installer.yaml

同样一个 PR 一个版本。老版本目录不要删——pin --version 和 --version 装旧版本时还要靠它。


10. 与本项目配合​

本站的自动化脚本(DrissionPage / ruyipage 等)常和 winget 配合用在两处:

1. 测试环境初始化​

自动化脚本要跑的机器上,浏览器、驱动、Python 依赖都得先在。用 winget 装,比在文档里写一堆「请先下载并安装……」可靠得多:

# 一台干净的 CI/测试机
winget install -e --id Python.Python.3.12 --silent --accept-package-agreements
winget install -e --id Google.Chrome --silent --accept-package-agreements
winget install -e --id Google.ChromeDriver --silent --accept-package-agreements
pip install DrissionPage ruyipage

2. 驱动版本对不上的兜底​

DrissionPage 这类库依赖特定版本的 Chrome/Edge。跑之前可以用 winget 校验浏览器版本:

$edge = winget list --id Microsoft.Edge -e
$edge.Version # 和脚本里期望的版本比对
if ($edge.Version -notlike '13*.0*') {
Write-Warning "Edge 版本不符合预期:$($edge.Version)"
winget upgrade --id Microsoft.Edge -e --silent --accept-package-agreements
}

3. 与油猴脚本配合​

本站的油猴文档 讲的是「在浏览器里注入代码」。如果你在自动化流程里需要加载某个用户脚本,可以:

  • 用 winget 把脚本依赖的环境装好(Node、浏览器);
  • 脚本本身存成 .user.js 文件,由自动化脚本读取后注入页面(相当于 @grant none 的无沙箱模式,能用的 GM_* API 有限)。

分工原则:一次性、可编程的逻辑放 Python 侧;长期复用、人机共用的页面增强放油猴脚本;环境依赖和版本校验交给 winget。


11. 排错清单​

症状根因处理
0x8A150007 / 0x8A150009索引或 manifest 版本比客户端新更新「应用安装程序」
0x8A15000A 索引损坏本地索引坏了winget source reset;仍不行就更新客户端
0x8A150011 哈希不匹配安装包与 manifest 不符--verbose 看详情;确认软件是否重传过安装包;不要随手 --ignore-security-hash
0x8A150010 无适用安装器架构/系统不匹配-a x64;或换 --installer-type
0x8A150014 找不到包ID 写错 / 包被下架winget search 核对 ID;看仓库有没有删除该包
0x8A150016 多个匹配多源重名--id X -e -s winget
0x8A15001D 实验功能未开用了需要开关的命令管理员 winget settings --enable <功能>
0x80070005 权限不足需要管理员用管理员 PowerShell,或 --scope user
0x80073D02 源被占用目标软件正在运行关掉那个软件再装
0x801901a0 / 0x80d03002代理/防火墙篡改或 SSL 中间人配好系统代理;装好根证书;--no-proxy 试一下
winget list 有、uninstall 找不到多源同名加 -s 限定源
装了但 PATH 里没有安装器没改环境变量新开一个终端再看
静默安装仍弹窗manifest 没写静默参数 / 安装器不支持用 --interactive 装一次确认行为;这是 manifest 的质量问题,提 PR 补上
--scope user 失败在管理员会话里跑的换成普通用户 PowerShell
-l 指定目录无效包不支持自定义位置winget show 确认;这类包改用 Scoop
装完还是提示可升级预览版 / 索引旧 / 源不一致source update;winget show 看版本后缀;pin add --version 锁住
升级后功能变了上游改了默认行为pin add --version <已知良好版本>
import 报一堆警告源里已无对应包用 export --include-versions 并把 JSON 提交进版本库,定期(比如每月)重新导出
configure 部分资源没跑断言失败导致依赖被跳过winget configure show 看断言;configure test 看差异

排错的通用顺序:

winget --version # 1. 客户端是不是太旧(最常见根因)
winget --info # 2. 源对不对、有没有被组策略改过
winget source update # 3. 索引刷新
winget error <错误码> # 4. 读懂报错
<原命令> --verbose # 5. 看详细日志

90% 的 winget「玄学问题」最后都落在第 1 步和第 3 步。 先更新客户端,再刷新索引,然后才开始怀疑别的。


参考​