Public/Set-AtmdScheduledTask.ps1
function Set-AtmdScheduledTask { <# .SYNOPSIS Создает задачу в Планировщике заданий Windows. .DESCRIPTION Создает задачу на основании представленных параметрв в виде объектов / хеш таблиц (см. пример). .PARAMETER BaseParams Объект, содержащий имя, путь и описание задачи. .PARAMETER Actions Объект, содержащий массив выполняемых действий. .PARAMETER Triggers Объект, содержащий массив триггеров задачи. .PARAMETER Settings Объект, содержащий перечень параметров задачи (см. пример). .PARAMETER Security Объект, содержащий информацию об имени пользователя и пароле для запуска задачи, если требуется запускать задачу от имени другого пользователя. .PARAMETER Force Set-AtmdScheduledTask создает задачу, если она не существует. Чтобы пересоздать существующую, необходимо указать параметр -Force. .PARAMETER PassThru Используйте параметр -PassThru для того, чтобы функция вернула CIMInstance созданной задачи. .EXAMPLE $BaseParams = @{ name = "Имя задачи" taskPath = "\ATMD User Tasks\ATMD-ilichev\" description = "Описание задачи" } $Action = @{ commandToExecute = "C:\Tools\BGInfo\Bginfo.exe" workingDirectory = "C:\Tools" } $Trigger = @{ periodicity = "Daily" relativeAt = "10" relativeEndBoundary = "15" repetition = @{ duration = "P35D" interval = "P33D" } } $Settings = @{ allowDemandStart = "true" allowHardTerminate = "false" deleteExpiredTaskAfter = "PT0S" disallowStartIfOnBatteries = "true" enabled = "true" executionTimeLimit = "PT1H30M" hidden = "false" multipleInstances = "IgnoreNew" priority = "100" restartCount = "0" restartInterval = "PT5M" startWhenAvailable = "true" } Import-Module ArielAuxFn Set-AtmdScheduledTask -BaseParams $BaseParams -Actions $Action -Triggers $Trigger -Settings $Settings -Force -PassThru -Verbose TaskPath TaskName State -------- -------- ----- \ATMD User Tasks\ATMD-ilichev\ Имя задачи Ready .INPUTS Функция получает 5 объектов, описыавающих основные параметры создаваемой задачи. .OUTPUTS Взвращает CIMInstance, получаемую в результате вызова Get-ScheduledTask .NOTES Цель написания этой функции - возможность быстро создавать задачи на основании их описания, которое будте храниться в JSON-файле. Фактически, данная функция просто собирает вместе несколько командлетов Powershell, необходимых для создания задачи и позволяет менять параметры задаче на основании конфигурационного файла, не меняя при этом формат вызова командлетов. #> [CmdletBinding()] [OutputType([System.Object])] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 0)] [System.Object] $BaseParams, [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 1)] [System.Object] $Actions, [Parameter(Mandatory = $true, ValueFromPipeline = $false, Position = 2)] [System.Object] $Triggers, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 3)] [System.Object] $Settings, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 4)] [System.Object] $Security, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 5)] [switch] $Force = $false, [Parameter(Mandatory = $false, ValueFromPipeline = $false, Position = 6)] [switch] $PassThru = $false ) begin { # Это волшебные цифры. Надо просто принят... $MagicKey = (4, 8, 15, 16, 23, 42, 4, 8, 15, 16, 23, 42, 4, 8, 15, 16, 23, 42, 4, 8, 15, 16, 23, 42) if (-not($BaseParams.Description)) { $BaseParams.Description = 'При создании задачи использовался PSC.' } } process { try { if ($IsLinux) { throw [System.Configuration.ConfigurationException]::New('This operation system does not supported.') } Write-Verbose -Message "<Set-AtmdScheduledTask> Проверяем наличие задачи $($BaseParams.Name) в Планировщике заданий." $ScheduledTask = Get-ScheduledTask -TaskName $BaseParams.Name -ErrorAction SilentlyContinue # Если задача уже есть, но указан параметр -Force - удаляем задачу. if ($ScheduledTask -and $Force) { Write-Verbose -Message "<Set-AtmdScheduledTask> Удаляем задачу $($BaseParams.Name), так как указан параметр -Force." Unregister-ScheduledTask -InputObject $ScheduledTask -Confirm:$false Start-Sleep -Seconds 2 $ScheduledTask = $null } # Задача не найдена или удалена. Создаем новую. if (-not($ScheduledTask)) { #region Определяем "Действия" - запуск програмы с аргумнтами или без. Write-Verbose -Message '<Set-AtmdScheduledTask> Определяем выполняемые действия (см. вкладку "Действия").' $TaskActions = @() foreach ($Action in $Actions) { $TaskAction = New-ScheduledTaskAction -Execute $Action.commandToExecute if ($Action.argument) { $TaskAction.Arguments = $Action.argument } if ($Action.workingDirectory) { $TaskAction.WorkingDirectory = $Action.workingDirectory } $TaskActions += $TaskAction } #endregion Определяем "Действия" - запуск програмы с аргумнтами или без. #region Определяем триггеры запуска задачи. Write-Verbose -Message '<Set-AtmdScheduledTask> Определяем триггеры запуска (см. вкладку "Триггеры").' $TaskTriggers = @() foreach ($Trigger in $Triggers) { # Для триггеров поддерживается относительно время запуска. # Это значит, что в конфигурационном файле может быть указано как точно время запуска, так и количество минут тносительно текущего времени. if ($Trigger.relativeAt) { $StartTime = Get-Date $StartTime = $StartTime.AddSeconds($([System.Xml.XmlConvert]::ToTimeSpan($Trigger.relativeAt)).TotalSeconds) } elseif ($Trigger.At) { $StartTime = Get-Date $Trigger.At } # Создавать задачу, время запуска которой в прошлом - дурная затея. # Поэтому, если прозевали время запуска сегодня - запустим завтра. if (($StartTime) -and ($StartTime -lt (Get-Date))) { $StartTime = $StartTime.AddDays(1) } switch ($Trigger.Periodicity) { # TimeTrigger object 'Once' { $TaskTrigger = New-ScheduledTaskTrigger -Once -At $StartTime break } # DailyTrigger object 'Daily' { $TaskTrigger = New-ScheduledTaskTrigger -Daily -At $StartTime break } # WeeklyTrigger object 'Weekly' { $TaskTrigger = New-ScheduledTaskTrigger -Weekly -WeeksInterval $Trigger.WeeksInterval -DaysOfWeek $Trigger.DaysOfWeek -At $StartTime break } # BootTrigger object 'AtStartup' { $TaskTrigger = New-ScheduledTaskTrigger -AtStartup break } # - RegistrationTrigger object 'AtRegistration' { $CIMCLass = Get-CimClass -ClassName 'MSFT_TaskRegistrationTrigger' -Namespace 'Root/Microsoft/Windows/TaskScheduler' $TaskTrigger = New-CimInstance -CimClass $CIMCLass -ClientOnly $TaskTrigger.Enabled = $true break } ## TODO Реализовать тригер "AtLogOn" # # LogonTrigger object # 'AtLogOn' { # break # } # Типы триггеров. которые существуют, но не реализованы тут: # - EventTrigger object # - IdleTrigger object # - MonthlyDOWTrigger object # - MonthlyTrigger object # - SessionStateChangeTrigger object Default { throw 'Данный тип триггера ещё не поддерживается скриптом.' } } # Срок действия (EndBoundary), как и время запуска, может быть указан в минутах относительно времени запуска. if ($Trigger.relativeEndBoundary) { $EndBoundary = $StartTime.AddSeconds($([System.Xml.XmlConvert]::ToTimeSpan($Trigger.relativeEndBoundary)).TotalSeconds) } elseif ($Trigger.EndBoundary) { $EndBoundary = Get-Date $Trigger.EndBoundary } if ($EndBoundary) { # Срок действия не должен истекать раньше первого запуска. Если облажались с этим - срок действия будте 10 минут. if ($StartTime -ge $EndBoundary) { $EndBoundary = $StartTime.AddMinutes(10) } Write-Verbose -Message '<Set-AtmdScheduledTask> Для триггера запуска указан срок действия.' # Параметр '-Formar s' небоходим, чтобы формат даты "попал" в формат хранения EndBoundary. $TaskTrigger.EndBoundary = Get-Date -Date $EndBoundary -Format s } # Параметры порторения (Галочка "Повторять задачу каждые:") - интервал и продолжительность. if ($Trigger.repetition.interval) { Write-Verbose -Message "<Set-AtmdScheduledTask> Для триггера запуска указан интервал повторения - $($Trigger.repetition.interval)" # Так как у нас продолжительность указана в строках вида PnYnMnDTnHnMnS, преобразуем это в TimeSpan для сравнения. $intervalTimeSpan = [System.Xml.XmlConvert]::ToTimeSpan($Trigger.repetition.interval) # Значение Interval не может быть больше значения Duration if ($Trigger.repetition.duration) { # Так как у нас продолжительность указана в строках вида PnYnMnDTnHnMnS, преобразуем это в TimeSpan для сравнения. $durationTimeSpan = [System.Xml.XmlConvert]::ToTimeSpan($Trigger.repetition.duration) if ($intervalTimeSpan -gt $durationTimeSpan) { $Trigger.repetition.interval = $null $Trigger.repetition.duration = $null Write-Warning -Message 'Значение интервала повторения задачи не может привышать дилтельность порторения. Параметры проигнорированы.' } } # Значение интервала не может превышать 31 день и не может быть меньше минуты. if (($intervalTimeSpan -gt $(New-TimeSpan -Days 31)) -or ($intervalTimeSpan -lt $(New-TimeSpan -Minutes 1))) { # Если "не попали" в допустимые значения - не будет ничего. $Trigger.repetition.interval = $null } # Устанавливаем значение. Если ещё есть, что устанавливать. if ($Trigger.repetition.interval) { $CIMCLass = Get-CimClass -ClassName 'MSFT_TaskRepetitionPattern' -Namespace 'Root/Microsoft/Windows/TaskScheduler' $Repetition = New-CimInstance -CimClass $CIMCLass -ClientOnly # Возможно, этот параметр можно вынести в конфиг. $Repetition.StopAtDurationEnd = $False $Repetition.Interval = $Trigger.repetition.interval $TaskTrigger.Repetition = $Repetition # К этому моменту значения $Trigger.repetition.duration может быть $null if ($Trigger.repetition.duration) { # Так как у нас продолжительность указана в строках вида PnYnMnDTnHnMnS, преобразуем это в TimeSpan для сравнения. $durationTimeSpan = [System.Xml.XmlConvert]::ToTimeSpan($Trigger.repetition.duration) # Значение продолжительности не может быть меньше минуты. if ($durationTimeSpan -lt $(New-TimeSpan -Minutes 1)) { # Если "не попали" в допустимые значения - не будет ничего. $Trigger.repetition.duration = $null } } # Устанавливаем значение. $TaskTrigger.Repetition.Duration = $Trigger.repetition.duration } } # end of 'if ($Trigger.repetition.interval) {...' $TaskTriggers += $TaskTrigger } # end of 'foreach ($Trigger in $Triggers) {...' #endregion Определяем триггеры запуска задачи. #region Определяем "Параметры". # Параметры (с примерами значений). которые можно задавать, но это не риализованы тут: # - Compatibility : Vista # - IdleSettings : MSFT_TaskIdleSettings # - NetworkSettings : MSFT_TaskNetworkSettings # - RunOnlyIfIdle : False # - RunOnlyIfNetworkAvailable : False # - StopIfGoingOnBatteries : True # - WakeToRun : False # - DisallowStartOnRemoteAppSession : False # - UseUnifiedSchedulingEngine : False # - MaintenanceSettings : # - volatile : False Write-Verbose -Message '<Set-AtmdScheduledTask> Приступаем к созданию настроек для задачи.' $TaskSettings = New-ScheduledTaskSettingsSet # Выполнять задачу по требованию if ($Settings.allowDemandStart) { $TaskSettings.AllowDemandStart = [System.Convert]::ToBoolean($Settings.allowDemandStart) } # Принудительная остановка задачи, если она не прекращается по запросу if ($Settings.allowHardTerminate) { $TaskSettings.AllowHardTerminate = [System.Convert]::ToBoolean($Settings.allowHardTerminate) } # Если повтор задачи не запланирован, удалять через if ($Settings.deleteExpiredTaskAfter) { $TaskSettings.DeleteExpiredTaskAfter = $Settings.deleteExpiredTaskAfter } # Запускать только при питании от электросети if ($Settings.disallowStartIfOnBatteries) { $TaskSettings.DisallowStartIfOnBatteries = [System.Convert]::ToBoolean($Settings.disallowStartIfOnBatteries) } # Отключить / Включить if ($Settings.enabled) { $TaskSettings.Enabled = [System.Convert]::ToBoolean($Settings.enabled) } # Останавливать задачу, выполняемую дольше if ($Settings.executionTimeLimit) { if ($Settings.executionTimeLimit -eq '') { $Settings.executionTimeLimit = $null } $TaskSettings.ExecutionTimeLimit = $Settings.executionTimeLimit } # Скрытая if ($Settings.hidden) { $TaskSettings.Hidden = [System.Convert]::ToBoolean($Settings.hidden) } # Если задача уже выполняется, то применять правило if ($Settings.multipleInstances) { $AllowedValues = @('IgnoreNew', 'Parallel', 'Queue', '') if ($Settings.multipleInstances -in $AllowedValues) { if ($Settings.multipleInstances -eq '') { $Settings.multipleInstances = $null } $TaskSettings.MultipleInstances = $Settings.multipleInstances } else { $TaskSettings.MultipleInstances = 'IgnoreNew' } } # Не нашел в интерфейсе... if ($Settings.priority) { # Если триоритет не попадает в допустимы диапазон от 0 до 10, то используется значение по умолчанию - 7. if ($Settings.priority -notin @(0..10)) { $Settings.priority = '7' } $TaskSettings.Priority = $Settings.priority } # Количество попыток перезапуска if ($Settings.restartCount) { if ($Settings.restartCount -lt 0) { $Settings.restartCount = 0 } if ($Settings.restartCount -gt 0) { # При сбое выполнять перезапуск через if ($Settings.restartInterval) { # Так как у нас продолжительность указана в строках вида PnYnMnDTnHnMnS, преобразуем это в TimeSpan для сравнения. $intervalTimeSpan = [System.Xml.XmlConvert]::ToTimeSpan($Settings.restartInterval) # Значение интервала не может превышать 31 день и не может быть меньше минуты. if (($intervalTimeSpan -gt $(New-TimeSpan -Days 31)) -or ($intervalTimeSpan -lt $(New-TimeSpan -Minutes 1))) { # Если "не попали" в допустимые значения - будет 5 минут. $Settings.restartInterval = 'PT5M' } # Устанавливаем значение. $TaskSettings.RestartInterval = $Settings.restartInterval } else { $Settings.restartCount = 0 } } $TaskSettings.RestartCount = $Settings.restartCount } # Немедленно запускать задачу, если пропущен плановый запук if ($Settings.startWhenAvailable) { $TaskSettings.StartWhenAvailable = [System.Convert]::ToBoolean($Settings.startWhenAvailable) } #endregion Определяем "Параметры". #region Определяем пользователя для запуска и создаем задачу. Write-Verbose -Message '<Set-AtmdScheduledTask> Указываем параметры запуска и регистрируем задачу.' if ($Security.UserID) { $TaskUserID = $Security.UserID # Если домен пользователя не указан, берем домен текущего компьютера. if (($TaskUserID.IndexOf('\') -lt 0) -and ($TaskUserID.IndexOf('@') -lt 0)) { $ComputerSystem = Get-CimInstance -ClassName Win32_ComputerSystem $TaskUserID = $TaskUserID + '@' + $ComputerSystem.Domain } ## TODO Вынести определение Run Level в конфиг # Run Level could be Limited or Highest $TaskPrincipal = New-ScheduledTaskPrincipal -UserId $TaskUserID -RunLevel Highest -LogonType Password $SecurityStringPassword = ConvertTo-SecureString -String $Security.userPassword -Key $MagicKey $BinaryStringPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecurityStringPassword) $TaskPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BinaryStringPassword) # Создаем задачу. $ScheduledTask = New-ScheduledTask -Action $TaskActions -Trigger $TaskTriggers -Settings $TaskSettings -Description $BaseParams.Description -Principal $TaskPrincipal # Регистрируем задачу. Register-ScheduledTask -TaskName $BaseParams.Name -TaskPath $BaseParams.TaskPath -InputObject $ScheduledTask -User $TaskUserID -Password $TaskPassword | Out-Null } else { # Создаем задачу. $ScheduledTask = New-ScheduledTask -Action $TaskActions -Trigger $TaskTriggers -Settings $TaskSettings -Description $BaseParams.Description # Регистрируем задачу. Register-ScheduledTask -TaskName $BaseParams.Name -TaskPath $BaseParams.TaskPath -InputObject $ScheduledTask | Out-Null } #endregion Определяем пользователя для запуска и создаем задачу. Start-Sleep -Seconds 2 $Result = Get-ScheduledTask -TaskName $BaseParams.Name -TaskPath $BaseParams.TaskPath } # end of 'if (-not($ScheduledTask)) {...' else { Write-Verbose -Message '<Set-AtmdScheduledTask> Задача с указанным именем уже существует.' $Result = $ScheduledTask } } catch { # $PSCmdlet.ThrowTerminatingError($PSitem) Write-Error -Exception $PSItem.Exception } } end { if ($PassThru) { return $Result } } } |