返回

WinPE磁盘只读终极指南:解决关键磁盘与零写入

windows

WinPE 启动就锁盘?彻底搞定所有磁盘只读,不留写入痕跡

搞 WinPE 镜像时,想让它启动后所有物理硬盘都变成只读状态,防止任何意外写入,结果碰壁了?特别是遇到那个 “关键磁盘”(critical disk) 的报错,用 PowerShell 的 Set-Diskdiskpart 都搞不定,感觉很头疼。而且,就算用了 SAN Policy 3 阻止了系统盘(比如 C 盘)自动挂载,怎么确保在手动设置只读之前,哪怕一丁点儿的写入都不会发生?

别急,这事儿有解。咱们来捋一捋为啥会这样,然后看看有哪些靠谱的办法能实现 WinPE 启动时,所有目标磁盘(除了 WinPE 自己运行的 X: 盘)都稳稳地处于只读状态。

一、为啥想让磁盘只读这么难?

简单来说,WinPE 本身也是个迷你 Windows 系统。它启动过程中,需要识别和初始化硬件,包括磁盘。这里面有几个坎:

  1. 自动挂载机制 :默认情况下,WinPE 会尝试挂载它识别到的所有可用分区,特别是那些带有活动 Windows 安装的分区。这就可能导致在你的脚本介入之前,系统进程(比如你看到的 TXF 日志)就已经开始在磁盘上“活动”了。虽然设置 SAN Policy 为 3 (OfflineInternal) 可以阻止内部磁盘自动挂载,解决了这个问题的一部分。
  2. “关键磁盘”保护 :Windows 有一套保护机制,防止你轻易地把系统运行所必需的磁盘(比如包含活动页面文件、系统休眠文件、或者被判定为引导/系统重要性的磁盘)设为只读或脱机。当 Set-Diskdiskpart 告诉你这是个“关键磁盘”时,就是这个机制在起作用。即使在 WinPE 环境下,如果某个磁盘在某个时刻被系统认定为关键,它就会拒绝你的只读请求。
  3. 操作时机 :你想在 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)这些已经是只读的磁盘卷。

操作步骤:

  1. 离线修改 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 的文件,内容如下:
      <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>
      
      (请根据你 WinPE 的架构修改 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。

  2. 编写 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 后续操作...
    

    确保脚本有足够的健壮性来处理各种磁盘状态和可能的错误。

关于“关键磁盘”:
使用 SAN Policy 3 后,内部磁盘默认是 Offline 状态,它们通常不会被 WinPE 初始化过程标记为“关键”。只有当你尝试 online 并设置只读时,系统才会重新评估。理论上,只要这个磁盘上没有 WinPE 运行依赖的关键组件(比如 WinPE 不是从这个磁盘启动的,没有页面文件等),Set-Diskattributes disk set readonly 应该能成功。如果你依然遇到“关键磁盘”错误,很可能是这个磁盘包含了一些被 WinPE 认为重要的分区(即使未挂载),或者是其他特殊硬件配置导致。

回答你的问题 (针对此方案):

  • Q1 (绕过关键磁盘): 这个方案主要通过 SAN Policy 提前阻止磁盘上线,来 规避 磁盘被标记为“关键”的可能性。它不是直接 绕过 保护机制,而是创造条件让机制不触发。如果某个磁盘因特殊原因(如硬件RAID 控制器信息、预留分区等)仍然被标记为关键,此方法可能依然失败。
  • Q2 (脚本执行时机与意外写入): 因为 SAN Policy 3 保证了磁盘在 wpeinit 后仍处于 Offline 状态,所以在你的 Startnet.cmd 脚本执行 Set-Diskattributes 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 (或不存在): 禁用写入保护(可写)。

操作步骤:

  1. 挂载 WinPE WIM 文件 (同方案一的第一步)。
  2. 加载 WinPE 镜像的 SYSTEM 注册表 Hive:
    reg load HKLM\WinPE_SYSTEM "C:\WinPE_Mount\Windows\System32\config\SYSTEM"
    
    (这里 WinPE_SYSTEM 是一个临时的挂载点名称,可以自定义)
  3. 修改或创建 WriteProtect 值:
    reg add HKLM\WinPE_SYSTEM\CurrentControlSet\Control\StorageDevicePolicies /v WriteProtect /t REG_DWORD /d 1 /f
    
    (如果 StorageDevicePolicies 项本身不存在,reg add 会自动创建它。/f 参数强制覆盖现有值)
  4. 卸载注册表 Hive:
    reg unload HKLM\WinPE_SYSTEM
    
  5. 卸载并保存 WIM 镜像 (同方案一的最后一步)。

效果:
用这个修改过的 WinPE 启动后,所有被 StorageDevicePolicies 影响的磁盘(通常是所有固定磁盘和移动磁盘)在系统层面就会被视为只读。diskpart 查看 attributes disk 会直接显示 Current Read-only State : Yes。此时你再运行 Get-Diskdiskpart,它们会报告磁盘已经是只读的。

关于“关键磁盘”:
这个方法是在更底层实现的写入保护,它直接作用于存储堆栈,早于“关键磁盘”逻辑的判断。因此,它能有效地“绕过”因为“关键磁盘”状态而无法设置只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”,方案二(修改注册表)似乎更能满足你的苛刻要求。它提供了一种更强的保证,确保在你的任何脚本或工具介入之前,目标磁盘就已经处于硬件(由驱动执行)层面的只读状态。