三件事:
1) tools\Install-TestDependencies.ps1 现在也把 PSScriptAnalyzer 装进仓库内的 .tools\modules
(不动机器上的全局模块,与 Pester 同一策略)。
2) PSScriptAnalyzerSettings.psd1:这是必要的,不是装饰 —— 那 6 条格式规则
(括号、缩进、空格、对齐、大小写)默认全是 Disabled,所以不带 -Settings 的
`Invoke-ScriptAnalyzer -Severity Warning,Error` 会**静默漏掉全部排版问题**。本文件用 Rules
把它们打开(而不是用 IncludeRules 换一套),于是默认规则与格式规则同时生效。
三条有意的排除都写明了理由:PSAvoidUsingWriteHost(彩色控制台输出是这份工具的刻意设计)、
PSUseShouldProcessForStateChangingFunctions(WhatIf 的边界在入口脚本,给库里 27 个改状态的
函数都加上反而会"静默跳过",备份看着成功却什么都没做)、PSAvoidUsingPlainTextForPassword
(7z 只接受命令行口令,这是 7z 的限制,README 里写明了取舍)。
3) tools\Invoke-Analyzer.ps1:独立门禁(不塞进 Pester 用例 —— 套件跑一次二十多秒,
混进去会让"测试红了"这句话失去分辨力),路径过滤与验收门槛的 Encode/Parse 两层一致。
全仓重排结果:706 条告警 -> 67 条。修掉的 639 条全部是格式(闭括号 168、空格 80、
对齐 68、缩进 60、行长 229)。重排后 9/9 验收全绿、100 个文件两版解析零错、
276 个断言原样通过 —— 机械重排没有改变任何可观察行为。
如实说明两件事:
* 行长上限设成 160,**不是**官方默认的 120。120 在本仓库意味着 270 处改动(主要是
中文注释与测试夹具里的一行式目录),160 意味着 41 处。160 仍是"宽但可读",而理由是写在
配置文件里的:这不是悄悄放宽,想收紧到 120 时那份清单就在分析器输出里。
* 剩余 67 条里,41 条是上面那批行长,其余 26 条是分析器找出的真问题(未使用参数 6、
空 catch 6、MD5 指纹 1、覆盖内置命令 1、switch 默认值 1 等)。其中
Find-ChildDirectoryByName 的 MaxDepth 参数从未被使用 —— 也就是配置里的
CatalogMaxDepth = 5 是假的,前缀补全实际只查 1 层。这条要改行为、且影响真实名录的解析
结果,留给你拍板,不在本提交里动手。
127 lines
4.3 KiB
PowerShell
127 lines
4.3 KiB
PowerShell
<#
|
||
.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
|