别再一个个点更新了:让 Agent 一次管好 Windows 11、Ubuntu 26.04 和 macOS 26 的常用软件升级
先说结论
常用软件升级这件事,最痛苦的不是命令复杂,而是它分散在三个世界里:Windows 11 有 WinGet、Microsoft Store 和传统安装器;Ubuntu 26.04 有 apt、snap、flatpak 和服务重启;macOS 26 有系统更新、App Store、Homebrew 和一堆手动下载的应用。人手工做,很容易漏;让 AI Agent 做,如果不给边界,又可能太激进。
我的建议是:把 Agent 当成“值班更新管理员”,让它先盘点、再预演、再执行、最后验收。本文给出三套不依赖第三方升级助手或云端服务的一键脚本,分别覆盖 Windows 11、Ubuntu 26.04 和 macOS 26;同时给出人工自动执行和 Agent 自动配置两种方法。脚本只调用系统已有的更新入口和已经配置好的包管理器,不包含任何真实内网地址、完整计算机名、私有域名或密钥。

图 1:AI 生成封面。升级软件不是“狂点下一步”,更像一次有记录、有验收的体检。
一、问题背景:为什么“更新软件”越来越适合交给 Agent
十年前,升级软件大多是一个应用弹一个窗口,用户点一下“更新”。现在完全不是这样。开发机、家用电脑、小服务器、笔记本经常混在一起用:浏览器、压缩软件、终端工具、编辑器、播放器、数据库客户端、容器工具、远程控制工具、同步工具、命令行工具都需要更新。它们有的来自商店,有的来自包管理器,有的来自厂商安装包,有的来自系统更新。
这件事看似很小,但长期不做会带来三个问题。第一是安全问题,浏览器、解压缩工具、远程访问工具、开发运行时一旦落后太多,很容易踩到已公开漏洞。第二是兼容性问题,某个 CLI 版本太旧,脚本在别人机器上能跑,在你机器上就失败。第三是可维护性问题,半年不更新,一次性升级时会同时碰到依赖变化、配置迁移、重启要求和插件不兼容。
如果把电脑比作厨房,软件升级就像定期检查冰箱、燃气灶、微波炉和水龙头。你不需要每天把厨房拆一遍,但也不能等到燃气灶点不着火才想起来维护。Agent 的价值不是替你“盲目升级到最新”,而是替你把重复检查做完整:有哪些软件可更新,哪些只是补丁,哪些可能需要重启,哪些失败了,失败日志在哪里。
图 2:推荐流程是盘点、预演、升级、验收、记录回滚线索。没有验收的升级,只能算“跑过命令”。
二、问题表现:你以为只是版本旧,其实是更新入口太分散
常见表现非常熟悉:Windows 上 winget upgrade 能看到一堆包,但 Microsoft Store 里还有应用要更新;Ubuntu 上 apt upgrade 完成了,snap refresh 还有浏览器或工具没动;macOS 上系统补丁装了,Homebrew 里的 openssl、node、ffmpeg 还停在旧版本。更麻烦的是,很多 GUI 应用自己也有更新器,包管理器不一定接管。
这就是“更新入口分散”的本质问题。它像一个小区有好几个快递柜:菜鸟柜、丰巢柜、物业办公室、门卫室都有包裹。你只去其中一个柜子看,当然会漏。Agent 要做的第一件事,不是立即升级,而是先确认“这个系统有几个更新入口”。
Windows 11 常见入口是 WinGet、Microsoft Store 和厂商自带更新器。Ubuntu 26.04 常见入口是 apt、snap、flatpak、语言生态包管理器,以及容器镜像。macOS 26 常见入口是 Software Update、App Store、Homebrew、Mac App Store 之外的自更新应用。本文脚本重点覆盖系统层和常见包管理器层,不会替你随意下载未知安装包,也不会接管需要登录账号的私有商店。
图 3:三个系统命令不同,但语法相同:先列清单,再升级,再清理,再验证。
三、问题分析:Agent 可以自动做,但不能没有护栏
Agent 做软件升级,最容易出问题的地方不是“不会执行命令”,而是“太听话”。你说“全部升级到最新”,它可能会尝试做大版本系统升级;你说“自动修复失败”,它可能会卸载冲突包;你说“把日志发我”,它可能把机器名、路径、用户名、仓库地址一起复制出来。真正靠谱的 Agent 自动化,一定要先把边界写清楚。
我的经验是,给 Agent 的升级任务至少要包含六条护栏:不要执行大版本 OS 升级;不要下载不明安装器;不要输出真实内网地址、完整计算机名、私有域名或密钥;默认先 dry-run 或列清单;需要重启时先提示;最后必须给出升级前后版本、失败项和日志路径。
这和请人修家电一样。你不会只说“把家里所有电器弄到最新”,你会说:先看哪些需要维护,不要拆墙,不要动保险箱,不要丢保修卡,换下来的零件放桌上,修完告诉我哪几个修好了。Agent 也一样,边界越清楚,执行越可靠。
图 4:Agent 不是魔法按钮,它更像执行力很强的值班员。值班员需要工作范围、禁止动作和验收标准。
四、根因:升级失败大多不是“版本号”问题,而是状态管理问题
软件升级失败,表面看是包下载失败、依赖冲突、文件占用、权限不够、网络慢、重启未完成。根因往往是状态管理没做:升级前不知道当前版本,升级中没有日志,升级后没有验证,失败时不知道是哪个入口失败。
Windows 上常见状态包括:某个安装器需要管理员权限,应用正在运行导致文件占用,WinGet 识别不到旧安装器,Store 应用需要登录状态。Ubuntu 上常见状态包括:apt 锁被其他进程占用,某些包被 hold,full-upgrade 会移除包,内核升级后需要重启。macOS 上常见状态包括:系统更新需要用户确认,App Store 应用需要 Apple ID,Homebrew formula 依赖链很长,某些 GUI 应用并不在 brew 管理范围内。
所以脚本设计不应该只是一句“upgrade all”。更好的做法是:创建日志目录;输出当前版本和可升级清单;执行包管理器自带升级;记录退出码;做清理;检查是否需要重启;最后打印下一步。这样即使失败,也知道失败在哪里。

图 5:WinGet 提供了统一的 upgrade 入口,适合做 Windows 常用软件的第一层批量升级。
五、人工自动执行:三套一键脚本
下面三套脚本都遵守同一个原则:默认只盘点,不直接升级。Windows 需要加 -Execute;Ubuntu 需要设置 EXECUTE=1;macOS 也需要设置 EXECUTE=1。它们不依赖第三方升级助手,不调用任何 AI API,也不会自动下载来历不明的安装器。它们只使用系统自带命令和本机已经安装、已经配置的软件包管理器。
5.1 Windows 11:PowerShell + WinGet
先保存为 Upgrade-Windows11-Apps.ps1,用普通或管理员 PowerShell 预演:
powershell -ExecutionPolicy Bypass -File .\Upgrade-Windows11-Apps.ps1
确认清单后执行:
powershell -ExecutionPolicy Bypass -File .\Upgrade-Windows11-Apps.ps1 -Execute
脚本如下:
param(
[switch]$Execute,
[switch]$IncludeUnknown
)
$ErrorActionPreference = "Stop"
$LogRoot = Join-Path $env:USERPROFILE "upgrade-logs"
$Stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$LogFile = Join-Path $LogRoot "windows11-app-upgrade-$Stamp.log"
New-Item -ItemType Directory -Force -Path $LogRoot | Out-Null
Start-Transcript -Path $LogFile -Force | Out-Null
try {
Write-Host "Mode: $(if ($Execute) { 'EXECUTE' } else { 'DRY-RUN' })"
Write-Host "Log: $LogFile"
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
throw "winget is not available. Install or repair App Installer from Microsoft Store first."
}
Write-Host "`n[System]"
Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsHardwareAbstractionLayer | Format-List
Write-Host "`n[WinGet sources]"
winget source list
Write-Host "`n[Upgradeable packages]"
winget upgrade
$ExportPath = Join-Path $LogRoot "winget-export-$Stamp.json"
Write-Host "`n[Inventory export] $ExportPath"
winget export --output $ExportPath --accept-source-agreements | Out-Host
if ($Execute) {
$args = @("upgrade", "--all", "--silent", "--disable-interactivity", "--accept-package-agreements", "--accept-source-agreements")
if ($IncludeUnknown) { $args += "--include-unknown" }
Write-Host "`n[Upgrade] winget $($args -join ' ')"
& winget @args
$code = $LASTEXITCODE
Write-Host "winget exit code: $code"
} else {
Write-Host "`nDry-run only. Re-run with -Execute to upgrade. Add -IncludeUnknown only after reviewing unknown-version packages."
}
Write-Host "`n[After]"
winget upgrade
Write-Host "`nIf Store apps still show updates, open Microsoft Store > Library > Get updates."
}
finally {
Stop-Transcript | Out-Null
}
--include-unknown 不建议默认开启。它像把“看不清标签的药盒”也拿去换新,可能没事,但应该先确认。对于工作电脑,我通常先跑不带 -IncludeUnknown 的升级,确认没有明显异常后,再单独处理未知版本软件。

图 6:升级前导出清单的意义,是让你知道“升级前这台机器大概装了什么”。
5.2 Ubuntu 26.04:Bash + apt/snap/flatpak
保存为 upgrade-ubuntu2604-apps.sh。先预演:
bash upgrade-ubuntu2604-apps.sh
确认后执行:
EXECUTE=1 bash upgrade-ubuntu2604-apps.sh
如果你明确允许 apt 做可能安装或移除包的完整升级,再加:
EXECUTE=1 FULL_UPGRADE=1 bash upgrade-ubuntu2604-apps.sh
脚本如下:
#!/usr/bin/env bash
set -euo pipefail
EXECUTE="${EXECUTE:-0}"
FULL_UPGRADE="${FULL_UPGRADE:-0}"
LOG_DIR="${LOG_DIR:-$HOME/upgrade-logs}"
STAMP="$(date +%Y%m%d-%H%M%S)"
LOG_FILE="$LOG_DIR/ubuntu2604-app-upgrade-$STAMP.log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
run() {
if [ "$EXECUTE" = "1" ]; then
echo "+ $*"
"$@"
else
printf '[dry-run]'; printf ' %q' "$@"; printf '\n'
fi
}
as_root() {
if [ "$(id -u)" -eq 0 ]; then "$@"; else sudo "$@"; fi
}
echo "Mode: $([ "$EXECUTE" = "1" ] && echo EXECUTE || echo DRY-RUN)"
echo "Log: $LOG_FILE"
echo
echo "[System]"
lsb_release -a 2>/dev/null || cat /etc/os-release
uname -srmo
df -h / /var 2>/dev/null || df -h /
echo
echo "[APT locks]"
if fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1; then
echo "APT lock is busy. Wait for other package tasks to finish."
exit 1
fi
echo
echo "[APT update]"
run as_root apt-get update
echo
echo "[APT upgrade simulation]"
as_root apt-get -s upgrade || true
if [ "$FULL_UPGRADE" = "1" ]; then
echo
echo "[APT full-upgrade simulation]"
as_root apt-get -s full-upgrade || true
fi
if [ "$EXECUTE" = "1" ]; then
as_root apt-get upgrade -y
if [ "$FULL_UPGRADE" = "1" ]; then
as_root apt-get full-upgrade -y
fi
as_root apt-get autoremove --purge -y
as_root apt-get autoclean -y
else
echo "Dry-run only. Set EXECUTE=1 to upgrade. Set FULL_UPGRADE=1 only after reviewing removals."
fi
if command -v snap >/dev/null 2>&1; then
echo
echo "[Snap]"
snap refresh --list || true
run as_root snap refresh
fi
if command -v flatpak >/dev/null 2>&1; then
echo
echo "[Flatpak]"
flatpak remote-ls --updates 2>/dev/null || true
run flatpak update -y
fi
echo
echo "[Restart check]"
if [ -f /var/run/reboot-required ]; then
echo "Reboot is required. Packages:"
cat /var/run/reboot-required.pkgs 2>/dev/null || true
else
echo "No reboot-required marker found."
fi
echo
echo "Done. Review log: $LOG_FILE"
Ubuntu 脚本里我把 full-upgrade 做成显式开关,是因为它可能为了解决依赖而移除旧包。普通用户或生产机器先用 upgrade 更稳,确认清单后再决定是否进入完整升级。

图 7:apt 自带模拟、升级、清理和自动移除能力。不要一上来用手工删除解决包管理问题。
5.3 macOS 26:zsh + Software Update + Homebrew
保存为 upgrade-macos26-apps.zsh。先预演:
zsh upgrade-macos26-apps.zsh
确认后执行:
EXECUTE=1 zsh upgrade-macos26-apps.zsh
脚本如下:
#!/bin/zsh
set -euo pipefail
EXECUTE="${EXECUTE:-0}"
LOG_DIR="${LOG_DIR:-$HOME/upgrade-logs}"
STAMP="$(date +%Y%m%d-%H%M%S)"
LOG_FILE="$LOG_DIR/macos26-app-upgrade-$STAMP.log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
run() {
if [[ "$EXECUTE" == "1" ]]; then
echo "+ $*"
"$@"
else
printf '[dry-run]'; printf ' %q' "$@"; printf '\n'
fi
}
echo "Mode: $([[ "$EXECUTE" == "1" ]] && echo EXECUTE || echo DRY-RUN)"
echo "Log: $LOG_FILE"
echo
echo "[System]"
sw_vers
uname -srmo
echo
echo "[Apple Software Update]"
softwareupdate --list || true
if [[ "$EXECUTE" == "1" ]]; then
echo "Installing all listed Apple software updates. Save work before running this script."
sudo softwareupdate --install --all
else
echo "Dry-run only. Set EXECUTE=1 to install listed Apple updates. This script does not request a major OS installer."
fi
if command -v brew >/dev/null 2>&1; then
echo
echo "[Homebrew]"
brew --version
brew update
brew outdated || true
if [[ "$EXECUTE" == "1" ]]; then
brew upgrade
brew cleanup
else
echo "[dry-run] brew upgrade"
brew cleanup --dry-run || true
fi
else
echo
echo "[Homebrew] not installed; skip. This script will not install it automatically."
fi
echo
echo "[App Store]"
echo "For Mac App Store apps, open App Store > Updates, or keep automatic app updates enabled. This script does not install third-party App Store CLI tools."
echo
echo "[Restart check]"
softwareupdate --history | tail -20 || true
echo "If Software Update reports a restart is required, reboot manually after saving work."
echo "Done. Review log: $LOG_FILE"
macOS 这套脚本有两个保守点。第一,它不会安装 Homebrew;有就更新,没有就跳过。第二,它不会调用第三方 App Store 命令行工具,因为那会引入额外依赖和账号授权问题。App Store 应用仍建议使用系统界面或自动更新。

图 8:Homebrew 适合管理开发工具和 CLI 软件,但它不是 macOS 所有应用的总管。
六、Agent 自动配置:一句话可以,但要把验收写进去
如果你已经有 Codex、Claude Code、OpenClaw、HermesAgent 或其他本地可执行命令的 Agent,可以直接给它下面这段指令。注意:这段指令不是让 Agent 去调用云端升级服务,而是让它在当前机器上使用本机已有的系统工具和包管理器完成升级流程。
请在当前机器上辅助升级常用软件。先识别当前系统是 Windows 11、Ubuntu 26.04 还是 macOS 26,再选择对应脚本或等价命令执行。要求:
1. 先盘点系统版本、磁盘空间、当前可升级软件清单和包管理器来源。
2. 默认先 dry-run 或仅列清单,不要直接升级。
3. 不要执行大版本操作系统升级,不要下载未知第三方安装器,不要安装新的包管理器。
4. 不要输出真实内网地址、完整计算机名、私有域名、Token、密钥或账号信息。
5. 执行前说明将要升级的入口:WinGet / apt / snap / flatpak / Software Update / Homebrew 等。
6. 执行升级后给出升级前后对比、失败项、日志路径、是否需要重启,以及可以回滚或暂缓的建议。
这段 prompt 的关键不是“请帮我升级”这五个字,而是后面的验收标准。没有验收标准的 Agent,容易像一个很勤快但没经验的维修工:工具箱很全,但可能不知道哪些柜子不能打开。写清楚“不要做什么”和“最后交付什么”,比写清楚“请做什么”更重要。

图 9:macOS 上的更新入口并不只有命令行。App Store 应用仍然需要关注系统界面或自动更新设置。
七、人工方法 vs Agent 方法:我怎么选
如果只有一台自己的电脑,我建议先走人工自动执行。自己复制脚本、看 dry-run 输出、确认再执行,这样最容易理解每一步。特别是第一次在某台机器上跑升级脚本时,人工方式能帮助你建立“这台机器到底有哪些更新入口”的直觉。
如果你管理多台机器,或者经常需要帮家人、同事、实验室机器做维护,Agent 方法更省心。你可以让 Agent 先运行盘点命令,把报告整理成表格,再逐台执行升级。这样做的好处是,每台机器的日志格式一致,失败项也容易汇总。坏处是你必须给 Agent 更严格的边界,尤其是不要让它跨机器复制密钥、地址、机器名或私有仓库信息。
我的实际习惯是:第一次在新环境使用人工脚本;确认脚本和环境匹配后,再让 Agent 执行同一套脚本。Agent 做的是“重复、记录、汇总”,而不是替我拍脑袋决定是否进入风险更高的完整升级。
八、Q&A:几个容易误解的问题
Q1:是不是所有软件都能被一键升级?
不能。包管理器只能升级它认识的软件。手动下载的绿色软件、企业内部分发的软件、需要登录授权的私有应用、浏览器插件、IDE 插件、容器镜像、语言生态里的项目依赖,都可能需要单独处理。脚本的目标是覆盖 80% 常见系统级和工具级更新,不是冒充万能更新器。
Q2:为什么不让脚本自动安装 Chocolatey、Homebrew、mas 或其他工具?
因为本文强调“不依赖第三方升级助手”。如果你的机器已经有 Homebrew,脚本会使用它;如果没有,就跳过。自动安装新的包管理器会改变机器状态,也可能带来额外信任边界。这个动作应该由人单独确认。
Q3:为什么 Ubuntu 不默认 full-upgrade?
full-upgrade 在解决依赖时可能安装新包或移除旧包。它不是坏命令,但风险级别更高。日常常用软件更新先用 upgrade,遇到内核、桌面环境、驱动或复杂依赖时,再读模拟输出决定是否打开 FULL_UPGRADE=1。
Q4:Agent 能不能自动重启?
技术上可以,流程上不建议默认允许。重启会中断编辑器、远程会话、虚拟机、容器和正在运行的任务。更稳妥的做法是让 Agent 输出“需要重启”的证据,由你选择时间窗口。
Q5:升级失败后是不是应该立刻回滚?
不一定。很多失败只是某个安装器返回非零码、某个 GUI 应用正在运行、网络源临时不可达。先看日志,确认失败范围,再决定重试、跳过、固定版本或回滚。没有日志的回滚,很容易把小问题扩大成大问题。
九、最后的建议:升级自动化要像体检,不要像拆盲盒
真正可靠的软件升级自动化,不是把“下一步”按钮点得更快,而是把每一步变得可解释、可复查、可停止。升级前知道有什么,升级中知道动了什么,升级后知道是否成功,失败时知道从哪里查。
Windows 11、Ubuntu 26.04、macOS 26 的工具链不同,但思路完全一致。Agent 可以让这件事从“偶尔想起来才做”变成“有节奏地维护”。前提是你别把 Agent 当魔法按钮,而是当值班管理员:给它清单、边界和验收标准,它就能把枯燥但重要的工作做得很稳。
参考资料
- Microsoft Learn:WinGet
upgrade命令,https://learn.microsoft.com/en-us/windows/package-manager/winget/upgrade - Microsoft Learn:WinGet
export命令,https://learn.microsoft.com/en-us/windows/package-manager/winget/export - Ubuntu Manpages:Ubuntu 26.04 系列
apt-getmanpage,https://manpages.ubuntu.com/manpages/resolute/man8/apt-get.8.html - Ubuntu Manpages:
unattended-upgrademanpage,https://manpages.ubuntu.com/manpages/resolute/man8/unattended-upgrade.8.html - Homebrew Documentation:Manpage,https://docs.brew.sh/Manpage
- Apple Support:How to manually update apps from the App Store,https://support.apple.com/en-us/102629