Files
BakNRet/tools/Register-BackupTask.ps1
T
Shuery dbc0c00554 重构为可核对、可恢复的备份工具(P0-P3)
修复(P0)
- 退出码:改用 .NET Process 继承控制台启动外部命令。Start-Process -PassThru 的
  ExitCode 在 PowerShell 7.7.0-preview.4 上恒为 $null,会把成功的压缩判成失败,
  并让 "exit 2 -> 删档重试" 的自愈分支永远不可达。
- BackupList.txt 解析:先按第一个 :: 切开再处理引号,修正整行被引号包住时
  排除表被吞进路径的问题(该条目此前被静默跳过,其 2.8 GB 归档成了孤儿)。
- 排除分隔符同时接受 , 与 ;:此前解析器只认 ; 而清单里写的是 ,,
  等于所有排除规则都没生效。
- 7z 排除参数不再嵌引号,含空格的模式自动转成 ?:旧写法 -x!"路径" 会让引号
  成为模式的一部分,导致排除对所有条目都失效。

加固(P1)
- 先写临时归档 -> 7z t 校验 -> 原子替换,中断不再污染正式归档。
- 放弃 7z 的更新模式 u:固实压缩下收益极小,却让排除规则改动与已删文件
  永远进不了归档。
- 新增 Backups/manifest.json 与 logs/*.log,跳过/失败有据可查。
- 结尾按失败数 exit;恢复支持 -WhatIf / -DryRun / -VerifyOnly / -Only。
- 恢复优先用 manifest 定位归档,并精确比较 BaseName(不再用 -Filter 通配)。
- 修正 tar 分支用 $LASTEXITCODE 判断成功与否的缺陷。
- 有警告(文件被占用)时拒绝用不完整的归档覆盖完整归档,需显式 -AcceptWarnings。

策略与安全(P2)
- Edge 条目加排除规则:解压后 4.22 GB 中 3.79 GB 是可再生的缓存/遥测/扩展本体,
  保留书签、密码、偏好、历史与站点数据。
- 可选 7z 加密(@encrypt 标记或全局开关),取不到口令时明确失败,绝不写明文。
- 磁盘空间守卫:放不下就跳过该条目,低于阈值告警。

工程化(P3)
- 新增 BackupConfig.psd1、README.md、.gitignore。
- tests/Run-Tests.ps1(32 项)与 tests/Run-E2E.ps1(16 项端到端验收)。
- tools/Register-BackupTask.ps1 注册每日计划任务。
- 归档命名算法保持不变,已有归档不会失联。
2026-09-21 20:10:18 +08:00

125 lines
4.3 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<#
.SYNOPSIS
注册 / 移除 BakNRet 的每日备份计划任务。
.DESCRIPTION
任务直接调用 Backup.ps1。脚本自身会写日志并按失败数返回退出码,
因此「上次运行结果」在任务计划程序里是可读的,不需要额外包装。
注册计划任务需要管理员权限(本脚本不自己提权,请从管理员终端运行)。
.EXAMPLE
# 先看将要注册什么(只读,不需要管理员)
.\tools\Register-BackupTask.ps1 -At '21:30' -DryRun
.EXAMPLE
.\tools\Register-BackupTask.ps1 -At '21:30'
.EXAMPLE
.\tools\Register-BackupTask.ps1 -Remove
#>
[CmdletBinding(SupportsShouldProcess = $true)]
param(
[string]$TaskName = 'BakNRet Backup',
# 每日触发时间,HH:mm
[ValidatePattern('^\d{1,2}:\d{2}$')]
[string]$At = '21:30',
# 额外传给 Backup.ps1 的参数,例如 @('-Snapshot')
[string[]]$BackupArgument = @(),
[ValidateSet('S4U', 'Interactive')]
[string]$LogonType = 'S4U',
# 用哪个 PowerShell 宿主执行(默认优先 pwsh.exe)
[string]$PowerShellPath,
[switch]$Remove,
[switch]$DryRun
)
$ErrorActionPreference = 'Stop'
$projectRoot = Split-Path -Parent $PSScriptRoot
$backupScript = Join-Path $projectRoot 'Backup.ps1'
if (-not (Test-Path -LiteralPath $backupScript)) {
Write-Error "找不到 Backup.ps1:$backupScript"
exit 1
}
if (-not $PowerShellPath) {
$pwsh = Get-Command pwsh.exe -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty Source
$PowerShellPath = if ($pwsh) { $pwsh } else { (Get-Command powershell.exe -ErrorAction Stop).Source }
}
$argumentParts = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', "`"$backupScript`"") + $BackupArgument
$argument = ($argumentParts -join ' ')
Write-Host ''
Write-Host "任务名 : $TaskName"
Write-Host "执行程序 : $PowerShellPath"
Write-Host "参数 : $argument"
Write-Host "触发 : 每天 $At"
Write-Host "登录类型 : $LogonType(S4U = 未登录也运行,但访问不到网络资源)"
Write-Host ''
if ($Remove) {
if ($DryRun) {
Write-Host '[试运行] 将移除计划任务' -ForegroundColor Yellow
exit 0
}
if (-not (Get-Command Unregister-ScheduledTask -ErrorAction SilentlyContinue)) {
Write-Error '当前环境没有计划任务 cmdlet。'
exit 1
}
try {
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction Stop
Write-Host "已移除计划任务:$TaskName" -ForegroundColor Green
} catch {
Write-Error "移除失败(多半是权限不足,请用管理员终端):$_"
exit 1
}
exit 0
}
if ($DryRun) {
Write-Host '[试运行] 未实际注册。去掉 -DryRun 并确保在管理员终端里执行即可注册。' -ForegroundColor Yellow
exit 0
}
foreach ($cmdlet in 'Register-ScheduledTask', 'New-ScheduledTaskTrigger', 'New-ScheduledTaskAction', 'New-ScheduledTaskSettingsSet', 'New-ScheduledTaskPrincipal') {
if (-not (Get-Command $cmdlet -ErrorAction SilentlyContinue)) {
Write-Error "当前环境缺少 $cmdlet,无法注册计划任务。"
exit 1
}
}
$action = New-ScheduledTaskAction -Execute $PowerShellPath -Argument $argument -WorkingDirectory $projectRoot
$trigger = New-ScheduledTaskTrigger -Daily -At $At
$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-MultipleInstances IgnoreNew `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-ExecutionTimeLimit (New-TimeSpan -Hours 6)
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$principal = New-ScheduledTaskPrincipal -UserId $identity -LogonType $LogonType -RunLevel Highest
try {
Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger `
-Settings $settings -Principal $principal -Force -ErrorAction Stop | Out-Null
Write-Host "已注册计划任务:$TaskName(每天 $At)" -ForegroundColor Green
Write-Host "查看上次运行结果:Get-ScheduledTaskInfo -TaskName '$TaskName'" -ForegroundColor DarkGray
Write-Host "手动跑一次验证 :Start-ScheduledTask -TaskName '$TaskName'" -ForegroundColor DarkGray
} catch {
Write-Error "注册失败(多半是权限不足,请用管理员终端):$_"
exit 1
}
exit 0