Task Scheduler через PowerShell: создаём, управляем и развёртываем задачи на парке серверов
Я Семёнов Евгений Сергеевич, директор АйТи Фреш. За 15+ лет на серверах у нас на клиентских парках работают сотни scheduled tasks: ежедневные бэкапы, отчёты по AD, чистки логов, синхронизации с 1С, выгрузки в ФНС. И я всегда настаиваю, чтобы задачи создавались из PowerShell — через модуль ScheduledTasks, а не кликами в GUI. Потому что скрипт-создание: воспроизводимо, читается как документация, деплоится на 50 серверов за минуту, сохраняется в git. В этой статье — как я сам настраиваю планировщик на клиентских серверах, включая сервисные учётки gMSA и массовое развёртывание.
Модуль ScheduledTasks
В Windows Server 2012+ и Windows 8+ есть встроенный модуль ScheduledTasks. Набор команд:
| Командлет | Что делает |
|---|---|
| New-ScheduledTaskAction | Описание, что выполнять (powershell.exe + аргументы) |
| New-ScheduledTaskTrigger | Когда запускать (Daily, Weekly, AtLogon, AtStartup) |
| New-ScheduledTaskPrincipal | От какой учётки, с какими правами |
| New-ScheduledTaskSettingsSet | Настройки: ExecutionTimeLimit, RestartOnFailure и т.д. |
| Register-ScheduledTask | Создать и зарегистрировать задачу |
| Get-ScheduledTask | Просмотр задач |
| Start-ScheduledTask | Запустить сейчас |
| Export-ScheduledTask / Register-ScheduledTask -Xml | Экспорт/импорт в XML |
Базовый пример
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -NonInteractive -ExecutionPolicy Bypass -File "C:\Scripts\Reports\Daily-AD.ps1"' `
-WorkingDirectory 'C:\Scripts\Reports'
$trigger = New-ScheduledTaskTrigger -Daily -At 7:30am
$principal = New-ScheduledTaskPrincipal `
-UserId 'CORP\svc_scripts' `
-LogonType Password `
-RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-ExecutionTimeLimit (New-TimeSpan -Hours 1) `
-RestartCount 2 `
-RestartInterval (New-TimeSpan -Minutes 5)
Register-ScheduledTask -TaskName 'Daily-AD-Report' `
-TaskPath '\ITFresh\' `
-Action $action -Trigger $trigger -Principal $principal -Settings $settings `
-Description 'Ежедневный отчёт по неактивным AD-учёткам в 7:30' `
-Force
При регистрации задач с LogonType Password нужно передать пароль:
$pwd = Read-Host -AsSecureString 'Password for svc_scripts'
Register-ScheduledTask ... -User 'CORP\svc_scripts' -Password (
[Runtime.InteropServices.Marshal]::PtrToStringAuto(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)))
Группа Managed Service Account (gMSA)
Пароли в Task Scheduler — боль: их нужно где-то хранить, менять каждые 3 месяца, обновлять на всех серверах. gMSA решает это: AD сам ротирует пароль каждые 30 дней, в задаче хранится только имя учётки.
# На DC — создание KDS root key (один раз на лес)
Add-KdsRootKey -EffectiveTime ((Get-Date).AddHours(-10))
# Создаём gMSA с доступом для конкретных серверов
New-ADServiceAccount -Name gmsa-scripts -DNSHostName gmsa-scripts.corp.itfresh.local `
-PrincipalsAllowedToRetrieveManagedPassword (Get-ADComputer 'srv-task01')
# На целевом сервере
Install-WindowsFeature RSAT-AD-PowerShell
Install-ADServiceAccount gmsa-scripts
Test-ADServiceAccount gmsa-scripts # должно вернуть True
# В задаче используем учётку без пароля
$principal = New-ScheduledTaskPrincipal -UserId 'CORP\gmsa-scripts$' `
-LogonType Password -RunLevel Highest
Register-ScheduledTask ... -Principal $principal
Я всегда использую gMSA для задач, которым нужен доступ к другим серверам: чтение AD, бэкап SQL, передача файлов по SMB. Пароль без нас никто не узнает, потому что его не существует в явном виде.
Триггеры: все варианты
# Ежедневно в 07:30
New-ScheduledTaskTrigger -Daily -At '07:30'
# Еженедельно по вторникам и четвергам в 22:00
New-ScheduledTaskTrigger -Weekly -DaysOfWeek Tuesday,Thursday -At '22:00'
# Ежемесячно 1-го числа в 04:00 (через XML или advanced)
$t = New-ScheduledTaskTrigger -Once -At '04:00'
# + advanced XML редактирование для монтли
# Каждые 15 минут круглосуточно
$t = New-ScheduledTaskTrigger -Once -At (Get-Date) `
-RepetitionInterval (New-TimeSpan -Minutes 15) `
-RepetitionDuration (New-TimeSpan -Days 3650)
# При запуске системы
New-ScheduledTaskTrigger -AtStartup
# При логоне конкретного пользователя
New-ScheduledTaskTrigger -AtLogOn -User 'CORP\operator1'
# По событию в Event Log
$trigger = New-CimInstance -ClassName MSFT_TaskEventTrigger `
-Namespace Root/Microsoft/Windows/TaskScheduler -ClientOnly `
-Property @{ Subscription = ' ' }
Мини-кейс: массовая миграция legacy-задач
Май 2025, клиент — холдинг из 6 юрлиц, 94 Windows-сервера в разных филиалах. На каждом — от 3 до 15 scheduled tasks, созданных руками за последние 10 лет разными админами. Пароли зашиты, некоторые под domain admin, половина с expired credentials. Задача — привести в порядок, централизовать, перевести на gMSA.
Что сделали за месяц:
- Инвентаризация: скрипт собрал все задачи с 94 серверов в один CSV, нашли 612 task, из них 87 с ошибкой «Last Result not 0x0», 23 под учётками уволившихся.
- Каталогизация: присвоили каждой задаче тип (Backup, Report, Cleanup, Sync) и определили подходящую gMSA.
- Создали 5 gMSA (gmsa-backup, gmsa-report, gmsa-cleanup, gmsa-sync, gmsa-monitor).
- Переписали XML каждой задачи: убрали пароли, подставили gMSA, исправили -NoProfile и -ExecutionPolicy Bypass, обновили пути.
- Выкатили через Invoke-Command на все серверы.
- Сервер автоматизации — Dell PowerEdge R750 с Xeon Platinum 8280, 64 ГБ RAM, NVMe 4x1.92 ТБ, 40G Mellanox до файл-сервера в дата-центре МТС.
Результат: 612 задач, 0 с зашитыми паролями, 0 под личными учётками, все с логированием в централизованный EventLog. За следующие 6 месяцев — ни одного инцидента «задача упала из-за смены пароля». Стоимость работ 210 тыс. руб.
Экспорт/импорт XML
Для массового развёртывания удобно экспортировать задачу в XML и разложить её по 50 серверам:
# На эталонном сервере
$xml = Export-ScheduledTask -TaskName 'Daily-AD-Report' -TaskPath '\ITFresh\'
Set-Content -Path '\\file\share\tasks\Daily-AD-Report.xml' -Value $xml -Encoding UTF8
# Массовый импорт
$servers = Get-Content .\task-servers.txt
$xml = Get-Content '\\file\share\tasks\Daily-AD-Report.xml' -Raw
foreach ($s in $servers) {
Invoke-Command -ComputerName $s -ScriptBlock {
Register-ScheduledTask -Xml $using:xml -TaskName 'Daily-AD-Report' `
-TaskPath '\ITFresh\' -Force
}
}
Мониторинг выполнения задач
# Все задачи с их последним результатом
Get-ScheduledTask -TaskPath '\ITFresh\*' |
Get-ScheduledTaskInfo |
Select-Object TaskName, LastRunTime,
@{N='LastResult';E={ '0x{0:X8}' -f $_.LastTaskResult }},
NextRunTime
# Задачи, упавшие в последний раз
Get-ScheduledTask | Get-ScheduledTaskInfo |
Where-Object { $_.LastTaskResult -ne 0 -and $_.LastTaskResult -ne 267009 } |
Format-Table TaskName, LastRunTime, LastTaskResult
# Проход по всем серверам через WinRM
$report = Invoke-Command -ComputerName (Get-ADComputer -Filter { OperatingSystem -like '*Server*' }).DNSHostName -ScriptBlock {
Get-ScheduledTask -TaskPath '\ITFresh\*' | Get-ScheduledTaskInfo |
Select-Object @{N='Host';E={ $env:COMPUTERNAME }}, TaskName, LastRunTime, LastTaskResult
}
$report | Where-Object LastTaskResult -ne 0 |
Export-Excel -Path .\failed-tasks.xlsx -AutoSize
Логирование и алерты
Я всегда добавляю в задачи:
- Start-Transcript или PSFramework в самом скрипте — чтобы был текстовый лог каждого запуска.
- Запись в Event Log в конце — успех или ошибка с номером EventId.
- Alertmanager/Telegram alert на любые ошибки: Prometheus exporter читает Event Log или CSV с LastTaskResult.
- Ежедневный сводный отчёт — сколько задач сработало, сколько упало, в какое время. Шлю email админам.
Типичные ошибки
- Пароль в явном виде в XML — XML-файл часто лежит в git, и там пароль. Всегда используйте gMSA или SecretManagement.
- Забытый -NonInteractive — скрипт ждёт ввода и висит часами.
- Относительные пути в скриптах — задача запускается из C:\Windows\System32, файл не найден. WorkingDirectory обязателен.
- ExecutionPolicy у пользователя — на сервере включен AllSigned, а ваш скрипт неподписан. -ExecutionPolicy Bypass решает.
- Run only when user is logged on — для фоновых задач всегда должно быть «Run whether user is logged on or not».
- Нет ExecutionTimeLimit — скрипт может зависнуть на сутки. Ставьте 1–2 часа максимум.
Наведём порядок в ваших задачах Windows
Проведу аудит всех scheduled tasks на ваших серверах, переведу на gMSA, добавлю логирование и алерты, напишу централизованный отчёт. От 10 серверов до сотен узлов, 15+ лет опыта с корпоративными Windows-инфраструктурами. Выделенный сервер автоматизации — Dell с Xeon Platinum 8280 и 40G Mellanox в дата-центре МТС, если нужно.
Телефон: +7 903 729-62-41
Telegram: @ITfresh_Boss
Семёнов Евгений Сергеевич, директор АйТи Фреш
FAQ — частые вопросы по Scheduled Tasks
- От какой учётки запускать задачи?
- Сервисная учётка с минимальными правами. Для регулярных задач в домене — gMSA (Group Managed Service Account), пароль ротирует сам AD. Под SYSTEM — только для системных задач, не для скриптов с доступом к сети.
- Как правильно указать аргумент PowerShell?
- powershell.exe -NoProfile -ExecutionPolicy Bypass -File 'C:\Scripts\daily.ps1'. Если параметры — через -Command. Не забудьте полный путь к скрипту и -NoProfile для скорости.
- Как задать запуск каждые 15 минут круглосуточно?
- New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes 15) -RepetitionDuration (New-TimeSpan -Days 3650). Или через -Daily + повтор в advanced-настройках.
- Почему задача помечена Running, но ничего не делает?
- Типичная причина — скрипт ждёт ввода или упёрся в неинтерактивную сессию. Добавьте -NonInteractive и -WindowStyle Hidden. Проверьте права запуска и, что скрипт не требует user interaction.
- Как массово развернуть задачу на 50 серверов?
- Экспортировать в XML (Export-ScheduledTask), через Invoke-Command импортировать на каждом. Или через DSC ресурс ScheduledTask из ComputerManagementDsc. Или через GPO Preferences — Scheduled Tasks.
