Files
BakNRet/BakNRet/Public/Get-BaknretSecurityRecord.ps1
T
Shuery 42f02d0eca refactor: 公共面补 BakNRet 前缀,产品名大小写全仓统一
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 行)。
2026-09-27 09:56:36 +08:00

103 lines
3.9 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-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
}