PowerShell 技能连载 - 计划任务管理

适用于 PowerShell 5.1 及以上版本(Windows),部分操作需要管理员权限

Windows 计划任务(Task Scheduler)是自动化运维的基石,系统维护脚本、定时备份、健康检查、日志清理等都依赖它来定时执行。在早期,管理计划任务需要使用 schtasks.exe 命令行工具,参数繁多且语法晦涩。PowerShell 3.0 引入了 ScheduledTasks 模块,提供了面向对象的任务管理方式,可以精确控制触发条件、执行账户、重试策略等高级配置。

本文将介绍计划任务的创建、管理和监控自动化。

任务创建与配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 创建基本计划任务
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\DailyBackup.ps1"

$trigger = New-ScheduledTaskTrigger -Daily -At "02:00"

$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-DontStopOnIdleEnd `
-ExecutionTimeLimit (New-TimeSpan -Hours 2) `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 5)

$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

Register-ScheduledTask -TaskName "DailyBackup" `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal `
-Description "每日凌晨 2:00 执行数据库备份"

Write-Host "已创建计划任务:DailyBackup" -ForegroundColor Green

# 创建多触发器任务
$actions = @(
New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -File C:\Scripts\HealthCheck.ps1"
)

$triggers = @(
(New-ScheduledTaskTrigger -Daily -At "06:00")
(New-ScheduledTaskTrigger -AtStartup)
)

Register-ScheduledTask -TaskName "ServerHealthCheck" `
-Action $actions `
-Trigger $triggers `
-Settings $settings `
-Principal $principal `
-Description "每日 6:00 及开机时检查服务器健康状态"

Write-Host "已创建计划任务:ServerHealthCheck" -ForegroundColor Green

# 创建每周任务
$weeklyTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At "03:00"
Register-ScheduledTask -TaskName "WeeklyReport" `
-Action (New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -File C:\Scripts\WeeklyReport.ps1") `
-Trigger $weeklyTrigger `
-Principal $principal `
-Description "每周日凌晨 3:00 生成周报"

Write-Host "已创建计划任务:WeeklyReport" -ForegroundColor Green

执行结果示例:

1
2
3
已创建计划任务:DailyBackup
已创建计划任务:ServerHealthCheck
已创建计划任务:WeeklyReport

任务查询与监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 查询所有计划任务
$tasks = Get-ScheduledTask | Where-Object {
$_.TaskPath -notlike "\Microsoft\*" -and $_.State -ne "Disabled"
}

$tasks | Select-Object TaskName, State,
@{N='LastRun'; E={ ($_. | Get-ScheduledTaskInfo).LastRunTime.ToString('yyyy-MM-dd HH:mm:ss') }},
@{N='LastResult'; E={ ($_. | Get-ScheduledTaskInfo).LastTaskResult }},
@{N='NextRun'; E={ ($_. | Get-ScheduledTaskInfo).NextRunTime.ToString('yyyy-MM-dd HH:mm:ss') }} |
Format-Table -AutoSize

# 查询失败的任务
$failed = Get-ScheduledTask | ForEach-Object {
$info = $_ | Get-ScheduledTaskInfo
if ($info.LastTaskResult -ne 0 -and $_.State -ne "Disabled") {
[PSCustomObject]@{
TaskName = $_.TaskName
State = $_.State
LastRun = $info.LastRunTime.ToString('yyyy-MM-dd HH:mm:ss')
ResultCode = $info.LastTaskResult
Description = $_.Description
}
}
}

if ($failed) {
Write-Host "发现失败的任务:" -ForegroundColor Red
$failed | Format-Table -AutoSize
} else {
Write-Host "所有任务执行正常" -ForegroundColor Green
}

# 任务运行时间统计
Get-ScheduledTask | Where-Object { $_.TaskPath -notlike "\Microsoft\*" } |
ForEach-Object {
$info = $_ | Get-ScheduledTaskInfo
[PSCustomObject]@{
TaskName = $_.TaskName
LastRun = $info.LastRunTime
NextRun = $info.NextRunTime
}
} | Sort-Object LastRun -Descending | Format-Table -AutoSize

执行结果示例:

1
2
3
4
5
6
7
TaskName           State  LastRun            LastResult NextRun
-------- ----- ------- ---------- -------
DailyBackup Ready 2025-09-03 02:00:00 0 2025-09-04 02:00:00
ServerHealthCheck Ready 2025-09-03 06:00:15 0 2025-09-04 06:00:00
WeeklyReport Ready 2025-09-01 03:00:00 0 2025-09-08 03:00:00

所有任务执行正常

批量任务管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 批量注册计划任务的函数
function Register-MaintenanceTasks {
param(
[string]$ScriptsPath = "C:\Scripts",
[string]$LogFile = "C:\Logs\TaskSetup.log"
)

$taskDefs = @(
@{
Name = "DiskCleanup"
Script = "DiskCleanup.ps1"
Schedule = "Weekly"
Time = "03:00"
Day = "Saturday"
Description = "每周六凌晨 3:00 清理磁盘临时文件"
}
@{
Name = "LogRotation"
Script = "LogRotation.ps1"
Schedule = "Daily"
Time = "00:30"
Description = "每日凌晨 0:30 轮转日志文件"
}
@{
Name = "CertExpiryCheck"
Script = "CertExpiryCheck.ps1"
Schedule = "Daily"
Time = "08:00"
Description = "每日 8:00 检查证书到期情况"
}
@{
Name = "SecurityScan"
Script = "SecurityScan.ps1"
Schedule = "Weekly"
Time = "04:00"
Day = "Sunday"
Description = "每周日凌晨 4:00 运行安全扫描"
}
)

$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -DontStopOnIdleEnd `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) -RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 10)

$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest

foreach ($def in $taskDefs) {
$scriptPath = Join-Path $ScriptsPath $def.Script

if (-not (Test-Path $scriptPath)) {
Write-Host "脚本不存在:$scriptPath" -ForegroundColor Red
continue
}

$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`""

if ($def.Schedule -eq "Daily") {
$trigger = New-ScheduledTaskTrigger -Daily -At $def.Time
} else {
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek $def.Day -At $def.Time
}

# 检查任务是否已存在
$existing = Get-ScheduledTask -TaskName $def.Name -ErrorAction SilentlyContinue
if ($existing) {
Set-ScheduledTask -TaskName $def.Name -Action $action -Trigger $trigger -Settings $settings
Write-Host "已更新任务:$($def.Name)" -ForegroundColor Yellow
} else {
Register-ScheduledTask -TaskName $def.Name -Action $action `
-Trigger $trigger -Settings $settings -Principal $principal `
-Description $def.Description
Write-Host "已创建任务:$($def.Name)" -ForegroundColor Green
}

"$((Get-Date).ToString('yyyy-MM-dd HH:mm:ss')) $($def.Name) registered" | Out-File $LogFile -Append
}
}

Register-MaintenanceTasks

# 远程服务器批量部署任务
$servers = @("SRV01", "SRV02", "SRV03")

foreach ($server in $servers) {
try {
Invoke-Command -ComputerName $server -ScriptBlock {
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
-Argument "-NoProfile -File C:\Scripts\HealthCheck.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "06:00"
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable

$existing = Get-ScheduledTask -TaskName "HealthCheck" -ErrorAction SilentlyContinue
if ($existing) {
Set-ScheduledTask -TaskName "HealthCheck" -Action $action -Trigger $trigger
} else {
Register-ScheduledTask -TaskName "HealthCheck" -Action $action `
-Trigger $trigger -Settings $settings `
-Principal (New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest)
}
} -ErrorAction Stop
Write-Host "$server :任务部署成功" -ForegroundColor Green
} catch {
Write-Host "$server :部署失败 - $($_.Exception.Message)" -ForegroundColor Red
}
}

执行结果示例:

1
2
3
4
5
6
7
已创建任务:DiskCleanup
已创建任务:LogRotation
已创建任务:CertExpiryCheck
已创建任务:SecurityScan
SRV01 :任务部署成功
SRV02 :任务部署成功
SRV03 :任务部署成功

注意事项

  1. 执行账户:SYSTEM 账户无法访问网络资源,需要网络访问的任务应使用 gMSA(组托管服务账户)
  2. 执行策略:脚本文件可能受执行策略限制,任务中使用 -ExecutionPolicy Bypass 参数绕过
  3. 时区问题:计划任务的触发时间基于服务器本地时区,分布式部署时注意时区差异
  4. 任务超时:默认执行时间限制为 72 小时,建议根据实际需求设置合理的超时时间
  5. 日志记录:脚本内部应实现日志输出,方便排查任务失败原因
  6. 并发冲突:如果任务可能重叠执行,使用文件锁或数据库锁防止并发冲突

PowerShell 技能连载 - 定时任务与计划任务

适用于 PowerShell 5.1 及以上版本(Windows)

自动化运维的核心是定时执行——每天凌晨备份数据库、每周清理临时文件、每小时检查服务状态、每月生成报表。Windows 计划任务(Task Scheduler)是实现定时执行的基础设施,而 PowerShell 的 ScheduledTasks 模块提供了完整的计划任务管理能力,可以替代传统的 GUI 操作和 schtasks.exe 命令行工具。

本文将讲解计划任务的创建、管理、高级触发器配置,以及常见的自动化任务模板。

基础任务管理

使用 ScheduledTasks 模块管理计划任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 导入模块(Windows 8/Server 2012+ 内置)
Import-Module ScheduledTasks

# 查看所有计划任务
Get-ScheduledTask | Where-Object State -ne 'Disabled' |
Select-Object TaskName, TaskPath, State |
Sort-Object TaskName |
Format-Table -AutoSize

# 查看特定任务详情
$task = Get-ScheduledTask -TaskName "MyBackupTask"
$task | Select-Object TaskName, State, Description
$task.Triggers | Format-List
$task.Actions | Format-List

# 查看任务运行历史
Get-ScheduledTaskInfo -TaskName "MyBackupTask" |
Select-Object TaskName, LastRunTime, LastTaskResult,
NextRunTime, NumberOfMissedRuns |
Format-List

# 启用/禁用任务
Enable-ScheduledTask -TaskName "MyBackupTask"
Disable-ScheduledTask -TaskName "MyBackupTask"

# 手动启动任务
Start-ScheduledTask -TaskName "MyBackupTask"

执行结果示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
TaskName              TaskPath           State
-------- -------- -----
MyBackupTask \CustomTasks\ Ready
GoogleUpdateTask \Google\ Ready
OneDrive Reporting \Microsoft\ Running

TaskName : MyBackupTask
State : Ready
Description : 每日数据库备份

LastRunTime : 2025-05-26 02:00:00
LastTaskResult : 0
NextRunTime : 2025-05-27 02:00:00
NumberOfMissedRuns: 0

创建基本计划任务

创建计划任务需要三个核心组件:触发器(何时运行)、操作(运行什么)和设置(运行条件):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# 创建每日备份任务
$action = New-ScheduledTaskAction `
-Execute "PowerShell.exe" `
-Argument "-NoProfile -ExecutionPolicy Bypass -File C:\Scripts\Backup-Database.ps1" `
-WorkingDirectory "C:\Scripts"

$trigger = New-ScheduledTaskTrigger -Daily -At "02:00AM"

$settings = New-ScheduledTaskSettingsSet `
-StartWhenAvailable `
-DontStopOnIdleEnd `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-ExecutionTimeLimit (New-TimeSpan -Hours 4)

$taskParams = @{
TaskName = "Daily-Database-Backup"
TaskPath = "\CustomTasks\"
Action = $action
Trigger = $trigger
Settings = $settings
Description = "每日凌晨 2 点执行数据库备份"
RunLevel = "Highest"
User = "SYSTEM"
}

Register-ScheduledTask @taskParams -Force
Write-Host "计划任务已创建:Daily-Database-Backup" -ForegroundColor Green

执行结果示例:

1
2
3
TaskName              TaskPath           State
-------- -------- -----
Daily-Database-Backup \CustomTasks\ Ready

注意-RunLevel Highest 表示以最高权限运行。如果任务需要管理员权限,使用 SYSTEM 账户或指定管理员账户。

多种触发器类型

计划任务支持多种触发器,可以满足复杂的调度需求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# 每周触发(周一至周五)
$weekdayTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Tuesday,Wednesday,Thursday,Friday -At "06:00AM"

# 每月触发(每月 1 日和 15 日)
$monthlyTrigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "09:00AM"
# 注意:PowerShell 的 New-ScheduledTaskTrigger 不直接支持每月特定日期
# 需要通过 XML 或 COM 对象配置

# 使用 COM 对象创建每月触发器
$taskService = New-Object -ComObject Schedule.Service
$taskService.Connect()
$taskFolder = $taskService.GetFolder("\")
$taskDef = $taskService.NewTask(0)

$monthlyTrigger = $taskDef.Triggers.Create(4) # 4 = Monthly trigger
$monthlyTrigger.DaysOfMonth = 1 -bor 15 # 1 日和 15 日
$monthlyTrigger.MonthsOfYear = 0xFFF # 所有月份
$monthlyTrigger.StartBoundary = "2025-01-01T09:00:00"
$monthlyTrigger.Enabled = $true

# 启动时触发
$bootTrigger = New-ScheduledTaskTrigger -AtStartup

# 用户登录时触发
$logonTrigger = New-ScheduledTaskTrigger -AtLogOn

# 一次性触发
$onceTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddHours(2)

# 重复触发(每 15 分钟一次,持续 1 天)
$repeatTrigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 15) `
-RepetitionDuration (New-TimeSpan -Days 1)

执行结果示例:

1
# 触发器创建无输出,需注册后查看

常见自动化任务模板

以下是几个实用的计划任务模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# 模板一:每日清理临时文件
$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -Command `
`Get-ChildItem 'C:\Temp' -Recurse -File | `
Where-Object { `$_.LastWriteTime -lt (Get-Date).AddDays(-7) } | `
Remove-Item -Force -Recurse`"

Register-ScheduledTask -TaskName "Cleanup-TempFiles" `
-TaskPath "\CustomTasks\" `
-Action $action `
-Trigger (New-ScheduledTaskTrigger -Daily -At "03:00AM") `
-Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable) `
-User "SYSTEM" -RunLevel Highest -Force

# 模板二:每周检查磁盘空间并发邮件
$diskCheckScript = @'
$threshold = 10 # GB
$disks = Get-CimInstance Win32_LogicalDisk -Filter "DriveType=3"
$lowSpace = $disks | Where-Object { ($_.FreeSpace / 1GB) -lt $threshold }

if ($lowSpace) {
$body = $lowSpace | ForEach-Object {
"驱动器 $($_.DeviceID) 可用空间:$([math]::Round($_.FreeSpace/1GB, 2)) GB"
}
Send-MailMessage -To "admin@contoso.com" -From "monitor@contoso.com" `
-Subject "磁盘空间告警" -Body ($body -join "`n") `
-SmtpServer "mail.contoso.com"
}
'@

Set-Content -Path "C:\Scripts\Check-DiskSpace.ps1" -Value $diskCheckScript

$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -File C:\Scripts\Check-DiskSpace.ps1"

Register-ScheduledTask -TaskName "Weekly-DiskSpace-Check" `
-TaskPath "\CustomTasks\" `
-Action $action `
-Trigger (New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At "08:00AM") `
-User "SYSTEM" -RunLevel Highest -Force

# 模板三:每小时检查服务状态
$serviceCheckScript = @'
$services = @('W3SVC', 'MSSQLSERVER', 'WinRM')
foreach ($svc in $services) {
$status = Get-Service -Name $svc -ErrorAction SilentlyContinue
if ($status -and $status.Status -ne 'Running') {
Write-Warning "服务 $svc 状态异常:$($status.Status)"
# 可以添加自动重启逻辑
Start-Service -Name $svc -ErrorAction SilentlyContinue
}
}
'@

Set-Content -Path "C:\Scripts\Check-Services.ps1" -Value $serviceCheckScript

$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -File C:\Scripts\Check-Services.ps1"

$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Hours 1) `
-RepetitionDuration (New-TimeSpan -Days 365)

Register-ScheduledTask -TaskName "Hourly-Service-Check" `
-TaskPath "\CustomTasks\" `
-Action $action `
-Trigger $trigger `
-Settings (New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries) `
-User "SYSTEM" -RunLevel Highest -Force

Write-Host "所有计划任务已创建" -ForegroundColor Green

执行结果示例:

1
所有计划任务已创建

批量部署计划任务

在多台服务器上部署相同的计划任务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
function Deploy-ScheduledTask {
<#
.SYNOPSIS
在远程服务器上部署计划任务
#>
param(
[Parameter(Mandatory)]
[string[]]$ComputerName,

[string]$ScriptPath = "C:\Scripts\Check-Services.ps1",
[string]$TaskName = "Service-Monitor"
)

foreach ($computer in $ComputerName) {
Write-Host "部署到:$computer" -ForegroundColor Cyan

# 复制脚本到远程
$session = New-PSSession -ComputerName $computer
Copy-Item -Path $ScriptPath -Destination "C:\Scripts\" -ToSession $session -Force

# 在远程创建计划任务
Invoke-Command -Session $session -ScriptBlock {
param($taskName, $scriptPath)

$action = New-ScheduledTaskAction -Execute "PowerShell.exe" `
-Argument "-NoProfile -File $scriptPath"

$trigger = New-ScheduledTaskTrigger -Daily -At "06:00AM"
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable

Register-ScheduledTask -TaskName $taskName `
-Action $action -Trigger $trigger -Settings $settings `
-User "SYSTEM" -RunLevel Highest -Force | Out-Null

Write-Host " 已注册任务:$taskName" -ForegroundColor Green
} -ArgumentList $TaskName, "C:\Scripts\$(Split-Path $ScriptPath -Leaf)"

Remove-PSSession $session
}
}

# 部署到所有 Web 服务器
Deploy-ScheduledTask -ComputerName @('SRV-WEB01', 'SRV-WEB02', 'SRV-WEB03') `
-TaskName "Service-Monitor"

执行结果示例:

1
2
3
4
5
6
部署到:SRV-WEB01
已注册任务:Service-Monitor
部署到:SRV-WEB02
已注册任务:Service-Monitor
部署到:SRV-WEB03
已注册任务:Service-Monitor

注意事项

  1. 执行策略:计划任务中运行 PowerShell 脚本时,使用 -ExecutionPolicy Bypass 参数绕过执行策略限制
  2. 路径使用绝对路径:计划任务的工作目录可能与交互式会话不同,始终使用绝对路径
  3. SYSTEM 账户限制:SYSTEM 账户没有网络访问权限,需要访问网络资源时应使用 gMSA 或服务账户
  4. 超时设置:为长时间运行的任务设置合理的 ExecutionTimeLimit,避免任务无限挂起
  5. 日志记录:计划任务中的脚本应将输出写入日志文件,便于排查问题
  6. 错过执行处理-StartWhenAvailable 设置确保因关机等原因错过的任务在下次启动时补执行