fix: 让 5.1 真正可用(显式编码 + 原生 stderr 处理 + .psd1 夹具带 BOM)

上一提交让 5.1 能解析源码,但 Unit 与 Smoke 在 5.1 上仍然是红的。根因是三类彼此
无关的 5.1/7 行为差,全部实测确认:

1) 不写 -Encoding 时,5.1 的 Get-Content / Set-Content 默认是 ANSI,7 是 UTF-8。
   症状是 UTF-8 字节被按 GBK 解出「璇存槑」这类乱码。62 处补上显式 -Encoding UTF8。
   用 AST 而不是正则定位,避免把注释里的散文也改掉。

2) .psd1 夹具用无 BOM 写,而引擎的 .psd1 读取器(Import-PowerShellDataFile)只能靠
   BOM 判断编码、没有参数可传,于是 5.1 按 ANSI 解。40 处夹具改为带 BOM 写 —— 这正是
   .editorconfig 里 [*.psd1] charset = utf-8-bom 本来就要求的,是夹具违反了自己的约定。
   .cmd 批次文件刻意保持无 BOM:cmd.exe 会被 BOM 弄坏。

3) 5.1 在 $ErrorActionPreference = Stop 下会把原生命令写到 stderr 的内容升级成终止性
   NativeCommandError,7 改了这条。takeown/icacls 的 ACL 复位调用、以及 test.ps1 自己
   调子进程的地方,都需要在 Continue 下跑。

验收:test.ps1 9/9 全绿(Encode + Parse + Unit + Smoke + E2E,在 7 与 5.1 上各跑一遍)。

已知未处理(留待后续提交):tools/lab/** 里还有若干「原生命令 + 2>&1 + Stop」的同类
写法(takeown / icacls / scoop / code / Get-WimInfo)。它们要 Hyper-V 实验机才跑得到,
不在验收门槛内。
This commit is contained in:
Shuery committed 2026-09-26 22:16:04 +08:00
1 parent 2d26f78d15
commit e10503be76
13 files changed
+122 -104

No files matched your search

+2 -2
View File
@@ -134,7 +134,7 @@ if (-not (Test-Path -LiteralPath $BackupListPath)) {
"# 示例: Edge`n" + "# 示例: Edge`n" +
"# %UserProfile%\.ssh :encrypt`n" + "# %UserProfile%\.ssh :encrypt`n" +
"# 完整语法见 README 与 BackupList.txt 自身的注释。`n" "# 完整语法见 README 与 BackupList.txt 自身的注释。`n"
[System.IO.File]::WriteAllText($BackupListPath, $template, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($BackupListPath, $template, [System.Text.UTF8Encoding]::new($true))
Write-Log '模板 BackupList.txt 已创建,请编辑后重试。' -Level INFO Write-Log '模板 BackupList.txt 已创建,请编辑后重试。' -Level INFO
Stop-BaknretLog Stop-BaknretLog
exit 0 exit 0
@@ -162,7 +162,7 @@ $encryptAll = [bool]$script:Config.Encryption.Enabled
$showToolOutput = (-not $QuietTool) -and ($script:Config.ToolOutput -ne 'quiet') $showToolOutput = (-not $QuietTool) -and ($script:Config.ToolOutput -ne 'quiet')
$toolQuietArgument = if ($showToolOutput) { @() } else { @('-bso0', '-bsp0') } $toolQuietArgument = if ($showToolOutput) { @() } else { @('-bso0', '-bsp0') }
$lines = Get-Content -LiteralPath $BackupListPath $lines = Get-Content -Encoding UTF8 -LiteralPath $BackupListPath
$seenBaseNames = @{} $seenBaseNames = @{}
$processed = 0; $skipped = 0; $failed = 0; $planned = 0 $processed = 0; $skipped = 0; $failed = 0; $planned = 0
$securityErrorCount = 0 # 有条目"安全描述符里有读不到的对象" $securityErrorCount = 0 # 有条目"安全描述符里有读不到的对象"
+2 -2
View File
@@ -484,7 +484,7 @@ if (-not (Test-Path -LiteralPath $BackupListPath)) {
} }
$content = "# BackupList.txt(自动生成,排除规则需要手工补回)`n" + (($paths -join [Environment]::NewLine) + [Environment]::NewLine) $content = "# BackupList.txt(自动生成,排除规则需要手工补回)`n" + (($paths -join [Environment]::NewLine) + [Environment]::NewLine)
[System.IO.File]::WriteAllText($BackupListPath, $content, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($BackupListPath, $content, [System.Text.UTF8Encoding]::new($true))
Write-Log "已生成配置,包含 $($paths.Count) 个项目,请检查后重新运行" -Level INFO Write-Log "已生成配置,包含 $($paths.Count) 个项目,请检查后重新运行" -Level INFO
Stop-BaknretLog Stop-BaknretLog
exit 0 exit 0
@@ -509,7 +509,7 @@ function Test-EntrySelected {
# 主流程 # 主流程
# ============================================================================ # ============================================================================
$lines = Get-Content -LiteralPath $BackupListPath -ErrorAction Stop $lines = Get-Content -Encoding UTF8 -LiteralPath $BackupListPath -ErrorAction Stop
$stats = @{ restored = 0; skipped = 0; failed = 0; verified = 0; planned = 0 } $stats = @{ restored = 0; skipped = 0; failed = 0; verified = 0; planned = 0 }
$securityApplied = 0 # 本次回放成功的安全描述符对象数 $securityApplied = 0 # 本次回放成功的安全描述符对象数
$failures = @() $failures = @()
+4
View File
@@ -94,6 +94,9 @@ function Invoke-ScriptInHost {
$argumentList = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath) + $Arguments $argumentList = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath) + $Arguments
try { try {
# 5.1 在 $ErrorActionPreference = 'Stop' 下会把原生命令写到 stderr 的内容升级成
# 终止性错误;而被调脚本往 stderr 写字是正常的(7z 就这么干),不该让验收崩掉。
$ErrorActionPreference = 'Continue'
if ($Quiet) { if ($Quiet) {
& $exeOf[$HostName] @argumentList *> $null & $exeOf[$HostName] @argumentList *> $null
} else { } else {
@@ -176,6 +179,7 @@ function Invoke-ParseLayer {
Set-Content -LiteralPath $probe -Value $parseProbeSource -Encoding ascii Set-Content -LiteralPath $probe -Value $parseProbeSource -Encoding ascii
try { try {
$ErrorActionPreference = 'Continue' # 同上:原生命令的 stderr 不是错误
$output = & $exeOf[$HostName] -NoProfile -ExecutionPolicy Bypass -File $probe $root 2>&1 $output = & $exeOf[$HostName] -NoProfile -ExecutionPolicy Bypass -File $probe $root 2>&1
$summary = @($output | Where-Object { $_ -match '^SUMMARY=' }) $summary = @($output | Where-Object { $_ -match '^SUMMARY=' })
$failures = @($output | Where-Object { $_ -match '^\s+\[!\]' }) $failures = @($output | Where-Object { $_ -match '^\s+\[!\]' })
+22 -22
View File
@@ -79,7 +79,7 @@ BeforeAll {
function Write-ListFile { function Write-ListFile {
param([Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Content) param([Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Content)
[System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($true))
return $Path return $Path
} }
} }
@@ -102,18 +102,18 @@ Describe '软件名录:Slot 形状(新契约)' {
$script:DirB = Join-Path $script:FormatRoot 'dirB' $script:DirB = Join-Path $script:FormatRoot 'dirB'
foreach ($directory in $script:DirA, $script:DirB) { foreach ($directory in $script:DirA, $script:DirB) {
New-Item -ItemType Directory -Path $directory -Force | Out-Null New-Item -ItemType Directory -Path $directory -Force | Out-Null
Set-Content -LiteralPath (Join-Path $directory 'keep.txt') "keep-$directory" Set-Content -Encoding UTF8 -LiteralPath (Join-Path $directory 'keep.txt') "keep-$directory"
} }
New-Item -ItemType Directory -Path (Join-Path $script:DirA 'Cache') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:DirA 'Cache') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:DirA 'Cache\c.bin') 'cache' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:DirA 'Cache\c.bin') 'cache'
$script:CfgFile = Join-Path $script:FormatRoot 'settings.json' $script:CfgFile = Join-Path $script:FormatRoot 'settings.json'
Set-Content -LiteralPath $script:CfgFile '{"a":1}' Set-Content -Encoding UTF8 -LiteralPath $script:CfgFile '{"a":1}'
# 两个不同父目录下各有一个**同名**子目录 —— 供"归档内同名"冲突测试用 # 两个不同父目录下各有一个**同名**子目录 —— 供"归档内同名"冲突测试用
$script:CollideRoot = Join-Path $script:FormatRoot 'collide' $script:CollideRoot = Join-Path $script:FormatRoot 'collide'
foreach ($parent in 'p1', 'p2') { foreach ($parent in 'p1', 'p2') {
New-Item -ItemType Directory -Path (Join-Path $script:CollideRoot "$parent\dupdir") -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:CollideRoot "$parent\dupdir") -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:CollideRoot "$parent\dupdir\x.txt") $parent Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:CollideRoot "$parent\dupdir\x.txt") $parent
} }
$script:MissingDir = Join-Path $script:FormatRoot 'not-here' $script:MissingDir = Join-Path $script:FormatRoot 'not-here'
@@ -324,12 +324,12 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ
New-Item -ItemType Directory -Path $script:SlotCfgDir -Force | Out-Null New-Item -ItemType Directory -Path $script:SlotCfgDir -Force | Out-Null
New-Item -ItemType Directory -Path $script:SlotInclude -Force | Out-Null New-Item -ItemType Directory -Path $script:SlotInclude -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') 'one' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') 'one'
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') 'deep' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') 'deep'
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') 'cache' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') 'cache'
Set-Content -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') 'two' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') 'two'
Set-Content -LiteralPath (Join-Path $script:SlotCfgDir 'settings.json') '{"a":1}' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotCfgDir 'settings.json') '{"a":1}'
Set-Content -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') 'mod' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') 'mod'
$appOne = $script:SlotAppOne $appOne = $script:SlotAppOne
$appTwo = $script:SlotAppTwo $appTwo = $script:SlotAppTwo
@@ -388,7 +388,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ
Test-Path -LiteralPath (Join-Path $verify 'Extra\two.txt') | Should -BeTrue Test-Path -LiteralPath (Join-Path $verify 'Extra\two.txt') | Should -BeTrue
Test-Path -LiteralPath (Join-Path $verify 'Modules\mod.txt') | Should -BeTrue Test-Path -LiteralPath (Join-Path $verify 'Modules\mod.txt') | Should -BeTrue
Test-Path -LiteralPath (Join-Path $verify 'Cfg') -PathType Leaf | Should -BeTrue Test-Path -LiteralPath (Join-Path $verify 'Cfg') -PathType Leaf | Should -BeTrue
(Get-Content -LiteralPath (Join-Path $verify 'Cfg') -Raw).Trim() | Should -Be '{"a":1}' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $verify 'Cfg') -Raw).Trim() | Should -Be '{"a":1}'
} }
It '真实恢复:目录 Slot、文件 Slot 与 Include 都落回各自的原位' { It '真实恢复:目录 Slot、文件 Slot 与 Include 都落回各自的原位' {
@@ -404,10 +404,10 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ
$run.ExitCode | Should -Be 0 $run.ExitCode | Should -Be 0
$run.Output | Should -Match '恢复成功: appkit' $run.Output | Should -Match '恢复成功: appkit'
(Get-Content -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') -Raw).Trim() | Should -Be 'one' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') -Raw).Trim() | Should -Be 'one'
(Get-Content -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') -Raw).Trim() | Should -Be 'deep' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') -Raw).Trim() | Should -Be 'deep'
(Get-Content -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') -Raw).Trim() | Should -Be 'two' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') -Raw).Trim() | Should -Be 'two'
(Get-Content -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') -Raw).Trim() | Should -Be 'mod' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') -Raw).Trim() | Should -Be 'mod'
# 被 Slot 排除的缓存没有进过归档,自然也不会被恢复出来 # 被 Slot 排除的缓存没有进过归档,自然也不会被恢复出来
Test-Path -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') | Should -BeFalse Test-Path -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') | Should -BeFalse
@@ -417,7 +417,7 @@ Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZ
# 目标文件被删掉了,名录解析只能得到 IsFile=false;判据要靠 manifest 的 layouts。 # 目标文件被删掉了,名录解析只能得到 IsFile=false;判据要靠 manifest 的 layouts。
$restored = Join-Path $script:SlotCfgDir 'settings.json' $restored = Join-Path $script:SlotCfgDir 'settings.json'
(Test-Path -LiteralPath $restored -PathType Leaf) | Should -BeTrue (Test-Path -LiteralPath $restored -PathType Leaf) | Should -BeTrue
(Get-Content -LiteralPath $restored -Raw).Trim() | Should -Be '{"a":1}' (Get-Content -Encoding UTF8 -LiteralPath $restored -Raw).Trim() | Should -Be '{"a":1}'
} }
It '恢复之后 manifest 记下 lastRestoreAt' { It '恢复之后 manifest 记下 lastRestoreAt' {
@@ -440,7 +440,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven
$script:LegacyLeaf = 'My Code Space' $script:LegacyLeaf = 'My Code Space'
New-Item -ItemType Directory -Path (Join-Path $script:LegacyHolder $script:LegacyLeaf) -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:LegacyHolder $script:LegacyLeaf) -Force | Out-Null
New-Item -ItemType Directory -Path $script:LegacyDestParent -Force | Out-Null New-Item -ItemType Directory -Path $script:LegacyDestParent -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:LegacyHolder "$script:LegacyLeaf\legacy.txt") 'old-layout' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:LegacyHolder "$script:LegacyLeaf\legacy.txt") 'old-layout'
$destPath = Join-Path $script:LegacyDestParent $script:LegacyLeaf $destPath = Join-Path $script:LegacyDestParent $script:LegacyLeaf
$script:LegacyCatalog = Write-ListFile -Path (Join-Path $script:LegacyRoot 'cat.psd1') -Content @" $script:LegacyCatalog = Write-ListFile -Path (Join-Path $script:LegacyRoot 'cat.psd1') -Content @"
@@ -480,7 +480,7 @@ Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSeven
$run.ExitCode | Should -Be 0 $run.ExitCode | Should -Be 0
$run.Output | Should -Match '按旧布局回退' $run.Output | Should -Match '按旧布局回退'
Test-Path -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") | Should -BeTrue Test-Path -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") | Should -BeTrue
(Get-Content -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") -Raw).Trim() | Should -Be 'old-layout' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") -Raw).Trim() | Should -Be 'old-layout'
} }
It '归档里既没有 Slot 层、也没有旧布局名字时明确失败(不再"成功地什么都没恢复")' { It '归档里既没有 Slot 层、也没有旧布局名字时明确失败(不再"成功地什么都没恢复")' {
@@ -513,7 +513,7 @@ Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script
$p2 = Join-Path $script:RejectRoot 'p2\dupdir' $p2 = Join-Path $script:RejectRoot 'p2\dupdir'
foreach ($directory in $p1, $p2) { foreach ($directory in $p1, $p2) {
New-Item -ItemType Directory -Path $directory -Force | Out-Null New-Item -ItemType Directory -Path $directory -Force | Out-Null
Set-Content -LiteralPath (Join-Path $directory 'x.txt') 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $directory 'x.txt') 'x'
} }
# Slot 叫 Data,Include 也要放进包内的 Data -> 同一个位置,必须报错 # Slot 叫 Data,Include 也要放进包内的 Data -> 同一个位置,必须报错
Write-ListFile -Path $script:RejectCatalog -Content "@{`n 'collide' = @{ Data = @{ Path = '$p1' } }`n}`n" | Out-Null Write-ListFile -Path $script:RejectCatalog -Content "@{`n 'collide' = @{ Data = @{ Path = '$p1' } }`n}`n" | Out-Null
@@ -556,7 +556,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip
$script:OrphanRoot = Join-Path $script:Sandbox 'orphan-e2e' $script:OrphanRoot = Join-Path $script:Sandbox 'orphan-e2e'
$script:OrphanSource = Join-Path $script:OrphanRoot 'src\My App' $script:OrphanSource = Join-Path $script:OrphanRoot 'src\My App'
New-Item -ItemType Directory -Path $script:OrphanSource -Force | Out-Null New-Item -ItemType Directory -Path $script:OrphanSource -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:OrphanSource 'data.txt') 'hello' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:OrphanSource 'data.txt') 'hello'
$script:OrphanCatalog = Write-ListFile -Path (Join-Path $script:OrphanRoot 'cat.psd1') ` $script:OrphanCatalog = Write-ListFile -Path (Join-Path $script:OrphanRoot 'cat.psd1') `
-Content "@{ 'my-app' = @{ Default = @{ Path = '$script:OrphanSource' } } }`n" -Content "@{ 'my-app' = @{ Default = @{ Path = '$script:OrphanSource' } } }`n"
@@ -606,7 +606,7 @@ Describe 'manifest 一致性:archive 字段只在文件真的存在时才写'
BeforeAll { BeforeAll {
$script:SyncRoot = Join-Path $script:Sandbox 'sync' $script:SyncRoot = Join-Path $script:Sandbox 'sync'
New-Item -ItemType Directory -Path $script:SyncRoot -Force | Out-Null New-Item -ItemType Directory -Path $script:SyncRoot -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:SyncRoot 'real.7z') 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:SyncRoot 'real.7z') 'x'
$script:SyncManifestPath = Join-Path $script:SyncRoot 'manifest.json' $script:SyncManifestPath = Join-Path $script:SyncRoot 'manifest.json'
$manifest = Read-BaknretManifest -Path $script:SyncManifestPath $manifest = Read-BaknretManifest -Path $script:SyncManifestPath
+19 -5
View File
@@ -97,8 +97,22 @@ BeforeAll {
<# .SYNOPSIS 先把 ACL 复位再删:拒绝型 / protected 的 DACL 会让 Remove-Item 直接失败。 #> <# .SYNOPSIS 先把 ACL 复位再删:拒绝型 / protected 的 DACL 会让 Remove-Item 直接失败。 #>
param([Parameter(Mandatory = $true)][string]$Path) param([Parameter(Mandatory = $true)][string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { return } if (-not (Test-Path -LiteralPath $Path)) { return }
& takeown.exe /F $Path /R /D Y 2>&1 | Out-Null
& icacls.exe $Path /reset /T /C /Q 2>&1 | Out-Null # 这两个是原生命令,失败与否都不该打断测试 —— 它们的唯一目的是"把 ACL 复位到能删"。
#
# 必须在 Continue 下调用:Windows PowerShell 5.1 在 $ErrorActionPreference = 'Stop'
# 时,会把原生命令写到 stderr 的内容升级成终止性的 NativeCommandError 抛出来
# (takeown / icacls 对"已经处理过"的对象就会写 stderr)。PowerShell 7 改了这条
# 规矩,所以这段在 7 上一直是绿的,只在 5.1 上炸。
$previousPreference = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
try {
& takeown.exe /F $Path /R /D Y 2>&1 | Out-Null
& icacls.exe $Path /reset /T /C /Q 2>&1 | Out-Null
} finally {
$ErrorActionPreference = $previousPreference
}
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue
} }
@@ -160,7 +174,7 @@ BeforeAll {
New-Item -ItemType Directory -Path $backupDir -Force | Out-Null New-Item -ItemType Directory -Path $backupDir -Force | Out-Null
$listPath = Join-Path $dir 'BackupList.txt' $listPath = Join-Path $dir 'BackupList.txt'
[System.IO.File]::WriteAllText($listPath, "$sourcePath`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($listPath, "$sourcePath`n", [System.Text.UTF8Encoding]::new($true))
$configPath = Join-Path $dir 'BackupConfig.psd1' $configPath = Join-Path $dir 'BackupConfig.psd1'
$configText = @" $configText = @"
@@ -180,7 +194,7 @@ BeforeAll {
DefaultExcludes = @() DefaultExcludes = @()
} }
"@ "@
[System.IO.File]::WriteAllText($configPath, $configText, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($configPath, $configText, [System.Text.UTF8Encoding]::new($true))
return [pscustomobject]@{ return [pscustomobject]@{
Dir = $dir Dir = $dir
@@ -426,7 +440,7 @@ Describe '与 Backup.ps1 / Restore.ps1 的集成' -Skip:(-not $script:HasSevenZi
$sidecars.Count | Should -Be 1 $sidecars.Count | Should -Be 1
$result.Output | Should -Match '安全描述符:3 个对象' $result.Output | Should -Match '安全描述符:3 个对象'
$manifest = Get-Content -LiteralPath (Join-Path $script:Harness.BackupDir 'manifest.json') -Raw | ConvertFrom-Json $manifest = Get-Content -Encoding UTF8 -LiteralPath (Join-Path $script:Harness.BackupDir 'manifest.json') -Raw | ConvertFrom-Json
$key = @($manifest.items.PSObject.Properties.Name)[0] $key = @($manifest.items.PSObject.Properties.Name)[0]
$manifest.items.$key.security.file | Should -Be $sidecars[0].Name $manifest.items.$key.security.file | Should -Be $sidecars[0].Name
$manifest.items.$key.security.objects | Should -Be 3 $manifest.items.$key.security.objects | Should -Be 3
+16 -16
View File
@@ -121,7 +121,7 @@ BeforeAll {
function Write-ListFile { function Write-ListFile {
param([Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Content) param([Parameter(Mandatory = $true)][string]$Path, [Parameter(Mandatory = $true)][string]$Content)
[System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($Path, $Content, [System.Text.UTF8Encoding]::new($true))
return $Path return $Path
} }
} }
@@ -348,7 +348,7 @@ Describe '归档命名' {
$script:NamingRoot = Join-Path $script:Sandbox 'naming' $script:NamingRoot = Join-Path $script:Sandbox 'naming'
$script:NamingApp = Join-Path $script:NamingRoot 'Real App' $script:NamingApp = Join-Path $script:NamingRoot 'Real App'
New-Item -ItemType Directory -Path $script:NamingApp -Force | Out-Null New-Item -ItemType Directory -Path $script:NamingApp -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:NamingApp 'keep.txt') 'k' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:NamingApp 'keep.txt') 'k'
$script:NamingCatalog = Write-ListFile -Path (Join-Path $script:NamingRoot 'cat.psd1') -Content @" $script:NamingCatalog = Write-ListFile -Path (Join-Path $script:NamingRoot 'cat.psd1') -Content @"
@{ @{
@@ -415,8 +415,8 @@ Describe '7z 排除参数翻译' {
$script:ExcludeTree = Join-Path $script:Sandbox 'exclude-tree' $script:ExcludeTree = Join-Path $script:Sandbox 'exclude-tree'
New-Item -ItemType Directory -Path (Join-Path $script:ExcludeTree 'sub\Cache') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:ExcludeTree 'sub\Cache') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $script:ExcludeTree 'Keep') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:ExcludeTree 'Keep') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:ExcludeTree 'sub\Cache\x.bin') 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:ExcludeTree 'sub\Cache\x.bin') 'x'
Set-Content -LiteralPath (Join-Path $script:ExcludeTree 'Keep\y.bin') 'y' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:ExcludeTree 'Keep\y.bin') 'y'
function New-TestArchiveItem { function New-TestArchiveItem {
param([string]$ArchivePath, [string]$RealPath = 'C:\nonexistent-path') param([string]$ArchivePath, [string]$RealPath = 'C:\nonexistent-path')
@@ -573,8 +573,8 @@ Describe '归档项与暂存目录' {
$script:StagingDir = Join-Path $script:StagingRoot 'real-dir' $script:StagingDir = Join-Path $script:StagingRoot 'real-dir'
$script:StagingFile = Join-Path $script:StagingRoot 'settings.json' $script:StagingFile = Join-Path $script:StagingRoot 'settings.json'
New-Item -ItemType Directory -Path (Join-Path $script:StagingDir 'inner') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:StagingDir 'inner') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:StagingDir 'inner\a.txt') 'a' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:StagingDir 'inner\a.txt') 'a'
Set-Content -LiteralPath $script:StagingFile 'file-content' Set-Content -Encoding UTF8 -LiteralPath $script:StagingFile 'file-content'
} }
It 'New-BaknretArchiveItem 规范化归档内路径并算出 TopName' { It 'New-BaknretArchiveItem 规范化归档内路径并算出 TopName' {
@@ -611,7 +611,7 @@ Describe '归档项与暂存目录' {
# 文件项:归档内的名字就叫 Cfg(没有扩展名),内容与源文件一致 # 文件项:归档内的名字就叫 Cfg(没有扩展名),内容与源文件一致
$cfg = Get-Item -LiteralPath (Join-Path $stage 'Cfg') -Force $cfg = Get-Item -LiteralPath (Join-Path $stage 'Cfg') -Force
$cfg.PSIsContainer | Should -BeFalse $cfg.PSIsContainer | Should -BeFalse
(Get-Content -LiteralPath $cfg.FullName -Raw).Trim() | Should -Be 'file-content' (Get-Content -Encoding UTF8 -LiteralPath $cfg.FullName -Raw).Trim() | Should -Be 'file-content'
} finally { } finally {
Remove-BaknretArchiveStaging -Root $stage Remove-BaknretArchiveStaging -Root $stage
} }
@@ -734,8 +734,8 @@ Describe '软件名录' {
New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'real app') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'real app') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'other') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'other') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'inc') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:CatRoot 'inc') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:CatRoot 'real app\keep.txt') 'k' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:CatRoot 'real app\keep.txt') 'k'
Set-Content -LiteralPath (Join-Path $script:CatRoot 'file.txt') 'file-content' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:CatRoot 'file.txt') 'file-content'
$script:CatReal = Join-Path $script:CatRoot 'real app' $script:CatReal = Join-Path $script:CatRoot 'real app'
$script:CatOther = Join-Path $script:CatRoot 'other' $script:CatOther = Join-Path $script:CatRoot 'other'
@@ -1090,12 +1090,12 @@ Describe '集成:真实 7z 压缩与排除规则' -Skip:(-not $script:HasSeven
foreach ($directory in 'Default\Cache', 'Default\Code Cache', 'Default\Extensions', 'component_crx_cache', 'Default\IndexedDB') { foreach ($directory in 'Default\Cache', 'Default\Code Cache', 'Default\Extensions', 'component_crx_cache', 'Default\IndexedDB') {
New-Item -ItemType Directory -Path (Join-Path $script:IntegrationPath $directory) -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $script:IntegrationPath $directory) -Force | Out-Null
} }
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'keep.txt') 'keep' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'keep.txt') 'keep'
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'Default\Cache\c.bin') 'c' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'Default\Cache\c.bin') 'c'
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'Default\Code Cache\cc.bin') 'cc' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'Default\Code Cache\cc.bin') 'cc'
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'Default\Extensions\e.bin') 'e' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'Default\Extensions\e.bin') 'e'
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'component_crx_cache\x.bin') 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'component_crx_cache\x.bin') 'x'
Set-Content -LiteralPath (Join-Path $script:IntegrationPath 'Default\IndexedDB\i.bin') 'i' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:IntegrationPath 'Default\IndexedDB\i.bin') 'i'
} }
It '排除规则与空格处理在真实归档上生效' { It '排除规则与空格处理在真实归档上生效' {
@@ -1326,7 +1326,7 @@ Describe '集成:方向标记与孤儿审计' -Skip:(-not ($script:HasSevenZip
$script:DirectionRoot = Join-Path $script:Sandbox 'direction' $script:DirectionRoot = Join-Path $script:Sandbox 'direction'
$script:DirectionSource = Join-Path $script:DirectionRoot 'src\Keep Me' $script:DirectionSource = Join-Path $script:DirectionRoot 'src\Keep Me'
New-Item -ItemType Directory -Path $script:DirectionSource -Force | Out-Null New-Item -ItemType Directory -Path $script:DirectionSource -Force | Out-Null
Set-Content -LiteralPath (Join-Path $script:DirectionSource 'data.txt') 'keep' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $script:DirectionSource 'data.txt') 'keep'
$script:DirectionBackupDir = Join-Path $script:DirectionRoot 'Backups' $script:DirectionBackupDir = Join-Path $script:DirectionRoot 'Backups'
$script:DirectionList = Join-Path $script:DirectionRoot 'list.txt' $script:DirectionList = Join-Path $script:DirectionRoot 'list.txt'
+2 -2
View File
@@ -444,7 +444,7 @@ foreach ($name in $Entries) {
[System.IO.File]::WriteAllText($scratchCatalog, [System.IO.File]::WriteAllText($scratchCatalog,
"@{`n '$catalogKey' = @{`n$($slotLines -join "`n")`n }`n}`n", [System.Text.UTF8Encoding]::new($false)) "@{`n '$catalogKey' = @{`n$($slotLines -join "`n")`n }`n}`n", [System.Text.UTF8Encoding]::new($false))
[System.IO.File]::WriteAllText($scratchList, "$catalogKey`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($scratchList, "$catalogKey`n", [System.Text.UTF8Encoding]::new($true))
[System.IO.File]::WriteAllText($scratchConfig, @" [System.IO.File]::WriteAllText($scratchConfig, @"
@{ @{
BackupDir = '$scratchBackupRoot' BackupDir = '$scratchBackupRoot'
@@ -461,7 +461,7 @@ foreach ($name in $Entries) {
if ($restore.Code -ne 0) { if ($restore.Code -ne 0) {
if ($restore.Log) { if ($restore.Log) {
foreach ($line in @(Get-Content -LiteralPath $restore.Log.FullName -ErrorAction SilentlyContinue | Select-Object -Last 12)) { foreach ($line in @(Get-Content -Encoding UTF8 -LiteralPath $restore.Log.FullName -ErrorAction SilentlyContinue | Select-Object -Last 12)) {
Write-Host (" | {0}" -f $line) -ForegroundColor DarkGray Write-Host (" | {0}" -f $line) -ForegroundColor DarkGray
} }
} }
+19 -19
View File
@@ -74,7 +74,7 @@ function New-E2EConfig {
if ($SoftwareCatalog) { $lines += " SoftwareCatalog = '$SoftwareCatalog'" } if ($SoftwareCatalog) { $lines += " SoftwareCatalog = '$SoftwareCatalog'" }
$lines += " CatalogMaxDepth = $CatalogMaxDepth" $lines += " CatalogMaxDepth = $CatalogMaxDepth"
$lines += '}' $lines += '}'
[System.IO.File]::WriteAllText($Path, ($lines -join "`r`n"), [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($Path, ($lines -join "`r`n"), [System.Text.UTF8Encoding]::new($true))
} }
function Get-NewestLog { function Get-NewestLog {
@@ -141,7 +141,7 @@ $blob = New-Object byte[] 8192
(New-Object System.Random 42).NextBytes($blob) (New-Object System.Random 42).NextBytes($blob)
[System.IO.File]::WriteAllBytes((Join-Path $source 'blob.bin'), $blob) [System.IO.File]::WriteAllBytes((Join-Path $source 'blob.bin'), $blob)
[System.IO.File]::WriteAllText($listPath, "# e2e`n$source :- logs\,!*Cache`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($listPath, "# e2e`n$source :- logs\,!*Cache`n", [System.Text.UTF8Encoding]::new($true))
$expectedHashes = @{} $expectedHashes = @{}
foreach ($relative in 'keep.txt', 'sub\b.txt', 'blob.bin') { foreach ($relative in 'keep.txt', 'sub\b.txt', 'blob.bin') {
@@ -243,7 +243,7 @@ $cfg2 = Join-Path $WorkRoot 'cfg2.psd1'
New-E2EConfig -Path $cfg2 -LogDir $logDir2 New-E2EConfig -Path $cfg2 -LogDir $logDir2
New-Item -ItemType Directory -Path $lockSource -Force | Out-Null New-Item -ItemType Directory -Path $lockSource -Force | Out-Null
Set-Content -LiteralPath (Join-Path $lockSource 'a.txt') -Value 'aaa' -Encoding UTF8 Set-Content -LiteralPath (Join-Path $lockSource 'a.txt') -Value 'aaa' -Encoding UTF8
[System.IO.File]::WriteAllText($lockList, "$lockSource`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($lockList, "$lockSource`n", [System.Text.UTF8Encoding]::new($true))
& $backupScript -BackupListPath $lockList -BackupDir $lockBackupDir -ConfigPath $cfg2 -Force -QuietTool & $backupScript -BackupListPath $lockList -BackupDir $lockBackupDir -ConfigPath $cfg2 -Force -QuietTool
$cleanExitCode = $LASTEXITCODE $cleanExitCode = $LASTEXITCODE
@@ -335,7 +335,7 @@ Set-Content -LiteralPath (Join-Path $incDir 'i.txt') -Value 'included' -Encoding
"@, [System.Text.UTF8Encoding]::new($false)) "@, [System.Text.UTF8Encoding]::new($false))
# 条目级排除用 Slot 前缀点名(AlphaData\plain)+ 任意层级(!*skip);:+ 把 inc 放到归档内 Modules\ # 条目级排除用 Slot 前缀点名(AlphaData\plain)+ 任意层级(!*skip);:+ 把 inc 放到归档内 Modules\
[System.IO.File]::WriteAllText($catList, "my-app :- AlphaData\plain,!*skip :+ Modules:$incDir`ncat-excl`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($catList, "my-app :- AlphaData\plain,!*skip :+ Modules:$incDir`ncat-excl`n", [System.Text.UTF8Encoding]::new($true))
New-E2EConfig -Path $catConfig -LogDir $catLogDir -SoftwareCatalog $catFile New-E2EConfig -Path $catConfig -LogDir $catLogDir -SoftwareCatalog $catFile
$catHashes = @{ $catHashes = @{
@@ -433,7 +433,7 @@ Test-Case '恢复不会把被排除的内容带回来' {
Test-Case '@pathname 覆盖:用名录里的真实路径跑命名算法(独立备份目录,避免污染共享 manifest)' { Test-Case '@pathname 覆盖:用名录里的真实路径跑命名算法(独立备份目录,避免污染共享 manifest)' {
$pathList = Join-Path $catRoot 'list-pathname.txt' $pathList = Join-Path $catRoot 'list-pathname.txt'
$pathNameDir = Join-Path $catRoot 'Backups-pathname' $pathNameDir = Join-Path $catRoot 'Backups-pathname'
[System.IO.File]::WriteAllText($pathList, "my-app @pathname :+ Modules:$settingsFile`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($pathList, "my-app @pathname :+ Modules:$settingsFile`n", [System.Text.UTF8Encoding]::new($true))
# settings.json 刚才被删了,重建一份,让 Include 的宿主机路径存在 # settings.json 刚才被删了,重建一份,让 Include 的宿主机路径存在
Set-Content -LiteralPath $settingsFile -Value '{"slot":"file"}' -Encoding UTF8 Set-Content -LiteralPath $settingsFile -Value '{"slot":"file"}' -Encoding UTF8
$expectedBase = Get-BackupBaseName -RawPath $dirA $expectedBase = Get-BackupBaseName -RawPath $dirA
@@ -455,8 +455,8 @@ $ovrConfig = Join-Path $ovrRoot 'config.psd1'
$ovrExtract = Join-Path $ovrRoot 'verify' $ovrExtract = Join-Path $ovrRoot 'verify'
New-Item -ItemType Directory -Path $ovrTarget -Force | Out-Null New-Item -ItemType Directory -Path $ovrTarget -Force | Out-Null
Set-Content -LiteralPath (Join-Path $ovrTarget 't.txt') -Value 'override-target' -Encoding UTF8 Set-Content -LiteralPath (Join-Path $ovrTarget 't.txt') -Value 'override-target' -Encoding UTF8
[System.IO.File]::WriteAllText($ovrCatalog, "@{`n 'ovr-app' = @{ DefaultData = @{ Path = '$ovrRoot\missing-src' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($ovrCatalog, "@{`n 'ovr-app' = @{ DefaultData = @{ Path = '$ovrRoot\missing-src' } }`n}`n", [System.Text.UTF8Encoding]::new($true))
[System.IO.File]::WriteAllText($ovrList, "ovr-app :: $ovrTarget`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($ovrList, "ovr-app :: $ovrTarget`n", [System.Text.UTF8Encoding]::new($true))
New-E2EConfig -Path $ovrConfig -LogDir (Join-Path $ovrRoot 'logs') -SoftwareCatalog $ovrCatalog New-E2EConfig -Path $ovrConfig -LogDir (Join-Path $ovrRoot 'logs') -SoftwareCatalog $ovrCatalog
& $backupScript -BackupListPath $ovrList -BackupDir $ovrBackupDir -ConfigPath $ovrConfig -Force -QuietTool & $backupScript -BackupListPath $ovrList -BackupDir $ovrBackupDir -ConfigPath $ovrConfig -Force -QuietTool
@@ -471,7 +471,7 @@ Test-Case ':: 覆盖:备份包内容来自被覆盖的路径,且归档项名
$null = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('x', '-bso0', '-bsp0', '-y', "-o$ovrExtract", $ovrArchives[0].FullName) $null = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('x', '-bso0', '-bsp0', '-y', "-o$ovrExtract", $ovrArchives[0].FullName)
} }
Assert-FileExists (Join-Path $ovrExtract 'DefaultData\t.txt') Assert-FileExists (Join-Path $ovrExtract 'DefaultData\t.txt')
Assert-Equal 'override-target' (Get-Content -LiteralPath (Join-Path $ovrExtract 'DefaultData\t.txt') -Raw).Trim() Assert-Equal 'override-target' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $ovrExtract 'DefaultData\t.txt') -Raw).Trim()
} }
Test-Case ':: 覆盖:删掉被覆盖的源后仍能恢复回该路径' { Test-Case ':: 覆盖:删掉被覆盖的源后仍能恢复回该路径' {
@@ -479,7 +479,7 @@ Test-Case ':: 覆盖:删掉被覆盖的源后仍能恢复回该路径' {
& $restoreScript -BackupListPath $ovrList -BackupDir $ovrBackupDir -ConfigPath $ovrConfig -Force & $restoreScript -BackupListPath $ovrList -BackupDir $ovrBackupDir -ConfigPath $ovrConfig -Force
Assert-Equal 0 $LASTEXITCODE Assert-Equal 0 $LASTEXITCODE
Assert-FileExists (Join-Path $ovrTarget 't.txt') Assert-FileExists (Join-Path $ovrTarget 't.txt')
Assert-Equal 'override-target' (Get-Content -LiteralPath (Join-Path $ovrTarget 't.txt') -Raw).Trim() Assert-Equal 'override-target' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $ovrTarget 't.txt') -Raw).Trim()
} }
# ============================================================================ # ============================================================================
@@ -499,10 +499,10 @@ New-Item -ItemType Directory -Path $appA -Force | Out-Null
New-Item -ItemType Directory -Path $appB -Force | Out-Null New-Item -ItemType Directory -Path $appB -Force | Out-Null
Set-Content -LiteralPath (Join-Path $appA 'a.txt') -Value 'dir-a' -Encoding UTF8 Set-Content -LiteralPath (Join-Path $appA 'a.txt') -Value 'dir-a' -Encoding UTF8
Set-Content -LiteralPath (Join-Path $appB 'b.txt') -Value 'dir-b' -Encoding UTF8 Set-Content -LiteralPath (Join-Path $appB 'b.txt') -Value 'dir-b' -Encoding UTF8
[System.IO.File]::WriteAllText($dirCatalog, "@{`n 'app-a' = @{ DefaultData = @{ Path = '$appA' } }`n 'app-b' = @{ DefaultData = @{ Path = '$appB' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($dirCatalog, "@{`n 'app-a' = @{ DefaultData = @{ Path = '$appA' } }`n 'app-b' = @{ DefaultData = @{ Path = '$appB' } }`n}`n", [System.Text.UTF8Encoding]::new($true))
New-E2EConfig -Path $dirConfig -LogDir $dirLogDir -SoftwareCatalog $dirCatalog New-E2EConfig -Path $dirConfig -LogDir $dirLogDir -SoftwareCatalog $dirCatalog
[System.IO.File]::WriteAllText($dirList1, "+ app-a`napp-b`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($dirList1, "+ app-a`napp-b`n", [System.Text.UTF8Encoding]::new($true))
[System.IO.File]::WriteAllText($dirList2, "+ app-a`n- app-b`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($dirList2, "+ app-a`n- app-b`n", [System.Text.UTF8Encoding]::new($true))
& $backupScript -BackupListPath $dirList1 -BackupDir $dirBackupDir -ConfigPath $dirConfig -Force -QuietTool & $backupScript -BackupListPath $dirList1 -BackupDir $dirBackupDir -ConfigPath $dirConfig -Force -QuietTool
Test-Case '行首 + = 仅备份:仍然会被打包' { Test-Case '行首 + = 仅备份:仍然会被打包' {
@@ -539,7 +539,7 @@ Test-Case '恢复端跳过行首 + 的条目、照常恢复 - 的条目' {
Assert-Equal 0 $LASTEXITCODE Assert-Equal 0 $LASTEXITCODE
Assert-FileMissing $appA '行首 + 的条目不该被恢复' Assert-FileMissing $appA '行首 + 的条目不该被恢复'
Assert-FileExists (Join-Path $appB 'b.txt') Assert-FileExists (Join-Path $appB 'b.txt')
Assert-Equal 'dir-b' (Get-Content -LiteralPath (Join-Path $appB 'b.txt') -Raw).Trim() Assert-Equal 'dir-b' (Get-Content -Encoding UTF8 -LiteralPath (Join-Path $appB 'b.txt') -Raw).Trim()
$restoreLog = Get-NewestLog -LogDir $dirLogDir -Prefix 'restore' $restoreLog = Get-NewestLog -LogDir $dirLogDir -Prefix 'restore'
$restoreContent = Get-Content -LiteralPath $restoreLog.FullName -Raw -Encoding UTF8 $restoreContent = Get-Content -LiteralPath $restoreLog.FullName -Raw -Encoding UTF8
@@ -572,8 +572,8 @@ if ($sevenZip) {
-WorkingDirectory $legacyScratch -WorkingDirectory $legacyScratch
} }
[System.IO.File]::WriteAllText($legacyCatalog, "@{`n 'legacy-file' = @{ LegacyData = @{ Path = '$legacyLive' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($legacyCatalog, "@{`n 'legacy-file' = @{ LegacyData = @{ Path = '$legacyLive' } }`n}`n", [System.Text.UTF8Encoding]::new($true))
[System.IO.File]::WriteAllText($legacyList, "legacy-file`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($legacyList, "legacy-file`n", [System.Text.UTF8Encoding]::new($true))
New-E2EConfig -Path $legacyConfig -LogDir $legacyLogDir -SoftwareCatalog $legacyCatalog 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')
@@ -595,7 +595,7 @@ if ($sevenZip) {
Test-Case '旧布局归档:按 <末级名> 回退,把文件还原回原位' { Test-Case '旧布局归档:按 <末级名> 回退,把文件还原回原位' {
Assert-Equal 0 $legacyExitCode Assert-Equal 0 $legacyExitCode
Assert-Equal '{"version":"old-layout"}' (Get-Content -LiteralPath $legacyLive -Raw).Trim() Assert-Equal '{"version":"old-layout"}' (Get-Content -Encoding UTF8 -LiteralPath $legacyLive -Raw).Trim()
$legacyLog = Get-NewestLog -LogDir $legacyLogDir -Prefix 'restore' $legacyLog = Get-NewestLog -LogDir $legacyLogDir -Prefix 'restore'
$legacyContent = Get-Content -LiteralPath $legacyLog.FullName -Raw -Encoding UTF8 $legacyContent = Get-Content -LiteralPath $legacyLog.FullName -Raw -Encoding UTF8
Assert-True ($legacyContent -like '*按旧布局回退*') '回退时必须给出明确告警' Assert-True ($legacyContent -like '*按旧布局回退*') '回退时必须给出明确告警'
@@ -633,7 +633,7 @@ Test-Case 'Backup -DryRun:退出码 0、不写归档也不写 manifest' {
Test-Case '归档名重复时直接报失败,不静默互相覆盖' { Test-Case '归档名重复时直接报失败,不静默互相覆盖' {
$dupList = Join-Path $catRoot 'dup.txt' $dupList = Join-Path $catRoot 'dup.txt'
[System.IO.File]::WriteAllText($dupList, "my-app`nmy-app`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($dupList, "my-app`nmy-app`n", [System.Text.UTF8Encoding]::new($true))
& $backupScript -BackupListPath $dupList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool & $backupScript -BackupListPath $dupList -BackupDir $catBackupDir -ConfigPath $catConfig -Force -QuietTool
Assert-Equal 1 $LASTEXITCODE '重复条目应让退出码为 1' Assert-Equal 1 $LASTEXITCODE '重复条目应让退出码为 1'
} }
@@ -641,14 +641,14 @@ Test-Case '归档名重复时直接报失败,不静默互相覆盖' {
Test-Case '字面路径不受名录影响,仍走路径命名' { Test-Case '字面路径不受名录影响,仍走路径命名' {
$literalList = Join-Path $catRoot 'list-literal.txt' $literalList = Join-Path $catRoot 'list-literal.txt'
$literalDir = Join-Path $catRoot 'Backups-literal' $literalDir = Join-Path $catRoot 'Backups-literal'
[System.IO.File]::WriteAllText($literalList, "$dirA2`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($literalList, "$dirA2`n", [System.Text.UTF8Encoding]::new($true))
& $backupScript -BackupListPath $literalList -BackupDir $literalDir -ConfigPath $catConfig -Force -QuietTool & $backupScript -BackupListPath $literalList -BackupDir $literalDir -ConfigPath $catConfig -Force -QuietTool
Assert-Equal 0 $LASTEXITCODE Assert-Equal 0 $LASTEXITCODE
Assert-FileExists (Join-Path $literalDir ((Get-BackupBaseName -RawPath $dirA2) + '.7z')) Assert-FileExists (Join-Path $literalDir ((Get-BackupBaseName -RawPath $dirA2) + '.7z'))
} }
$missingList = Join-Path $WorkRoot 'missing.txt' $missingList = Join-Path $WorkRoot 'missing.txt'
[System.IO.File]::WriteAllText($missingList, "Z:\definitely-not-here-12345`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($missingList, "Z:\definitely-not-here-12345`n", [System.Text.UTF8Encoding]::new($true))
& $backupScript -BackupListPath $missingList -BackupDir $backupDir -ConfigPath $cfg1 -Force -QuietTool & $backupScript -BackupListPath $missingList -BackupDir $backupDir -ConfigPath $cfg1 -Force -QuietTool
$missingExitCode = $LASTEXITCODE $missingExitCode = $LASTEXITCODE
+30 -30
View File
@@ -385,8 +385,8 @@ Test-Case '归档项的属性集与契约一致(没有旧的 Sources / Dirs /
Test-Case 'New-BaknretArchiveStaging:目录走 junction、文件走硬链接,按归档内名字挂载' { Test-Case 'New-BaknretArchiveStaging:目录走 junction、文件走硬链接,按归档内名字挂载' {
$root = Join-Path $itemSandbox 'stage-src' $root = Join-Path $itemSandbox 'stage-src'
New-Item -ItemType Directory -Path (Join-Path $root 'App') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'App') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $root 'App\f.txt') -Value 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'App\f.txt') -Value 'x'
Set-Content -LiteralPath (Join-Path $root 'settings.json') -Value '{}' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'settings.json') -Value '{}'
$items = @( $items = @(
(New-BaknretArchiveItem -ArchivePath 'AlphaData' -RealPath (Join-Path $root 'App') -Exists $true), (New-BaknretArchiveItem -ArchivePath 'AlphaData' -RealPath (Join-Path $root 'App') -Exists $true),
@@ -412,7 +412,7 @@ Test-Case 'New-BaknretJunction / Remove-BaknretJunction:只删连接点,不
$target = Join-Path $itemSandbox 'j-target' $target = Join-Path $itemSandbox 'j-target'
$link = Join-Path $itemSandbox 'j-link' $link = Join-Path $itemSandbox 'j-link'
New-Item -ItemType Directory -Path $target -Force | Out-Null New-Item -ItemType Directory -Path $target -Force | Out-Null
Set-Content -LiteralPath (Join-Path $target 'keep.txt') -Value 'keep' 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 Assert-Equal 'Junction' (Get-Item -LiteralPath $link -Force).LinkType
@@ -630,7 +630,7 @@ Test-Case '配置缺失时返回默认值' {
Test-Case '配置嵌套段落合并且不丢默认键' { Test-Case '配置嵌套段落合并且不丢默认键' {
$path = Join-Path $sandbox 'cfg.psd1' $path = Join-Path $sandbox 'cfg.psd1'
Set-Content -LiteralPath $path -Value "@{ Encryption = @{ Enabled = `$true }; CompressionLevel = 5 }" 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-Equal 5 $config.CompressionLevel
Assert-True $config.Encryption.Enabled Assert-True $config.Encryption.Enabled
@@ -663,7 +663,7 @@ $configDir = Join-Path $catalogSandbox 'Config'
Test-Case '名录解析:裸键、引号键、带 - 与 . 的名字' { Test-Case '名录解析:裸键、引号键、带 - 与 . 的名字' {
$catPath = Join-Path $catalogSandbox 'SoftwareCatalog.psd1' $catPath = Join-Path $catalogSandbox 'SoftwareCatalog.psd1'
$content = "@{`n FooClolor = @{ DefaultData = @{ Path = '$appDir' } }`n 'scoop-config' = @{ DefaultConfig = @{ Path = '$configDir' } }`n '.ssh' = @{ DefaultData = @{ Path = '$appDir' } }`n}`n" $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($false)) [System.IO.File]::WriteAllText($catPath, $content, [System.Text.UTF8Encoding]::new($true))
$catalog = Get-SoftwareCatalog -Path $catPath -NoCache $catalog = Get-SoftwareCatalog -Path $catPath -NoCache
Assert-Equal 3 $catalog.Count Assert-Equal 3 $catalog.Count
@@ -675,7 +675,7 @@ Test-Case '名录解析:裸键、引号键、带 - 与 . 的名字' {
Test-Case '名录:Slot 按名字排序(哈希表顺序不可依赖)' { Test-Case '名录:Slot 按名字排序(哈希表顺序不可依赖)' {
$catPath = Join-Path $catalogSandbox 'sorted.psd1' $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($false)) [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-SoftwareCatalog -Path $catPath -NoCache
Assert-Equal 'Multi' $catalog['FooClolor'].Kind Assert-Equal 'Multi' $catalog['FooClolor'].Kind
@@ -687,8 +687,8 @@ Test-Case '名录:Slot 按名字排序(哈希表顺序不可依赖)' {
Test-Case '名录:Slot 对象字段与契约一致' { Test-Case '名录:Slot 对象字段与契约一致' {
$catPath = Join-Path $catalogSandbox 'fields.psd1' $catPath = Join-Path $catalogSandbox 'fields.psd1'
$filePath = Join-Path $catalogSandbox 'App\settings.json' $filePath = Join-Path $catalogSandbox 'App\settings.json'
Set-Content -LiteralPath $filePath -Value '{}' 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($false)) [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-SoftwareCatalog -Path $catPath -NoCache
$entry = $catalog['Rich'] $entry = $catalog['Rich']
@@ -721,7 +721,7 @@ Test-Case '名录解析:目录带版本后缀时按前缀补全(legendary_2.
$root = Join-Path $catalogSandbox 'probe' $root = Join-Path $catalogSandbox 'probe'
New-Item -ItemType Directory -Path (Join-Path $root 'legendary_2.0.4') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'legendary_2.0.4') -Force | Out-Null
$catPath = Join-Path $catalogSandbox 'suffix.psd1' $catPath = Join-Path $catalogSandbox 'suffix.psd1'
[System.IO.File]::WriteAllText($catPath, "@{`n legendary = @{ DefaultConfig = @{ Path = '$root\legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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-SoftwareCatalog -Path $catPath -NoCache
Assert-Equal 'Single' $catalog['legendary'].Kind Assert-Equal 'Single' $catalog['legendary'].Kind
@@ -733,7 +733,7 @@ Test-Case '名录解析:不会把 Legendary 误配成 LegendarySomething' {
$root = Join-Path $catalogSandbox 'strict' $root = Join-Path $catalogSandbox 'strict'
New-Item -ItemType Directory -Path (Join-Path $root 'LegendarySomething') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'LegendarySomething') -Force | Out-Null
$catPath = Join-Path $catalogSandbox 'strict.psd1' $catPath = Join-Path $catalogSandbox 'strict.psd1'
[System.IO.File]::WriteAllText($catPath, "@{`n Legendary = @{ DefaultConfig = @{ Path = '$root\Legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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-SoftwareCatalog -Path $catPath -NoCache
Assert-Equal 'Unresolved' $catalog['Legendary'].Kind '必须以下一个字符是 _ 或 - 为界' Assert-Equal 'Unresolved' $catalog['Legendary'].Kind '必须以下一个字符是 _ 或 - 为界'
@@ -759,7 +759,7 @@ Test-Case '名录:一个 Slot 补全出多个目录时明确报错(否则会
New-Item -ItemType Directory -Path (Join-Path $root 'Legendary_1') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Legendary_1') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $root 'Legendary_2') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Legendary_2') -Force | Out-Null
$catPath = Join-Path $catalogSandbox 'multicand.psd1' $catPath = Join-Path $catalogSandbox 'multicand.psd1'
[System.IO.File]::WriteAllText($catPath, "@{`n Legendary = @{ DefaultConfig = @{ Path = '$root\Legendary' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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-SoftwareCatalog -Path $catPath -NoCache
$entry = $catalog['Legendary'] $entry = $catalog['Legendary']
@@ -769,7 +769,7 @@ Test-Case '名录:一个 Slot 补全出多个目录时明确报错(否则会
Test-Case '名录:Slot 写法不对 / 缺少 Path 都记成 Error,而不是崩掉' { Test-Case '名录:Slot 写法不对 / 缺少 Path 都记成 Error,而不是崩掉' {
$bad = Join-Path $catalogSandbox 'bad.psd1' $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($false)) [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-SoftwareCatalog -Path $bad -NoCache
Assert-Equal 'Invalid' $catalog['A'].Kind Assert-Equal 'Invalid' $catalog['A'].Kind
Assert-True ($catalog['A'].Error -like '*Slot S*') Assert-True ($catalog['A'].Error -like '*Slot S*')
@@ -779,12 +779,12 @@ Test-Case '名录:Slot 写法不对 / 缺少 Path 都记成 Error,而不是
Test-Case '名录:裸字符串 / 数组等旧结构会被 ERROR 跳过(不再当目录用)' { Test-Case '名录:裸字符串 / 数组等旧结构会被 ERROR 跳过(不再当目录用)' {
$legacy = Join-Path $catalogSandbox 'legacy.psd1' $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($false)) [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-SoftwareCatalog -Path $legacy -NoCache
Assert-Equal 0 $catalog.Count '旧结构必须被跳过,不能猜成目录' Assert-Equal 0 $catalog.Count '旧结构必须被跳过,不能猜成目录'
$dirs = Join-Path $catalogSandbox 'dirs.psd1' $dirs = Join-Path $catalogSandbox 'dirs.psd1'
[System.IO.File]::WriteAllText($dirs, "@{`n C = @{ Dirs = @('C:\1','C:\2') }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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-SoftwareCatalog -Path $dirs -NoCache
Assert-Equal 1 $catalog.Count Assert-Equal 1 $catalog.Count
Assert-Equal 'Invalid' $catalog['C'].Kind Assert-Equal 'Invalid' $catalog['C'].Kind
@@ -800,8 +800,8 @@ Test-Case '名录:%VAR% 与 $( ... ) 子表达式都会被展开' {
Test-Case '名录:Includes 分文件维护会被合并' { Test-Case '名录:Includes 分文件维护会被合并' {
$extraPath = Join-Path $catalogSandbox 'Extra.psd1' $extraPath = Join-Path $catalogSandbox 'Extra.psd1'
$basePath = Join-Path $catalogSandbox 'Base.psd1' $basePath = Join-Path $catalogSandbox 'Base.psd1'
[System.IO.File]::WriteAllText($extraPath, "@{`n 'extra-app' = @{ D = @{ Path = '$appDir' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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($false)) [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-SoftwareCatalog -Path $basePath -NoCache
Assert-True $catalog.ContainsKey('base-app') Assert-True $catalog.ContainsKey('base-app')
@@ -810,7 +810,7 @@ Test-Case '名录:Includes 分文件维护会被合并' {
Test-Case '名录:结果按文件指纹缓存,-NoCache 强制重读' { Test-Case '名录:结果按文件指纹缓存,-NoCache 强制重读' {
$catPath = Join-Path $catalogSandbox 'cache.psd1' $catPath = Join-Path $catalogSandbox 'cache.psd1'
[System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$appDir' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$appDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true))
$first = Get-SoftwareCatalog -Path $catPath $first = Get-SoftwareCatalog -Path $catPath
$second = Get-SoftwareCatalog -Path $catPath $second = Get-SoftwareCatalog -Path $catPath
@@ -819,7 +819,7 @@ Test-Case '名录:结果按文件指纹缓存,-NoCache 强制重读' {
$fresh = Get-SoftwareCatalog -Path $catPath -NoCache $fresh = Get-SoftwareCatalog -Path $catPath -NoCache
Assert-False ([object]::ReferenceEquals($first, $fresh)) '-NoCache 必须重新读' Assert-False ([object]::ReferenceEquals($first, $fresh)) '-NoCache 必须重新读'
[System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$configDir' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($catPath, "@{`n Cached = @{ D = @{ Path = '$configDir' } }`n}`n", [System.Text.UTF8Encoding]::new($true))
$changed = Get-SoftwareCatalog -Path $catPath $changed = Get-SoftwareCatalog -Path $catPath
Assert-Equal $configDir $changed['Cached'].Slots[0].Resolved '文件变了缓存必须失效' Assert-Equal $configDir $changed['Cached'].Slots[0].Resolved '文件变了缓存必须失效'
} }
@@ -918,7 +918,7 @@ Test-Case '名录里没有该名字:BaseName 退回可读目录名,并给出
Test-Case '名录里的路径不存在时仍给出归档项(恢复要靠它还原回原位)' { Test-Case '名录里的路径不存在时仍给出归档项(恢复要靠它还原回原位)' {
$catPath = Join-Path $resolveSandbox 'gone.psd1' $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($false)) [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' $item = ConvertFrom-BackupListLine -Line 'gone-app'
$resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catPath $resolved = Resolve-BackupEntry -Entry $item -CatalogPath $catPath
Assert-Equal 1 $resolved.Items.Count '恢复端据 Items 还原' Assert-Equal 1 $resolved.Items.Count '恢复端据 Items 还原'
@@ -943,7 +943,7 @@ Test-Case '条目级 :+ 覆盖名录里的 Include(不是叠加)' {
$incDir = Join-Path $resolveSandbox 'Inc' $incDir = Join-Path $resolveSandbox 'Inc'
New-Item -ItemType Directory -Path $incDir -Force | Out-Null New-Item -ItemType Directory -Path $incDir -Force | Out-Null
$catPath = Join-Path $resolveSandbox 'withinc.psd1' $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($false)) [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-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'IncApp') -CatalogPath $catPath
Assert-Equal 2 $plain.Items.Count Assert-Equal 2 $plain.Items.Count
@@ -988,7 +988,7 @@ Test-Case '方向标记随条目一路传到 Resolve-BackupEntry' {
Test-Case '条目级 :- 覆盖名录里的 Exclude(不是叠加)' { Test-Case '条目级 :- 覆盖名录里的 Exclude(不是叠加)' {
$catPath = Join-Path $resolveSandbox 'withexc.psd1' $catPath = Join-Path $resolveSandbox 'withexc.psd1'
[System.IO.File]::WriteAllText($catPath, "@{`n ExcApp = @{ DefaultData = @{ Path = '$dirA'; Exclude = '!*CatalogCache' } }`n}`n", [System.Text.UTF8Encoding]::new($false)) [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-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'ExcApp') -CatalogPath $catPath
Assert-False $plain.HasExcludeOverride Assert-False $plain.HasExcludeOverride
@@ -1018,12 +1018,12 @@ if (-not $sevenZip) {
foreach ($d in 'Default\Cache', 'Default\Code Cache', 'Default\Extensions', 'component_crx_cache', 'Default\IndexedDB') { foreach ($d in 'Default\Cache', 'Default\Code Cache', 'Default\Extensions', 'component_crx_cache', 'Default\IndexedDB') {
New-Item -ItemType Directory -Path (Join-Path $source $d) -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $source $d) -Force | Out-Null
} }
Set-Content -LiteralPath (Join-Path $source 'keep.txt') 'keep' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'keep.txt') 'keep'
Set-Content -LiteralPath (Join-Path $source 'Default\Cache\c.bin') 'c' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'Default\Cache\c.bin') 'c'
Set-Content -LiteralPath (Join-Path $source 'Default\Code Cache\cc.bin') 'cc' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'Default\Code Cache\cc.bin') 'cc'
Set-Content -LiteralPath (Join-Path $source 'Default\Extensions\e.bin') 'e' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'Default\Extensions\e.bin') 'e'
Set-Content -LiteralPath (Join-Path $source 'component_crx_cache\x.bin') 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source 'component_crx_cache\x.bin') 'x'
Set-Content -LiteralPath (Join-Path $source 'Default\IndexedDB\i.bin') 'i' 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') $patterns = @('!*Cache', 'component_crx_cache', 'Default\Code Cache', 'Default\Extensions', 'Default\IndexedDB')
$archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'path' -Exists $true $archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'path' -Exists $true
@@ -1054,7 +1054,7 @@ if (-not $sevenZip) {
$source = Join-Path $root $itemName $source = Join-Path $root $itemName
foreach ($d in 'Cache1', 'Cache2', 'Keep', 'sub\Cache3') { foreach ($d in 'Cache1', 'Cache2', 'Keep', 'sub\Cache3') {
New-Item -ItemType Directory -Path (Join-Path $source $d) -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $source $d) -Force | Out-Null
Set-Content -LiteralPath (Join-Path $source "$d\x.bin") 'x' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $source "$d\x.bin") 'x'
} }
$archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'slot' -Exists $true $archiveItem = New-BaknretArchiveItem -ArchivePath $itemName -RealPath $source -Kind 'slot' -Exists $true
@@ -1096,8 +1096,8 @@ if (-not $sevenZip) {
$root = Join-Path $sandbox 'topnames' $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 'Alpha') -Force | Out-Null
New-Item -ItemType Directory -Path (Join-Path $root 'Beta') -Force | Out-Null New-Item -ItemType Directory -Path (Join-Path $root 'Beta') -Force | Out-Null
Set-Content -LiteralPath (Join-Path $root 'Alpha\a.txt') 'a' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'Alpha\a.txt') 'a'
Set-Content -LiteralPath (Join-Path $root 'Beta\b.txt') 'b' Set-Content -Encoding UTF8 -LiteralPath (Join-Path $root 'Beta\b.txt') 'b'
$archive = Join-Path $sandbox 'topnames.7z' $archive = Join-Path $sandbox 'topnames.7z'
$code = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $archive, 'Alpha', 'Beta') -WorkingDirectory $root $code = Invoke-ExternalCommand -FilePath $sevenZip -ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $archive, 'Alpha', 'Beta') -WorkingDirectory $root
Assert-Equal 0 $code Assert-Equal 0 $code
+1 -1
View File
@@ -117,7 +117,7 @@ $conflicts = @()
$seenNew = @{} $seenNew = @{}
$entries = @() $entries = @()
foreach ($line in (Get-Content -LiteralPath $BackupListPath)) { foreach ($line in (Get-Content -Encoding UTF8 -LiteralPath $BackupListPath)) {
$item = ConvertFrom-BackupListLine -Line $line $item = ConvertFrom-BackupListLine -Line $line
if (-not $item) { continue } if (-not $item) { continue }
+1 -1
View File
@@ -267,7 +267,7 @@ switch ($Verb) {
while ($sw.Elapsed.TotalMinutes -lt 30) { while ($sw.Elapsed.TotalMinutes -lt 30) {
if (Test-LabGuestReady) { if (Test-LabGuestReady) {
Write-LabLog ("VM 已就绪(等待 {0} 分钟)" -f [math]::Round($sw.Elapsed.TotalMinutes, 1)) 'STEP' Write-LabLog ("VM 已就绪(等待 {0} 分钟)" -f [math]::Round($sw.Elapsed.TotalMinutes, 1)) 'STEP'
$facts = Invoke-LabCommand -ScriptBlock { Get-Content 'C:\BakNRet-Lab\state\provisioned.json' -Raw } $facts = Invoke-LabCommand -ScriptBlock { Get-Content -Encoding UTF8 'C:\BakNRet-Lab\state\provisioned.json' -Raw }
Write-Host $facts Write-Host $facts
break break
} }
+1 -1
View File
@@ -224,7 +224,7 @@ function Wait-LabProvision {
while ($sw.Elapsed.TotalMinutes -lt 30) { while ($sw.Elapsed.TotalMinutes -lt 30) {
if (Test-LabGuestReady) { if (Test-LabGuestReady) {
Write-LabLog "供给完成,耗时 $([math]::Round($sw.Elapsed.TotalMinutes,1)) 分钟" 'STEP' Write-LabLog "供给完成,耗时 $([math]::Round($sw.Elapsed.TotalMinutes,1)) 分钟" 'STEP'
$facts = Invoke-LabCommand -ScriptBlock { Get-Content 'C:\BakNRet-Lab\state\provisioned.json' -Raw } $facts = Invoke-LabCommand -ScriptBlock { Get-Content -Encoding UTF8 'C:\BakNRet-Lab\state\provisioned.json' -Raw }
Write-Host $facts Write-Host $facts
return return
} }
+3 -3
View File
@@ -340,7 +340,7 @@ $listPath = Join-Path $WorkRoot 'BackupList.txt'
$entries = @() $entries = @()
if ($vscodeReady) { $entries += $vscodeApp; $entries += $vscodePersist } if ($vscodeReady) { $entries += $vscodeApp; $entries += $vscodePersist }
$entries += $bData $entries += $bData
[System.IO.File]::WriteAllText($listPath, (($entries -join [Environment]::NewLine) + [Environment]::NewLine), [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($listPath, (($entries -join [Environment]::NewLine) + [Environment]::NewLine), [System.Text.UTF8Encoding]::new($true))
$configPath = Join-Path $WorkRoot 'BackupConfig.psd1' $configPath = Join-Path $WorkRoot 'BackupConfig.psd1'
$configText = @" $configText = @"
@@ -359,7 +359,7 @@ $configText = @"
DefaultExcludes = @('!Thumbs.db', '!desktop.ini') DefaultExcludes = @('!Thumbs.db', '!desktop.ini')
} }
"@ "@
[System.IO.File]::WriteAllText($configPath, $configText, [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($configPath, $configText, [System.Text.UTF8Encoding]::new($true))
Write-Host '' Write-Host ''
Write-Host '[备份] Backup.ps1' -ForegroundColor Yellow Write-Host '[备份] Backup.ps1' -ForegroundColor Yellow
@@ -406,7 +406,7 @@ if ($vscodeReady) {
$settingsOk = $false $settingsOk = $false
if ($settingsPath -and (Test-Path -LiteralPath $settingsPath)) { if ($settingsPath -and (Test-Path -LiteralPath $settingsPath)) {
$settingsOk = (Get-Content -LiteralPath $settingsPath -Raw) -match [regex]::Escape($probe) $settingsOk = (Get-Content -Encoding UTF8 -LiteralPath $settingsPath -Raw) -match [regex]::Escape($probe)
} }
Test-Scenario 'A: 改过的 settings.json 被原样恢复(读得到、内容对)' $settingsOk $settingsPath Test-Scenario 'A: 改过的 settings.json 被原样恢复(读得到、内容对)' $settingsOk $settingsPath