Главная

Friday, 8 December 2017

Варианты перехвата ошибок в PowerShell.

Всем привет.

В предыдущих двух постах были подробно изложены методы отлова критических и некритических ошибок при работе с PowerShell. Но я решил что будет несправедливым не отметить самые основные моменты, которые возникают в работе если что-то пошло не так. Т.е. переменные и обьекты в PowerShell которые содержат первичную информацию о работе нашего скрипта. Речь пойдет об объекте ErrorRecord, о переменной $Error, о мониторинге возникновения ошибки c помощью переменной $? и кодов возврата, а также упомянем командлет Set-PSDebug и точки прерывания. Поможет нам в этом замечательная книга Попова А. В. "Введение в Windows PowerShell" — СПб.: БХВ-Петербург, 2009.


Поехали.


1. Объект ErrorRecord и поток ошибок.


В PowerShell информация о возникающих ошибках записывается в поток ошибок, который по умолчанию отображается на экране, например:
PS C:\> dir "C:\Folder doesn't exist"
Get-ChildItem : Не удается найти путь "C:\Folder doesn't exist",
так как он не существует.
В строка:1 знак:4
+ dir <<<< "C:\Folder doesn't exist"


Поток ошибок можно перенаправить в текстовый файл с помощью специального оператора 2>, например:
PS C:\> dir "C:\Folder doesn't exist" 2>err.txt


Проверим теперь содержимое файла err.txt:
PS C:\> Get-Content err.txt
Get-ChildItem : Не удается найти путь "C:\Folder doesn't exist",
так как он не существует.
В строка:1 знак:4
+ dir <<<< "Folder doesn't exist" 2> err.txt
Как видите, в файл err.txt полностью записалось сообщение об ошибке, однако никакой дополнительной информации не появилось.


Где произошла ошибка смотрим так
PS C:\> $err.InvocationInfo
MyCommand : Get-ChildItem
ScriptLineNumber : 1
OffsetInLine : -2147483648
ScriptName :
Line : $err = dir "C:\Folder doesn't exist" 2>&1
PositionMessage :
 В строка:1 знак:11
 + $err = dir <<<< "C:\Folder doesn't exist" 2>&1
InvocationName : dir
PipelineLength : 1
PipelinePosition : 1
Хотя этот вывод даст нам место скрипта(или непсредственного ввода)
где ошибку интепретирует сама PowerShell, истинное место проблемы надо будет
искать самим.


2. Специальная переменная $Error.

В PowerShell имеется специальная переменная $Error, которая содержит коллекцию (массив) объектов ErrorRecord, соответствующих ошибкам, возникавшим в текущем сеансе работы. Максимальное количество элементов в данной коллекции задается значением переменной $MaximumErrorCount (по умолчанию это 256):
PS C:\> $MaximumErrorCount
256


После заполнения массива $Error объекты для вновь возникающих ошибок будут заменять объекты, соответствующие старым ошибкам. Возникновение каждой новой ошибки приводит к смещению элементов в массиве $Error: объект для последней ошибки хранится в первом элементе ($Error[0]), объект для предыдущей ошибки — во втором элементе ($Error[1]) и т. д.
Убедимся, что элемент $Error[0] имеет тип ErrorRecord, и выведем содержимое этого элемента:
PS C:\> $Error[0].GetType().FullName
System.Management.Automation.ErrorRecord


PS C:\> $Error[0]
Get-ChildItem : Не удается найти путь "C:\Несуществующий каталог",
так как он не существует.
В строка:1 знак:4
+ dir <<<< "Несуществующий каталог"
Естественно, мы можем обращаться ко всем свойствам объекта ErrorRecord:


PS C:\> $Error[0].Exception
Не удается найти путь "C:\Несуществующий каталог", так как он не существует.


Повторим вывод свойства InvocationInfo
PS C:\> $Error[0].InvocationInfo
MyCommand : Get-ChildItem
ScriptLineNumber : 1
OffsetInLine : -2147483648
ScriptName :
Line : $err = dir "C:\Folder doesn't exist" 2>&1
PositionMessage :
 В строка:1 знак:11
 + $err = dir <<<< "C:\Folder doesn't exist" 2>&1
InvocationName : dir
PipelineLength : 1
PipelinePosition : 1

3. Мониторинг возникновения ошибки переменной $?.


В PowerShell имеется логическая переменная $?, которая равна $True, если последняя выполняемая операция завершена успешно, и $False, если во время выполнения последней
операции возникла ошибка. Такая же существует и в Linux.) Например, если выполнить командлет Get-Item для заведомо существующего каталога, то значение переменной $? будет равно $True:
PS C:\> Get-Item С:\
Каталог:
Mode LastWriteTime Length Name
---- ------------- ------ ----
d--hs 21.03.2016 12:00 C:\


PS C:\> $?
True


Если же выполнить этот же командлет для несуществующего каталога, то значение переменной $? будет равно $False:
PS C:\> Get-Item С:\321
Get-Item : Не удается найти путь "C:\321", так как он не существует.
В строка:1 знак:9
Глава 9. Обработка ошибок и отладка 183
+ Get-Item <<<< С:\321
PS C:\> $?
False


4. Мониторинг возникновения ошибки по коду возврата.


Для внешних команд Windows и сценариев PowerShell определено понятие кода возврата (напомним, что для сценариев PowerShell этот код можно установить с помощью инструкции Exit). В операционной системе код возврата последней команды доступен через переменную среды %ERRORLEVEL%; в оболочке PowerShell данный код возврата хранится в специальной переменной $LASTEXITCODE. При этом, если код возврата равен нулю, то переменной $? присваивается значение $True. Если же код возврата не равен нулю, то считается, что при выполнении данной команды произошла ошибка, и переменной $? присваивается значение $False.


В качестве примера выполним команду интерпретатора cmd.exe, которая устанавливает нулевой код возврата. Для этого можно запустить cmd.exe с ключом /c (выполнить команду и завершить работу интерпретатора) и указать для исполнения команду exit 0:
PS C:\> cmd /c exit 0

Проверим значения переменных $LASTEXITCODE и $?:
PS C:\> $LASTEXITCODE
0
PS C:\> $?
True
Теперь выполним команду интерпретатора cmd.exe с ненулевым кодом возврата (пусть, например, код возврата равен 10):
PS C:\> cmd /c exit 10
Вновь проверим переменные $LASTEXITCODE и $?:
PS C:\> $?
False
PS C:\> $LASTEXITCODE
10


5. Командлет Set-PSDebug и точки прерывания.


Процесс поиска ошибок в сценариях неразрывно связан с отладкой. Поэтому нам стоит упомянуть что основным встроенным инструментом для отладки сценариев является командлет Set-PSDebug. Параметры этого командлета позволяют включить режимы трассировки и пошагового выполнения команд, а также режим обязательного объявления переменных:
-Trace 0 Отключение трассировки
-Trace 1 Включение основного режима трассировки
-Trace 2 Включение полного режима трассировки
-Step Включение режима пошагового выполнения
-Strict Включение режима обязательного объявления переменных
-Off Отключение всех механизмов отладки.


Примеры приводить не буду, назначение ключей очевидно из их названия. Самым интересным является режим пошагового выполнения -Step, который позволяет в любой момент вызвать вложенную командную строку для анализа или изменения состояния интерпретатора. Однако отлаживать более-менее большие сценарии с помощью данного метода часто оказывается неудобно, так как для запуска вложенного сеанса в определенной строке сценария придется каждый раз добираться до этой строки с самого начала сценария (каждую команду при этом
нужно выполнять, нажимая клавишу <A>).

Гораздо удобнее было бы использовать точки прерывания, позволяющие запускать сценарий в автоматическом режиме и приостанавливать выполнение на нужной команде. В PowerShell аналогом установки точки прерывания можно считать вызов метода $Host.EnterNestedPrompt(), например:
PS C:\> for ($i=0; $i -lt 10; $i++) {
>> "i = $i"
>> if ($i -eq 5) {
>> "Моя точка прерывания"
>> $Host.EnterNestedPrompt()
>> }
>> }
>>
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
Моя точка прерывания
PS C:\>>>


В данном примере на пятой итерации цикла выводится строка "Моя точка прерывания" и вызывается метод $Host.EnterNestedPrompt(). В результате выполнение цикла приостанавливается, и мы попадаем во вложенную командную строку (вид приглашения изменяется на >>>).


Выход из вложенного сеанса с помощью инструкции Exit:
PS C:\>>> Exit
PS C:\>


Вот так вот.

No comments:

Post a Comment

А что вы думаете по этому поводу?