WinPE磁盘只读终极指南:解决关键磁盘与零写入
2025-03-29 04:21:16
WinPE 启动就锁盘?彻底搞定所有磁盘只读,不留写入痕跡
搞 WinPE 镜像时,想让它启动后所有物理硬盘都变成只读状态,防止任何意外写入,结果碰壁了?特别是遇到那个 “关键磁盘”(critical disk) 的报错,用 PowerShell 的 Set-Disk
或 diskpart
都搞不定,感觉很头疼。而且,就算用了 SAN Policy 3 阻止了系统盘(比如 C 盘)自动挂载,怎么确保在手动设置只读之前,哪怕一丁点儿的写入都不会发生?
别急,这事儿有解。咱们来捋一捋为啥会这样,然后看看有哪些靠谱的办法能实现 WinPE 启动时,所有目标磁盘(除了 WinPE 自己运行的 X: 盘)都稳稳地处于只读状态。
一、为啥想让磁盘只读这么难?
简单来说,WinPE 本身也是个迷你 Windows 系统。它启动过程中,需要识别和初始化硬件,包括磁盘。这里面有几个坎:
- 自动挂载机制 :默认情况下,WinPE 会尝试挂载它识别到的所有可用分区,特别是那些带有活动 Windows 安装的分区。这就可能导致在你的脚本介入之前,系统进程(比如你看到的 TXF 日志)就已经开始在磁盘上“活动”了。虽然设置 SAN Policy 为 3 (OfflineInternal) 可以阻止内部磁盘自动挂载,解决了这个问题的一部分。
- “关键磁盘”保护 :Windows 有一套保护机制,防止你轻易地把系统运行所必需的磁盘(比如包含活动页面文件、系统休眠文件、或者被判定为引导/系统重要性的磁盘)设为只读或脱机。当
Set-Disk
或diskpart
告诉你这是个“关键磁盘”时,就是这个机制在起作用。即使在 WinPE 环境下,如果某个磁盘在某个时刻被系统认定为关键,它就会拒绝你的只读请求。 - 操作时机 :你想在
Startnet.cmd
里执行脚本设只读。这个脚本通常在wpeinit
完成初始化后运行。虽然 SAN Policy 3 阻止了自动挂载,但在wpeinit
到你的脚本执行之间,理论上仍有极小的窗口期。更关键的是,如果某些磁盘在 WinPE 启动的更早阶段(加载驱动时)被错误地标记或访问,也可能导致后续设置只读失败。
所以,核心挑战在于:既要阻止自动挂载和早期访问,又要绕过“关键磁盘”的限制,还要确保操作足够早,杜绝任何潜在的写入机会。
二、解决思路与方案
针对上面的难点,咱们可以尝试几种不同的策略。
法子一:先礼后兵 - SAN 策略加脚本组合拳 (优化版)
这是基于你已经部分成功的路子进行的优化和说明。关键在于确保 SAN Policy 生效够早,并且理解脚本的作用时机。
原理与作用:
- SAN Policy 3 (OfflineInternal) :这个策略告诉系统,在 WinPE 启动时,不要自动挂载(online)任何内部磁盘(SATA, NVMe 等)。磁盘会被识别,但在
diskpart
里看会是Offline
状态。这从源头上阻止了 WinPE 对这些磁盘进行不必要的访问和写入。这个设置必须在制作 WinPE 镜像时应用,而不是等到 WinPE 启动后再设置。 Startnet.cmd
脚本 :在wpeinit
初始化完成后,这个脚本开始执行。此时,因为 SAN Policy 的作用,所有内部磁盘应该都是Offline
状态,还没被挂载。你的脚本可以安全地遍历所有Offline
状态的物理磁盘,并尝试将它们设置为只读。设置成功后,你可以根据需要选择性地手动挂载(online disk
)这些已经是只读的磁盘卷。
操作步骤:
-
离线修改 WinPE 镜像的 SAN Policy:
- 挂载你的 WinPE WIM 文件到一个临时目录(例如
C:\WinPE_Mount
):Dism /Mount-Image /ImageFile:"C:\path\to\your\winpe.wim" /Index:1 /MountDir:"C:\WinPE_Mount"
- 创建一个名为
setsan.xml
的文件,内容如下:
(请根据你 WinPE 的架构修改<unattend xmlns="urn:schemas-microsoft-com:unattend"> <settings pass="windowsPE"> <component name="Microsoft-Windows-PartitionManager" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"> <SanPolicy>3</SanPolicy> </component> <!-- 如果你的 WinPE 是 x86,把 processorArchitecture 改为 x86 --> <!-- 如果你的 WinPE 是 arm64,把 processorArchitecture 改为 arm64 --> </settings> </unattend>
processorArchitecture
) - 将这个 unattend 文件应用到挂载的镜像:
Dism /Image:"C:\WinPE_Mount" /Apply-Unattend:C:\path\to\setsan.xml
- 卸载并保存 WIM 镜像:
Dism /Unmount-Image /MountDir:"C:\WinPE_Mount" /Commit
现在,这个 WinPE 镜像启动时就会默认采用 SAN Policy 3。
- 挂载你的 WinPE WIM 文件到一个临时目录(例如
-
编写
Startnet.cmd
脚本:- 编辑 WinPE 镜像中的
Windows\System32\Startnet.cmd
文件。 - 在
wpeinit
命令之后,添加设置磁盘只读的逻辑。
PowerShell 示例 (
Startnet.cmd
内容):@echo off wpeinit echo Setting all physical disks to ReadOnly (excluding system/removable)... powershell.exe -NoProfile -ExecutionPolicy Bypass -Command "& { $disks = Get-Disk | Where-Object { $_.Number -ne $null -and $_.IsSystem -eq $false -and $_.BusType -ne 'USB' -and $_.IsReadOnly -eq $false } # 筛选条件可以按需调整 if ($disks) { foreach ($disk in $disks) { try { Write-Host ('Attempting to set Disk {0} ({1}) to ReadOnly...' -f $disk.Number, $disk.FriendlyName) # 先确保磁盘联机才能设置,因为 SAN Policy 3 让它们脱机了 # 注意:这里联机是为了能执行 Set-Disk,但此时还未分配盘符挂载卷 Set-Disk -Number $disk.Number -IsOffline $false -ErrorAction Stop Set-Disk -Number $disk.Number -IsReadOnly $true -ErrorAction Stop Write-Host ('Disk {0} set to ReadOnly successfully.' -f $disk.Number) } catch { Write-Warning ('Failed to set Disk {0} to ReadOnly. Error: {1}' -f $disk.Number, $_.Exception.Message) # 如果仍然遇到 Critical Disk 错误,可能需要进一步排查 # 可以选择在这里将磁盘重新脱机,如果不需要后续访问 # Set-Disk -Number $disk.Number -IsOffline $true } } } else { Write-Host 'No target disks found or all applicable disks are already ReadOnly.' } }" echo Disk ReadOnly configuration finished. REM 在这里可以添加你后续需要手动挂载或处理只读磁盘的逻辑 REM 例如: diskpart /s mount_script.txt
Diskpart 示例 (
Startnet.cmd
内容):@echo off wpeinit echo Setting all physical disks to ReadOnly using Diskpart... echo list disk > temp_disk_list.txt for /f "tokens=1,2" %%a in ('diskpart /s temp_disk_list.txt ^| findstr /B /C:"Disk"') do ( if /I "%%b" NEQ "Status" ( echo select disk %%b > temp_disk_script.txt echo attributes disk >> temp_disk_script.txt for /f "tokens=3" %%s in ('diskpart /s temp_disk_script.txt ^| findstr /C:"Current Read-only State : Yes"') do ( echo Disk %%b is already read-only. Skipping. goto :nextdisk_%%b ) for /f "tokens=3" %%s in ('diskpart /s temp_disk_script.txt ^| findstr /C:"Current Read-only State : No"') do ( echo Disk %%b is writeable. Attempting to set read-only... REM 需要先 online disk 才能设置属性 echo select disk %%b > temp_disk_script_set.txt echo online disk >> temp_disk_script_set.txt echo attributes disk set readonly >> temp_disk_script_set.txt diskpart /s temp_disk_script_set.txt del temp_disk_script_set.txt echo Checking status for Disk %%b after attempt... echo select disk %%b > temp_disk_script_check.txt echo attributes disk >> temp_disk_script_check.txt diskpart /s temp_disk_script_check.txt | findstr /C:"Current Read-only State" del temp_disk_script_check.txt REM 可以选择性地再 offline disk REM echo select disk %%b > temp_disk_script_off.txt REM echo offline disk >> temp_disk_script_off.txt REM diskpart /s temp_disk_script_off.txt REM del temp_disk_script_off.txt ) ) :nextdisk_%%b ) del temp_disk_list.txt del temp_disk_script.txt > nul 2>&1 echo Disk ReadOnly configuration finished. REM 后续操作...
确保脚本有足够的健壮性来处理各种磁盘状态和可能的错误。
- 编辑 WinPE 镜像中的
关于“关键磁盘”:
使用 SAN Policy 3 后,内部磁盘默认是 Offline
状态,它们通常不会被 WinPE 初始化过程标记为“关键”。只有当你尝试 online
并设置只读时,系统才会重新评估。理论上,只要这个磁盘上没有 WinPE 运行依赖的关键组件(比如 WinPE 不是从这个磁盘启动的,没有页面文件等),Set-Disk
或 attributes disk set readonly
应该能成功。如果你依然遇到“关键磁盘”错误,很可能是这个磁盘包含了一些被 WinPE 认为重要的分区(即使未挂载),或者是其他特殊硬件配置导致。
回答你的问题 (针对此方案):
- Q1 (绕过关键磁盘): 这个方案主要通过 SAN Policy 提前阻止磁盘上线,来 规避 磁盘被标记为“关键”的可能性。它不是直接 绕过 保护机制,而是创造条件让机制不触发。如果某个磁盘因特殊原因(如硬件RAID 控制器信息、预留分区等)仍然被标记为关键,此方法可能依然失败。
- Q2 (脚本执行时机与意外写入): 因为 SAN Policy 3 保证了磁盘在
wpeinit
后仍处于Offline
状态,所以在你的Startnet.cmd
脚本执行Set-Disk
或attributes disk set readonly
之前,Windows 内核本身不会主动向这些Offline
的卷写入数据(比如日志、文件系统更新)。这个窗口期内发生意外写入的可能性极低。直到你手动online disk
或挂载卷之前,它们都是安全的。设置只读后再online
,是比较稳妥的顺序。
进阶技巧/注意事项:
- 脚本中的磁盘筛选逻辑(PowerShell 的
Where-Object
)可以根据你的具体需求调整,比如你可能想排除所有 USB 磁盘,或者只针对特定 BusType 的磁盘。 - 增加错误处理,比如记录哪些磁盘设置失败,以及失败原因。
- 如果 WinPE 是从 USB 启动,而你想让这个 USB 盘保持可写,确保脚本能正确识别并跳过它。
法子二:釜底抽薪 - 直接修改注册表冻结写入
如果你追求的是绝对的、从驱动加载层面就开始的只读保护,修改注册表可能是更彻底的办法。这相当于给存储驱动带上了一个“紧箍咒”。
原理与作用:
Windows 有一个注册表项可以控制存储设备的写入保护策略。通过在 WinPE 镜像构建时修改这个注册表值,可以强制所有(或特定类型的)存储设备在被系统识别时就默认为只读状态。这个设置在 wpeinit
运行之前、甚至在 Startnet.cmd
脚本执行之前就已经生效。
关键注册表项:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies
关键值 (DWORD): WriteProtect
- 值为
1
: 启用写入保护(只读)。 - 值为
0
(或不存在): 禁用写入保护(可写)。
操作步骤:
- 挂载 WinPE WIM 文件 (同方案一的第一步)。
- 加载 WinPE 镜像的 SYSTEM 注册表 Hive:
(这里reg load HKLM\WinPE_SYSTEM "C:\WinPE_Mount\Windows\System32\config\SYSTEM"
WinPE_SYSTEM
是一个临时的挂载点名称,可以自定义) - 修改或创建 WriteProtect 值:
(如果reg add HKLM\WinPE_SYSTEM\CurrentControlSet\Control\StorageDevicePolicies /v WriteProtect /t REG_DWORD /d 1 /f
StorageDevicePolicies
项本身不存在,reg add
会自动创建它。/f
参数强制覆盖现有值) - 卸载注册表 Hive:
reg unload HKLM\WinPE_SYSTEM
- 卸载并保存 WIM 镜像 (同方案一的最后一步)。
效果:
用这个修改过的 WinPE 启动后,所有被 StorageDevicePolicies
影响的磁盘(通常是所有固定磁盘和移动磁盘)在系统层面就会被视为只读。diskpart
查看 attributes disk
会直接显示 Current Read-only State : Yes
。此时你再运行 Get-Disk
或 diskpart
,它们会报告磁盘已经是只读的。
关于“关键磁盘”:
这个方法是在更底层实现的写入保护,它直接作用于存储堆栈,早于“关键磁盘”逻辑的判断。因此,它能有效地“绕过”因为“关键磁盘”状态而无法设置只read-only的问题。磁盘驱动加载时就会遵循这个全局只读策略。
回答你的问题 (针对此方案):
- Q1 (绕过关键磁盘): 是的,这个方法通过修改全局策略,直接让磁盘以只读状态初始化,从而有效绕过了后续因为“关键性”判断导致的设置失败问题。
- Q2 (脚本执行时机与意外写入): 这个方法在 WinPE 内核启动、驱动加载时就已经生效,远早于
Startnet.cmd
。它能确保从一开始就不会有任何对受保护磁盘的写入操作。因此,不存在意外写入的窗口期。 - Q3 (在启动时执行): 这个方法本质上就是通过修改镜像配置,实现了“在启动时”(更准确地说是启动过程中驱动加载时)就强制只读。这是最接近你“零写入”要求的方法。
安全建议/注意事项:
- 这个设置是全局性的。一旦设置,该 WinPE 环境下所有受影响的磁盘都将是只读的,包括你可能后续想写入的磁盘(比如,如果你的工具需要在某个数据盘上创建报告或日志)。你需要非常确定这是你想要的效果。
- 要恢复写入能力,要么使用一个未修改的 WinPE,要么需要再次离线修改 WIM 文件,将
WriteProtect
值改回0
或删除。 - 这个方法对于一些需要直接扇区访问或者使用特殊驱动的磁盘工具,其行为可能需要测试确认。
三、咋选?看你的需求严格程度
- 如果你只是想防止常规操作下的意外写入,并且可以接受在
Startnet.cmd
中进行控制 :
方案一 (SAN Policy + 脚本) 是一个不错的选择。它相对灵活,通过 SAN Policy 阻止自动挂载已经解决了大部分问题,脚本再加一道保险。虽然理论上对“关键磁盘”的绕过不是100%保证,但在大多数标准场景下应该足够。 - 如果你追求绝对的“零写入”,要求从 WinPE 启动的瞬间起,磁盘就不能有任何被写入的风险,并且不介意对 WinPE 镜像做更底层的修改 :
方案二 (修改注册表WriteProtect
) 是更可靠、更彻底的方案。它从驱动层面锁定了写入,有效地解决了“关键磁盘”问题和时机问题,真正做到了启动即只读。但缺点是缺乏灵活性,一旦设定,该 WinPE 环境下就全是只读盘(X:盘除外)。
根据你的,尤其是“需要 0 Writes to disk. even a measly log could ruin things”,方案二(修改注册表)似乎更能满足你的苛刻要求。它提供了一种更强的保证,确保在你的任何脚本或工具介入之前,目标磁盘就已经处于硬件(由驱动执行)层面的只读状态。