Files
BakNRet/tools/Register-BackupTask.ps1
T
Shuery 4e3c0461c0 refactor: 入口改名(Backup/Restore -> Backup-Data/Restore-Data)+ 只留一轮的垫片
git mv 保留历史。旧名字留薄垫片:入口脚本是**外部接口**(README 二十多处引用、使用者的肌肉记忆、注册脚本里的路径),内部实现改名断了会当场报错,外部接口改名断了是静默没用 —— 后者对备份工具尤其不能接受(ADR-0012)。

垫片踩到四个坑,全部由门禁报出(E2E 与 Pester 集成用例本来就是通过子进程调这两个入口的,于是它们原封不动成了垫片的验收):① `& script.ps1` 里子脚本的 exit 不会把退出码传到父脚本的 $LASTEXITCODE —— 会把失败变成成功,而计划任务靠退出码判断成败;② 调用方传 -Verbose 时垫片里的 Import-Module 会多打一行加载信息,顶掉测试的输出断言;③ 不重定向时子进程的输出到不了调用方(父进程的 stdout 常常是管道,而 .NET 起的进程默认只继承控制台)—— 改成显式重定向 + 异步读转发(同步先读 stdout 再读 stderr 会在管道写满时死锁);④ **`[CmdletBinding()]` 会把 -Verbose 当通用参数绑走,它不会落进 $Rest,于是没被转发给子进程** —— 而测试正是靠 -Verbose 拿那条 VERBOSE 级日志的。现在显式把 -Verbose / -Debug 加进转发参数。

参数引号化用模块自己的 ConvertTo-BakNRetNativeArgumentString(5.1 上 ProcessStartInfo.ArgumentList 不存在)。tools\Register-BackupTask.ps1 已同步改指 Backup-Data.ps1(4 处)。

门禁新增一条机械检查:**源码里不得出现"左边空的赋值"** —— 它是"用双引号拼代码导致 $变量 被插值成空"那个坑的指纹(合法语法、Parse 层抓不到、只有跑到才炸,同一块代码里出现过五次)。垫片全部用单引号 here-string 生成,零插值。

验收:test.ps1 9/9 全绿(5.1 与 7);真实清单只读冒烟 4/4。
2026-09-27 19:51:24 +08:00

127 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-Data.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-Data.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-Data.ps1'
if (-not (Test-Path -LiteralPath $backupScript)) {
Write-Error "找不到 Backup-Data.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