Обёртка для сценария powershell: регистрация событий скрипта в журнале событий
Скрипты мы зачастую пишем для того, чтобы затем запланировать их автоматическое исполнение. Естественно, возможны непредусмотренные изначально обстоятельства, которые в дальнейшем приведут к ошибкам в работе скрипта, о которых следует узнать. Поможет нам журнал событий и перехват исключений.
Дополню статью. Имеем ошибки с нашим сценарием контроля функционирования 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)
RSS комментарии
Обратная ссылка
Хорошая статья и набор ссылок об отладке сцериев powershell.
И ещё любопытные и полезные решения для отладки сценариев, а также статья о перенаправлении потока DEBUG в файл:
Следует также обратить внимание на объект System.Diagnostics.Debugger.
Обратил внимание на любопытный эффект. Если в командной строке:
опустить точку, тогда $global.myinvocation не будет соответствовать области сценария, и регистрация событий не будет работать (из-за некорректного формирования source). Поэтому точка ВАЖНА!
Вскрылась одна неприятность. Если размер сообщения ($message), которое мы хотим отправить в event log, превышает 32766, мы получим ошибку, сценарий «вылетит», а в журнале событий будет пусто. Поэтому код пришлось несколько поправить: