Всем привет!

Хочу поделиться 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. Проблема с экранированием решена.

Комментарии (0)