16 个没有前缀的公共函数补上 BakNRet(Write-Log → Write-BakNRetLog、Resolve-BackupEntry → Resolve-BakNRetBackupEntry、Find-ChildDirectoryByName → Find-BakNRetChildDirectoryByName 等), 另外把全仓的 Baknret 统一成 BakNRet(47 个文件、940 处、65 个定义文件重命名)。 这不是审美问题:静态分析直接拓出一条实据 —— Write-Log 与本机某个已装模块导出的命令 **重名**(PSAvoidOverwritingBuiltInCmdlets),而重名的后果是导入两个模块时有一方的命令被 静默遮蔽。补前缀正是这条规则的解法,改名后它归零。 为什么敢做这个规模:PowerShell 的函数名解析大小写不敏感,所以 Baknret → BakNRet 在功能 上是零风险;真正要验证的是 16 个补前缀的调用点,而 276 个断言几乎覆盖了每个函数。另外 "名字与文件名一致"这条不变式有断言盯着(加载器点源的文件集合 vs 磁盘)。 踩到并记下的坑:Windows 文件系统大小写不敏感,所以**只改大小写**的重命名会被 Move-Item 当成同一个文件而静默跳过 —— 同一批里同时改了名字的那 16 个文件却成功了,于是"看起来能跑"。 最后用"先移到临时名、再移到目标名"的两步走解决,判断与替换全部改用显式大小写敏感的形式 (-creplace / -cmatch)。 顺带把名录指纹缓存从 MD5 换成 SHA256(PSAvoidUsingBrokenHashAlgorithms):它只是缓存键, 没有兼容负担。 验收:test.ps1 9/9 全绿(7 与 5.1)、100 个文件两版解析零错、276 个断言全过、 构建工具仍能合回单文件(3300 行)。
103 lines
3.9 KiB
PowerShell
103 lines
3.9 KiB
PowerShell
function Get-BakNRetSecurityRecord {
|
||
<#
|
||
.SYNOPSIS
|
||
读一个对象的安全描述符,产出可序列化的一条记录。
|
||
|
||
.DESCRIPTION
|
||
返回 [pscustomobject]:
|
||
p / k 归档内相对路径 / 类型(d 目录、f 文件)
|
||
s SDDL 原文(含 O: / G: / D:)
|
||
o / g 属主 / 属组 SID 字符串
|
||
e 读不到时的错误(**必须记账**,不能当成"没有特殊权限")
|
||
Protected / Explicit / Inherited / Inheritable / Analyzed
|
||
Smart 模式判断"是否与父目录不同"用的分析结果
|
||
|
||
属主/属组一律取 SID 字符串(GetOwner(SecurityIdentifier).Value):
|
||
走 .Owner 会触发账户名解析,孤儿 SID 上会抛异常或很慢,而我们只要数值身份。
|
||
|
||
读 SD 需要 READ_CONTROL;C:\ProgramData 里确实有 Get-Acl 直接报
|
||
"Attempted to perform an unauthorized operation" 的目录,先开 SeBackupPrivilege
|
||
能救回大部分,救不回的会带 e 字段落进 sidecar。
|
||
#>
|
||
param(
|
||
[Parameter(Mandatory = $true)][string]$Path,
|
||
[Parameter(Mandatory = $true)][string]$Key,
|
||
[ValidateSet('d', 'f')][string]$Kind = 'd',
|
||
[switch]$IncludeSacl,
|
||
[AllowNull()][string[]]$ParentSignatures = $null
|
||
)
|
||
|
||
$record = [pscustomobject]@{
|
||
p = $Key
|
||
k = $Kind
|
||
s = $null
|
||
o = $null
|
||
g = $null
|
||
e = $null
|
||
Protected = $false
|
||
Explicit = 0
|
||
Inherited = 0
|
||
Inheritable = 0
|
||
InheritedSignatures = @()
|
||
AllSignatures = @()
|
||
Analyzed = $false
|
||
}
|
||
|
||
$acl = $null
|
||
try {
|
||
if ($IncludeSacl) {
|
||
$acl = Get-Acl -LiteralPath $Path -Audit -ErrorAction Stop
|
||
}
|
||
else {
|
||
$acl = Get-Acl -LiteralPath $Path -ErrorAction Stop
|
||
}
|
||
}
|
||
catch {
|
||
$record.e = $_.Exception.Message
|
||
return $record
|
||
}
|
||
|
||
try {
|
||
# 陈旧继承 ACE 要固化成显式 ACE,否则恢复后会消失(见 Get-BakNRetSecuritySddlWithStale)
|
||
$record.s = Get-BakNRetSecuritySddlWithStale -Acl $acl -ParentSignatures $ParentSignatures
|
||
}
|
||
catch {
|
||
$record.e = $_.Exception.Message
|
||
}
|
||
if (-not $record.s) {
|
||
if (-not $record.e) { $record.e = '读不到安全描述符' }
|
||
return $record
|
||
}
|
||
|
||
try { $record.o = $acl.GetOwner([System.Security.Principal.SecurityIdentifier]).Value } catch { }
|
||
try { $record.g = $acl.GetGroup([System.Security.Principal.SecurityIdentifier]).Value } catch { }
|
||
|
||
try {
|
||
$sid = [System.Security.Principal.SecurityIdentifier]
|
||
$record.Protected = [bool]$acl.AreAccessRulesProtected
|
||
|
||
$explicitRules = @($acl.GetAccessRules($true, $false, $sid))
|
||
$inheritedRules = @($acl.GetAccessRules($false, $true, $sid))
|
||
$record.Explicit = $explicitRules.Count
|
||
$record.Inherited = $inheritedRules.Count
|
||
$record.InheritedSignatures = @(Get-BakNRetAceSignatureList -Rules $inheritedRules)
|
||
$record.AllSignatures = @(Get-BakNRetAceSignatureList -Rules @($acl.GetAccessRules($true, $true, $sid)))
|
||
|
||
$inheritable = 0
|
||
foreach ($rule in @($acl.GetAccessRules($true, $true, $sid))) {
|
||
$fsRule = $rule -as [System.Security.AccessControl.FileSystemAccessRule]
|
||
if ($fsRule -and ($fsRule.InheritanceFlags -ne [System.Security.AccessControl.InheritanceFlags]::None)) {
|
||
$inheritable++
|
||
}
|
||
}
|
||
$record.Inheritable = $inheritable
|
||
$record.Analyzed = $true
|
||
}
|
||
catch {
|
||
# 分析失败时一律当成"需要保留"(Analyzed=$false 会让 keeper 直接保留)
|
||
$record.Analyzed = $false
|
||
}
|
||
|
||
return $record
|
||
}
|