Всем привет!
Хочу поделиться PowerShell шаблоном для создания ранбуков. Хотя правильнее, наверное, сказать Activity.
Входящие данные и различные параметры будем хранить в хэш таблице $rb_input объявленной в начале скрипта. В последующем эту переменную будет удобно передавать во все функции и вызовы Invoke-Command через весь скрипт в качестве параметра. А также с помощью такой организации входящих данных скрипт становится легко переносимым на другие системы и конфигурации: меняем всё в $rb_input и переиспользуем.
Многие общие параметры для ранбуков я храню во внешнем файле и ссылаюсь на них в $rb_input. Изменение скриптов во внешнем редакторе становится удобнее, чем при использовании переменных Оркестратора. В переменных Оркестратора я храню только пароли.
На выходе ранбук возвращает обязательные параметры: количество возникших ошибок в переменной $errors, количество предупреждений $warnings и текст этих самых ошибок и предупреждений в $message. И не обязательные - результат работы ранбука или Activity.
Чтобы иметь возможность выстраивать цепочки из Activity или вызовов других ранбуков (Invoke Runbook) без лишних ветвлений, на входе принимаем возвращенные данные от предыдущего Activity: $errors, $warnings, $message. В процессе работы скрипта эти переменные инкрементируются и дополняются. На выходе из последнего Activity получим полную картину ошибок и предупреждений. Достаточно будет забрать эти переменные из последнего Activity.
Если предыдущие Activity или вызовы других ранбуков основанных на этом шаблоне завершились с ошибками, то скрипт не будет выполняться. Для этого проверяем равна ли нулю переменная $errors.
# Runbook template
# Файл с общими часто используемыми параметрами
. c:\Orchestrator\settings\config.ps1
# Все входящие и не публичные параметры указываем в $rb_input
$rb_input = @{
param1 = '{Пользовательский параметр1 из Initialize Data}'
param2 = '{Пользовательский параметр2 из Initialize Data}'
mail_to = ''
mail_to_admin = 'admin@example.org'
ps_server = 'localhost'
ps_user = ''
ps_passwd = ''
who_start_runbook = '{Параметр одного из предыдущих Activity}'
smtp_server = $global:smtp_server # Переменная из файла config.ps1 с адресом SMTP сервера
smtp_from = $global:smtp_from # Переменная из файла config.ps1 с адресом отправителя
}
# Если ранбуки или Activity запускаются цепочкой, то результат возвращенный предыдущим
# Activity указываем здесь
$result = @{
errors = [int] '{errors из предыдущего Activity либо оставляем 0}'
warnings = [int] '{warnings из предыдущего Activity либо оставляем 0}'
messages = @(@'
{message из предыдущего Activity либо оставляем пустым}
'@)
}
# Основной блок ранбука
$DebugPreference = 'SilentlyContinue' # Изменить на Continue для отображения отладочных сообщений
$ErrorActionPreference = 'Stop'
function main($rb_input)
{
trap
{
return @{ errors = 0; warnings = 0; messages = @("Critical error[{0},{1}]: {2}`r`n`r`nProcess interrupted!`r`n" -f $_.InvocationInfo.ScriptLineNumber, $_.InvocationInfo.OffsetInLine, $_.Exception.Message); }
}
try
{
$result = @{ errors = 0; warnings = 0; messages = @() }
# Проверка корректности заполнения полей
if([string]::IsNullOrWhiteSpace($rb_input.param1))
{
$result.errors++; $result.messages += 'Ошибка: Не заполнено поле param1';
}
if([string]::IsNullOrWhiteSpace($rb_input.param2))
{
$result.errors++; $result.messages += 'Ошибка: Не заполнено поле param2';
}
if($result.errors -gt 0)
{
return $result
}
# *** PUT YOU CODE HERE ***
Write-Debug 'PUT YOU CODE HERE'
$result['other_param1'] = 'Какая-то произвольная переменная с результатами работы скрипта'
return $result
}
catch
{
$result.errors++; $result.messages += ('ERROR[{0},{1}]: {2}' -f $_.InvocationInfo.ScriptLineNumber, $_.InvocationInfo.OffsetInLine, $_.Exception.Message);
return $result
}
}
# Выполняем ранбук, только если предыдущие завершились без ошибок
if($result.errors -eq 0)
{
$output = main -rb_input $rb_input
# Объединяем результаты с предыдущим ранбуком
$result.errors += $output.errors
$result.warnings += $output.warnings
$result.messages += $output.messages
# Если скрипт завершился без ошибок, то возвращаем и результат
if($output.errors -eq 0)
{
$other_param1 = $output.other_param1 # Та самая произвольная переменная
}
}
# Возврат значений
$exit_code = 0
if($result.errors -gt 0 -or $result.warnings -gt 0)
{
$exit_code = 1
}
$errors = $result.errors
$warnings = $result.warnings
$message = $result.messages -join "`r`n"
Write-Debug ('Errors: {0}, Warnings: {1}, Messages: {2}' -f $errors, $warnings, $message)
Если ранбук предназначен для запуска конечным пользователем, а не для вызова из других ранбуков, в последнем Activity я использую скрипт ThrowException для визуализации того, что ранбук завершился с ошибками или предупреждениями. В этом случае в консоли меняется иконка на восклицательный знак.
[int] $errors = '{errors из последнего Activity}'
[int] $warnings = '{warnings из последнего Activity}'
$message = @'
{message из последнего Activity}
'@
if(($errors + $warnings) -ne 0)
{
throw New-Object System.Exception('Message: {0}' -f $message)
}
Скрипт на GitHub
В результате хотелось бы узнать как другие пишут ранбуки. Возможно я ошибаюсь и мой подход не правильный.
И ещё мучает вопрос, на который не смог найти ответ. Как экранировать пользовательский ввод? Orchestrator в голом виде вставляет введенные пользователем параметры и легко можно сделать инъекцию в скрипт.
Update: Сделал альтернативную консоль для запуска ранбуков WebSCO. Проблема с экранированием решена.