From 10f97246c87439cc679020ad148da7502d053538 Mon Sep 17 00:00:00 2001 From: Shuery <2463253700@qq.com> Date: Sun, 27 Sep 2026 09:22:45 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20lab=20=E7=9A=84=20ACL=20=E5=9C=BA?= =?UTF-8?q?=E6=99=AF=E5=9C=A8=205.1=20=E4=B8=8A=E4=BC=9A=E8=A2=AB=E5=8E=9F?= =?UTF-8?q?=E7=94=9F=E5=91=BD=E4=BB=A4=E7=9A=84=20stderr=20=E4=B8=AD?= =?UTF-8?q?=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tools\lab\payload\run-acl-scenario.ps1 是 $ErrorActionPreference = Stop,而它要大量调用 rmdir / takeown / icacls / scoop / code —— 其中 rmdir 与 takeown 在"对象已经处理过"或 "连接点已经悬空"时就会往 stderr 写字。Windows PowerShell 5.1 会把那升级成终止性的 NativeCommandError(7 改了这条规矩),于是清理函数在设计要处理的**恰恰那个场景**里直接崩掉。 修法:新增 Invoke-NativeTolerant,在进入原生命令时把 EAP 放到 Continue、退出时还原, 把 stdout 与 stderr 合并返回;12 处调用全部收进它。判断"删掉了没有"本来就该用 Test-Path, 不需要让 stderr 变成异常。这与 tests\BakNRet.Security.Tests.ps1 里对 takeown / icacls 用的是同一招(那里已被 5.1 的失败实测逼出来过)。 范围说明:只改了 run-acl-scenario.ps1。provision.ps1 的同类写法**不需要**改 —— 它是 $ErrorActionPreference = Continue,那两处本来就不会踩。 验证边界(如实说明):这个文件要 Hyper-V + 一台 VM 才跑得到,本会话无法执行它。 所以这一步的证据是:两个版本上解析零错(Parse 层覆盖)、12 处替换逐条断言命中、以及 diff 逐行复核。它是"降低风险",不是"已验证修复"。 验收:test.ps1 9/9 全绿(7 与 5.1);Run-RealSmoke 4/4 全绿。 --- tools/lab/payload/run-acl-scenario.ps1 | 49 ++++++++++++++++++++------ 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/tools/lab/payload/run-acl-scenario.ps1 b/tools/lab/payload/run-acl-scenario.ps1 index 2f7416a..67eec85 100644 --- a/tools/lab/payload/run-acl-scenario.ps1 +++ b/tools/lab/payload/run-acl-scenario.ps1 @@ -43,6 +43,33 @@ Import-Module (Join-Path $RepoPath 'Common.psm1') -Force $script:Passed = 0 $script:Failures = @() +function Invoke-NativeTolerant { + <# + .SYNOPSIS + 跑一个原生命令,把 stdout 与 stderr 合并成字符串数组返回,不让 stderr 变成错误。 + + .DESCRIPTION + Windows PowerShell 5.1 在 $ErrorActionPreference = 'Stop' 下会把原生命令写到 + stderr 的内容升级成终止性的 NativeCommandError(PowerShell 7 改了这条规矩)。 + + 本脚本要调用 rmdir / takeown / icacls / scoop / code,其中 rmdir 与 takeown 在 + "对象已经处理过"或"连接点已经悬空"时就会往 stderr 写字 —— 那些话不是错误:真正该 + 判断的是"删掉了没有",用 Test-Path 看。所以在进入原生命令时把 EAP 放到 Continue, + 退出时还原。这也是 tests\BakNRet.Security.Tests.ps1 里对 takeown / icacls 用的同一招。 + #> + param( + [Parameter(Mandatory = $true)][string]$FilePath, + [string[]]$ArgumentList = @() + ) + + $previous = $ErrorActionPreference + $ErrorActionPreference = 'Continue' + try { + return @(& $FilePath @ArgumentList 2>&1) + } finally { + $ErrorActionPreference = $previous + } +} function Test-Scenario { param([Parameter(Mandatory = $true)][string]$Name, [bool]$Ok, [string]$Detail = '') if ($Ok) { @@ -159,17 +186,17 @@ function Remove-TreeHard { $links = @(Get-ChildItem -LiteralPath $Path -Recurse -Force -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint }) foreach ($link in $links) { - & cmd.exe /c ('rmdir "{0}"' -f $link.FullName) 2>&1 | Out-Null + $null = Invoke-NativeTolerant -FilePath 'cmd.exe' -ArgumentList @('/c', ('rmdir "{0}"' -f $link.FullName)) Remove-BaknretJunction -Path $link.FullName } - & cmd.exe /c ('rmdir /s /q "{0}"' -f $Path) 2>&1 | Out-Null + $null = Invoke-NativeTolerant -FilePath 'cmd.exe' -ArgumentList @('/c', ('rmdir /s /q "{0}"' -f $Path)) if (Test-Path -LiteralPath $Path) { # 只处理这一条路径:拿回属主 → 换成继承来的默认 ACL → 再删 - & takeown.exe /F $Path /R /D Y 2>&1 | Out-Null - & icacls.exe $Path /reset /T /C /Q 2>&1 | Out-Null - & cmd.exe /c ('rmdir /s /q "{0}"' -f $Path) 2>&1 | Out-Null + $null = Invoke-NativeTolerant -FilePath 'takeown.exe' -ArgumentList @('/F', $Path, '/R', '/D', 'Y') + $null = Invoke-NativeTolerant -FilePath 'icacls.exe' -ArgumentList @($Path, '/reset', '/T', '/C', '/Q') + $null = Invoke-NativeTolerant -FilePath 'cmd.exe' -ArgumentList @('/c', ('rmdir /s /q "{0}"' -f $Path)) Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue } } @@ -246,21 +273,21 @@ if (-not $SkipScoop) { # 这台 VM 出厂不带 git,所以 main bucket 只能先用 zip 兜底进来,git 再由此装上。 if ((Test-Path -LiteralPath $scoopCmd) -and -not (Test-Path -LiteralPath (Join-Path $scoopRoot 'shims\git.exe'))) { Write-Host '[A] scoop install git(后面 bucket add 要靠它)' -ForegroundColor Yellow - & $scoopCmd install git 2>&1 | ForEach-Object { ' ' + $_ } + Invoke-NativeTolerant -FilePath $scoopCmd -ArgumentList @('install', 'git') | ForEach-Object { ' ' + $_ } } # vscode 在 extras bucket,不在 main 里 if ((Test-Path -LiteralPath $scoopCmd) -and -not (Test-Path -LiteralPath (Join-Path $scoopRoot 'buckets\extras'))) { Write-Host '[A] scoop bucket add extras' -ForegroundColor Yellow - & $scoopCmd bucket add extras 2>&1 | ForEach-Object { ' ' + $_ } + Invoke-NativeTolerant -FilePath $scoopCmd -ArgumentList @('bucket', 'add', 'extras') | ForEach-Object { ' ' + $_ } } if ((Test-Path -LiteralPath $scoopCmd) -and -not (Test-Path -LiteralPath $vscodeCli)) { Write-Host '[A] scoop install vscode(从 extras 下载几百 MB,慢是正常的)' -ForegroundColor Yellow - & $scoopCmd install vscode 2>&1 | ForEach-Object { ' ' + $_ } + Invoke-NativeTolerant -FilePath $scoopCmd -ArgumentList @('install', 'vscode') | ForEach-Object { ' ' + $_ } if (-not (Test-Path -LiteralPath $vscodeCli)) { Write-Host '[A] 第一次没装上,重试一次(下载超时是常见原因)' -ForegroundColor Yellow - & $scoopCmd install vscode 2>&1 | ForEach-Object { ' ' + $_ } + Invoke-NativeTolerant -FilePath $scoopCmd -ArgumentList @('install', 'vscode') | ForEach-Object { ' ' + $_ } } } } @@ -282,7 +309,7 @@ if ($vscodeReady) { $probe = 'baknret-' + [guid]::NewGuid().ToString('N').Substring(0, 8) $settingsPath = $null if ($vscodeReady) { - $versionText = (& $codeCmd --version 2>&1 | Out-String).Trim() + $versionText = (Invoke-NativeTolerant -FilePath $codeCmd -ArgumentList @('--version') | Out-String).Trim() Test-Scenario 'A: vscode CLI 可执行(--version)' ($LASTEXITCODE -eq 0) ($versionText -split "`n" | Select-Object -First 1) # scoop 的 vscode 清单带 persist:data,用户数据落在 persist 里(portable 模式); @@ -401,7 +428,7 @@ Test-Scenario '恢复日志里出现安全描述符回放' ($restore.Output -mat Write-Host '' Write-Host '--- A 断言 ---' -ForegroundColor Cyan if ($vscodeReady) { - $versionText = (& $codeCmd --version 2>&1 | Out-String).Trim() + $versionText = (Invoke-NativeTolerant -FilePath $codeCmd -ArgumentList @('--version') | Out-String).Trim() Test-Scenario 'A: 恢复后 vscode CLI 仍可执行' ($LASTEXITCODE -eq 0) ($versionText -split "`n" | Select-Object -First 1) $settingsOk = $false