适用于 Windows 10/11,PowerShell 7.0 及以上版本
WinGet:Windows 包管理的新时代 在 Linux 世界中,apt、yum、pacman 等包管理器早已是系统管理的标配工具。而在 Windows 平台上,长期以来我们只能依赖图形界面的安装向导,或者各自为政的第三方管理工具(如 Chocolatey、Scoop)。2020 年微软推出 WinGet(Windows Package Manager)后,这一局面终于开始改变。
如今 WinGet 已经相当成熟,软件源中收录了超过 6000 款常用软件,覆盖开发工具、浏览器、办公套件等几乎所有类别。更重要的是,WinGet 是 Windows 10/11 的内置组件,无需额外安装即可使用。将 WinGet 与 PowerShell 结合,我们可以实现软件安装、升级、卸载的完全自动化——无论是日常维护还是新机初始化,都能一键搞定。
本文将介绍如何用 PowerShell 封装 WinGet 命令、批量安装软件清单、自动检测升级、生成软件报告,以及编写新机初始化脚本。
封装 WinGet 常用命令 WinGet 的命令行输出是纯文本格式,不便于在脚本中进一步处理。我们先将常用的 WinGet 操作封装成 PowerShell 函数,输出结构化对象,方便后续链式调用和自动化编排。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 function Get-WinGetPackage { [CmdletBinding ()] param ( [string ]$Name ) $args = @ ('list' ) if ($Name ) { $args += @ ('--name' , $Name ) } $output = winget @args 2 >&1 $lines = $output | Where-Object { $_ -notmatch '^Name\s+Id' -and $_ -notmatch '^\s*$' -and $_ -notmatch '^\d+ packages' } foreach ($line in $lines ) { $parts = $line -split '\s{2,}' if ($parts .Count -ge 3 ) { [PSCustomObject ]@ { Name = $parts [0 ] Id = $parts [1 ] Version = $parts [2 ] } } } } function Install-WinGetPackage { [CmdletBinding (SupportsShouldProcess )] param ( [Parameter (Mandatory )] [string ]$Id , [switch ]$Force ) if ($PSCmdlet .ShouldProcess($Id , 'Install WinGet package' )) { $installArgs = @ ('install' , '--id' , $Id , '--accept-package-agreements' , '--accept-source-agreements' ) if ($Force ) { $installArgs += '--force' } winget @installArgs 2 >&1 } } function Update-WinGetPackage { [CmdletBinding (SupportsShouldProcess )] param ( [string ]$Id , [switch ]$All ) if ($PSCmdlet .ShouldProcess($Id , 'Update WinGet package' )) { $upgradeArgs = @ ('upgrade' ) if ($All ) { $upgradeArgs += '--all' } elseif ($Id ) { $upgradeArgs += @ ('--id' , $Id ) } $upgradeArgs += @ ('--accept-package-agreements' , '--accept-source-agreements' ) winget @upgradeArgs 2 >&1 } }
上面的代码定义了三个核心函数:Get-WinGetPackage 解析 winget list 的纯文本输出为结构化对象,Install-WinGetPackage 封装安装命令并自动接受协议,Update-WinGetPackage 封装升级命令。每个函数都支持 ShouldProcess,可以通过 -WhatIf 参数进行干运行预览。
1 2 3 4 5 6 7 Name Id Version ---- -- ------- PowerShell Microsoft.PowerShell 7.4.6 Visual Studio Code Microsoft.VisualStudioCode 1.96.2 Windows Terminal Microsoft.WindowsTerminal 1.21.3231 Git Git.Git 2.47.1 7-Zip 7zip.7zip 24.08
批量安装软件清单 重装系统后手动安装几十个软件是一件耗时且容易遗漏的事情。将常用软件整理成 JSON 清单文件,然后用脚本批量安装,可以确保每次安装的软件集一致且不遗漏。
首先,创建一个软件清单 JSON 文件,将需要的软件按类别分组:
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 $manifest = @' { "categories": [ { "name": "开发工具", "packages": [ "Microsoft.VisualStudioCode", "Git.Git", "Microsoft.PowerShell", "OpenJS.NodeJS.LTS", "Python.Python.3.12" ] }, { "name": "浏览器", "packages": [ "Google.Chrome", "Mozilla.Firefox" ] }, { "name": "系统工具", "packages": [ "7zip.7zip", "Microsoft.WindowsTerminal", "Microsoft.Powertoys", "voidtools.Everything" ] }, { "name": "效率工具", "packages": [ "Obsidian.Obsidian", "Telegram.TelegramDesktop" ] } ] } '@ $pkgList = $manifest | ConvertFrom-Json foreach ($category in $pkgList .categories) { Write-Host "`n=== 安装类别: $ ($category .name) ===" -ForegroundColor Cyan foreach ($pkgId in $category .packages) { Write-Host "正在安装: $pkgId ..." -ForegroundColor Yellow -NoNewline $result = winget install --id $pkgId ` --accept-package-agreements ` --accept-source-agreements 2 >&1 if ($LASTEXITCODE -eq 0 ) { Write-Host " 完成" -ForegroundColor Green } else { Write-Host " 失败 (退出码: $LASTEXITCODE )" -ForegroundColor Red Write-Host " 错误信息: $ ($result | Select-Object -Last 2)" -ForegroundColor DarkRed } } }
这段代码将软件清单以 JSON 格式定义,按类别分组管理。安装时逐类别遍历,每个软件包单独安装并报告结果。JSON 格式的清单文件也可以单独保存到磁盘,纳入版本控制,方便团队共享。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 === 安装类别: 开发工具 === 正在安装: Microsoft.VisualStudioCode ... 完成 正在安装: Git.Git ... 完成 正在安装: Microsoft.PowerShell ... 完成 正在安装: OpenJS.NodeJS.LTS ... 完成 正在安装: Python.Python.3.12 ... 完成 === 安装类别: 浏览器 === 正在安装: Google.Chrome ... 完成 正在安装: Mozilla.Firefox ... 完成 === 安装类别: 系统工具 === 正在安装: 7zip.7zip ... 完成 正在安装: Microsoft.WindowsTerminal ... 完成 正在安装: Microsoft.Powertoys ... 完成 正在安装: voidtools.Everything ... 完成 === 安装类别: 效率工具 === 正在安装: Obsidian.Obsidian ... 完成 正在安装: Telegram.TelegramDesktop ... 完成
检测已安装软件并自动升级 软件更新往往包含安全补丁和功能改进,但手动逐个检查升级非常繁琐。下面的脚本会扫描所有可通过 WinGet 升级的软件,筛选出有可用更新的,然后自动执行升级。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 function Invoke-WinGetAutoUpgrade { [CmdletBinding ()] param ( [switch ]$DryRun , [string []]$ExcludeIds ) Write-Host "正在扫描可升级的软件包..." -ForegroundColor Cyan $upgradeList = winget upgrade 2 >&1 $packages = $upgradeList | Where-Object { $_ -match '^\S' -and $_ -notmatch '^Name\s+Id' -and $_ -notmatch '^\d+ upgrades' } | ForEach-Object { $parts = $_ -split '\s{2,}' if ($parts .Count -ge 4 ) { [PSCustomObject ]@ { Name = $parts [0 ] Id = $parts [1 ] InstalledVer = $parts [2 ] AvailableVer = $parts [3 ] } } } if ($ExcludeIds ) { $packages = $packages | Where-Object { $_ .Id -notin $ExcludeIds } } if (-not $packages ) { Write-Host "所有软件均为最新版本" -ForegroundColor Green return } Write-Host "发现 $ ($packages .Count) 个可升级软件包:" -ForegroundColor Yellow $packages | Format-Table -Property Name, Id, InstalledVer, AvailableVer -AutoSize if ($DryRun ) { Write-Host "[DryRun] 跳过实际升级" -ForegroundColor DarkYellow return } foreach ($pkg in $packages ) { Write-Host "升级 $ ($pkg .Name) ($ ($pkg .InstalledVer) -> $ ($pkg .AvailableVer))..." ` -ForegroundColor Yellow -NoNewline $result = winget upgrade --id $pkg .Id ` --accept-package-agreements ` --accept-source-agreements 2 >&1 if ($LASTEXITCODE -eq 0 ) { Write-Host " 完成" -ForegroundColor Green } else { Write-Host " 失败" -ForegroundColor Red } } Write-Host "`n升级完成" -ForegroundColor Green } Invoke-WinGetAutoUpgrade -DryRun -ExcludeIds @ ('SomeApp.Legacy' )
这个函数支持 -DryRun 参数先预览可升级列表而不实际执行,也支持 -ExcludeIds 排除某些已知升级后会出问题的软件。对于生产环境,建议先运行干运行模式确认,再执行实际升级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 正在扫描可升级的软件包... 发现 4 个可升级软件包: Name Id InstalledVer AvailableVer ---- -- ------------ ------------ 7-Zip 7zip.7zip 24.06 24.08 Visual Studio Code Microsoft.VisualStudioCode 1.95.3 1.96.2 Git Git.Git 2.46.0 2.47.1 PowerShell Microsoft.PowerShell 7.4.5 7.4.6 升级 7-Zip (24.06 -> 24.08)... 完成 升级 Visual Studio Code (1.95.3 -> 1.96.2)... 完成 升级 Git (2.46.0 -> 2.47.1)... 完成 升级 PowerShell (7.4.5 -> 7.4.6)... 完成 升级完成
生成软件清单报告 定期生成系统已安装软件的报告,有助于审计和资产盘点。下面的脚本将 WinGet 管理的软件信息导出为 CSV 和 HTML 两种格式,便于存档和查看。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 function Export-WinGetReport { [CmdletBinding ()] param ( [string ]$OutputDir = [Environment ]::GetFolderPath('Desktop' ) ) $timestamp = Get-Date -Format 'yyyyMMdd_HHmmss' $csvPath = Join-Path $OutputDir "WinGet_Inventory_$ {timestamp}.csv" $htmlPath = Join-Path $OutputDir "WinGet_Inventory_$ {timestamp}.html" Write-Host "正在收集已安装软件信息..." -ForegroundColor Cyan $rawOutput = winget list 2 >&1 $packages = $rawOutput | Where-Object { $_ -match '^\S' -and $_ -notmatch '^Name\s+Id' -and $_ -notmatch '^\d+ packages' } | ForEach-Object { $parts = $_ -split '\s{2,}' if ($parts .Count -ge 3 ) { [PSCustomObject ]@ { Name = $parts [0 ] Id = $parts [1 ] Version = $parts [2 ] ScanDate = (Get-Date -Format 'yyyy-MM-dd' ) } } } $packages | Export-Csv -Path $csvPath -NoTypeInformation -Encoding UTF8 Write-Host "CSV 报告已保存: $csvPath " -ForegroundColor Green $htmlHeader = @" <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>WinGet 软件清单报告 - $timestamp </title> <style> body { font-family: 'Segoe UI', sans-serif; margin: 20px; } h1 { color: #0078d4; } table { border-collapse: collapse; width: 100%; } th { background: #0078d4; color: white; padding: 8px 12px; text-align: left; } td { border-bottom: 1px solid #e0e0e0; padding: 8px 12px; } tr:nth-child(even) { background: #f5f5f5; } .total { margin-top: 16px; font-weight: bold; color: #333; } </style> </head> <body> <h1>WinGet 软件清单报告</h1> <p>生成时间: $ (Get-Date -Format 'yyyy-MM-dd HH:mm:ss')</p> "@ $htmlBody = $packages | ConvertTo-Html -Fragment $htmlFooter = @" <p class="total">共 $ ($packages .Count) 个软件包</p> </body></html> "@ $htmlHeader + $htmlBody + $htmlFooter | Set-Content -Path $htmlPath -Encoding UTF8 Write-Host "HTML 报告已保存: $htmlPath " -ForegroundColor Green Write-Host "共发现 $ ($packages .Count) 个已安装软件包" -ForegroundColor Cyan } Export-WinGetReport
脚本会同时输出 CSV 和 HTML 两种格式。CSV 适合导入 Excel 进行进一步分析,HTML 报告则可以直接在浏览器中查看,样式更友好。两种文件都以时间戳命名,方便归档对比。
1 2 3 4 正在收集已安装软件信息... CSV 报告已保存: C:\Users\admin\Desktop\WinGet_Inventory_20250429_080000.csv HTML 报告已保存: C:\Users\admin\Desktop\WinGet_Inventory_20250429_080000.html 共发现 42 个已安装软件包
新机初始化脚本 将前面的功能组合起来,我们可以编写一个完整的新机初始化脚本。这个脚本会安装基础软件、配置 PowerShell 环境、生成初始软件报告,让一台全新的 Windows 电脑在几分钟内进入可工作状态。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 param ( [string ]$ManifestPath , [switch ]$SkipUpgrade , [switch ]$SkipReport ) $ErrorActionPreference = 'Stop' Write-Host @" ============================================ Windows 新机初始化脚本 $ (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ============================================ "@ -ForegroundColor CyanWrite-Host "[1/5] 检查 WinGet 可用性..." -ForegroundColor Yellow$wingetCmd = Get-Command winget -ErrorAction SilentlyContinueif (-not $wingetCmd ) { Write-Host "WinGet 未安装,正在通过 App Installer 安装..." -ForegroundColor Yellow Write-Host "请从 Microsoft Store 安装 '应用安装程序' 后重试" -ForegroundColor Red return } Write-Host "WinGet 版本: $ (winget --version)" -ForegroundColor GreenWrite-Host "`n[2/5] 加载软件安装清单..." -ForegroundColor Yellowif (-not $ManifestPath ) { $ManifestPath = Join-Path $PSScriptRoot 'software-manifest.json' } if (-not (Test-Path $ManifestPath )) { Write-Host "清单文件不存在: $ManifestPath " -ForegroundColor Red Write-Host "请创建清单文件或通过 -ManifestPath 参数指定路径" -ForegroundColor Yellow return } $manifest = Get-Content $ManifestPath -Raw | ConvertFrom-Json $totalPkgs = ($manifest .categories | ForEach-Object { $_ .packages.Count } | Measure-Object -Sum ).Sum Write-Host "清单包含 $ ($manifest .categories.Count) 个类别,共 $totalPkgs 个软件包" ` -ForegroundColor Green Write-Host "`n[3/5] 开始批量安装..." -ForegroundColor Yellow$success = 0 $failed = 0 foreach ($category in $manifest .categories) { Write-Host "`n--- $ ($category .name) ---" -ForegroundColor Cyan foreach ($pkgId in $category .packages) { try { winget install --id $pkgId ` --accept-package-agreements ` --accept-source-agreements 2 >&1 | Out-Null if ($LASTEXITCODE -eq 0 ) { Write-Host " [OK] $pkgId " -ForegroundColor Green $success ++ } else { Write-Host " [FAIL] $pkgId (退出码: $LASTEXITCODE )" -ForegroundColor Red $failed ++ } } catch { Write-Host " [ERROR] $pkgId : $_ " -ForegroundColor Red $failed ++ } } } Write-Host "`n安装统计: 成功 $success / 失败 $failed / 总计 $totalPkgs " -ForegroundColor Cyanif (-not $SkipUpgrade ) { Write-Host "`n[4/5] 升级所有已安装软件到最新版..." -ForegroundColor Yellow winget upgrade --all --accept-package-agreements --accept-source-agreements 2 >&1 Write-Host "升级完成" -ForegroundColor Green } else { Write-Host "`n[4/5] 跳过软件升级" -ForegroundColor DarkYellow } if (-not $SkipReport ) { Write-Host "`n[5/5] 生成软件清单报告..." -ForegroundColor Yellow Export-WinGetReport } else { Write-Host "`n[5/5] 跳过报告生成" -ForegroundColor DarkYellow } Write-Host @" ============================================ 初始化完成! 成功安装: $success 安装失败: $failed $ (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') ============================================ "@ -ForegroundColor Green
这个脚本设计了五个步骤:检查 WinGet 可用性、加载软件清单、批量安装、升级已安装软件、生成报告。每一步都有清晰的进度提示,支持通过参数跳过升级和报告步骤。脚本开头使用 #Requires -RunAsAdministrator 确保以管理员权限运行,因为部分软件安装需要提升权限。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 ============================================ Windows 新机初始化脚本 2025-04-29 08:00:00 ============================================ [1/5] 检查 WinGet 可用性... WinGet 版本: v1.9.25200 [2/5] 加载软件安装清单... 清单包含 4 个类别,共 13 个软件包 [3/5] 开始批量安装... --- 开发工具 --- [OK] Microsoft.VisualStudioCode [OK] Git.Git [OK] Microsoft.PowerShell [OK] OpenJS.NodeJS.LTS [OK] Python.Python.3.12 --- 浏览器 --- [OK] Google.Chrome [OK] Mozilla.Firefox --- 系统工具 --- [OK] 7zip.7zip [OK] Microsoft.WindowsTerminal [OK] Microsoft.Powertoys [OK] voidtools.Everything --- 效率工具 --- [OK] Obsidian.Obsidian [OK] Telegram.TelegramDesktop 安装统计: 成功 13 / 失败 0 / 总计 13 [4/5] 升级所有已安装软件到最新版... 升级完成 [5/5] 生成软件清单报告... CSV 报告已保存: C:\Users\admin\Desktop\WinGet_Inventory_20250429_080000.csv HTML 报告已保存: C:\Users\admin\Desktop\WinGet_Inventory_20250429_080000.html 共发现 42 个已安装软件包 ============================================ 初始化完成! 成功安装: 13 安装失败: 0 2025-04-29 08:05:32 ============================================
注意事项
管理员权限 :部分软件(如系统级安装的程序)需要以管理员身份运行 PowerShell,脚本中已通过 #Requires -RunAsAdministrator 确保这一点
WinGet 输出解析 :WinGet 的命令行输出格式可能在版本更新后变化,解析逻辑需要相应调整。如果遇到解析异常,建议检查 WinGet 版本并更新解析正则
软件源一致性 :WinGet 有两个源(winget 和 msstore),某些软件同时存在于两个源中,安装时建议使用 --id 精确指定软件包 ID,避免歧义
安装顺序 :部分软件有依赖关系(如 VS Code 扩展依赖 VS Code 本体),清单中应确保依赖项排在前面
网络环境 :在企业网络中,可能需要配置代理或使用内部软件源,WinGet 支持通过 --source 参数指定自定义源
错误处理 :批量安装时某个软件失败不应阻断整个流程,脚本中对每个软件单独 try/catch 处理,最终汇总成功和失败数量