diff --git a/Backup.ps1 b/Backup.ps1 index ca4572a..135df33 100644 --- a/Backup.ps1 +++ b/Backup.ps1 @@ -91,9 +91,9 @@ if (-not (Test-Path -LiteralPath $modulePath)) { } Import-Module $modulePath -Force -if ($PSBoundParameters.ContainsKey('Verbose')) { Set-BaknretDebug } +if ($PSBoundParameters.ContainsKey('Verbose')) { Set-BakNRetDebug } -$script:Config = Get-BaknretConfig -Path $ConfigPath +$script:Config = Get-BakNRetConfig -Path $ConfigPath function Resolve-ConfigPath { param([string]$Path, [string]$Default) @@ -107,28 +107,28 @@ function Resolve-ConfigPath { if (-not $BackupDir) { $BackupDir = Resolve-ConfigPath -Path $null -Default $script:Config.BackupDir } $logDir = Resolve-ConfigPath -Path $null -Default $script:Config.LogDir $snapshotDir = Resolve-ConfigPath -Path $null -Default $script:Config.SnapshotDir -$catalogPath = Resolve-CatalogPath -Configured $script:Config.SoftwareCatalog -Root $PSScriptRoot +$catalogPath = Resolve-BakNRetCatalogPath -Configured $script:Config.SoftwareCatalog -Root $PSScriptRoot $manifestPath = Join-Path $BackupDir 'manifest.json' -$logPath = Start-BaknretLog -Directory $logDir -Prefix 'backup' -Write-Log "日志文件:$logPath" -Write-Log "备份目录:$BackupDir" -Write-Log ("软件名录:{0}{1}" -f $catalogPath, $(if (Test-Path -LiteralPath $catalogPath) { '' } else { '(不存在,将只支持字面路径)' })) +$logPath = Start-BakNRetLog -Directory $logDir -Prefix 'backup' +Write-BakNRetLog "日志文件:$logPath" +Write-BakNRetLog "备份目录:$BackupDir" +Write-BakNRetLog ("软件名录:{0}{1}" -f $catalogPath, $(if (Test-Path -LiteralPath $catalogPath) { '' } else { '(不存在,将只支持字面路径)' })) -if (-not (Test-Administrator)) { - Write-Log '建议以管理员身份运行以获取完整的目录访问权限' -Level WARN +if (-not (Test-BakNRetAdministrator)) { + Write-BakNRetLog '建议以管理员身份运行以获取完整的目录访问权限' -Level WARN } # 同一份备份目录同一时间只允许一个进程操作(见 BakNRet 模块的「运行锁」一节)。 # -DryRun 不取锁:它一个字节都不写,没必要被正在跑的备份挡在外面。 $runLock = $null if (-not $DryRun) { - $runLock = Enter-BaknretRunLock -Directory $BackupDir + $runLock = Enter-BakNRetRunLock -Directory $BackupDir if (-not $runLock) { - Write-Log ("另一次运行正在进行中(锁文件:{0},里面写明了持有者)。本次不执行。" -f (Get-BaknretRunLockPath -Directory $BackupDir)) -Level ERROR - Stop-BaknretLog + Write-BakNRetLog ("另一次运行正在进行中(锁文件:{0},里面写明了持有者)。本次不执行。" -f (Get-BakNRetRunLockPath -Directory $BackupDir)) -Level ERROR + Stop-BakNRetLog exit 1 } - Write-Log ("已取得运行锁:{0}" -f (Get-BaknretRunLockPath -Directory $BackupDir)) -Level DEBUG + Write-BakNRetLog ("已取得运行锁:{0}" -f (Get-BakNRetRunLockPath -Directory $BackupDir)) -Level DEBUG } # ============================================================================ @@ -137,7 +137,7 @@ if (-not $DryRun) { if (-not (Test-Path -LiteralPath $BackupDir)) { New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null - Write-Log "创建备份目录: $BackupDir" -Level DEBUG + Write-BakNRetLog "创建备份目录: $BackupDir" -Level DEBUG } if (-not (Test-Path -LiteralPath $BackupListPath)) { @@ -147,15 +147,15 @@ if (-not (Test-Path -LiteralPath $BackupListPath)) { "# %UserProfile%\.ssh :encrypt`n" + "# 完整语法见 README 与 BackupList.txt 自身的注释。`n" [System.IO.File]::WriteAllText($BackupListPath, $template, [System.Text.UTF8Encoding]::new($true)) - Write-Log '模板 BackupList.txt 已创建,请编辑后重试。' -Level INFO - Stop-BaknretLog + Write-BakNRetLog '模板 BackupList.txt 已创建,请编辑后重试。' -Level INFO + Stop-BakNRetLog exit 0 } -$tool = Resolve-CompressionTool +$tool = Resolve-BakNRetCompressionTool if (-not $tool) { - Write-Log '没有找到可用的压缩工具。' -Level ERROR - Stop-BaknretLog + Write-BakNRetLog '没有找到可用的压缩工具。' -Level ERROR + Stop-BakNRetLog exit 1 } @@ -164,13 +164,13 @@ $toolVersion = try { if ($info.ProductVersion) { $info.ProductVersion } elseif ($info.FileVersion) { $info.FileVersion } else { $null } } catch { $null } -Write-Log ("压缩工具:{0}{1}" -f $tool.Name, $(if ($toolVersion) { "($toolVersion)" } else { '' })) +Write-BakNRetLog ("压缩工具:{0}{1}" -f $tool.Name, $(if ($toolVersion) { "($toolVersion)" } else { '' })) -$manifest = Read-BaknretManifest -Path $manifestPath +$manifest = Read-BakNRetManifest -Path $manifestPath $manifest.compressor = [pscustomobject]@{ name = $tool.Name; command = $tool.Command; extension = $tool.Extension; version = $toolVersion } $passwordFile = if ($KeyFile) { $KeyFile } else { $script:Config.Encryption.PasswordFile } -$password = Get-BaknretPassword -PasswordFile $passwordFile +$password = Get-BakNRetPassword -PasswordFile $passwordFile $encryptAll = [bool]$script:Config.Encryption.Enabled $showToolOutput = (-not $QuietTool) -and ($script:Config.ToolOutput -ne 'quiet') $toolQuietArgument = if ($showToolOutput) { @() } else { @('-bso0', '-bsp0') } @@ -181,11 +181,11 @@ $processed = 0; $skipped = 0; $failed = 0; $planned = 0 $securityErrorCount = 0 # 有条目"安全描述符里有读不到的对象" $securityFailed = 0 # 有条目"安全描述符完全没存下来" $failures = @() -$freeSpaceGB = Get-BaknretFreeSpaceGB -Path $BackupDir +$freeSpaceGB = Get-BakNRetFreeSpaceGB -Path $BackupDir if ($freeSpaceGB -ge 0) { - Write-Log ("备份目录所在卷剩余空间:{0} GB" -f $freeSpaceGB) + Write-BakNRetLog ("备份目录所在卷剩余空间:{0} GB" -f $freeSpaceGB) if ($freeSpaceGB -lt $script:Config.MinFreeSpaceGB) { - Write-Log ("剩余空间低于阈值 {0} GB,大条目可能失败" -f $script:Config.MinFreeSpaceGB) -Level WARN + Write-BakNRetLog ("剩余空间低于阈值 {0} GB,大条目可能失败" -f $script:Config.MinFreeSpaceGB) -Level WARN } } @@ -282,7 +282,7 @@ function Save-ItemRecord { # 压缩 + 校验 + 原子替换;返回 @{ Ok; ExitCode; Warnings; Reason } # -# 归档内容由调用方决定:它已经用 New-BaknretArchiveStaging 把每个归档项按"归档内的名字" +# 归档内容由调用方决定:它已经用 New-BakNRetArchiveStaging 把每个归档项按"归档内的名字" # 挂进了 $StagingRoot(目录走 junction、文件走硬链接/复制),所以这里只做三件事: # 1. 以暂存目录为工作目录调用压缩工具,把项名加进去; # 2. 用 7z t 校验,并核对多 Slot 条目的每个顶层名字都真的在包里; @@ -376,11 +376,11 @@ function Invoke-BackupItem { Remove-Item -LiteralPath $tempPath -Force -ErrorAction SilentlyContinue return [pscustomobject]@{ Ok = $false; ExitCode = $verifyCode; Warnings = $false; Reason = "归档校验失败(7z t 退出码 $verifyCode),已丢弃临时文件" } } - Write-Log '归档校验通过(7z t)' -Level DEBUG + Write-BakNRetLog '归档校验通过(7z t)' -Level DEBUG # 多个 Slot / 追加项时确认每一个顶层名字都真的进了归档:7z 的"警告"可能只体现在某一部分上 if ($SourceItems.Count -gt 1) { - $listed = @(Get-ArchiveTopLevelNames -ArchivePath $tempPath -SevenZip $tool.Command -Password $(if ($UseEncryption) { $password } else { $null })) + $listed = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $tempPath -SevenZip $tool.Command -Password $(if ($UseEncryption) { $password } else { $null })) if ($listed.Count -gt 0) { $expected = @($SourceItems | ForEach-Object { [string]$_.TopName } | Select-Object -Unique) $absent = @($expected | Where-Object { $_ -notin $listed }) @@ -406,7 +406,7 @@ function Invoke-BackupItem { } } - Move-BaknretArchiveIntoPlace -TempPath $tempPath -DestinationPath $FinalPath + Move-BakNRetArchiveIntoPlace -TempPath $tempPath -DestinationPath $FinalPath return [pscustomobject]@{ Ok = $true; ExitCode = 0; Warnings = $warnings; Reason = $null } } catch { @@ -437,7 +437,7 @@ foreach ($planLine in $lines) { if (-not $planItem) { continue } $planDisplayPath = $planItem.Path - $planResolved = Resolve-BackupEntry -Entry $planItem -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth + $planResolved = Resolve-BakNRetBackupEntry -Entry $planItem -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth if (-not $planResolved.BaseName) { continue } if (-not (Test-ItemSelected -DisplayPath $planDisplayPath -BaseName $planResolved.BaseName)) { continue } if ($planResolved.Direction -eq 'restore') { continue } @@ -450,7 +450,7 @@ foreach ($planLine in $lines) { $planSourceFiles = 0 $planLatest = $null foreach ($planSource in $planItems) { - $planSummary = Get-FolderSummary -FolderPath $planSource.RealPath + $planSummary = Get-BakNRetFolderSummary -FolderPath $planSource.RealPath $planSourceBytes += [int64]$planSummary.TotalSize $planSourceFiles += [int]$planSummary.FileCount if ($planSummary.LatestModifiedTime -and (-not $planLatest -or $planSummary.LatestModifiedTime -gt $planLatest)) { @@ -487,10 +487,10 @@ foreach ($planLine in $lines) { } } -$freeNowGB = Get-BaknretFreeSpaceGB -Path $BackupDir +$freeNowGB = Get-BakNRetFreeSpaceGB -Path $BackupDir if ($spacePlan.Count -eq 0) { - Write-Log '备份前空间预估:本次没有需要重打的条目(源未更新或源不存在),不会写入新归档' -Level INFO + Write-BakNRetLog '备份前空间预估:本次没有需要重打的条目(源未更新或源不存在),不会写入新归档' -Level INFO } else { $spacePeak = [double]0 @@ -504,33 +504,33 @@ else { $estimateGB = ((($spacePlan | Measure-Object -Property Estimate -Sum).Sum)) / 1GB $existingGB = ((($spacePlan | Measure-Object -Property Existing -Sum).Sum)) / 1GB - Write-Log '==== 备份前空间预估(只读)====' -Level INFO - Write-Log (" 目标卷可用空间:{0} GB" -f $freeNowGB) - Write-Log (" 本次要重打 {0} 个条目(另有 {1} 个源未更新会跳过、{2} 个源不存在)" -f $spacePlan.Count, $spaceSkipped, $spaceNoSource) - Write-Log (" 新归档合计约 {0} GB;其中会替换掉的旧归档 {1} GB" -f [math]::Round($estimateGB, 2), [math]::Round($existingGB, 2)) + Write-BakNRetLog '==== 备份前空间预估(只读)====' -Level INFO + Write-BakNRetLog (" 目标卷可用空间:{0} GB" -f $freeNowGB) + Write-BakNRetLog (" 本次要重打 {0} 个条目(另有 {1} 个源未更新会跳过、{2} 个源不存在)" -f $spacePlan.Count, $spaceSkipped, $spaceNoSource) + Write-BakNRetLog (" 新归档合计约 {0} GB;其中会替换掉的旧归档 {1} GB" -f [math]::Round($estimateGB, 2), [math]::Round($existingGB, 2)) foreach ($plan in ($spacePlan | Sort-Object Estimate -Descending | Select-Object -First 15)) { - Write-Log (" - {0,-22} 源 {1,8:N1} MB / {2,6} 文件 现有 {3,7:N1} MB 预估 {4,7:N1} MB" -f ` + Write-BakNRetLog (" - {0,-22} 源 {1,8:N1} MB / {2,6} 文件 现有 {3,7:N1} MB 预估 {4,7:N1} MB" -f ` $plan.Name, ($plan.SourceBytes / 1MB), $plan.Files, ($plan.Existing / 1MB), ($plan.Estimate / 1MB)) } if ($spacePlan.Count -gt 15) { - Write-Log (" …… 另有 {0} 个条目未逐条列出" -f ($spacePlan.Count - 15)) + Write-BakNRetLog (" …… 另有 {0} 个条目未逐条列出" -f ($spacePlan.Count - 15)) } - Write-Log (" 预计峰值新增占用:{0} GB(全程净增量 {1} GB)" -f [math]::Round($peakGB, 2), [math]::Round($netGB, 2)) + Write-BakNRetLog (" 预计峰值新增占用:{0} GB(全程净增量 {1} GB)" -f [math]::Round($peakGB, 2), [math]::Round($netGB, 2)) if ($freeNowGB -lt 0) { - Write-Log ' 结论:读不到目标卷可用空间,请自行确认是否够用' -Level WARN + Write-BakNRetLog ' 结论:读不到目标卷可用空间,请自行确认是否够用' -Level WARN } elseif ($peakGB -le $freeNowGB) { - Write-Log (" 结论:空间足够(预计用 {0} GB / 可用 {1} GB)" -f [math]::Round($peakGB, 2), $freeNowGB) -Level INFO + Write-BakNRetLog (" 结论:空间足够(预计用 {0} GB / 可用 {1} GB)" -f [math]::Round($peakGB, 2), $freeNowGB) -Level INFO } else { - Write-Log (" 结论:空间可能不够!预计需要 {0} GB,可用只有 {1} GB,差 {2} GB" -f ` + Write-BakNRetLog (" 结论:空间可能不够!预计需要 {0} GB,可用只有 {1} GB,差 {2} GB" -f ` [math]::Round($peakGB, 2), $freeNowGB, [math]::Round($peakGB - $freeNowGB, 2)) -Level WARN - Write-Log ' 仍会继续执行:真正放不下的条目会被逐条目守卫跳过。建议先腾空间,或用 -Only / -Skip 分批备份。' -Level WARN + Write-BakNRetLog ' 仍会继续执行:真正放不下的条目会被逐条目守卫跳过。建议先腾空间,或用 -Only / -Skip 分批备份。' -Level WARN } - Write-Log '============================' -Level INFO + Write-BakNRetLog '============================' -Level INFO } # ============================================================================ @@ -542,7 +542,7 @@ foreach ($line in $lines) { if (-not $item) { continue } $displayPath = $item.Path - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth if (-not $resolved.BaseName) { $record = New-ItemRecord -BaseName ('raw:' + $displayPath) -Source $displayPath -ResolvedSource $displayPath -Phase 'parse' @@ -555,7 +555,7 @@ foreach ($line in $lines) { $sourcePath = [Environment]::ExpandEnvironmentVariables($displayPath) if (-not (Test-ItemSelected -DisplayPath $displayPath -BaseName $baseName)) { - Write-Log "跳过(未选中): $displayPath" -Level DEBUG + Write-BakNRetLog "跳过(未选中): $displayPath" -Level DEBUG continue } @@ -564,7 +564,7 @@ foreach ($line in $lines) { # 备份列表里写重了会生成两个同名归档、互相覆盖 —— 直接报错,不猜。 if ($seenBaseNames.ContainsKey($baseName)) { $reason = "归档名 '$baseName' 与清单中的 '$($seenBaseNames[$baseName])' 重复(由 '$displayPath' 生成),两者会互相覆盖" - Write-Log "失败: $displayPath,$reason" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$reason" -Level ERROR $record = New-ItemRecord -BaseName $baseName -Source $displayPath -ResolvedSource $sourcePath -Phase 'backup' Save-ItemRecord -Record $record -Action 'failed' -Reason $reason | Out-Null $failed++; $failures += $displayPath @@ -573,7 +573,7 @@ foreach ($line in $lines) { $seenBaseNames[$baseName] = $displayPath if ($resolved.Direction -eq 'restore') { - Write-Log "跳过(行首 -,仅恢复): $displayPath" -Level INFO + Write-BakNRetLog "跳过(行首 -,仅恢复): $displayPath" -Level INFO continue } @@ -584,12 +584,12 @@ foreach ($line in $lines) { # root= 是历史标记:包内的一层目录现在由名录里的 Slot 决定,这里只告警不静默忽略。 if (@($item.Flags | Where-Object { $_ -like 'root=*' }).Count -gt 0) { - Write-Log "警告: $displayPath 使用了 root= 标记。归档内的一层目录现在由 Slot 决定,该标记已废弃,本次忽略" -Level WARN + Write-BakNRetLog "警告: $displayPath 使用了 root= 标记。归档内的一层目录现在由 Slot 决定,该标记已废弃,本次忽略" -Level WARN } # 归档内路径冲突:明确失败,绝不把两块内容静默搅进同一棵树 if ($resolved.Blocking) { - Write-Log "失败: $displayPath,$($resolved.Blocking)" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$($resolved.Blocking)" -Level ERROR Save-ItemRecord -Record $record -Action 'failed' -Reason $resolved.Blocking | Out-Null $failed++; $failures += $displayPath continue @@ -604,7 +604,7 @@ foreach ($line in $lines) { else { $planCatalogExcludes = @($resolved.Items | ForEach-Object { @($_.Exclude) } | Where-Object { $_ } | Select-Object -Unique) } - Write-BackupEntryPlan -Resolved $resolved -DisplayPath $displayPath ` + Write-BakNRetBackupEntryPlan -Resolved $resolved -DisplayPath $displayPath ` -ListExcludes $planListExcludes -CatalogExcludes $planCatalogExcludes ` -ConfigExcludes @($script:Config.DefaultExcludes) -Comment $item.Comment @@ -613,26 +613,26 @@ foreach ($line in $lines) { # 但 Items 是给出的(恢复端要靠它把内容还原回原位),备份端由下面的存在性检查统一处理。 if ($resolved.Items.Count -eq 0) { $reason = if ($resolved.Error) { $resolved.Error } else { '解析不出任何源路径' } - Write-Log "跳过: $displayPath,$reason" -Level WARN + Write-BakNRetLog "跳过: $displayPath,$reason" -Level WARN Save-ItemRecord -Record $record -Action 'missing-source' -Reason $reason | Out-Null $skipped++ continue } - # 源存在性检查必须在 Get-FolderSummary / Get-Item 之前: + # 源存在性检查必须在 Get-BakNRetFolderSummary / Get-Item 之前: # 两者对不存在的路径要么抛异常、要么返回会误导判断的空摘要。 $missingItems = @($resolved.Items | Where-Object { -not (Test-Path -LiteralPath $_.RealPath) }) if ($missingItems.Count -ge $resolved.Items.Count) { $missingText = @($missingItems | ForEach-Object { $_.RealPath }) -join ';' - Write-Log "跳过: $displayPath,源路径不存在" -Level WARN + Write-BakNRetLog "跳过: $displayPath,源路径不存在" -Level WARN Save-ItemRecord -Record $record -Action 'missing-source' -Reason ('源路径不存在:' + $missingText) | Out-Null $skipped++ continue } if ($missingItems.Count -gt 0) { - Write-Log ("警告: {0} 有 {1} 个归档项的源路径不存在,本次只打包存在的部分:{2}" -f ` + Write-BakNRetLog ("警告: {0} 有 {1} 个归档项的源路径不存在,本次只打包存在的部分:{2}" -f ` $displayPath, $missingItems.Count, (@($missingItems | ForEach-Object { $_.RealPath }) -join ';')) -Level WARN } @@ -640,7 +640,7 @@ foreach ($line in $lines) { $liveItems = @($resolved.Items | Where-Object { Test-Path -LiteralPath $_.RealPath }) # 归档内的顶层条目名 = 真实存在的归档项在包内的第一层名字(Slot 名 / 源目录名)。 - # 这里记录可核对的事实,备份成功后还会用 Get-ArchiveTopLevelNames 与归档内容对账。 + # 这里记录可核对的事实,备份成功后还会用 Get-BakNRetArchiveTopLevelNames 与归档内容对账。 $record.roots = @($liveItems | ForEach-Object { $_.TopName } | Select-Object -Unique) # 恢复端要知道每个项在归档里是目录还是文件:记在 manifest 里, @@ -654,17 +654,17 @@ foreach ($line in $lines) { $primarySource = $liveItems[0].RealPath if ([string]::IsNullOrWhiteSpace($primarySource)) { - Write-Log "跳过: $displayPath,无法确定主源路径" -Level WARN + Write-BakNRetLog "跳过: $displayPath,无法确定主源路径" -Level WARN Save-ItemRecord -Record $record -Action 'invalid-path' -Reason '归档项没有可用路径' | Out-Null $skipped++ continue } - $summary = Get-FolderSummary -FolderPath $primarySource + $summary = Get-BakNRetFolderSummary -FolderPath $primarySource # 从第二个归档项开始累加。刻意不用 `$liveItems[1..($liveItems.Count-1)]`: # 只有一项时 `1..0` 会退化成 `1,0`,把同一份源数两遍(旧实现的隐蔽 bug)。 for ($index = 1; $index -lt $liveItems.Count; $index++) { - $extra = Get-FolderSummary -FolderPath $liveItems[$index].RealPath + $extra = Get-BakNRetFolderSummary -FolderPath $liveItems[$index].RealPath $summary.FileCount += $extra.FileCount $summary.TotalSize += $extra.TotalSize if ($extra.LatestModifiedTime -and $extra.LatestModifiedTime -gt $summary.LatestModifiedTime) { @@ -677,9 +677,9 @@ foreach ($line in $lines) { $archiveExists = Test-Path -LiteralPath $finalPath $archiveItem = if ($archiveExists) { Get-Item -LiteralPath $finalPath } else { $null } - Write-Log ("开始备份: {0} -> {1}({2} 个文件,{3} MB)" -f $displayPath, $record.archive, $summary.FileCount, [math]::Round(($summary.TotalSize / 1MB), 2)) + Write-BakNRetLog ("开始备份: {0} -> {1}({2} 个文件,{3} MB)" -f $displayPath, $record.archive, $summary.FileCount, [math]::Round(($summary.TotalSize / 1MB), 2)) - # 空目录时 Get-FolderSummary 拿不到任何条目,回退到源自身的修改时间 + # 空目录时 Get-BakNRetFolderSummary 拿不到任何条目,回退到源自身的修改时间 # (源路径上面已经确认存在,这里的 Get-Item 不会再抛异常) $sourceLatest = $summary.LatestModifiedTime if (-not $sourceLatest) { @@ -687,7 +687,7 @@ foreach ($line in $lines) { } if (-not $Force -and $archiveItem -and $sourceLatest -and $sourceLatest -le $archiveItem.LastWriteTime) { - Write-Log "跳过: $displayPath,源目录未更新" -Level INFO + Write-BakNRetLog "跳过: $displayPath,源目录未更新" -Level INFO $record.archiveBytes = $archiveItem.Length Save-ItemRecord -Record $record -Action 'skip-unchanged' -Reason ('源最新修改时间 {0} 不晚于归档时间 {1}' -f $sourceLatest, $archiveItem.LastWriteTime) | Out-Null $skipped++ @@ -700,17 +700,17 @@ foreach ($line in $lines) { $archiveGB = $archiveItem.Length / 1GB $estimatedGB = [math]::Min($estimatedGB, $archiveGB * 1.3) } - $freeSpaceGB = Get-BaknretFreeSpaceGB -Path $BackupDir + $freeSpaceGB = Get-BakNRetFreeSpaceGB -Path $BackupDir if ($freeSpaceGB -ge 0 -and $estimatedGB -gt 0 -and $freeSpaceGB -lt $estimatedGB) { $reason = ('剩余空间 {0} GB 不足以写入预估 {1} GB 的新归档' -f $freeSpaceGB, [math]::Round($estimatedGB, 2)) - Write-Log "失败: $displayPath,$reason" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$reason" -Level ERROR Save-ItemRecord -Record $record -Action 'failed' -Reason $reason | Out-Null $failed++; $failures += $displayPath continue } if ($DryRun) { - Write-Log ("[试运行] 将打包 {0} -> {1}" -f $sourcePath, $finalPath) -Level INFO + Write-BakNRetLog ("[试运行] 将打包 {0} -> {1}" -f $sourcePath, $finalPath) -Level INFO $record.reason = '试运行,未执行压缩' Save-ItemRecord -Record $record -Action 'planned' -Reason '试运行,未执行压缩' | Out-Null $planned++ @@ -733,19 +733,19 @@ foreach ($line in $lines) { @($resolved.Items | ForEach-Object { @($_.Exclude) } | Where-Object { $_ } | Select-Object -Unique) } $allPatterns = @($script:Config.DefaultExcludes) + $patternSource - $scopeMap = Split-BaknretPatternScope -Items $liveItems -Patterns $allPatterns + $scopeMap = Split-BakNRetPatternScope -Items $liveItems -Patterns $allPatterns $excludeLists = @() $excludeError = $null for ($index = 0; $index -lt $liveItems.Count; $index++) { - $expanded = Get-BaknretExcludeArgument -Item $liveItems[$index] -Patterns @($scopeMap[$index]) + $expanded = Get-BakNRetExcludeArgument -Item $liveItems[$index] -Patterns @($scopeMap[$index]) if ($expanded.Error) { $excludeError = $expanded.Error } $excludeLists += , @($expanded.Arguments) } - $effectiveExcludes = @(Merge-BaknretExcludeArgument -ArgumentLists $excludeLists) + $effectiveExcludes = @(Merge-BakNRetExcludeArgument -ArgumentLists $excludeLists) if ($excludeError) { - Write-Log "失败: $displayPath,$excludeError" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$excludeError" -Level ERROR Save-ItemRecord -Record $record -Action 'failed' -Reason $excludeError | Out-Null $failed++; $failures += $displayPath continue @@ -765,7 +765,7 @@ foreach ($line in $lines) { # 挂进暂存目录(junction / 硬链接),再让压缩工具以暂存目录为工作目录打包。 $stagingRoot = $null try { - $stagingRoot = New-BaknretArchiveStaging -Items $liveItems + $stagingRoot = New-BakNRetArchiveStaging -Items $liveItems $result = Invoke-BackupItem -SourceItems $liveItems -StagingRoot $stagingRoot ` -FinalPath $finalPath -ExcludePatterns $effectiveExcludes -UseEncryption:$useEncryption ` -ProtectPrevious:$protectPrevious -AcceptWarnings:$AcceptWarnings @@ -774,7 +774,7 @@ foreach ($line in $lines) { $result = [pscustomobject]@{ Ok = $false; ExitCode = $null; Warnings = $false; Reason = "准备归档内容失败:$_" } } finally { - Remove-BaknretArchiveStaging -Root $stagingRoot + Remove-BakNRetArchiveStaging -Root $stagingRoot } $record.exitCode = $result.ExitCode @@ -783,7 +783,7 @@ foreach ($line in $lines) { $record.durationSec = [math]::Round(((Get-Date) - $startedAt).TotalSeconds, 1) if (-not $result.Ok) { - Write-Log "备份失败: $displayPath,$($result.Reason)" -Level ERROR + Write-BakNRetLog "备份失败: $displayPath,$($result.Reason)" -Level ERROR Save-ItemRecord -Record $record -Action 'failed' -Reason $result.Reason | Out-Null $failed++; $failures += $displayPath continue @@ -792,11 +792,11 @@ foreach ($line in $lines) { $written = Get-Item -LiteralPath $finalPath $record.archiveBytes = $written.Length if ($result.Warnings) { - Write-Log "备份成功(压缩工具报告了警告,可能有文件被占用而没打进归档): $displayPath" -Level WARN - Write-Log ' 该归档在 manifest 里标记为 warnings=true;如果以后现有归档是完整的,会拒绝被它覆盖' -Level WARN + Write-BakNRetLog "备份成功(压缩工具报告了警告,可能有文件被占用而没打进归档): $displayPath" -Level WARN + Write-BakNRetLog ' 该归档在 manifest 里标记为 warnings=true;如果以后现有归档是完整的,会拒绝被它覆盖' -Level WARN } else { - Write-Log "备份成功: $baseName" -Level INFO + Write-BakNRetLog "备份成功: $baseName" -Level INFO } # ------------------------------------------------------------------ @@ -813,9 +813,9 @@ foreach ($line in $lines) { $sidecarName = "$baseName.acl.json" $sidecarPath = Join-Path $BackupDir $sidecarName try { - $capture = Get-BaknretSecurityRecords -Items $liveItems -ScopeMap $scopeMap -Mode $securityMode ` + $capture = Get-BakNRetSecurityRecords -Items $liveItems -ScopeMap $scopeMap -Mode $securityMode ` -IncludeSacl:([bool]$script:Config.Security.IncludeSacl) - Save-BaknretSecuritySidecar -Path $sidecarPath -Records $capture.Records -Mode $securityMode ` + Save-BakNRetSecuritySidecar -Path $sidecarPath -Records $capture.Records -Mode $securityMode ` -IncludeSacl:([bool]$script:Config.Security.IncludeSacl) ` -Errors $capture.Errors -Scanned $capture.Scanned | Out-Null @@ -827,25 +827,25 @@ foreach ($line in $lines) { errors = $capture.Errors capturedAt = (Get-Date).ToString('o') } - Write-Log ("安全描述符:{0} 个对象写进 {1}(扫描 {2} 个,读不到 {3} 个)" -f ` + Write-BakNRetLog ("安全描述符:{0} 个对象写进 {1}(扫描 {2} 个,读不到 {3} 个)" -f ` $capture.Kept, $sidecarName, $capture.Scanned, $capture.Errors) -Level INFO if ($capture.Errors -gt 0) { $securityErrorCount++ $unreadable = @($capture.Records | Where-Object { $_.e } | Select-Object -First 3 -ExpandProperty p) - Write-Log (" {0} 个对象的安全描述符读不到(恢复后它们的属主/ACL 会是新建对象的默认值),例如:{1}" -f ` + Write-BakNRetLog (" {0} 个对象的安全描述符读不到(恢复后它们的属主/ACL 会是新建对象的默认值),例如:{1}" -f ` $capture.Errors, ($unreadable -join '、')) -Level WARN } } catch { $securityFailed++ - Write-Log "安全描述符采集/写盘失败:$displayPath —— $_" -Level WARN + Write-BakNRetLog "安全描述符采集/写盘失败:$displayPath —— $_" -Level WARN $record.security = [ordered]@{ file = $sidecarName; error = "$_" } if ([bool]$script:Config.Security.FailOnError) { $securityFatal = $true } } if ($securityFatal) { - Write-Log "失败: $displayPath,归档已替换,但安全描述符没能存下来(Security.FailOnError = \$true)" -Level ERROR + Write-BakNRetLog "失败: $displayPath,归档已替换,但安全描述符没能存下来(Security.FailOnError = \$true)" -Level ERROR Save-ItemRecord -Record $record -Action 'failed' -Reason '归档已替换,但安全描述符没能存下来' | Out-Null $failed++; $failures += $displayPath continue @@ -854,7 +854,7 @@ foreach ($line in $lines) { if ($Hash -or $script:Config.ComputeHash) { $record.sha256 = (Get-FileHash -LiteralPath $finalPath -Algorithm SHA256).Hash - Write-Log "SHA256: $($record.sha256)" -Level DEBUG + Write-BakNRetLog "SHA256: $($record.sha256)" -Level DEBUG } if ($Snapshot -or $script:Config.Snapshot.Enabled) { @@ -863,7 +863,7 @@ foreach ($line in $lines) { $targetDir = Split-Path -Parent $target if (-not (Test-Path -LiteralPath $targetDir)) { New-Item -ItemType Directory -Path $targetDir -Force | Out-Null } Copy-Item -LiteralPath $finalPath -Destination $target -Force - Write-Log "已留存快照: $target" -Level INFO + Write-BakNRetLog "已留存快照: $target" -Level INFO } Save-ItemRecord -Record $record -Action 'backed-up' -Reason $null -ArchiveWarnings $result.Warnings | Out-Null @@ -875,17 +875,17 @@ foreach ($line in $lines) { # ============================================================================ if ($DryRun) { - Write-Log '试运行:manifest 与归档都不会被写入' -Level INFO + Write-BakNRetLog '试运行:manifest 与归档都不会被写入' -Level INFO } else { # manifest 里写了 archive 的记录,磁盘上就必须真有那个文件 - $clearedArchiveFields = Sync-BaknretManifestArchive -Manifest $manifest -BackupDir $BackupDir + $clearedArchiveFields = Sync-BakNRetManifestArchive -Manifest $manifest -BackupDir $BackupDir if ($clearedArchiveFields.Count -gt 0) { - Write-Log ("已清空 {0} 条记录里指向不存在归档的 archive 字段:{1}" -f $clearedArchiveFields.Count, ($clearedArchiveFields -join '、')) -Level WARN + Write-BakNRetLog ("已清空 {0} 条记录里指向不存在归档的 archive 字段:{1}" -f $clearedArchiveFields.Count, ($clearedArchiveFields -join '、')) -Level WARN } - Write-BaknretManifest -Path $manifestPath -Manifest $manifest | Out-Null - Write-Log "manifest 已更新:$manifestPath" -Level DEBUG + Write-BakNRetManifest -Path $manifestPath -Manifest $manifest | Out-Null + Write-BakNRetLog "manifest 已更新:$manifestPath" -Level DEBUG } # 孤儿归档审计:磁盘上有、但**当前清单里任何条目都不指向**的归档。 @@ -906,37 +906,37 @@ if (-not $DryRun -and $Only.Count -eq 0 -and $Skip.Count -eq 0) { Where-Object { $_.Extension.ToLower() -in @('.7z', '.rar', '.zip', '.tar') -and -not $known.ContainsKey($_.BaseName) }) if ($orphanArchives.Count -gt 0) { - Write-Log ("发现 {0} 个孤儿归档(当前清单里没有任何条目指向,恢复不到,注意别误删):" -f $orphanArchives.Count) -Level WARN + Write-BakNRetLog ("发现 {0} 个孤儿归档(当前清单里没有任何条目指向,恢复不到,注意别误删):" -f $orphanArchives.Count) -Level WARN foreach ($orphan in $orphanArchives) { $inManifest = $manifest.items.Contains($orphan.BaseName) - Write-Log (" - {0}({1:N1} MB,{2}){3}" -f $orphan.Name, ($orphan.Length / 1MB), $orphan.LastWriteTime, $(if ($inManifest) { ';manifest 里还留着它的历史记录,但清单里已经没有了' } else { '' })) -Level WARN + Write-BakNRetLog (" - {0}({1:N1} MB,{2}){3}" -f $orphan.Name, ($orphan.Length / 1MB), $orphan.LastWriteTime, $(if ($inManifest) { ';manifest 里还留着它的历史记录,但清单里已经没有了' } else { '' })) -Level WARN } } else { - Write-Log '孤儿归档审计:没有发现(所有归档都有清单条目指向)' -Level DEBUG + Write-BakNRetLog '孤儿归档审计:没有发现(所有归档都有清单条目指向)' -Level DEBUG } } if ($failures.Count -gt 0) { - Write-Log '失败条目:' -Level ERROR - foreach ($failure in $failures) { Write-Log " - $failure" -Level ERROR } + Write-BakNRetLog '失败条目:' -Level ERROR + foreach ($failure in $failures) { Write-BakNRetLog " - $failure" -Level ERROR } } if ($securityFailed -gt 0) { - Write-Log ("有 {0} 个条目的安全描述符完全没能存下来(manifest 的 security.error 里有原文)" -f $securityFailed) -Level WARN + Write-BakNRetLog ("有 {0} 个条目的安全描述符完全没能存下来(manifest 的 security.error 里有原文)" -f $securityFailed) -Level WARN } if ($securityErrorCount -gt 0) { - Write-Log ("有 {0} 个条目存在'读不到安全描述符'的对象;恢复后这些对象的属主/ACL 是新建对象的默认值,可查 manifest 的 security.errors" -f $securityErrorCount) -Level WARN + Write-BakNRetLog ("有 {0} 个条目存在'读不到安全描述符'的对象;恢复后这些对象的属主/ACL 是新建对象的默认值,可查 manifest 的 security.errors" -f $securityErrorCount) -Level WARN } $summaryText = "备份完成。成功: $processed, 跳过: $skipped, 失败: $failed" if ($DryRun) { $summaryText += ", 试运行计划: $planned" } -Write-Log $summaryText -Level INFO +Write-BakNRetLog $summaryText -Level INFO -$logPath = Get-BaknretLogPath -if ($logPath) { Write-Log "日志已写入:$logPath" -Level INFO } -Exit-BaknretRunLock -Lock $runLock -Stop-BaknretLog +$logPath = Get-BakNRetLogPath +if ($logPath) { Write-BakNRetLog "日志已写入:$logPath" -Level INFO } +Exit-BakNRetRunLock -Lock $runLock +Stop-BakNRetLog if ($failed -gt 0) { exit 1 } exit 0 diff --git a/BakNRet/BakNRet.psd1 b/BakNRet/BakNRet.psd1 index caedd6d..dfa935e 100644 --- a/BakNRet/BakNRet.psd1 +++ b/BakNRet/BakNRet.psd1 @@ -19,68 +19,68 @@ CompatiblePSEditions = @('Desktop', 'Core') FunctionsToExport = @( - 'Enter-BaknretRunLock', - 'Exit-BaknretRunLock', - 'Get-BaknretRunLockPath', - 'Set-BaknretDebug', - 'Start-BaknretLog', - 'Stop-BaknretLog', - 'Get-BaknretLogPath', - 'Write-Log', - 'Test-Administrator', - 'Get-BaknretFreeSpaceGB', - 'ConvertTo-NativeArgumentString', + 'Enter-BakNRetRunLock', + 'Exit-BakNRetRunLock', + 'Get-BakNRetRunLockPath', + 'Set-BakNRetDebug', + 'Start-BakNRetLog', + 'Stop-BakNRetLog', + 'Get-BakNRetLogPath', + 'Write-BakNRetLog', + 'Test-BakNRetAdministrator', + 'Get-BakNRetFreeSpaceGB', + 'ConvertTo-BakNRetNativeArgumentString', 'Invoke-ExternalCommand', - 'Resolve-CompressionTool', + 'Resolve-BakNRetCompressionTool', 'Get-Optimized7zArgument', - 'Split-BaknretToken', - 'Remove-BaknretQuote', - 'Test-BaknretMarker', - 'ConvertFrom-BaknretPatternList', + 'Split-BakNRetToken', + 'Remove-BakNRetQuote', + 'Test-BakNRetMarker', + 'ConvertFrom-BakNRetPatternList', 'ConvertFrom-BackupListLine', - 'Test-LiteralPath', - 'Get-BaknretRegexExclude', - 'Get-BaknretExcludeArgument', - 'Split-BaknretPatternScope', - 'Merge-BaknretExcludeArgument', - 'Resolve-CatalogPath', - 'Get-SoftwareCatalog', - 'Find-ChildDirectoryByName', - 'Format-CatalogName', + 'Test-BakNRetLiteralPath', + 'Get-BakNRetRegexExclude', + 'Get-BakNRetExcludeArgument', + 'Split-BakNRetPatternScope', + 'Merge-BakNRetExcludeArgument', + 'Resolve-BakNRetCatalogPath', + 'Get-BakNRetSoftwareCatalog', + 'Find-BakNRetChildDirectoryByName', + 'Format-BakNRetCatalogName', 'Expand-CatalogPathText', - 'Get-ArchiveTopLevelNames', - 'Get-BaknretArchiveTopName', - 'New-BaknretArchiveItem', - 'New-BaknretJunction', - 'Remove-BaknretJunction', - 'New-BaknretArchiveStaging', - 'Remove-BaknretArchiveStaging', - 'Get-ItemArchiveName', - 'Resolve-BackupEntry', - 'Write-BackupEntryPlan', - 'Get-BackupBaseName', - 'Convert-BackupFileNameToPath', - 'Get-FolderSummary', - 'Read-BaknretManifest', - 'Write-BaknretManifest', - 'Sync-BaknretManifestArchive', - 'Move-BaknretArchiveIntoPlace', - 'Enable-BaknretPrivilege', - 'ConvertTo-BaknretWildcardPattern', - 'Test-BaknretPathExcluded', - 'Get-BaknretAceSignatureList', - 'Get-BaknretSecuritySddlWithStale', - 'Get-BaknretSecurityRecord', - 'Test-BaknretSecurityRecordNeeded', - 'Get-BaknretSecurityRecords', - 'Write-BaknretAtomicText', - 'Save-BaknretSecuritySidecar', - 'Read-BaknretSecuritySidecar', - 'Convert-BaknretSidMap', - 'Set-BaknretObjectSecurity', - 'Restore-BaknretSecurity', - 'Get-BaknretConfig', - 'Get-BaknretPassword' + 'Get-BakNRetArchiveTopLevelNames', + 'Get-BakNRetArchiveTopName', + 'New-BakNRetArchiveItem', + 'New-BakNRetJunction', + 'Remove-BakNRetJunction', + 'New-BakNRetArchiveStaging', + 'Remove-BakNRetArchiveStaging', + 'Get-BakNRetItemArchiveName', + 'Resolve-BakNRetBackupEntry', + 'Write-BakNRetBackupEntryPlan', + 'Get-BakNRetBackupBaseName', + 'Convert-BakNRetBackupFileNameToPath', + 'Get-BakNRetFolderSummary', + 'Read-BakNRetManifest', + 'Write-BakNRetManifest', + 'Sync-BakNRetManifestArchive', + 'Move-BakNRetArchiveIntoPlace', + 'Enable-BakNRetPrivilege', + 'ConvertTo-BakNRetWildcardPattern', + 'Test-BakNRetPathExcluded', + 'Get-BakNRetAceSignatureList', + 'Get-BakNRetSecuritySddlWithStale', + 'Get-BakNRetSecurityRecord', + 'Test-BakNRetSecurityRecordNeeded', + 'Get-BakNRetSecurityRecords', + 'Write-BakNRetAtomicText', + 'Save-BakNRetSecuritySidecar', + 'Read-BakNRetSecuritySidecar', + 'Convert-BakNRetSidMap', + 'Set-BakNRetObjectSecurity', + 'Restore-BakNRetSecurity', + 'Get-BakNRetConfig', + 'Get-BakNRetPassword' ) CmdletsToExport = @() VariablesToExport = @() diff --git a/BakNRet/BakNRet.psm1 b/BakNRet/BakNRet.psm1 index a4bd714..d23be5f 100644 --- a/BakNRet/BakNRet.psm1 +++ b/BakNRet/BakNRet.psm1 @@ -46,155 +46,155 @@ . (Join-Path $PSScriptRoot 'Private\State.ps1') # ---- 日志 ---- -. (Join-Path $PSScriptRoot 'Public\Set-BaknretDebug.ps1') -. (Join-Path $PSScriptRoot 'Public\Start-BaknretLog.ps1') -. (Join-Path $PSScriptRoot 'Public\Stop-BaknretLog.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretLogPath.ps1') -. (Join-Path $PSScriptRoot 'Public\Write-Log.ps1') +. (Join-Path $PSScriptRoot 'Public\Set-BakNRetDebug.ps1') +. (Join-Path $PSScriptRoot 'Public\Start-BakNRetLog.ps1') +. (Join-Path $PSScriptRoot 'Public\Stop-BakNRetLog.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetLogPath.ps1') +. (Join-Path $PSScriptRoot 'Public\Write-BakNRetLog.ps1') # ---- 运行锁 ---- -. (Join-Path $PSScriptRoot 'Public\Get-BaknretRunLockPath.ps1') -. (Join-Path $PSScriptRoot 'Public\Enter-BaknretRunLock.ps1') -. (Join-Path $PSScriptRoot 'Public\Exit-BaknretRunLock.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetRunLockPath.ps1') +. (Join-Path $PSScriptRoot 'Public\Enter-BakNRetRunLock.ps1') +. (Join-Path $PSScriptRoot 'Public\Exit-BakNRetRunLock.ps1') # ---- 环境 ---- -. (Join-Path $PSScriptRoot 'Public\Test-Administrator.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretFreeSpaceGB.ps1') +. (Join-Path $PSScriptRoot 'Public\Test-BakNRetAdministrator.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetFreeSpaceGB.ps1') # ---- 外部命令 ---- -. (Join-Path $PSScriptRoot 'Public\ConvertTo-NativeArgumentString.ps1') +. (Join-Path $PSScriptRoot 'Public\ConvertTo-BakNRetNativeArgumentString.ps1') . (Join-Path $PSScriptRoot 'Public\Invoke-ExternalCommand.ps1') -. (Join-Path $PSScriptRoot 'Public\Resolve-CompressionTool.ps1') +. (Join-Path $PSScriptRoot 'Public\Resolve-BakNRetCompressionTool.ps1') . (Join-Path $PSScriptRoot 'Public\Get-Optimized7zArgument.ps1') # ---- BackupList.txt 解析 ---- -. (Join-Path $PSScriptRoot 'Public\Split-BaknretToken.ps1') -. (Join-Path $PSScriptRoot 'Public\Remove-BaknretQuote.ps1') -. (Join-Path $PSScriptRoot 'Public\Test-BaknretMarker.ps1') -. (Join-Path $PSScriptRoot 'Public\ConvertFrom-BaknretPatternList.ps1') +. (Join-Path $PSScriptRoot 'Public\Split-BakNRetToken.ps1') +. (Join-Path $PSScriptRoot 'Public\Remove-BakNRetQuote.ps1') +. (Join-Path $PSScriptRoot 'Public\Test-BakNRetMarker.ps1') +. (Join-Path $PSScriptRoot 'Public\ConvertFrom-BakNRetPatternList.ps1') . (Join-Path $PSScriptRoot 'Public\ConvertFrom-BackupListLine.ps1') -. (Join-Path $PSScriptRoot 'Public\Test-LiteralPath.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretRegexExclude.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretExcludeArgument.ps1') -. (Join-Path $PSScriptRoot 'Public\Split-BaknretPatternScope.ps1') -. (Join-Path $PSScriptRoot 'Public\Merge-BaknretExcludeArgument.ps1') +. (Join-Path $PSScriptRoot 'Public\Test-BakNRetLiteralPath.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetRegexExclude.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetExcludeArgument.ps1') +. (Join-Path $PSScriptRoot 'Public\Split-BakNRetPatternScope.ps1') +. (Join-Path $PSScriptRoot 'Public\Merge-BakNRetExcludeArgument.ps1') # ---- 软件名录(SoftwareCatalog.psd1) ---- -. (Join-Path $PSScriptRoot 'Public\Resolve-CatalogPath.ps1') -. (Join-Path $PSScriptRoot 'Public\Format-CatalogName.ps1') -. (Join-Path $PSScriptRoot 'Private\Test-BaknretMapKey.ps1') -. (Join-Path $PSScriptRoot 'Private\Get-BaknretMapValue.ps1') -. (Join-Path $PSScriptRoot 'Private\Get-BaknretMapKeys.ps1') +. (Join-Path $PSScriptRoot 'Public\Resolve-BakNRetCatalogPath.ps1') +. (Join-Path $PSScriptRoot 'Public\Format-BakNRetCatalogName.ps1') +. (Join-Path $PSScriptRoot 'Private\Test-BakNRetMapKey.ps1') +. (Join-Path $PSScriptRoot 'Private\Get-BakNRetMapValue.ps1') +. (Join-Path $PSScriptRoot 'Private\Get-BakNRetMapKeys.ps1') . (Join-Path $PSScriptRoot 'Public\Expand-CatalogPathText.ps1') -. (Join-Path $PSScriptRoot 'Private\Import-BaknretDataFile.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-SoftwareCatalog.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-ArchiveTopLevelNames.ps1') -. (Join-Path $PSScriptRoot 'Public\Find-ChildDirectoryByName.ps1') +. (Join-Path $PSScriptRoot 'Private\Import-BakNRetDataFile.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetSoftwareCatalog.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetArchiveTopLevelNames.ps1') +. (Join-Path $PSScriptRoot 'Public\Find-BakNRetChildDirectoryByName.ps1') # ---- 归档命名与路径还原 ---- -. (Join-Path $PSScriptRoot 'Public\Get-ItemArchiveName.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretArchiveTopName.ps1') -. (Join-Path $PSScriptRoot 'Public\New-BaknretArchiveItem.ps1') -. (Join-Path $PSScriptRoot 'Public\New-BaknretJunction.ps1') -. (Join-Path $PSScriptRoot 'Public\Remove-BaknretJunction.ps1') -. (Join-Path $PSScriptRoot 'Public\New-BaknretArchiveStaging.ps1') -. (Join-Path $PSScriptRoot 'Public\Remove-BaknretArchiveStaging.ps1') -. (Join-Path $PSScriptRoot 'Public\Resolve-BackupEntry.ps1') -. (Join-Path $PSScriptRoot 'Public\Write-BackupEntryPlan.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BackupBaseName.ps1') -. (Join-Path $PSScriptRoot 'Public\Convert-BackupFileNameToPath.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-FolderSummary.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetItemArchiveName.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetArchiveTopName.ps1') +. (Join-Path $PSScriptRoot 'Public\New-BakNRetArchiveItem.ps1') +. (Join-Path $PSScriptRoot 'Public\New-BakNRetJunction.ps1') +. (Join-Path $PSScriptRoot 'Public\Remove-BakNRetJunction.ps1') +. (Join-Path $PSScriptRoot 'Public\New-BakNRetArchiveStaging.ps1') +. (Join-Path $PSScriptRoot 'Public\Remove-BakNRetArchiveStaging.ps1') +. (Join-Path $PSScriptRoot 'Public\Resolve-BakNRetBackupEntry.ps1') +. (Join-Path $PSScriptRoot 'Public\Write-BakNRetBackupEntryPlan.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetBackupBaseName.ps1') +. (Join-Path $PSScriptRoot 'Public\Convert-BakNRetBackupFileNameToPath.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetFolderSummary.ps1') # ---- manifest.json ---- -. (Join-Path $PSScriptRoot 'Public\Read-BaknretManifest.ps1') -. (Join-Path $PSScriptRoot 'Public\Sync-BaknretManifestArchive.ps1') -. (Join-Path $PSScriptRoot 'Public\Write-BaknretManifest.ps1') +. (Join-Path $PSScriptRoot 'Public\Read-BakNRetManifest.ps1') +. (Join-Path $PSScriptRoot 'Public\Sync-BakNRetManifestArchive.ps1') +. (Join-Path $PSScriptRoot 'Public\Write-BakNRetManifest.ps1') # ---- 归档原子替换 ---- -. (Join-Path $PSScriptRoot 'Public\Move-BaknretArchiveIntoPlace.ps1') +. (Join-Path $PSScriptRoot 'Public\Move-BakNRetArchiveIntoPlace.ps1') # ---- 安全描述符(NTFS 属主 / ACL) ---- -. (Join-Path $PSScriptRoot 'Public\Enable-BaknretPrivilege.ps1') -. (Join-Path $PSScriptRoot 'Public\ConvertTo-BaknretWildcardPattern.ps1') -. (Join-Path $PSScriptRoot 'Public\Test-BaknretPathExcluded.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretAceSignatureList.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretSecuritySddlWithStale.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretSecurityRecord.ps1') -. (Join-Path $PSScriptRoot 'Public\Test-BaknretSecurityRecordNeeded.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretSecurityRecords.ps1') -. (Join-Path $PSScriptRoot 'Public\Write-BaknretAtomicText.ps1') -. (Join-Path $PSScriptRoot 'Public\Save-BaknretSecuritySidecar.ps1') -. (Join-Path $PSScriptRoot 'Public\Read-BaknretSecuritySidecar.ps1') -. (Join-Path $PSScriptRoot 'Public\Convert-BaknretSidMap.ps1') -. (Join-Path $PSScriptRoot 'Public\Set-BaknretObjectSecurity.ps1') -. (Join-Path $PSScriptRoot 'Public\Restore-BaknretSecurity.ps1') +. (Join-Path $PSScriptRoot 'Public\Enable-BakNRetPrivilege.ps1') +. (Join-Path $PSScriptRoot 'Public\ConvertTo-BakNRetWildcardPattern.ps1') +. (Join-Path $PSScriptRoot 'Public\Test-BakNRetPathExcluded.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetAceSignatureList.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetSecuritySddlWithStale.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetSecurityRecord.ps1') +. (Join-Path $PSScriptRoot 'Public\Test-BakNRetSecurityRecordNeeded.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetSecurityRecords.ps1') +. (Join-Path $PSScriptRoot 'Public\Write-BakNRetAtomicText.ps1') +. (Join-Path $PSScriptRoot 'Public\Save-BakNRetSecuritySidecar.ps1') +. (Join-Path $PSScriptRoot 'Public\Read-BakNRetSecuritySidecar.ps1') +. (Join-Path $PSScriptRoot 'Public\Convert-BakNRetSidMap.ps1') +. (Join-Path $PSScriptRoot 'Public\Set-BakNRetObjectSecurity.ps1') +. (Join-Path $PSScriptRoot 'Public\Restore-BakNRetSecurity.ps1') # ---- 配置 ---- -. (Join-Path $PSScriptRoot 'Public\Get-BaknretConfig.ps1') -. (Join-Path $PSScriptRoot 'Public\Get-BaknretPassword.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetConfig.ps1') +. (Join-Path $PSScriptRoot 'Public\Get-BakNRetPassword.ps1') Export-ModuleMember -Function @( - 'Enter-BaknretRunLock', - 'Exit-BaknretRunLock', - 'Get-BaknretRunLockPath', - 'Set-BaknretDebug', - 'Start-BaknretLog', - 'Stop-BaknretLog', - 'Get-BaknretLogPath', - 'Write-Log', - 'Test-Administrator', - 'Get-BaknretFreeSpaceGB', - 'ConvertTo-NativeArgumentString', + 'Enter-BakNRetRunLock', + 'Exit-BakNRetRunLock', + 'Get-BakNRetRunLockPath', + 'Set-BakNRetDebug', + 'Start-BakNRetLog', + 'Stop-BakNRetLog', + 'Get-BakNRetLogPath', + 'Write-BakNRetLog', + 'Test-BakNRetAdministrator', + 'Get-BakNRetFreeSpaceGB', + 'ConvertTo-BakNRetNativeArgumentString', 'Invoke-ExternalCommand', - 'Resolve-CompressionTool', + 'Resolve-BakNRetCompressionTool', 'Get-Optimized7zArgument', - 'Split-BaknretToken', - 'Remove-BaknretQuote', - 'Test-BaknretMarker', - 'ConvertFrom-BaknretPatternList', + 'Split-BakNRetToken', + 'Remove-BakNRetQuote', + 'Test-BakNRetMarker', + 'ConvertFrom-BakNRetPatternList', 'ConvertFrom-BackupListLine', - 'Test-LiteralPath', - 'Get-BaknretRegexExclude', - 'Get-BaknretExcludeArgument', - 'Split-BaknretPatternScope', - 'Merge-BaknretExcludeArgument', - 'Resolve-CatalogPath', - 'Get-SoftwareCatalog', - 'Find-ChildDirectoryByName', - 'Format-CatalogName', + 'Test-BakNRetLiteralPath', + 'Get-BakNRetRegexExclude', + 'Get-BakNRetExcludeArgument', + 'Split-BakNRetPatternScope', + 'Merge-BakNRetExcludeArgument', + 'Resolve-BakNRetCatalogPath', + 'Get-BakNRetSoftwareCatalog', + 'Find-BakNRetChildDirectoryByName', + 'Format-BakNRetCatalogName', 'Expand-CatalogPathText', - 'Get-ArchiveTopLevelNames', - 'Get-BaknretArchiveTopName', - 'New-BaknretArchiveItem', - 'New-BaknretJunction', - 'Remove-BaknretJunction', - 'New-BaknretArchiveStaging', - 'Remove-BaknretArchiveStaging', - 'Get-ItemArchiveName', - 'Resolve-BackupEntry', - 'Write-BackupEntryPlan', - 'Get-BackupBaseName', - 'Convert-BackupFileNameToPath', - 'Get-FolderSummary', - 'Read-BaknretManifest', - 'Write-BaknretManifest', - 'Sync-BaknretManifestArchive', - 'Move-BaknretArchiveIntoPlace', - 'Enable-BaknretPrivilege', - 'ConvertTo-BaknretWildcardPattern', - 'Test-BaknretPathExcluded', - 'Get-BaknretAceSignatureList', - 'Get-BaknretSecuritySddlWithStale', - 'Get-BaknretSecurityRecord', - 'Test-BaknretSecurityRecordNeeded', - 'Get-BaknretSecurityRecords', - 'Write-BaknretAtomicText', - 'Save-BaknretSecuritySidecar', - 'Read-BaknretSecuritySidecar', - 'Convert-BaknretSidMap', - 'Set-BaknretObjectSecurity', - 'Restore-BaknretSecurity', - 'Get-BaknretConfig', - 'Get-BaknretPassword' + 'Get-BakNRetArchiveTopLevelNames', + 'Get-BakNRetArchiveTopName', + 'New-BakNRetArchiveItem', + 'New-BakNRetJunction', + 'Remove-BakNRetJunction', + 'New-BakNRetArchiveStaging', + 'Remove-BakNRetArchiveStaging', + 'Get-BakNRetItemArchiveName', + 'Resolve-BakNRetBackupEntry', + 'Write-BakNRetBackupEntryPlan', + 'Get-BakNRetBackupBaseName', + 'Convert-BakNRetBackupFileNameToPath', + 'Get-BakNRetFolderSummary', + 'Read-BakNRetManifest', + 'Write-BakNRetManifest', + 'Sync-BakNRetManifestArchive', + 'Move-BakNRetArchiveIntoPlace', + 'Enable-BakNRetPrivilege', + 'ConvertTo-BakNRetWildcardPattern', + 'Test-BakNRetPathExcluded', + 'Get-BakNRetAceSignatureList', + 'Get-BakNRetSecuritySddlWithStale', + 'Get-BakNRetSecurityRecord', + 'Test-BakNRetSecurityRecordNeeded', + 'Get-BakNRetSecurityRecords', + 'Write-BakNRetAtomicText', + 'Save-BakNRetSecuritySidecar', + 'Read-BakNRetSecuritySidecar', + 'Convert-BakNRetSidMap', + 'Set-BakNRetObjectSecurity', + 'Restore-BakNRetSecurity', + 'Get-BakNRetConfig', + 'Get-BakNRetPassword' ) diff --git a/BakNRet/Private/Get-BaknretMapKeys.ps1 b/BakNRet/Private/Get-BaknretMapKeys.ps1 index 3b9359b..3b78b81 100644 --- a/BakNRet/Private/Get-BaknretMapKeys.ps1 +++ b/BakNRet/Private/Get-BaknretMapKeys.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretMapKeys { +function Get-BakNRetMapKeys { <# .SYNOPSIS 列出哈希表或 JSON 对象的全部键。 #> param($Map) if ($null -eq $Map) { return @() } diff --git a/BakNRet/Private/Get-BaknretMapValue.ps1 b/BakNRet/Private/Get-BaknretMapValue.ps1 index 22fa8d7..89e1b93 100644 --- a/BakNRet/Private/Get-BaknretMapValue.ps1 +++ b/BakNRet/Private/Get-BaknretMapValue.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretMapValue { +function Get-BakNRetMapValue { <# .SYNOPSIS 从哈希表或 JSON 对象里按键取值。 #> param($Map, [string]$Key) if ($null -eq $Map) { return $null } diff --git a/BakNRet/Private/Import-BaknretDataFile.ps1 b/BakNRet/Private/Import-BaknretDataFile.ps1 index d980c25..1432c60 100644 --- a/BakNRet/Private/Import-BaknretDataFile.ps1 +++ b/BakNRet/Private/Import-BaknretDataFile.ps1 @@ -1,4 +1,4 @@ -function Import-BaknretDataFile { +function Import-BakNRetDataFile { <# .SYNOPSIS 读取 .psd1 / .json 配置数据。 @@ -23,7 +23,7 @@ } catch { $firstLine = ([string]$_.Exception.Message) -split "`r?`n" | Select-Object -First 1 - Write-Log "psd1 里有 Import-PowerShellDataFile 不接受的表达式($firstLine),改用 PowerShell 求值:$Path" -Level DEBUG + Write-BakNRetLog "psd1 里有 Import-PowerShellDataFile 不接受的表达式($firstLine),改用 PowerShell 求值:$Path" -Level DEBUG $raw = [System.IO.File]::ReadAllText($Path) return [scriptblock]::Create($raw).Invoke() } diff --git a/BakNRet/Private/State.ps1 b/BakNRet/Private/State.ps1 index fd69bb0..b91c7cb 100644 --- a/BakNRet/Private/State.ps1 +++ b/BakNRet/Private/State.ps1 @@ -18,5 +18,5 @@ $script:CatalogCache = @{} $script:CatalogExpressionCache = @{} -$script:BaknretPrivilegeState = @{} +$script:BakNRetPrivilegeState = @{} diff --git a/BakNRet/Private/Test-BaknretMapKey.ps1 b/BakNRet/Private/Test-BaknretMapKey.ps1 index ebebe09..ac8acbe 100644 --- a/BakNRet/Private/Test-BaknretMapKey.ps1 +++ b/BakNRet/Private/Test-BaknretMapKey.ps1 @@ -1,4 +1,4 @@ -function Test-BaknretMapKey { +function Test-BakNRetMapKey { <# .SYNOPSIS 判断一个数据对象(哈希表或 JSON 对象)里有没有某个键。 #> param($Map, [string]$Key) if ($null -eq $Map) { return $false } diff --git a/BakNRet/Public/Convert-BackupFileNameToPath.ps1 b/BakNRet/Public/Convert-BakNRetBackupFileNameToPath.ps1 similarity index 82% rename from BakNRet/Public/Convert-BackupFileNameToPath.ps1 rename to BakNRet/Public/Convert-BakNRetBackupFileNameToPath.ps1 index ccd41ac..a5a682f 100644 --- a/BakNRet/Public/Convert-BackupFileNameToPath.ps1 +++ b/BakNRet/Public/Convert-BakNRetBackupFileNameToPath.ps1 @@ -1,4 +1,4 @@ -function Convert-BackupFileNameToPath { +function Convert-BakNRetBackupFileNameToPath { <# .SYNOPSIS 把归档文件名还原成原始路径(用于没有 manifest 时的兜底)。 @@ -22,11 +22,11 @@ }) $reconstructed = ($parts -join '\') + '\' + $folderPart - Write-Log "逆向解析:$FileName -> $reconstructed" -Level DEBUG + Write-BakNRetLog "逆向解析:$FileName -> $reconstructed" -Level DEBUG return $reconstructed } catch { - Write-Log "无法解析备份文件名:$FileName" -Level WARN + Write-BakNRetLog "无法解析备份文件名:$FileName" -Level WARN return $null } } diff --git a/BakNRet/Public/Convert-BaknretSidMap.ps1 b/BakNRet/Public/Convert-BaknretSidMap.ps1 index f272ff5..38d02a7 100644 --- a/BakNRet/Public/Convert-BaknretSidMap.ps1 +++ b/BakNRet/Public/Convert-BaknretSidMap.ps1 @@ -1,4 +1,4 @@ -function Convert-BaknretSidMap { +function Convert-BakNRetSidMap { <# .SYNOPSIS 按 SID 映射表改写 SDDL 里的 SID(跨机恢复用)。 diff --git a/BakNRet/Public/ConvertFrom-BackupListLine.ps1 b/BakNRet/Public/ConvertFrom-BackupListLine.ps1 index 50be85c..2600d81 100644 --- a/BakNRet/Public/ConvertFrom-BackupListLine.ps1 +++ b/BakNRet/Public/ConvertFrom-BackupListLine.ps1 @@ -42,7 +42,7 @@ if ([string]::IsNullOrEmpty($content)) { return $null } } - $tokens = @(Split-BaknretToken -Text $content) + $tokens = @(Split-BakNRetToken -Text $content) if ($tokens.Count -eq 0) { return $null } # 整行被一对引号包住是**历史写法**(`"C:\a b\CodeSpace :: X\"`)。 @@ -56,9 +56,9 @@ $last = $raw[$raw.Length - 1] if ($first -eq $last -and ($first -eq '"' -or $first -eq "'")) { $inner = $raw.Substring(1, $raw.Length - 2) - foreach ($innerToken in @(Split-BaknretToken -Text $inner)) { - if (Test-BaknretMarker -Token $innerToken) { - Write-Log "整行被引号包住,引号里的修饰符不会被识别(历史写法)。请去掉外层引号,并注意现在 `:-` 才是排除、`::` 是覆盖 Path:$Line" -Level WARN + foreach ($innerToken in @(Split-BakNRetToken -Text $inner)) { + if (Test-BakNRetMarker -Token $innerToken) { + Write-BakNRetLog "整行被引号包住,引号里的修饰符不会被识别(历史写法)。请去掉外层引号,并注意现在 `:-` 才是排除、`::` 是覆盖 Path:$Line" -Level WARN break } } @@ -98,11 +98,11 @@ # 所以这里取"第一个修饰符记号之前的全部记号",而不是只取第一个记号。 $firstMarker = -1 for ($index = 0; $index -lt $tokens.Count; $index++) { - if (Test-BaknretMarker -Token $tokens[$index]) { $firstMarker = $index; break } + if (Test-BakNRetMarker -Token $tokens[$index]) { $firstMarker = $index; break } } if ($firstMarker -eq 0) { - Write-Log "清单行缺少目标,已忽略:$Line" -Level WARN + Write-BakNRetLog "清单行缺少目标,已忽略:$Line" -Level WARN return $null } @@ -115,7 +115,7 @@ $markerTokens = @($tokens[$firstMarker..($tokens.Count - 1)]) } - $target = Remove-BaknretQuote -Text $targetText + $target = Remove-BakNRetQuote -Text $targetText if ([string]::IsNullOrWhiteSpace($target)) { return $null } $overrides = @{} @@ -124,24 +124,24 @@ $index = 0 while ($index -lt $markerTokens.Count) { - $kind = Test-BaknretMarker -Token $markerTokens[$index] + $kind = Test-BakNRetMarker -Token $markerTokens[$index] $inline = $null if ($kind -eq 'at') { $inline = $markerTokens[$index].Substring(1) } $index++ $values = @() if (-not [string]::IsNullOrWhiteSpace($inline)) { $values += $inline } - while ($index -lt $markerTokens.Count -and -not (Test-BaknretMarker -Token $markerTokens[$index])) { + while ($index -lt $markerTokens.Count -and -not (Test-BakNRetMarker -Token $markerTokens[$index])) { $values += $markerTokens[$index] $index++ } switch ($kind) { 'path' { - $value = Remove-BaknretQuote -Text ($values -join ' ') + $value = Remove-BakNRetQuote -Text ($values -join ' ') if (-not [string]::IsNullOrWhiteSpace($value)) { if ($overrides.ContainsKey('Path')) { - Write-Log "同一条目里给了多次路径覆盖,用最后一个:$Line" -Level WARN + Write-BakNRetLog "同一条目里给了多次路径覆盖,用最后一个:$Line" -Level WARN } $overrides['Path'] = $value } @@ -149,31 +149,31 @@ # 同类记号可以出现多次(`Foo :- a :- b`),**累积**而不是后者覆盖前者: # 静默丢掉前一条排除规则正是这个工具最不该犯的错。 'exclude' { - $parsed = @(ConvertFrom-BaknretPatternList -Values $values) + $parsed = @(ConvertFrom-BakNRetPatternList -Values $values) if ($overrides.ContainsKey('Exclude')) { $overrides['Exclude'] = @($overrides['Exclude']) + $parsed } else { $overrides['Exclude'] = $parsed } } 'include' { - $parsed = @(ConvertFrom-BaknretPatternList -Values $values) + $parsed = @(ConvertFrom-BakNRetPatternList -Values $values) if ($overrides.ContainsKey('Include')) { $overrides['Include'] = @($overrides['Include']) + $parsed } else { $overrides['Include'] = $parsed } } 'encrypt' { - if ($overrides.ContainsKey('Encrypt')) { Write-Log "同一条目里给了多次加密开关,用最后一个:$Line" -Level WARN } + if ($overrides.ContainsKey('Encrypt')) { Write-BakNRetLog "同一条目里给了多次加密开关,用最后一个:$Line" -Level WARN } $overrides['Encrypt'] = $true } 'noencrypt' { - if ($overrides.ContainsKey('Encrypt')) { Write-Log "同一条目里给了多次加密开关,用最后一个:$Line" -Level WARN } + if ($overrides.ContainsKey('Encrypt')) { Write-BakNRetLog "同一条目里给了多次加密开关,用最后一个:$Line" -Level WARN } $overrides['Encrypt'] = $false } 'at' { - $text = Remove-BaknretQuote -Text ($values -join ' ') + $text = Remove-BakNRetQuote -Text ($values -join ' ') if ([string]::IsNullOrWhiteSpace($text)) { continue } $equals = $text.IndexOf('=') if ($equals -lt 0) { # 兼容历史写法:`@encrypt` / `@!encrypt` / `@pathname` / `@root=名` - foreach ($legacy in @(ConvertFrom-BaknretPatternList -Values @($text))) { + foreach ($legacy in @(ConvertFrom-BakNRetPatternList -Values @($text))) { $name = $legacy.Trim().TrimStart('@') if ($name -ieq 'encrypt') { $overrides['Encrypt'] = $true } elseif ($name -ieq '!encrypt') { $overrides['Encrypt'] = $false } @@ -183,16 +183,16 @@ } $key = $text.Substring(0, $equals).Trim() - $value = Remove-BaknretQuote -Text $text.Substring($equals + 1) + $value = Remove-BakNRetQuote -Text $text.Substring($equals + 1) switch -Regex ($key) { '(?i)^path$' { $overrides['Path'] = $value } '(?i)^exclude$' { - $parsed = @(ConvertFrom-BaknretPatternList -Values @($value)) + $parsed = @(ConvertFrom-BakNRetPatternList -Values @($value)) if ($overrides.ContainsKey('Exclude')) { $overrides['Exclude'] = @($overrides['Exclude']) + $parsed } else { $overrides['Exclude'] = $parsed } } '(?i)^include$' { - $parsed = @(ConvertFrom-BaknretPatternList -Values @($value)) + $parsed = @(ConvertFrom-BakNRetPatternList -Values @($value)) if ($overrides.ContainsKey('Include')) { $overrides['Include'] = @($overrides['Include']) + $parsed } else { $overrides['Include'] = $parsed } } @@ -205,7 +205,7 @@ } foreach ($unknown in $unknownKeys) { - Write-Log "清单里的 @ 字段 '$unknown' 不是已知字段(Path / Exclude / Include / Encrypt),已忽略:$Line" -Level WARN + Write-BakNRetLog "清单里的 @ 字段 '$unknown' 不是已知字段(Path / Exclude / Include / Encrypt),已忽略:$Line" -Level WARN } $resolvedExclude = @() @@ -218,7 +218,7 @@ Path = $target # 目录名或文件名,需要靠 SoftwareCatalog 换成真实路径; # 带分隔符或 %变量% 的写法按字面路径处理。 - IsName = (-not (Test-LiteralPath -Path $target)) + IsName = (-not (Test-BakNRetLiteralPath -Path $target)) Overrides = $overrides ExcludePatterns = $resolvedExclude Includes = $resolvedInclude diff --git a/BakNRet/Public/ConvertFrom-BaknretPatternList.ps1 b/BakNRet/Public/ConvertFrom-BaknretPatternList.ps1 index ecb9fb6..d8cf5f7 100644 --- a/BakNRet/Public/ConvertFrom-BaknretPatternList.ps1 +++ b/BakNRet/Public/ConvertFrom-BaknretPatternList.ps1 @@ -1,4 +1,4 @@ -function ConvertFrom-BaknretPatternList { +function ConvertFrom-BakNRetPatternList { <# .SYNOPSIS 把修饰符的值列表拼成字符串并按 `,` / `;` 拆成多个模式。 @@ -7,7 +7,7 @@ $parts = @() foreach ($value in @($Values)) { - $text = Remove-BaknretQuote -Text ([string]$value) + $text = Remove-BakNRetQuote -Text ([string]$value) if ([string]::IsNullOrWhiteSpace($text)) { continue } $parts += @($text -split '[,;]' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) } diff --git a/BakNRet/Public/ConvertTo-NativeArgumentString.ps1 b/BakNRet/Public/ConvertTo-BakNRetNativeArgumentString.ps1 similarity index 97% rename from BakNRet/Public/ConvertTo-NativeArgumentString.ps1 rename to BakNRet/Public/ConvertTo-BakNRetNativeArgumentString.ps1 index 1bbd001..79dda4d 100644 --- a/BakNRet/Public/ConvertTo-NativeArgumentString.ps1 +++ b/BakNRet/Public/ConvertTo-BakNRetNativeArgumentString.ps1 @@ -1,4 +1,4 @@ -function ConvertTo-NativeArgumentString { +function ConvertTo-BakNRetNativeArgumentString { <# .SYNOPSIS 按 Windows 的命令行引用规则,把参数数组拼成单个命令行字符串。 diff --git a/BakNRet/Public/ConvertTo-BaknretWildcardPattern.ps1 b/BakNRet/Public/ConvertTo-BaknretWildcardPattern.ps1 index 89a4fa9..911e9a1 100644 --- a/BakNRet/Public/ConvertTo-BaknretWildcardPattern.ps1 +++ b/BakNRet/Public/ConvertTo-BaknretWildcardPattern.ps1 @@ -1,10 +1,10 @@ -function ConvertTo-BaknretWildcardPattern { +function ConvertTo-BakNRetWildcardPattern { <# .SYNOPSIS 把 7z 风格的通配符(* 与 ?)转成正则片段。 .DESCRIPTION - 与 Get-BaknretExcludeArgument 保持一致:模式里的空格先转成 `?`(7z 的 + 与 Get-BakNRetExcludeArgument 保持一致:模式里的空格先转成 `?`(7z 的 `-x!` 不接受带空格的模式)。`*` 转 `.*`,跨过路径分隔符, 这样锚定模式 `Default\*` 才能命中 `Default\a\b`。 #> diff --git a/BakNRet/Public/Enable-BaknretPrivilege.ps1 b/BakNRet/Public/Enable-BaknretPrivilege.ps1 index 5aa3cba..91c639e 100644 --- a/BakNRet/Public/Enable-BaknretPrivilege.ps1 +++ b/BakNRet/Public/Enable-BaknretPrivilege.ps1 @@ -1,4 +1,4 @@ -function Enable-BaknretPrivilege { +function Enable-BakNRetPrivilege { <# .SYNOPSIS 在当前进程令牌里启用指定特权,返回哪些没能启用。 @@ -26,9 +26,9 @@ Failed = @() } - if (-not ('Baknret.Privileges' -as [type])) { + if (-not ('BakNRet.Privileges' -as [type])) { try { - Add-Type -Namespace Baknret -Name Privileges -MemberDefinition @' + Add-Type -Namespace BakNRet -Name Privileges -MemberDefinition @' [DllImport("advapi32.dll", SetLastError = true)] static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok); [DllImport("advapi32.dll", SetLastError = true)] @@ -61,7 +61,7 @@ public static int Enable(string name) { '@ } catch { - Write-Log "特权启用代码编译失败(本次不启用任何特权):$($_.Exception.Message)" -Level WARN + Write-BakNRetLog "特权启用代码编译失败(本次不启用任何特权):$($_.Exception.Message)" -Level WARN $result.Failed = @($Name) return $result } @@ -70,12 +70,12 @@ public static int Enable(string name) { $enabled = @(); $missing = @(); $failed = @() foreach ($privilege in @($Name)) { $cacheKey = $privilege - if ($script:BaknretPrivilegeState.ContainsKey($cacheKey)) { - $state = $script:BaknretPrivilegeState[$cacheKey] + if ($script:BakNRetPrivilegeState.ContainsKey($cacheKey)) { + $state = $script:BakNRetPrivilegeState[$cacheKey] } else { - $state = [Baknret.Privileges]::Enable($privilege) - $script:BaknretPrivilegeState[$cacheKey] = $state + $state = [BakNRet.Privileges]::Enable($privilege) + $script:BakNRetPrivilegeState[$cacheKey] = $state } switch ($state) { 0 { $enabled += $privilege } @@ -85,10 +85,10 @@ public static int Enable(string name) { } if ($missing.Count -gt 0) { - Write-Log ("这些特权不在当前令牌里(需要管理员或 SYSTEM):{0} —— 属主将无法改成别的账户,只能恢复 DACL" -f ($missing -join '、')) -Level WARN + Write-BakNRetLog ("这些特权不在当前令牌里(需要管理员或 SYSTEM):{0} —— 属主将无法改成别的账户,只能恢复 DACL" -f ($missing -join '、')) -Level WARN } if ($failed.Count -gt 0) { - Write-Log ("这些特权启用失败:{0}" -f ($failed -join '、')) -Level WARN + Write-BakNRetLog ("这些特权启用失败:{0}" -f ($failed -join '、')) -Level WARN } $result.Enabled = @($enabled) diff --git a/BakNRet/Public/Enter-BaknretRunLock.ps1 b/BakNRet/Public/Enter-BaknretRunLock.ps1 index 4d2f44a..aedffb7 100644 --- a/BakNRet/Public/Enter-BaknretRunLock.ps1 +++ b/BakNRet/Public/Enter-BaknretRunLock.ps1 @@ -1,4 +1,4 @@ -function Enter-BaknretRunLock { +function Enter-BakNRetRunLock { <# .SYNOPSIS 取得"同一份备份目录同一时间只允许一个进程操作"的锁;拿不到时返回 $null。 @@ -8,7 +8,7 @@ "等它跑完"对用户来说和挂住没区别,不如直接说清楚是谁占着。 返回的是一个已打开的文件流。**不需要刻意释放**:进程退出(含 exit)时句柄由系统 - 关闭,锁随之释放。调用方仍应显式调 Exit-BaknretRunLock,让锁的覆盖范围一眼可见。 + 关闭,锁随之释放。调用方仍应显式调 Exit-BakNRetRunLock,让锁的覆盖范围一眼可见。 #> param([Parameter(Mandatory = $true)][string]$Directory) @@ -16,7 +16,7 @@ New-Item -ItemType Directory -Path $Directory -Force | Out-Null } - $path = Get-BaknretRunLockPath -Directory $Directory + $path = Get-BakNRetRunLockPath -Directory $Directory try { $stream = [System.IO.File]::Open( $path, @@ -34,7 +34,7 @@ while ($inner -and $inner -isnot [System.IO.IOException]) { $inner = $inner.InnerException } if (-not $inner) { throw } - Write-Log ("运行锁不可用({0}):{1}" -f $inner.GetType().Name, $inner.Message) -Level DEBUG + Write-BakNRetLog ("运行锁不可用({0}):{1}" -f $inner.GetType().Name, $inner.Message) -Level DEBUG return $null } diff --git a/BakNRet/Public/Exit-BaknretRunLock.ps1 b/BakNRet/Public/Exit-BaknretRunLock.ps1 index 1214681..252705d 100644 --- a/BakNRet/Public/Exit-BaknretRunLock.ps1 +++ b/BakNRet/Public/Exit-BaknretRunLock.ps1 @@ -1,4 +1,4 @@ -function Exit-BaknretRunLock { +function Exit-BakNRetRunLock { <# .SYNOPSIS 释放运行锁。锁文件本身留着 —— 它的内容是最后一次持有者的线索,删不删都无所谓。 @@ -10,6 +10,6 @@ $Lock.Dispose() } catch { - Write-Log "释放运行锁失败(进程退出时会自动释放):$_" -Level WARN + Write-BakNRetLog "释放运行锁失败(进程退出时会自动释放):$_" -Level WARN } } diff --git a/BakNRet/Public/Expand-CatalogPathText.ps1 b/BakNRet/Public/Expand-CatalogPathText.ps1 index 05f2e10..0e19613 100644 --- a/BakNRet/Public/Expand-CatalogPathText.ps1 +++ b/BakNRet/Public/Expand-CatalogPathText.ps1 @@ -48,7 +48,7 @@ if ($null -ne $evaluated) { $replacement = ([string]($evaluated)).Trim() } } catch { - Write-Log "名录路径里的表达式求值失败:$expression —— $($_.Exception.Message)" -Level WARN + Write-BakNRetLog "名录路径里的表达式求值失败:$expression —— $($_.Exception.Message)" -Level WARN } $value = $value.Substring(0, $start) + $replacement + $value.Substring($end + 1) } diff --git a/BakNRet/Public/Find-ChildDirectoryByName.ps1 b/BakNRet/Public/Find-BakNRetChildDirectoryByName.ps1 similarity index 94% rename from BakNRet/Public/Find-ChildDirectoryByName.ps1 rename to BakNRet/Public/Find-BakNRetChildDirectoryByName.ps1 index 5a9648f..5012327 100644 --- a/BakNRet/Public/Find-ChildDirectoryByName.ps1 +++ b/BakNRet/Public/Find-BakNRetChildDirectoryByName.ps1 @@ -1,4 +1,4 @@ -function Find-ChildDirectoryByName { +function Find-BakNRetChildDirectoryByName { <# .SYNOPSIS 在 $Parent 下按精确名或"<名>_<后缀>"/"<名>-<后缀>"形式找目录。 diff --git a/BakNRet/Public/Format-CatalogName.ps1 b/BakNRet/Public/Format-BakNRetCatalogName.ps1 similarity index 94% rename from BakNRet/Public/Format-CatalogName.ps1 rename to BakNRet/Public/Format-BakNRetCatalogName.ps1 index 9c87960..358ca96 100644 --- a/BakNRet/Public/Format-CatalogName.ps1 +++ b/BakNRet/Public/Format-BakNRetCatalogName.ps1 @@ -1,4 +1,4 @@ -function Format-CatalogName { +function Format-BakNRetCatalogName { <# .SYNOPSIS 把软件名规范化成合法的归档基础名。 diff --git a/BakNRet/Public/Get-ArchiveTopLevelNames.ps1 b/BakNRet/Public/Get-BakNRetArchiveTopLevelNames.ps1 similarity index 92% rename from BakNRet/Public/Get-ArchiveTopLevelNames.ps1 rename to BakNRet/Public/Get-BakNRetArchiveTopLevelNames.ps1 index 5a95fe1..c2d8167 100644 --- a/BakNRet/Public/Get-ArchiveTopLevelNames.ps1 +++ b/BakNRet/Public/Get-BakNRetArchiveTopLevelNames.ps1 @@ -1,4 +1,4 @@ -function Get-ArchiveTopLevelNames { +function Get-BakNRetArchiveTopLevelNames { <# .SYNOPSIS 列出归档内的顶层条目名(用于确认多目录打包时每个目录都真的进去了)。 @@ -36,7 +36,7 @@ Select-Object -ExpandProperty Name) } catch { - Write-Log "无法清点归档内容(跳过顶层名核对):$($_.Exception.Message)" -Level DEBUG + Write-BakNRetLog "无法清点归档内容(跳过顶层名核对):$($_.Exception.Message)" -Level DEBUG $names = @() } finally { diff --git a/BakNRet/Public/Get-BackupBaseName.ps1 b/BakNRet/Public/Get-BakNRetBackupBaseName.ps1 similarity index 88% rename from BakNRet/Public/Get-BackupBaseName.ps1 rename to BakNRet/Public/Get-BakNRetBackupBaseName.ps1 index 15b347a..55a6ec2 100644 --- a/BakNRet/Public/Get-BackupBaseName.ps1 +++ b/BakNRet/Public/Get-BakNRetBackupBaseName.ps1 @@ -1,4 +1,4 @@ -function Get-BackupBaseName { +function Get-BakNRetBackupBaseName { <# .SYNOPSIS 由清单中的原始路径生成归档基础名。 @@ -16,7 +16,7 @@ $parts = @($normalized -split '\\' | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) if ($parts.Count -eq 0) { - Write-Log "无法解析路径:$RawPath" -Level ERROR + Write-BakNRetLog "无法解析路径:$RawPath" -Level ERROR return $null } @@ -39,6 +39,6 @@ }) $baseName = $baseName -replace ':', '_' - Write-Log "生成文件基础名:$baseName" -Level DEBUG + Write-BakNRetLog "生成文件基础名:$baseName" -Level DEBUG return $baseName } diff --git a/BakNRet/Public/Get-FolderSummary.ps1 b/BakNRet/Public/Get-BakNRetFolderSummary.ps1 similarity index 92% rename from BakNRet/Public/Get-FolderSummary.ps1 rename to BakNRet/Public/Get-BakNRetFolderSummary.ps1 index 5fc8600..da27f32 100644 --- a/BakNRet/Public/Get-FolderSummary.ps1 +++ b/BakNRet/Public/Get-BakNRetFolderSummary.ps1 @@ -1,4 +1,4 @@ -function Get-FolderSummary { +function Get-BakNRetFolderSummary { <# .SYNOPSIS 统计目录/文件的文件数、总大小与最新修改时间。 @@ -26,7 +26,7 @@ } } catch { - Write-Log "无法读取文件夹摘要:$FolderPath" -Level WARN + Write-BakNRetLog "无法读取文件夹摘要:$FolderPath" -Level WARN return [pscustomobject]@{ FileCount = 0 TotalSize = 0 diff --git a/BakNRet/Public/Get-ItemArchiveName.ps1 b/BakNRet/Public/Get-BakNRetItemArchiveName.ps1 similarity index 60% rename from BakNRet/Public/Get-ItemArchiveName.ps1 rename to BakNRet/Public/Get-BakNRetItemArchiveName.ps1 index 22d6ec7..a81068b 100644 --- a/BakNRet/Public/Get-ItemArchiveName.ps1 +++ b/BakNRet/Public/Get-BakNRetItemArchiveName.ps1 @@ -1,4 +1,4 @@ -function Get-ItemArchiveName { +function Get-BakNRetItemArchiveName { <# .SYNOPSIS 决定一个条目的归档基础名(不含扩展名)。 @@ -14,27 +14,27 @@ # @pathname 时用"真实路径"跑路径命名算法。 # 清单里写的可能是软件名,必须先经名录换成真实路径, - # 否则 Get-BackupBaseName 会对软件名本身运算,得出错误的名字。 + # 否则 Get-BakNRetBackupBaseName 会对软件名本身运算,得出错误的名字。 if ($Entry.Flags -contains 'pathname') { $nameSource = $Entry.Path - if (-not (Test-LiteralPath -Path $Entry.Path)) { - $catalogForPath = Get-SoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth + if (-not (Test-BakNRetLiteralPath -Path $Entry.Path)) { + $catalogForPath = Get-BakNRetSoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth if ($catalogForPath.ContainsKey($Entry.Path)) { $nameSource = $catalogForPath[$Entry.Path].Path } } - return Get-BackupBaseName -RawPath $nameSource + return Get-BakNRetBackupBaseName -RawPath $nameSource } - $looksLikePath = Test-LiteralPath -Path $Entry.Path + $looksLikePath = Test-BakNRetLiteralPath -Path $Entry.Path if (-not $looksLikePath) { - $catalog = Get-SoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth + $catalog = Get-BakNRetSoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth if ($catalog.ContainsKey($Entry.Path)) { return $catalog[$Entry.Path].Name } - Write-Log "名录里没有 '$($Entry.Path)',按目录名处理" -Level WARN - return (Format-CatalogName -Name $Entry.Path) + Write-BakNRetLog "名录里没有 '$($Entry.Path)',按目录名处理" -Level WARN + return (Format-BakNRetCatalogName -Name $Entry.Path) } - return Get-BackupBaseName -RawPath $Entry.Path + return Get-BakNRetBackupBaseName -RawPath $Entry.Path } diff --git a/BakNRet/Public/Get-SoftwareCatalog.ps1 b/BakNRet/Public/Get-BakNRetSoftwareCatalog.ps1 similarity index 79% rename from BakNRet/Public/Get-SoftwareCatalog.ps1 rename to BakNRet/Public/Get-BakNRetSoftwareCatalog.ps1 index 4a77b7b..78c946f 100644 --- a/BakNRet/Public/Get-SoftwareCatalog.ps1 +++ b/BakNRet/Public/Get-BakNRetSoftwareCatalog.ps1 @@ -1,4 +1,4 @@ -function Get-SoftwareCatalog { +function Get-BakNRetSoftwareCatalog { <# .SYNOPSIS 载入"软件名 -> Slot 组"名录。 @@ -44,7 +44,7 @@ if (-not $NoCache) { try { $item = Get-Item -LiteralPath $Path -ErrorAction Stop - $hash = (Get-FileHash -LiteralPath $Path -Algorithm MD5 -ErrorAction Stop).Hash + $hash = (Get-FileHash -LiteralPath $Path -Algorithm SHA256 -ErrorAction Stop).Hash $stamp = '{0}-{1}-{2}' -f $item.LastWriteTimeUtc.Ticks, $item.Length, $hash if ($script:CatalogCache.ContainsKey($Path) -and $script:CatalogCache[$Path].Stamp -eq $stamp) { return $script:CatalogCache[$Path].Data @@ -57,22 +57,22 @@ $data = $null try { - $data = Import-BaknretDataFile -Path $Path + $data = Import-BakNRetDataFile -Path $Path } catch { - Write-Log "软件名录读取失败:$Path —— $_" -Level ERROR + Write-BakNRetLog "软件名录读取失败:$Path —— $_" -Level ERROR return $result } # 递归引入其它名录文件(路径相对本文件) - $includeValue = Get-BaknretMapValue -Map $data -Key 'Includes' + $includeValue = Get-BakNRetMapValue -Map $data -Key 'Includes' if ($includeValue) { $baseDir = Split-Path -Parent $Path foreach ($include in @($includeValue)) { if (-not $include) { continue } $includePath = [string]$include if (-not [System.IO.Path]::IsPathRooted($includePath)) { $includePath = Join-Path $baseDir $includePath } - $included = Get-SoftwareCatalog -Path $includePath -MaxDepth $MaxDepth + $included = Get-BakNRetSoftwareCatalog -Path $includePath -MaxDepth $MaxDepth foreach ($includedName in $included.Keys) { if ($result.ContainsKey($includedName)) { continue } $result[$includedName] = $included[$includedName] @@ -80,30 +80,30 @@ } } - foreach ($key in @(Get-BaknretMapKeys -Map $data | Where-Object { $_ -ne 'Includes' })) { - $name = Format-CatalogName -Name ([string]$key) + foreach ($key in @(Get-BakNRetMapKeys -Map $data | Where-Object { $_ -ne 'Includes' })) { + $name = Format-BakNRetCatalogName -Name ([string]$key) if (-not $name) { continue } - $raw = Get-BaknretMapValue -Map $data -Key $key + $raw = Get-BakNRetMapValue -Map $data -Key $key if ($raw -isnot [System.Collections.IDictionary] -and $null -ne $raw -and -not ($raw -is [psobject] -and @($raw.PSObject.Properties.Name).Count -gt 0)) { - Write-Log "名录条目 '$key' 格式不对:应写成 @{ = @{ Path = '...' } }" -Level ERROR + Write-BakNRetLog "名录条目 '$key' 格式不对:应写成 @{ = @{ Path = '...' } }" -Level ERROR continue } $slots = @() $errors = @() - foreach ($slotKey in @(Get-BaknretMapKeys -Map $raw)) { + foreach ($slotKey in @(Get-BakNRetMapKeys -Map $raw)) { $slotName = ([string]$slotKey).Trim() if (-not $slotName) { continue } - $slotRaw = Get-BaknretMapValue -Map $raw -Key $slotKey + $slotRaw = Get-BakNRetMapValue -Map $raw -Key $slotKey if ($slotRaw -isnot [System.Collections.IDictionary] -and -not ($slotRaw -is [psobject])) { $errors += "Slot $slotName 的写法不对,应写成 @{ Path = '...' }" continue } - $declaredRaw = [string](Get-BaknretMapValue -Map $slotRaw -Key 'Path') + $declaredRaw = [string](Get-BakNRetMapValue -Map $slotRaw -Key 'Path') if ([string]::IsNullOrWhiteSpace($declaredRaw)) { $errors += "Slot $slotName 缺少 Path" continue @@ -125,7 +125,7 @@ $parent = Split-Path -Path $declared -Parent $leafName = Split-Path -Path $declared -Leaf if ($parent -and $leafName -and (Test-Path -LiteralPath $parent)) { - $candidates = @(Find-ChildDirectoryByName -Parent $parent -Name $leafName -MaxDepth $MaxDepth) + $candidates = @(Find-BakNRetChildDirectoryByName -Parent $parent -Name $leafName -MaxDepth $MaxDepth) } } @@ -144,10 +144,10 @@ if ($resolvedItem) { $isFile = -not $resolvedItem.PSIsContainer } } - $excludeText = [string](Get-BaknretMapValue -Map $slotRaw -Key 'Exclude') - $includeText = [string](Get-BaknretMapValue -Map $slotRaw -Key 'Include') - $encryptValue = Get-BaknretMapValue -Map $slotRaw -Key 'Encrypt' - $description = [string](Get-BaknretMapValue -Map $slotRaw -Key 'Description') + $excludeText = [string](Get-BakNRetMapValue -Map $slotRaw -Key 'Exclude') + $includeText = [string](Get-BakNRetMapValue -Map $slotRaw -Key 'Include') + $encryptValue = Get-BakNRetMapValue -Map $slotRaw -Key 'Encrypt' + $description = [string](Get-BakNRetMapValue -Map $slotRaw -Key 'Description') $slots += [pscustomobject]@{ Name = $slotName @@ -157,8 +157,8 @@ IsFile = $isFile Suffixed = $suffixed Description = $description - Exclude = @(ConvertFrom-BaknretPatternList -Values @($excludeText)) - Include = @(ConvertFrom-BaknretPatternList -Values @($includeText)) + Exclude = @(ConvertFrom-BakNRetPatternList -Values @($excludeText)) + Include = @(ConvertFrom-BakNRetPatternList -Values @($includeText)) Encrypt = [bool]$encryptValue } } @@ -178,18 +178,18 @@ else { 'Single' } if ($errors.Count -gt 0) { - Write-Log ("名录条目 {0} 有问题:{1}" -f $name, ($errors -join ';')) -Level ERROR + Write-BakNRetLog ("名录条目 {0} 有问题:{1}" -f $name, ($errors -join ';')) -Level ERROR } elseif ($missing.Count -gt 0) { - Write-Log ("名录:{0} 有 {1} 个 Slot 的路径当前不存在:{2}" -f $name, $missing.Count, (($missing | ForEach-Object { $_.Declared }) -join ';')) -Level DEBUG + Write-BakNRetLog ("名录:{0} 有 {1} 个 Slot 的路径当前不存在:{2}" -f $name, $missing.Count, (($missing | ForEach-Object { $_.Declared }) -join ';')) -Level DEBUG } if ($slots.Count -gt 0) { - Write-Log ("名录:{0} -> {1} 个 Slot,其中存在 {2} 个" -f $name, $slots.Count, $existing.Count) -Level DEBUG + Write-BakNRetLog ("名录:{0} -> {1} 个 Slot,其中存在 {2} 个" -f $name, $slots.Count, $existing.Count) -Level DEBUG } if ($result.ContainsKey($name)) { - Write-Log ("名录里有两条规范化之后同名的条目:{0}(后者覆盖前者)" -f $name) -Level WARN + Write-BakNRetLog ("名录里有两条规范化之后同名的条目:{0}(后者覆盖前者)" -f $name) -Level WARN } $result[$name] = [pscustomobject]@{ diff --git a/BakNRet/Public/Get-BaknretAceSignatureList.ps1 b/BakNRet/Public/Get-BaknretAceSignatureList.ps1 index d2274c6..007f12c 100644 --- a/BakNRet/Public/Get-BaknretAceSignatureList.ps1 +++ b/BakNRet/Public/Get-BaknretAceSignatureList.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretAceSignatureList { +function Get-BakNRetAceSignatureList { <# .SYNOPSIS 把 ACE 列表压成可比对的"签名"集合(`类型|SID|掩码`)。 diff --git a/BakNRet/Public/Get-BaknretArchiveTopName.ps1 b/BakNRet/Public/Get-BaknretArchiveTopName.ps1 index 0710f2b..941ce97 100644 --- a/BakNRet/Public/Get-BaknretArchiveTopName.ps1 +++ b/BakNRet/Public/Get-BaknretArchiveTopName.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretArchiveTopName { +function Get-BakNRetArchiveTopName { <# .SYNOPSIS 取归档内相对路径的第一段(顶层名字)。 #> param([AllowEmptyString()][string]$ArchivePath) diff --git a/BakNRet/Public/Get-BaknretConfig.ps1 b/BakNRet/Public/Get-BaknretConfig.ps1 index c84e5c0..0c921cc 100644 --- a/BakNRet/Public/Get-BaknretConfig.ps1 +++ b/BakNRet/Public/Get-BaknretConfig.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretConfig { +function Get-BakNRetConfig { <# .SYNOPSIS 读取 BackupConfig.psd1 并与内置默认值合并。 @@ -22,7 +22,7 @@ Snapshot = @{ Enabled = $false; KeepCount = 3; KeepDays = 30 } Encryption = @{ Enabled = $false; PasswordFile = ''; EncryptHeaders = $true } # 安全描述符(属主 / ACL)的采集与回放。 - # Mode Off | Roots | Smart | Full(语义见 Get-BaknretSecurityRecords) + # Mode Off | Roots | Smart | Full(语义见 Get-BakNRetSecurityRecords) # **默认 Full**:这个功能存在的意义就是不丢权限,正确性优先于体积; # Smart 是体积优化(靠继承复现的对象不落盘),已在真机上见过 # 它需要处理的"陈旧继承 ACE",判据偏保守,但终究是启发式。 @@ -43,10 +43,10 @@ } try { - $loaded = Import-BaknretDataFile -Path $Path + $loaded = Import-BakNRetDataFile -Path $Path } catch { - Write-Log "配置文件读取失败(改用默认值):$Path —— $_" -Level WARN + Write-BakNRetLog "配置文件读取失败(改用默认值):$Path —— $_" -Level WARN return $defaults } diff --git a/BakNRet/Public/Get-BaknretExcludeArgument.ps1 b/BakNRet/Public/Get-BaknretExcludeArgument.ps1 index 4478509..ad1f116 100644 --- a/BakNRet/Public/Get-BaknretExcludeArgument.ps1 +++ b/BakNRet/Public/Get-BaknretExcludeArgument.ps1 @@ -1,15 +1,15 @@ -function Get-BaknretExcludeArgument { +function Get-BakNRetExcludeArgument { <# .SYNOPSIS 把一个归档项的模式列表翻译成 7z 的 `-x!` / `-xr!` 参数。 .DESCRIPTION - 传进来的模式**已经按项分配好**(见 Split-BaknretPatternScope),因此这里 + 传进来的模式**已经按项分配好**(见 Split-BakNRetPatternScope),因此这里 拿到的模式一律是"相对该项归档根"的: * `<相对路径>` -> `-x!\<相对路径>`(锚定在归档根) * `!<通配>` -> `-xr!<通配>`(任意层级,模式里的空格自动转 `?`) - * `!re:<正则>` -> 遍历源目录翻译成若干 `-x!<完整路径>`(见 Get-BaknretRegexExclude) + * `!re:<正则>` -> 遍历源目录翻译成若干 `-x!<完整路径>`(见 Get-BakNRetRegexExclude) 7z 排除语义(已实测确认): * `-x!<完整归档内路径>` 匹配对象的完整路径,所以要带上项自己的归档根名; @@ -33,7 +33,7 @@ if ($text.StartsWith('!re:')) { $regexText = $text.Substring(4).Trim() if (-not $regexText) { continue } - $expanded = Get-BaknretRegexExclude -Item $Item -Pattern $regexText -MaxMatches $MaxRegexMatches + $expanded = Get-BakNRetRegexExclude -Item $Item -Pattern $regexText -MaxMatches $MaxRegexMatches if ($expanded.Error) { $errorText = $expanded.Error; continue } $arguments += @($expanded.Arguments) continue diff --git a/BakNRet/Public/Get-BaknretFreeSpaceGB.ps1 b/BakNRet/Public/Get-BaknretFreeSpaceGB.ps1 index ba57884..5ce4dfa 100644 --- a/BakNRet/Public/Get-BaknretFreeSpaceGB.ps1 +++ b/BakNRet/Public/Get-BaknretFreeSpaceGB.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretFreeSpaceGB { +function Get-BakNRetFreeSpaceGB { <# .SYNOPSIS 返回 $Path 所在卷的剩余空间(GB);无法确定时返回 -1。 diff --git a/BakNRet/Public/Get-BaknretLogPath.ps1 b/BakNRet/Public/Get-BaknretLogPath.ps1 index cd68b2a..03c0123 100644 --- a/BakNRet/Public/Get-BaknretLogPath.ps1 +++ b/BakNRet/Public/Get-BaknretLogPath.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretLogPath { +function Get-BakNRetLogPath { <# .SYNOPSIS 返回当前日志文件路径(未启用时返回 $null)。 #> return $script:LogConfig.FilePath } diff --git a/BakNRet/Public/Get-BaknretPassword.ps1 b/BakNRet/Public/Get-BaknretPassword.ps1 index d3a1ec4..7145bc9 100644 --- a/BakNRet/Public/Get-BaknretPassword.ps1 +++ b/BakNRet/Public/Get-BaknretPassword.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretPassword { +function Get-BakNRetPassword { <# .SYNOPSIS 取加密口令:命令行参数 > 环境变量 > 密码文件 > 交互式询问。 @@ -34,7 +34,7 @@ try { $interactive = -not [System.Console]::IsInputRedirected } catch { $interactive = $false } if ($interactive) { - Write-Log '需要加密口令,请在弹出的提示里输入(不会回显、不会落盘)' -Level WARN + Write-BakNRetLog '需要加密口令,请在弹出的提示里输入(不会回显、不会落盘)' -Level WARN try { $secure = Read-Host -Prompt '请输入加密口令' -AsSecureString $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure) @@ -46,7 +46,7 @@ } } catch { - Write-Log "口令输入失败:$_" -Level ERROR + Write-BakNRetLog "口令输入失败:$_" -Level ERROR return $null } } diff --git a/BakNRet/Public/Get-BaknretRegexExclude.ps1 b/BakNRet/Public/Get-BaknretRegexExclude.ps1 index a0a6f01..e60d46e 100644 --- a/BakNRet/Public/Get-BaknretRegexExclude.ps1 +++ b/BakNRet/Public/Get-BaknretRegexExclude.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretRegexExclude { +function Get-BakNRetRegexExclude { <# .SYNOPSIS 把一条 `!re:<正则>` 展开成若干 `-x!<归档内路径>` 参数。 diff --git a/BakNRet/Public/Get-BaknretRunLockPath.ps1 b/BakNRet/Public/Get-BaknretRunLockPath.ps1 index e357548..be46636 100644 --- a/BakNRet/Public/Get-BaknretRunLockPath.ps1 +++ b/BakNRet/Public/Get-BaknretRunLockPath.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretRunLockPath { +function Get-BakNRetRunLockPath { <# .SYNOPSIS 运行锁文件的位置(放在备份目录里,与它保护的账本同处)。 #> param([Parameter(Mandatory = $true)][string]$Directory) return (Join-Path $Directory '.baknret.lock') diff --git a/BakNRet/Public/Get-BaknretSecurityRecord.ps1 b/BakNRet/Public/Get-BaknretSecurityRecord.ps1 index 1ce6519..c008372 100644 --- a/BakNRet/Public/Get-BaknretSecurityRecord.ps1 +++ b/BakNRet/Public/Get-BaknretSecurityRecord.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretSecurityRecord { +function Get-BakNRetSecurityRecord { <# .SYNOPSIS 读一个对象的安全描述符,产出可序列化的一条记录。 @@ -58,8 +58,8 @@ } try { - # 陈旧继承 ACE 要固化成显式 ACE,否则恢复后会消失(见 Get-BaknretSecuritySddlWithStale) - $record.s = Get-BaknretSecuritySddlWithStale -Acl $acl -ParentSignatures $ParentSignatures + # 陈旧继承 ACE 要固化成显式 ACE,否则恢复后会消失(见 Get-BakNRetSecuritySddlWithStale) + $record.s = Get-BakNRetSecuritySddlWithStale -Acl $acl -ParentSignatures $ParentSignatures } catch { $record.e = $_.Exception.Message @@ -80,8 +80,8 @@ $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))) + $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))) { diff --git a/BakNRet/Public/Get-BaknretSecurityRecords.ps1 b/BakNRet/Public/Get-BaknretSecurityRecords.ps1 index c2f79e2..85ca161 100644 --- a/BakNRet/Public/Get-BaknretSecurityRecords.ps1 +++ b/BakNRet/Public/Get-BaknretSecurityRecords.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretSecurityRecords { +function Get-BakNRetSecurityRecords { <# .SYNOPSIS 采集一组归档项的安全描述符,键是**归档内相对路径**(`\…`)。 @@ -10,7 +10,7 @@ 遍历用显式栈,并且**跳过 reparse point**:PS 5.1 的 Get-ChildItem -Recurse 会 跟着 junction 无限转;scoop 的 `apps\\current` 就是 junction,正撞在这个坑上。 - $ScopeMap 由 Split-BaknretPatternScope 产出(项下标 -> 该相对根的模式数组), + $ScopeMap 由 Split-BakNRetPatternScope 产出(项下标 -> 该相对根的模式数组), 所以这里的排除判定与真正交给 7z 的 -x! / -xr! 是同一套规则。 Mode: @@ -37,7 +37,7 @@ # 读不到的对象会带 e 字段落进 sidecar,而不是被静默当成"没有特殊权限"。 $privileges = @('SeBackupPrivilege') if ($IncludeSacl) { $privileges += 'SeSecurityPrivilege' } - Enable-BaknretPrivilege -Name $privileges | Out-Null + Enable-BakNRetPrivilege -Name $privileges | Out-Null for ($index = 0; $index -lt $Items.Count; $index++) { $item = $Items[$index] @@ -55,14 +55,14 @@ if (-not $rootItem) { continue } if (-not $rootItem.PSIsContainer) { - $record = Get-BaknretSecurityRecord -Path $real -Key $archiveRoot -Kind 'f' -IncludeSacl:$IncludeSacl + $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 + $rootRecord = Get-BakNRetSecurityRecord -Path $real -Key $archiveRoot -Kind 'd' -IncludeSacl:$IncludeSacl $scanned++ if ($rootRecord.e) { $errorCount++ } $records.Add($rootRecord) @@ -85,10 +85,10 @@ 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 } + if (Test-BakNRetPathExcluded -RelativePath $childRel -Patterns $patterns) { continue } $kind = if ($child.PSIsContainer) { 'd' } else { 'f' } - $record = Get-BaknretSecurityRecord -Path $child.FullName -Key ($archiveRoot + '\' + $childRel) ` + $record = Get-BakNRetSecurityRecord -Path $child.FullName -Key ($archiveRoot + '\' + $childRel) ` -Kind $kind -IncludeSacl:$IncludeSacl -ParentSignatures $frame.Signatures $scanned++ if ($record.e) { $errorCount++ } @@ -96,7 +96,7 @@ if ($Mode -eq 'Full') { $records.Add($record) } - elseif (Test-BaknretSecurityRecordNeeded -Record $record ` + elseif (Test-BakNRetSecurityRecordNeeded -Record $record ` -ParentOwner $frame.Owner -ParentGroup $frame.Group ` -ParentInheritable $frame.Inheritable -ParentSignatures $frame.Signatures) { $records.Add($record) diff --git a/BakNRet/Public/Get-BaknretSecuritySddlWithStale.ps1 b/BakNRet/Public/Get-BaknretSecuritySddlWithStale.ps1 index 2b60ea2..218e83b 100644 --- a/BakNRet/Public/Get-BaknretSecuritySddlWithStale.ps1 +++ b/BakNRet/Public/Get-BaknretSecuritySddlWithStale.ps1 @@ -1,4 +1,4 @@ -function Get-BaknretSecuritySddlWithStale { +function Get-BakNRetSecuritySddlWithStale { <# .SYNOPSIS 对象与父目录的继承链**不自洽**时,把整套 ACE 冻结成显式副本(并置 protected), @@ -38,7 +38,7 @@ # 自洽 = 继承来的 ACE 每一条都能在父目录的 ACL 里找到出处 $stale = @() foreach ($rule in $inherited) { - $signature = @(Get-BaknretAceSignatureList -Rules @($rule))[0] + $signature = @(Get-BakNRetAceSignatureList -Rules @($rule))[0] if ($ParentSignatures -notcontains $signature) { $stale += $rule } } if ($stale.Count -eq 0) { return $Acl.Sddl } @@ -68,6 +68,6 @@ $rebuilt.SetAccessRuleProtection($true, $false) - Write-Log ("{0} 条继承 ACE 已无出处(父目录里找不到),整套 ACE 冻结为显式并置 protected" -f $stale.Count) -Level DEBUG + Write-BakNRetLog ("{0} 条继承 ACE 已无出处(父目录里找不到),整套 ACE 冻结为显式并置 protected" -f $stale.Count) -Level DEBUG return $rebuilt.GetSecurityDescriptorSddlForm($sections) } diff --git a/BakNRet/Public/Get-Optimized7zArgument.ps1 b/BakNRet/Public/Get-Optimized7zArgument.ps1 index 49eb12e..1fd5924 100644 --- a/BakNRet/Public/Get-Optimized7zArgument.ps1 +++ b/BakNRet/Public/Get-Optimized7zArgument.ps1 @@ -27,12 +27,12 @@ $fileCount++ $totalSize += [int64]$item.Length } - Write-Log ("分析路径 '{0}':已累计 {1} 个文件,{2} MB" -f $path, $fileCount, [math]::Round($totalSize / 1MB, 2)) -Level DEBUG + Write-BakNRetLog ("分析路径 '{0}':已累计 {1} 个文件,{2} MB" -f $path, $fileCount, [math]::Round($totalSize / 1MB, 2)) -Level DEBUG } if ($null -eq $totalSize) { $totalSize = 0 } $totalSizeMB = [math]::Round($totalSize / 1MB, 2) - Write-Log ("合计分析:{0} 个文件,总大小 {1} MB" -f $fileCount, $totalSizeMB) -Level DEBUG + Write-BakNRetLog ("合计分析:{0} 个文件,总大小 {1} MB" -f $fileCount, $totalSizeMB) -Level DEBUG if ($totalSizeMB -gt 1024) { $dictSize = '1024m' } elseif ($totalSizeMB -gt 100) { $dictSize = '256m' } @@ -47,7 +47,7 @@ $threads = 2 } - Write-Log ("参数优化:字典=$dictSize, 线程=$threads, 级别=$Level") -Level DEBUG + Write-BakNRetLog ("参数优化:字典=$dictSize, 线程=$threads, 级别=$Level") -Level DEBUG return [pscustomobject]@{ # 只放压缩相关开关。输出开关(-bso0/-bsp0 或默认进度)必须由调用方 diff --git a/BakNRet/Public/Invoke-ExternalCommand.ps1 b/BakNRet/Public/Invoke-ExternalCommand.ps1 index 4066850..8fc396a 100644 --- a/BakNRet/Public/Invoke-ExternalCommand.ps1 +++ b/BakNRet/Public/Invoke-ExternalCommand.ps1 @@ -20,7 +20,7 @@ $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $FilePath - $startInfo.Arguments = ConvertTo-NativeArgumentString -ArgumentList $ArgumentList + $startInfo.Arguments = ConvertTo-BakNRetNativeArgumentString -ArgumentList $ArgumentList $startInfo.UseShellExecute = $false $startInfo.CreateNoWindow = $false if ($WorkingDirectory) { @@ -37,7 +37,7 @@ $loggableArguments = @($ArgumentList | ForEach-Object { if ($_ -is [string] -and $_ -like '-p*') { '-p<口令已隐藏>' } else { $_ } }) - Write-Log ('执行: {0} {1}' -f $FilePath, (ConvertTo-NativeArgumentString -ArgumentList $loggableArguments)) -Level DEBUG + Write-BakNRetLog ('执行: {0} {1}' -f $FilePath, (ConvertTo-BakNRetNativeArgumentString -ArgumentList $loggableArguments)) -Level DEBUG $process = [System.Diagnostics.Process]::Start($startInfo) try { diff --git a/BakNRet/Public/Merge-BaknretExcludeArgument.ps1 b/BakNRet/Public/Merge-BaknretExcludeArgument.ps1 index d13dece..2c24e14 100644 --- a/BakNRet/Public/Merge-BaknretExcludeArgument.ps1 +++ b/BakNRet/Public/Merge-BaknretExcludeArgument.ps1 @@ -1,4 +1,4 @@ -function Merge-BaknretExcludeArgument { +function Merge-BakNRetExcludeArgument { <# .SYNOPSIS 合并多个归档项展开出来的排除参数并去重(保序)。 diff --git a/BakNRet/Public/Move-BaknretArchiveIntoPlace.ps1 b/BakNRet/Public/Move-BaknretArchiveIntoPlace.ps1 index df02102..df13fa0 100644 --- a/BakNRet/Public/Move-BaknretArchiveIntoPlace.ps1 +++ b/BakNRet/Public/Move-BaknretArchiveIntoPlace.ps1 @@ -1,4 +1,4 @@ -function Move-BaknretArchiveIntoPlace { +function Move-BakNRetArchiveIntoPlace { <# .SYNOPSIS 把临时归档原子地替换到最终路径。 @@ -23,7 +23,7 @@ return } catch { - Write-Log "File.Move(overwrite) 不可用(5.1 没有这个重载),改用 File.Replace:$_" -Level DEBUG + Write-BakNRetLog "File.Move(overwrite) 不可用(5.1 没有这个重载),改用 File.Replace:$_" -Level DEBUG } # 5.1 走的这条。以前是"先删后移"—— 中途失败会让目标文件消失(旧归档没了、新归档还在 diff --git a/BakNRet/Public/New-BaknretArchiveItem.ps1 b/BakNRet/Public/New-BaknretArchiveItem.ps1 index 280d086..56da3d3 100644 --- a/BakNRet/Public/New-BaknretArchiveItem.ps1 +++ b/BakNRet/Public/New-BaknretArchiveItem.ps1 @@ -1,4 +1,4 @@ -function New-BaknretArchiveItem { +function New-BakNRetArchiveItem { <# .SYNOPSIS 构造一个"归档项":宿主机上的一个目录 / 文件,对应归档内的一条路径。 @@ -27,7 +27,7 @@ $clean = ([string]$ArchivePath).Trim().Trim([char[]]@('\', '/')) return [pscustomobject]@{ ArchivePath = $clean - TopName = (Get-BaknretArchiveTopName -ArchivePath $clean) + TopName = (Get-BakNRetArchiveTopName -ArchivePath $clean) RealPath = $RealPath Kind = $Kind Slot = $Slot diff --git a/BakNRet/Public/New-BaknretArchiveStaging.ps1 b/BakNRet/Public/New-BaknretArchiveStaging.ps1 index ebdf12d..e7e4232 100644 --- a/BakNRet/Public/New-BaknretArchiveStaging.ps1 +++ b/BakNRet/Public/New-BaknretArchiveStaging.ps1 @@ -1,4 +1,4 @@ -function New-BaknretArchiveStaging { +function New-BakNRetArchiveStaging { <# .SYNOPSIS 建一个暂存目录,把每个归档项按"归档内的名字"挂进去,供压缩工具直接打包。 @@ -10,7 +10,7 @@ * 目录项 -> 建 junction(不复制数据,等于零成本改名); * 文件项 -> 先试硬链接(同卷),失败再复制(配置文件都很小)。 - 返回暂存目录路径;调用方用完必须调 Remove-BaknretArchiveStaging 清理。 + 返回暂存目录路径;调用方用完必须调 Remove-BakNRetArchiveStaging 清理。 建不出连接点时**明确抛错**,绝不悄悄退化成另一种归档布局 —— 布局一变,恢复就对不上。 #> param( @@ -26,7 +26,7 @@ # 半途失败必须在这里自己清干净,不能把责任留给调用方。 # # 原因:调用方拿到的是**返回值**,而抛错时根本没有返回值 —— Backup.ps1 的 finally 里 - # `$stagingRoot` 还是 $null,而 Remove-BaknretArchiveStaging 对 $null 是直接 return。 + # `$stagingRoot` 还是 $null,而 Remove-BakNRetArchiveStaging 对 $null 是直接 return。 # 结果是已经建好的 junction 与临时目录永久留在 %TEMP%,而那些 junction 指向的是真实 # 数据;临时目录迟早会被某次 Remove-Item -Recurse 扫到,那一下就会走进真实数据。 try { @@ -40,14 +40,14 @@ if ($parent -and -not (Test-Path -LiteralPath $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } - if (Test-Path -LiteralPath $linkPath) { Remove-BaknretJunction -Path $linkPath } + if (Test-Path -LiteralPath $linkPath) { Remove-BakNRetJunction -Path $linkPath } if ($item.IsFile) { try { New-Item -ItemType HardLink -Path $linkPath -Target $item.RealPath -ErrorAction Stop | Out-Null } catch { - Write-Log ("暂存:硬链接不可用({0}),改为复制文件 {1}" -f $_.Exception.Message, $item.RealPath) -Level DEBUG + Write-BakNRetLog ("暂存:硬链接不可用({0}),改为复制文件 {1}" -f $_.Exception.Message, $item.RealPath) -Level DEBUG Copy-Item -LiteralPath $item.RealPath -Destination $linkPath -Force -ErrorAction Stop } } @@ -55,18 +55,18 @@ New-Item -ItemType Junction -Path $linkPath -Target $item.RealPath -ErrorAction Stop | Out-Null } - Write-Log ("暂存:{0} -> {1}" -f $item.ArchivePath, $item.RealPath) -Level DEBUG + Write-BakNRetLog ("暂存:{0} -> {1}" -f $item.ArchivePath, $item.RealPath) -Level DEBUG } return $Root } catch { try { - Remove-BaknretArchiveStaging -Root $Root + Remove-BakNRetArchiveStaging -Root $Root } catch { # 清理失败不能盖掉真正的失败原因(那才是排查需要的),所以只告警并点名残留路径 - Write-Log ("暂存目录自清理失败,需要手工删除:{0} —— {1}" -f $Root, $_.Exception.Message) -Level WARN + Write-BakNRetLog ("暂存目录自清理失败,需要手工删除:{0} —— {1}" -f $Root, $_.Exception.Message) -Level WARN } throw } diff --git a/BakNRet/Public/New-BaknretJunction.ps1 b/BakNRet/Public/New-BaknretJunction.ps1 index 2adddb2..d1bd240 100644 --- a/BakNRet/Public/New-BaknretJunction.ps1 +++ b/BakNRet/Public/New-BaknretJunction.ps1 @@ -1,4 +1,4 @@ -function New-BaknretJunction { +function New-BakNRetJunction { <# .SYNOPSIS 建一个 junction;失败时抛异常(调用方决定降级还是报错)。 diff --git a/BakNRet/Public/Read-BaknretManifest.ps1 b/BakNRet/Public/Read-BaknretManifest.ps1 index 3c9d827..c5ff02b 100644 --- a/BakNRet/Public/Read-BaknretManifest.ps1 +++ b/BakNRet/Public/Read-BaknretManifest.ps1 @@ -1,4 +1,4 @@ -function Read-BaknretManifest { +function Read-BakNRetManifest { <# .SYNOPSIS 读取 manifest.json;不存在或损坏时返回空清单。 @@ -40,7 +40,7 @@ } } catch { - Write-Log "manifest 解析失败(将重新建立):$Path —— $_" -Level WARN + Write-BakNRetLog "manifest 解析失败(将重新建立):$Path —— $_" -Level WARN return $empty } } diff --git a/BakNRet/Public/Read-BaknretSecuritySidecar.ps1 b/BakNRet/Public/Read-BaknretSecuritySidecar.ps1 index 86411b4..d0de4b4 100644 --- a/BakNRet/Public/Read-BaknretSecuritySidecar.ps1 +++ b/BakNRet/Public/Read-BaknretSecuritySidecar.ps1 @@ -1,4 +1,4 @@ -function Read-BaknretSecuritySidecar { +function Read-BakNRetSecuritySidecar { <# .SYNOPSIS 读 sidecar;不存在或损坏时返回 $null(调用方据此打"该归档不含安全描述符"的告警)。 @@ -27,7 +27,7 @@ } } catch { - Write-Log "安全描述符文件解析失败:$Path —— $($_.Exception.Message)" -Level WARN + Write-BakNRetLog "安全描述符文件解析失败:$Path —— $($_.Exception.Message)" -Level WARN return $null } } diff --git a/BakNRet/Public/Remove-BaknretArchiveStaging.ps1 b/BakNRet/Public/Remove-BaknretArchiveStaging.ps1 index f43136e..77b64c5 100644 --- a/BakNRet/Public/Remove-BaknretArchiveStaging.ps1 +++ b/BakNRet/Public/Remove-BaknretArchiveStaging.ps1 @@ -1,4 +1,4 @@ -function Remove-BaknretArchiveStaging { +function Remove-BakNRetArchiveStaging { <# .SYNOPSIS 安全拆掉暂存目录:先手工摘掉 junction,再删剩下的普通文件 / 目录。 @@ -17,7 +17,7 @@ $current = $pending.Pop() foreach ($child in @(Get-ChildItem -LiteralPath $current -Force -ErrorAction SilentlyContinue)) { if ($child.LinkType -eq 'Junction' -or $child.LinkType -eq 'SymbolicLink') { - Remove-BaknretJunction -Path $child.FullName + Remove-BakNRetJunction -Path $child.FullName continue } if ($child.PSIsContainer) { $pending.Push($child.FullName) } diff --git a/BakNRet/Public/Remove-BaknretJunction.ps1 b/BakNRet/Public/Remove-BaknretJunction.ps1 index a895363..a380dee 100644 --- a/BakNRet/Public/Remove-BaknretJunction.ps1 +++ b/BakNRet/Public/Remove-BaknretJunction.ps1 @@ -1,4 +1,4 @@ -function Remove-BaknretJunction { +function Remove-BakNRetJunction { <# .SYNOPSIS 只删连接点本身,绝不顺着它删到目标目录里去。 diff --git a/BakNRet/Public/Remove-BaknretQuote.ps1 b/BakNRet/Public/Remove-BaknretQuote.ps1 index 2f3e92a..63979a0 100644 --- a/BakNRet/Public/Remove-BaknretQuote.ps1 +++ b/BakNRet/Public/Remove-BaknretQuote.ps1 @@ -1,4 +1,4 @@ -function Remove-BaknretQuote { +function Remove-BakNRetQuote { <# .SYNOPSIS 去掉值两端成对的引号(单双都认);不成对时原样返回。 diff --git a/BakNRet/Public/Resolve-BackupEntry.ps1 b/BakNRet/Public/Resolve-BakNRetBackupEntry.ps1 similarity index 91% rename from BakNRet/Public/Resolve-BackupEntry.ps1 rename to BakNRet/Public/Resolve-BakNRetBackupEntry.ps1 index ab5bfb6..acb7af9 100644 --- a/BakNRet/Public/Resolve-BackupEntry.ps1 +++ b/BakNRet/Public/Resolve-BakNRetBackupEntry.ps1 @@ -1,4 +1,4 @@ -function Resolve-BackupEntry { +function Resolve-BakNRetBackupEntry { <# .SYNOPSIS 把清单条目解析成"实际要打包什么、归档里长什么样"。 @@ -8,7 +8,7 @@ IsName / BaseName / ArchiveFlavor / Direction CatalogEntry —— 名录条目(软件名写法才有) - Items —— 归档项数组(见 New-BaknretArchiveItem) + Items —— 归档项数组(见 New-BakNRetArchiveItem) Encrypt —— 该归档是否加密 ExcludePatterns / HasExcludeOverride —— 条目级 `:-` / `@ Exclude` 覆盖 Includes / HasIncludeOverride —— 条目级 `:+` / `@ Include` 覆盖 @@ -28,9 +28,9 @@ [int]$MaxDepth = 5 ) - $isName = -not (Test-LiteralPath -Path $Entry.Path) + $isName = -not (Test-BakNRetLiteralPath -Path $Entry.Path) $forcePathFlavor = ($Entry.Flags -contains 'pathname') - $baseName = Get-ItemArchiveName -Entry $Entry -CatalogPath $CatalogPath -MaxDepth $MaxDepth + $baseName = Get-BakNRetItemArchiveName -Entry $Entry -CatalogPath $CatalogPath -MaxDepth $MaxDepth $overrides = $Entry.Overrides if (-not $overrides) { $overrides = @{} } @@ -70,13 +70,13 @@ $errorText = "无法从路径里拆出末级名:$real" } else { - $items += New-BaknretArchiveItem -ArchivePath $leaf -RealPath $real -Kind 'path' ` + $items += New-BakNRetArchiveItem -ArchivePath $leaf -RealPath $real -Kind 'path' ` -Origin 'path' -Exists $exists -IsFile $isFile } } else { # ---- 写法一:软件名录里的软件名 ---- - $catalog = Get-SoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth + $catalog = Get-BakNRetSoftwareCatalog -Path $CatalogPath -MaxDepth $MaxDepth if (-not $catalog.ContainsKey($Entry.Path)) { $errorText = "软件名录里没有 '$($Entry.Path)'" } @@ -110,7 +110,7 @@ } } - $items += New-BaknretArchiveItem -ArchivePath $slot.Name -RealPath $resolvedPath -Kind 'slot' ` + $items += New-BakNRetArchiveItem -ArchivePath $slot.Name -RealPath $resolvedPath -Kind 'slot' ` -Slot $slot.Name -Description $slot.Description -Origin 'catalog' ` -Exists $exists -IsFile $isFile -Exclude $slot.Exclude } @@ -147,7 +147,7 @@ if ($archivePart -match '^[A-Za-z]$' -and ($hostPart.StartsWith('\') -or $hostPart.StartsWith('/'))) { $lastSeparator = $text.LastIndexOf(':') if ($lastSeparator -gt $separator) { - Write-Log ("包含项写得像'宿主机:归档内':{0} —— 语法应为 <归档内相对路径>:<宿主机绝对路径>,已按后者解释" -f $text) -Level WARN + Write-BakNRetLog ("包含项写得像'宿主机:归档内':{0} —— 语法应为 <归档内相对路径>:<宿主机绝对路径>,已按后者解释" -f $text) -Level WARN $hostPart = $text.Substring(0, $lastSeparator).Trim() $archivePart = $text.Substring($lastSeparator + 1).Trim() } @@ -156,7 +156,7 @@ $hostPath = [Environment]::ExpandEnvironmentVariables($hostPart).Trim() if ([string]::IsNullOrWhiteSpace($hostPath)) { - Write-Log "包含项 '$text' 里没有宿主机路径,已忽略" -Level WARN + Write-BakNRetLog "包含项 '$text' 里没有宿主机路径,已忽略" -Level WARN continue } @@ -170,7 +170,7 @@ $archivePath = $archivePart if ([string]::IsNullOrWhiteSpace($archivePath)) { $archivePath = Split-Path -Path $hostPath -Leaf } - $items += New-BaknretArchiveItem -ArchivePath $archivePath -RealPath $hostPath -Kind 'include' ` + $items += New-BakNRetArchiveItem -ArchivePath $archivePath -RealPath $hostPath -Kind 'include' ` -Origin 'include' -Exists $exists -IsFile $isFile } @@ -220,7 +220,7 @@ $encryptedSlots = @($slots | Where-Object { $_.Encrypt }) $encrypt = $encryptedSlots.Count -gt 0 if ($encryptedSlots.Count -gt 0 -and $encryptedSlots.Count -lt $slots.Count) { - Write-Log ("{0}:名录里各 Slot 的 Encrypt 不一致,整个归档按加密处理" -f $Entry.Path) -Level WARN + Write-BakNRetLog ("{0}:名录里各 Slot 的 Encrypt 不一致,整个归档按加密处理" -f $Entry.Path) -Level WARN } } diff --git a/BakNRet/Public/Resolve-CatalogPath.ps1 b/BakNRet/Public/Resolve-BakNRetCatalogPath.ps1 similarity index 93% rename from BakNRet/Public/Resolve-CatalogPath.ps1 rename to BakNRet/Public/Resolve-BakNRetCatalogPath.ps1 index 1a9670f..0aea2a5 100644 --- a/BakNRet/Public/Resolve-CatalogPath.ps1 +++ b/BakNRet/Public/Resolve-BakNRetCatalogPath.ps1 @@ -1,4 +1,4 @@ -function Resolve-CatalogPath { +function Resolve-BakNRetCatalogPath { <# .SYNOPSIS 计算软件名录的绝对路径(优先 .psd1,找不到就退而用 .json)。 diff --git a/BakNRet/Public/Resolve-CompressionTool.ps1 b/BakNRet/Public/Resolve-BakNRetCompressionTool.ps1 similarity index 83% rename from BakNRet/Public/Resolve-CompressionTool.ps1 rename to BakNRet/Public/Resolve-BakNRetCompressionTool.ps1 index 9386ab5..e80aaa4 100644 --- a/BakNRet/Public/Resolve-CompressionTool.ps1 +++ b/BakNRet/Public/Resolve-BakNRetCompressionTool.ps1 @@ -1,4 +1,4 @@ -function Resolve-CompressionTool { +function Resolve-BakNRetCompressionTool { <# .SYNOPSIS 探测可用的压缩工具,优先 7z,其次 RAR,最后内置 ZIP。 @@ -17,17 +17,17 @@ $sevenZip = $candidates | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -First 1 } if ($sevenZip) { - Write-Log '检测到 7z 压缩工具' -Level DEBUG + Write-BakNRetLog '检测到 7z 压缩工具' -Level DEBUG return [pscustomobject]@{ Name = '7z'; Command = $sevenZip; Extension = '.7z' } } $rar = Get-Command rar, winrar -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source if ($rar) { - Write-Log '检测到 RAR 压缩工具' -Level DEBUG + Write-BakNRetLog '检测到 RAR 压缩工具' -Level DEBUG return [pscustomobject]@{ Name = 'RAR'; Command = $rar; Extension = '.rar' } } - Write-Log '使用内置 ZIP 工具' -Level DEBUG + Write-BakNRetLog '使用内置 ZIP 工具' -Level DEBUG return [pscustomobject]@{ Name = 'ZIP'; Command = 'Compress-Archive'; Extension = '.zip' } } diff --git a/BakNRet/Public/Restore-BaknretSecurity.ps1 b/BakNRet/Public/Restore-BaknretSecurity.ps1 index 4e6fa97..1390d43 100644 --- a/BakNRet/Public/Restore-BaknretSecurity.ps1 +++ b/BakNRet/Public/Restore-BaknretSecurity.ps1 @@ -1,4 +1,4 @@ -function Restore-BaknretSecurity { +function Restore-BakNRetSecurity { <# .SYNOPSIS 把 sidecar 里属于某个归档项的那部分安全描述符,回放到真实目标路径上。 @@ -33,7 +33,7 @@ # 写属主需要 SeRestorePrivilege,而且必须**显式启用**:管理员的过滤令牌里它默认是 # disabled,Set-Acl / SetAccessControl 都不会替你打开。没有它,属主会写失败并静默 # 退化成"只恢复 DACL" —— 那恰恰丢掉了这个功能存在的理由(CREATOR OWNER 判给谁)。 - Enable-BaknretPrivilege -Name @('SeRestorePrivilege', 'SeBackupPrivilege') | Out-Null + Enable-BakNRetPrivilege -Name @('SeRestorePrivilege', 'SeBackupPrivilege') | Out-Null if (-not $Sidecar -or -not $Sidecar.Records) { return $result } @@ -79,22 +79,22 @@ if ($WhatIf) { continue } - $sddl = Convert-BaknretSidMap -Sddl ([string]$entry.Record.s) -SidMap $SidMap + $sddl = Convert-BakNRetSidMap -Sddl ([string]$entry.Record.s) -SidMap $SidMap try { - Set-BaknretObjectSecurity -Item $item -Sddl $sddl -Scope All + Set-BakNRetObjectSecurity -Item $item -Sddl $sddl -Scope All $result.Applied++ } catch { $fullError = $_ try { - Set-BaknretObjectSecurity -Item $item -Sddl $sddl -Scope OwnerAndAccess + Set-BakNRetObjectSecurity -Item $item -Sddl $sddl -Scope OwnerAndAccess $result.OwnerFailed++ $result.Failures += ("{0}:属组未恢复,属主与 DACL 已恢复({1})" -f $target, $fullError.Exception.Message) } catch { try { - Set-BaknretObjectSecurity -Item $item -Sddl $sddl -Scope AccessOnly + Set-BakNRetObjectSecurity -Item $item -Sddl $sddl -Scope AccessOnly $result.OwnerFailed++ $result.Failures += ("{0}:属主/属组未恢复({1}),已只恢复 DACL" -f $target, $_.Exception.Message) } diff --git a/BakNRet/Public/Save-BaknretSecuritySidecar.ps1 b/BakNRet/Public/Save-BaknretSecuritySidecar.ps1 index 6b0f468..ad138d9 100644 --- a/BakNRet/Public/Save-BaknretSecuritySidecar.ps1 +++ b/BakNRet/Public/Save-BaknretSecuritySidecar.ps1 @@ -1,4 +1,4 @@ -function Save-BaknretSecuritySidecar { +function Save-BakNRetSecuritySidecar { <# .SYNOPSIS 把采集结果写成 sidecar(`<归档名>.acl.json`)。 @@ -47,5 +47,5 @@ } $json = $payload | ConvertTo-Json -Depth 5 - return (Write-BaknretAtomicText -Path $Path -Text $json) + return (Write-BakNRetAtomicText -Path $Path -Text $json) } diff --git a/BakNRet/Public/Set-BaknretDebug.ps1 b/BakNRet/Public/Set-BaknretDebug.ps1 index b1ea744..1e05b0d 100644 --- a/BakNRet/Public/Set-BaknretDebug.ps1 +++ b/BakNRet/Public/Set-BaknretDebug.ps1 @@ -1,4 +1,4 @@ -function Set-BaknretDebug { +function Set-BakNRetDebug { <# .SYNOPSIS 打开 DEBUG 级别日志。 #> param([switch]$Enabled = $true) $script:LogConfig.EnableDebug = [bool]$Enabled diff --git a/BakNRet/Public/Set-BaknretObjectSecurity.ps1 b/BakNRet/Public/Set-BaknretObjectSecurity.ps1 index 8f5ee3a..8636dba 100644 --- a/BakNRet/Public/Set-BaknretObjectSecurity.ps1 +++ b/BakNRet/Public/Set-BaknretObjectSecurity.ps1 @@ -1,4 +1,4 @@ -function Set-BaknretObjectSecurity { +function Set-BakNRetObjectSecurity { <# .SYNOPSIS 把一条 SDDL 落到一个对象上。 diff --git a/BakNRet/Public/Split-BaknretPatternScope.ps1 b/BakNRet/Public/Split-BaknretPatternScope.ps1 index 110c695..5117268 100644 --- a/BakNRet/Public/Split-BaknretPatternScope.ps1 +++ b/BakNRet/Public/Split-BaknretPatternScope.ps1 @@ -1,4 +1,4 @@ -function Split-BaknretPatternScope { +function Split-BakNRetPatternScope { <# .SYNOPSIS 把条目级的模式按 `<归档项名>\` 前缀分配到各个归档项上。 @@ -23,7 +23,7 @@ $map = @{} for ($index = 0; $index -lt $Items.Count; $index++) { $map[$index] = @() } - # 归档项的名字(顶层目录名)。同一个条目里不允许重名,Resolve-BackupEntry 会拦。 + # 归档项的名字(顶层目录名)。同一个条目里不允许重名,Resolve-BakNRetBackupEntry 会拦。 $topIndex = @{} for ($index = 0; $index -lt $Items.Count; $index++) { $name = [string]$Items[$index].ArchivePath diff --git a/BakNRet/Public/Split-BaknretToken.ps1 b/BakNRet/Public/Split-BaknretToken.ps1 index 2e05938..03929a2 100644 --- a/BakNRet/Public/Split-BaknretToken.ps1 +++ b/BakNRet/Public/Split-BaknretToken.ps1 @@ -1,4 +1,4 @@ -function Split-BaknretToken { +function Split-BakNRetToken { <# .SYNOPSIS 把清单的一行切成空白分隔的记号;引号内的空白不切分,引号本身留在记号里。 diff --git a/BakNRet/Public/Start-BaknretLog.ps1 b/BakNRet/Public/Start-BaknretLog.ps1 index ca57615..e225ff0 100644 --- a/BakNRet/Public/Start-BaknretLog.ps1 +++ b/BakNRet/Public/Start-BaknretLog.ps1 @@ -1,4 +1,4 @@ -function Start-BaknretLog { +function Start-BakNRetLog { <# .SYNOPSIS 把后续日志同时写入 /-<时间戳>.log,返回日志文件路径。 diff --git a/BakNRet/Public/Stop-BaknretLog.ps1 b/BakNRet/Public/Stop-BaknretLog.ps1 index 96e1cf8..5576f3d 100644 --- a/BakNRet/Public/Stop-BaknretLog.ps1 +++ b/BakNRet/Public/Stop-BaknretLog.ps1 @@ -1,4 +1,4 @@ -function Stop-BaknretLog { +function Stop-BakNRetLog { <# .SYNOPSIS 停止写入日志文件。 #> $script:LogConfig.FilePath = $null } diff --git a/BakNRet/Public/Sync-BaknretManifestArchive.ps1 b/BakNRet/Public/Sync-BaknretManifestArchive.ps1 index 2aeeb7d..3711170 100644 --- a/BakNRet/Public/Sync-BaknretManifestArchive.ps1 +++ b/BakNRet/Public/Sync-BaknretManifestArchive.ps1 @@ -1,4 +1,4 @@ -function Sync-BaknretManifestArchive { +function Sync-BakNRetManifestArchive { <# .SYNOPSIS 清空 manifest 里"指向了一个不存在的归档"的 archive 字段,返回被清空的条目名。 diff --git a/BakNRet/Public/Test-Administrator.ps1 b/BakNRet/Public/Test-BakNRetAdministrator.ps1 similarity index 86% rename from BakNRet/Public/Test-Administrator.ps1 rename to BakNRet/Public/Test-BakNRetAdministrator.ps1 index 04320d8..00b3070 100644 --- a/BakNRet/Public/Test-Administrator.ps1 +++ b/BakNRet/Public/Test-BakNRetAdministrator.ps1 @@ -1,4 +1,4 @@ -function Test-Administrator { +function Test-BakNRetAdministrator { <# .SYNOPSIS 当前进程是否以管理员身份运行。 #> $principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent() return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) diff --git a/BakNRet/Public/Test-LiteralPath.ps1 b/BakNRet/Public/Test-BakNRetLiteralPath.ps1 similarity index 93% rename from BakNRet/Public/Test-LiteralPath.ps1 rename to BakNRet/Public/Test-BakNRetLiteralPath.ps1 index 97ad34d..8a7e403 100644 --- a/BakNRet/Public/Test-LiteralPath.ps1 +++ b/BakNRet/Public/Test-BakNRetLiteralPath.ps1 @@ -1,4 +1,4 @@ -function Test-LiteralPath { +function Test-BakNRetLiteralPath { <# .SYNOPSIS 判断清单里的一行是不是"字面路径"(而非软件名)。 diff --git a/BakNRet/Public/Test-BaknretMarker.ps1 b/BakNRet/Public/Test-BaknretMarker.ps1 index 6a45b20..f276a68 100644 --- a/BakNRet/Public/Test-BaknretMarker.ps1 +++ b/BakNRet/Public/Test-BaknretMarker.ps1 @@ -1,4 +1,4 @@ -function Test-BaknretMarker { +function Test-BakNRetMarker { <# .SYNOPSIS 判断一个记号是不是清单修饰符,返回它的种类;不是则返回 $null。 diff --git a/BakNRet/Public/Test-BaknretPathExcluded.ps1 b/BakNRet/Public/Test-BaknretPathExcluded.ps1 index a8c6c1e..bbfbaa7 100644 --- a/BakNRet/Public/Test-BaknretPathExcluded.ps1 +++ b/BakNRet/Public/Test-BaknretPathExcluded.ps1 @@ -1,4 +1,4 @@ -function Test-BaknretPathExcluded { +function Test-BakNRetPathExcluded { <# .SYNOPSIS 判断归档内的一个相对路径是否命中排除模式。 @@ -38,7 +38,7 @@ } } catch { - Write-Log "排除正则非法,安全描述符采集按'不排除'处理:$regexText —— $($_.Exception.Message)" -Level WARN + Write-BakNRetLog "排除正则非法,安全描述符采集按'不排除'处理:$regexText —— $($_.Exception.Message)" -Level WARN } continue } @@ -46,7 +46,7 @@ if ($text.StartsWith('!')) { $wildcard = $text.Substring(1).Trim() if (-not $wildcard) { continue } - $componentPattern = '^(?:' + (ConvertTo-BaknretWildcardPattern -Pattern $wildcard) + ')$' + $componentPattern = '^(?:' + (ConvertTo-BakNRetWildcardPattern -Pattern $wildcard) + ')$' foreach ($component in $components) { if ($component -match $componentPattern) { return $true } } @@ -55,7 +55,7 @@ $anchored = ([string]$text).Trim([char[]]@('\', '/')) if (-not $anchored) { continue } - $anchoredPattern = '^(?:' + (ConvertTo-BaknretWildcardPattern -Pattern $anchored) + ')$' + $anchoredPattern = '^(?:' + (ConvertTo-BakNRetWildcardPattern -Pattern $anchored) + ')$' if ($relative -match $anchoredPattern) { return $true } } diff --git a/BakNRet/Public/Test-BaknretSecurityRecordNeeded.ps1 b/BakNRet/Public/Test-BaknretSecurityRecordNeeded.ps1 index e709945..71aab98 100644 --- a/BakNRet/Public/Test-BaknretSecurityRecordNeeded.ps1 +++ b/BakNRet/Public/Test-BaknretSecurityRecordNeeded.ps1 @@ -1,4 +1,4 @@ -function Test-BaknretSecurityRecordNeeded { +function Test-BakNRetSecurityRecordNeeded { <# .SYNOPSIS Smart 模式下判断这条记录是否必须落进 sidecar。 diff --git a/BakNRet/Public/Write-BackupEntryPlan.ps1 b/BakNRet/Public/Write-BakNRetBackupEntryPlan.ps1 similarity index 59% rename from BakNRet/Public/Write-BackupEntryPlan.ps1 rename to BakNRet/Public/Write-BakNRetBackupEntryPlan.ps1 index 2e838f7..2cfc429 100644 --- a/BakNRet/Public/Write-BackupEntryPlan.ps1 +++ b/BakNRet/Public/Write-BakNRetBackupEntryPlan.ps1 @@ -1,4 +1,4 @@ -function Write-BackupEntryPlan { +function Write-BakNRetBackupEntryPlan { <# .SYNOPSIS 在动手打包之前,把"这条会打包哪些目录、归档里长什么样、排除了什么、为什么"打印出来。 @@ -27,41 +27,41 @@ 'restore' = '仅恢复(行首 -)' } - Write-Log ("条目:{0}" -f $DisplayPath) - Write-Log (" 归档:{0}.7z;方向:{1};加密:{2}" -f $Resolved.BaseName, + Write-BakNRetLog ("条目:{0}" -f $DisplayPath) + Write-BakNRetLog (" 归档:{0}.7z;方向:{1};加密:{2}" -f $Resolved.BaseName, $(if ($directionText.ContainsKey($Resolved.Direction)) { $directionText[$Resolved.Direction] } else { $Resolved.Direction }), $(if ($Resolved.Encrypt) { '是' } else { '否' })) - if ($Comment) { Write-Log (" 说明:{0}" -f $Comment) } - if ($Resolved.Error) { Write-Log (" 提示:{0}" -f $Resolved.Error) -Level WARN } + if ($Comment) { Write-BakNRetLog (" 说明:{0}" -f $Comment) } + if ($Resolved.Error) { Write-BakNRetLog (" 提示:{0}" -f $Resolved.Error) -Level WARN } $items = @($Resolved.Items) - if ($items.Count -eq 0) { Write-Log ' 归档项:没有解析出任何目录' -Level WARN } + if ($items.Count -eq 0) { Write-BakNRetLog ' 归档项:没有解析出任何目录' -Level WARN } for ($index = 0; $index -lt $items.Count; $index++) { $item = $items[$index] $exists = Test-Path -LiteralPath $item.RealPath $origin = if ($item.Origin -and $originText.ContainsKey($item.Origin)) { $originText[$item.Origin] } else { $item.Origin } - Write-Log (" 归档项 {0}/{1}:{2} <- {3}" -f ($index + 1), $items.Count, $item.ArchivePath, $item.RealPath) - Write-Log (" 来源:{0};{1};{2}" -f $origin, + Write-BakNRetLog (" 归档项 {0}/{1}:{2} <- {3}" -f ($index + 1), $items.Count, $item.ArchivePath, $item.RealPath) + Write-BakNRetLog (" 来源:{0};{1};{2}" -f $origin, $(if ($exists) { '存在,会打包' } else { '当前不存在,本次跳过' }), $(if ($item.IsFile) { '文件' } else { '目录' })) - if ($item.Description) { Write-Log (" 介绍:{0}" -f $item.Description) } + if ($item.Description) { Write-BakNRetLog (" 介绍:{0}" -f $item.Description) } if (@($item.Exclude).Count -gt 0) { - Write-Log (" 名录里的排除:{0}" -f (@($item.Exclude) -join '、')) + Write-BakNRetLog (" 名录里的排除:{0}" -f (@($item.Exclude) -join '、')) } } if ($ListExcludes.Count -gt 0) { - Write-Log (" 排除 {0} 条(来自清单的 :- / @ Exclude):{1}" -f $ListExcludes.Count, ($ListExcludes -join '、')) + Write-BakNRetLog (" 排除 {0} 条(来自清单的 :- / @ Exclude):{1}" -f $ListExcludes.Count, ($ListExcludes -join '、')) } if ($CatalogExcludes.Count -gt 0) { - Write-Log (" 排除 {0} 条(来自名录 Slot 的 Exclude):{1}" -f $CatalogExcludes.Count, ($CatalogExcludes -join '、')) + Write-BakNRetLog (" 排除 {0} 条(来自名录 Slot 的 Exclude):{1}" -f $CatalogExcludes.Count, ($CatalogExcludes -join '、')) } if ($ConfigExcludes.Count -gt 0) { - Write-Log (" 排除 {0} 条(来自 BackupConfig.psd1 的 DefaultExcludes):{1}" -f $ConfigExcludes.Count, ($ConfigExcludes -join '、')) + Write-BakNRetLog (" 排除 {0} 条(来自 BackupConfig.psd1 的 DefaultExcludes):{1}" -f $ConfigExcludes.Count, ($ConfigExcludes -join '、')) } if ($ListExcludes.Count -eq 0 -and $CatalogExcludes.Count -eq 0 -and $ConfigExcludes.Count -eq 0) { - Write-Log ' 排除:无(整包收下)' + Write-BakNRetLog ' 排除:无(整包收下)' } } diff --git a/BakNRet/Public/Write-Log.ps1 b/BakNRet/Public/Write-BakNRetLog.ps1 similarity index 97% rename from BakNRet/Public/Write-Log.ps1 rename to BakNRet/Public/Write-BakNRetLog.ps1 index aa3cc44..7db5d3b 100644 --- a/BakNRet/Public/Write-Log.ps1 +++ b/BakNRet/Public/Write-BakNRetLog.ps1 @@ -1,4 +1,4 @@ -function Write-Log { +function Write-BakNRetLog { <# .SYNOPSIS 写一条日志到控制台,并在启用日志文件时落盘。 diff --git a/BakNRet/Public/Write-BaknretAtomicText.ps1 b/BakNRet/Public/Write-BaknretAtomicText.ps1 index 6f288ef..df25d5e 100644 --- a/BakNRet/Public/Write-BaknretAtomicText.ps1 +++ b/BakNRet/Public/Write-BaknretAtomicText.ps1 @@ -1,4 +1,4 @@ -function Write-BaknretAtomicText { +function Write-BakNRetAtomicText { <# .SYNOPSIS 原子写一个文本文件(先写 .tmp,再替换)。 @@ -17,7 +17,7 @@ [System.IO.File]::WriteAllText($temp, $Text, $script:LogEncoding) if (-not (Test-Path -LiteralPath $Path)) { - Move-BaknretArchiveIntoPlace -TempPath $temp -DestinationPath $Path + Move-BakNRetArchiveIntoPlace -TempPath $temp -DestinationPath $Path return $Path } diff --git a/BakNRet/Public/Write-BaknretManifest.ps1 b/BakNRet/Public/Write-BaknretManifest.ps1 index 1b9c010..6afcb35 100644 --- a/BakNRet/Public/Write-BaknretManifest.ps1 +++ b/BakNRet/Public/Write-BaknretManifest.ps1 @@ -1,4 +1,4 @@ -function Write-BaknretManifest { +function Write-BakNRetManifest { <# .SYNOPSIS 原子写入 manifest.json(UTF-8 无 BOM)。 @@ -18,6 +18,6 @@ # 复用原子写,而不是自己"删旧再改名":后者一旦在中途失败,旧 manifest 已经没了 —— # 而 manifest 是"这块归档是谁的"的唯一账本,丢了它只能靠文件名反推。 - Write-BaknretAtomicText -Path $Path -Text $json + Write-BakNRetAtomicText -Path $Path -Text $json return $Path } diff --git a/CONTEXT.md b/CONTEXT.md index 8a04e26..702f7d4 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,4 +1,4 @@ -# BakNRet +# BakNRet 一个 Windows 备份 / 恢复工具:把机器上指定的软件与目录收进归档,并能把它们放回原位。本文件是本项目的术语表——「要处理什么」「东西放在哪」这些概念在本仓库里只有一个叫法,写作与命名都照这里的词来。 @@ -70,4 +70,4 @@ _Avoid_: 无用归档、残留、垃圾文件 **BakNRet**: 本工具的名称,任何场合(文档、文件名、标识符)都一律写作 `BakNRet`。 -_Avoid_: Baknret、baknret、BakRet、BaknRet 备份工具 +_Avoid_: BakNRet、baknret、BakRet、BaknRet 备份工具 diff --git a/PSScriptAnalyzerSettings.psd1 b/PSScriptAnalyzerSettings.psd1 index 20d5980..5a89453 100644 --- a/PSScriptAnalyzerSettings.psd1 +++ b/PSScriptAnalyzerSettings.psd1 @@ -14,7 +14,7 @@ 逐条决策(都在本次改造里确认过,理由另见 docs/adr/): - * PSAvoidUsingWriteHost 全局排除 —— Write-Log 的彩色控制台输出是这份工具的**刻意设计**, + * PSAvoidUsingWriteHost 全局排除 —— Write-BakNRetLog 的彩色控制台输出是这份工具的**刻意设计**, 不是疏忽。这是架构性选择,所以在配置里排除掉,而不是逐处 Suppress 假装它是例外。 * PSUseSingularNouns 放行 SecurityRecords / MapKeys / ArchiveTopLevelNames —— 单数名 Get-BakNretSecurityRecord 已被"单对象版本"占用,为了规则把两个语义搅在一起 diff --git a/Restore.ps1 b/Restore.ps1 index 4cdabb7..72df373 100644 --- a/Restore.ps1 +++ b/Restore.ps1 @@ -81,9 +81,9 @@ if (-not (Test-Path -LiteralPath $modulePath)) { } Import-Module $modulePath -Force -if ($PSBoundParameters.ContainsKey('Verbose')) { Set-BaknretDebug } +if ($PSBoundParameters.ContainsKey('Verbose')) { Set-BakNRetDebug } -$script:Config = Get-BaknretConfig -Path $ConfigPath +$script:Config = Get-BakNRetConfig -Path $ConfigPath $SupportedFormats = @('.7z', '.rar', '.zip', '.tar') function Resolve-ConfigPath { @@ -97,33 +97,33 @@ function Resolve-ConfigPath { if (-not $BackupDir) { $BackupDir = Resolve-ConfigPath -Path $null -Default $script:Config.BackupDir } $logDir = Resolve-ConfigPath -Path $null -Default $script:Config.LogDir -$catalogPath = Resolve-CatalogPath -Configured $script:Config.SoftwareCatalog -Root $PSScriptRoot +$catalogPath = Resolve-BakNRetCatalogPath -Configured $script:Config.SoftwareCatalog -Root $PSScriptRoot $manifestPath = Join-Path $BackupDir 'manifest.json' -$logPath = Start-BaknretLog -Directory $logDir -Prefix 'restore' -Write-Log "日志文件:$logPath" -Write-Log "备份目录:$BackupDir" -Write-Log ("软件名录:{0}{1}" -f $catalogPath, $(if (Test-Path -LiteralPath $catalogPath) { '' } else { '(不存在,将只支持字面路径)' })) -if ($WhatIfPreference) { Write-Log '试运行模式(-WhatIf / -DryRun):不会写入任何文件' -Level WARN } +$logPath = Start-BakNRetLog -Directory $logDir -Prefix 'restore' +Write-BakNRetLog "日志文件:$logPath" +Write-BakNRetLog "备份目录:$BackupDir" +Write-BakNRetLog ("软件名录:{0}{1}" -f $catalogPath, $(if (Test-Path -LiteralPath $catalogPath) { '' } else { '(不存在,将只支持字面路径)' })) +if ($WhatIfPreference) { Write-BakNRetLog '试运行模式(-WhatIf / -DryRun):不会写入任何文件' -Level WARN } -if (-not (Test-Administrator)) { - Write-Log '建议以管理员身份运行以获取完整的目录访问权限' -Level WARN +if (-not (Test-BakNRetAdministrator)) { + Write-BakNRetLog '建议以管理员身份运行以获取完整的目录访问权限' -Level WARN } # 同一份备份目录同一时间只允许一个进程操作(见 BakNRet 模块的「运行锁」一节)。 # 三种只读模式不取锁:它们一个字节都不写,没必要被正在跑的备份挡在外面。 $runLock = $null if (-not $WhatIfPreference -and -not $VerifyOnly) { - $runLock = Enter-BaknretRunLock -Directory $BackupDir + $runLock = Enter-BakNRetRunLock -Directory $BackupDir if (-not $runLock) { - Write-Log ("另一次运行正在进行中(锁文件:{0},里面写明了持有者)。本次不执行。" -f (Get-BaknretRunLockPath -Directory $BackupDir)) -Level ERROR - Stop-BaknretLog + Write-BakNRetLog ("另一次运行正在进行中(锁文件:{0},里面写明了持有者)。本次不执行。" -f (Get-BakNRetRunLockPath -Directory $BackupDir)) -Level ERROR + Stop-BakNRetLog exit 1 } - Write-Log ("已取得运行锁:{0}" -f (Get-BaknretRunLockPath -Directory $BackupDir)) -Level DEBUG + Write-BakNRetLog ("已取得运行锁:{0}" -f (Get-BakNRetRunLockPath -Directory $BackupDir)) -Level DEBUG } $passwordFile = if ($KeyFile) { $KeyFile } else { $script:Config.Encryption.PasswordFile } -$password = Get-BaknretPassword -PasswordFile $passwordFile +$password = Get-BakNRetPassword -PasswordFile $passwordFile # ============================================================================ # 归档查找 @@ -157,7 +157,7 @@ function Get-ArchiveForEntry { if (Test-Path -LiteralPath $path) { return [pscustomobject]@{ File = (Get-Item -LiteralPath $path); Source = 'manifest'; Record = $record } } - Write-Log "manifest 记录的归档不存在,回退按文件名查找:$archiveName" -Level WARN + Write-BakNRetLog "manifest 记录的归档不存在,回退按文件名查找:$archiveName" -Level WARN } } @@ -207,7 +207,7 @@ function Invoke-ExtractionRaw { $sevenZip = Get-7zExecutable if ($sevenZip) { - Write-Log '使用 7z 解压' -Level DEBUG + Write-BakNRetLog '使用 7z 解压' -Level DEBUG $argument = @('x', '-bsp2', '-y', "-o$Destination") if ($Password) { $argument += "-p$Password" } $argument += $ArchiveFile.FullName @@ -222,23 +222,23 @@ function Invoke-ExtractionRaw { '.rar' { $rarExe = Get-Command rar, unrar -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source if (-not $rarExe) { throw '未找到 RAR 工具' } - Write-Log '使用 RAR 解压' -Level DEBUG + Write-BakNRetLog '使用 RAR 解压' -Level DEBUG $argument = @('x', '-idp', '-idn', '-y', $ArchiveFile.FullName, "$Destination\") if ($RelativePath) { $argument += $RelativePath } $exitCode = Invoke-ExternalCommand -FilePath $rarExe -ArgumentList $argument if ($exitCode -ne 0) { throw "RAR 解压失败(退出码:$exitCode)" } } '.zip' { - Write-Log '使用内置 ZIP 解压' -Level DEBUG + Write-BakNRetLog '使用内置 ZIP 解压' -Level DEBUG if ($RelativePath) { - Write-Log "内置 ZIP 不支持只解子树,将整包解压($RelativePath)" -Level WARN + Write-BakNRetLog "内置 ZIP 不支持只解子树,将整包解压($RelativePath)" -Level WARN } Expand-Archive -LiteralPath $ArchiveFile.FullName -DestinationPath $Destination -Force } '.tar' { $tarExe = Get-Command tar -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source if (-not $tarExe) { throw '未找到 TAR 工具' } - Write-Log '使用 TAR 解压' -Level DEBUG + Write-BakNRetLog '使用 TAR 解压' -Level DEBUG $argument = @('-xf', $ArchiveFile.FullName, '-C', $Destination) if ($RelativePath) { $argument += $RelativePath } $exitCode = Invoke-ExternalCommand -FilePath $tarExe -ArgumentList $argument @@ -308,18 +308,18 @@ function Invoke-ExtractionByLayout { New-Item -ItemType Directory -Path $destPath -Force | Out-Null } - $anchorName = Get-BaknretArchiveTopName -ArchivePath $archivePath + $anchorName = Get-BakNRetArchiveTopName -ArchivePath $archivePath $anchorPath = if ($anchorName) { Join-Path $destParent $anchorName } else { $null } $junctionCreated = $false if ($anchorPath -and -not (Test-Path -LiteralPath $anchorPath)) { try { - New-BaknretJunction -Path $anchorPath -Target $destPath | Out-Null + New-BakNRetJunction -Path $anchorPath -Target $destPath | Out-Null $junctionCreated = $true - Write-Log ("落地:{0} -> {1}(经连接点 {2})" -f $archivePath, $destPath, $anchorPath) -Level DEBUG + Write-BakNRetLog ("落地:{0} -> {1}(经连接点 {2})" -f $archivePath, $destPath, $anchorPath) -Level DEBUG } catch { - Write-Log "无法建连接点($($_.Exception.Message)),改为先解到临时目录再合并" -Level WARN + Write-BakNRetLog "无法建连接点($($_.Exception.Message)),改为先解到临时目录再合并" -Level WARN } } @@ -328,11 +328,11 @@ function Invoke-ExtractionByLayout { return (Invoke-ExtractionRaw -ArchiveFile $ArchiveFile -Destination $destParent -RelativePath $archivePath -Password $Password) } finally { - Remove-BaknretJunction -Path $anchorPath + Remove-BakNRetJunction -Path $anchorPath } } - Write-Log ("落地:{0} -> {1}(先解到临时目录再合并)" -f $archivePath, $destPath) -Level WARN + Write-BakNRetLog ("落地:{0} -> {1}(先解到临时目录再合并)" -f $archivePath, $destPath) -Level WARN $temp = Join-Path $env:TEMP ('bnr-merge-' + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $temp -Force | Out-Null try { @@ -353,7 +353,7 @@ function Invoke-ExtractionByLayout { return $true } -function Test-BaknretArchivePath { +function Test-BakNRetArchivePath { <# .SYNOPSIS 归档里有没有这条路径。 @@ -394,14 +394,14 @@ function Test-BaknretArchivePath { $argument += $item $null = Start-Process -FilePath $sevenZip ` - -ArgumentList (ConvertTo-NativeArgumentString -ArgumentList $argument) ` + -ArgumentList (ConvertTo-BakNRetNativeArgumentString -ArgumentList $argument) ` -RedirectStandardOutput $outFile -RedirectStandardError $errFile ` -NoNewWindow -Wait -PassThru $lines = @(Get-Content -LiteralPath $outFile -Encoding UTF8 -ErrorAction SilentlyContinue) } catch { - Write-Log "无法列出归档内容(跳过预判):$($_.Exception.Message)" -Level DEBUG + Write-BakNRetLog "无法列出归档内容(跳过预判):$($_.Exception.Message)" -Level DEBUG return $true } finally { @@ -443,13 +443,13 @@ function Invoke-Extraction { $destPath = [string]$Item.RealPath $legacyName = Split-Path -Path $destPath -Leaf - if (Test-BaknretArchivePath -ArchiveFile $ArchiveFile -RelativePath $archivePath -Password $Password) { + if (Test-BakNRetArchivePath -ArchiveFile $ArchiveFile -RelativePath $archivePath -Password $Password) { return (Invoke-ExtractionByLayout -ArchiveFile $ArchiveFile -Item $Item -Password $Password) } if ($legacyName -and ($legacyName -ine $archivePath) -and - (Test-BaknretArchivePath -ArchiveFile $ArchiveFile -RelativePath $legacyName -Password $Password)) { - Write-Log ("归档里没有 '{0}'(可能是重构前的旧归档),按旧布局回退为 '{1}'" -f $archivePath, $legacyName) -Level WARN + (Test-BakNRetArchivePath -ArchiveFile $ArchiveFile -RelativePath $legacyName -Password $Password)) { + Write-BakNRetLog ("归档里没有 '{0}'(可能是重构前的旧归档),按旧布局回退为 '{1}'" -f $archivePath, $legacyName) -Level WARN $parent = Split-Path -Path $destPath -Parent if (-not (Test-Path -LiteralPath $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } return (Invoke-ExtractionRaw -ArchiveFile $ArchiveFile -Destination $parent -RelativePath $legacyName -Password $Password) @@ -464,15 +464,15 @@ function Invoke-Extraction { # ============================================================================ if (-not (Test-Path -LiteralPath $BackupDir)) { - Write-Log "备份目录不存在: $BackupDir" -Level ERROR - Stop-BaknretLog + Write-BakNRetLog "备份目录不存在: $BackupDir" -Level ERROR + Stop-BakNRetLog exit 1 } -$manifest = Read-BaknretManifest -Path $manifestPath +$manifest = Read-BakNRetManifest -Path $manifestPath if (-not (Test-Path -LiteralPath $BackupListPath)) { - Write-Log '未找到配置文件,正在从备份内容生成...' -Level INFO + Write-BakNRetLog '未找到配置文件,正在从备份内容生成...' -Level INFO $paths = @() @@ -489,22 +489,22 @@ if (-not (Test-Path -LiteralPath $BackupListPath)) { $backupFiles = Get-ChildItem -LiteralPath $BackupDir -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Extension.ToLower() -in $SupportedFormats -and $_.BaseName -match '_from_' } foreach ($file in $backupFiles) { - $original = Convert-BackupFileNameToPath -FileName $file.Name + $original = Convert-BakNRetBackupFileNameToPath -FileName $file.Name if ($original) { $paths += $original } } } $paths = @($paths | Sort-Object -Unique) if ($paths.Count -eq 0) { - Write-Log '无法从备份内容还原出任何路径。' -Level ERROR - Stop-BaknretLog + Write-BakNRetLog '无法从备份内容还原出任何路径。' -Level ERROR + Stop-BakNRetLog exit 1 } $content = "# BackupList.txt(自动生成,排除规则需要手工补回)`n" + (($paths -join [Environment]::NewLine) + [Environment]::NewLine) [System.IO.File]::WriteAllText($BackupListPath, $content, [System.Text.UTF8Encoding]::new($true)) - Write-Log "已生成配置,包含 $($paths.Count) 个项目,请检查后重新运行" -Level INFO - Stop-BaknretLog + Write-BakNRetLog "已生成配置,包含 $($paths.Count) 个项目,请检查后重新运行" -Level INFO + Stop-BakNRetLog exit 0 } @@ -539,14 +539,14 @@ $referencedArchives = @() # "试运行不会写入任何文件" 的承诺(已用 manifest 的 SHA256 复现)。 $manifestDirty = $false -Write-Log '开始执行恢复' -Level INFO +Write-BakNRetLog '开始执行恢复' -Level INFO foreach ($line in $lines) { $item = ConvertFrom-BackupListLine -Line $line if (-not $item) { continue } $displayPath = $item.Path - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $script:Config.CatalogMaxDepth $baseName = $resolved.BaseName if (-not $baseName) { $stats.skipped++; continue } @@ -556,14 +556,14 @@ foreach ($line in $lines) { # 仅备份的条目照样要登记归档名:审计要能看出"这个归档是有主的", # 否则它会被误报成孤儿(只是它本来就恢复不到,因为行首写了 +)。 $referencedArchives += $baseName - Write-Log "跳过(行首 +,仅备份): $displayPath" -Level DEBUG + Write-BakNRetLog "跳过(行首 +,仅备份): $displayPath" -Level DEBUG continue } # 解析阶段就定死的结构性错误(名录条目有问题、归档内路径冲突): # 恢复一半比明确失败更危险,所以整条失败。 if ($resolved.Blocking) { - Write-Log "失败: $displayPath,$($resolved.Blocking)" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$($resolved.Blocking)" -Level ERROR $stats.failed++ $failures += $displayPath continue @@ -571,7 +571,7 @@ foreach ($line in $lines) { $found = Get-ArchiveForEntry -Entry ([pscustomobject]@{ baseName = $baseName }) -Manifest $manifest if (-not $found) { - Write-Log "跳过: $displayPath,未找到归档 $baseName" -Level WARN + Write-BakNRetLog "跳过: $displayPath,未找到归档 $baseName" -Level WARN $stats.skipped++ continue } @@ -639,7 +639,7 @@ foreach ($line in $lines) { $targets = @($targets | Where-Object { $_.DestPath -and -not [string]::IsNullOrWhiteSpace($_.DestPath) }) if ($targets.Count -eq 0) { $reason = "无法确定恢复目的地(清单条目 '$displayPath' 解析不出任何归档项)" - Write-Log "失败: $displayPath,$reason" -Level ERROR + Write-BakNRetLog "失败: $displayPath,$reason" -Level ERROR $stats.failed++ $failures += $displayPath continue @@ -657,7 +657,7 @@ foreach ($line in $lines) { $isEncrypted = [bool]$found.Record.encrypted } if ($isEncrypted -and -not $password) { - Write-Log "失败: $displayPath,归档已加密但取不到口令(设置 BAKNRET_PASSWORD 或用 -KeyFile 指定密码文件)" -Level ERROR + Write-BakNRetLog "失败: $displayPath,归档已加密但取不到口令(设置 BAKNRET_PASSWORD 或用 -KeyFile 指定密码文件)" -Level ERROR $stats.failed++ $failures += $displayPath continue @@ -665,12 +665,12 @@ foreach ($line in $lines) { if ($VerifyOnly) { if ($archiveFile.Extension.ToLower() -ne '.7z') { - Write-Log "跳过校验(非 7z): $($archiveFile.Name)" -Level DEBUG + Write-BakNRetLog "跳过校验(非 7z): $($archiveFile.Name)" -Level DEBUG continue } $verifyTool = Get-7zExecutable if (-not $verifyTool) { - Write-Log '未找到 7z,无法校验' -Level ERROR + Write-BakNRetLog '未找到 7z,无法校验' -Level ERROR $stats.failed++ $failures += $displayPath continue @@ -680,45 +680,45 @@ foreach ($line in $lines) { $verifyArgument += $archiveFile.FullName $verifyCode = Invoke-ExternalCommand -FilePath $verifyTool -ArgumentList $verifyArgument if ($verifyCode -eq 0) { - Write-Log "校验通过: $($archiveFile.Name)" -Level INFO + Write-BakNRetLog "校验通过: $($archiveFile.Name)" -Level INFO $stats.verified++ } else { - Write-Log "校验失败: $($archiveFile.Name)(退出码 $verifyCode)" -Level ERROR + Write-BakNRetLog "校验失败: $($archiveFile.Name)(退出码 $verifyCode)" -Level ERROR $stats.failed++ $failures += $displayPath } continue } - Write-Log "准备恢复: $displayPath <- $($archiveFile.Name)(来源:$($found.Source))" -Level INFO + Write-BakNRetLog "准备恢复: $displayPath <- $($archiveFile.Name)(来源:$($found.Source))" -Level INFO if ((Test-Path -LiteralPath $destPath) -and -not $Force) { try { - $destSummary = Get-FolderSummary -FolderPath $destPath + $destSummary = Get-BakNRetFolderSummary -FolderPath $destPath $archiveTime = $archiveFile.LastWriteTime if ($destSummary.LatestModifiedTime -and $destSummary.LatestModifiedTime -gt $archiveTime) { - Write-Log "跳过: $displayPath,目标目录比归档新(用 -Force 覆盖)" -Level WARN + Write-BakNRetLog "跳过: $displayPath,目标目录比归档新(用 -Force 覆盖)" -Level WARN $stats.skipped++ continue } } catch { - Write-Log "目标目录摘要读取失败,继续恢复:$_" -Level DEBUG + Write-BakNRetLog "目标目录摘要读取失败,继续恢复:$_" -Level DEBUG } } $plannedTargets = @($targets | Where-Object { $_.DestPath }) # 说清楚"这条会把哪些目录还原到哪儿、为什么" - Write-Log ("恢复计划:{0}(归档 {1})" -f $displayPath, $archiveFile.Name) + Write-BakNRetLog ("恢复计划:{0}(归档 {1})" -f $displayPath, $archiveFile.Name) foreach ($target in $plannedTargets) { $targetExists = Test-Path -LiteralPath $target.DestPath - Write-Log (" 目标:{0}" -f $target.DestPath) - Write-Log (" 归档内路径:{0}({1});{2}" -f $target.ArchivePath, + Write-BakNRetLog (" 目标:{0}" -f $target.DestPath) + Write-BakNRetLog (" 归档内路径:{0}({1});{2}" -f $target.ArchivePath, $(if ($target.IsFile) { '文件' } else { '目录' }), $(if ($targetExists) { '已存在,将覆盖同名文件' } else { '不存在,将新建' })) - if ($target.Description) { Write-Log (" 介绍:{0}" -f $target.Description) } + if ($target.Description) { Write-BakNRetLog (" 介绍:{0}" -f $target.Description) } } foreach ($target in $plannedTargets) { @@ -726,10 +726,10 @@ foreach ($line in $lines) { # Split-Path -Parent 对根路径(如 "E:\")返回空串,此时无父目录可建 $targetParent = Split-Path -Path $target.DestPath -Parent if ($targetParent) { - Write-Log "提示: 目标不存在,将新建 $targetParent" -Level DEBUG + Write-BakNRetLog "提示: 目标不存在,将新建 $targetParent" -Level DEBUG } else { - Write-Log "提示: 目标不存在,且没有可创建的父目录:$($target.DestPath)" -Level DEBUG + Write-BakNRetLog "提示: 目标不存在,且没有可创建的父目录:$($target.DestPath)" -Level DEBUG } } @@ -740,7 +740,7 @@ foreach ($line in $lines) { if (-not $shouldRun) { foreach ($target in $plannedTargets) { - Write-Log "[试运行] 将解压 $($archiveFile.Name) -> $($target.DestPath)" -Level INFO + Write-BakNRetLog "[试运行] 将解压 $($archiveFile.Name) -> $($target.DestPath)" -Level INFO } $stats.planned++ continue @@ -764,10 +764,10 @@ foreach ($line in $lines) { # CREATOR OWNER 把全权给"对象的属主",属主一变,原程序就没了权限。 # 必须在解压**之后**、对真实目标路径做(连接点在 Invoke-Extraction 里已经拆掉了)。 if ($SkipSecurity) { - Write-Log '按 -SkipSecurity 跳过了安全描述符恢复' -Level DEBUG + Write-BakNRetLog '按 -SkipSecurity 跳过了安全描述符恢复' -Level DEBUG } elseif (([string]$script:Config.Security.Mode) -eq 'Off') { - Write-Log '配置里 Security.Mode = Off,跳过安全描述符恢复' -Level DEBUG + Write-BakNRetLog '配置里 Security.Mode = Off,跳过安全描述符恢复' -Level DEBUG } else { $sidecarName = $null @@ -776,9 +776,9 @@ foreach ($line in $lines) { } if (-not $sidecarName) { $sidecarName = "$baseName.acl.json" } - $sidecar = Read-BaknretSecuritySidecar -Path (Join-Path $BackupDir $sidecarName) + $sidecar = Read-BakNRetSecuritySidecar -Path (Join-Path $BackupDir $sidecarName) if (-not $sidecar) { - Write-Log ("这个归档没有安全描述符旁挂文件({0}):恢复出来的属主/ACL 是新建对象的默认值 —— 原程序若依赖特殊权限(ProgramData 下的 CREATOR OWNER 最典型),会报无读写权限" -f $sidecarName) -Level WARN + Write-BakNRetLog ("这个归档没有安全描述符旁挂文件({0}):恢复出来的属主/ACL 是新建对象的默认值 —— 原程序若依赖特殊权限(ProgramData 下的 CREATOR OWNER 最典型),会报无读写权限" -f $sidecarName) -Level WARN } else { $sidMap = @{} @@ -787,7 +787,7 @@ foreach ($line in $lines) { $secTotal = 0; $secApplied = 0; $secOwnerFailed = 0; $secSkipped = 0; $secFailed = 0 $secMessages = @() foreach ($target in $plannedTargets) { - $sec = Restore-BaknretSecurity -Sidecar $sidecar -ArchiveRoot $target.ArchivePath ` + $sec = Restore-BakNRetSecurity -Sidecar $sidecar -ArchiveRoot $target.ArchivePath ` -TargetPath $target.DestPath -SidMap $sidMap $secTotal += $sec.Total $secApplied += $sec.Applied @@ -798,20 +798,20 @@ foreach ($line in $lines) { } $securityApplied += $secApplied - Write-Log ("安全描述符:回放 {0}/{1} 个对象(属主/属组未恢复 {2},跳过 {3},失败 {4})" -f ` + Write-BakNRetLog ("安全描述符:回放 {0}/{1} 个对象(属主/属组未恢复 {2},跳过 {3},失败 {4})" -f ` $secApplied, $secTotal, $secOwnerFailed, $secSkipped, $secFailed) -Level INFO foreach ($message in @($secMessages | Select-Object -First 5)) { - Write-Log (" ! {0}" -f $message) -Level WARN + Write-BakNRetLog (" ! {0}" -f $message) -Level WARN } if ($secFailed -gt 0) { - Write-Log ("恢复成功但安全描述符有 {0} 个对象失败,已计入失败条目(退出码 1)" -f $secFailed) -Level ERROR + Write-BakNRetLog ("恢复成功但安全描述符有 {0} 个对象失败,已计入失败条目(退出码 1)" -f $secFailed) -Level ERROR $failures += $displayPath } } } $stats.restored++ - Write-Log "恢复成功: $baseName" -Level INFO + Write-BakNRetLog "恢复成功: $baseName" -Level INFO if ($manifest.items.Contains($baseName)) { $record = $manifest.items[$baseName] @@ -830,7 +830,7 @@ foreach ($line in $lines) { } } catch { - Write-Log "恢复失败: $displayPath,$_" -Level ERROR + Write-BakNRetLog "恢复失败: $displayPath,$_" -Level ERROR $stats.failed++ $failures += $displayPath } @@ -845,48 +845,48 @@ if (-not $VerifyOnly -and $Only.Count -eq 0 -and $Skip.Count -eq 0) { Where-Object { $_.Extension.ToLower() -in $SupportedFormats -and $_.BaseName -notin $referencedArchives }) if ($orphans.Count -gt 0) { - Write-Log '以下归档没有任何清单条目指向(恢复不到,注意别误删):' -Level WARN + Write-BakNRetLog '以下归档没有任何清单条目指向(恢复不到,注意别误删):' -Level WARN foreach ($orphan in $orphans) { - Write-Log (" - {0}({1} MB,{2})" -f $orphan.Name, [math]::Round($orphan.Length / 1MB, 2), $orphan.LastWriteTime) -Level WARN + Write-BakNRetLog (" - {0}({1} MB,{2})" -f $orphan.Name, [math]::Round($orphan.Length / 1MB, 2), $orphan.LastWriteTime) -Level WARN } } } elseif (-not $VerifyOnly) { # 带 -Only/-Skip 时只有被选中的条目会被处理,其余归档都不在 $referencedArchives 里, # 按上面的算法报出来全是假孤儿(还会吓唬人说"注意别误删"),所以整段跳过。 - Write-Log '本次只恢复了部分条目,跳过孤儿归档审计(避免把未选中的归档误报成孤儿)' -Level DEBUG + Write-BakNRetLog '本次只恢复了部分条目,跳过孤儿归档审计(避免把未选中的归档误报成孤儿)' -Level DEBUG } try { if ($manifestDirty) { # 顺手维持"manifest 写了 archive,磁盘上就真有那个文件"这条不变式 - $null = Sync-BaknretManifestArchive -Manifest $manifest -BackupDir $BackupDir - Write-BaknretManifest -Path $manifestPath -Manifest $manifest | Out-Null - Write-Log 'manifest 已更新(记下本次恢复时间)' -Level DEBUG + $null = Sync-BakNRetManifestArchive -Manifest $manifest -BackupDir $BackupDir + Write-BakNRetManifest -Path $manifestPath -Manifest $manifest | Out-Null + Write-BakNRetLog 'manifest 已更新(记下本次恢复时间)' -Level DEBUG } else { - Write-Log 'manifest 无需更新:本次没有实际恢复任何条目' -Level DEBUG + Write-BakNRetLog 'manifest 无需更新:本次没有实际恢复任何条目' -Level DEBUG } } catch { - Write-Log "manifest 写回失败(不影响本次恢复):$_" -Level WARN + Write-BakNRetLog "manifest 写回失败(不影响本次恢复):$_" -Level WARN } if ($failures.Count -gt 0) { - Write-Log '失败条目:' -Level ERROR - foreach ($failure in $failures) { Write-Log " - $failure" -Level ERROR } + Write-BakNRetLog '失败条目:' -Level ERROR + foreach ($failure in $failures) { Write-BakNRetLog " - $failure" -Level ERROR } } $summaryText = "恢复完成 - 成功:$($stats.restored),跳过:$($stats.skipped),失败:$($stats.failed)" if ($securityApplied -gt 0) { $summaryText += ",安全描述符:$securityApplied 个对象" } if ($VerifyOnly) { $summaryText = "校验完成 - 通过:$($stats.verified),失败:$($stats.failed)" } if ($stats.planned -gt 0) { $summaryText += ",试运行计划:$($stats.planned)" } -Write-Log $summaryText -Level INFO +Write-BakNRetLog $summaryText -Level INFO -$logPath = Get-BaknretLogPath -if ($logPath) { Write-Log "日志已写入:$logPath" -Level INFO } -Exit-BaknretRunLock -Lock $runLock -Stop-BaknretLog +$logPath = Get-BakNRetLogPath +if ($logPath) { Write-BakNRetLog "日志已写入:$logPath" -Level INFO } +Exit-BakNRetRunLock -Lock $runLock +Stop-BakNRetLog if ($stats.failed -gt 0) { exit 1 } exit 0 diff --git a/tests/BakNRet.Formats.Tests.ps1 b/tests/BakNRet.Formats.Tests.ps1 index 99dfbcc..4f2218f 100644 --- a/tests/BakNRet.Formats.Tests.ps1 +++ b/tests/BakNRet.Formats.Tests.ps1 @@ -37,7 +37,7 @@ BeforeAll { # 见 BakNRet.Tests.ps1 里的说明:本机沙箱禁止 PowerShell 为捕获原生子进程输出建管道, # 所以走"临时 .cmd + 文件重定向 + Invoke-ExternalCommand(继承 stdio)"这条路。 - function Invoke-BaknretScript { + function Invoke-BakNRetScript { param( [Parameter(Mandatory = $true)][string]$Script, [hashtable]$Parameters = @{} @@ -138,7 +138,7 @@ Describe '软件名录:Slot 形状(新契约)' { } It '一个软件多个 Slot:Kind=Multi,Slot 按名排序且说明被保留' { - $catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache $catalog['pair'].Kind | Should -Be 'Multi' @($catalog['pair'].Slots).Count | Should -Be 2 (@($catalog['pair'].Slots | ForEach-Object { $_.Name }) -join ',') | Should -Be 'A,B' @@ -150,7 +150,7 @@ Describe '软件名录:Slot 形状(新契约)' { It '每个 Slot 都是一个独立的归档项来源(Kind=slot / Origin=catalog)' { $entry = ConvertFrom-BackupListLine -Line 'pair' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 2 (@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'A,B' (@($resolved.Items | ForEach-Object { $_.Kind }) -join ',') | Should -Be 'slot,slot' @@ -161,22 +161,22 @@ Describe '软件名录:Slot 形状(新契约)' { } It 'Slot 级排除写在 Slot 自己身上(相对本 Slot 的归档根)' { - $catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache (@($catalog['slotex'].Slots[0].Exclude) -join '|') | Should -Be '!*Cache|logs\' $entry = ConvertFrom-BackupListLine -Line 'slotex' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 (@($resolved.Items[0].Exclude) -join '|') | Should -Be '!*Cache|logs\' $resolved.HasExcludeOverride | Should -BeFalse } It '数组里"当前不存在"的 Slot 仍然产出归档项(恢复要靠它还原回原位)' { - $catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache $catalog['partial'].Kind | Should -Be 'Partial' @($catalog['partial'].Missing) | Should -Contain $script:MissingDir $entry = ConvertFrom-BackupListLine -Line 'partial' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 2 @($resolved.Items | ForEach-Object { $_.RealPath }) | Should -Contain $script:MissingDir $resolved.Blocking | Should -BeNullOrEmpty # 源不存在不是致命错误 @@ -184,7 +184,7 @@ Describe '软件名录:Slot 形状(新契约)' { It '文件 Slot:归档项是文件项(归档里就是名为 Slot 的文件)' { $entry = ConvertFrom-BackupListLine -Line 'fileapp' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 1 $resolved.Items[0].IsFile | Should -BeTrue $resolved.Items[0].ArchivePath | Should -Be 'Cfg' @@ -193,13 +193,13 @@ Describe '软件名录:Slot 形状(新契约)' { } It '旧的裸字符串 / 字符串数组写法被拒绝(ERROR + 跳过)' { - $catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache $catalog.ContainsKey('legacystr') | Should -BeFalse $catalog.ContainsKey('legacyarr') | Should -BeFalse } It '旧的 @{ Dirs = @(...) } 写法不再展开,留下 Invalid 与原因' { - $catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache $catalog.ContainsKey('legacydirs') | Should -BeTrue $catalog['legacydirs'].Kind | Should -Be 'Invalid' $catalog['legacydirs'].Error | Should -Not -BeNullOrEmpty @@ -208,7 +208,7 @@ Describe '软件名录:Slot 形状(新契约)' { It '多 Slot 的名录条目在清单里仍然按软件名命名归档' { $entry = ConvertFrom-BackupListLine -Line 'pair' - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be 'pair' + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be 'pair' } } @@ -227,7 +227,7 @@ Describe '清单修饰符:新契约格式' { It ':: 覆盖 Path:单 Slot 条目直接生效' { $entry = ConvertFrom-BackupListLine -Line "solo :: $script:DirB" $entry.Overrides.ContainsKey('Path') | Should -BeTrue - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 1 $resolved.Items[0].ArchivePath | Should -Be 'Only' $resolved.Items[0].RealPath | Should -Be $script:DirB @@ -236,7 +236,7 @@ Describe '清单修饰符:新契约格式' { It ':: 覆盖遇到多 Slot 条目 -> Blocking(不知道给哪一个,绝不猜)' { $entry = ConvertFrom-BackupListLine -Line "pair :: $script:DirB" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 0 $resolved.Blocking | Should -Match '不能用一个' } @@ -246,7 +246,7 @@ Describe '清单修饰符:新契约格式' { (@($entry.ExcludePatterns) -join '|') | Should -Be 'logs\|!*Cache' (@($entry.Includes) -join '|') | Should -Be "Mods:$script:DirB" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3 $resolved.HasExcludeOverride | Should -BeTrue $resolved.HasIncludeOverride | Should -BeTrue @($resolved.Items | ForEach-Object { $_.ArchivePath }) | Should -Contain 'Mods' @@ -261,13 +261,13 @@ Describe '清单修饰符:新契约格式' { } It ':encrypt / :!encrypt 覆盖名录里的加密默认值' { - $base = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp') -CatalogPath $script:FormatCatalog -MaxDepth 3 + $base = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp') -CatalogPath $script:FormatCatalog -MaxDepth 3 $base.Encrypt | Should -BeTrue # 名录里 Cfg Slot 标了 Encrypt - $off = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp :!encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3 + $off = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp :!encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3 $off.Encrypt | Should -BeFalse - $on = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'pair :encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3 + $on = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'pair :encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3 $on.Encrypt | Should -BeTrue } @@ -278,8 +278,8 @@ Describe '清单修饰符:新契约格式' { # @pathname 对软件名条目也会改用真实路径命名 $entry = ConvertFrom-BackupListLine -Line 'pair @pathname' - $expected = Get-BackupBaseName -RawPath $script:DirA - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be $expected + $expected = Get-BakNRetBackupBaseName -RawPath $script:DirA + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be $expected } It '同一行里重复写同类记号会累积(不静默丢掉前一条规则)' { @@ -350,14 +350,14 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ $script:SlotList = Write-ListFile -Path (Join-Path $script:SlotRoot 'list.txt') -Content "appkit`n" $script:SlotBackupDir = Join-Path $script:SlotRoot 'Backups' - $script:SlotBackupRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $script:SlotBackupRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:SlotList BackupDir = $script:SlotBackupDir ConfigPath = $script:SlotConfig Force = $true QuietTool = $true } - $script:SlotManifest = Read-BaknretManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json') + $script:SlotManifest = Read-BakNRetManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json') $script:SlotArchive = @(Get-ChildItem -LiteralPath $script:SlotBackupDir -File -Filter *.7z)[0] } @@ -367,7 +367,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ } It '归档顶层就是各个 Slot 名(目录 Slot + 文件 Slot + Include 项)' { - $top = @(Get-ArchiveTopLevelNames -ArchivePath $script:SlotArchive.FullName -SevenZip $script:SevenZip) + $top = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $script:SlotArchive.FullName -SevenZip $script:SevenZip) (@($top | Sort-Object) -join ',') | Should -Be 'Cfg,Data,Extra,Modules' } @@ -396,7 +396,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ Remove-Item -LiteralPath (Join-Path $script:SlotRoot 'apps') -Recurse -Force Remove-Item -LiteralPath $script:SlotInclude -Recurse -Force - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:SlotList BackupDir = $script:SlotBackupDir ConfigPath = $script:SlotConfig @@ -422,7 +422,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ } It '恢复之后 manifest 记下 lastRestoreAt' { - $manifest = Read-BaknretManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json') + $manifest = Read-BakNRetManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json') $manifest.items['appkit'].lastRestoreAt | Should -Not -BeNullOrEmpty } } @@ -462,7 +462,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven } It '归档确实是旧布局:顶层是源目录名而不是 Slot 名' { - $top = @(Get-ArchiveTopLevelNames -ArchivePath $script:LegacyArchive -SevenZip $script:SevenZip) + $top = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $script:LegacyArchive -SevenZip $script:SevenZip) (@($top | Sort-Object) -join ',') | Should -Be $script:LegacyLeaf } @@ -471,7 +471,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven # 不能靠 7z 的退出码猜(7z 对不存在的条目同样返回 0)。 Remove-Item -LiteralPath (Join-Path $script:LegacyDestParent $script:LegacyLeaf) -Recurse -Force -ErrorAction SilentlyContinue - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:LegacyList BackupDir = $script:LegacyBackupDir ConfigPath = $script:LegacyConfig @@ -490,7 +490,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven $missingList = Write-ListFile -Path (Join-Path $script:LegacyRoot 'missing-list.txt') ` -Content "oldapp :: $script:LegacyRoot\dest2\Nothing Here`n" - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $missingList BackupDir = $script:LegacyBackupDir ConfigPath = $script:LegacyConfig @@ -522,7 +522,7 @@ Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script $script:RejectConfig = Write-ListFile -Path (Join-Path $script:RejectRoot 'config.psd1') -Content "@{ SoftwareCatalog = '$script:RejectCatalog' }`n" $script:RejectBackupDir = Join-Path $script:RejectRoot 'Backups' - $script:RejectRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $script:RejectRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:RejectList BackupDir = $script:RejectBackupDir ConfigPath = $script:RejectConfig @@ -538,7 +538,7 @@ Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script } It 'manifest 里记下这次是 failed,并带上原因' { - $manifest = Read-BaknretManifest -Path (Join-Path $script:RejectBackupDir 'manifest.json') + $manifest = Read-BakNRetManifest -Path (Join-Path $script:RejectBackupDir 'manifest.json') $record = $manifest.items['collide'] $record.action | Should -Be 'failed' $record.reason | Should -Match '归档内路径冲突' @@ -568,7 +568,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip # 第一次:清单里有 my-app Write-ListFile -Path $script:OrphanList -Content "my-app`n" | Out-Null - $null = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $null = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:OrphanList BackupDir = $script:OrphanBackupDir ConfigPath = $script:OrphanConfig @@ -578,7 +578,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip # 第二次:把条目从清单里拿掉,归档留在磁盘上 Write-ListFile -Path $script:OrphanList -Content "# 清单里没有条目了`n" | Out-Null - $script:OrphanRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $script:OrphanRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:OrphanList BackupDir = $script:OrphanBackupDir ConfigPath = $script:OrphanConfig @@ -588,7 +588,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip It '归档确实还在磁盘上,manifest 里也还留着历史记录' { Test-Path -LiteralPath (Join-Path $script:OrphanBackupDir 'my-app.7z') | Should -BeTrue - $manifest = Read-BaknretManifest -Path (Join-Path $script:OrphanBackupDir 'manifest.json') + $manifest = Read-BakNRetManifest -Path (Join-Path $script:OrphanBackupDir 'manifest.json') $manifest.items.Contains('my-app') | Should -BeTrue } @@ -610,16 +610,16 @@ Describe 'manifest 一致性:archive 字段只在文件真的存在时才写' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SyncRoot 'real.7z') 'x' $script:SyncManifestPath = Join-Path $script:SyncRoot 'manifest.json' - $manifest = Read-BaknretManifest -Path $script:SyncManifestPath + $manifest = Read-BakNRetManifest -Path $script:SyncManifestPath $manifest.items['real'] = [pscustomobject]@{ archive = 'real.7z'; action = 'backed-up'; source = 'real' } $manifest.items['gone'] = [pscustomobject]@{ archive = 'gone.7z'; action = 'missing-source'; source = 'gone' } $manifest.items['none'] = [pscustomobject]@{ archive = $null; action = 'missing-source'; source = 'none' } - Write-BaknretManifest -Path $script:SyncManifestPath -Manifest $manifest | Out-Null + Write-BakNRetManifest -Path $script:SyncManifestPath -Manifest $manifest | Out-Null } It '存在的归档保留 archive,不存在的被清空' { - $manifest = Read-BaknretManifest -Path $script:SyncManifestPath - $cleared = Sync-BaknretManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot + $manifest = Read-BakNRetManifest -Path $script:SyncManifestPath + $cleared = Sync-BakNRetManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot $cleared.Count | Should -Be 1 $cleared[0] | Should -Be 'gone' @@ -629,16 +629,16 @@ Describe 'manifest 一致性:archive 字段只在文件真的存在时才写' } It '清空 archive 时保留条目本身的历史(source / action 不动)' { - $manifest = Read-BaknretManifest -Path $script:SyncManifestPath - $null = Sync-BaknretManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot + $manifest = Read-BakNRetManifest -Path $script:SyncManifestPath + $null = Sync-BakNRetManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot $manifest.items['gone'].source | Should -Be 'gone' $manifest.items['gone'].action | Should -Be 'missing-source' } It '人工删掉归档之后再同步一次,记录会被纠正过来' { Remove-Item -LiteralPath (Join-Path $script:SyncRoot 'real.7z') -Force - $manifest = Read-BaknretManifest -Path $script:SyncManifestPath - $cleared = Sync-BaknretManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot + $manifest = Read-BakNRetManifest -Path $script:SyncManifestPath + $cleared = Sync-BakNRetManifestArchive -Manifest $manifest -BackupDir $script:SyncRoot $cleared | Should -Contain 'real' $manifest.items['real'].archive | Should -BeNullOrEmpty diff --git a/tests/BakNRet.Security.Tests.ps1 b/tests/BakNRet.Security.Tests.ps1 index 79ab128..7cf1973 100644 --- a/tests/BakNRet.Security.Tests.ps1 +++ b/tests/BakNRet.Security.Tests.ps1 @@ -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 diff --git a/tests/BakNRet.Tests.ps1 b/tests/BakNRet.Tests.ps1 index 087e3a6..32cd085 100644 --- a/tests/BakNRet.Tests.ps1 +++ b/tests/BakNRet.Tests.ps1 @@ -54,7 +54,7 @@ BeforeAll { # 于是这里改成:把整条命令写进临时 .cmd,由 cmd 自己把输出重定向到文件, # 再用 Invoke-ExternalCommand 调 `cmd /c <批处理>`。退出码来自真实进程, # 输出从文件读回,全程不经过 PowerShell 的管道。 - function Invoke-BaknretScript { + function Invoke-BakNRetScript { param( [Parameter(Mandatory = $true)][string]$Script, [hashtable]$Parameters = @{} @@ -337,29 +337,29 @@ Describe 'BackupList.txt 解析' { } It '记号切分:引号内的空白不切分,引号本身留在记号里' { - (@(Split-BaknretToken -Text 'A "B C" D') -join '|') | Should -Be 'A|"B C"|D' + (@(Split-BakNRetToken -Text 'A "B C" D') -join '|') | Should -Be 'A|"B C"|D' # 引号不配对时按"引号延伸到行尾"处理,不抛异常 - (@(Split-BaknretToken -Text 'A "B C') -join '|') | Should -Be 'A|"B C' - (@(Split-BaknretToken -Text '') -join '|') | Should -Be '' + (@(Split-BakNRetToken -Text 'A "B C') -join '|') | Should -Be 'A|"B C' + (@(Split-BakNRetToken -Text '') -join '|') | Should -Be '' } It '记号识别只认完整记号' { - (Test-BaknretMarker -Token '::') | Should -Be 'path' - (Test-BaknretMarker -Token ':-') | Should -Be 'exclude' - (Test-BaknretMarker -Token ':+') | Should -Be 'include' - (Test-BaknretMarker -Token ':encrypt') | Should -Be 'encrypt' - (Test-BaknretMarker -Token ':!encrypt') | Should -Be 'noencrypt' - (Test-BaknretMarker -Token '@') | Should -Be 'at' - (Test-BaknretMarker -Token 'C:\a:-b') | Should -BeNullOrEmpty - (Test-BaknretMarker -Token ':') | Should -BeNullOrEmpty + (Test-BakNRetMarker -Token '::') | Should -Be 'path' + (Test-BakNRetMarker -Token ':-') | Should -Be 'exclude' + (Test-BakNRetMarker -Token ':+') | Should -Be 'include' + (Test-BakNRetMarker -Token ':encrypt') | Should -Be 'encrypt' + (Test-BakNRetMarker -Token ':!encrypt') | Should -Be 'noencrypt' + (Test-BakNRetMarker -Token '@') | Should -Be 'at' + (Test-BakNRetMarker -Token 'C:\a:-b') | Should -BeNullOrEmpty + (Test-BakNRetMarker -Token ':') | Should -BeNullOrEmpty } It '去引号:成对才去,不成对原样返回' { - (Remove-BaknretQuote -Text '"a"') | Should -Be 'a' - (Remove-BaknretQuote -Text "'a'") | Should -Be 'a' - (Remove-BaknretQuote -Text 'a') | Should -Be 'a' - (Remove-BaknretQuote -Text '"a') | Should -Be '"a' - (Remove-BaknretQuote -Text '""') | Should -Be '' + (Remove-BakNRetQuote -Text '"a"') | Should -Be 'a' + (Remove-BakNRetQuote -Text "'a'") | Should -Be 'a' + (Remove-BakNRetQuote -Text 'a') | Should -Be 'a' + (Remove-BakNRetQuote -Text '"a') | Should -Be '"a' + (Remove-BakNRetQuote -Text '""') | Should -Be '' } } @@ -382,49 +382,49 @@ Describe '归档命名' { # 注意:Pester 的 It 名字里出现 $( ) 会被求值,所以标题里不要写子表达式。 It '基础命名规则:末级名_from_上级路径用加号连接' { - Get-BackupBaseName -RawPath 'C:\Programs\FooClolor' | Should -Be 'FooClolor_from_C_+Programs' + Get-BakNRetBackupBaseName -RawPath 'C:\Programs\FooClolor' | Should -Be 'FooClolor_from_C_+Programs' } It '/ 与 \ 以及重复分隔符结果一致' { - $a = Get-BackupBaseName -RawPath 'C:\a/b' - $b = Get-BackupBaseName -RawPath 'C:\\a\\\\b' + $a = Get-BakNRetBackupBaseName -RawPath 'C:\a/b' + $b = Get-BakNRetBackupBaseName -RawPath 'C:\\a\\\\b' $a | Should -Be $b } It '命名与路径往返' { - $base = Get-BackupBaseName -RawPath 'D:\UserData\Documents\Aria' - Convert-BackupFileNameToPath -FileName ($base + '.7z') | Should -Be 'D:\UserData\Documents\Aria' + $base = Get-BakNRetBackupBaseName -RawPath 'D:\UserData\Documents\Aria' + Convert-BakNRetBackupFileNameToPath -FileName ($base + '.7z') | Should -Be 'D:\UserData\Documents\Aria' } It '归档名里不含非法文件名字符' { - $base = Get-BackupBaseName -RawPath 'C:\ac|d?e*f' + $base = Get-BakNRetBackupBaseName -RawPath 'C:\ac|d?e*f' ($base.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars())) | Should -Be -1 } It '%变量% 写法里的 % 会保留在归档名里' { - Get-BackupBaseName -RawPath '%UserProfile%\.ssh' | Should -Be '.ssh_from_%UserProfile%' + Get-BakNRetBackupBaseName -RawPath '%UserProfile%\.ssh' | Should -Be '.ssh_from_%UserProfile%' } It '软件名条目:默认用软件名做归档名' { $entry = ConvertFrom-BackupListLine -Line 'my-app' - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'my-app' + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'my-app' } It '字面路径条目:仍用路径命名算法(现有清单无需改写)' { $entry = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor' - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'FooClolor_from_C_+Programs' + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'FooClolor_from_C_+Programs' } It '@pathname 用名录里的真实路径命名,而不是软件名' { $entry = ConvertFrom-BackupListLine -Line 'my-app @pathname' - $expected = Get-BackupBaseName -RawPath $script:NamingApp - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be $expected + $expected = Get-BakNRetBackupBaseName -RawPath $script:NamingApp + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be $expected } It '名录里没有该名字:归档名退回可读目录名,并给出 Error' { $entry = ConvertFrom-BackupListLine -Line 'no-such-thing' - (Get-ItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'no-such-thing' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3 + (Get-BakNRetItemArchiveName -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3) | Should -Be 'no-such-thing' + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:NamingCatalog -MaxDepth 3 $resolved.Error | Should -Not -BeNullOrEmpty @($resolved.Items).Count | Should -Be 0 } @@ -443,142 +443,142 @@ Describe '7z 排除参数翻译' { function New-TestArchiveItem { param([string]$ArchivePath, [string]$RealPath = 'C:\nonexistent-path') - New-BaknretArchiveItem -ArchivePath $ArchivePath -RealPath $RealPath -Kind 'path' -Origin 'path' -Exists $false -IsFile $false + New-BakNRetArchiveItem -ArchivePath $ArchivePath -RealPath $RealPath -Kind 'path' -Origin 'path' -Exists $false -IsFile $false } } It '相对模式自动补上归档根目录名' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('logs\') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('logs\') @($result.Arguments) | Should -Contain '-x!Foo\logs' $result.Error | Should -BeNullOrEmpty } - It '模式已带根名前缀时,Split-BaknretPatternScope 先摘掉前缀,结果不重复' { + It '模式已带根名前缀时,Split-BakNRetPatternScope 先摘掉前缀,结果不重复' { $item = New-TestArchiveItem -ArchivePath 'Foo' - $map = Split-BaknretPatternScope -Items @($item) -Patterns @('Foo\logs\') + $map = Split-BakNRetPatternScope -Items @($item) -Patterns @('Foo\logs\') (@($map[0]) -join '|') | Should -Be 'logs' - $result = Get-BaknretExcludeArgument -Item $item -Patterns @($map[0]) + $result = Get-BakNRetExcludeArgument -Item $item -Patterns @($map[0]) @($result.Arguments) | Should -Contain '-x!Foo\logs' @($result.Arguments) | Should -Not -Contain '-x!Foo\Foo\logs' } It '! 前缀翻译成递归组件匹配(-xr!)' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache') @($result.Arguments) | Should -Contain '-xr!*Cache' } It '模式里的空格转成 ?(归档项自己的名字按原样保留)' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('Default\Code Cache') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('Default\Code Cache') @($result.Arguments) | Should -Contain '-x!User Data\Default\Code?Cache' - $plain = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('Default\Code Cache') + $plain = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('Default\Code Cache') @($plain.Arguments) | Should -Contain '-x!Foo\Default\Code?Cache' } It '生成的参数里绝不出现引号(旧实现 -x!"路径" 让排除全部失效)' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache', 'Default\Code Cache') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'User Data') -Patterns @('!*Cache', 'Default\Code Cache') (@($result.Arguments) -join ' ') | Should -Not -Match '"' } It '空模式被忽略' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('', ' ', '!') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Foo') -Patterns @('', ' ', '!') @($result.Arguments).Count | Should -Be 0 $result.Error | Should -BeNullOrEmpty } It '!re: 会把正则展开成精确的 -x! 参数' { $item = New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree - $result = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:^Cache$') + $result = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:^Cache$') @($result.Arguments) | Should -Contain '-x!Top\sub\Cache' # 命中目录后不再往下走(一个命中只产生一条参数,避免命令行爆炸) - $pruned = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:^sub$') + $pruned = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:^sub$') @($pruned.Arguments) | Should -Contain '-x!Top\sub' @($pruned.Arguments).Count | Should -Be 1 } It '非法正则给出 Error,而不是抛异常' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) -Patterns @('!re:[') + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) -Patterns @('!re:[') @($result.Arguments).Count | Should -Be 0 $result.Error | Should -Not -BeNullOrEmpty } It '正则命中数超过上限时明确报错' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) ` + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top' -RealPath $script:ExcludeTree) ` -Patterns @('!re:.') -MaxRegexMatches 1 $result.Error | Should -Not -BeNullOrEmpty } It '参数总长超过安全上限时明确报错' { - $result = Get-BaknretExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top') ` + $result = Get-BakNRetExcludeArgument -Item (New-TestArchiveItem -ArchivePath 'Top') ` -Patterns @('!*zzz') -MaxCommandLineChars 5 $result.Error | Should -Not -BeNullOrEmpty } - It 'Split-BaknretPatternScope:指名 Slot 的模式只分给那个 Slot' { + It 'Split-BakNRetPatternScope:指名 Slot 的模式只分给那个 Slot' { $items = @( New-TestArchiveItem -ArchivePath 'Alpha' New-TestArchiveItem -ArchivePath 'Beta' ) - $map = Split-BaknretPatternScope -Items $items -Patterns @('Alpha\logs\') + $map = Split-BakNRetPatternScope -Items $items -Patterns @('Alpha\logs\') (@($map[0]) -join '|') | Should -Be 'logs' @($map[1]).Count | Should -Be 0 # 用 / 分隔也一样 - $slash = Split-BaknretPatternScope -Items $items -Patterns @('Beta/sub') + $slash = Split-BakNRetPatternScope -Items $items -Patterns @('Beta/sub') (@($slash[1]) -join '|') | Should -Be 'sub' @($slash[0]).Count | Should -Be 0 } - It 'Split-BaknretPatternScope:没点名任何 Slot 的模式广播给每一项' { + It 'Split-BakNRetPatternScope:没点名任何 Slot 的模式广播给每一项' { $items = @( New-TestArchiveItem -ArchivePath 'Alpha' New-TestArchiveItem -ArchivePath 'Beta' ) - $map = Split-BaknretPatternScope -Items $items -Patterns @('plain\path', 'Alpha') + $map = Split-BakNRetPatternScope -Items $items -Patterns @('plain\path', 'Alpha') (@($map[0]) -join '|') | Should -Be 'plain\path|Alpha' (@($map[1]) -join '|') | Should -Be 'plain\path|Alpha' } - It 'Split-BaknretPatternScope:! 与 !re: 模式广播给每一项' { + It 'Split-BakNRetPatternScope:! 与 !re: 模式广播给每一项' { $items = @( New-TestArchiveItem -ArchivePath 'Alpha' New-TestArchiveItem -ArchivePath 'Beta' ) - $map = Split-BaknretPatternScope -Items $items -Patterns @('!*Cache', '!re:^x$') + $map = Split-BakNRetPatternScope -Items $items -Patterns @('!*Cache', '!re:^x$') (@($map[0]) -join '|') | Should -Be '!*Cache|!re:^x$' (@($map[1]) -join '|') | Should -Be '!*Cache|!re:^x$' } - It 'Split-BaknretPatternScope:每个归档项都有键(没有模式时是空数组)' { + It 'Split-BakNRetPatternScope:每个归档项都有键(没有模式时是空数组)' { $items = @( New-TestArchiveItem -ArchivePath 'Alpha' New-TestArchiveItem -ArchivePath 'Beta' ) - $map = Split-BaknretPatternScope -Items $items -Patterns @() + $map = Split-BakNRetPatternScope -Items $items -Patterns @() $map.ContainsKey(0) | Should -BeTrue $map.ContainsKey(1) | Should -BeTrue @($map[0]).Count | Should -Be 0 } - It 'Merge-BaknretExcludeArgument 去重且保持顺序' { - $merged = Merge-BaknretExcludeArgument -ArgumentLists @(@('-x!a', '-x!b'), @('-x!b', '-x!c')) + It 'Merge-BakNRetExcludeArgument 去重且保持顺序' { + $merged = Merge-BakNRetExcludeArgument -ArgumentLists @(@('-x!a', '-x!b'), @('-x!b', '-x!c')) (@($merged) -join ' ') | Should -Be '-x!a -x!b -x!c' } It '多 Slot 条目:逐项分配 + 翻译 + 去重合成一份参数' { $items = @( - New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Alpha' -Origin 'catalog' -Exists $true - New-BaknretArchiveItem -ArchivePath 'Beta' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Beta' -Origin 'catalog' -Exists $true + New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Alpha' -Origin 'catalog' -Exists $true + New-BakNRetArchiveItem -ArchivePath 'Beta' -RealPath $script:ExcludeTree -Kind 'slot' -Slot 'Beta' -Origin 'catalog' -Exists $true ) $patterns = @('Alpha\logs\', '!*Cache', 'Beta\sub', '!*Cache') - $map = Split-BaknretPatternScope -Items $items -Patterns $patterns + $map = Split-BakNRetPatternScope -Items $items -Patterns $patterns $lists = @() for ($index = 0; $index -lt $items.Count; $index++) { - $lists += , @((Get-BaknretExcludeArgument -Item $items[$index] -Patterns @($map[$index])).Arguments) + $lists += , @((Get-BakNRetExcludeArgument -Item $items[$index] -Patterns @($map[$index])).Arguments) } - $effective = @(Merge-BaknretExcludeArgument -ArgumentLists $lists) + $effective = @(Merge-BakNRetExcludeArgument -ArgumentLists $lists) $effective | Should -Contain '-x!Alpha\logs' $effective | Should -Contain '-x!Beta\sub' $effective | Should -Contain '-xr!*Cache' @@ -600,8 +600,8 @@ Describe '归档项与暂存目录' { Set-Content -Encoding UTF8 -LiteralPath $script:StagingFile 'file-content' } - It 'New-BaknretArchiveItem 规范化归档内路径并算出 TopName' { - $item = New-BaknretArchiveItem -ArchivePath '\Alpha\Sub\' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' + It 'New-BakNRetArchiveItem 规范化归档内路径并算出 TopName' { + $item = New-BakNRetArchiveItem -ArchivePath '\Alpha\Sub\' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' $item.ArchivePath | Should -Be 'Alpha\Sub' $item.TopName | Should -Be 'Alpha' $item.Kind | Should -Be 'slot' @@ -610,10 +610,10 @@ Describe '归档项与暂存目录' { @($item.Exclude).Count | Should -Be 0 } - It 'Get-BaknretArchiveTopName 取归档内相对路径的第一段' { - (Get-BaknretArchiveTopName -ArchivePath 'A\B\c.txt') | Should -Be 'A' - (Get-BaknretArchiveTopName -ArchivePath ' A ') | Should -Be 'A' - (Get-BaknretArchiveTopName -ArchivePath '') | Should -Be '' + It 'Get-BakNRetArchiveTopName 取归档内相对路径的第一段' { + (Get-BakNRetArchiveTopName -ArchivePath 'A\B\c.txt') | Should -Be 'A' + (Get-BakNRetArchiveTopName -ArchivePath ' A ') | Should -Be 'A' + (Get-BakNRetArchiveTopName -ArchivePath '') | Should -Be '' } It '暂存半途失败会自己清干净(不留指向真实数据的 junction)' { @@ -628,12 +628,12 @@ Describe '归档项与暂存目录' { $items = @( # 第一项会成功挂上,而且是**目录走 junction** —— 这正是危险的那个物件:它指向 # 真实数据,而临时目录迟早会被某次 Remove-Item -Recurse 扫到。 - New-BaknretArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false + New-BakNRetArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false # 第二项必然失败:源文件不存在,硬链接与复制都会失败 - New-BaknretArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true + New-BakNRetArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true ) - { New-BaknretArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') } | Should -Throw + { New-BakNRetArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') } | Should -Throw $left = @(Get-ChildItem -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue) @($left | Where-Object { $_.LinkType -eq 'Junction' }).Count | Should -Be 0 @@ -644,12 +644,12 @@ Describe '归档项与暂存目录' { It '目录项用 junction 挂进暂存目录,文件项用硬链接(名字就是归档内路径)' { $items = @( - New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false - New-BaknretArchiveItem -ArchivePath 'Nested\Deep' -RealPath $script:StagingDir -Kind 'include' -Origin 'include' -Exists $true -IsFile $false - New-BaknretArchiveItem -ArchivePath 'Cfg' -RealPath $script:StagingFile -Kind 'slot' -Slot 'Cfg' -Exists $true -IsFile $true + New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false + New-BakNRetArchiveItem -ArchivePath 'Nested\Deep' -RealPath $script:StagingDir -Kind 'include' -Origin 'include' -Exists $true -IsFile $false + New-BakNRetArchiveItem -ArchivePath 'Cfg' -RealPath $script:StagingFile -Kind 'slot' -Slot 'Cfg' -Exists $true -IsFile $true ) - $stage = New-BaknretArchiveStaging -Items $items + $stage = New-BakNRetArchiveStaging -Items $items try { $alpha = Get-Item -LiteralPath (Join-Path $stage 'Alpha') -Force $alpha.LinkType | Should -Be 'Junction' @@ -663,15 +663,15 @@ Describe '归档项与暂存目录' { (Get-Content -Encoding UTF8 -LiteralPath $cfg.FullName -Raw).Trim() | Should -Be 'file-content' } finally { - Remove-BaknretArchiveStaging -Root $stage + Remove-BakNRetArchiveStaging -Root $stage } } - It 'Remove-BaknretArchiveStaging 只删连接点,不顺着走进真实目录' { - $stage = New-BaknretArchiveStaging -Items @( - New-BaknretArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false + It 'Remove-BakNRetArchiveStaging 只删连接点,不顺着走进真实目录' { + $stage = New-BakNRetArchiveStaging -Items @( + New-BakNRetArchiveItem -ArchivePath 'Alpha' -RealPath $script:StagingDir -Kind 'slot' -Slot 'Alpha' -Exists $true -IsFile $false ) - Remove-BaknretArchiveStaging -Root $stage + Remove-BakNRetArchiveStaging -Root $stage Test-Path -LiteralPath $stage | Should -BeFalse Test-Path -LiteralPath (Join-Path $script:StagingDir 'inner\a.txt') | Should -BeTrue @@ -683,23 +683,23 @@ Describe '命令行参数拼接' { # ============================================================================ It '无空格参数原样输出' { - ConvertTo-NativeArgumentString -ArgumentList @('a', '-mx=9') | Should -Be 'a -mx=9' + ConvertTo-BakNRetNativeArgumentString -ArgumentList @('a', '-mx=9') | Should -Be 'a -mx=9' } It '含空格参数加引号' { - ConvertTo-NativeArgumentString -ArgumentList @('C:\a b\c') | Should -Be '"C:\a b\c"' + ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\a b\c') | Should -Be '"C:\a b\c"' } It '引号内的结尾反斜杠翻倍(否则会被当成转义引号)' { - ConvertTo-NativeArgumentString -ArgumentList @('C:\a b\') | Should -Be '"C:\a b\\"' + ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\a b\') | Should -Be '"C:\a b\\"' } It '内部引号被转义' { - ConvertTo-NativeArgumentString -ArgumentList @('a"b c') | Should -Be '"a\"b c"' + ConvertTo-BakNRetNativeArgumentString -ArgumentList @('a"b c') | Should -Be '"a\"b c"' } It '空参数输出一对空引号' { - ConvertTo-NativeArgumentString -ArgumentList @('') | Should -Be '""' + ConvertTo-BakNRetNativeArgumentString -ArgumentList @('') | Should -Be '""' } } @@ -709,11 +709,11 @@ Describe 'manifest 与配置' { It 'manifest 读写往返' { $path = Join-Path $script:Sandbox 'roundtrip\manifest.json' - $manifest = Read-BaknretManifest -Path $path + $manifest = Read-BakNRetManifest -Path $path $manifest.items['demo'] = [pscustomobject]@{ archive = 'demo.7z'; action = 'backed-up'; verified = $true } - Write-BaknretManifest -Path $path -Manifest $manifest | Out-Null + Write-BakNRetManifest -Path $path -Manifest $manifest | Out-Null - $again = Read-BaknretManifest -Path $path + $again = Read-BakNRetManifest -Path $path $again.items.Count | Should -Be 1 $again.items['demo'].archive | Should -Be 'demo.7z' $again.items['demo'].verified | Should -BeTrue @@ -721,7 +721,7 @@ Describe 'manifest 与配置' { It 'manifest 原样保存 layouts(name/kind),全新恢复时靠它判断目录还是文件' { $path = Join-Path $script:Sandbox 'layouts\manifest.json' - $manifest = Read-BaknretManifest -Path $path + $manifest = Read-BakNRetManifest -Path $path $manifest.items['app'] = [pscustomobject]@{ archive = 'app.7z' layouts = @( @@ -729,28 +729,28 @@ Describe 'manifest 与配置' { [pscustomobject]@{ name = 'Cfg'; kind = 'file' } ) } - Write-BaknretManifest -Path $path -Manifest $manifest | Out-Null + Write-BakNRetManifest -Path $path -Manifest $manifest | Out-Null - $again = Read-BaknretManifest -Path $path + $again = Read-BakNRetManifest -Path $path @($again.items['app'].layouts).Count | Should -Be 2 (@($again.items['app'].layouts | ForEach-Object { $_.name + ':' + $_.kind }) -join '|') | Should -Be 'Data:dir|Cfg:file' } It 'manifest 损坏时不抛异常,而是重建空清单' { $path = Write-ListFile -Path (Join-Path $script:Sandbox 'broken.json') -Content '{ this is not json' - { $script:broken = Read-BaknretManifest -Path $path } | Should -Not -Throw + { $script:broken = Read-BakNRetManifest -Path $path } | Should -Not -Throw $script:broken.items.Count | Should -Be 0 } It '配置缺失时返回默认值' { - $config = Get-BaknretConfig -Path (Join-Path $script:Sandbox 'no-such-config.psd1') + $config = Get-BakNRetConfig -Path (Join-Path $script:Sandbox 'no-such-config.psd1') $config.BackupDir | Should -Be 'Backups' $config.VerifyArchive | Should -BeTrue } It '配置嵌套段落合并且不丢默认键' { $path = Write-ListFile -Path (Join-Path $script:Sandbox 'cfg.psd1') -Content "@{ MinFreeSpaceGB = 3; Encryption = @{ Enabled = `$true } }" - $config = Get-BaknretConfig -Path $path + $config = Get-BakNRetConfig -Path $path $config.MinFreeSpaceGB | Should -Be 3 $config.Encryption.Enabled | Should -BeTrue # 没写到的子键要保留默认值。Encryption 是哈希表,用 ContainsKey 查。 @@ -761,9 +761,9 @@ Describe 'manifest 与配置' { $saved = $env:BAKNRET_PASSWORD try { $env:BAKNRET_PASSWORD = 'sekrit' - (Get-BaknretPassword) | Should -Be 'sekrit' + (Get-BakNRetPassword) | Should -Be 'sekrit' $env:BAKNRET_PASSWORD = $null - (Get-BaknretPassword) | Should -BeNullOrEmpty + (Get-BakNRetPassword) | Should -BeNullOrEmpty } finally { $env:BAKNRET_PASSWORD = $saved @@ -827,7 +827,7 @@ Describe '软件名录' { } It '名录解析:条目与 Slot 的字段集合就是新契约' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $entry = $catalog['single'] (@($entry.PSObject.Properties.Name) -join ',') | Should -Be 'Name,Path,Description,Slots,Kind,Missing,Error,Raw' $entry.Name | Should -Be 'single' @@ -849,7 +849,7 @@ Describe '软件名录' { } It '一个软件多个 Slot:Kind=Multi,Slot 按名字排序' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $catalog['multi'].Kind | Should -Be 'Multi' @($catalog['multi'].Slots).Count | Should -Be 2 (@($catalog['multi'].Slots | ForEach-Object { $_.Name }) -join ',') | Should -Be 'Alpha,Zeta' @@ -858,7 +858,7 @@ Describe '软件名录' { } It 'Kind:Single / Multi / Partial / Unresolved / Invalid' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $catalog['single'].Kind | Should -Be 'Single' $catalog['multi'].Kind | Should -Be 'Multi' $catalog['partial'].Kind | Should -Be 'Partial' @@ -873,7 +873,7 @@ Describe '软件名录' { } It '前缀补全:<名>_<后缀> 与 <名>-<后缀> 都会被补全' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $catalog['legendary'].Slots[0].Resolved | Should -Be (Join-Path $script:CatRoot 'legendary_2.0.4') $catalog['legendary'].Slots[0].Suffixed | Should -BeTrue @@ -882,12 +882,12 @@ Describe '软件名录' { } It '不会把 Legendary 误配成 LegendarySomething' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $catalog['legendary'].Slots[0].Resolved | Should -Not -Be (Join-Path $script:CatRoot 'LegendarySomething') } It '一个 Slot 命中多个候选目录:报 Error,绝不悄悄挑一棵树' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $entry = $catalog['dupslot'] $entry.Error | Should -Match '只能对应一个目录' $entry.Error | Should -Match 'dup_1' @@ -897,14 +897,14 @@ Describe '软件名录' { } It '文件 Slot:Path 指向文件时 IsFile 为真,Encrypt 也带上' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $slot = $catalog['fileslot'].Slots[0] $slot.IsFile | Should -BeTrue $slot.Encrypt | Should -BeTrue } It '%变量% 会在名录路径里展开' { - $catalog = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache $catalog['envpath'].Slots[0].Resolved | Should -Be ([Environment]::ExpandEnvironmentVariables('%TEMP%')) } @@ -916,39 +916,39 @@ Describe '软件名录' { } It '读取结果按"路径 + 时间戳 + 长度 + 内容 MD5"缓存' { - $first = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 - $second = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 + $first = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 + $second = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 [object]::ReferenceEquals($first, $second) | Should -BeTrue - $fresh = Get-SoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache + $fresh = Get-BakNRetSoftwareCatalog -Path $script:CatFile -MaxDepth 3 -NoCache [object]::ReferenceEquals($first, $fresh) | Should -BeFalse } It '名录文件内容变了以后缓存自动失效' { $path = Write-ListFile -Path (Join-Path $script:CatRoot 'cache.psd1') -Content "@{ 'x' = @{ S = @{ Path = '$script:CatReal'; Description = 'one' } } }" - (Get-SoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'one' + (Get-BakNRetSoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'one' Write-ListFile -Path $path -Content "@{ 'x' = @{ S = @{ Path = '$script:CatReal'; Description = 'two' } } }" | Out-Null - (Get-SoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'two' + (Get-BakNRetSoftwareCatalog -Path $path -MaxDepth 3)['x'].Description | Should -Be 'two' } It 'Includes:分文件维护的名录会被合并' { Write-ListFile -Path (Join-Path $script:CatRoot 'Extra.psd1') -Content "@{ 'extra-app' = @{ S = @{ Path = '$script:CatReal' } } }" | Out-Null $main = Write-ListFile -Path (Join-Path $script:CatRoot 'Main.psd1') -Content "@{ Includes = @('Extra.psd1'); 'main-app' = @{ S = @{ Path = '$script:CatReal' } } }" - $catalog = Get-SoftwareCatalog -Path $main -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $main -MaxDepth 3 -NoCache $catalog.ContainsKey('main-app') | Should -BeTrue $catalog.ContainsKey('extra-app') | Should -BeTrue } It '旧的裸字符串 / 字符串数组 / 对象数组写法都会报 ERROR 并被跳过' { - $catalog = Get-SoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache $catalog.ContainsKey('bare') | Should -BeFalse $catalog.ContainsKey('arr') | Should -BeFalse $catalog.ContainsKey('objarr') | Should -BeFalse } It '旧的 @{ Dirs = @(...) } 写法不再展开:留下 Invalid 条目和原因' { - $catalog = Get-SoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $script:LegacyFile -MaxDepth 3 -NoCache $catalog.ContainsKey('dirstyle') | Should -BeTrue $catalog['dirstyle'].Kind | Should -Be 'Invalid' $catalog['dirstyle'].Error | Should -Not -BeNullOrEmpty @@ -956,14 +956,14 @@ Describe '软件名录' { } It '软件名会被规范化成合法文件名' { - $clean = Format-CatalogName -Name 'ac:d/e' + $clean = Format-BakNRetCatalogName -Name 'ac:d/e' ($clean.IndexOfAny([System.IO.Path]::GetInvalidFileNameChars())) | Should -Be -1 - (Format-CatalogName -Name ' My App ') | Should -Be 'My App' + (Format-BakNRetCatalogName -Name ' My App ') | Should -Be 'My App' } } # ============================================================================ -Describe 'Resolve-BackupEntry:条目解析' { +Describe 'Resolve-BakNRetBackupEntry:条目解析' { # ============================================================================ BeforeAll { @@ -976,7 +976,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It 'resolve 与归档项的字段集合就是新契约(旧字段已删除)' { $entry = ConvertFrom-BackupListLine -Line 'single' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 (@($resolved.PSObject.Properties.Name) -join ',') | Should -Be 'IsName,CatalogEntry,BaseName,ArchiveFlavor,Direction,Items,Encrypt,ExcludePatterns,HasExcludeOverride,Includes,HasIncludeOverride,Source,Error,Blocking' @($resolved.Items)[0].PSObject.Properties.Name | Should -Contain 'ArchivePath' @($resolved.Items)[0].PSObject.Properties.Name | Should -Contain 'Origin' @@ -985,7 +985,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '软件名条目:isName/CatalogEntry/BaseName/ArchiveFlavor/Source' { $entry = ConvertFrom-BackupListLine -Line 'single' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.IsName | Should -BeTrue $resolved.BaseName | Should -Be 'single' $resolved.ArchiveFlavor | Should -Be 'name' @@ -997,7 +997,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '软件名条目:Items 来自 Slot(ArchivePath=Slot 名,Kind=slot,Origin=catalog)' { $entry = ConvertFrom-BackupListLine -Line 'multi' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 @($resolved.Items).Count | Should -Be 2 (@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'Alpha,Zeta' (@($resolved.Items | ForEach-Object { $_.Kind }) -join ',') | Should -Be 'slot,slot' @@ -1009,7 +1009,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '字面路径条目:一个 path 项(ArchivePath 是末级名)' { $entry = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.IsName | Should -BeFalse $resolved.ArchiveFlavor | Should -Be 'path' @($resolved.Items).Count | Should -Be 1 @@ -1021,7 +1021,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '字面路径条目上的 @ Path= 覆盖目标路径,但归档名仍按原路径' { $entry = ConvertFrom-BackupListLine -Line "C:\Programs\Foo @ Path='D:\Elsewhere'" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.Items[0].RealPath | Should -Be 'D:\Elsewhere' $resolved.BaseName | Should -Be 'Foo_from_C_+Programs' } @@ -1029,7 +1029,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '名录里的路径不存在时仍给出 Items(恢复要靠它还原回原位)' { $missingCatalog = Write-ListFile -Path (Join-Path $script:Sandbox 'catalog\Missing.psd1') -Content "@{ 'gone' = @{ S = @{ Path = 'C:\definitely-not-here-98765' } } }" $entry = ConvertFrom-BackupListLine -Line 'gone' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $missingCatalog -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $missingCatalog -MaxDepth 3 @($resolved.Items).Count | Should -Be 1 $resolved.Items[0].Exists | Should -BeFalse $resolved.Items[0].RealPath | Should -Be 'C:\definitely-not-here-98765' @@ -1037,7 +1037,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It ':: 覆盖 Path:单 Slot 条目直接生效' { $entry = ConvertFrom-BackupListLine -Line "single :: $script:CatOther" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 @($resolved.Items).Count | Should -Be 1 $resolved.Items[0].ArchivePath | Should -Be 'Main' $resolved.Items[0].RealPath | Should -Be $script:CatOther @@ -1047,7 +1047,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It ':: / @ Path= 覆盖遇到多 Slot 条目 -> Blocking(不猜是哪一个)' { foreach ($line in @("multi :: $script:CatOther", "multi @ Path='$script:CatOther'")) { $entry = ConvertFrom-BackupListLine -Line $line - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 @($resolved.Items).Count | Should -Be 0 $resolved.Blocking | Should -Match '不能用一个' } @@ -1055,14 +1055,14 @@ Describe 'Resolve-BackupEntry:条目解析' { It '条目级 :- 覆盖名录里的排除:HasExcludeOverride 为真' { $entry = ConvertFrom-BackupListLine -Line 'single :- logs\,!*Cache' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.HasExcludeOverride | Should -BeTrue (@($resolved.ExcludePatterns) -join '|') | Should -Be 'logs\|!*Cache' } It '名录 Slot 的 Include 会追加成 include 项' { $entry = ConvertFrom-BackupListLine -Line 'incapp' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 @($resolved.Items).Count | Should -Be 2 $include = @($resolved.Items | Where-Object { $_.Origin -eq 'include' }) $include.Count | Should -Be 1 @@ -1074,7 +1074,7 @@ Describe 'Resolve-BackupEntry:条目解析' { It '条目级 :+ / @ Include= 覆盖名录里的 Include' { foreach ($line in @("incapp :+ Other:$script:CatOther", "incapp @ Include='Other:$script:CatOther'")) { $entry = ConvertFrom-BackupListLine -Line $line - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.HasIncludeOverride | Should -BeTrue (@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'A,Other' @($resolved.Items)[1].RealPath | Should -Be $script:CatOther @@ -1082,34 +1082,34 @@ Describe 'Resolve-BackupEntry:条目解析' { } It '加密:名录里各 Slot 取或;条目级 :encrypt / :!encrypt 覆盖' { - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp :!encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single :encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line "single @ Encrypt='true'") -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'encapp :!encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeFalse + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single :encrypt') -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line "single @ Encrypt='true'") -CatalogPath $script:CatFile -MaxDepth 3).Encrypt | Should -BeTrue } - It '方向标记会传递到 Resolve-BackupEntry.Direction' { - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'backup' - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '- single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'restore' - (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'both' + It '方向标记会传递到 Resolve-BakNRetBackupEntry.Direction' { + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'backup' + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '- single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'restore' + (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'single') -CatalogPath $script:CatFile -MaxDepth 3).Direction | Should -Be 'both' } It '归档内路径冲突(include 与 Slot 同名)-> Blocking' { $entry = ConvertFrom-BackupListLine -Line "conflict :+ Data:$script:CatOther" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.Blocking | Should -Match '归档内路径冲突' } It '归档内路径冲突(父子关系)-> Blocking' { $entry = ConvertFrom-BackupListLine -Line "conflict :+ Data\sub:$script:CatOther" - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.Blocking | Should -Match '父子关系' } It '名录里没有该名字:Error 给出,Items 为空' { $entry = ConvertFrom-BackupListLine -Line 'no-such-thing' - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $script:CatFile -MaxDepth 3 $resolved.Error | Should -Match 'no-such-thing' @($resolved.Items).Count | Should -Be 0 } @@ -1125,13 +1125,13 @@ Describe '外部命令退出码(旧实现的核心缺陷)' { $sentinel = 'SENTINEL-PW-9f3a' $logPath = $null try { - $logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret' - Set-BaknretDebug + $logPath = Start-BakNRetLog -Directory $sandbox -Prefix 'secret' + Set-BakNRetDebug $null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel") } finally { - Set-BaknretDebug -Enabled:$false - Stop-BaknretLog + Set-BakNRetDebug -Enabled:$false + Stop-BakNRetLog } $logText = Get-Content -Encoding UTF8 -LiteralPath $logPath -Raw @@ -1150,21 +1150,21 @@ Describe '外部命令退出码(旧实现的核心缺陷)' { $first = $null $second = $null try { - $first = Enter-BaknretRunLock -Directory $dir + $first = Enter-BakNRetRunLock -Directory $dir $first | Should -Not -BeNullOrEmpty - $second = Enter-BaknretRunLock -Directory $dir + $second = Enter-BakNRetRunLock -Directory $dir $second | Should -BeNullOrEmpty - Test-Path -LiteralPath (Get-BaknretRunLockPath -Directory $dir) | Should -BeTrue + Test-Path -LiteralPath (Get-BakNRetRunLockPath -Directory $dir) | Should -BeTrue } finally { - Exit-BaknretRunLock -Lock $second - Exit-BaknretRunLock -Lock $first + Exit-BakNRetRunLock -Lock $second + Exit-BakNRetRunLock -Lock $first } # 锁文件是独占打开的(FileShare.None),内容只能在释放之后读 - (Get-Content -Encoding UTF8 -LiteralPath (Get-BaknretRunLockPath -Directory $dir) -Raw) | + (Get-Content -Encoding UTF8 -LiteralPath (Get-BakNRetRunLockPath -Directory $dir) -Raw) | Should -Match "pid=$PID" Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue @@ -1173,13 +1173,13 @@ Describe '外部命令退出码(旧实现的核心缺陷)' { It '运行锁:释放之后可以重新取得' { $dir = Join-Path $env:TEMP ("bnr-lock-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) try { - $first = Enter-BaknretRunLock -Directory $dir + $first = Enter-BakNRetRunLock -Directory $dir $first | Should -Not -BeNullOrEmpty - Exit-BaknretRunLock -Lock $first + Exit-BakNRetRunLock -Lock $first - $second = Enter-BaknretRunLock -Directory $dir + $second = Enter-BakNRetRunLock -Directory $dir $second | Should -Not -BeNullOrEmpty - Exit-BaknretRunLock -Lock $second + Exit-BakNRetRunLock -Lock $second } finally { Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue @@ -1192,7 +1192,7 @@ Describe '外部命令退出码(旧实现的核心缺陷)' { $empty = Join-Path $env:TEMP ("bnr-empty-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) New-Item -ItemType Directory -Path $empty -Force | Out-Null try { - $summary = Get-FolderSummary -FolderPath $empty + $summary = Get-BakNRetFolderSummary -FolderPath $empty $summary.FileCount | Should -Be 0 $summary.TotalSize | Should -Not -BeNullOrEmpty $summary.TotalSize | Should -Be 0 @@ -1207,18 +1207,18 @@ Describe '外部命令退出码(旧实现的核心缺陷)' { New-Item -ItemType Directory -Path $base -Force | Out-Null $target = Join-Path $base 'text.json' try { - Write-BaknretAtomicText -Path $target -Text 'FIRST' | Out-Null + Write-BakNRetAtomicText -Path $target -Text 'FIRST' | Out-Null (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'FIRST' # 目标已存在时走 File.Replace - Write-BaknretAtomicText -Path $target -Text 'SECOND' | Out-Null + Write-BakNRetAtomicText -Path $target -Text 'SECOND' | Out-Null (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'SECOND' "$target.tmp" | Should -Not -Exist # 目标只读 -> 替换必然失败 -> 旧内容必须还在。这正是 File.Replace 与"先删后移" # 的区别:后者失败时旧文件已经没了,而 manifest 丢了只能靠文件名反推。 (Get-Item -LiteralPath $target).IsReadOnly = $true - { Write-BaknretAtomicText -Path $target -Text 'SHOULD-NOT-LAND' } | Should -Throw + { Write-BakNRetAtomicText -Path $target -Text 'SHOULD-NOT-LAND' } | Should -Throw (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) | Should -Be 'SECOND' } finally { @@ -1260,9 +1260,9 @@ Describe '集成:真实 7z 压缩与排除规则' -Skip:(-not $script:HasSeven It '排除规则与空格处理在真实归档上生效' { $patterns = @('!*Cache', 'component_crx_cache', 'Default\Code Cache', 'Default\Extensions', 'Default\IndexedDB') - $item = New-BaknretArchiveItem -ArchivePath $script:IntegrationItem -RealPath $script:IntegrationPath ` + $item = New-BakNRetArchiveItem -ArchivePath $script:IntegrationItem -RealPath $script:IntegrationPath ` -Kind 'path' -Origin 'path' -Exists $true -IsFile $false - $excludeArguments = @((Get-BaknretExcludeArgument -Item $item -Patterns $patterns).Arguments) + $excludeArguments = @((Get-BakNRetExcludeArgument -Item $item -Patterns $patterns).Arguments) $archive = Join-Path $script:IntegrationRoot 'excl.7z' $arguments = @('a', '-t7z', '-mx=1', '-bso0', '-bsp0') + $excludeArguments + @($archive, $script:IntegrationItem) @@ -1316,14 +1316,14 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - $script:E2EHashes = New-VerifiableSourceTree -Root $script:E2ESource Write-ListFile -Path $script:E2EList -Content "# e2e`n$script:E2ESource :- logs\,!*Cache`n" | Out-Null - $script:BackupRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $script:BackupRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir Force = $true QuietTool = $true } $script:E2EManifestPath = Join-Path $script:E2EBackupDir 'manifest.json' - $script:E2EManifest = Read-BaknretManifest -Path $script:E2EManifestPath + $script:E2EManifest = Read-BakNRetManifest -Path $script:E2EManifestPath $script:E2EArchive = @(Get-ChildItem -LiteralPath $script:E2EBackupDir -File -Filter *.7z)[0] } @@ -1352,7 +1352,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - } It '字面路径条目沿用 <末级名> 布局;归档里保留应保留内容、不含被排除项' { - (Get-BaknretArchiveTopName -ArchivePath 'My Code Space\keep.txt') | Should -Be 'My Code Space' + (Get-BakNRetArchiveTopName -ArchivePath 'My Code Space\keep.txt') | Should -Be 'My Code Space' $verify = Join-Path $script:E2ERoot 'verify' New-Item -ItemType Directory -Path $verify -Force | Out-Null @@ -1370,7 +1370,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - $record.roots | Should -Contain 'My Code Space' # 和归档里的真实内容对一次账,而不是只信 manifest 自己 - $topLevel = @(Get-ArchiveTopLevelNames -ArchivePath $script:E2EArchive.FullName -SevenZip $script:SevenZip) + $topLevel = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $script:E2EArchive.FullName -SevenZip $script:SevenZip) (@($topLevel | Sort-Object) -join ',') | Should -Be 'My Code Space' } @@ -1378,7 +1378,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - $before = (Get-FileHash -LiteralPath $script:E2EManifestPath -Algorithm SHA256).Hash Remove-Item -LiteralPath $script:E2ESource -Recurse -Force - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir DryRun = $true @@ -1390,7 +1390,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - } It 'Restore -WhatIf 同样不写盘' { - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir WhatIf = $true @@ -1402,7 +1402,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - It '真实恢复:退出码 0,文件逐字节一致,被排除项没有被恢复出来' { Test-Path -LiteralPath $script:E2ESource | Should -BeFalse # 保证不是空操作 - $run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir Force = $true @@ -1420,7 +1420,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - } It '真实恢复之后 manifest 才被更新(lastRestoreAt)' { - $manifest = Read-BaknretManifest -Path $script:E2EManifestPath + $manifest = Read-BakNRetManifest -Path $script:E2EManifestPath $record = $manifest.items[$script:E2EArchive.BaseName] $record.lastRestoreAt | Should -Not -BeNullOrEmpty } @@ -1428,7 +1428,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - It '孤儿归档会被点名报告,但不影响退出码' { Copy-Item -LiteralPath $script:E2EArchive.FullName -Destination (Join-Path $script:E2EBackupDir 'Orphaned-Archive.7z') -Force - $run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir QuietTool = $true @@ -1440,7 +1440,7 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - } It '带 -Only 时不做孤儿审计(避免把未选中的归档误报成孤儿)' { - $run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:E2EList BackupDir = $script:E2EBackupDir Only = @('My Code Space') @@ -1451,20 +1451,20 @@ Describe '集成:Backup.ps1 / Restore.ps1 端到端(字面路径条目)' - It '源路径不存在时记为 missing-source,退出码仍为 0(跳过不算失败)' { $missingList = Write-ListFile -Path (Join-Path $script:E2ERoot 'missing.txt') -Content "Z:\definitely-not-here-12345`n" - $run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $missingList BackupDir = $script:E2EBackupDir QuietTool = $true } $run.ExitCode | Should -Be 0 - $manifest = Read-BaknretManifest -Path $script:E2EManifestPath + $manifest = Read-BakNRetManifest -Path $script:E2EManifestPath $manifest.items['definitely-not-here-12345_from_Z_'].action | Should -Be 'missing-source' } It '归档名重复时直接报失败(退出码 1),不静默互相覆盖' { $duplicateList = Write-ListFile -Path (Join-Path $script:E2ERoot 'dup.txt') -Content "$script:E2ESource`n$script:E2ESource`n" - $run = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $run = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $duplicateList BackupDir = $script:E2EBackupDir Force = $true @@ -1493,7 +1493,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip # 正确方向:先正常备份出归档 Write-ListFile -Path $script:DirectionList -Content "$script:DirectionSource`n" | Out-Null - $null = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $null = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:DirectionList BackupDir = $script:DirectionBackupDir Force = $true @@ -1507,7 +1507,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip # 行首 -:仅恢复,备份端应当跳过它,但要把它算作"有主" Write-ListFile -Path $script:DirectionList -Content "- $script:DirectionSource`n" | Out-Null - $script:BackupDirectionRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{ + $script:BackupDirectionRun = Invoke-BakNRetScript -Script $script:BackupScript -Parameters @{ BackupListPath = $script:DirectionList BackupDir = $script:DirectionBackupDir QuietTool = $true @@ -1516,7 +1516,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip # 行首 +:仅备份,恢复端应当跳过它,同样要登记归档名 Remove-Item -LiteralPath $script:DirectionSource -Recurse -Force Write-ListFile -Path $script:DirectionList -Content "+ $script:DirectionSource`n" | Out-Null - $script:RestoreDirectionRun = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{ + $script:RestoreDirectionRun = Invoke-BakNRetScript -Script $script:RestoreScript -Parameters @{ BackupListPath = $script:DirectionList BackupDir = $script:DirectionBackupDir Force = $true diff --git a/tests/Restore-Drill.ps1 b/tests/Restore-Drill.ps1 index abc54ff..980e09d 100644 --- a/tests/Restore-Drill.ps1 +++ b/tests/Restore-Drill.ps1 @@ -82,8 +82,8 @@ if (-not (Test-Path -LiteralPath $restoreScript)) { exit 1 } -$config = Get-BaknretConfig -Path $ConfigPath -$catalogPath = Resolve-CatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot +$config = Get-BakNRetConfig -Path $ConfigPath +$catalogPath = Resolve-BakNRetCatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot if (-not $BackupDir) { $BackupDir = $config.BackupDir @@ -100,7 +100,7 @@ if (-not $WorkRoot) { } New-Item -ItemType Directory -Path $WorkRoot -Force | Out-Null -$manifest = Read-BaknretManifest -Path (Join-Path $BackupDir 'manifest.json') +$manifest = Read-BakNRetManifest -Path (Join-Path $BackupDir 'manifest.json') $archiveFiles = @(Get-ChildItem -LiteralPath $BackupDir -File -Force -ErrorAction SilentlyContinue | Where-Object { $_.Extension.ToLower() -in @('.7z', '.rar', '.zip', '.tar') }) @@ -322,7 +322,7 @@ foreach ($name in $Entries) { $entry = ConvertFrom-BackupListLine -Line $name if (-not $entry) { continue } - $resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth + $resolved = Resolve-BakNRetBackupEntry -Entry $entry -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth if (-not $resolved.BaseName) { $failures += "$name :解析不出归档名" diff --git a/tests/Run-E2E.ps1 b/tests/Run-E2E.ps1 index 794ba9d..dae0e17 100644 --- a/tests/Run-E2E.ps1 +++ b/tests/Run-E2E.ps1 @@ -167,7 +167,7 @@ $manifestPath = Join-Path $backupDir 'manifest.json' Test-Case 'manifest 记录了条目、动作、校验结果、roots 与 layouts' { Assert-FileExists $manifestPath - $manifest = Read-BaknretManifest -Path $manifestPath + $manifest = Read-BakNRetManifest -Path $manifestPath Assert-Equal 1 $manifest.items.Count $record = $manifest.items[$archives[0].BaseName] @@ -251,7 +251,7 @@ $cleanExitCode = $LASTEXITCODE $cleanArchive = Get-ChildItem -LiteralPath $lockBackupDir -File -Filter *.7z | Select-Object -First 1 $cleanSize = $cleanArchive.Length $lockManifestPath = Join-Path $lockBackupDir 'manifest.json' -$cleanRecord = (Read-BaknretManifest -Path $lockManifestPath).items[$cleanArchive.BaseName] +$cleanRecord = (Read-BakNRetManifest -Path $lockManifestPath).items[$cleanArchive.BaseName] Test-Case '干净归档:退出码 0,manifest 记 warnings=false' { Assert-Equal 0 $cleanExitCode @@ -269,7 +269,7 @@ try { & $backupScript -BackupListPath $lockList -BackupDir $lockBackupDir -ConfigPath $cfg2 -Force -QuietTool $warnExitCode = $LASTEXITCODE $afterArchive = Get-ChildItem -LiteralPath $lockBackupDir -File -Filter *.7z | Select-Object -First 1 - $afterRecord = (Read-BaknretManifest -Path $lockManifestPath).items[$afterArchive.BaseName] + $afterRecord = (Read-BakNRetManifest -Path $lockManifestPath).items[$afterArchive.BaseName] Test-Case '有警告时拒绝覆盖完整归档:退出码 1,旧归档原样保留' { Assert-Equal 1 $warnExitCode @@ -282,7 +282,7 @@ try { # 明确接受之后才允许覆盖 & $backupScript -BackupListPath $lockList -BackupDir $lockBackupDir -ConfigPath $cfg2 -Force -QuietTool -AcceptWarnings $acceptExitCode = $LASTEXITCODE - $acceptedRecord = (Read-BaknretManifest -Path $lockManifestPath).items[$afterArchive.BaseName] + $acceptedRecord = (Read-BakNRetManifest -Path $lockManifestPath).items[$afterArchive.BaseName] Test-Case '-AcceptWarnings 允许覆盖,并在 manifest 记 warnings=true' { Assert-Equal 0 $acceptExitCode @@ -357,7 +357,7 @@ Test-Case '名录条目:一个软件一个归档,归档名 = 软件名;独 } Test-Case 'manifest.roots 列出各归档项的顶层名,layouts 标出 dir / file' { - $record = (Read-BaknretManifest -Path (Join-Path $catBackupDir 'manifest.json')).items['my-app'] + $record = (Read-BakNRetManifest -Path (Join-Path $catBackupDir 'manifest.json')).items['my-app'] Assert-True ($null -ne $record) $roots = @($record.roots | Sort-Object) Assert-Equal 3 $roots.Count @@ -438,7 +438,7 @@ Test-Case '@pathname 覆盖:用名录里的真实路径跑命名算法(独 [System.IO.File]::WriteAllText($pathList, "my-app @pathname :+ Modules:$settingsFile`n", [System.Text.UTF8Encoding]::new($true)) # settings.json 刚才被删了,重建一份,让 Include 的宿主机路径存在 Set-Content -LiteralPath $settingsFile -Value '{"slot":"file"}' -Encoding UTF8 - $expectedBase = Get-BackupBaseName -RawPath $dirA + $expectedBase = Get-BakNRetBackupBaseName -RawPath $dirA & $backupScript -BackupListPath $pathList -BackupDir $pathNameDir -ConfigPath $catConfig -Force -QuietTool Assert-Equal 0 $LASTEXITCODE Assert-FileExists (Join-Path $pathNameDir ($expectedBase + '.7z')) @@ -578,7 +578,7 @@ if ($sevenZip) { [System.IO.File]::WriteAllText($legacyList, "legacy-file`n", [System.Text.UTF8Encoding]::new($true)) New-E2EConfig -Path $legacyConfig -LogDir $legacyLogDir -SoftwareCatalog $legacyCatalog -$legacyManifest = Read-BaknretManifest -Path (Join-Path $legacyBackupDir 'manifest.json') +$legacyManifest = Read-BakNRetManifest -Path (Join-Path $legacyBackupDir 'manifest.json') $legacyManifest.items['legacy-file'] = [ordered]@{ baseName = 'legacy-file' source = 'legacy-file' @@ -586,7 +586,7 @@ $legacyManifest.items['legacy-file'] = [ordered]@{ action = 'backed-up' encrypted = $false } -Write-BaknretManifest -Path (Join-Path $legacyBackupDir 'manifest.json') -Manifest $legacyManifest | Out-Null +Write-BakNRetManifest -Path (Join-Path $legacyBackupDir 'manifest.json') -Manifest $legacyManifest | Out-Null # 让"恢复确实做了事"可验证:把活文件改成别的内容,恢复后应回到归档里的内容 Set-Content -LiteralPath $legacyLive -Value '{"version":"changed-after-backup"}' -Encoding UTF8 @@ -646,7 +646,7 @@ Test-Case '字面路径不受名录影响,仍走路径命名' { [System.IO.File]::WriteAllText($literalList, "$dirA2`n", [System.Text.UTF8Encoding]::new($true)) & $backupScript -BackupListPath $literalList -BackupDir $literalDir -ConfigPath $catConfig -Force -QuietTool Assert-Equal 0 $LASTEXITCODE - Assert-FileExists (Join-Path $literalDir ((Get-BackupBaseName -RawPath $dirA2) + '.7z')) + Assert-FileExists (Join-Path $literalDir ((Get-BakNRetBackupBaseName -RawPath $dirA2) + '.7z')) } $missingList = Join-Path $WorkRoot 'missing.txt' @@ -656,7 +656,7 @@ $missingExitCode = $LASTEXITCODE Test-Case '源路径不存在被记为 missing-source,退出码仍为 0(跳过不算失败)' { Assert-Equal 0 $missingExitCode - $after = Read-BaknretManifest -Path $manifestPath + $after = Read-BakNRetManifest -Path $manifestPath $record = $after.items['definitely-not-here-12345_from_Z_'] Assert-True ($null -ne $record) 'manifest 里应有该条目的记录' Assert-Equal 'missing-source' $record.action diff --git a/tests/Run-RealSmoke.ps1 b/tests/Run-RealSmoke.ps1 index 7ff0d5c..1556c9c 100644 --- a/tests/Run-RealSmoke.ps1 +++ b/tests/Run-RealSmoke.ps1 @@ -58,13 +58,13 @@ if (-not (Test-Path -LiteralPath $manifestPath)) { exit 0 } -$config = Get-BaknretConfig -Path $configPath -$catalogPath = Resolve-CatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot +$config = Get-BakNRetConfig -Path $configPath +$catalogPath = Resolve-BakNRetCatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot $listLines = @(Get-Content -Encoding UTF8 -LiteralPath $listPath) # 用 cmd 批次文件把子进程输出落盘再读回。为什么不用 `& pwsh ... 2>&1` 直接捕获: # 那会给子进程建管道,在受限环境 / 沙箱里会被直接拒绝(见 tests\BakNRet.Tests.ps1 里的同款注释)。 -function Invoke-BaknretCaptured { +function Invoke-BakNRetCaptured { param([string]$Script, [string[]]$Arguments) $hostExe = if ($PSVersionTable.PSEdition -eq 'Core') { 'pwsh' } else { 'powershell' } @@ -130,7 +130,7 @@ Test-Case '真实清单里每个条目都能解析出方向,且软件名都能 if ($item.Direction -eq 'backup') { $backupOnly++ } if ($item.Direction -eq 'restore') { $restoreOnly++ } - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth if ($resolved.Error -and $resolved.Error -like '*名录里没有*') { $catalogMiss += $resolved.Error } } @@ -144,7 +144,7 @@ Write-Host "`n== 只读模式不写一个字节 ==" -ForegroundColor Cyan Test-Case 'Backup.ps1 -DryRun 退出 0、不再有"名录里没有"告警、manifest 不变' { $before = Get-ManifestHash $arguments = @('-BackupListPath', $listPath, '-DryRun', '-QuietTool', '-Only') + $SampleEntries - $run = Invoke-BaknretCaptured -Script $backupScript -Arguments $arguments + $run = Invoke-BakNRetCaptured -Script $backupScript -Arguments $arguments Assert-Equal 0 $run.ExitCode ("DryRun 退出码不是 0;输出尾部:" + ($run.Output -split "`n" | Select-Object -Last 4 | Out-String)) Assert-False ($run.Output -match "名录里没有 '") "DryRun 仍然把某些条目当成查不到的软件名" @@ -153,7 +153,7 @@ Test-Case 'Backup.ps1 -DryRun 退出 0、不再有"名录里没有"告警、mani Test-Case 'Restore.ps1 -VerifyOnly 退出 0、把行首 + 识别成"仅备份"、manifest 不变' { $before = Get-ManifestHash - $run = Invoke-BaknretCaptured -Script $restoreScript -Arguments @( + $run = Invoke-BakNRetCaptured -Script $restoreScript -Arguments @( '-BackupListPath', $listPath, '-VerifyOnly', '-Only', $RestoreSample, '-Verbose') Assert-Equal 0 $run.ExitCode ("VerifyOnly 退出码不是 0;输出尾部:" + ($run.Output -split "`n" | Select-Object -Last 4 | Out-String)) diff --git a/tests/Run-Tests.ps1 b/tests/Run-Tests.ps1 index 40339ed..932c750 100644 --- a/tests/Run-Tests.ps1 +++ b/tests/Run-Tests.ps1 @@ -13,7 +13,7 @@ * 软件名录新结构(`软件名 -> Slot -> @{ Path = ... }`):Slot 排序、前缀补全、 多候选报错、`%VAR%` / `$( ... )` 展开、缓存与 -NoCache * 归档命名与逆向解析(软件名 / 字面路径 / @pathname) - * 归档项与归档内布局(New-BaknretArchiveItem / 暂存 junction / 硬链接) + * 归档项与归档内布局(New-BakNRetArchiveItem / 暂存 junction / 硬链接) * 7z 排除参数翻译(!前缀、!re: 正则展开、Slot 前缀分配、去重) * Windows 命令行拼接 * manifest 读写往返与 layouts @@ -271,18 +271,18 @@ Test-Case '标记必须是独立记号:C:\a:-b 仍然是一个路径' { $r = ConvertFrom-BackupListLine -Line 'C:\a:-b' Assert-Equal 'C:\a:-b' $r.Path Assert-Equal 0 $r.Overrides.Keys.Count - Assert-Null (Test-BaknretMarker -Token 'C:\a:-b') - Assert-Null (Test-BaknretMarker -Token ':+X') + Assert-Null (Test-BakNRetMarker -Token 'C:\a:-b') + Assert-Null (Test-BakNRetMarker -Token ':+X') } -Test-Case 'Test-BaknretMarker 识别全部标记(大小写敏感)' { - Assert-Equal 'path' (Test-BaknretMarker -Token '::') - Assert-Equal 'exclude' (Test-BaknretMarker -Token ':-') - Assert-Equal 'include' (Test-BaknretMarker -Token ':+') - Assert-Equal 'encrypt' (Test-BaknretMarker -Token ':encrypt') - Assert-Equal 'noencrypt' (Test-BaknretMarker -Token ':!encrypt') - Assert-Equal 'at' (Test-BaknretMarker -Token '@x') - Assert-Equal 'path' (Test-BaknretMarker -Token ' :: ') +Test-Case 'Test-BakNRetMarker 识别全部标记(大小写敏感)' { + Assert-Equal 'path' (Test-BakNRetMarker -Token '::') + Assert-Equal 'exclude' (Test-BakNRetMarker -Token ':-') + Assert-Equal 'include' (Test-BakNRetMarker -Token ':+') + Assert-Equal 'encrypt' (Test-BakNRetMarker -Token ':encrypt') + Assert-Equal 'noencrypt' (Test-BakNRetMarker -Token ':!encrypt') + Assert-Equal 'at' (Test-BakNRetMarker -Token '@x') + Assert-Equal 'path' (Test-BakNRetMarker -Token ' :: ') } Test-Case '记号里有空格时目标可以带空格(第一个修饰符之前全是目标)' { @@ -308,8 +308,8 @@ Test-Case '没有说明时 Comment 为 $null' { Assert-Null (ConvertFrom-BackupListLine -Line 'App').Comment } -Test-Case 'Split-BaknretToken:引号内的空白不切分,引号保留' { - $tokens = @(Split-BaknretToken -Text 'a "b c" d :- ''e,f''') +Test-Case 'Split-BakNRetToken:引号内的空白不切分,引号保留' { + $tokens = @(Split-BakNRetToken -Text 'a "b c" d :- ''e,f''') Assert-Equal 5 $tokens.Count Assert-Equal 'a' $tokens[0] Assert-Equal '"b c"' $tokens[1] @@ -318,19 +318,19 @@ Test-Case 'Split-BaknretToken:引号内的空白不切分,引号保留' { Assert-Equal "'e,f'" $tokens[4] } -Test-Case 'Remove-BaknretQuote:成对引号去掉,不成对原样返回' { - Assert-Equal 'a' (Remove-BaknretQuote -Text "'a'") - Assert-Equal 'a' (Remove-BaknretQuote -Text '"a"') - Assert-Equal '"a' (Remove-BaknretQuote -Text '"a') - Assert-Equal 'plain' (Remove-BaknretQuote -Text 'plain') +Test-Case 'Remove-BakNRetQuote:成对引号去掉,不成对原样返回' { + Assert-Equal 'a' (Remove-BakNRetQuote -Text "'a'") + Assert-Equal 'a' (Remove-BakNRetQuote -Text '"a"') + Assert-Equal '"a' (Remove-BakNRetQuote -Text '"a') + Assert-Equal 'plain' (Remove-BakNRetQuote -Text 'plain') } -Test-Case 'Test-LiteralPath:带分隔符或 % 的按路径,其余按软件名' { - Assert-True (Test-LiteralPath -Path 'C:\a') - Assert-True (Test-LiteralPath -Path 'C:/a') - Assert-True (Test-LiteralPath -Path '%UserProfile%\.ssh') - Assert-False (Test-LiteralPath -Path 'MyApp') - Assert-True (Test-LiteralPath -Path '') +Test-Case 'Test-BakNRetLiteralPath:带分隔符或 % 的按路径,其余按软件名' { + Assert-True (Test-BakNRetLiteralPath -Path 'C:\a') + Assert-True (Test-BakNRetLiteralPath -Path 'C:/a') + Assert-True (Test-BakNRetLiteralPath -Path '%UserProfile%\.ssh') + Assert-False (Test-BakNRetLiteralPath -Path 'MyApp') + Assert-True (Test-BakNRetLiteralPath -Path '') } # ============================================================================ @@ -338,19 +338,19 @@ Write-Host "`n== 归档命名 ==" -ForegroundColor Cyan # ============================================================================ Test-Case '基础命名规则' { - Assert-Equal 'legendary_from_%UserProfile%+.config' (Get-BackupBaseName -RawPath '%UserProfile%/.config/legendary') - Assert-Equal 'FooClolor_from_C_+Programs' (Get-BackupBaseName -RawPath 'C:\Programs\FooClolor') - Assert-Equal 'settings.json_from_C_+Programs' (Get-BackupBaseName -RawPath 'C:\Programs\settings.json') + Assert-Equal 'legendary_from_%UserProfile%+.config' (Get-BakNRetBackupBaseName -RawPath '%UserProfile%/.config/legendary') + Assert-Equal 'FooClolor_from_C_+Programs' (Get-BakNRetBackupBaseName -RawPath 'C:\Programs\FooClolor') + Assert-Equal 'settings.json_from_C_+Programs' (Get-BakNRetBackupBaseName -RawPath 'C:\Programs\settings.json') } Test-Case '盘符冒号被归一化成下划线' { - Assert-Equal 'a_from_C_' (Get-BackupBaseName -RawPath 'C:\a') + Assert-Equal 'a_from_C_' (Get-BakNRetBackupBaseName -RawPath 'C:\a') } Test-Case '/ 与 \ 以及重复分隔符结果一致' { - $a = Get-BackupBaseName -RawPath '%UserProfile%/.config/scoop' - $b = Get-BackupBaseName -RawPath '%UserProfile%\.config\scoop' - $c = Get-BackupBaseName -RawPath '%UserProfile%\\Documents\\WindowsPowerShell' + $a = Get-BakNRetBackupBaseName -RawPath '%UserProfile%/.config/scoop' + $b = Get-BakNRetBackupBaseName -RawPath '%UserProfile%\.config\scoop' + $c = Get-BakNRetBackupBaseName -RawPath '%UserProfile%\\Documents\\WindowsPowerShell' Assert-Equal $a $b Assert-Equal 'WindowsPowerShell_from_%UserProfile%+Documents' $c } @@ -362,26 +362,26 @@ Test-Case '命名 <-> 路径往返' { '%LocalAppData%\Microsoft\Edge\User Data' ) foreach ($case in $cases) { - $base = Get-BackupBaseName -RawPath $case - $back = Convert-BackupFileNameToPath -FileName "$base.7z" + $base = Get-BakNRetBackupBaseName -RawPath $case + $back = Convert-BakNRetBackupFileNameToPath -FileName "$base.7z" Assert-Equal ($case -replace '[/\\]+', '\') $back "往返 $case" } } Test-Case '归档名里不含非法文件名字符(保留 & % +)' { - $base = Get-BackupBaseName -RawPath 'C:\a\b?c*d|e' + $base = Get-BakNRetBackupBaseName -RawPath 'C:\a\b?c*d|e' $invalid = [System.IO.Path]::GetInvalidFileNameChars() | Where-Object { $_ -notin @('&', '%', '+') } foreach ($ch in $invalid) { Assert-False ($base.Contains([string]$ch)) "归档名里出现了非法字符 $ch" } - Assert-Equal 'keep%and&and+plus' (Format-CatalogName -Name 'keep%and&and+plus') + Assert-Equal 'keep%and&and+plus' (Format-BakNRetCatalogName -Name 'keep%and&and+plus') } -Test-Case 'Get-BaknretArchiveTopName:取归档内相对路径的第一段' { - Assert-Equal 'Slot' (Get-BaknretArchiveTopName -ArchivePath 'Slot\a\b') - Assert-Equal 'Slot' (Get-BaknretArchiveTopName -ArchivePath '\Slot\') - Assert-Equal 'leaf' (Get-BaknretArchiveTopName -ArchivePath 'leaf') - Assert-Equal '' (Get-BaknretArchiveTopName -ArchivePath ' ') +Test-Case 'Get-BakNRetArchiveTopName:取归档内相对路径的第一段' { + Assert-Equal 'Slot' (Get-BakNRetArchiveTopName -ArchivePath 'Slot\a\b') + Assert-Equal 'Slot' (Get-BakNRetArchiveTopName -ArchivePath '\Slot\') + Assert-Equal 'leaf' (Get-BakNRetArchiveTopName -ArchivePath 'leaf') + Assert-Equal '' (Get-BakNRetArchiveTopName -ArchivePath ' ') } # ============================================================================ @@ -390,8 +390,8 @@ Write-Host "`n== 归档项构造与暂存 ==" -ForegroundColor Cyan $itemSandbox = Join-Path $env:TEMP ("baknret-item-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) -Test-Case 'New-BaknretArchiveItem:归档内路径被清理,TopName 是第一段' { - $item = New-BaknretArchiveItem -ArchivePath '\AlphaData\' -RealPath 'C:\x' +Test-Case 'New-BakNRetArchiveItem:归档内路径被清理,TopName 是第一段' { + $item = New-BakNRetArchiveItem -ArchivePath '\AlphaData\' -RealPath 'C:\x' Assert-Equal 'AlphaData' $item.ArchivePath Assert-Equal 'AlphaData' $item.TopName Assert-Equal 'slot' $item.Kind @@ -400,13 +400,13 @@ Test-Case 'New-BaknretArchiveItem:归档内路径被清理,TopName 是第一 Assert-False $item.IsFile Assert-Equal 0 $item.Exclude.Count - $nested = New-BaknretArchiveItem -ArchivePath 'Alpha\sub' -RealPath 'C:\y' -Kind 'include' -Origin 'include' + $nested = New-BakNRetArchiveItem -ArchivePath 'Alpha\sub' -RealPath 'C:\y' -Kind 'include' -Origin 'include' Assert-Equal 'Alpha' $nested.TopName Assert-Equal 'include' $nested.Kind } Test-Case '归档项的属性集与契约一致(没有旧的 Sources / Dirs / Variants)' { - $item = New-BaknretArchiveItem -ArchivePath 'A' -RealPath 'C:\a' + $item = New-BakNRetArchiveItem -ArchivePath 'A' -RealPath 'C:\a' foreach ($expected in 'ArchivePath', 'TopName', 'RealPath', 'Kind', 'Slot', 'Description', 'Origin', 'Exists', 'IsFile', 'Exclude') { Assert-True ($item.PSObject.Properties.Name -contains $expected) "缺少属性 $expected" } @@ -428,13 +428,13 @@ Test-Case '暂存半途失败会自己清干净(不留指向真实数据的 ju $items = @( # 第一项会成功挂上,而且是**目录走 junction** —— 这正是危险的那个物件:它指向 # 真实数据,而临时目录迟早会被某次 Remove-Item -Recurse 扫到。 - (New-BaknretArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false) + (New-BakNRetArchiveItem -ArchivePath 'Good' -RealPath $realDir -Kind 'slot' -Slot 'Good' -Exists $true -IsFile $false) # 第二项必然失败:源文件不存在,硬链接与复制都会失败 - (New-BaknretArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true) + (New-BakNRetArchiveItem -ArchivePath 'Missing.txt' -RealPath (Join-Path $base 'does-not-exist.txt') -Kind 'slot' -Slot 'Missing' -Exists $false -IsFile $true) ) $threw = $false - try { New-BaknretArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') | Out-Null } catch { $threw = $true } + try { New-BakNRetArchiveStaging -Items $items -Root (Join-Path $sandbox 'stage') | Out-Null } catch { $threw = $true } Assert-True $threw '第二项应当抛错(源文件不存在,硬链接与复制都会失败)' $left = @(Get-ChildItem -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue) @@ -445,18 +445,18 @@ Test-Case '暂存半途失败会自己清干净(不留指向真实数据的 ju Remove-Item -LiteralPath $base -Recurse -Force -ErrorAction SilentlyContinue } -Test-Case 'New-BaknretArchiveStaging:目录走 junction、文件走硬链接,按归档内名字挂载' { +Test-Case 'New-BakNRetArchiveStaging:目录走 junction、文件走硬链接,按归档内名字挂载' { $root = Join-Path $itemSandbox 'stage-src' New-Item -ItemType Directory -Path (Join-Path $root 'App') -Force | Out-Null Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'App\f.txt') -Value 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'settings.json') -Value '{}' $items = @( - (New-BaknretArchiveItem -ArchivePath 'AlphaData' -RealPath (Join-Path $root 'App') -Exists $true), - (New-BaknretArchiveItem -ArchivePath 'BetaFile' -RealPath (Join-Path $root 'settings.json') -Exists $true -IsFile $true) + (New-BakNRetArchiveItem -ArchivePath 'AlphaData' -RealPath (Join-Path $root 'App') -Exists $true), + (New-BakNRetArchiveItem -ArchivePath 'BetaFile' -RealPath (Join-Path $root 'settings.json') -Exists $true -IsFile $true) ) - $staging = New-BaknretArchiveStaging -Items $items -Root (Join-Path $itemSandbox 'stage') + $staging = New-BakNRetArchiveStaging -Items $items -Root (Join-Path $itemSandbox 'stage') try { $dirLink = Get-Item -LiteralPath (Join-Path $staging 'AlphaData') -Force $fileLink = Get-Item -LiteralPath (Join-Path $staging 'BetaFile') -Force @@ -465,44 +465,44 @@ Test-Case 'New-BaknretArchiveStaging:目录走 junction、文件走硬链接 Assert-True (Test-Path -LiteralPath (Join-Path $staging 'AlphaData\f.txt')) '应能穿过 junction 看到内容' } finally { - Remove-BaknretArchiveStaging -Root $staging + Remove-BakNRetArchiveStaging -Root $staging } Assert-False (Test-Path -LiteralPath $staging) '暂存目录应被清理' Assert-True (Test-Path -LiteralPath (Join-Path $root 'App\f.txt')) '清理暂存绝不能删到真实数据' } -Test-Case 'New-BaknretJunction / Remove-BaknretJunction:只删连接点,不动目标' { +Test-Case 'New-BakNRetJunction / Remove-BakNRetJunction:只删连接点,不动目标' { $target = Join-Path $itemSandbox 'j-target' $link = Join-Path $itemSandbox 'j-link' New-Item -ItemType Directory -Path $target -Force | Out-Null Set-Content -Encoding UTF8 -LiteralPath (Join-Path $target 'keep.txt') -Value 'keep' - New-BaknretJunction -Path $link -Target $target | Out-Null + New-BakNRetJunction -Path $link -Target $target | Out-Null Assert-Equal 'Junction' (Get-Item -LiteralPath $link -Force).LinkType - Remove-BaknretJunction -Path $link + Remove-BakNRetJunction -Path $link Assert-False (Test-Path -LiteralPath $link) Assert-True (Test-Path -LiteralPath (Join-Path $target 'keep.txt')) '目标内容必须留着' try { - New-BaknretJunction -Path $link -Target $target | Out-Null - Remove-BaknretJunction -Path $link - New-BaknretJunction -Path $link -Target $target | Out-Null + New-BakNRetJunction -Path $link -Target $target | Out-Null + Remove-BakNRetJunction -Path $link + New-BakNRetJunction -Path $link -Target $target | Out-Null Assert-True $true } finally { - Remove-BaknretJunction -Path $link + Remove-BakNRetJunction -Path $link } } -Test-Case 'New-BaknretJunction:目标已存在时明确抛错' { +Test-Case 'New-BakNRetJunction:目标已存在时明确抛错' { $target = Join-Path $itemSandbox 'j2-target' $link = Join-Path $itemSandbox 'j2-link' New-Item -ItemType Directory -Path $target -Force | Out-Null New-Item -ItemType Directory -Path $link -Force | Out-Null $threw = $false - try { New-BaknretJunction -Path $link -Target $target | Out-Null } catch { $threw = $true } + try { New-BakNRetJunction -Path $link -Target $target | Out-Null } catch { $threw = $true } Assert-True $threw '同名实体存在时必须抛错,不能静默替换' } @@ -515,35 +515,35 @@ Test-Case '旧的 Get-ArchiveExcludeArgument 已经不存在(契约破坏点 } Test-Case '相对模式自动补上归档项名(锚定在归档根)' { - $item = New-BaknretArchiveItem -ArchivePath 'March7thAssistant' -RealPath 'C:\x' - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('logs\') + $item = New-BakNRetArchiveItem -ArchivePath 'March7thAssistant' -RealPath 'C:\x' + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('logs\') Assert-Equal 1 $exclude.Arguments.Count Assert-Equal '-x!March7thAssistant\logs' $exclude.Arguments[0] Assert-Null $exclude.Error } Test-Case '模式里再写一遍归档项名不会被当成前缀剥掉(新语义:总是拼在项名之后)' { - $item = New-BaknretArchiveItem -ArchivePath 'March7thAssistant' -RealPath 'C:\x' - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('March7thAssistant\logs\') + $item = New-BakNRetArchiveItem -ArchivePath 'March7thAssistant' -RealPath 'C:\x' + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('March7thAssistant\logs\') Assert-Equal '-x!March7thAssistant\March7thAssistant\logs' $exclude.Arguments[0] } Test-Case '! 前缀翻译成任意层级匹配' { - $item = New-BaknretArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('!*Cache') + $item = New-BakNRetArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('!*Cache') Assert-Equal '-xr!*Cache' $exclude.Arguments[0] } Test-Case '模式里的空格转成 ? (7z 的模式不支持空格)' { - $item = New-BaknretArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('Default\Code Cache') + $item = New-BakNRetArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('Default\Code Cache') Assert-Equal '-x!UserData\Default\Code?Cache' $exclude.Arguments[0] } Test-Case '生成的参数里绝不出现引号(旧实现 -x!"路径" 让排除全部失效)' { - $item = New-BaknretArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' + $item = New-BakNRetArchiveItem -ArchivePath 'UserData' -RealPath 'C:\x' $patterns = @('Default\Code Cache', '!*Cache', 'logs\', 'UserData\3rdparty\WebBrowser\UserProfile\Integrated') - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns $patterns + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns $patterns Assert-Equal 4 $exclude.Arguments.Count foreach ($argument in $exclude.Arguments) { Assert-False ($argument.Contains('"')) "参数里出现了引号: $argument" @@ -551,8 +551,8 @@ Test-Case '生成的参数里绝不出现引号(旧实现 -x!"路径" 让排 } Test-Case '空模式被忽略' { - $item = New-BaknretArchiveItem -ArchivePath 'x' -RealPath 'C:\x' - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('', ' ', 'real\') + $item = New-BakNRetArchiveItem -ArchivePath 'x' -RealPath 'C:\x' + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('', ' ', 'real\') Assert-Equal 1 $exclude.Arguments.Count Assert-Equal '-x!x\real' $exclude.Arguments[0] } @@ -562,9 +562,9 @@ Test-Case '!re:<正则> 展开成源目录里命中的精确路径' { foreach ($dir in 'Cache1', 'Cache2', 'Keep', 'sub\Cache3') { New-Item -ItemType Directory -Path (Join-Path $root $dir) -Force | Out-Null } - $item = New-BaknretArchiveItem -ArchivePath 'Data' -RealPath $root + $item = New-BakNRetArchiveItem -ArchivePath 'Data' -RealPath $root - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:^Cache\d+$') + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:^Cache\d+$') Assert-Null $exclude.Error $sorted = @($exclude.Arguments | Sort-Object) Assert-Equal 3 $sorted.Count @@ -575,34 +575,34 @@ Test-Case '!re:<正则> 展开成源目录里命中的精确路径' { Test-Case '非法的 !re: 正则给出明确的 Error,而不是静默漏排除' { $root = Join-Path $itemSandbox 'regex-src' - $item = New-BaknretArchiveItem -ArchivePath 'Data' -RealPath $root - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:*Cache') + $item = New-BakNRetArchiveItem -ArchivePath 'Data' -RealPath $root + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:*Cache') Assert-True ($null -ne $exclude.Error) Assert-Equal 0 $exclude.Arguments.Count } Test-Case '空的 !re: 被忽略' { $root = Join-Path $itemSandbox 'regex-src' - $item = New-BaknretArchiveItem -ArchivePath 'Data' -RealPath $root - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('!re:') + $item = New-BakNRetArchiveItem -ArchivePath 'Data' -RealPath $root + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('!re:') Assert-Equal 0 $exclude.Arguments.Count Assert-Null $exclude.Error } Test-Case '排除参数超过命令行安全长度时明确报错' { $root = Join-Path $itemSandbox 'regex-src' - $item = New-BaknretArchiveItem -ArchivePath 'Data' -RealPath $root - $exclude = Get-BaknretExcludeArgument -Item $item -Patterns @('aaaa\bbbb') -MaxCommandLineChars 5 + $item = New-BakNRetArchiveItem -ArchivePath 'Data' -RealPath $root + $exclude = Get-BakNRetExcludeArgument -Item $item -Patterns @('aaaa\bbbb') -MaxCommandLineChars 5 Assert-True ($null -ne $exclude.Error) } -Test-Case 'Split-BaknretPatternScope:`<项名>\` 前缀把模式分配给对应归档项' { +Test-Case 'Split-BakNRetPatternScope:`<项名>\` 前缀把模式分配给对应归档项' { $items = @( - (New-BaknretArchiveItem -ArchivePath 'DefaultConfig' -RealPath 'C:\c'), - (New-BaknretArchiveItem -ArchivePath 'GlobalPersist' -RealPath 'C:\g'), - (New-BaknretArchiveItem -ArchivePath 'UserPersist' -RealPath 'C:\u') + (New-BakNRetArchiveItem -ArchivePath 'DefaultConfig' -RealPath 'C:\c'), + (New-BakNRetArchiveItem -ArchivePath 'GlobalPersist' -RealPath 'C:\g'), + (New-BakNRetArchiveItem -ArchivePath 'UserPersist' -RealPath 'C:\u') ) - $map = Split-BaknretPatternScope -Items $items -Patterns @('GlobalPersist\steam\steamapps', 'plain\one', '!*Cache') + $map = Split-BakNRetPatternScope -Items $items -Patterns @('GlobalPersist\steam\steamapps', 'plain\one', '!*Cache') Assert-Equal 'steam\steamapps' $map[1][0] Assert-Equal 'plain\one' $map[0][0] '没点名任何项的模式对每一项各展开一份' @@ -612,8 +612,8 @@ Test-Case 'Split-BaknretPatternScope:`<项名>\` 前缀把模式分配给对 } } -Test-Case 'Merge-BaknretExcludeArgument:去重且保序' { - $merged = Merge-BaknretExcludeArgument -ArgumentLists @(@('-x!a', '-xr!c'), @('-x!a', '-x!b')) +Test-Case 'Merge-BakNRetExcludeArgument:去重且保序' { + $merged = Merge-BakNRetExcludeArgument -ArgumentLists @(@('-x!a', '-xr!c'), @('-x!a', '-x!b')) Assert-Equal 3 $merged.Count Assert-Equal '-x!a' $merged[0] Assert-Equal '-xr!c' $merged[1] @@ -625,23 +625,23 @@ Write-Host "`n== 命令行拼接 ==" -ForegroundColor Cyan # ============================================================================ Test-Case '无空格参数原样输出' { - Assert-Equal 'a -t7z' (ConvertTo-NativeArgumentString -ArgumentList @('a', '-t7z')) + Assert-Equal 'a -t7z' (ConvertTo-BakNRetNativeArgumentString -ArgumentList @('a', '-t7z')) } Test-Case '含空格参数加引号' { - Assert-Equal '"C:\Program Files\x"' (ConvertTo-NativeArgumentString -ArgumentList @('C:\Program Files\x')) + Assert-Equal '"C:\Program Files\x"' (ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\Program Files\x')) } Test-Case '引号内的结尾反斜杠翻倍(否则会被当成转义引号)' { - Assert-Equal '"C:\Program Files\\"' (ConvertTo-NativeArgumentString -ArgumentList @('C:\Program Files\')) + Assert-Equal '"C:\Program Files\\"' (ConvertTo-BakNRetNativeArgumentString -ArgumentList @('C:\Program Files\')) } Test-Case '内部引号被转义' { - Assert-Equal '"say \"hi\""' (ConvertTo-NativeArgumentString -ArgumentList @('say "hi"')) + Assert-Equal '"say \"hi\""' (ConvertTo-BakNRetNativeArgumentString -ArgumentList @('say "hi"')) } Test-Case '空参数输出一对空引号' { - Assert-Equal '""' (ConvertTo-NativeArgumentString -ArgumentList @('')) + Assert-Equal '""' (ConvertTo-BakNRetNativeArgumentString -ArgumentList @('')) } # ============================================================================ @@ -654,7 +654,7 @@ Test-Case 'manifest 读写往返(含 layouts)' { New-Item -ItemType Directory -Path $sandbox -Force | Out-Null $path = Join-Path $sandbox 'manifest.json' - $m = Read-BaknretManifest -Path $path + $m = Read-BakNRetManifest -Path $path Assert-Equal 0 $m.items.Count $m.compressor = [pscustomobject]@{ name = '7z'; version = '26.03' } @@ -667,9 +667,9 @@ Test-Case 'manifest 读写往返(含 layouts)' { [ordered]@{ name = 'BetaFile'; kind = 'file' } ) } - Write-BaknretManifest -Path $path -Manifest $m | Out-Null + Write-BakNRetManifest -Path $path -Manifest $m | Out-Null - $again = Read-BaknretManifest -Path $path + $again = Read-BakNRetManifest -Path $path Assert-Equal 1 $again.items.Count Assert-Equal 'backed-up' $again.items['my-app'].action Assert-Equal 2 $again.items['my-app'].layouts.Count @@ -683,12 +683,12 @@ Test-Case 'manifest 读写往返(含 layouts)' { Test-Case 'manifest 损坏时不抛异常' { $path = Join-Path $sandbox 'broken.json' [System.IO.File]::WriteAllText($path, '{ this is not json') - $m = Read-BaknretManifest -Path $path + $m = Read-BakNRetManifest -Path $path Assert-Equal 0 $m.items.Count } Test-Case '配置缺失时返回默认值' { - $config = Get-BaknretConfig -Path (Join-Path $sandbox 'nope.psd1') + $config = Get-BakNRetConfig -Path (Join-Path $sandbox 'nope.psd1') Assert-Equal 'Backups' $config.BackupDir Assert-True ($config.MinFreeSpaceGB -gt 0) } @@ -696,7 +696,7 @@ Test-Case '配置缺失时返回默认值' { Test-Case '配置嵌套段落合并且不丢默认键' { $path = Join-Path $sandbox 'cfg.psd1' Set-Content -Encoding UTF8 -LiteralPath $path -Value "@{ Encryption = @{ Enabled = `$true }; CompressionLevel = 5 }" - $config = Get-BaknretConfig -Path $path + $config = Get-BakNRetConfig -Path $path Assert-Equal 5 $config.CompressionLevel Assert-True $config.Encryption.Enabled Assert-True $config.Encryption.EncryptHeaders '未覆盖的嵌套键应保留默认值' @@ -706,9 +706,9 @@ Test-Case '口令:环境变量可读取,取不到返回 $null' { $old = $env:BAKNRET_PASSWORD try { Remove-Item Env:BAKNRET_PASSWORD -ErrorAction SilentlyContinue - Assert-Null (Get-BaknretPassword -PasswordFile (Join-Path $sandbox 'nope')) + Assert-Null (Get-BakNRetPassword -PasswordFile (Join-Path $sandbox 'nope')) $env:BAKNRET_PASSWORD = 'from-env' - Assert-Equal 'from-env' (Get-BaknretPassword) + Assert-Equal 'from-env' (Get-BakNRetPassword) } finally { Remove-Item Env:BAKNRET_PASSWORD -ErrorAction SilentlyContinue @@ -731,7 +731,7 @@ Test-Case '名录解析:裸键、引号键、带 - 与 . 的名字' { $content = "@{`n FooClolor = @{ DefaultData = @{ Path = '$appDir' } }`n 'scoop-config' = @{ DefaultConfig = @{ Path = '$configDir' } }`n '.ssh' = @{ DefaultData = @{ Path = '$appDir' } }`n}`n" [System.IO.File]::WriteAllText($catPath, $content, [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache Assert-Equal 3 $catalog.Count Assert-True $catalog.ContainsKey('FooClolor') Assert-True $catalog.ContainsKey('scoop-config') '带连字符的键必须能解析' @@ -743,7 +743,7 @@ Test-Case '名录:Slot 按名字排序(哈希表顺序不可依赖)' { $catPath = Join-Path $catalogSandbox 'sorted.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n FooClolor = @{ Zeta = @{ Path = '$appDir' }; Alpha = @{ Path = '$configDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache Assert-Equal 'Multi' $catalog['FooClolor'].Kind Assert-Equal 2 $catalog['FooClolor'].Slots.Count Assert-Equal 'Alpha' $catalog['FooClolor'].Slots[0].Name @@ -756,7 +756,7 @@ Test-Case '名录:Slot 对象字段与契约一致' { Set-Content -Encoding UTF8 -LiteralPath $filePath -Value '{}' [System.IO.File]::WriteAllText($catPath, "@{`n Rich = @{`n Beta = @{ Path = '$filePath'; Encrypt = `$true; Description = 'beta 说明' }`n Alpha = @{ Path = '$configDir'; Exclude = '!*Cache,sub\one'; Include = 'Modules:$appDir' }`n }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache $entry = $catalog['Rich'] foreach ($expected in 'Name', 'Path', 'Description', 'Slots', 'Kind', 'Error', 'Missing', 'Raw') { Assert-True ($entry.PSObject.Properties.Name -contains $expected) "名录条目缺少 $expected" @@ -789,7 +789,7 @@ Test-Case '名录解析:目录带版本后缀时按前缀补全(legendary_2. $catPath = Join-Path $catalogSandbox 'suffix.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n legendary = @{ DefaultConfig = @{ Path = '$root\legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache Assert-Equal 'Single' $catalog['legendary'].Kind Assert-True $catalog['legendary'].Slots[0].Resolved -like '*legendary_2.0.4' Assert-True $catalog['legendary'].Slots[0].Suffixed '补全出来的路径要标记 Suffixed' @@ -801,20 +801,20 @@ Test-Case '名录解析:不会把 Legendary 误配成 LegendarySomething' { $catPath = Join-Path $catalogSandbox 'strict.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n Legendary = @{ DefaultConfig = @{ Path = '$root\Legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache Assert-Equal 'Unresolved' $catalog['Legendary'].Kind '必须以下一个字符是 _ 或 - 为界' Assert-False $catalog['Legendary'].Slots[0].Exists Assert-Equal 1 $catalog['Legendary'].Missing.Count } -Test-Case 'Find-ChildDirectoryByName:_ / - 边界之外不补全' { +Test-Case 'Find-BakNRetChildDirectoryByName:_ / - 边界之外不补全' { $root = Join-Path $catalogSandbox 'boundary' New-Item -ItemType Directory -Path (Join-Path $root 'Legendary_1.0') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Legendary-2.0') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'LegendarySomething') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Unrelated') -Force | Out-Null - $found = @(Find-ChildDirectoryByName -Parent $root -Name 'Legendary') + $found = @(Find-BakNRetChildDirectoryByName -Parent $root -Name 'Legendary') Assert-Equal 2 $found.Count Assert-True (@($found | Where-Object { $_ -like '*Legendary_1.0' }).Count -eq 1) Assert-True (@($found | Where-Object { $_ -like '*Legendary-2.0' }).Count -eq 1) @@ -827,7 +827,7 @@ Test-Case '名录:一个 Slot 补全出多个目录时明确报错(否则会 $catPath = Join-Path $catalogSandbox 'multicand.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n Legendary = @{ DefaultConfig = @{ Path = '$root\Legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $catPath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache $entry = $catalog['Legendary'] Assert-True ($null -ne $entry.Error) Assert-True ($entry.Error -like '*拆成多个 Slot*') '错误里要给出可操作的指引' @@ -836,7 +836,7 @@ Test-Case '名录:一个 Slot 补全出多个目录时明确报错(否则会 Test-Case '名录:Slot 写法不对 / 缺少 Path 都记成 Error,而不是崩掉' { $bad = Join-Path $catalogSandbox 'bad.psd1' [System.IO.File]::WriteAllText($bad, "@{`n A = @{ S = 'C:\x' }`n B = @{ S = @{ Description = 'x' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $bad -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $bad -NoCache Assert-Equal 'Invalid' $catalog['A'].Kind Assert-True ($catalog['A'].Error -like '*Slot S*') Assert-Equal 'Invalid' $catalog['B'].Kind @@ -846,12 +846,12 @@ Test-Case '名录:Slot 写法不对 / 缺少 Path 都记成 Error,而不是 Test-Case '名录:裸字符串 / 数组等旧结构会被 ERROR 跳过(不再当目录用)' { $legacy = Join-Path $catalogSandbox 'legacy.psd1' [System.IO.File]::WriteAllText($legacy, "@{`n A = 'C:\Programs\A'`n B = @('C:\1','C:\2')`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $legacy -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $legacy -NoCache Assert-Equal 0 $catalog.Count '旧结构必须被跳过,不能猜成目录' $dirs = Join-Path $catalogSandbox 'dirs.psd1' [System.IO.File]::WriteAllText($dirs, "@{`n C = @{ Dirs = @('C:\1','C:\2') }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $dirs -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $dirs -NoCache Assert-Equal 1 $catalog.Count Assert-Equal 'Invalid' $catalog['C'].Kind Assert-True ($catalog['C'].Error -like '*Dirs*') @@ -869,7 +869,7 @@ Test-Case '名录:Includes 分文件维护会被合并' { [System.IO.File]::WriteAllText($extraPath, "@{`n 'extra-app' = @{ D = @{ Path = '$appDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) [System.IO.File]::WriteAllText($basePath, "@{`n Includes = @('Extra.psd1')`n 'base-app' = @{ D = @{ Path = '$configDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $catalog = Get-SoftwareCatalog -Path $basePath -NoCache + $catalog = Get-BakNRetSoftwareCatalog -Path $basePath -NoCache Assert-True $catalog.ContainsKey('base-app') Assert-True $catalog.ContainsKey('extra-app') 'Includes 引入的条目也应在' } @@ -878,20 +878,20 @@ Test-Case '名录:结果按文件指纹缓存,-NoCache 强制重读' { $catPath = Join-Path $catalogSandbox 'cache.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$appDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $first = Get-SoftwareCatalog -Path $catPath - $second = Get-SoftwareCatalog -Path $catPath + $first = Get-BakNRetSoftwareCatalog -Path $catPath + $second = Get-BakNRetSoftwareCatalog -Path $catPath Assert-True ([object]::ReferenceEquals($first, $second)) '同一文件同一指纹应命中缓存' - $fresh = Get-SoftwareCatalog -Path $catPath -NoCache + $fresh = Get-BakNRetSoftwareCatalog -Path $catPath -NoCache Assert-False ([object]::ReferenceEquals($first, $fresh)) '-NoCache 必须重新读' [System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$configDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $changed = Get-SoftwareCatalog -Path $catPath + $changed = Get-BakNRetSoftwareCatalog -Path $catPath Assert-Equal $configDir $changed['Cached'].Slots[0].Resolved '文件变了缓存必须失效' } # ============================================================================ -Write-Host "`n== Resolve-BackupEntry(归档项解析) ==" -ForegroundColor Cyan +Write-Host "`n== Resolve-BakNRetBackupEntry(归档项解析) ==" -ForegroundColor Cyan # ============================================================================ $resolveSandbox = Join-Path $env:TEMP ("baknret-resolve-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) @@ -909,7 +909,7 @@ $resolveCatalog = Join-Path $resolveSandbox 'catalog.psd1' Test-Case '软件名条目:一个 Slot -> 一个归档项,路径是 Slot 名,归档名是软件名' { $item = ConvertFrom-BackupListLine -Line 'FooClolor' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-True $resolved.IsName Assert-Equal 'name' $resolved.ArchiveFlavor @@ -925,9 +925,9 @@ Test-Case '软件名条目:一个 Slot -> 一个归档项,路径是 Slot 名 Assert-True $resolved.Items[0].Exists } -Test-Case 'Resolve-BackupEntry 的返回值里没有旧的 Sources / RootName 等属性' { +Test-Case 'Resolve-BakNRetBackupEntry 的返回值里没有旧的 Sources / RootName 等属性' { $item = ConvertFrom-BackupListLine -Line 'FooClolor' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog foreach ($expected in 'IsName', 'CatalogEntry', 'BaseName', 'ArchiveFlavor', 'Direction', 'Items', 'Encrypt', 'ExcludePatterns', 'HasExcludeOverride', 'Includes', 'HasIncludeOverride', 'Source', 'Error', 'Blocking') { Assert-True ($resolved.PSObject.Properties.Name -contains $expected) "缺少属性 $expected" } @@ -938,7 +938,7 @@ Test-Case 'Resolve-BackupEntry 的返回值里没有旧的 Sources / RootName Test-Case '字面路径条目:仍用路径命名算法,归档内是历史布局 <末级名>' { $item = ConvertFrom-BackupListLine -Line 'C:\Programs\FooClolor' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-False $resolved.IsName Assert-Equal 'path' $resolved.ArchiveFlavor Assert-Equal 'FooClolor_from_C_+Programs' $resolved.BaseName @@ -949,34 +949,34 @@ Test-Case '字面路径条目:仍用路径命名算法,归档内是历史布 Test-Case '@pathname 用名录里的真实路径跑命名算法' { $item = ConvertFrom-BackupListLine -Line 'FooClolor @pathname' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog - Assert-Equal (Get-BackupBaseName -RawPath $dirA) $resolved.BaseName + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog + Assert-Equal (Get-BakNRetBackupBaseName -RawPath $dirA) $resolved.BaseName Assert-Equal 'DefaultData' $resolved.Items[0].ArchivePath '归档内名字仍由 Slot 决定' } Test-Case ':: / @ Path= 覆盖单 Slot 条目的真实路径' { $item = ConvertFrom-BackupListLine -Line "FooClolor :: $dirB" - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-Equal 1 $resolved.Items.Count Assert-Equal $dirB $resolved.Items[0].RealPath Assert-Equal 'DefaultData' $resolved.Items[0].ArchivePath Assert-Null $resolved.Blocking $atItem = ConvertFrom-BackupListLine -Line "FooClolor @ Path='$dirB'" - $atResolved = Resolve-BackupEntry -Entry $atItem -CatalogPath $resolveCatalog + $atResolved = Resolve-BakNRetBackupEntry -Entry $atItem -CatalogPath $resolveCatalog Assert-Equal $dirB $atResolved.Items[0].RealPath } Test-Case ':: 覆盖路径时,多 Slot 的条目会 Blocking(说不清是哪个 Slot)' { $item = ConvertFrom-BackupListLine -Line "MultiApp :: $dirB" - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-True ($null -ne $resolved.Blocking) Assert-True ($resolved.Blocking -like '*2 个 Slot*') } Test-Case '名录里没有该名字:BaseName 退回可读目录名,并给出 Error' { $item = ConvertFrom-BackupListLine -Line 'no-such-thing' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-Equal 'no-such-thing' $resolved.BaseName Assert-True ($null -ne $resolved.Error) '应给出错误说明' Assert-Equal 0 $resolved.Items.Count @@ -986,7 +986,7 @@ Test-Case '名录里的路径不存在时仍给出归档项(恢复要靠它还 $catPath = Join-Path $resolveSandbox 'gone.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n 'gone-app' = @{ DefaultData = @{ Path = 'C:\definitely\not\here' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) $item = ConvertFrom-BackupListLine -Line 'gone-app' - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catPath + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catPath Assert-Equal 1 $resolved.Items.Count '恢复端据 Items 还原' Assert-Equal 'C:\definitely\not\here' $resolved.Items[0].RealPath Assert-False $resolved.Items[0].Exists @@ -995,7 +995,7 @@ Test-Case '名录里的路径不存在时仍给出归档项(恢复要靠它还 Test-Case ':+ 追加项:<归档内相对路径>:<宿主机绝对路径>' { $item = ConvertFrom-BackupListLine -Line "FooClolor :+ Modules:$dirExtra" - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-Equal 2 $resolved.Items.Count Assert-Equal 'DefaultData' $resolved.Items[0].ArchivePath Assert-Equal 'Modules' $resolved.Items[1].ArchivePath @@ -1011,11 +1011,11 @@ Test-Case '条目级 :+ 覆盖名录里的 Include(不是叠加)' { $catPath = Join-Path $resolveSandbox 'withinc.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n IncApp = @{ DefaultData = @{ Path = '$dirA'; Include = 'CatalogInc:$incDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $plain = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'IncApp') -CatalogPath $catPath + $plain = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'IncApp') -CatalogPath $catPath Assert-Equal 2 $plain.Items.Count Assert-Equal 'CatalogInc' $plain.Items[1].ArchivePath - $overridden = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line "IncApp :+ EntryInc:$dirExtra") -CatalogPath $catPath + $overridden = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line "IncApp :+ EntryInc:$dirExtra") -CatalogPath $catPath Assert-Equal 2 $overridden.Items.Count Assert-Equal 'EntryInc' $overridden.Items[1].ArchivePath Assert-Equal $dirExtra $overridden.Items[1].RealPath @@ -1023,45 +1023,45 @@ Test-Case '条目级 :+ 覆盖名录里的 Include(不是叠加)' { Test-Case '归档内路径冲突:同名追加项 -> Blocking' { $item = ConvertFrom-BackupListLine -Line "FooClolor :+ DefaultData:$dirExtra" - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-True ($null -ne $resolved.Blocking) Assert-True ($resolved.Blocking -like '*冲突*') } Test-Case '归档内路径冲突:父子关系 -> Blocking' { $item = ConvertFrom-BackupListLine -Line "FooClolor :+ DefaultData\sub:$dirExtra" - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $resolveCatalog + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $resolveCatalog Assert-True ($null -ne $resolved.Blocking) Assert-True ($resolved.Blocking -like '*父子*') } Test-Case '加密:名录 Slot 的 Encrypt 之和,条目级覆盖优先' { - $catalogEncrypt = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'FooClolor') -CatalogPath $resolveCatalog + $catalogEncrypt = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'FooClolor') -CatalogPath $resolveCatalog Assert-True $catalogEncrypt.Encrypt - $off = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'FooClolor :!encrypt') -CatalogPath $resolveCatalog + $off = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'FooClolor :!encrypt') -CatalogPath $resolveCatalog Assert-False $off.Encrypt - $on = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'MultiApp :encrypt') -CatalogPath $resolveCatalog + $on = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'MultiApp :encrypt') -CatalogPath $resolveCatalog Assert-True $on.Encrypt '名录没开、条目开了 -> 开' Assert-False $on.HasExcludeOverride } -Test-Case '方向标记随条目一路传到 Resolve-BackupEntry' { - Assert-Equal 'backup' (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ FooClolor') -CatalogPath $resolveCatalog).Direction - Assert-Equal 'restore' (Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line '- FooClolor') -CatalogPath $resolveCatalog).Direction +Test-Case '方向标记随条目一路传到 Resolve-BakNRetBackupEntry' { + Assert-Equal 'backup' (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '+ FooClolor') -CatalogPath $resolveCatalog).Direction + Assert-Equal 'restore' (Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line '- FooClolor') -CatalogPath $resolveCatalog).Direction } Test-Case '条目级 :- 覆盖名录里的 Exclude(不是叠加)' { $catPath = Join-Path $resolveSandbox 'withexc.psd1' [System.IO.File]::WriteAllText($catPath, "@{`n ExcApp = @{ DefaultData = @{ Path = '$dirA'; Exclude = '!*CatalogCache' } }`n}`n", [System.Text.UTF8Encoding]::new($true)) - $plain = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'ExcApp') -CatalogPath $catPath + $plain = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'ExcApp') -CatalogPath $catPath Assert-False $plain.HasExcludeOverride Assert-Equal 0 $plain.ExcludePatterns.Count Assert-Equal '!*CatalogCache' $plain.Items[0].Exclude[0] - $overridden = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'ExcApp :- !*Listed') -CatalogPath $catPath + $overridden = Resolve-BakNRetBackupEntry -Entry (ConvertFrom-BackupListLine -Line 'ExcApp :- !*Listed') -CatalogPath $catPath Assert-True $overridden.HasExcludeOverride Assert-Equal '!*Listed' $overridden.ExcludePatterns[0] } @@ -1093,8 +1093,8 @@ else { Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'Default\IndexedDB\i.bin') 'i' $patterns = @('!*Cache', 'component_crx_cache', 'Default\Code Cache', 'Default\Extensions', 'Default\IndexedDB') - $archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'path' -Exists $true - $excludeArgs = @((Get-BaknretExcludeArgument -Item $archiveItem -Patterns $patterns).Arguments) + $archiveItem = New-BakNRetArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'path' -Exists $true + $excludeArgs = @((Get-BakNRetExcludeArgument -Item $archiveItem -Patterns $patterns).Arguments) Assert-True ($excludeArgs.Count -ge 5) '排除参数应被翻译出来' $archive = Join-Path $sandbox 'excl.7z' @@ -1124,8 +1124,8 @@ else { Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source "$d\x.bin") 'x' } - $archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'slot' -Exists $true - $excludeArgs = @((Get-BaknretExcludeArgument -Item $archiveItem -Patterns @('!re:^Cache\d+$')).Arguments) + $archiveItem = New-BakNRetArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'slot' -Exists $true + $excludeArgs = @((Get-BakNRetExcludeArgument -Item $archiveItem -Patterns @('!re:^Cache\d+$')).Arguments) Assert-Equal 3 $excludeArgs.Count '两个顶层 Cache 目录 + 嵌套的 sub\Cache3' $archive = Join-Path $sandbox 're.7z' @@ -1159,7 +1159,7 @@ else { Assert-True (Test-Path -LiteralPath (Join-Path $verify "$itemName\Default\Cache\c.bin")) '对照组应当包含被排除的那个文件' } - Test-Case 'Get-ArchiveTopLevelNames 能列出归档内的顶层条目名' { + Test-Case 'Get-BakNRetArchiveTopLevelNames 能列出归档内的顶层条目名' { $root = Join-Path $sandbox 'topnames' New-Item -ItemType Directory -Path (Join-Path $root 'Alpha') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Beta') -Force | Out-Null @@ -1169,7 +1169,7 @@ else { $code = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $archive, 'Alpha', 'Beta') -WorkingDirectory $root Assert-Equal 0 $code - $names = @(Get-ArchiveTopLevelNames -ArchivePath $archive -SevenZip $sevenZip) + $names = @(Get-BakNRetArchiveTopLevelNames -ArchivePath $archive -SevenZip $sevenZip) $sorted = @($names | Sort-Object) Assert-Equal 2 $sorted.Count Assert-Equal 'Alpha' $sorted[0] @@ -1203,13 +1203,13 @@ Test-Case '口令不会进日志:DEBUG 下打印的命令行要遮蔽 -p 参 $sentinel = 'SENTINEL-PW-9f3a' $logPath = $null try { - $logPath = Start-BaknretLog -Directory $sandbox -Prefix 'secret' - Set-BaknretDebug + $logPath = Start-BakNRetLog -Directory $sandbox -Prefix 'secret' + Set-BakNRetDebug $null = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', 'exit 0', "-p$sentinel") } finally { - Set-BaknretDebug -Enabled:$false - Stop-BaknretLog + Set-BakNRetDebug -Enabled:$false + Stop-BakNRetLog } $logText = Get-Content -Encoding UTF8 -LiteralPath $logPath -Raw @@ -1236,7 +1236,7 @@ Test-Case '空目录的摘要给 0 而不是 $null(否则空间守卫会静默 $empty = Join-Path $env:TEMP ("bnr-empty-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) New-Item -ItemType Directory -Path $empty -Force | Out-Null try { - $summary = Get-FolderSummary -FolderPath $empty + $summary = Get-BakNRetFolderSummary -FolderPath $empty Assert-Equal 0 $summary.FileCount Assert-True ($null -ne $summary.TotalSize) 'TotalSize 不能是 $null' Assert-True ($summary.TotalSize -is [long]) 'TotalSize 应当是整数' @@ -1252,11 +1252,11 @@ Test-Case '原子替换:成功时新内容到位且不留 .tmp;失败时旧 New-Item -ItemType Directory -Path $base -Force | Out-Null $target = Join-Path $base 'text.json' try { - Write-BaknretAtomicText -Path $target -Text 'FIRST' | Out-Null + Write-BakNRetAtomicText -Path $target -Text 'FIRST' | Out-Null Assert-Equal 'FIRST' (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) # 目标已存在时走 File.Replace - Write-BaknretAtomicText -Path $target -Text 'SECOND' | Out-Null + Write-BakNRetAtomicText -Path $target -Text 'SECOND' | Out-Null Assert-Equal 'SECOND' (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) Assert-FileMissing "$target.tmp" @@ -1264,7 +1264,7 @@ Test-Case '原子替换:成功时新内容到位且不留 .tmp;失败时旧 # 后者失败时旧文件已经没了,而 manifest 丢了就只能靠文件名反推。 (Get-Item -LiteralPath $target).IsReadOnly = $true $threw = $false - try { Write-BaknretAtomicText -Path $target -Text 'SHOULD-NOT-LAND' | Out-Null } catch { $threw = $true } + try { Write-BakNRetAtomicText -Path $target -Text 'SHOULD-NOT-LAND' | Out-Null } catch { $threw = $true } Assert-True $threw '目标只读时替换应当抛错' Assert-Equal 'SECOND' (Get-Content -Encoding UTF8 -LiteralPath $target -Raw) } @@ -1286,22 +1286,22 @@ Test-Case '运行锁:同一份备份目录同时只能有一个持有者' { $first = $null $second = $null try { - $first = Enter-BaknretRunLock -Directory $dir + $first = Enter-BakNRetRunLock -Directory $dir Assert-True ($null -ne $first) '第一次应当拿到锁' - $second = Enter-BaknretRunLock -Directory $dir + $second = Enter-BakNRetRunLock -Directory $dir Assert-True ($null -eq $second) '同一目录的第二次不应当拿到锁' - Assert-FileExists (Get-BaknretRunLockPath -Directory $dir) + Assert-FileExists (Get-BakNRetRunLockPath -Directory $dir) } finally { - Exit-BaknretRunLock -Lock $second - Exit-BaknretRunLock -Lock $first + Exit-BakNRetRunLock -Lock $second + Exit-BakNRetRunLock -Lock $first } # 锁文件是**独占**打开的(FileShare.None),内容只能在释放之后读 —— 而它本来就该 # 留在那里给排查的人看:"到底是谁占着"这个问题不该靠猜。 - $info = Get-Content -Encoding UTF8 -LiteralPath (Get-BaknretRunLockPath -Directory $dir) -Raw + $info = Get-Content -Encoding UTF8 -LiteralPath (Get-BakNRetRunLockPath -Directory $dir) -Raw Assert-True ($info -match "pid=$PID") "锁文件里应当写明持有进程,实际内容是:$info" Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue @@ -1310,13 +1310,13 @@ Test-Case '运行锁:同一份备份目录同时只能有一个持有者' { Test-Case '运行锁:释放之后可以重新取得' { $dir = Join-Path $env:TEMP ("bnr-lock-" + [guid]::NewGuid().ToString('N').Substring(0, 8)) try { - $first = Enter-BaknretRunLock -Directory $dir + $first = Enter-BakNRetRunLock -Directory $dir Assert-True ($null -ne $first) '第一次应当拿到锁' - Exit-BaknretRunLock -Lock $first + Exit-BakNRetRunLock -Lock $first - $second = Enter-BaknretRunLock -Directory $dir + $second = Enter-BakNRetRunLock -Directory $dir Assert-True ($null -ne $second) '释放之后应当能重新拿到锁' - Exit-BaknretRunLock -Lock $second + Exit-BakNRetRunLock -Lock $second } finally { Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue diff --git a/tools/Rename-Archives.ps1 b/tools/Rename-Archives.ps1 index 91a4517..415b774 100644 --- a/tools/Rename-Archives.ps1 +++ b/tools/Rename-Archives.ps1 @@ -51,14 +51,14 @@ if (-not $BackupListPath) { $BackupListPath = Join-Path $projectRoot 'BackupList Import-Module (Join-Path $projectRoot 'BakNRet\BakNRet.psd1') -Force if (-not $ConfigPath) { $ConfigPath = Join-Path $projectRoot 'BackupConfig.psd1' } -$config = Get-BaknretConfig -Path $ConfigPath +$config = Get-BakNRetConfig -Path $ConfigPath if (-not $BackupDir) { $BackupDir = $config.BackupDir if (-not [System.IO.Path]::IsPathRooted($BackupDir)) { $BackupDir = Join-Path $projectRoot $BackupDir } } -$catalogPath = Resolve-CatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot +$catalogPath = Resolve-BakNRetCatalogPath -Configured $config.SoftwareCatalog -Root $projectRoot $manifestPath = Join-Path $BackupDir 'manifest.json' $supportedFormats = @('.7z', '.rar', '.zip', '.tar') @@ -85,7 +85,7 @@ function Find-ArchiveByBaseName { # 阶段一:规划 # --------------------------------------------------------------------------- -$manifestOld = Read-BaknretManifest -Path $manifestPath +$manifestOld = Read-BakNRetManifest -Path $manifestPath # manifest 里的历史归档名按 source / resolvedSource 建索引: # 条目写法改过(软件名 -> 手写路径、改名、合并)之后,键对不上了, @@ -121,7 +121,7 @@ foreach ($line in (Get-Content -Encoding UTF8 -LiteralPath $BackupListPath)) { $item = ConvertFrom-BackupListLine -Line $line if (-not $item) { continue } - $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth + $resolved = Resolve-BakNRetBackupEntry -Entry $item -CatalogPath $catalogPath -MaxDepth $config.CatalogMaxDepth $newName = $resolved.BaseName if (-not $newName) { continue } @@ -143,11 +143,11 @@ foreach ($line in (Get-Content -Encoding UTF8 -LiteralPath $BackupListPath)) { $pathSource = if ($slots.Count -eq 1) { $slots[0].Declared } else { $null } } if ($pathSource) { - $derived = Get-BackupBaseName -RawPath $pathSource + $derived = Get-BakNRetBackupBaseName -RawPath $pathSource if ($derived) { $candidates += $derived } } if ($resolved.IsName) { - $candidates += (Format-CatalogName -Name $item.Path) + $candidates += (Format-BakNRetCatalogName -Name $item.Path) } if ($manifestOld.items.Contains($newName)) { $recorded = $manifestOld.items[$newName] @@ -316,7 +316,7 @@ $manifestNew = [pscustomobject]@{ compressor = $manifestOld.compressor items = $rebuilt } -Write-BaknretManifest -Path $manifestPath -Manifest $manifestNew | Out-Null +Write-BakNRetManifest -Path $manifestPath -Manifest $manifestNew | Out-Null # --------------------------------------------------------------------------- # 阶段四:报告 diff --git a/tools/lab/README.md b/tools/lab/README.md index a703c3e..3f66f73 100644 --- a/tools/lab/README.md +++ b/tools/lab/README.md @@ -1,4 +1,4 @@ -# tools\lab —— BakNRet 的隔离测试环境(Hyper-V 真机级 VM) +# tools\lab —— BakNRet 的隔离测试环境(Hyper-V 真机级 VM) 在**宿主机之外的 Windows 虚拟机**里跑 BakNRet 的备份 / 恢复 / 测试。宿主机仓库、`Backups\`、 `logs\` 在本环境里只被读取,从不写入;VM 内也没有挂载宿主机的任何目录(一切交互走 @@ -138,14 +138,14 @@ gsudo pwsh -NoProfile -File .\tools\lab\Lab.ps1 reset # 秒回 clean-baseli ### 在 VM 里直接跑 `tests\Run-Pester.ps1` 会红 8 项(都是中文断言) -`tests\BakNRet*.Tests.ps1` 里的 `Invoke-BaknretScript` 这样抓子进程输出: +`tests\BakNRet*.Tests.ps1` 里的 `Invoke-BakNRetScript` 这样抓子进程输出: ``` cmd /c pwsh -File Backup.ps1 ... > out.txt 2>&1 Get-Content -LiteralPath out.txt -Encoding UTF8 ``` -而 `Backup.ps1` / `Restore.ps1` 的 `Write-Log` 走 `Write-Host`,写进 `out.txt` 的**字节编码取自 +而 `Backup.ps1` / `Restore.ps1` 的 `Write-BakNRetLog` 走 `Write-Host`,写进 `out.txt` 的**字节编码取自 `[Console]::OutputEncoding`**: | 环境 | `[Console]::OutputEncoding` | 结果 | diff --git a/tools/lab/payload/run-acl-scenario.ps1 b/tools/lab/payload/run-acl-scenario.ps1 index 0e3118a..69f427b 100644 --- a/tools/lab/payload/run-acl-scenario.ps1 +++ b/tools/lab/payload/run-acl-scenario.ps1 @@ -101,7 +101,7 @@ function Get-SecurityFingerprint { return ('O={0} G={1} P={2} [{3}]' -f $acl.GetOwner($sid).Value, $acl.GetGroup($sid).Value, $acl.AreAccessRulesProtected, ($aces -join ' ')) } -function Invoke-BaknretChild { +function Invoke-BakNRetChild { <# .SYNOPSIS 用独立进程跑 Backup.ps1 / Restore.ps1(两个脚本结尾都会 exit)。 @@ -190,7 +190,7 @@ function Remove-TreeHard { Where-Object { $_.Attributes -band [IO.FileAttributes]::ReparsePoint }) foreach ($link in $links) { $null = Invoke-NativeTolerant -FilePath 'cmd.exe' -ArgumentList @('/c', ('rmdir "{0}"' -f $link.FullName)) - Remove-BaknretJunction -Path $link.FullName + Remove-BakNRetJunction -Path $link.FullName } $null = Invoke-NativeTolerant -FilePath 'cmd.exe' -ArgumentList @('/c', ('rmdir /s /q "{0}"' -f $Path)) @@ -217,7 +217,7 @@ else { $BackupDir = Join-Path $WorkRoot 'backups' New-Item -ItemType Directory -Path $BackupDir -Force | Out-Null -$privileges = Enable-BaknretPrivilege -Name @('SeRestorePrivilege', 'SeBackupPrivilege') +$privileges = Enable-BakNRetPrivilege -Name @('SeRestorePrivilege', 'SeBackupPrivilege') if ($privileges.Missing.Count -gt 0) { Write-Host ('[acl] 警告:{0} 不在令牌里 —— 属主无法恢复,B 段会失败(应以管理员/SYSTEM 运行)' -f ($privileges.Missing -join '、')) -ForegroundColor Yellow } @@ -398,7 +398,7 @@ $configText = @" Write-Host '' Write-Host '[备份] Backup.ps1' -ForegroundColor Yellow -$backup = Invoke-BaknretChild -Script (Join-Path $RepoPath 'Backup.ps1') -Parameters @{ +$backup = Invoke-BakNRetChild -Script (Join-Path $RepoPath 'Backup.ps1') -Parameters @{ BackupListPath = $listPath ConfigPath = $configPath BackupDir = $BackupDir @@ -420,7 +420,7 @@ Test-Scenario '源已删除(模拟真的丢了)' ($leftovers.Count -eq 0) ($ Write-Host '' Write-Host '[恢复] Restore.ps1' -ForegroundColor Yellow -$restore = Invoke-BaknretChild -Script (Join-Path $RepoPath 'Restore.ps1') -Parameters @{ +$restore = Invoke-BakNRetChild -Script (Join-Path $RepoPath 'Restore.ps1') -Parameters @{ BackupListPath = $listPath ConfigPath = $configPath BackupDir = $BackupDir diff --git a/tools/lab/payload/run-suite-utf8.ps1 b/tools/lab/payload/run-suite-utf8.ps1 index 40265d9..f06f6bc 100644 --- a/tools/lab/payload/run-suite-utf8.ps1 +++ b/tools/lab/payload/run-suite-utf8.ps1 @@ -3,12 +3,12 @@ 在 VM 内以 UTF-8 控制台编码运行一个测试套件(不改仓库里的任何测试代码)。 .DESCRIPTION - 为什么需要它 —— tests\BakNRet*.Tests.ps1 的 Invoke-BaknretScript 是这么抓子进程输出的: + 为什么需要它 —— tests\BakNRet*.Tests.ps1 的 Invoke-BakNRetScript 是这么抓子进程输出的: cmd /c pwsh -File Backup.ps1 ... > out.txt 2>&1 Get-Content -LiteralPath out.txt -Encoding UTF8 - 而 Backup.ps1 / Restore.ps1 的 Write-Log 走 Write-Host,写进 out.txt 的字节用的是 + 而 Backup.ps1 / Restore.ps1 的 Write-BakNRetLog 走 Write-Host,写进 out.txt 的字节用的是 `[Console]::OutputEncoding`: * 宿主机上它是 utf-8 -> 文件是 UTF-8 -> 按 UTF-8 读回,中文正确,套件全绿;