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 行)。
48 lines
2.3 KiB
PowerShell
48 lines
2.3 KiB
PowerShell
function Test-BakNRetSecurityRecordNeeded {
|
||
<#
|
||
.SYNOPSIS
|
||
Smart 模式下判断这条记录是否必须落进 sidecar。
|
||
|
||
.DESCRIPTION
|
||
判据是"恢复时不能被继承自动复现",任何一条成立就得留:
|
||
|
||
* 读不到(e)—— 必须记账,恢复时要能报出来;
|
||
* DACL 是 protected(断开继承)—— 只靠父目录继承永远复现不出这一套;
|
||
* 有显式 ACE(Explicit > 0)—— 同上;
|
||
* NULL DACL(NO_ACCESS_CONTROL)—— 那不是"没有特殊权限",是"人人全权";
|
||
* 属主 / 属组与父目录不同 —— CREATOR OWNER 的解析结果就取决于属主;
|
||
* 继承链路与父目录脱节 —— 条数对不上,或某条继承来的 ACE 在父目录 ACL 里
|
||
找不到出处(父目录改过权限、子对象还留着老 ACE);空 DACL 也会在这里露出来。
|
||
|
||
分析不了(Analyzed=$false)时一律保留:多存永远比少存安全。
|
||
#>
|
||
param(
|
||
[Parameter(Mandatory = $true)]$Record,
|
||
[string]$ParentOwner,
|
||
[string]$ParentGroup,
|
||
[int]$ParentInheritable = -1,
|
||
[string[]]$ParentSignatures = @(),
|
||
[switch]$Force
|
||
)
|
||
|
||
if ($Force) { return $true }
|
||
if ($Record.e) { return $true }
|
||
if (-not $Record.s) { return $true }
|
||
if (-not $Record.Analyzed) { return $true }
|
||
if ($Record.Protected) { return $true }
|
||
if ($Record.Explicit -gt 0) { return $true }
|
||
if ($Record.s -match 'NO_ACCESS_CONTROL') { return $true }
|
||
if ($Record.o -and $ParentOwner -and ($Record.o -ne $ParentOwner)) { return $true }
|
||
if ($Record.g -and $ParentGroup -and ($Record.g -ne $ParentGroup)) { return $true }
|
||
|
||
# 继承链还接不接得上父目录:先比条数,再比每一条在父目录 ACL 里有没有出处。
|
||
# 只比条数会漏判 —— 真机实测过:子对象留着"改权限之前"的老 ACE,
|
||
# 条数与父目录可继承条数正好相等(都 4 条),内容却完全不同。
|
||
if ($ParentInheritable -ge 0 -and $Record.Inherited -ne $ParentInheritable) { return $true }
|
||
foreach ($signature in @($Record.InheritedSignatures)) {
|
||
if ($ParentSignatures -notcontains $signature) { return $true }
|
||
}
|
||
|
||
return $false
|
||
}
|