Скрипты мы зачастую пишем для того, чтобы затем запланировать их автоматическое исполнение. Естественно, возможны непредусмотренные изначально обстоятельства, которые в дальнейшем приведут к ошибкам в работе скрипта, о которых следует узнать. Поможет нам журнал событий и перехват исключений.

Дополню статью. Имеем ошибки с нашим сценарием контроля функционирования SMTP сервиса через event log средствами powershell. Однако, при этом в журнале событий видим совершенно бесполезные сообщения:

Event Type:    Error
Event Source:    itgCheckMailFlow.ps1
Event Category:    None
Event ID:    0
Date:
Time:
Description:
Exception has been thrown by the target of an invocation.

И какие, позвольте спросить, выводы можно сделать из такого сообщения? Никаких…

Привёл в статье обёртку для wsh сценариев. Прежде, чем приступать к чему-то подобному на powershell, рекомендую изучить 11 главу об обработке ошибок.

# включаем обработку и регистрацию ошибок в журнале
$ErrorActionPreference = "Stop"

trap {
	$errorDescription = @"
При выполнении сценария $($myinvocation.mycommand.name) $((Get-Date).ToShortDateString()) в $((Get-Date).ToShortTimeString()) возникла ошибка:

	$($_.Exception)
	$($_.InvocationInfo.PositionMessage)
"@
	$evt=new-object System.Diagnostics.EventLog("Application")
	$evt.Source=$myinvocation.mycommand.name
	$evt.WriteEntry(`
		$errorDescription, `
		[System.Diagnostics.EventLogEntryType]::Error `
	)
	write-debug $errorDescription
}

Выполнить указанные выше действия необходимо до всех возможных исключений, то есть — в самом начале скрипта. В первую очередь мы указываем, что по каждой ошибке необходимо остановить исполнение скрипта исключением, затем определяем обработчик исключений, который уже и будет писать суть проблемы в журнал.

Что же теперь мы видим в журнале событий?:

Event Type:	Error
Event Source:	itgCheckMailFlow
Event Category:	None
Event ID:	0
Date:		10.11.2010
Time:		9:56:25
User:		N/A
Computer:	SERVER-12
Description:
При выполнении сценария itgCheckMailFlow.ps1 10.11.2010 в 9:56 возникла ошибка:

At C:\temp\itgCheckMailFlow.ps1:27 char:13
+ $test = 25 / <<<<  $test2;
System.DivideByZeroException: Attempted to divide by zero.
   at System.Management.Automation.ParserOps.PolyDiv(ExecutionContext context, Token opToken, Object lval, Object rval)

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.

Согласитесь, сообщение куда более информативное (не стоит придираться к сути, ошибка сделана специально, для наглядности).

Ещё лучше будет оформить всё необходимое в виде модуля (последняя версия доступна через мой SVN репозиторий, доступ на чтение — svn_sysadm, пароль svn):

# включаем обработку и регистрацию ошибок в журнале
$ErrorActionPreference = "Stop"

function Write-EventLogITG {
	<#
		.Synopsis
            Регистрирует событие в журнале событий, при этом корректно регистрирует источник событий.
		.Description
            Регистрирует событие в журнале событий, при этом корректно регистрирует источник событий.
		.Parameter message
		    собственно текст сообщения.
		.Parameter eventLog
			Идентификатор журнала для регистрации событий. При необходимости - будет создан.
		.Parameter EntryType
			Тип сообщения
		.Parameter eventId
			Идентификатор сообщения
		.Parameter source
			Источник сообщения. В случае отсутствия - имя файла сценария.
		.Example
			Write-EventLog `
                -msg "такое вот событие" `
                -type [System.Diagnostics.EventLogEntryType]::Error `
	#>

    param (
    	[Parameter(
            Mandatory=$true,
            Position=0,
            ValueFromPipeline=$true,
            HelpMessage="Текстовое сообщение."
		)]
        [string]$message,

		[Parameter(
			Mandatory=$false,
			Position=1,
			ValueFromPipeline=$false,
			HelpMessage="Журнал, в который планируем регистрировать событие."
		)]
        [string]$eventLog = 'Application',

		[Parameter(
			Mandatory=$false,
			Position=2,
			ValueFromPipeline=$false,
			HelpMessage="Тип сообщения."
		)]
        [System.Diagnostics.EventLogEntryType]$EntryType = [System.Diagnostics.EventLogEntryType]::Information,

		[Parameter(
			Mandatory=$false,
			Position=3,
			ValueFromPipeline=$false,
			HelpMessage="Идентификатор сообщения."
		)]
        [int]$eventId = 0,

		[Parameter(
			Mandatory=$false,
			Position=4,
			ValueFromPipeline=$false,
			HelpMessage="Источник сообщения. По умолчанию - имя файла сценария."
		)]
        [string]$source = $([System.IO.Path]::GetFileNameWithoutExtension($global:myinvocation.mycommand.name))
    )

    #Create the source, if it does not already exist.
    if (![System.Diagnostics.EventLog]::SourceExists($source)) {
        [System.Diagnostics.EventLog]::CreateEventSource($source, $eventLog)
    } else {
        $eventLog = [System.Diagnostics.EventLog]::LogNameFromSourceName($source,'.')
    }
    #Check if Event Type is correct
    $log = New-Object System.Diagnostics.EventLog($eventLog)
    $log.Source=$source
    $log.WriteEntry(`
        $message,`
        $EntryType,`
        $eventid
    )
}

function Write-CurrentException {
	<#
		.Synopsis
            Регистрирует в журнале событий текущую ошибку (исключение).
		.Description
            Регистрирует в журнале событий текущую ошибку (исключение).
		.Parameter exception
			Объект - описатель ошибки ($_ в trap).
		.Example
            trap {
                Write-CurrentException
            }
	#>

    param (
		[Parameter(
			Mandatory=$true,
			Position=0,
			ValueFromPipeline=$false,
			HelpMessage='Объект - описатель ошибки ($_ в trap).'
		)]
        $exception
    )

	$errorDescription = @"
При выполнении сценария $($global:myinvocation.mycommand.name) $((Get-Date).ToShortDateString()) в $((Get-Date).ToShortTimeString()) возникла ошибка:
$($exception.InvocationInfo.PositionMessage)
$($exception.Exception)
"@
    Write-EventLogITG `
        -message $errorDescription `
        -entryType Error
	write-debug $errorDescription
}

function Write-Successfull {
	<#
		.Synopsis
            Регистрирует в журнале событий информацию о успешном завершении сценария.
		.Description
            Регистрирует в журнале событий информацию о успешном завершении сценария.
		.Example
            Write-Successfull
	#>

    param (
    )

    Write-EventLogITG `
        -message "Скрипт успешно завершил работу." `
        -entryType Information
}

Export-ModuleMember "Write-EventLogITG", "Write-CurrentException", "Write-Successfull"

Обращаю внимание на явное указание области global в $global:myinvocation, потому как просто $myinvocation уже даст Вам область модуля, а не исходного сценария.

Пример использования модуля:

ImportSystemModules
Import-Module "c:\temp\itgWrapper.psm1"

$ErrorActionPreference = "Stop"
trap {
    Write-CurrentException($_)
}

# сам сценарий

Write-Successfull 

Естественно, не стоит забывать про Write-EventLog, но этот командлет требует предварительно зарегистрированного источника.

Кроме того, в microsoft в свой статье достаточно наглядно демонстрирует возможность использования .dll в качестве источника “шаблонов” сообщений в event log, в том числе – и с возможностью локализации.

Дополнительные ссылки: http://xaegr.wordpress.com/?s=errors.

Получилось пока не очень красиво. Буду искать решения с подключением “обёртки” одной строкой. Любые замечания и комментарии с удовольствием приму.

Отзывы » (4)

  1. И ещё любопытные и полезные решения для отладки сценариев, а также статья о перенаправлении потока DEBUG в файл:

    set-PSdebug -trace 2 # активируем трассировку сценария в поток debug
    
    # перенаправляем debug в файл
    [System.Diagnostics.Debug]::Listeners | ? { $_.name -eq “Default” } | % {
        Write-Verbose “Setting log file to $file”
        $_.LogFileName=$file
    }
    

    Следует также обратить внимание на объект System.Diagnostics.Debugger.

  2. Обратил внимание на любопытный эффект. Если в командной строке:

    powershell . c:\temp\itgCheckMailFlow.ps1
    

    опустить точку, тогда $global.myinvocation не будет соответствовать области сценария, и регистрация событий не будет работать (из-за некорректного формирования source). Поэтому точка ВАЖНА!

  3. Вскрылась одна неприятность. Если размер сообщения ($message), которое мы хотим отправить в event log, превышает 32766, мы получим ошибку, сценарий «вылетит», а в журнале событий будет пусто. Поэтому код пришлось несколько поправить:

        $log = New-Object System.Diagnostics.EventLog($eventLog)
        $log.Source=$source
        if ($message.length -ge 32767) {$message = $message.Substring(1,32766)};
        $log.WriteEntry(`
            $message,`
            $EntryType,`
            $eventid
        )
    
Опубликовать комментарий

XHTML: Вы можете использовать следующие HTML теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Tags Связь с комментариями статьи:
RSS комментарии
Обратная ссылка