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 行)。
This commit is contained in:
1 parent
187d2759fd
commit
42f02d0eca
84 files changed
+1043
-1043
No files matched your search
@@ -118,7 +118,7 @@ BeforeAll {
|
||||
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
function Invoke-BaknretScript {
|
||||
function Invoke-BakNRetScript {
|
||||
<# .SYNOPSIS 用子进程跑 Backup.ps1 / Restore.ps1(两个脚本结尾都 exit,必须独立进程)。 #>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Script,
|
||||
@@ -224,31 +224,31 @@ AfterAll {
|
||||
Describe '排除判定与 7z 的 -x! / -xr! 语义对齐' {
|
||||
# ============================================================================
|
||||
It '锚定模式只命中它自己那棵子树' {
|
||||
Test-BaknretPathExcluded -RelativePath 'Default\Cache' -Patterns @('Default\Cache') | Should -BeTrue
|
||||
Test-BaknretPathExcluded -RelativePath 'Default\Cache\sub\x.bin' -Patterns @('Default\Cache') | Should -BeFalse
|
||||
Test-BaknretPathExcluded -RelativePath 'Other\Cache' -Patterns @('Default\Cache') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'Default\Cache' -Patterns @('Default\Cache') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'Default\Cache\sub\x.bin' -Patterns @('Default\Cache') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'Other\Cache' -Patterns @('Default\Cache') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '! 通配按任意层级的组件名匹配(* 不是正则)' {
|
||||
Test-BaknretPathExcluded -RelativePath 'a\Code Cache\f' -Patterns @('!*Cache') | Should -BeTrue
|
||||
Test-BaknretPathExcluded -RelativePath 'Code Cache' -Patterns @('!*Cache') | Should -BeTrue
|
||||
Test-BaknretPathExcluded -RelativePath 'a\teamcache.bin' -Patterns @('!*Cache') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'a\Code Cache\f' -Patterns @('!*Cache') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'Code Cache' -Patterns @('!*Cache') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'a\teamcache.bin' -Patterns @('!*Cache') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '!re: 走正则,且组件名与整条相对路径都算命中' {
|
||||
Test-BaknretPathExcluded -RelativePath 'x\y\a.log' -Patterns @('!re:\.log$') | Should -BeTrue
|
||||
Test-BaknretPathExcluded -RelativePath 'x\y\a.txt' -Patterns @('!re:\.log$') | Should -BeFalse
|
||||
Test-BaknretPathExcluded -RelativePath 'Cache\data' -Patterns @('!re:^Cache$') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'x\y\a.log' -Patterns @('!re:\.log$') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'x\y\a.txt' -Patterns @('!re:\.log$') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'Cache\data' -Patterns @('!re:^Cache$') | Should -BeTrue
|
||||
}
|
||||
|
||||
It '没有模式时一律不排除' {
|
||||
Test-BaknretPathExcluded -RelativePath 'a\b' -Patterns @() | Should -BeFalse
|
||||
Test-BaknretPathExcluded -RelativePath '' -Patterns @('!*') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'a\b' -Patterns @() | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath '' -Patterns @('!*') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '模式里的空格按 7z 的规矩当 ? 处理' {
|
||||
Test-BaknretPathExcluded -RelativePath 'a\Cache' -Patterns @('!*Cache Extras') | Should -BeFalse
|
||||
Test-BaknretPathExcluded -RelativePath 'a\Cache Extras' -Patterns @('!*Cache Extras') | Should -BeTrue
|
||||
Test-BakNRetPathExcluded -RelativePath 'a\Cache' -Patterns @('!*Cache Extras') | Should -BeFalse
|
||||
Test-BakNRetPathExcluded -RelativePath 'a\Cache Extras' -Patterns @('!*Cache Extras') | Should -BeTrue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,19 +257,19 @@ Describe 'SID 映射(跨机恢复)' {
|
||||
# ============================================================================
|
||||
It '整 SID 精确替换' {
|
||||
$sddl = 'O:S-1-5-21-1-2-3-1001G:S-1-5-21-1-2-3-1001D:(A;;FA;;;S-1-5-21-1-2-3-1001)'
|
||||
$mapped = Convert-BaknretSidMap -Sddl $sddl -SidMap @{ 'S-1-5-21-1-2-3-1001' = 'S-1-5-21-9-8-7-1001' }
|
||||
$mapped = Convert-BakNRetSidMap -Sddl $sddl -SidMap @{ 'S-1-5-21-1-2-3-1001' = 'S-1-5-21-9-8-7-1001' }
|
||||
$mapped | Should -Be 'O:S-1-5-21-9-8-7-1001G:S-1-5-21-9-8-7-1001D:(A;;FA;;;S-1-5-21-9-8-7-1001)'
|
||||
}
|
||||
|
||||
It '不会误伤以它为前缀的更长的 SID' {
|
||||
$sddl = 'D:(A;;FA;;;S-1-5-21-1-2-3-1001)(A;;FA;;;S-1-5-21-1-2-3-10012)'
|
||||
$mapped = Convert-BaknretSidMap -Sddl $sddl -SidMap @{ 'S-1-5-21-1-2-3-1001' = 'S-1-5-21-9-8-7-1001' }
|
||||
$mapped = Convert-BakNRetSidMap -Sddl $sddl -SidMap @{ 'S-1-5-21-1-2-3-1001' = 'S-1-5-21-9-8-7-1001' }
|
||||
$mapped | Should -Be 'D:(A;;FA;;;S-1-5-21-9-8-7-1001)(A;;FA;;;S-1-5-21-1-2-3-10012)'
|
||||
}
|
||||
|
||||
It '空映射表时原样返回' {
|
||||
$sddl = 'D:(A;;FA;;;SY)'
|
||||
Convert-BaknretSidMap -Sddl $sddl -SidMap @{} | Should -Be $sddl
|
||||
Convert-BakNRetSidMap -Sddl $sddl -SidMap @{} | Should -Be $sddl
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,7 +283,7 @@ Describe '安全描述符采集' {
|
||||
}
|
||||
|
||||
It 'Full:每个对象一条记录,键是归档内相对路径' {
|
||||
$capture = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$capture = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$capture.Scanned | Should -Be 3
|
||||
$capture.Kept | Should -Be 3
|
||||
$capture.Errors | Should -Be 0
|
||||
@@ -291,7 +291,7 @@ Describe '安全描述符采集' {
|
||||
}
|
||||
|
||||
It '根记录的 SDDL 保留了 CREATOR OWNER、IO 标志、孤儿 SID 和 protected 位' {
|
||||
$capture = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$capture = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$root = @($capture.Records | Where-Object { $_.p -eq 'Data' })[0]
|
||||
$root.s | Should -Match 'D:PAI'
|
||||
$root.s | Should -Match '\(A;OICIIO;GA;;;CO\)'
|
||||
@@ -300,23 +300,23 @@ Describe '安全描述符采集' {
|
||||
}
|
||||
|
||||
It 'Smart 比 Full 少,但根永远保留' {
|
||||
$full = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$smart = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Smart
|
||||
$full = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$smart = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Smart
|
||||
$smart.Kept | Should -BeLessOrEqual $full.Kept
|
||||
@($smart.Records | ForEach-Object { $_.p }) | Should -Contain 'Data'
|
||||
}
|
||||
|
||||
It 'Roots 只存归档项的根,不再往下走' {
|
||||
$roots = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Roots
|
||||
$roots = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Roots
|
||||
$roots.Kept | Should -Be 1
|
||||
$roots.Records[0].p | Should -Be 'Data'
|
||||
}
|
||||
|
||||
It 'sidecar 往返:条数与 SDDL 原样保留' {
|
||||
$capture = Get-BaknretSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$capture = Get-BakNRetSecurityRecords -Items @($script:CaptureItem) -Mode Full
|
||||
$path = Join-Path $script:Sandbox 'roundtrip.acl.json'
|
||||
Save-BaknretSecuritySidecar -Path $path -Records $capture.Records -Mode Full -Errors $capture.Errors -Scanned $capture.Scanned | Out-Null
|
||||
$sidecar = Read-BaknretSecuritySidecar -Path $path
|
||||
Save-BakNRetSecuritySidecar -Path $path -Records $capture.Records -Mode Full -Errors $capture.Errors -Scanned $capture.Scanned | Out-Null
|
||||
$sidecar = Read-BakNRetSecuritySidecar -Path $path
|
||||
$sidecar.Records.Count | Should -Be 3
|
||||
$record = @($sidecar.Records | Where-Object { $_.p -eq 'Data\sub\a.txt' })[0]
|
||||
$record.k | Should -Be 'f'
|
||||
@@ -324,7 +324,7 @@ Describe '安全描述符采集' {
|
||||
}
|
||||
|
||||
It '旁挂文件不存在时读出 $null(调用方据此打告警,而不是静默当没事)' {
|
||||
Read-BaknretSecuritySidecar -Path (Join-Path $script:Sandbox 'nope.acl.json') | Should -BeNullOrEmpty
|
||||
Read-BakNRetSecuritySidecar -Path (Join-Path $script:Sandbox 'nope.acl.json') | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '排除模式在采集时同样生效(采集树 == 归档树)' {
|
||||
@@ -336,7 +336,7 @@ Describe '安全描述符采集' {
|
||||
[System.IO.File]::WriteAllText((Join-Path $walkRoot 'keep.txt'), 'x')
|
||||
|
||||
$walkItem = [pscustomobject]@{ ArchivePath = 'Data'; RealPath = $walkRoot }
|
||||
$capture = Get-BaknretSecurityRecords -Items @($walkItem) -Mode Full -ScopeMap @{ 0 = @('!Cache') }
|
||||
$capture = Get-BakNRetSecurityRecords -Items @($walkItem) -Mode Full -ScopeMap @{ 0 = @('!Cache') }
|
||||
@($capture.Records | ForEach-Object { $_.p }) | Should -Not -Contain 'Data\Cache'
|
||||
@($capture.Records | ForEach-Object { $_.p }) | Should -Contain 'Data\keep.txt'
|
||||
}
|
||||
@@ -350,17 +350,17 @@ Describe '安全描述符回放' {
|
||||
$script:TargetRoot = Join-Path $script:Sandbox 'restore\target'
|
||||
$script:RestoreFingerprints = New-AclSourceTree -Root $script:RestoreRoot
|
||||
|
||||
$capture = Get-BaknretSecurityRecords -Items @([pscustomobject]@{ ArchivePath = 'Data'; RealPath = $script:RestoreRoot }) -Mode Full
|
||||
$capture = Get-BakNRetSecurityRecords -Items @([pscustomobject]@{ ArchivePath = 'Data'; RealPath = $script:RestoreRoot }) -Mode Full
|
||||
$script:RestoreSidecarPath = Join-Path $script:Sandbox 'restore.acl.json'
|
||||
Save-BaknretSecuritySidecar -Path $script:RestoreSidecarPath -Records $capture.Records -Mode Full -Errors $capture.Errors -Scanned $capture.Scanned | Out-Null
|
||||
$script:RestoreSidecar = Read-BaknretSecuritySidecar -Path $script:RestoreSidecarPath
|
||||
Save-BakNRetSecuritySidecar -Path $script:RestoreSidecarPath -Records $capture.Records -Mode Full -Errors $capture.Errors -Scanned $capture.Scanned | Out-Null
|
||||
$script:RestoreSidecar = Read-BakNRetSecuritySidecar -Path $script:RestoreSidecarPath
|
||||
}
|
||||
|
||||
It '回放后根对象的安全描述符与源逐字节一致(protected / CO / 孤儿 SID 全在)' {
|
||||
# 干净目标:只拷内容,不带 ACL(ACL 是新建对象的默认值)
|
||||
& robocopy.exe $script:RestoreRoot $script:TargetRoot /E /COPY:DAT /NFL /NDL /NJH /NJS /NP | Out-Null
|
||||
|
||||
$result = Restore-BaknretSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Data' -TargetPath $script:TargetRoot
|
||||
$result = Restore-BakNRetSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Data' -TargetPath $script:TargetRoot
|
||||
$result.Total | Should -Be 3
|
||||
$result.Failed | Should -Be 0
|
||||
$result.Applied | Should -Be 3
|
||||
@@ -374,7 +374,7 @@ Describe '安全描述符回放' {
|
||||
$targetPath = if ($relative -eq '.') { $script:TargetRoot } else { Join-Path $script:TargetRoot $relative }
|
||||
|
||||
# 唯一允许的差异:陈旧继承 ACE 被"冻结"成显式 + protected 的对象,
|
||||
# protected 位会从 False 变 True(见 Get-BaknretSecuritySddlWithStale)。
|
||||
# protected 位会从 False 变 True(见 Get-BakNRetSecuritySddlWithStale)。
|
||||
$expected = $script:RestoreFingerprints[$relative] -replace ' P=(True|False) ', ' P='
|
||||
$actual = (Get-AclFingerprint -Path $targetPath) -replace ' P=(True|False) ', ' P='
|
||||
$actual | Should -Be $expected -Because "$relative 的 ACE 集合应当与源一致"
|
||||
@@ -382,7 +382,7 @@ Describe '安全描述符回放' {
|
||||
}
|
||||
|
||||
It '目标不存在或不是普通对象时记 Skipped,不记 Failed' {
|
||||
$result = Restore-BaknretSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Data' `
|
||||
$result = Restore-BakNRetSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Data' `
|
||||
-TargetPath (Join-Path $script:Sandbox 'restore\does-not-exist')
|
||||
$result.Total | Should -Be 3
|
||||
$result.Skipped | Should -Be 3
|
||||
@@ -390,7 +390,7 @@ Describe '安全描述符回放' {
|
||||
}
|
||||
|
||||
It '归档根名对不上时一条都不回放(不会把兄弟项的 ACL 倒过来)' {
|
||||
$result = Restore-BaknretSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Other' -TargetPath $script:TargetRoot
|
||||
$result = Restore-BakNRetSecurity -Sidecar $script:RestoreSidecar -ArchiveRoot 'Other' -TargetPath $script:TargetRoot
|
||||
$result.Total | Should -Be 0
|
||||
$result.Applied | Should -Be 0
|
||||
}
|
||||
@@ -406,7 +406,7 @@ Describe '安全描述符回放' {
|
||||
Records = @([pscustomobject]@{ p = 'Data'; k = 'd'; s = $sddl })
|
||||
}
|
||||
|
||||
$result = Restore-BaknretSecurity -Sidecar $sidecar -ArchiveRoot 'Data' -TargetPath $path
|
||||
$result = Restore-BakNRetSecurity -Sidecar $sidecar -ArchiveRoot 'Data' -TargetPath $path
|
||||
$result.Failed | Should -Be 0
|
||||
($result.Applied + $result.OwnerFailed) | Should -Be 1
|
||||
(Get-Acl -LiteralPath $path).Sddl | Should -Match '\(A;;FA;;;SY\)'
|
||||
@@ -416,7 +416,7 @@ Describe '安全描述符回放' {
|
||||
$record = [pscustomobject]@{ p = 'Data'; k = 'd'; s = $null; e = '读不到' }
|
||||
$sidecar = [pscustomobject]@{ Records = @($record) }
|
||||
$path = Join-Path $script:Sandbox 'restore\bogus-group'
|
||||
$result = Restore-BaknretSecurity -Sidecar $sidecar -ArchiveRoot 'Data' -TargetPath $path
|
||||
$result = Restore-BakNRetSecurity -Sidecar $sidecar -ArchiveRoot 'Data' -TargetPath $path
|
||||
$result.Skipped | Should -Be 1
|
||||
$result.Applied | Should -Be 0
|
||||
$result.Failed | Should -Be 0
|
||||
@@ -432,7 +432,7 @@ Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZi
|
||||
}
|
||||
|
||||
It '备份会写出 <归档名>.acl.json,并在 manifest 里记下它' {
|
||||
$result = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
$result = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:Harness.ListPath
|
||||
ConfigPath = $script:Harness.ConfigPath
|
||||
BackupDir = $script:Harness.BackupDir
|
||||
@@ -454,7 +454,7 @@ Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZi
|
||||
Reset-AclTree -Path $script:Harness.SourcePath
|
||||
(Test-Path -LiteralPath $script:Harness.SourcePath) | Should -BeFalse
|
||||
|
||||
$result = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$result = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:Harness.ListPath
|
||||
ConfigPath = $script:Harness.ConfigPath
|
||||
BackupDir = $script:Harness.BackupDir
|
||||
@@ -476,7 +476,7 @@ Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZi
|
||||
|
||||
It '-SkipSecurity 时不回放(目标保持新建对象的默认 ACL)' {
|
||||
Reset-AclTree -Path $script:Harness.SourcePath
|
||||
$result = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$result = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:Harness.ListPath
|
||||
ConfigPath = $script:Harness.ConfigPath
|
||||
BackupDir = $script:Harness.BackupDir
|
||||
@@ -491,7 +491,7 @@ Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZi
|
||||
Reset-AclTree -Path $script:Harness.SourcePath
|
||||
Get-ChildItem -LiteralPath $script:Harness.BackupDir -Filter '*.acl.json' | Remove-Item -Force
|
||||
|
||||
$result = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
$result = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:Harness.ListPath
|
||||
ConfigPath = $script:Harness.ConfigPath
|
||||
BackupDir = $script:Harness.BackupDir
|
||||
|
||||
Reference in new issue
Block a user