
简介面向在 Windows Server 2012 R2 Standard 上部署 .NET Framework 3.5 的运维与系统管理人员这份 SxS 组件包主要应对在线安装失败、需要指定备用源路径的场景。压缩包为 rar 格式共 1568 个文件约 85.45MB以 dll、resx、exe、aspx、config、sql 等类型为主兼有 ascx 控件、browser、ini、manifest、xml 等配置与描述文件覆盖运行库、资源文件、管理页面、数据库脚本及部署清单等模块目录层级较完整便于按组件查找和比对。现有 1808 人学习下载适合需要离线补齐系统组件、排查安装源缺失问题的读者参考。其价值在于提供一套可直接作为备用路径使用的 SxS 文件集合帮助定位缺失组件、核对版本与依赖关系并为遗留应用环境中的 .NET 3.5 安装排错提供对照材料。对于缺少安装介质、依赖组件版本不一致或系统组件缓存不完整的服务器可作为离线补装与问题复现的参考素材同时也能帮助读者理解 SxS 目录的组成方式、组件命名规律及依赖组织关系便于在同类系统中快速筛选所需文件。1. 一台 2012 R2 Standard 卡在 .NET 3.5sxs 文件不是拿来覆盖的某实验室一台跑老版打印组件的 Windows Server 2012 R2 Standard装到一半弹窗要 .NET Framework 3.5。管理员图省事把安装介质里的sources\sxs整个文件夹拷进C:\Windows\WinSxS覆盖同名目录重启后系统直接进不去——组件存储被写坏连sfc都救不回来。这类故障每年总要见几回大家把 sxs 当成补丁包它其实是安装源。Windows Server 2012 R2 Standard 上的 .NET Framework 3.5 属于按需安装的功能payload 就在介质里系统默认却去外网找。离线环境下不指定源命令必然失败。这篇记录我反复走过的那套流程版本怎么对、目录怎么挂、DISM 与Install-WindowsFeature两条命令怎么选、报0x800f0906时先看哪里。适合手上只有一台 2012 R2、又不敢乱动系统目录的人。2. 环境先列清2012 R2 Standard 装载 .NET 3.5 的三条源同一句Install-WindowsFeature NET-Framework-Core在能上网的测试机上跑得很快在内网生产机上可能挂半小时再回滚。差别不在命令在源。装之前把「这台机器能拿到哪些源」列清楚比事后翻 CBS 日志省事得多。2.1 Windows Update、sxs 目录、WIM 索引三条路各自适合谁2012 R2 的功能清单里.NET Framework 3.5 Features分成两层外层NET-Framework-Features是个集合里面NET-Framework-Core才是 2.0/3.0/3.5 的运行时本体另外还有 WCF 激活、HTTP 激活这些子项。真正需要安装源的是NET-Framework-Core其余子项多在本地就能装完。很多人看到功能列表里一片「可用」就整片勾选结果卡在第一个需要源的功能上。三条源路子的取舍很明确来源方式是否需要外网典型耗时适用场景常见失败码默认走 Windows Update需要530 分钟临时测试机、能直连外网0x800f0906、0x800f0907、0x800f0954-Source指向sources\sxs不需要28 分钟内网生产机、批量部署0x800f081f、0x800f0922-Source指向WIM:...install.wim:N不需要310 分钟手上只有整份 ISO、没单独解出 sxs0x800f081f索引取错第一条路看着最省事实际最不稳定。企业内网里 2012 R2 一般被 WSUS 或组策略接管「指定可选组件安装和组件修复的设置」一旦配成「从不尝试从 Windows Update 下载 payload」命令会立刻返回0x800f0907跟断网没区别。所以我一般直接跳到第二条手上有介质就别让系统去外网碰运气。第二条路的前提是sources\sxs目录完整。Windows 8.1 / Server 2012 R2 这一代的介质里sources\sxs是一批.cab文件install.wim里同时也留着对应的组件副本所以第三条路是把 wim 当源直接读省掉解压这一步。代价是必须算准映像索引取错了就是另一台机器的 payload报错和版本不匹配长得一模一样。提示Server Core 与带 GUI 的 2012 R2 在功能清单上略有差异但NET-Framework-Core的装载方式完全一致命令不用改。2.2 版本与架构对齐2012 的 sxs 不能喂给 2012 R2这是最容易翻车的一环。Windows Server 2012 的内核版本是 6.2.92002012 R2 是 6.3.9600。两份介质里的sources\sxs文件名高度相似肉眼几乎分不出来混用之后 DISM 会在 60% 左右停下报0x800f081f——「找不到源文件」而文件明明就在那儿。真正的原因是 payload 的组件版本号与目标系统不匹配系统拒绝接收。装之前先在两台机器上各跑一遍版本核对把输出对齐# 目标机确认内核版本2012 R2 应当是 6.3.9600 [System.Environment]::OSVersion.Version # 目标机确认产品名与架构别在 x64 上找 x86 的包 Get-CimInstance Win32_OperatingSystem | Select-Object Caption, Version, OSArchitecture# 源介质直接看 install.wim 里有哪些映像索引从 1 开始 Get-WindowsImage -ImagePath D:\sources\install.wim | Select-Object ImageIndex, ImageName, Version第一段命令输出6.3.9600才说明是 R2如果是6.2.9200手上这台根本不是 R2介质自然对不上。第二段的ImageIndex就是后面-Source WIM:...:N里那个 N别凭记忆写。2012 R2 的安装介质通常把 Standard 与 Datacenter 的完整版、核心版打包在同一个 wim 里索引顺序各家封装不同看输出最稳。架构这块2012 R2 只有 x64 介质一般不会错真正会错的是「顺手拿了同一台机器上 Windows 8.1 的 ISO」。8.1 与 2012 R2 同为 9600build 号对得上但客户端与服务器版本的功能清单不同混用后可能出现装完功能状态显示已安装、实际运行库缺件的情况。我一般只用同产品、同版本、同语言的介质这条不做妥协。语言方面2012 R2 的 sxs payload 在多数场景下不区分显示语言但遇到装了语言包的机器优先选与系统显示语言一致的那份能少一类玄学问题。2.3 装前检查磁盘、待重启、组件存储健康度三步检查两分钟能挡掉一半的返工。第一是空间组件存储要为 3.5 的 payload 落地腾位置系统盘只剩几百兆的时候DISM 会在收尾阶段回滚报0x800f0922日志里看不出跟空间有关。第二是待重启标志之前装过补丁没重启功能安装会卡在 100% 再退回去。第三是组件存储本身有没有坏坏着的时候装什么都白搭。# 1) 系统盘剩余空间低于 2GB 先去清理 Get-PSDrive C | Select-Object Used, Free # 2) 是否存在挂起的重启操作 $pfro (Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager -Name PendingFileRenameOperations -ErrorAction SilentlyContinue).PendingFileRenameOperations $cbs Test-Path HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending PendingFileRename: $([bool]$pfro) CBSRebootPending: $cbs # 3) 组件存储健康度可以扫但要等几分钟 DISM /Online /Cleanup-Image /ScanHealth三项全绿再动手。PendingFileRename或CBSRebootPending任一为 True先重启再回来不要在挂起状态硬装。ScanHealth报告「组件存储可修复」时先跑/RestoreHealth修完再装 3.5否则装到一半会因为同一个损坏点失败而报错信息指向的是 .NET很容易带偏方向。注意/ScanHealth在低配虚拟机上可能跑十几分钟别以为卡死了就 CtrlC中断扫描本身不影响系统但会白等一次。3. 拿到 sxs 文件之后目录校验与投递方式文件到手不等于能用。我见过最离谱的一次是有人把sources\sxs里的 cab 只拷了前几个因为「看着差不多够了」结果装到 40% 失败报的错跟版本不匹配完全一样。目录校验这一步花不了几分钟能省掉一轮怀疑人生。3.1 校验来源目录结构、文件数量、介质哈希先确认目录层级对。正确的位置是介质根下的sources\sxs也就是D:\sources\sxs把sxs整个文件夹再套一层放到D:\sxs\sources\sxs也能用只要-Source指到有 cab 的那一层。指到D:\sources会报找不到源因为系统要的是 sxs 本身。# 目录里应该是一批 cab 文件数量以实际介质为准 Get-ChildItem D:\sources\sxs -File | Measure-Object -Property Length -Sum | Select-Object Count, {nTotalGB;e{[math]::Round($_.Sum/1GB,2)}} # 抽查前几个文件确认不是 0 字节的占位文件 Get-ChildItem D:\sources\sxs -Filter *.cab | Select-Object -First 5 Name, Length, LastWriteTime如果Count只有个位数、TotalGB不到 0.1基本可以判定是残缺包。正常的 2012 R2 介质里这一层承载 3.5 全部 payload体积远不止几十兆。Length出现 0 的文件通常是拷贝中断留下的重拷一次即可。有条件的话对整份 ISO 做一次哈希比对比逐个文件看大小可靠# 只在你手上有官方公布的哈希值时才有意义别拿网上随手贴的值当真 Get-FileHash D:\iso\SERVER_2012R2_STD.iso -Algorithm SHA256介质来源不明时我个人更倾向于整份重新获取而不是从别处单独补几个 cab 进来。sxs 里的文件之间有版本约束混搭出来的目录可能装得上、跑起来却出问题。3.2 三种投递方式挂 ISO、解压到本地盘、走共享挂 ISO 最干净用完就卸不留垃圾。2012 R2 自带的Mount-DiskImage足够用不必装额外工具。# 挂载介质注意返回的盘符不一定是 D脚本里别写死 $iso D:\iso\SERVER_2012R2_STD.iso Mount-DiskImage -ImagePath $iso $drive (Get-DiskImage -ImagePath $iso | Get-Volume).DriveLetter 挂载到: ${drive}: Test-Path ${drive}:\sources\sxsMount-DiskImage对已挂载的镜像重复执行不会报错盘符也保持不变这点比手工右键挂载稳。Get-Volume取盘符这一步不能省——同一台机器上先后挂过多个镜像时盘符会往后顺延写死D:的脚本换台机器就崩。用完Dismount-DiskImage -ImagePath $iso卸掉尤其在做快照或备份之前。解压到本地盘的写法适合「一台机器装完还要装另外几台」的场景把sources\sxs整个目录复制到D:\sxs\之后所有机器指向同一个本地路径速度快、不受光驱盘符影响。复制时用Copy-Item -Recurse别用资源管理器拖拽中断后再续传容易留下半截文件。走共享目录是虚拟化环境里最常见的选择把 sxs 放在文件服务器上多台机器指同一个 UNC 路径。这里有个前提DISM 以系统身份读取源共享目录的 NTFS 权限必须给到目标机器的计算机账户域环境或Everyone读取工作组只给管理员账号是不够的。我一般先在目标机上用Test-Path \\fileserver\sxs\*.cab验一下能不能列目录能列再往下走。3.3 路径与权限上最容易忽略的两点路径别带空格和中文。-Source D:\安装源\sxs这种写法在 PowerShell 里语法没错但底层 DISM 对非 ASCII 路径的处理并不总是可靠报错还很难懂。我习惯统一放到D:\sxs、D:\src\sxs这种纯英文短路径下。第二个是别把 sxs 放到C:\Windows下面。有人图方便解压到C:\Windows\Temp\sxs装完忘了删之后系统清理任务或安全软件扫描把这个目录当异常文件处理隔一阵子就出一次莫名其妙的失败。源目录用完即删或者干脆放数据盘。4. 两条命令走通安装DISM 与 Install-WindowsFeature到这一步环境已经干净了剩下就是选命令。DISM 和 PowerShell 两条路最终调的是同一套组件服务区别在参数表达和可编排性临时救火用 DISM 敲一行更快要写进部署脚本就用 PowerShell返回值好判断。4.1 DISM/LimitAccess 这个参数为什么不能省:: 从本地 sxs 目录启用 .NET 3.5禁止回落到 Windows Update dism /online /enable-feature /featurename:NetFx3 /all /limitaccess /source:D:\sources\sxs逐段拆/online指当前运行的系统离线改 wim 时换成/image:/featurename:NetFx3是 DISM 侧的功能名跟 PowerShell 的NET-Framework-Core是同一个东西的两个叫法写错会直接报功能不存在/all表示把父级功能一并启用少了它可能出现 3.5 装上了但父功能仍显示未安装的状态/limitaccess是关键它阻止 DISM 在本地源缺件时悄悄去外网拉没有这个参数内网机器会先卡在连接超时上几分钟再返回一个看起来像源文件损坏的错误/source:指向含 cab 的那一层目录。命令执行完看最后一行。出现「操作成功完成」只代表命令没抛错不代表功能真的启用还要看有没有RestartNeeded。DISM 返回The operation completed successfully但提示需要重启时别急着继续装别的组件先重启把挂起操作落盘。同一个命令也可以用来装多台在每台机器上执行前先用/get-featureinfo确认当前状态避免重复操作。:: 看当前状态State 显示 Enabled 就不用再装了 dism /online /get-featureinfo /featurename:NetFx34.2 Install-WindowsFeature普通源与 WIM 源两种写法普通源写法最直观# 从解压好的 sxs 目录安装返回对象里能看到 Success 和 RestartNeeded $r Install-WindowsFeature -Name NET-Framework-Core -Source D:\sources\sxs -Restart:$false $r | Select-Object FeatureResult, Success, RestartNeeded, ExitCode-Source接受字符串数组源目录有多个副本时可以一起给系统按顺序找。-Restart:$false表示不自动重启装完自己挑时间重启生产机上这个开关比让它自动重启安全得多。返回的ExitCode里0是成功3010是成功但需要重启其他值直接去查第 5 章那张表。WIM 源写法适合手上只有整份 ISO 的情况# 直接从 install.wim 的第 2 个映像取 payload索引先用 Get-WindowsImage 确认 Install-WindowsFeature -Name NET-Framework-Core -Source WIM:D:\sources\install.wim:2 -Restart:$falseWIM:前缀告诉服务从映像里读文件冒号后面的数字是映像索引。索引取错的典型表现是命令跑很久才失败因为系统会真的去解压一份不匹配的 payload 再校验。2012 R2 的介质里 Standard 完整版、Standard 核心版、Datacenter 完整版、Datacenter 核心版通常都在同一个 wim 中具体顺序看Get-WindowsImage的输出别抄别人的数字。注意WIM:源读取时会对 wim 做临时挂载wim 所在磁盘的剩余空间要留够放在只读共享上时偶尔会失败遇到就改用解压出来的 sxs 目录。4.3 封装成可重复执行的脚本单台机器敲命令没问题一批机器就得靠脚本。核心是两点先判断状态再动手失败时把退出码和日志位置打出来。下面这段是我常用的骨架改路径就能用。# Install-NetFx35.ps1 —— 幂等安装 .NET 3.5失败时输出定位信息 param( [string]$Source D:\sources\sxs, [switch]$AllowReboot ) $feat Get-WindowsFeature -Name NET-Framework-Core if ($feat.InstallState -eq Installed) { Write-Output already installed exit 0 } if (-not (Test-Path (Join-Path $Source *.cab))) { Write-Output source not found: $Source exit 2 } $r Install-WindowsFeature -Name NET-Framework-Core -Source $Source -Restart:$AllowReboot Write-Output (exit{0} success{1} restart{2} -f $r.ExitCode, $r.Success, $r.RestartNeeded) if (-not $r.Success) { # 失败时把最近的 CBS 错误行捞出来比让人去翻整份日志快得多 Select-String -Path $env:windir\Logs\CBS\CBS.log -Pattern Error | Select-Object -Last 20 exit 1 }Get-WindowsFeature的InstallState有三个取值Installed、Available、Removed。Removed状态下重装需要显式带源跟Available的处理一样。脚本里先判状态再动手是为了让它在自动化流程里可以反复执行——部署脚本被重跑是常态。批量分发时用Invoke-Command把脚本推到目标机比Install-WindowsFeature -ComputerName可控因为你可以把源路径作为参数传进去也能拿到完整的输出流# 把脚本内容推到多台机器执行源统一指共享目录 $sb { param($src) C:\deploy\Install-NetFx35.ps1 -Source $src } Invoke-Command -ComputerName srv-a,srv-b -ScriptBlock $sb -ArgumentList \\fileserver\sxs共享源在这条路上要先过权限那一关见 3.2。跑之前挑一台做试点确认退出码是 0 或 3010再铺开。5. 避坑与排查五条真实记录下面五条按「现象 → 原因 → 解决」写都是我或者同事实际撞过的。排查顺序建议从上往下先看源再看策略最后才怀疑系统本身。反过来查的人往往在组件存储上白折腾两小时。5.1 源与版本类三个高频错误码第一条0x800f0906提示无法下载源文件。现象是命令跑了几分钟才失败日志里写着「组件存储损坏」或「无法下载」看着像系统坏了。原因八成是没带-Source或者带了但-LimitAccess缺失系统先去外网找被防火墙或 WSUS 挡住后回头报错把真正的方向带偏。解决确认-Source指向含 cab 的目录DISM 加/limitaccessPowerShell 加-Source即可——-Source本身就会抑制外网回落但 DISM 侧的/limitaccess必须显式写。第二条0x800f081f源文件找不到。文件明明在命令就是不认。最常见的原因是版本错配把 20126.2.9200的 sxs 用在了 2012 R26.3.9600上两个目录长得几乎一样。其次是目录层级指错指到了D:\sources而不是D:\sources\sxs。第三是 cab 文件残缺前面拷了一半。解决按 2.2 的命令核对两端版本号按 3.1 检查目录内文件数量和大小用Get-WindowsImage确认 wim 索引。三条都对了还报这个错才去怀疑语言不一致。第三条0x800f0907或0x800f0954被策略拦住。现象是命令几乎瞬间返回日志里提到组策略或 WSUS。原因在于域策略里「指定可选组件安装和组件修复的设置」被配成了「从不尝试从 Windows Update 下载 payload」或者 WSUS 接管了可选组件安装。解决有两条临时改本地策略把备用源文件路径填成D:\sources\sxs或者在命令里直接给-Source绕开策略判断。生产环境我倾向后者改策略要走变更流程命令行参数不用。5.2 系统状态类两个装到一半才炸的坑第四条0x800f0922装到 100% 回滚。最难受的一种进度条眼看走完突然回滚。两个原因系统盘空间不足payload 落地时没地方写或者存在挂起的重启操作组件服务拒绝在挂起状态提交变更。解决按 2.3 的命令查空间和重启标志先重启再清理C:\Windows\Temp和旧的 CBS 日志腾出至少 2GB 再装。虚拟机快照占满存储导致磁盘实际不可写的场景也见过Get-PSDrive看着有空间写入却失败——这种要看存储层的剩余容量。第五条0x80073712组件存储损坏。现象是任何功能安装都失败不限于 3.5报错指向组件存储。原因通常是之前有人手工动过C:\Windows\WinSxS或者非正常断电导致组件库写坏。解决先修再装修复时同样要给源。:: 用同版本介质修复组件存储修复完再回来装 3.5 dism /online /cleanup-image /restorehealth /source:D:\sources\sxs /limitaccess sfc /scannow/restorehealth走的是同一份 payload所以前面校验过的 sxs 目录在这里同样能用。修完再跑sfc /scannow兜一遍两条都干净了再装 .NET 3.5。这一步急不得组件存储没修好装什么都是白装。注意永远不要把介质里的 sxs 文件手工拷进C:\Windows\WinSxS那个目录由组件服务独占管理手工写入等于直接毁掉组件库恢复代价远高于重装。6. 装完怎么验证四层确认 3.5 真的能跑命令返回成功就收工是这类安装最常见的翻车点。功能状态显示已安装、程序启动仍报「需要 .NET Framework 3.5」的情况我遇到过不止一次多数是 32 位与 64 位两套运行时只装了一半或者 ASP.NET 没注册。验证要分层做每层都能独立证伪。第一层看功能状态Get-WindowsFeature -Name NET-Framework-*NET-Framework-Core应是Installed同时看NET-Framework-Features这个父项是否也跟着变成已安装。父项没装上的话某些依赖它的角色仍然起不来。第二层看注册表这一层能区分「功能标记打上了」和「运行时真的注册了」# Install 应为 1Version 与 SP 能看出实际落地的版本 Get-ItemProperty HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5 | Select-Object Install, Version, SP第三层看目录32 位和 64 位两套都要在C:\Windows\Microsoft.NET\Framework\v3.5与C:\Windows\Microsoft.NET\Framework64\v3.5。只有前者说明装的是半套跑 64 位程序照样报缺件。第四层最实在——用 3.5 自带的编译器现场编一个程序跑起来// hello35.cs能被 v3.5 的 csc 编过说明编译链和运行时都完整 using System; class P { static void Main() { Console.WriteLine(Environment.Version); // 3.5 跑在 CLR 2.0 上输出 2.0.50727.x 是正常的 Console.WriteLine(netfx35 ok); } }:: 分别用 32 位和 64 位编译器各编一次两个都能跑才算完整 C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe /nologo /out:C:\temp\h32.exe C:\temp\hello35.cs C:\Windows\Microsoft.NET\Framework64\v3.5\csc.exe /nologo /out:C:\temp\h64.exe C:\temp\hello35.cs C:\temp\h32.exe C:\temp\h64.exe看到2.0.50727.x别以为装错了。.NET Framework 3.5 是叠加在 CLR 2.0 之上的一组程序集Environment.Version报的永远是底层 CLR 版本这跟 4.x 报4.0.30319是两套体系。这个细节能省掉一次「是不是装成 2.0 了」的自我怀疑。如果这台机器还要跑 IIS 上的老 ASP.NET 应用再补一步注册否则站点照样 500:: 只在确实需要跑 ASP.NET 2.0/3.5 站点时执行 C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_regiis.exe -i这一步注册的是 IIS 侧的 ISAPI 映射跟 3.5 运行时本身无关别把它当成安装步骤的一部分。说实话这套流程我走过太多次翻车之后才固定下来不管多急装之前一定先跑 2.2 的版本核对和 2.3 的三项检查装完一定用第四层那个 hello 程序编一遍。命令返回成功只说明组件服务收下了请求能编能跑才说明这台机器真的把 3.5 用起来了。那份从标准版介质里整份留出来的sources\sxs目录我一直单独归档在部署盘上配合同一套脚本新机器从零到跑通不超过十分钟。希望帮到你。本文还有配套的精品资源点击获取