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