Автоматизация задач Windows через PowerShell и Task Scheduler — АйТи Фреш

Автоматизация задач Windows через PowerShell и Task Scheduler

Зачем управлять Task Scheduler через PowerShell

Task Scheduler — встроенный компонент Windows, который запускает скрипты и программы по расписанию или в ответ на события. Через графический интерфейс настроить пару задач — не проблема. Но когда серверов двадцать, тридцать, пятьдесят — кликать по GUI становится просто невыносимо. Модуль PowerShell ScheduledTasks решает это радикально: создаёшь, меняешь и мониторишь задачи программно — в скриптах, конвейерах, DSC-конфигурациях.

Что мы чаще всего автоматизируем через Task Scheduler у клиентов:

  • Ежедневная очистка старых логов и временных файлов — диски на серверах не резиновые
  • Перезапуск зависших служб по расписанию, пока до них не добрались пользователи
  • Сбор метрик и отправка отчётов руководству или в мониторинг
  • Запуск скриптов при входе пользователя или старте системы
  • Реакция на события в Event Log — аварии, блокировки, критические ошибки

Создание запланированных задач через PowerShell

Модуль ScheduledTasks доступен начиная с Windows 8 и Server 2012. Любая задача собирается из трёх кубиков: Action (что делаем), Trigger (когда делаем) и Principal (от чьего имени). Разобрался один раз — дальше по шаблону.

Базовый пример: очистка логов

Вот живой пример — задача, которая каждую ночь в 03:00 удаляет логи старше 30 дней:

# Определяем действие — запуск PowerShell-скрипта
$action = New-ScheduledTaskAction `
    -Execute "powershell.exe" `
    -Argument '-NoProfile -ExecutionPolicy Bypass -File "C:\Scripts\Clean-OldLogs.ps1"' `
    -WorkingDirectory "C:\Scripts"

# Определяем триггер — ежедневно в 03:00
$trigger = New-ScheduledTaskTrigger -Daily -At "03:00"

# Определяем принципал — от имени SYSTEM
$principal = New-ScheduledTaskPrincipal `
    -UserId "SYSTEM" `
    -LogonType ServiceAccount `
    -RunLevel Highest

# Настройки задачи
$settings = New-ScheduledTaskSettingsSet `
    -AllowStartIfOnBatteries `
    -DontStopIfGoingOnBatteries `
    -StartWhenAvailable `
    -RunOnlyIfNetworkAvailable `
    -ExecutionTimeLimit (New-TimeSpan -Hours 1) `
    -RestartCount 3 `
    -RestartInterval (New-TimeSpan -Minutes 5)

# Регистрация задачи
Register-ScheduledTask `
    -TaskName "Clean-OldLogs" `
    -TaskPath "\IT\Maintenance\" `
    -Action $action `
    -Trigger $trigger `
    -Principal $principal `
    -Settings $settings `
    -Description "Удаление логов старше 30 дней"

Параметр -TaskPath создаёт отдельную папку в планировщике — это сильно помогает, когда задач накапливается несколько десятков. -StartWhenAvailable — на практике очень полезный флаг: если сервер в три ночи был выключен или перезагружался, задача отработает сразу после старта, а не пропустит цикл.

Скрипт очистки логов

Содержимое файла C:\Scripts\Clean-OldLogs.ps1:

# Clean-OldLogs.ps1
$logPaths = @(
    "C:\inetpub\logs\LogFiles",
    "C:\Windows\Temp",
    "D:\Apps\Logs"
)
$daysToKeep = 30
$cutoffDate = (Get-Date).AddDays(-$daysToKeep)
$totalDeleted = 0

foreach ($path in $logPaths) {
    if (Test-Path $path) {
        $files = Get-ChildItem -Path $path -Recurse -File |
            Where-Object { $_.LastWriteTime -lt $cutoffDate }
        $count = ($files | Measure-Object).Count
        $size = ($files | Measure-Object -Property Length -Sum).Sum / 1MB
        $files | Remove-Item -Force -ErrorAction SilentlyContinue
        Write-Output "$path : удалено $count файлов ($([math]::Round($size, 2)) МБ)"
        $totalDeleted += $count
    }
}

Write-Output "Итого удалено: $totalDeleted файлов"

# Запись в Event Log
New-EventLog -LogName Application -Source "LogCleanup" -ErrorAction SilentlyContinue
Write-EventLog -LogName Application -Source "LogCleanup" -EventId 1000 `
    -EntryType Information -Message "Очистка логов: удалено $totalDeleted файлов"

Типы триггеров и комбинации

PowerShell закрывает все типы триггеров, которые есть в Task Scheduler: по времени, по событию в системе, при входе конкретного пользователя, при простое машины. Практически любой сценарий покрывается.

Примеры различных триггеров

Примеры триггеров под разные задачи:

# Еженедельно — каждый понедельник и четверг в 22:00
$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday,Thursday -At "22:00"

# При запуске системы (с задержкой 5 минут)
$trigger = New-ScheduledTaskTrigger -AtStartup
$trigger.Delay = 'PT5M'  # 5 минут задержки

# При входе конкретного пользователя
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "DOMAIN\admin"

# Повтор каждые 15 минут в течение рабочего дня
$trigger = New-ScheduledTaskTrigger -Once -At "08:00" `
    -RepetitionInterval (New-TimeSpan -Minutes 15) `
    -RepetitionDuration (New-TimeSpan -Hours 10)

# По событию в Event Log (блокировка учётной записи)
$trigger = New-ScheduledTaskTrigger -AtLogOn  # Placeholder
$CIMTriggerClass = Get-CimClass -ClassName MSFT_TaskEventTrigger `
    -Namespace Root/Microsoft/Windows/TaskScheduler
$trigger = New-CimInstance -CimClass $CIMTriggerClass -ClientOnly
$trigger.Subscription = @"
<QueryList>
  <Query Id="0" Path="Security">
    <Select Path="Security">*[System[EventID=4740]]</Select>
  </Query>
</QueryList>
"@
$trigger.Enabled = $true

Комбинация нескольких триггеров

Кстати, одна задача может работать сразу с несколькими триггерами — например, и по расписанию, и по событию:

# Задача запускается и по расписанию, и при старте системы
$triggers = @(
    (New-ScheduledTaskTrigger -Daily -At "06:00"),
    (New-ScheduledTaskTrigger -AtStartup)
)

Register-ScheduledTask `
    -TaskName "Health-Check" `
    -TaskPath "\IT\Monitoring\" `
    -Action $action `
    -Trigger $triggers `
    -Principal $principal

Управление существующими задачами

После того как задача создана, жизнь на этом не заканчивается. PowerShell даёт полный контроль: смотреть статусы, менять параметры, запускать и останавливать задачи вручную — всё из консоли.

Просмотр и фильтрация задач

Базовые команды для работы с уже существующими задачами:

# Все задачи в папке
Get-ScheduledTask -TaskPath "\IT\*" | Format-Table TaskName, State, TaskPath

# Информация о конкретной задаче
Get-ScheduledTask -TaskName "Clean-OldLogs" | Get-ScheduledTaskInfo
# LastRunTime  : 08.04.2024 3:00:05
# LastTaskResult : 0  (успех)
# NextRunTime  : 09.04.2024 3:00:00

# Все задачи, которые завершились с ошибкой
Get-ScheduledTask | Get-ScheduledTaskInfo |
    Where-Object { $_.LastTaskResult -ne 0 -and $_.LastTaskResult -ne $null } |
    Select-Object TaskName, LastRunTime, LastTaskResult

# Запуск задачи вручную
Start-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\"

# Отключение задачи
Disable-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\"

# Удаление задачи
Unregister-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\" -Confirm:$false

Модификация параметров задачи

Как поменять расписание или действие у задачи, которая уже висит в планировщике:

# Изменение триггера — перенос на 04:00
$newTrigger = New-ScheduledTaskTrigger -Daily -At "04:00"
Set-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\" -Trigger $newTrigger

# Изменение учётной записи запуска
Set-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\" `
    -User "DOMAIN\svc-tasks" -Password "ServicePassword123!"

# Добавление описания
$task = Get-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\"
$task.Description = "Удаление логов старше 30 дней. Контакт: admin@example.com"
$task | Set-ScheduledTask

Продвинутые сценарии автоматизации

Теперь — несколько сценариев прямо из нашей практики. Именно такие задачи мы настраиваем клиентам чаще всего.

Мониторинг дискового пространства

Скрипт мониторинга свободного места на дисках с отправкой уведомления, если порог превышен:

# Monitor-DiskSpace.ps1
$threshold = 10  # процентов
$alertRecipients = "admin@example.com"
$smtpServer = "mail.example.com"

$volumes = Get-Volume | Where-Object {
    $_.DriveLetter -and $_.DriveType -eq 'Fixed'
}

$alerts = @()
foreach ($vol in $volumes) {
    $freePercent = [math]::Round(($vol.SizeRemaining / $vol.Size) * 100, 1)
    if ($freePercent -lt $threshold) {
        $freeGB = [math]::Round($vol.SizeRemaining / 1GB, 1)
        $alerts += "Диск $($vol.DriveLetter): — $freePercent% свободно ($freeGB ГБ)"
    }
}

if ($alerts.Count -gt 0) {
    $body = "Сервер: $env:COMPUTERNAME`n`n" + ($alerts -join "`n")
    Send-MailMessage -From "monitoring@example.com" -To $alertRecipients `
        -Subject "[ALERT] Мало места на $env:COMPUTERNAME" `
        -Body $body -SmtpServer $smtpServer -Encoding UTF8
}

Перезапуск зависших служб

Скрипт для автоматического перезапуска критичных служб, если они упали:

# Restart-StuckServices.ps1
$criticalServices = @("W3SVC", "MSSQLSERVER", "Spooler")
$restartedServices = @()

foreach ($svcName in $criticalServices) {
    $svc = Get-Service -Name $svcName -ErrorAction SilentlyContinue
    if ($svc -and $svc.Status -ne 'Running') {
        try {
            Restart-Service -Name $svcName -Force -ErrorAction Stop
            $restartedServices += "$svcName — перезапущен"
            Write-EventLog -LogName Application -Source "ServiceMonitor" `
                -EventId 2000 -EntryType Warning `
                -Message "Служба $svcName была перезапущена автоматически"
        } catch {
            $restartedServices += "$svcName — ОШИБКА: $($_.Exception.Message)"
        }
    }
}

if ($restartedServices.Count -gt 0) {
    # Уведомление (email или webhook)
    $body = "Перезапуск служб на $env:COMPUTERNAME :`n" + ($restartedServices -join "`n")
    Send-MailMessage -From "monitoring@example.com" -To "admin@example.com" `
        -Subject "Перезапуск служб на $env:COMPUTERNAME" `
        -Body $body -SmtpServer "mail.example.com" -Encoding UTF8
}

Развёртывание задач на нескольких серверах

Когда серверов много — вручную заходить на каждый бессмысленно. Здесь в игру вступает PowerShell Remoting: разворачиваешь задачу сразу на всём парке за один прогон скрипта.

Массовое создание задач через Invoke-Command

Скрипт для одновременного развёртывания задачи на всех серверах из списка:

# Deploy-ScheduledTask.ps1
$servers = Get-Content "C:\Scripts\servers.txt"
$scriptContent = Get-Content "C:\Scripts\Clean-OldLogs.ps1" -Raw

Invoke-Command -ComputerName $servers -ScriptBlock {
    param($script)

    # Копируем скрипт на целевой сервер
    $scriptPath = "C:\Scripts\Clean-OldLogs.ps1"
    if (-not (Test-Path "C:\Scripts")) { New-Item -Path "C:\Scripts" -ItemType Directory }
    Set-Content -Path $scriptPath -Value $script -Encoding UTF8

    # Создаём задачу
    $action = New-ScheduledTaskAction -Execute "powershell.exe" `
        -Argument "-NoProfile -ExecutionPolicy Bypass -File $scriptPath"
    $trigger = New-ScheduledTaskTrigger -Daily -At "03:00"
    $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
    $settings = New-ScheduledTaskSettingsSet -StartWhenAvailable

    # Удаляем существующую задачу если есть
    Unregister-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\" `
        -Confirm:$false -ErrorAction SilentlyContinue

    Register-ScheduledTask -TaskName "Clean-OldLogs" -TaskPath "\IT\Maintenance\" `
        -Action $action -Trigger $trigger -Principal $principal -Settings $settings

    Write-Output "$env:COMPUTERNAME : задача создана"
} -ArgumentList $scriptContent

Мониторинг и отчёты по задачам

Развернуть задачи — полдела. Нужно ещё понимать, что они реально отрабатывают. Мы обычно делаем централизованный отчёт по всем серверам сразу.

Скрипт сбора статуса задач

Скрипт собирает статус всех задач из папки \IT\ со всех серверов и сводит в одну таблицу:

# Get-TaskReport.ps1
$servers = Get-Content "C:\Scripts\servers.txt"

$report = Invoke-Command -ComputerName $servers -ScriptBlock {
    Get-ScheduledTask -TaskPath "\IT\*" | ForEach-Object {
        $info = $_ | Get-ScheduledTaskInfo
        [PSCustomObject]@{
            Server       = $env:COMPUTERNAME
            TaskName     = $_.TaskName
            State        = $_.State
            LastRun      = $info.LastRunTime
            LastResult   = $info.LastTaskResult
            NextRun      = $info.NextRunTime
            ResultText   = if ($info.LastTaskResult -eq 0) { "OK" } else { "ERROR" }
        }
    }
}

# Фильтр: только ошибки
$errors = $report | Where-Object { $_.LastResult -ne 0 }
if ($errors) {
    $errors | Format-Table Server, TaskName, LastRun, LastResult -AutoSize
}

# Экспорт в CSV
$report | Export-Csv -Path "C:\Reports\TaskReport_$(Get-Date -Format yyyyMMdd).csv" `
    -NoTypeInformation -Encoding UTF8

Часто задаваемые вопросы

Учётку для запуска задач задаём параметрами -User и -Password при регистрации: Register-ScheduledTask ... -User "DOMAIN\svc-user" -Password "Pass123!". Учтите: у этой учётки должно быть право «Log on as a batch job» — настраивается через Local Security Policy → User Rights Assignment. Если запускаете от SYSTEM — пароль не нужен вообще.

Код 0x1 — «Incorrect function» — одна из самых частых ошибок, с которыми приходят клиенты. Причина почти всегда одна из трёх: путь к powershell.exe указан не полностью, в аргументе -File кривой путь к скрипту или скрипт вообще не существует по этому адресу. Ещё бывают синтаксические ошибки в самом скрипте — и задача молча падает. Первое, что добавляем при диагностике: Start-Transcript -Path C:\Logs\task.log в начало скрипта. Лог сразу показывает, где именно всё сломалось.

Да, через Group Policy Preferences (Computer Configuration → Preferences → Control Panel Settings → Scheduled Tasks) задачи создавать можно. Мы сами так делали лет десять назад. Но GPP-задачи заметно ограничены: не все типы триггеров доступны, с параметрами — мучение. Для чего-то простого сойдёт, но если сценарий сложнее «запустить bat-файл» — берите PowerShell-скрипт и вешайте его через GPO Startup Script.

Аудит включается через групповую политику: Computer Configuration → Policies → Windows Settings → Security Settings → Advanced Audit Policy → Object Access → Audit Other Object Access Events. После этого все изменения задач пишутся в Security Event Log — Event ID 4698 (создание), 4699 (удаление), 4702 (изменение). В каждом событии видно пользователя, имя задачи и полный XML с конфигурацией. Очень помогает, когда кто-то что-то «случайно» поменял.

Нужна помощь с настройкой?

Если нужно внедрить автоматизацию под конкретные задачи вашего бизнеса — специалисты АйТи Фреш разберутся и настроят. Более 15 лет в IT-аутсорсинге, обслуживание от 15 000 ₽/мес.

📞 Связаться с нами
#PowerShell Task Scheduler#запланированные задачи Windows#автоматизация PowerShell#New-ScheduledTask#планировщик задач#скрипты по расписанию#Windows автоматизация#ScheduledTasks модуль
Комментарии 0

Оставить комментарий

загрузка...

Подпишитесь на рассылку ITfresh

Каждую неделю мы выпускаем практические гайды для руководителей IT и системных администраторов. Это не просто теория! Здесь вы найдёте всё: безопасность, 1С, миграции, резервные копии и проверенные лайфхаки из наших реальных проектов.

Реквизиты оператора персональных данных

ООО «АЙТИ-ФРЕШ», ИНН 7719418495, КПП 771901001. Юридический адрес: 105523, г. Москва, Щёлковское шоссе, д. 92, корп. 7. Контакт: info@itfresh.ru, +7 903 729-62-41. Оператор обрабатывает e-mail подписчика в целях рассылки информационных и рекламных материалов до момента отзыва согласия.