简介:这份面向 Windows Server 2012 R2 Standard 运维与系统管理场景的 SxS 组件包,聚焦解决 .NET Framework 3.5 安装时因缺少源文件而失败的问题,适合需要在离线或受限环境中补齐系统组件、通过指定备用路径完成安装的管理员使用。压缩包共 1568 个文件,约 85.45MB,以 dll、resx、exe、aspx、config、sql、browser 等类型为主,涵盖程序集、资源、可执行组件、配置与数据库脚本等配套内容,目录组织保留系统组件源的结构特征,便于按需检索和定位。已有 1808 人学习下载,具备一定的使用参考基础。对于遇到 .NET 3.5 功能安装报错、需要补全 SxS 依赖的读者,可从中获得较完整的备用源文件集合,减少逐项查找缺失组件的成本,也便于核对组件版本与配置项,提升系统角色和功能部署时的排错效率。
1. 一台内网机器卡在 .NET Framework 3.5 上
上周有同事把一台 Windows Server 2012 R2 Standard 搬到内网,需要运行一套老 ERP,安装程序一启动就弹窗要求启用 .NET Framework 3.5。他先尝试在服务器管理器里勾选 .NET Framework 3.5 功能,结果卡在“正在搜索需要的文件”;接着用 DISM 安装,报 0x800F0906;最后去下载 dotnetfx35.exe,运行时提示需要联网下载。整条路走下来两小时,问题一直没解决。
问题的根源在.NET Framework 3.5的载荷本身:从 2012 R2 开始,这套运行时被归入“按需功能”(Features on Demand),系统盘里只留描述文件,真正的 cab 包位于安装介质的sources\sxs目录中。也就是说,跑在机器上的 2012 R2 与安装介质里的 sxs 文件必须匹配,这是能否安装成功的关键条件。
这篇笔记面向遇到 0x800F0906、0x800F081F 的系统管理员和运维人员,也面向需要在 Server Core 上离线安装 .NET 3.5 的同行。SXS 是什么、介质怎么挑选、DISM 参数怎么填写、实际会踩哪些错误,下面逐一说明。
2. SxS 与按需功能:2012 R2 为什么不能直接勾选
2.1 从 WinSxS 到 sources\sxs:载荷到底放在哪
很多人第一次接触 SxS 会把它和C:\Windows\WinSxS搞混。这两个东西名字像,职责完全不同。C:\Windows\WinSxS是组件存储,系统里每一个已安装的更新、每一个并行程序集都在里面有硬链接副本,它的作用是让不同版本的同名 DLL 能共存,回滚和修复也靠它。而安装介质上的sources\sxs是另一回事,它是一份纯载荷目录,里面放的是.cab和.mum文件,等待被 DISM 提取并注册进组件存储。
2012 R2 把 .NET Framework 3.5(包含 2.0 和 3.0 的运行时)划进了按需功能清单。这个设计从 Windows 8 / Server 2012 开始,逻辑是:新系统默认带 .NET 4.x,需要 3.5 的应用属于历史包袱,不该让所有机器都背上几百兆的载荷。于是系统只保留功能的“描述”,功能真正的二进制文件留在介质里。你在服务器管理器里勾选那个复选框时,系统会做三件事:先看本地组件存储里有没有现成载荷,没有就去 Windows Update 找,再没有就报错。
提示:企业内网机器通常既连不上 Windows Update,也没有配置 WSUS 的可选组件源,所以第三步必然报错。这就是 0x800F0906 最常见的来源,不是介质坏了,是系统根本无处下载。
2.2 sources\sxs 目录里有什么,怎么确认它是完整的
挂载一个原版 2012 R2 介质,打开sources\sxs,会看到几十个文件,命名规律大致是microsoft-windows-xxx-ondemand-package~31bf3856ad364e35~amd64~~6.3.9600.16384.cab,配一份同名.mum。31bf3856ad364e35是微软的公开密钥标记,amd64是架构,6.3.9600.16384是 2012 R2 RTM 的版本号。这三个字段后续判断介质是否匹配时会反复用到。
.NET 3.5 对应的关键文件是microsoft-windows-netfx3-ondemand-package.cab及其.mum。但只挑这一个文件复制出来是不够的,因为 NetFx3 功能在 CBS 里有一串依赖关系,DISM 在安装过程中会按需从同一个源目录里取其他包。我在实操里从不单独拷一个 cab,都是整个sxs目录一起带走,目录不大,几百兆,换来的是不用第二次找介质。
验证目录是否完整,最省事的办法不是数文件个数,而是看关键文件在不在、版本号对不对:
# 挂载后的盘符假设为 E: $sxs = "E:\sources\sxs" # 1. 关键包是否存在 Get-ChildItem $sxs -Filter "microsoft-windows-netfx3-ondemand-package*" | Select-Object Name, Length # 2. 看 .mum 里的版本号,确认是 6.3.9600(2012 R2)而不是 6.2.9200(2012) Get-Content "$sxs\microsoft-windows-netfx3-ondemand-package~31bf3856ad364e35~amd64~~6.3.9600.16384.mum" -TotalCount 5第一段列出 NetFx3 相关的 cab 和 mum,正常情况下至少能看到一对。第二段读.mum头部,6.3.9600是 2012 R2,6.2.9200是 2012,10.0.14393是 2016。版本号对不上,后面所有命令都白费,这一点比任何参数都重要。
2.3 Standard、Datacenter、Core 与语言:介质匹配的四条硬约束
2012 R2 的 SKU 差异不影响 sxs。Standard 和 Datacenter 用的是同一份install.wim里的不同索引,sources\sxs是共享的,Core 和带 GUI 的版本也一样共享。所以“我是 Standard,能不能用 Datacenter 的 sxs”这个问题,答案是能,只要版本号和架构对得上。
真正会卡人的是下面四条:
| 约束项 | 要求 | 不满足时的表现 |
|---|---|---|
| 主版本 | 6.3.9600 只能配 6.3.9600 | 0x800F081F,源文件找不到 |
| 架构 | x64 系统只能用 amd64 的 cab | 0x800F0906 或安装后功能不可用 |
| 语言 | 建议与系统显示语言一致 | 功能可装,但部分本地化资源缺失 |
| 介质完整性 | sxs 目录未被裁剪 | 报缺失某个依赖包,回滚 |
语言这一条要说明白:.NET 3.5 的核心载荷是语言中立的,语言包单独走lp.cab。混用英文介质给中文系统装,通常能装上,运行也不报错,但错误消息、部分资源字符串会回落成英文。生产环境里我一般要求和系统语言一致,避免以后排查时看到半中半英的异常信息,多一层干扰。
还有一个容易被忽略的点:2012 R2 的 sxs 不能拿去给 2016 用,反过来也不行。虽然 2016 里 NetFx3 同样是按需功能,也同样存在sources\sxs,但 CBS 的组件版本体系变了,强行指定会直接报源无效。
3. 用 DISM 指定 SxS 源:从挂载到装完的三步
3.1 挂载 ISO 并锁定盘符
带外管理口挂载的虚拟光驱、iLO/iDRAC 的虚拟介质、以及直接拷到本地的 ISO,处理方式略有差别。最稳的是把 ISO 拷到本地磁盘再挂载,避免虚拟介质在安装过程中断开。PowerShell 挂载:
$isoPath = "D:\iso\SERVER2012R2_X64.iso" # 挂载并取回对象,-PassThru 才能拿到后续属性 $img = Mount-DiskImage -ImagePath $isoPath -PassThru # 从卷信息里取盘符,避免手写 E: 结果撞上已有的数据盘 $drive = ($img | Get-Volume).DriveLetter $sxs = "${drive}:\sources\sxs" # 挂载后立刻验证,别等到 DISM 报错再回头查 if (Test-Path "$sxs\microsoft-windows-netfx3-ondemand-package.cab") { "源目录就绪: $sxs" } else { "源目录异常,检查 ISO 是否为原版介质" }Mount-DiskImage返回的是磁盘映像对象,必须加-PassThru才有返回值,否则拿到的是空。Get-Volume依赖存储模块,在 2012 R2 的完整安装和 Core 安装里都在。最后那个Test-Path不是可有可无的礼貌检查,我遇到过用第三方工具重新打包的 ISO,sources目录被压成了sources.cab或者干脆删掉了 sxs,直接进 DISM 只会得到一句含糊的“找不到源文件”。
如果环境里 PowerShell 受限,或者你在一个远程命令行会话里,diskpart 同样能挂 ISO:
diskpart select vdisk file="D:\iso\SERVER2012R2_X64.iso" attach vdisk readonly list volume exitreadonly一定要加,防止误写介质。list volume输出的盘符就是后面要填的源路径。用完记得detach vdisk,不然盘符会一直被占用,批量脚本里尤其容易踩。
3.2 DISM /Enable-Feature 的五个参数逐个拆
源目录准备好后,核心命令只有一行:
DISM /Online /Enable-Feature /FeatureName:NetFx3 /All ^ /Source:E:\sources\sxs /LimitAccess /NoRestart(命令提示符里的续行符是^,PowerShell 里换成反引号`,或者干脆写成一行。)
参数含义如下:
/Online:目标是当前正在运行的系统。如果写成/Image:C:\mnt,就变成离线修改挂载的 WIM,两者的日志位置、失败表现都不一样,不要混。/FeatureName:NetFx3:这是 DISM 侧的功能名。注意它和 PowerShell 侧不一样,Install-WindowsFeature用的是Net-Framework-Core。把 PowerShell 的名字填进 DISM,会得到“功能名称未知”。/All:连带启用所有父功能。NetFx3 在组件树里不是叶子节点,缺了/All,某些依赖的激活组件不会被拉起,装完看着是启用了,程序运行起来仍然报错。/Source:E:\sources\sxs:指向 sxs 目录本身,不是sources,也不是 ISO 根目录。这是最高频的低级错误。/LimitAccess:阻止 DISM 回连 Windows Update 或 WSUS。内网环境必须加,加了之后如果本地源不满足,它会立刻失败并给出明确错误码,而不是卡在那里等超时。
/NoRestart的作用是把重启决策权交回来,脚本里用它来判断返回码 3010。手动操作时加不加都行,加了之后记得自己重启。
执行前先看一眼当前状态,能省掉很多“是不是本来就已经装好了”的争论:
DISM /Online /Get-FeatureInfo /FeatureName:NetFx3输出里State : Disabled表示载荷缺失、需要源;State : Enabled表示已经装好,此时再执行 Enable 会返回 0 并提示无需操作。这个命令还有个用处:安装失败后重复执行,能看到State是否停在Enable Pending,那说明卡在了提交阶段。
3.3 组策略与 WSUS 环境下的本地源改法
有些域环境里,DISM 加了/LimitAccess依然报 0x800F0906,或者报 0x800F0907,这时候要怀疑组策略。2012 R2 有一条策略专门管可选组件的载荷来源:计算机配置 → 管理模板 → 系统 → 指定可选组件安装和组件修复的设置。这条策略一旦被域控推下来,并且指向了一台已经下线的 WSUS 或者一台不提供可选组件内容的服务器,DISM 的行为就被劫持了,你在命令行里写的/Source不一定能覆盖它。
处理思路有两种。临时排障时,把机器从该 GPO 的作用范围里摘出来,gpupdate /force后重试,确认问题是不是策略引起的。长期方案是把策略改成指向内部的 sxs 共享:
# 先看当前生效的策略值,确认是不是被域控覆盖了 $path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing" if (Test-Path $path) { Get-ItemProperty $path | Format-List } else { "本地无该策略项,问题不在组策略" }这条路径下的具体值名会随策略版本变化,我不建议手写注册表去“造”策略,正确做法是在一台样机上用gpedit.msc配好,然后reg export出来对比。策略里那个“从不尝试从 Windows Update 下载载荷”的勾必须打上,否则即使指定了本地源,系统仍然会先尝试联网,超时之后才回落,批量部署时这一等就是半小时。
把 sxs 放到共享上还有一个权限细节:共享必须让目标机器的计算机账户可读,不是你的登录账户可读。用Install-WindowsFeature -Source \\fileserver\sxs时,访问身份取决于执行会话,容易出“我能打开、它装不上”的诡异现象。稳妥做法是共享给Domain Computers读权限,或者本地拷贝后再装。
3.4 装完之后的三处证据
命令返回 0 不等于装好了,尤其是老应用对 .NET 版本特别敏感的时候。我一般查三处:
# 证据一:功能状态 DISM /Online /Get-FeatureInfo /FeatureName:NetFx3 | Select-String "State" # 证据二:注册表安装标记,Install=1 表示 .NET 3.5 已注册 (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5").Install # 证据三:编译器是否落盘,能被调用才算真装上 Test-Path "$env:windir\Microsoft.NET\Framework64\v3.5\csc.exe"第一处看State : Enabled。第二处看返回值是不是 1,注意 64 位系统上这个键只有一份,不需要找 Wow6432Node。第三处是最实在的,csc.exe存在说明运行时目录结构完整,有些“装上了但目录是空的”情况就是这么被发现的。
如果三处都正常,程序还报缺 3.5,去翻C:\Windows\Logs\CBS\CBS.log,搜NetFx3,看最后一次安装的结尾是Success还是Rollback。日志很长,Select-String -Pattern "NetFx3" -Context 2,2能快速定位。
4. Server Core 与离线 WIM:两条不同的落地路径
4.1 Server Core 上等价的 PowerShell 写法
Server Core 没有服务器管理器 GUI,全靠命令行。PowerShell 的Install-WindowsFeature是 DISM 的一层封装,参数更符合直觉:
# Server Core 上启用 .NET 3.5,源指向挂载后的 sxs Install-WindowsFeature -Name Net-Framework-Core ` -Source "E:\sources\sxs" ` -Restart:$false -Verbose-Name用的是功能名Net-Framework-Core,不是 DISM 的NetFx3,这是两个体系。-Source只接受目录,指向sources会失败。-Verbose在 Core 上很值得加,因为默认输出只有一行 Success,失败时什么信息都不给;加上之后能看到 CBS 正在处理哪个包,卡住时立刻知道停在哪。
-Restart:$false配合后续的Restart-Computer使用,让脚本能先记录结果再重启。如果返回Success但Restart Needed : Yes,说明载荷已就位,只差提交。
查询功能清单在 Core 上同样可用:
Get-WindowsFeature -Name Net-Framework-Core | Format-List Name, InstallStateInstallState有三个值:Installed、Available、Removed。看到Available说明载荷不在本地,需要-Source;看到Removed说明功能被显式移除过,通常要先用/Remove的反向操作清状态才能重装,这种情况在被人手工折腾过的机器上很常见。
4.2 离线注进 install.wim:批量装机前把功能装好
如果要批量部署几十台机器,逐台挂 ISO 装功能太慢,正确做法是改镜像。把install.wim挂起来,注入 NetFx3,再提交回去,装出来的系统天生就带 3.5。
第一步是确认索引,2012 R2 的install.wim里通常有四个索引,对应 Standard/Datacenter 各自的 Core 与 GUI 版本,但不同来源的介质编号顺序不一定一样,不要背,直接查:
Dism /Get-WimInfo /WimFile:D:\images\install.wim输出会给每个索引的 Name 和 Description,认准带Server with a GUI字样的那个。接下来的四步是固定的:挂载、查状态、启用、提交。
:: 1. 挂载到空目录(目录必须存在且为空) Dism /Mount-Image /ImageFile:D:\images\install.wim /Index:2 /MountDir:C:\mnt :: 2. 确认镜像内该功能当前状态 Dism /Image:C:\mnt /Get-FeatureInfo /FeatureName:NetFx3 :: 3. 注入,注意这里用 /Image 而不是 /Online Dism /Image:C:\mnt /Enable-Feature /FeatureName:NetFx3 /All ^ /Source:E:\sources\sxs /LimitAccess :: 4. 提交并卸载,/Commit 不能漏 Dism /Unmount-Image /MountDir:C:\mnt /Commit几个关键点。/MountDir指向的目录必须是空目录,里面有任何文件都会导致挂载失败。第 3 步的/Source指向的还是介质上的 sxs,不是镜像里的路径。第 4 步的/Commit如果写成/Discard,前面所有工作会瞬间消失,而且不会有任何提示,这个错误我犯过一次,排查了二十分钟才发现是复制命令时少改了三个字母。
离线注入还有个版本约束:用来执行 DISM 的宿主机系统版本不能低于被修改的镜像版本。用 Windows 7 去挂 2012 R2 的 wim,会在挂载阶段就报错。要么用一台 2012 R2 及以上的机器,要么装对应版本的 ADK 用它的 DISM。
4.3 批量脚本与返回码判读
批量场景下,脚本要能自己判断成败,而不是靠人盯屏幕。DISM 的返回码是有明确语义的,值得在脚本里显式处理:
$source = "E:\sources\sxs" $log = "C:\temp\netfx3_install.log" $p = Start-Process -FilePath "dism.exe" ` -ArgumentList "/Online /Enable-Feature /FeatureName:NetFx3 /All /Source:$source /LimitAccess /NoRestart" ` -Wait -PassThru -NoNewWindow switch ($p.ExitCode) { 0 { "安装成功,无需重启" } 3010 { "安装成功,需要重启后生效" } 2 { "源路径不可访问,检查盘符与 sxs 目录" } 50 { "功能名或参数不被支持,确认系统是 2012 R2" } 740 { "权限不足,需以管理员身份运行" } default { $hex = "0x{0:X8}" -f $p.ExitCode "失败,返回码 $hex,查 C:\Windows\Logs\DISM\dism.log" } }Start-Process -Wait -PassThru才能拿到退出码,直接调用dism.exe拿不到。3010 是最常见的“成功但需要重启”,很多脚本把它当失败处理,结果反复重试。负数退出码(在 PowerShell 里表现为很大的正数)通常对应0x800Fxxxx系列,转成十六进制再去搜,比直接搜十进制有效得多。
日志路径要记牢:DISM 自己的日志在C:\Windows\Logs\DISM\dism.log,CBS 的详细过程在C:\Windows\Logs\CBS\CBS.log。前者记录命令层面的错误,后者记录组件层面的依赖缺失,两个要对照着看。
5. 避坑:五类失败现场与它们的成因
5.1 现象:报 0x800F0906,但源路径明明存在
现象:/Source:E:\sources\sxs写得很清楚,Test-Path也返回 True,DISM 仍然报“无法下载源文件”,错误码 0x800F0906。
原因:这个错误码的语义是“找不到可用的源”,不是“路径不存在”。最常见的是/LimitAccess漏了,DISM 在本地源里没匹配到需要的包之后,转去联系 Windows Update,内网不通,超时后返回 0906。第二种是路径写到了sources而不是sources\sxs,sources目录下确实没有 NetFx3 的 cab,DISM 认为源无效。
解决:先把/LimitAccess补上,命令写成一行完整执行;再用Get-ChildItem E:\sources\sxs -Filter "microsoft-windows-netfx3*"确认包在目录里;最后翻dism.log,搜0x800f0906前后的CBS行,里面会明确写出它在找哪个包、在哪个路径找的。
5.2 现象:0x800F081F,源文件找不到
现象:命令、权限、路径都检查过,报 0x800F081F,提示“找不到源文件”。
原因:这个码更多指向载荷内容不匹配,而不是路径。我遇到过三种:一是拿 2012(6.2.9200)的介质给 2012 R2 装,install.wim索引看着一样,cab 的版本号差一个数;二是介质被第三方精简过,sxs 目录里只留了几个常用包,NetFx3 的依赖包被删了;三是用 x86 的介质去装 x64 的系统。
解决:用 2.2 里那段命令读.mum的版本号,确认是 6.3.9600;用Get-ChildItem E:\sources\sxs | Measure-Object粗略看文件数量,原版介质是几十个,只有十几个基本可以判定被裁剪;最后换一份未经改动的原版介质重试,这一步别犹豫,比在旧介质上反复试参数快得多。
5.3 现象:装到一半失败回滚,重试还是失败
现象:进度条走到 60% 多,然后报错回滚,再执行一次还是同一个位置断。
原因:系统里有挂起的组件操作。之前有更新装在“已安装待重启”状态,或者 CBS 目录下存在pending.xml,此时新功能无法提交。这类问题跟 sxs 一点关系没有,换十份介质也一样。
解决:先重启,让挂起的操作落地;重启后执行DISM /Online /Cleanup-Image /ScanHealth看组件存储有没有损坏;确认C:\Windows\WinSxS\pending.xml是否存在,存在说明还有未完成的事务,不要手工删,走DISM /Online /Cleanup-Image /StartComponentCleanup让它自己收尾;以上都做完再重试启用功能。
5.4 现象:功能显示已启用,老程序照样报错
现象:Get-FeatureInfo显示State : Enabled,注册表 Install=1,程序启动还是弹“需要 .NET Framework 3.5”。
原因:多数情况是程序本身的问题,不是环境。比如应用配置里写死了supportedRuntime version="v2.0.50727",而机器上实际运行在 4.x 的 CLR 上,需要配useLegacyV2RuntimeActivationPolicy;或者是 32 位程序去调 64 位的混合模式程序集,加载失败被包装成了“缺少框架”的提示。
解决:先看应用程序日志里那条真正的异常,别信弹窗文案;用Framework64\v3.5\csc.exe编译一个最小控制台程序验证运行时本身可用;确认无误后,问题交回给应用侧,检查app.config和位数匹配,不要继续在系统层面折腾。
5.5 现象:用了“离线安装包”,越装越乱
现象:网上找的绿色版 / 一体化离线包,装完当时能用,之后 Windows Update 反复提示同一个 .NET 3.5 更新,装了失败、失败了再提示。
原因:这类包本质上是把别人的 sxs 重新打包,注册进 CBS 时组件版本和本机不匹配,更新程序识别到组件状态异常但修不好,形成循环。
解决:用DISM /Online /Get-FeatureInfo /FeatureName:NetFx3确认状态,再用/Disable-Feature /Remove把功能连同载荷一起清掉,重启后回到用原版介质 + sxs 的正路。生产机器上我一律只用官方介质里的 sxs,来源不明的 cab 省下的那十分钟,后面要用几小时去还。
6. 把 sxs 固化成内部源:一次准备,长期可用
单台机器装完就结束,那是运气好。真正省事的是把 sxs 变成内部基础设施:找一台文件服务器,建一个只读共享,把原版介质的sources\sxs整个目录拷进去,再按版本分文件夹——\\fileserver\sxs\2012R2\6.3.9600\x64\。这样做的好处是,以后不管谁接手装 .NET 3.5,都不需要再去翻 ISO,也不会出现“拷了半个目录”的事故。
共享权限按 3.3 里说的配,给Domain Computers读权限。然后写一个开机就能跑的自检脚本,把“源是否可用”这件事前置:
$src = "\\fileserver\sxs\2012R2\6.3.9600\x64" # 逐项验证,任何一项失败都直接退出,不留半吊子状态 $checks = @{ "NetFx3 cab" = "$src\microsoft-windows-netfx3-ondemand-package.cab" "NetFx3 mum" = "$src\microsoft-windows-netfx3-ondemand-package~31bf3856ad364e35~amd64~~6.3.9600.16384.mum" } foreach ($k in $checks.Keys) { if (-not (Test-Path $checks[$k])) { Write-Error "源不完整,缺少: $k"; exit 1 } } # 源可用再装,装完立刻验证注册表标记 Install-WindowsFeature -Name Net-Framework-Core -Source $src -Restart:$false (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5").Install脚本里那个哈希表不是我故弄玄虚,是因为曾经有一次共享被人“整理”过,目录结构还在,关键 cab 被挪进了子文件夹,Test-Path查目录通过了,DISM 到一半才失败。从那以后我每次做共享源,都强制走一遍逐文件校验,宁可多写十行,也不要在几十台机器上同时排查同一个问题。
还有一个容易忽略的收尾动作:sxs 装出来的 .NET 3.5 是 RTM 水平的载荷,补丁不会跟着来。功能装好之后,还要让 Windows Update 把 3.5 对应的安全更新补齐,否则版本停留在初始状态,过不了基线检查。离线环境就在共享目录旁边再放一个补丁目录,装完功能顺手把补丁打上,一步到位。
共享源、版本目录、逐文件校验、补丁目录,这四样凑齐,2012 R2 上的 NetFx3 就从一个每次都要现场查资料的麻烦事,变成了一条十分钟内能走完的固定流程。希望帮到你。
本文还有配套的精品资源,点击获取