简介:针对 Windows Server 2012 中 .NET Framework 3.5 默认未启用、在线安装易受网络与更新源影响而失败的问题,这份 SxS 镜像资源包面向系统管理员、运维人员及需运行旧版 .NET 应用的用户,提供可离线启用的组件集合,兼顾 .NET 3.5 安装失败排查与服务开启场景。压缩包共 1569 个文件,约 101.69MB,以 dll、exe、config、aspx、resx 等为主,涵盖框架运行库、配置模板与安装支撑文件,目录保留 Sources\sxs 结构,便于通过 DISM 指定源路径完成部署。已有 10501 人学习下载,说明其在旧系统兼容与内网环境部署中具有较高参考价值。使用者可据此减少对 Windows Update 的依赖,在断网或防火墙受限的服务器上完成 NetFx3 功能启用,并借助包内说明梳理安装路径、权限要求与常见报错处理思路,适合需要稳定复现安装流程的运维场景。
1. 安装 .NET 3.5 卡在 0x800F0906:离线服务器上最常见的那道坎
刚装完的 Windows Server 2012,服务器管理器里勾上“.NET Framework 3.5 功能”,进度条走到一半弹出一行红字:0x800F0906,提示无法从 Windows 更新下载源文件。换成 DISM 命令行再敲一遍,错误码一模一样;有人让你去挂个 ISO,挂上去路径一填,报错又变成 0x800F081F。这类 .net3.5安装失败 在内网机器、隔离机房、默认不通外网的云主机上几乎是每周都会碰到的固定节目,折腾程度不亚于排查一个偶发蓝屏。这篇记录围绕 window server 2012 安装 net3.5 时用 --sxs 镜像资源包 补源这条主线,把几个高频错误码拆开,给出可以直接抄的命令和判断顺序,适合正被 .net 3.5安装 卡住、需要尽快让业务跑起来的人。
2. 报错 0x800F0906 / 0x800F081F:先弄明白「按需功能」为什么不肯自己下载
报错码只是结果,不是原因。同一个 0x800F0906,可能是机器没网,可能是组策略把下载源关了,也可能是你挂的镜像根本不是同一个系统版本。不把这层关系理清,就会陷入「换个 ISO 再试一次」的循环,试到第四次还是同样的红字。
2.1 从 Windows Server 2012 起,.NET 3.5 的实体文件不再随系统落地
2008 R2 及更早的系统上,.NET 3.5 是系统自带组件,文件实打实躺在 WinSxS 里,勾选即用,根本不需要源。2012 开始微软改了打包方式,把 .NET 3.5 归入 Features on Demand(按需功能):系统盘里只保留一个几百 KB 的「存根」,真正的负载文件被挪到了安装介质的\sources\sxs目录下,形式是一堆 cab 包,其中最关键的一个叫microsoft-windows-netfx3-ondemand-package.cab。
这个设计带来的直接后果是:系统知道自己「有这个功能」,但手上没有「装这个功能的料」。默认行为是去 Windows 更新把料拉下来,拉不到就抛 0x800F0906。所以很多人会困惑——我明明在C:\Windows\WinSxS下看到 netfx3 相关的目录,怎么就装不上?那些目录只是清单和存根,不是负载。这个认知差是绝大多数 .net3.5安装失败 的起点。
理解这一点,--sxs 镜像资源包 的价值就清楚了:它把安装介质里的sources\sxs单独抽出来,按系统版本分好,你不用每次去翻原版 ISO、不用挂载、不用担心盘符变化,直接把 DISM 的/Source指过去就行。本质上是给「按需功能」补一份稳定的、版本对得上的料。
2.2 四个高频错误码,各自指向完全不同的故障面
很多人把这几个码当成同一件事,其实它们分布在链路的四个不同环节。先把对照关系摆出来,后面排查时能省一大半时间。
| 错误码 | 字面含义 | 真实卡点 | 典型场景 |
|---|---|---|---|
| 0x800F0906 | 无法下载源文件 | 没有可用源,且未指定/Source | 内网机、云主机默认无外网 |
| 0x800F0907 | 组策略禁止 | 可选组件安装策略被设为「从不尝试」 | 域内机器被统一策略管控 |
| 0x800F081F | 找不到源文件 | 指定了/Source,但路径下没有匹配的 cab | 路径写错、版本不匹配、sxs 被裁剪 |
| 0x800F0922 | 安装未完成 | 系统分区或保留分区空间不足、事务中断 | 小容量系统盘、长期未清理的机器 |
看清楚这张表,判断路径就出来了:0906 和 0907 是「拿不到料」,081F 是「料不对」,0922 是「放不下」。前者去补源或改策略,后者去看磁盘和日志,方向完全不同。我在现场见过最典型的翻车,是有人遇到 081F 还在拼命加网络权限,实际上跟网络一点关系都没有。
2.3 先判断缺的是「源」还是「权限」,再决定动不动手
动手之前先花两分钟查状态,别上来就敲 DISM。三条命令,分别看功能当前状态、功能详细信息、以及有没有策略在拦。
# 查看 NetFx3 当前状态:Available 表示未安装,Installed 表示已装 Import-Module ServerManager Get-WindowsFeature -Name NetFx3 | Format-List Name, InstallState, DisplayNameGet-WindowsFeature是 2012 上判断功能状态最直接的方式。InstallState只有两种取值有意义:Available说明负载不在本机,需要外部源;Installed说明已经装好了,那你遇到的问题就是另一个方向(比如程序仍然报缺少运行库,那是应用池的 .NET 版本或 32/64 位问题)。Removed状态比较少见,通常是被人用Uninstall-WindowsFeature卸过。
REM 查看功能详细信息,重点看 State 和 Restart Required DISM /Online /Get-FeatureInfo /FeatureName:NetFx3DISM 的输出比 PowerShell 啰嗦,但多了Restart Required字段。如果这里显示Possible,说明系统里有挂起的事务,得先重启再装,否则后面无论怎么指定源都会失败。这个细节很容易被忽略,尤其是在刚打完补丁就急着装 .NET 3.5 的机器上。
REM 查可选组件安装策略:RepairContentServerSource 为 0 时表示从不尝试联网 reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing" /v RepairContentServerSource REM 查是否被 WSUS 接管:UseWUServer 为 1 表示走内网更新服务器 reg query "HKLM\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" /v UseWUServer第一条注册表查询如果返回0x2(找不到),说明策略未配置,走默认行为,此时无外网必然 0906;如果返回0,说明被显式设成「从不尝试」,这就是 0907 的来源。第二条返回1时,DISM 的下载行为会被重定向到内网更新服务器,如果那台服务器上没有批准 NetFx3 这个包,表现同样是长时间卡住后失败。查到这一步,你基本能确定该走「补源」还是「改策略」。
3. 挂 --sxs 镜像资源包补源:DISM 命令的四种写法和参数取舍
确定是「缺料」之后,剩下的就是把料喂进去。这一步命令不长,但参数组合有好几种,选错了会绕远路。下面四种写法按适用场景从常用到特殊排列,每种都标清楚什么时候用。
3.1 挂载介质并确认 sxs 目录的真实路径
如果用原版 ISO,先挂载再看盘符。服务器上盘符经常和你想的不一样,尤其是已经有几个数据盘的生产机。
# 挂载 ISO 并取出实际分配的盘符,避免手写 E: 结果盘符是 F: $img = Mount-DiskImage -ImagePath "D:\iso\win2012r2.iso" -PassThru $drive = ($img | Get-Volume).DriveLetter Write-Output "挂载盘符: $drive" Test-Path "${drive}:\sources\sxs\microsoft-windows-netfx3-ondemand-package.cab"Mount-DiskImage -PassThru是关键,不加这个参数就拿不到对象,后面取不到盘符。第三行直接去测试那个核心 cab 是否存在,返回True才说明这个介质可用。这一步看着多余,实际上能挡掉一半的 081F——很多 ISO 是被精简过的,sources\sxs目录在,但里面的 cab 被删了。
如果用的是已经抽好的 --sxs 镜像资源包,通常是一个按版本分好的目录,直接Test-Path确认目标子目录里有 cab 即可,省掉挂载这一步。这也是它在批量部署里更省事的地方:不用每台机器都挂一次 ISO,拷过去指个路径就完事。
3.2 最常用的一条:目录源 + 强制不走更新
REM /Online 表示对当前运行的系统操作 REM /FeatureName:NetFx3 是 .NET 3.5 的功能名,注意不是 NET-Framework-Features REM /All 连带安装所有父功能和依赖 REM /Source 指向 sxs 目录,路径必须包含 cab 文件本身 REM /LimitAccess 强制只从本地源取,不再尝试联网,避免卡在下载阶段 DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /Source:D:\sxs\2012R2 /LimitAccess六个参数里,最容易漏的是/LimitAccess。不加它,DISM 会先尝试联网,无外网的机器会在这一步耗很久(有时十几分钟)才回落到本地源,看起来像卡死。加了它,DISM 直接从本地读,几秒钟就出结果。/All也不能省,NetFx3 下面挂着几个父功能,不加的话可能出现「功能装了但依赖没装全」,程序照样报错。
/Source的值指向的必须是包含 cab 文件的目录本身,不是它的上级。很多人写成D:\sxs而实际 cab 在D:\sxs\2012R2下,就会得到 081F——DISM 不会递归找子目录。多个源路径可以用分号分隔,常见做法是把资源包目录和备用目录一起写上,提高一次成功的概率。
3.3 不挂载 ISO,直接把 WIM 当源用
介质不方便挂载、或者手上只有 install.wim 的场景,可以让 DISM 直接读 WIM 内部的索引,省掉挂载。
REM 先列出 install.wim 里有哪些索引,确认哪个是目标版本 DISM /Get-WimInfo /WimFile:E:\sources\install.wim REM 直接指定 WIM 和索引号,语法是 WIM:路径:索引 DISM /Online /Enable-Feature /FeatureName:NetFx3 /All /Source:WIM:E:\sources\install.wim:1 /LimitAccess/Get-WimInfo那一步不能跳。一个 install.wim 里常常打包了 Standard、Datacenter、Core 好几个版本,索引号从 1 开始。索引选错不一定会立刻报错,但装进去的负载和当前系统 build 不匹配,后续可能出现功能异常这种更难受的问题。选索引的原则是看Edition和Version两行,和winver显示的信息对上。
WIM:这个前缀和后面的冒号索引是固定语法,写成WIM:E:\sources\install.wim不带索引会直接报错。这条写法还有个好处:WIM 是只读的,不会被误改,适合放在共享目录里给多台机器当统一源。
3.4 用 PowerShell 封装,方便批量执行
单台机器用 DISM 就够,要在几十台上重复做,用 PowerShell 包一层更稳。
# 依次尝试资源包目录和原版介质,任一成功即停止 $sources = @("D:\sxs\2012R2", "E:\sources\sxs") foreach ($src in $sources) { if (Test-Path "$src\microsoft-windows-netfx3-ondemand-package.cab") { Write-Output "使用源: $src" # -All 装齐依赖,-LimitAccess 禁止联网,-Source 指定本地路径 Install-WindowsFeature -Name NET-Framework-Features -Source $src -LimitAccess -All break } }Install-WindowsFeature是Enable-WindowsOptionalFeature在服务器端的等价物,参数语义和 DISM 一一对应:-Source对应/Source,-LimitAccess对应/LimitAccess,-All对应/All。功能名要用NET-Framework-Features这个 PowerShell 别名,写错会提示功能不存在。这段脚本的价值在于先做存在性检查再执行,避免在错的源上反复重试——排查时最耗时间的往往不是装不上,而是不知道自己在拿一个坏源反复试。
3.5 装完必须验证,别只看命令返回 0
命令返回 0 不等于功能真的上线了,这条血泪经验值得单独强调。验证分三层:功能状态、注册表、实际运行。
# 第一层:功能状态必须是 Installed Get-WindowsFeature -Name NetFx3 | Select-Object Name, InstallState # 第二层:注册表里 Install 值为 1,Version 显示 3.5.x reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5" /v Install # 第三层:跑一个 .NET 3.5 编译的小程序,或看事件日志有无侧并加载记录 Get-EventLog -LogName Application -Source ".NET Runtime" -Newest 5第一层最常见的问题是显示Installed但Restart Required为Yes,此时功能文件已落盘但没生效,重启后才算真正完成 .net 3.5服务开启。第二层的Install值为1是最终判据,比任何界面显示都可靠。第三层是给业务方确认用的,尤其是老系统上跑 ASP.NET 2.0/3.5 站点的场景,应用池的 .NET CLR 版本还要单独设成 v2.0.50727,这两件事不能混为一谈——功能装好只是前提,站点跑不起来还有应用池这一层。
4. 组策略和 WSUS 挡住了安装:命令返回成功但功能没上线的排查
命令敲了,没报错,返回码 0,结果Get-WindowsFeature一看还是Available。这种「看着成功实际没装」的情况,八成是被策略层拦住了,或者在事务层面没走完。这一章专治这类哑巴亏。
4.1 可选组件安装策略的四个取值,决定 DISM 去哪找料
这条策略在「计算机配置 → 管理模板 → 系统 → 指定 Windows 功能安装和修复的设置」,对应注册表HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing\RepairContentServerSource。它有四个状态,含义差别很大:
| 取值 | 策略状态 | DISM 行为 |
|---|---|---|
| 未配置(键不存在) | 默认 | 先尝试 Windows 更新,失败才回落 |
| 0 | 从不尝试从 Windows 更新下载 | 只认/Source,没有源直接 0907 |
| 1 | 从 WSUS 下载 | 走内网更新服务器,服务器上没有就失败 |
| 2 | 从 Windows 更新下载 | 无外网时表现和未配置类似,但不受本地缓存影响 |
这张表解释了一个反直觉现象:有些机器上/Source明明指对了还是报错,就是因为策略被设成 0 且同时配置了LocalSourcePath指向一个失效路径,策略里的路径优先级高于命令行。处理办法要么临时把策略改成「未配置」,要么把LocalSourcePath指到资源包目录。
REM 临时清掉策略级的源限制,装完再恢复(域环境请先跟管理员确认) reg delete "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing" /v LocalSourcePath /f reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing" /v RepairContentServerSource /t REG_DWORD /d 2 /f REM 刷新策略使其立即生效 gpupdate /forcereg delete那一行删的是策略里硬编码的源路径,/f表示不再确认。第二条把下载源改回 Windows 更新,这样命令行指定的/Source才能生效。gpupdate /force是为了让组策略立即应用,不然要等下一次刷新周期,排查时容易误判成「改了没用」。域环境下这几步要先走变更流程,别直接在生产机上动。
4.2 WSUS 接管时,DISM 会停在某个百分比不动
内网统一用 WSUS 的环境,UseWUServer会被设成 1,DISM 的下载请求全部重定向到内网更新服务器。如果那台服务器的产品列表里没有批准 NetFx3 对应的按需功能包,表现就是进度条卡在 20% 或 60% 好几分钟,最后抛 0906 或 0907。
判断方法很直接,看C:\Windows\Logs\DISM\dism.log里有没有WU或WSUS关键字的连接记录,以及C:\Windows\Logs\CBS\CBS.log里失败前最后几行指向哪里。日志很长,按时间倒序找Error就行。
# 抓取 DISM 日志中最近的错误行,定位失败环节 Select-String -Path "C:\Windows\Logs\DISM\dism.log" -Pattern "Error|0x800f" | Select-Object -Last 15 | ForEach-Object { $_.Line }Select-String比直接打开日志翻要快得多,-Last 15只看最后十几条,避免被大量无关记录淹没。看到Failed to find source就是源问题,看到WU client相关就是走网络失败了。定位到这一层,要么在 WSUS 里补批准,要么干脆用本地源加/LimitAccess绕开——后者在生产机上更快,不用等更新服务器那边排期。
4.3 返回码是 0 但功能没上线,通常是事务没走完
还有一种情况:DISM 输出「操作成功完成」,日志里也没有 Error,但功能状态就是不变。常见原因有三个。一是系统里有挂起的 CBS 事务,Restart Required为Possible,此时所有安装动作都会被排队而不执行,解决办法只有重启。二是/All没加,功能本体装了但依赖缺失,状态可能显示异常。三是系统盘空间不足,文件写了一半被截断,这种情况往往伴随 0922。
# 检查是否有挂起的重启标记 Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" # 检查系统盘剩余空间,NetFx3 解压后需要留出足够余量 Get-PSDrive C | Select-Object Used, Free第一个检查返回True就别再折腾了,重启一次再装,这是最省时间的做法。第二个检查留出至少 2 GB 余量比较稳妥,系统盘快满的机器上装大功能,失败方式往往很难看。这两条检查加起来不到十秒,却能挡掉相当一部分「命令明明成功但就是没装」的玄学问题。
5. 避坑清单:sxs 源包在真实机房里翻了六次车
这一节是踩坑记录,五条,每条按「现象 → 原因 → 解决」写。都是实际遇到过、且新手很容易重复踩的。
5.1 现象:命令报 081F;原因:拿 2012 R2 的 sxs 去装 2012
现象很典型,/Source路径明明存在,cab 也在,DISM 就是咬定「找不到源文件」。根因是版本不匹配:2012 和 2012 R2 是两个不同的 build(6.2 和 6.3),sxs 目录里的 cab 做了版本签名校验,跨版本喂过去会直接被拒,报错还偏偏是「找不到」而不是「版本不对」,很误导人。
解决办法是核对 build 号再选目录。用winver或systeminfo看系统版本,资源包目录按版本分开存放,别图省事混用。核对命令如下:
# 取当前系统 build,6.2 对应 2012,6.3 对应 2012 R2 [System.Environment]::OSVersion.Version | Select-Object Major, Minor, BuildMajor.Minor是6.2就用 2012 的源,是6.3就用 2012 R2 的源。这个判断要在动手之前做,装完再发现版本不对,清理起来更麻烦——功能装了一半的状态下卸载有时需要额外处理。
5.2 现象:昨天能装今天报错;原因:镜像盘符在重启后变了
挂载的 ISO 在重启后盘符可能从 E 变成 F,脚本里硬编码的E:\sources\sxs就失效了。现象是同一份脚本、同一台机器,前一天成功,第二天失败,很迷惑。
解决办法是不要硬编码盘符,每次挂载后动态取。或者更彻底一点,把 sxs 目录直接拷到本地固定路径(比如C:\sxs),从此不再受挂载状态影响。批量部署场景下推荐后者,一次拷贝,长期有效,也省掉了每台机器挂载 ISO 的步骤。
5.3 现象:cab 都在但装到一半失败;原因:源来自精简版镜像
用第三方精简过、或者被各种「优化」工具处理过的系统镜像,sources\sxs目录结构还在,但里面少了几个依赖 cab。DISM 开始装的时候不报错,装到某个依赖时突然失败,日志里能看到具体缺哪个包。
解决办法是换一份完整介质。判断方法是在装之前数一下 cab 数量,完整的 sxs 目录有几十个文件,只有零星几个基本可以判定被裁过。这也是抽好的 --sxs 镜像资源包比「随便找个 ISO」更可靠的原因:它是从完整介质里提取的,文件完整性有保证。
5.4 现象:装完程序还是报缺运行库;原因:只装了功能没设应用池
功能状态是Installed,Get-WindowsFeature也没问题,但老站点一访问就报「无法加载程序集」。根因不在功能,在 IIS 应用池——默认应用池的 .NET CLR 版本是 v4.0,跑 .NET 3.5 的站点必须改成 v2.0.50727。
解决办法是单独设置应用池,两步都要做:
# 加载 IIS 管理模块 Import-Module WebAdministration # 把目标应用池的 CLR 版本切到 v2.0,.NET 3.5 站点必须走这个版本 Set-ItemProperty IIS:\AppPools\DefaultAppPool -Name managedRuntimeVersion -Value "v2.0"managedRuntimeVersion的合法值只有v2.0和v4.0两个,写v3.5会直接报错——.NET 3.5 在运行时层面用的就是 2.0 的 CLR,这一点经常被误解。改完应用池要回收一次才生效。
5.5 现象:加了/LimitAccess还是联网;原因:策略里的源优先级更高
命令行参数不是最高的。组策略里的LocalSourcePath一旦配置,会覆盖/Source,DISM 拿着策略里的路径去找料,找不到就失败,而你还在怀疑是自己路径写错了。
解决办法是先确认策略状态,再决定改哪一边。查法和改法在第 4 章已经给出,核心就一句:RepairContentServerSource和LocalSourcePath这两个键存在时,命令行的/Source不一定是最终生效的那个。排查顺序上,先查策略再查路径,能少走很多弯路。
6. 三分钟定位:怎么快速判断卡住的是源、策略还是镜像
这套判断顺序是我这几年养成的习惯,遇到 .net3.5安装失败 先走一遍,基本两三分钟就能锁定方向,不用盲目试错。
第一步看错误码。0907 直接去查RepairContentServerSource,这条路径最短;0906 看有没有配/Source,没配就是没源,配了还要看/LimitAccess加没加;081F 一律先验路径下有没有microsoft-windows-netfx3-ondemand-package.cab,有再往下查版本;0922 别犹豫,先看磁盘和挂起重启。
第二步验版本。[System.Environment]::OSVersion.Version取 build,和资源包目录对一遍。这一步只要十秒,却能排掉最多的无效尝试。
第三步查策略。RepairContentServerSource和UseWUServer两个键,前者决定本地源能不能生效,后者决定会不会被重定向去内网更新服务器。域内机器这两项几乎必然被配置过,跳过这步等于白查。
第四步翻日志。C:\Windows\Logs\DISM\dism.log找Error,CBS.log找失败前最后几条。日志里的原话往往比错误码精确得多,尤其是「Failed to find source」和「WU client」这两种,一看就知道该往哪个方向走。
把这几步整理成一张判断表,现场排查时对着看:
| 观察到的信号 | 优先怀疑 | 第一个动作 |
|---|---|---|
| 报 0x800F0907 | 组策略禁止下载 | 查RepairContentServerSource是否为 0 |
报 0x800F0906 且无/Source | 缺源 | 挂载介质或指向 sxs 目录 |
报 0x800F0906 且加了/Source | 被 WSUS 接管或策略覆盖 | 查UseWUServer,加/LimitAccess |
| 报 0x800F081F | 路径或版本不对 | Test-Path验 cab,比对 build |
| 返回 0 但状态未变 | 事务未完成或有挂起重启 | 查RebootPending,重启后重装 |
| 装完程序仍报错 | 应用池 CLR 版本 | 改managedRuntimeVersion为v2.0 |
从那以后我每次上机做这类补功能的操作,都会强制走一遍「验版本 → 验路径 → 查策略 → 看挂起重启」这四步,哪怕那台机器看起来一切正常。血泪经验是:跳过检查省下的两分钟,通常会在后面以一个更莫名其妙的错误码还回来。希望这套顺序能帮你少折腾几次。
本文还有配套的精品资源,点击获取