<# .SYNOPSIS BakNRet 隔离测试 VM 的首次登录供给脚本(由 unattend.xml 的 FirstLogonCommands 调用)。 .DESCRIPTION 运行环境是 VM 内全新安装的 Windows 11(Windows PowerShell 5.1、管理员 lab 账户)。 目标:把 VM 变成「可以直接跑 BakNRet 全链路测试」的真机状态: 1. 电源 / 休眠 / 锁屏:测试期间不要因为空闲睡下去; 2. 执行策略 Bypass(仅此实验 VM); 3. 把注入的 7-Zip 与 PowerShell 7 放到机器 PATH 上,与宿主机的工具版本对齐; 4. 安装 Pester 5.9.1(Windows PowerShell 与 pwsh 两侧都能导入); 5. 不让 Windows Update 自动重启,并给实验目录加 Defender 排除 (避免杀软把「文件被占用」类用例变成随机失败,同时显著拖慢压缩); 6. 关掉首次登录后的 SCOOBE「完成设备设置」向导; 7. 写出 state\provisioned.json 与 state\provision.ok 作为「供给完成」的硬凭据。 幂等:可重复执行,第二次跑不会失败。 #> $ErrorActionPreference = 'Continue' $ProgressPreference = 'SilentlyContinue' $lab = 'C:\BakNRet-Lab' $logDir = Join-Path $lab 'logs' $stateDir = Join-Path $lab 'state' New-Item -ItemType Directory -Force -Path $logDir, $stateDir | Out-Null Start-Transcript -Path (Join-Path $logDir 'provision.log') -Force | Out-Null function Step($m) { Write-Host "==> $m" } try { Step '1/7 电源与显示:不休眠、不锁屏、关休眠' powercfg /change standby-timeout-ac 0 | Out-Null powercfg /change monitor-timeout-ac 0 | Out-Null powercfg /change hibernate-timeout-ac 0 | Out-Null powercfg /hibernate off | Out-Null Step '2/7 执行策略:LocalMachine = Bypass(仅此实验 VM)' Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Bypass -Force Step '3/7 工具上机器 PATH:7-Zip 与 PowerShell 7' $zipSrc = Join-Path $lab 'payload\7zip' $zipDst = 'C:\Program Files\7-Zip' $pwshSrc = Join-Path $lab 'payload\pwsh' $pwshDst = 'C:\Program Files\PowerShell\7' if (-not (Test-Path $zipDst)) { robocopy $zipSrc $zipDst /MIR /NFL /NDL /NJH /NJS /NP | Out-Null } if (-not (Test-Path $pwshDst)) { robocopy $pwshSrc $pwshDst /MIR /NFL /NDL /NJH /NJS /NP | Out-Null } $machinePath = [Environment]::GetEnvironmentVariable('Path', 'Machine') foreach ($p in @($zipDst, $pwshDst)) { if ($machinePath -notlike "*$p*") { $machinePath = $machinePath.TrimEnd(';') + ';' + $p } if ($env:Path -notlike "*$p*") { $env:Path = $env:Path.TrimEnd(';') + ';' + $p } } [Environment]::SetEnvironmentVariable('Path', $machinePath, 'Machine') Step '4/7 安装 Pester 5.9.1(Windows PowerShell 与 pwsh 各一份)' $pesterSrc = Join-Path $lab 'payload\Pester\5.9.1' foreach ($dst in @("$env:ProgramFiles\WindowsPowerShell\Modules\Pester\5.9.1", "$env:ProgramFiles\PowerShell\Modules\Pester\5.9.1")) { if (-not (Test-Path $dst)) { robocopy $pesterSrc $dst /MIR /NFL /NDL /NJH /NJS /NP | Out-Null } } Step '5/7 Windows Update 不自动重启 + Defender 排除实验目录' $wu = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU' New-Item -Path $wu -Force | Out-Null New-ItemProperty -Path $wu -Name 'NoAutoRebootWithLoggedOnUsers' -Value 1 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path $wu -Name 'AUOptions' -Value 2 -PropertyType DWord -Force | Out-Null New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Power' -Name 'HiberbootEnabled' -Value 0 -PropertyType DWord -Force | Out-Null Add-MpPreference -ExclusionPath 'C:\BakNRet', 'C:\BakNRet-Lab' -ErrorAction SilentlyContinue Step '6/7 关掉 SCOOBE「完成设备设置」' $scoobe = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\UserProfileEngagement' New-Item -Path $scoobe -Force | Out-Null New-ItemProperty -Path $scoobe -Name 'ScoobeSystemSettingEnabled' -Value 0 -PropertyType DWord -Force | Out-Null Step '7/7 采集真机事实并落盘' $zipExe = Join-Path $zipDst '7z.exe' $pwshExe = Join-Path $pwshDst 'pwsh.exe' $pwshVer = '缺失' if (Test-Path $pwshExe) { $pwshVer = (& $pwshExe -NoProfile -Command '$PSVersionTable.PSVersion.ToString()' 2>&1) -join ' ' } $zipVer = '缺失' if (Test-Path $zipExe) { $zipVer = (& $zipExe 2>&1 | Select-Object -First 2) -join ' / ' } $facts = [ordered]@{ ProvisionedAt = (Get-Date).ToString('s') ComputerName = $env:COMPUTERNAME User = (whoami) IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) OsCaption = (Get-CimInstance Win32_OperatingSystem).Caption OsVersion = (Get-CimInstance Win32_OperatingSystem).Version OsBuild = (Get-CimInstance Win32_OperatingSystem).BuildNumber OsArch = (Get-CimInstance Win32_OperatingSystem).OSArchitecture WindowsPS = $PSVersionTable.PSVersion.ToString() SevenZipVersion = $zipVer PwshVersion = $pwshVer PesterVersion = (Get-Module -ListAvailable Pester -ErrorAction SilentlyContinue | Select-Object -First 1).Version.ToString() PathHasSevenZip = ([Environment]::GetEnvironmentVariable('Path', 'Machine')) -like '*7-Zip*' PathHasPwsh = ([Environment]::GetEnvironmentVariable('Path', 'Machine')) -like '*PowerShell\7*' CpuCount = (Get-CimInstance Win32_ComputerSystem).NumberOfLogicalProcessors RamGB = [math]::Round((Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory / 1GB, 1) Drives = @(Get-Volume | Where-Object DriveLetter | ForEach-Object { [ordered]@{ Letter = "$($_.DriveLetter):"; Fs = $_.FileSystemType; SizeGB = [math]::Round($_.Size / 1GB, 1) } }) } $facts | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath (Join-Path $stateDir 'provisioned.json') -Encoding UTF8 $facts.GetEnumerator() | ForEach-Object { Write-Host (" {0,-16} {1}" -f $_.Key, $_.Value) } 'provision-ok' | Set-Content -LiteralPath (Join-Path $stateDir 'provision.ok') -Encoding ASCII Write-Host '==> 供给完成' } catch { Write-Host ("供给失败:" + $_.Exception.Message) -ForegroundColor Red ("provision-FAILED: " + $_.Exception.Message) | Set-Content -LiteralPath (Join-Path $stateDir 'provision.FAILED') -Encoding UTF8 } finally { Stop-Transcript | Out-Null }