Files
BakNRet/BakNRet/Public/Get-BaknretSecurityRecords.ps1
T
Shuery 232a82cd3c style: 逐条修静态分析告警(706 → 44),并把 MaxDepth 这条假承诺删掉
修掉的:空 catch 5 处;名词白名单 7 处;default-value 开关、自带 -WhatIf、lab 的明文口令与 irm|iex 各挂抑制并写明理由。

MaxDepth:它是分析器拓出来的真 bug —— 参数声明了却从未使用,也就是配置里的 CatalogMaxDepth 是假的,前缀补全实际只查 1 层,而配置注释与 README 都承诺「向下找几层」。按确认过的原则处理:**先让文档不撒谎**,所以把整条链路去掉(配置默认值、三个函数的参数、70 处实参、配置注释),而不是留一个假旋钮。零行为变化。想真的支持多层补全时,那是一个独立决定。

剩下 3 条都是分析器的误判,而且我实测确认过其中一条:$sourcePath 被报「赋值后从未使用」,我照着改成 $null = 之后,Set-StrictMode -Version 3.0 下读未定义变量直接抛错,Security 套件的 BeforeAll 挂掉、4 条用例连带失败。恢复后才绿。

这一类误判有共同成因:静态分析看不到「在传给 Test-Case / It / Where-Object 的 scriptblock 里被使用」。所以我只对能证明是误判的挂抑制并写明理由,不为了数字好看去改代码。

验收:test.ps1 9/9 全绿(7 与 5.1)、100 个文件两版解析零错、Run-RealSmoke 4/4。
2026-09-27 10:16:10 +08:00

128 lines
5.6 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
function Get-BakNRetSecurityRecords {
<#
.SYNOPSIS
采集一组归档项的安全描述符,键是**归档内相对路径**(`<Slot>\…`)。
.DESCRIPTION
键用归档内路径而不是宿主机路径:目标机器上 `%UserProfile%` 会变、名录的前缀补全
(legendary -> legendary_2.0.4)也会变,只有归档内相对路径在两端是同一个坐标系。
遍历用显式栈,并且**跳过 reparse point**:PS 5.1 的 Get-ChildItem -Recurse 会
跟着 junction 无限转;scoop 的 `apps\<app>\current` 就是 junction,正撞在这个坑上。
$ScopeMap 由 Split-BakNRetPatternScope 产出(项下标 -> 该相对根的模式数组),
所以这里的排除判定与真正交给 7z 的 -x! / -xr! 是同一套规则。
Mode:
* Roots —— 只存每个归档项的根(最省,适合"权限只在根上"的场景)
* Smart —— 根 + 所有"继承复现不出来"的对象(默认;几万文件的树 sidecar 也只有几百 KB)
* Full —— 每一个对象都存(最保险,sidecar 会大到几 MB)
返回 [pscustomobject]@{ Records; Scanned; Kept; Errors }。
#>
[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '',
Justification = '同上:它采集一整棵树的记录,单数名是「读单个对象」的那个函数。')]
param(
[array]$Items = @(),
[hashtable]$ScopeMap = @{},
[ValidateSet('Roots', 'Smart', 'Full')][string]$Mode = 'Smart',
[switch]$IncludeSacl
)
$records = New-Object System.Collections.Generic.List[object]
$scanned = 0
$errorCount = 0
# 读安全描述符要 READ_CONTROL:系统目录里读不到是常态(C:\ProgramData 下就有
# Get-Acl 直接报 "Attempted to perform an unauthorized operation" 的目录)。
# SeBackupPrivilege 启用后系统会把读权限授予任何文件;连它都没有的账户,
# 读不到的对象会带 e 字段落进 sidecar,而不是被静默当成"没有特殊权限"。
$privileges = @('SeBackupPrivilege')
if ($IncludeSacl) { $privileges += 'SeSecurityPrivilege' }
Enable-BakNRetPrivilege -Name $privileges | Out-Null
for ($index = 0; $index -lt $Items.Count; $index++) {
$item = $Items[$index]
if (-not $item) { continue }
$archiveRoot = [string]$item.ArchivePath
$real = [string]$item.RealPath
if ([string]::IsNullOrWhiteSpace($archiveRoot) -or [string]::IsNullOrWhiteSpace($real)) { continue }
if (-not (Test-Path -LiteralPath $real)) { continue }
$patterns = @()
if ($ScopeMap -and $ScopeMap.ContainsKey($index)) { $patterns = @($ScopeMap[$index]) }
$rootItem = Get-Item -LiteralPath $real -Force -ErrorAction SilentlyContinue
if (-not $rootItem) { continue }
if (-not $rootItem.PSIsContainer) {
$record = Get-BakNRetSecurityRecord -Path $real -Key $archiveRoot -Kind 'f' -IncludeSacl:$IncludeSacl
$scanned++
if ($record.e) { $errorCount++ }
$records.Add($record)
continue
}
$rootRecord = Get-BakNRetSecurityRecord -Path $real -Key $archiveRoot -Kind 'd' -IncludeSacl:$IncludeSacl
$scanned++
if ($rootRecord.e) { $errorCount++ }
$records.Add($rootRecord)
if ($Mode -eq 'Roots') { continue }
$pending = New-Object System.Collections.Generic.Stack[object]
$pending.Push(@{
Dir = $rootItem
Rel = ''
Owner = $rootRecord.o
Group = $rootRecord.g
Inheritable = $rootRecord.Inheritable
Signatures = $rootRecord.AllSignatures
})
while ($pending.Count -gt 0) {
$frame = $pending.Pop()
foreach ($child in @(Get-ChildItem -LiteralPath $frame.Dir.FullName -Force -ErrorAction SilentlyContinue)) {
if ($child.Attributes -band [IO.FileAttributes]::ReparsePoint) { continue }
$childRel = if ($frame.Rel) { $frame.Rel + '\' + $child.Name } else { $child.Name }
if (Test-BakNRetPathExcluded -RelativePath $childRel -Patterns $patterns) { continue }
$kind = if ($child.PSIsContainer) { 'd' } else { 'f' }
$record = Get-BakNRetSecurityRecord -Path $child.FullName -Key ($archiveRoot + '\' + $childRel) `
-Kind $kind -IncludeSacl:$IncludeSacl -ParentSignatures $frame.Signatures
$scanned++
if ($record.e) { $errorCount++ }
if ($Mode -eq 'Full') {
$records.Add($record)
}
elseif (Test-BakNRetSecurityRecordNeeded -Record $record `
-ParentOwner $frame.Owner -ParentGroup $frame.Group `
-ParentInheritable $frame.Inheritable -ParentSignatures $frame.Signatures) {
$records.Add($record)
}
if ($child.PSIsContainer) {
$pending.Push(@{
Dir = $child
Rel = $childRel
Owner = $record.o
Group = $record.g
Inheritable = $record.Inheritable
Signatures = $record.AllSignatures
})
}
}
}
}
return [pscustomobject]@{
Records = @($records.ToArray())
Scanned = $scanned
Kept = $records.Count
Errors = $errorCount
}
}