
简介Windows全平台离线更新下载工具面向需要为多版本Windows与Office批量补齐补丁的运维人员、装机人员和离线环境维护者覆盖Windows XP至8.1、Server 2003至2012 R2以及Office 2003—2013的x86/x64版本常用于系统集成、批量装机与内网维护等场景。它的核心作用是智能判断系统已安装补丁按需下载缺失更新并支持制作ISO镜像方便在无网络或隔离环境中完成离线安装减少装完系统后长时间逐条打补丁的重复劳动。压缩包共636个文件约2.11MB以526个txt补丁清单与说明为主辅以xsl清单、cmd和vbs批处理脚本、少量exe工具及au3源码清单、下载、安装与镜像生成等环节由脚本串联目录与脚本说明相对完整便于按目标系统筛选和复用。已有2271人学习下载适合希望梳理补丁管理流程、为不同系统定制离线更新包并减少重复操作的技术人员参考。1. 一台没网的机器和它缺的 47 个补丁内网机房里最耗时间的环节往往出现在装完系统之后。一台刚做完镜像的 Windows Server点开 Update 检查更新进度条能转四十分钟最后弹一个超时——因为它根本连不出去。Windows 全平台离线更新下载工具要解决的就是这一段在一台能上网的机器上把 Windows 10/11、Windows Server 各版本各架构需要的 Update 包一次性拉全连同哈希清单一起打包拷进内网后按顺序离线安装。它适合做内网交付、批量装机、桌面运维的人也适合只想给自家那台老机器补一次更新、不想再盯着转圈的人。下面按我实际落地的顺序讲先弄懂包里装的是什么再跑通然后讲那些真装过才会遇到的坑。2. 补丁来源先摸清累积更新、服务堆栈与 .NET 的选型账离线更新最容易翻车的地方发生在下载之前没想清楚要下哪些包。我见过不止一个人把更新目录里所有带 KB 号的文件全拖下来结果 30GB 的仓库里一半是三个月前就被替代掉的旧包装的时候还得手动挑。先把补丁家族的分类搞明白工具里的勾选项才有意义。2.1 三类包撑起九成的离线更新Windows 的更新包本质上都是 CAB外层套了个 MSU 的壳双击走 wusa命令行走 DISM。按用途分真正需要关注的是下面这几类包类型文件名特征是否必装说明服务堆栈更新 SSUssu-*.msuKB 号通常较小老版本必装新版本多数已并入 LCU更新的是「安装器本身」装不上多半是它的锅月度累积更新 LCUwindows10.0-kb*.msu、windows11.0-kb*.msu必装含当月全部安全与非安全修复自带向前替代.NET 累积更新ndp*.msu、windows*-dotnet*.msu视业务跑 .NET 应用的机器必须补纯文件服务器可缓可选功能包 FODMicrosoft-Windows-*-FOD-Package按需离线装 .NET 3.5 时经常要用它当源驱动极少出现在月度包里按需服务器场景一般是厂商单独给别混进补丁目录优先级很清楚先 SSU、再 LCU、最后 .NET 和其他。这个顺序不能反反了 LCU 会直接以 0x800f0823 报「需要更新的服务堆栈」——工具下载得再全也没用。还有一点更新目录站点是按「产品 架构 分类」检索的不是按「哪台机器缺什么」检索的。所以用工具时的检索条件本质上是在描述你的目标机器群而不是描述某台具体机器。2.2 版本基线为什么只下最新一个就够又为什么不够累积更新里的「累积」两个字是实打实的。同一 build 下最新那个 LCU 已经包含了此前所有月份的修复所以一台缺了八个月补丁的机器不需要按月顺着装八次装最新一个就能一次性补齐。这是离线包体积能压下来的根本原因。但反直觉的地方在于「补丁」和「升版本」是两码事。Windows 10 21H2 到 22H2、Windows 11 22H2 到 23H2走的是启用包enablement package文件只有几百 KB装完内部版本号直接跳变。它不在月度 LCU 的替代链条里离线包里必须单独准备一份否则你在内网永远跨不过去。跨大版本的产品条目也得对上。下表是我一般会写在交付文档里的对照关系工具里配 target 时直接照抄目标系统内部版本月度包前缀额外注意Windows 10 22H219045windows10.0-kb*.msu与 Win11 包完全不通用Windows 11 23H222631windows11.0-kb*.msu启用包单独下Windows Server 201614393windows10.0-kb*.msu产品条目是 Server 2016别拿 Win10 1607 的包顶替Windows Server 201917763windows10.0-kb*.msuSSU 常需单独准备Windows Server 202220348windows10.0-kb*.msu与客户端 21H2 不同源Server 2016 和 Windows 10 1607 内部版本号一模一样都是 14393很多人以为包能互换。装上去会被产品校验拒掉——包本身是好的只是它认的产品名不对。这种错误不看日志根本猜不到后面排查章节还会提到。判断目标机器到底在哪个 build别靠「设置」里那个版本名命令行更靠谱# 取内部版本号与显示版本两个都要看 $os Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion [pscustomobject]{ Product $os.ProductName # Windows Server 2022 Datacenter Build $os.CurrentBuildNumber # 20348 UBR $os.UBR # 修订号能看出补到哪个月 Display $os.DisplayVersion # 21H2 }CurrentBuildNumber决定你要下哪个产品线的包UBR决定这台机器还差多少。UBR 是 0 或者远小于最新月度包的机器就是需要离线补的典型对象。这段脚本在 Server Core 上一样跑得通比翻系统信息界面快。2.3 仓库目录与命名约定下载下来的包怎么放决定了你三个月后还能不能维护。两千个 msu 堆在一个文件夹里靠文件名里的 KB 号肉眼找这是血泪经验里最容易避免的一种。我固定的分层是「产品 / 版本 / 架构」最底下一层才放文件再往上一层放清单和日志。建目录这一步用几行 PowerShell 就够了# 按 产品/版本/架构 三层建仓避免所有包平铺在一层 $root D:\UpdateRepo $targets ( { Product Win10; Version 22H2; Arch x64 }, { Product Win11; Version 23H2; Arch x64 }, { Product Server; Version 2016; Arch x64 }, { Product Server; Version 2019; Arch x64 }, { Product Server; Version 2022; Arch x64 } ) foreach ($t in $targets) { $path Join-Path $root ({0}\{1}\{2} -f $t.Product, $t.Version, $t.Arch) New-Item -ItemType Directory -Path $path -Force | Out-Null } # 顺手确认一下层级对不对 Get-ChildItem $root -Recurse -Directory | Select-Object -ExpandProperty FullName-Force让脚本可以重复执行已经存在的目录不报错这对每月跑一次的定时任务很重要。目录名全用 ASCII是为了绕开某些安装器对非 ASCII 路径的处理问题这不是玄学是确实踩过。命名上我的原则是不重命名原始文件。工具的下载结果保留windows10.0-kb五十万几-x64.msu这种原始名好处是你随时能拿这个名字回更新目录站对照说明。真要在前面加序号体现安装顺序就加在清单文件里别动文件名——改了名字出问题时你连搜索的抓手都没有。3. 跑通一遍从配置文件到内网机器上装完工具的界面部分各家做得不一样但离线下载这件事的骨架是一致的描述目标 → 拉包 → 校验 → 分发 → 按序安装。掌握这条链路换哪个工具都能上手。下面这套流程我用过很多次配置文件和脚本的形式按最常见的来写字段名以包内示例为准逻辑是通用的。3.1 配置项逐条拆解一次任务的核心是一个目标数组。每个元素描述「一台什么样的机器要补丁」而不是「哪一台机器」。这样同一个配置可以覆盖几十台同型号的机器。{ targets: [ { product: Windows 10, version: 22H2, arch: x64, lang: zh-cn }, { product: Windows 11, version: 23H2, arch: x64, lang: zh-cn }, { product: Windows Server, version: 2019, arch: x64, lang: zh-cn }, { product: Windows Server, version: 2022, arch: x64, lang: zh-cn } ], categories: [SecurityUpdates, CriticalUpdates, UpdateRollups], includePrerequisites: true, maxParallel: 4, retry: 3, outDir: D:\\UpdateRepo, verify: true }逐项说一下为什么这么填字段常用取值作用与取值理由targets[].productWindows 10 / Windows 11 / Windows Server决定走哪条产品线Server 与客户端不能混targets[].version22H2 / 23H2 / 2019 / 2022版本决定 build版本错包全错targets[].archx64 / arm64 / x86架构不符装的时候必报 0x80240017categoriesSecurityUpdates、CriticalUpdates、UpdateRollups取并集只勾一个必然漏包includePrerequisitestrue连带拉 SSU 和前置老系统必须开maxParallel18建议 4太高容易被源站限速太低跑一整夜retry35大文件断线重试别设 1verifytrue下完立刻比对哈希别等拷进内网才发现坏了categories那一行是最容易出问题的。很多人只勾SecurityUpdates觉得安全补丁就够了结果漏掉UpdateRollups里的 .NET 和组件更新装完一测业务系统还是起不来。3.2 下载、重试与断点配置有了接下来是一次批量调用。我不建议在界面上一个个点写成循环更容易复现也方便挂到计划任务里。# 读取配置逐 target 调用下载器单个 target 失败不阻断整批 $cfg Get-Content .\targets.json -Raw | ConvertFrom-Json $log () foreach ($t in $cfg.targets) { $out Join-Path $cfg.outDir ({0}\{1}\{2} -f ($t.product -replace , ), $t.version, $t.arch) New-Item -ItemType Directory -Path $out -Force | Out-Null # 参数名以包内 README 为准这里的写法是最常见的命名 .\UpdateDownloader.exe --product $t.product --version $t.version --arch $t.arch --lang $t.lang --category ($cfg.categories -join ,) --out $out --retry $cfg.retry --parallel $cfg.maxParallel $code $LASTEXITCODE $log [pscustomobject]{ Target $($t.product) $($t.version) $($t.arch) Code $code Status if ($code -eq 0) { OK } else { FAIL } Time (Get-Date).ToString(s) } if ($code -ne 0) { Write-Warning 下载失败$($t.product) $($t.version)退出码 $code } } $log | Export-Csv .\download-log.csv -NoTypeInformation -Encoding UTF8 $log | Format-Table -AutoSize关键在$LASTEXITCODE的判断和「不阻断」的设计。整批跑下来某个版本失败不应该让后面四个版本全部作废——先全部跑完再回头看哪些失败。日志落 CSV 而不是只打屏是因为你要拿它跟内网的安装记录做对账。--parallel不要贪。我一般设 4独享宽带时可以到 8。超过这个数源站大概率开始返回不完整响应表现出来就是文件下到一半断了重试几次都断在同一位置。3.3 哈希清单省这一步后面全是玄学包从公网到 U 盘到内网文件服务器中间经过的每一跳都可能让文件小几个字节。装的时候报「包损坏」你会怀疑人生。下载完立刻生成一次 SHA256 清单这件事花不了两分钟能省掉后面两小时# 为整个仓库生成清单相对路径 大小 SHA256 $repo D:\UpdateRepo $files Get-ChildItem $repo -Recurse -Include *.msu, *.cab $manifest foreach ($f in $files) { [pscustomobject]{ Path $f.FullName.Substring($repo.Length 1) # 存相对路径跨机器可用 SizeMB [math]::Round($f.Length / 1MB, 1) SHA256 (Get-FileHash $f.FullName -Algorithm SHA256).Hash } } $manifest | Export-Csv $repo\manifest.csv -NoTypeInformation -Encoding UTF8 $stat $manifest | Measure-Object SizeMB -Sum 共 {0} 个包合计 {1} GB -f $manifest.Count, [math]::Round($stat.Sum / 1024, 2)存相对路径是为了让清单跨机器可用源机器上是D:\UpdateRepo目标机器上可能是E:\Patches相对路径照样能对上。SizeMB保留一位小数就够它的作用是快速发现「明显不该这么小」的文件——一个累积更新只有 0.3MB基本可以确定是下载失败留下的残片。清单本身也要跟着包一起走。拷到内网后在目标侧再跑一次同样的脚本用Compare-Object对比两份清单不一致的就是传输过程中坏掉的。3.4 在目标机上按顺序安装装的时候按 SSU → LCU → .NET 的顺序来。桌面体验版可以用 wusaServer Core 上没有可用的 wusa 图形壳直接上 DISM。# 逐个装 msu 并解读退出码3010 表示需要重启不能忽略 $pkgDir E:\Patches\Server\2022\x64 $result () Get-ChildItem $pkgDir -Filter *.msu | Sort-Object Name | ForEach-Object { $p Start-Process wusa.exe -ArgumentList $($_.FullName) /quiet /norestart -Wait -PassThru $result [pscustomobject]{ Name $_.Name Hex (0x{0:X8} -f $p.ExitCode) # 负数的 ExitCode 也能正确显示成 0x80xxxxxx } } $result | Format-Table -AutoSize/quiet /norestart是必须的不加/norestart第一个需要重启的包会把机器重启掉后面的安装脚本全断在半路。Sort-Object Name保证顺序稳定——大部分 SSU 的 KB 号比 LCU 小按名字排基本能对上顺序对不上时改用你自己维护的顺序清单。退出码怎么读下面这几个覆盖了九成情况十六进制含义处理0x00000000安装成功继续下一个0x00003010成功需重启记下来全部装完统一重启别中途重启0x00240006该更新已安装正常跳过0x80240017包不适用版本、架构或前置补丁不满足回头查 target0x800F0823需要更新的服务堆栈先补 SSU再重试 LCU0x800F081F找不到源文件.NET 3.5 类安装常见需要挂载安装镜像做源Server Core 上换成 DISM命令结构不一样但语义一致# Server CoreDISM 直接吃 msu/NoRestart 同样要加 $pkg E:\Patches\Server\2022\x64\ssu-kb五零几万-x64.msu dism /Online /Add-Package /PackagePath:$pkg /NoRestart # 装完确认到底进去没有看 Package Identity 和 State dism /Online /Get-Packages /Format:Table | Select-String Install Pending|Installed/Add-Package后面必须带完整路径和引号路径里有空格时最容易在这翻车。装完用Get-Packages核对一遍 State比凭感觉相信「刚才好像没报错」靠谱得多。4. 排查五个下不全、装不上的现场下面五条是我自己踩过的按「现象 → 原因 → 解决」记遇到同类问题可以直接对号入座。4.1 下载阶段的现场现象一某个版本的包总是下到 60%80% 断开重试也是同一位置。原因是并发数开太高源站对同一 IP 的多连接做了限速或者直接掐断表现就是固定比例断流。解决是把maxParallel降到 2 甚至 1同时把retry提到 5让工具用单线程慢慢拉。大文件慢一点无所谓断一次重来才是真的费时间。现象二仓库里能搜到某个 KB 的文件但体积只有几百 KB装的时候报损坏。原因通常是下载过程中的临时文件被当成了成品磁盘写满、或者杀软在文件落盘瞬间把它隔离了剩下一个残缺的壳。解决分两步先确认磁盘余量够至少是包体积的三倍再给下载目录加进杀软白名单。上一节的哈希清单就是为这种情况准备的跑一次比对所有残片一眼就能挑出来。现象三清单里少了一整个产品线比如 Server 2019 一个包都没有。原因是categories只勾了SecurityUpdates而某些月份该产品的更新被归到了别的分类下或者工具的类别名和你填的对不上。解决是取并集把CriticalUpdates、UpdateRollups一起勾上跑完之后对照更新目录站的产品页面数一下当月该有几个包数量对不上就说明筛选条件有问题。4.2 安装阶段的现场现象四LCU 装到一半报 0x800F0823提示需要更新的服务堆栈。这个错误信息其实很明确机器上的服务堆栈太老新 LCU 的安装器版本不够装不进去。原因是你把 SSU 跳过了或者 SSU 本身也是旧的。解决是先单独装最新的 SSU重启后再装 LCU。老版本的 Server 2016 和部分 2019 版本尤其容易撞上新版本的月度累积更新一般已经把 SSU 合并进去了。现象五Server Core 上跑安装脚本第一步就报找不到 wusa.exe。原因是 Server Core 没有桌面体验组件wusa 这个壳不存在。解决是整条链路换成 DISM 的/Add-Package语义完全一样退出码也基本对得上。同一批脚本里最好把两个分支都写好按Get-ComputerInfo里的安装类型判断走哪条别等部署当天现场改。现象六补一个最容易忽略的所有包都返回 3010脚本一路跑完重启后系统还是旧的。原因是你把 3010 当成成功了但实际上多个包排着队等重启后面的包因为前面的还没生效而全部跳过。解决是把 3010 收集起来全部装完后统一重启一次重启后再跑一遍安装脚本——第二遍大部分包会返回 0x00240006已安装剩下没装上的才是真正需要处理的。5. 规模化的那一步多版本批量任务的时间与带宽预算单机补丁是手工活几十上百台就是工程活了。这一节讲的是把「能装」变成「能按时装完」的那些参数。5.1 并发、限速与出口带宽下载侧的瓶颈几乎永远在出口带宽不在磁盘。一条 100Mbps 的线路理论极限 12.5MB/s实际能稳定跑到 58MB/s 就不错了。五个产品线全量拉一遍体积轻松上到 815GB按 6MB/s 算就是半小时到四十分钟。我的做法是先跑一个小样本测速只下一个版本记录耗时算出实际吞吐再决定并发数。这个数比任何经验值都准。并发设 4 是因为多数源站对单 IP 的连接数有限制4 个连接基本能跑满带宽又不会触发限流。时间窗口也要算进去。如果下载安排在夜间就按「最慢情况」估单版本 20 分钟五个版本串行 100 分钟并发 4 的情况下大约 40 分钟。留一倍余量别卡着点跑。5.2 增量同步第二次只下新增的每月全量重下十几 GB 是最浪费的一步。累积更新的特点决定了大部分文件在下一个月份就作废了真正需要新增的只有零星几个。增量同步靠的是两份清单的差异。上一月的manifest.csv留一份副本新一月的生成后对比# 对比两期清单找出本次新增的包 $prev Import-Csv D:\UpdateRepo\manifest_prev.csv $curr Import-Csv D:\UpdateRepo\manifest.csv $added Compare-Object $prev $curr -Property Path -PassThru | Where-Object SideIndicator -eq 新增 {0} 个约 {1} GB -f $added.Count, [math]::Round((($added | Measure-Object SizeMB -Sum).Sum) / 1024, 2) $added | Select-Object Path, SizeMB, SHA256 | Format-Table -AutoSizeCompare-Object只比Path一个属性-PassThru让输出带上完整对象后面才能继续取SizeMB。差异结果里表示只在当期存在也就是新增的反向的是已废弃的可以直接从仓库里清掉省磁盘也省得以后误装。同步到内网文件服务器用robocopy/MIR会镜像整个目录树把源端删掉的文件同步删掉正好配合上面的废弃清理# /MIR 镜像同步/XO 跳过目标端更新的文件/R:2 失败重试两次 robocopy D:\UpdateRepo \\fileserver\patches\UpdateRepo /MIR /XO /R:2 /W:5 /NP /LOG:sync.log/XO在双向都有人写入的场景里能避免覆盖新文件/R:2防止某个被占用的文件让整批卡住。日志一定要留同步失败往往不是全挂而是某几个大文件被占用。5.3 磁盘与时间预算预算表比经验判断可靠。按我这边的实测一个产品线的年度仓库大致是这样一个量级规模仓库体积下载耗时6MB/s单机安装耗时备注单版本单架构1.53 GB510 分钟1530 分钟含一次重启五版本全量815 GB3045 分钟每台 2040 分钟建议并发 4十版本含 ARM641530 GB11.5 小时同上ARM64 包另算内网文件服务器预留 2 倍——留出上月包做回滚最后一行的「2 倍」是回滚余量。新一批包推到内网后上一批不要立刻删至少留一个周期。真碰上某个 LCU 和业务软件冲突把旧包重新装回去比重新下载快得多。安装侧的时间要按「单机串行」估算不要指望并行。同一台机器上同时跑两个 wusaCBS 会直接锁住第二个必然失败。真要提速只能多台机器并行一台机器一次一个包。6. 交付前的闭环验证三步确认这套包真的能装包下载完了、清单也齐了直接推到内网是很危险的。我现在固定走三步验证缺一步都不发货。第一步清单自校验。在源机器上重跑一次哈希脚本把结果和manifest.csv做Compare-Object有差异就说明包在下完之后被动过。第二步传输后复验。拷到内网文件服务器后在目标侧用相对路径再算一遍哈希两边对齐才算传输成功。这两步纯机械写成脚本挂在同步任务后面几乎不占时间。第三步是最容易被跳过的一步找一台刚做完镜像、干净得没有任何补丁的虚拟机把 SSU 和最新 LCU 走一遍完整流程。注意是「刚做完镜像」的机器不是随手找一台已经打满补丁的机器——后者会让所有包都返回「已安装」你什么都验证不出来。# 在验证机上确认补丁真的落地了而不是只看安装器返回 0 Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5 (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion).UBRGet-HotFix能看到最近装上的 KBUBR是修订号两个数对上了才算真装进去。只看退出码是有风险的我遇到过一次安装器返回 0 但包实际没落地原因是有个挂起的 CBS 事务卡在那里重启一次才恢复。从那以后我每次把离线包推进内网之前都会在验证机上强制走一遍这套三步流程宁可多花二十分钟也不要在客户现场对着 0x800F0823 翻日志。希望帮到你。本文还有配套的精品资源点击获取