Powershell если ошибка то

В Powershell существует несколько уровней ошибок и несколько способов их обработать. Проблемы одного уровня (Non-Terminating Errors) можно решить с помощью привычных для Powershell команд. Другой уровень ошибок (Terminating Errors) решается с помощью исключений (Exceptions) стандартного, для большинства языков, блока в виде Try, Catch и Finally. 

Как Powershell обрабатывает ошибки

До рассмотрения основных методов посмотрим на теоретическую часть.

Автоматические переменные $Error

В Powershell существует множество переменных, которые создаются автоматически. Одна из таких переменных — $Error хранит в себе все ошибки за текущий сеанс PS. Например так я выведу количество ошибок и их сообщение за весь сеанс:

Get-TestTest
$Error
$Error.Count

Переменная $Error в Powershell

При отсутствии каких либо ошибок мы бы получили пустой ответ, а счетчик будет равняться 0:

Счетчик ошибок с переменной $Error в Powershell

Переменная $Error являет массивом и мы можем по нему пройтись или обратиться по индексу что бы найти нужную ошибку:

$Error[0]

foreach ($item in $Error){$item}

Вывод ошибки по индексу в Powershell c $Error

Свойства объекта $Error

Так же как и все что создается в Powershell переменная $Error так же имеет свойства (дополнительную информацию) и методы. Названия свойств и методов можно увидеть через команду Get-Member:

$Error | Get-Member

Свойства переменной $Error в Powershell

Например, с помощью свойства InvocationInfo, мы можем вывести более структурный отчет об ошибки:

$Error[0].InvocationInfo

Детальная информация об ошибке с $Error в Powershell

Методы объекта $Error

Например мы можем очистить логи ошибок используя clear:

$Error.clear()

Очистка логов об ошибке в Powershell с $Error

Критические ошибки (Terminating Errors)

Критические (завершающие) ошибки останавливают работу скрипта. Например это может быть ошибка в названии командлета или параметра. В следующем примере команда должна была бы вернуть процессы «svchost» дважды, но из-за использования несуществующего параметра ‘—Error’ не выполнится вообще:

'svchost','svchost' | % {Get-Process -Name $PSItem} --Error 

Критические ошибки в Powershell Terminating Errors

Не критические ошибки (Non-Terminating Errors)

Не критические (не завершающие) ошибки не остановят работу скрипта полностью, но могут вывести сообщение об этом. Это могут быть ошибки не в самих командлетах Powershell, а в значениях, которые вы используете. На предыдущем примере мы можем допустить опечатку в названии процессов, но команда все равно продолжит работу:

'svchost111','svchost' | % {Get-Process -Name $PSItem}

Не критические ошибки в Powershell Non-Terminating Errors

Как видно у нас появилась информация о проблеме с первым процессом ‘svchost111’, так как его не существует. Обычный процесс ‘svchost’ он у нас вывелся корректно.

Параметр ErrorVariable

Если вы не хотите использовать автоматическую переменную $Error, то сможете определять свою переменную индивидуально для каждой команды. Эта переменная определяется в параметре ErrorVariable:

'svchost111','svchost' | % {Get-Process -Name $PSItem } -ErrorVariable my_err_var
$my_err_var

Использование ErrorVariable в Powershell

Переменная будет иметь те же свойства, что и автоматическая:

$my_err_var.InvocationInfo

Свойства  ErrorVariable в Powershell

Обработка некритических ошибок

У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.

Приоритет ошибок с $ErrorActionPreference

Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:

$ErrorActionPreference

Определение $ErrorActionPreference в Powershell

Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Определение глобальной переменной $ErrorActionPreference в Powershell

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

Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:

  • Continue — вывод ошибки и продолжение работы;
  • Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
  • SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
  • Stop — остановка скрипта при первой ошибке.

Самый частый параметр, который мне приходится использовать — SilentlyContinue:

$ErrorActionPreference = 'SilentlyContinue'
'svchost111','svchost' | % {Get-Process -Name $PSItem}

Игнорирование ошибок в Powershell с ErrorActionPreference и SilentlyContinue

Использование параметра ErrorAction

Переменная $ErrorActionPreference указывает глобальный приоритет, но мы можем определить такую логику в рамках команды с параметром ErrorAction. Этот параметр имеет больший приоритет чем $ErrorActionPreference. В следующем примере, глобальная переменная определяет полную остановку скрипта, а в параметр ErrorAction говорит «не выводить ошибок и продолжить работу»:

$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'SilentlyContinue'}

Использование параметра ErrorAction в ошибках с Powershell

Кроме ‘SilentlyContinue’ мы можем указывать те же параметры, что и в переменной $ErrorActionPreference. 

Значение Stop, в обоих случаях, делает ошибку критической.

Обработка критических ошибок и исключений с Try, Catch и Finally

Когда мы ожидаем получить какую-то ошибку и добавить логику нужно использовать Try и Catch. Например, если в вариантах выше мы определяли нужно ли нам отображать ошибку или останавливать скрипт, то теперь сможем изменить выполнение скрипта или команды вообще. Блок Try и Catch работает только с критическими ошибками и в случаях если $ErrorActionPreference или ErrorAction имеют значение Stop.

Например, если с помощью Powershell мы пытаемся подключиться к множеству компьютеров один из них может быть выключен — это приведет к ошибке. Так как эту ситуацию мы можем предвидеть, то мы можем обработать ее. Процесс обработки ошибок называется исключением (Exception).

Синтаксис и логика работы команды следующая:

try {
    # Пытаемся подключиться к компьютеру
}
catch [Имя исключения 1],[Имя исключения 2]{
    # Раз компьютер не доступен, сделать то-то
}
finally {
    # Блок, который выполняется в любом случае последним
}

Блок try мониторит ошибки и если она произойдет, то она добавится в переменную $Error и скрипт перейдет к блоку Catch. Так как ошибки могут быть разные (нет доступа, нет сети, блокирует правило фаервола и т.д.) то мы можем прописывать один блок Try и несколько Catch:

try {
    # Пытаемся подключится
}
catch ['Нет сети']['Блокирует фаервол']{
    # Записываем в файл
}
catch ['Нет прав на подключение']{
    # Подключаемся под другим пользователем
}

Сам блок finally — не обязательный и используется редко. Он выполняется самым последним, после try и catch и не имеет каких-то условий.

Catch для всех типов исключений

Как и было показано выше мы можем использовать блок Catch для конкретного типа ошибок, например при проблемах с доступом. Если в этом месте ничего не указывать — в этом блоке будут обрабатываться все варианты ошибок:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
}

Игнорирование всех ошибок с try и catch в Powershell

Такой подход не рекомендуется использовать часто, так как вы можете пропустить что-то важное.

Мы можем вывести в блоке catch текст ошибки используя $PSItem.Exception:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
   Write-Host "Какая-то неисправность" -ForegroundColor RED
   $PSItem.Exception
}

Переменная PSITem в блоке try и catch в Powershell

Переменная $PSItem хранит информацию о текущей ошибке, а глобальная переменная $Error будет хранит информацию обо всех ошибках. Так, например, я выведу одну и ту же информацию:

$Error[0].Exception

Вывод сообщения об ошибке в блоке try и catch в Powershell

Создание отдельных исключений

Что бы обработать отдельную ошибку сначала нужно найти ее имя. Это имя можно увидеть при получении свойств и методов у значения переменной $Error:

$Error[0].Exception | Get-Member

Поиск имени для исключения ошибки в Powershell

Так же сработает и в блоке Catch с $PSItem:

Наименование ошибок для исключений в Powershell

Для вывода только имени можно использовать свойство FullName:

$Error[0].Exception.GetType().FullName

Вывод типа ошибок и их названия в Powershell

Далее, это имя, мы вставляем в блок Catch:

try {
   'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException]{
   Write-Host "Произошла ошибка" -ForegroundColor RED
   $PSItem.Exception
}

Указываем исключение ошибки в блоке Try Catch Powershell

Так же, как и было описано выше мы можем усложнять эти блоки как угодно указывая множество исключений в одном catch.

Выброс своих исключений

Иногда нужно создать свои собственные исключения. Например мы можем запретить добавлять через какой-то скрипт названия содержащие маленькие буквы или сотрудников без указания возраста и т.д. Способов создать такие ошибки — два и они тоже делятся на критические и обычные.

Выброс с throw

Throw — выбрасывает ошибку, которая останавливает работу скрипта. Этот тип ошибок относится к критическим. Например мы можем указать только текст для дополнительной информации:

$name = 'AD.1'

if ($name -match '.'){
   throw 'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Если нужно, то мы можем использовать исключения, которые уже были созданы в Powershell:

$name = 'AD.1'

if ($name -like '*.*'){
   throw [System.IO.FileNotFoundException]'Запрещено использовать точки в названиях'
}

Выброс ошибки с throw в Powershell

Использование Write-Error

Команда Write-Error работает так же, как и ключ ErrorAction. Мы можем просто отобразить какую-то ошибку и продолжить выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка'
   }
   else{
      $name
   }
}

Использование Write-Error для работы с исключениями в Powershell

При необходимости мы можем использовать параметр ErrorAction. Значения этого параметра были описаны выше. Мы можем указать значение ‘Stop’, что полностью остановит выполнение скрипта:

$names = @('CL1', 'AD.1', 'CL3')

foreach ($name in $names){
   if ($name -like '*.*'){
      Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'
   }
   else{
      $name
   }
}

Использование Write-Error и Stop в Powershell

Отличие команды Write-Error с ключом ErrorAction от обычных команд в том, что мы можем указывать исключения в параметре Exception:

Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'

Write-Error -Message 'Исключение' -Exception [System.IO.FileNotFoundException] -ErrorAction 'Stop'

Свойства Write-Errror в Powershell

В Exception мы так же можем указывать сообщение. При этом оно будет отображаться в переменной $Error:

Write-Error -Exception [System.IO.FileNotFoundException]'Моё сообщение'

Свойства Write-Errror в Powershell 

Теги:

#powershell

#ошибки

Продолжаем тему обработки ошибок в PowerShell, начатую в предыдущей статье. Сегодня речь пойдет об обработке прерывающих ошибок (исключений). Надо понимать, что сообщение об ошибке — это не то же самое, что исключение. Как вы помните, в PowerShell есть два типа ошибок — непрерывающие и прерывающие.

Непрерывающие ошибки позволяют продолжить работу, тогда как прерывающие останавливают выполнение команды. Остановка приводит к исключению (exception), которое и можно отлавливать и обрабатывать.

Примечание. Таким же образом можно обрабатывать и непрерывающие ошибки. Изменение параметра ErrorAction на Stop прервет выполнение команды и произведет исключение, которое можно уловить.

Для наглядности сгенерируем ошибку, попытавшись прочитать файл, на который у нас нет прав. А теперь обратимся к переменной $Error и выведем данные об исключении. Как видите, данное исключение имеет тип UnauthorizedAccessException и относится к базовому типу System.SystemException.

определение исключения

Для обработки исключений в PowerShell есть несколько способов, которые мы сегодня и рассмотрим.

Try/Catch/Finally

Конструкция Try/Catch/Finally предназначена для обработки исключений, возникающих в процессе выполнения скрипта. В блоке Try располагается исполняемый код, в котором должны отслеживаться ошибки. При возникновении в блоке Try прерывающей ошибки оболочка PowerShell ищет соответствующий блок Catch для обработки этой ошибки, и если он найден, то выполняет находящиеся в нем инструкции. Блок Catch может включать в себя любые команды, необходимые для обработки возникнувшей ошибки иили восстановления дальнейшей работы скрипта.

Блок Finally располагается обычно в конце скрипта. Команды, находящиеся в нем, выполняются в любом случае, независимо от возникновения ошибок. Была ли  ошибка перехвачена и обработана блоком Catch или при выполнении скрипта ошибок не возникало вообще, блок Finally будет выполнен. Присутствие этого блока в скрипте необязательно, основная его задача — высвобождение ресурсов (закрытие процессов, очистка памяти и т.п.).

В качестве примера в блок Try поместим код, который читает файлы из указанной директории. При возникновении проблем блок Catch выводит сообщение об ошибке, после чего отрабатывает блок Finally и работа скрипта завершается:

try {
Get-Content -Path ″C:Files*″ -ErrorAction Stop
}
catch {
Write-Host ″Some error was found.″
}
finally {
Write-Host ″Finish.″
}

обработка общей ошибки с помощью trycatch

Для блока Catch можно указать конкретный тип ошибки, добавив после ключевого слова Catch в квадратных скобках название исключения. Так в следующем примере укажем в качестве типа исключение System.UnauthorizedAccessException, которое возникает при отсутствии необходимых прав доступа к объекту:

try {
Get-Content -Path ″C:Files*″ -ErrorAction Stop
}
catch [System.UnauthorizedAccessException]
{
Write-Host ″File is not accessible.″
}
finally {
Write-Host ″Finish.″
}

обработка конкретного исключения в trycatch

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

Блок Try может включать несколько блоков Catch, для разных типов ошибок, соответственно можно для каждого типа ошибки задать свой обработчик. Например, возьмем два блока Catch, один для ошибки при отсутствии прав доступа, а второй — для любых других ошибок, которые могут возникнуть в процессе выполнения скрипта:

try {
Get-Content -Path ″C:Files*″ -ErrorAction Stop
}
catch [System.UnauthorizedAccessException]
{
Write-Host ″File is not accessible.″
}
catch {
Write-Host ″Other type of error was found:″
Write-Host ″Exception type is $($_.Exception.GetType().Name)″
}
finally {
Write-Host ″Finish.″
}

обработка нескольких исключений в trycatch

Trap

Еще один способ обработки ошибок — это установка ловушки (trap). В этом варианте обработчик ошибок задается с помощью ключевого слова Trap, определяющего список команд, которые должны выполниться при возникновении прерывающей ошибки и исключения. Когда происходит исключение, то оболочка PowerShell ищет в коде инструкции Trap для данной ошибки, и если находит, то выполняет их.

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

trap {
Write-Host ″Error was found.″
}
Get-Content -Path C:File* -ErrorAction Stop

обработка исключения с помощью trap

Так же, как и для trycatch, ключевое слово Trap позволяет задавать тип исключения, указав его в квадратных скобках. К примеру, можно задать в скрипте несколько ловушек, одну нацелив на конкретную ошибку, а вторую на обработку оставшихся:

trap [System.Management.Automation.ItemNotFoundException]
{
Write-Host ″File is not accessible.″
break
}
trap {
Write-Host ″Other error was found.″
continue
}
Get-Content -Path C:File* -ErrorAction Stop

Вместе с Trap можно использовать ключевые слова Break и Continue, которые позволяют определить, должен ли скрипт продолжать выполняться после возникновения прерывающей ошибки. По умолчанию при возникновении исключения выполняются команды, входящие в блок Trap, выводится информация об ошибке, после чего выполнение скрипта продолжается со строки, вызвавшей ошибку:

trap {
Write-Host ″Error was found.″
}
Write-Host ″Before error.″
Get-Content -Path C:File* -ErrorAction Stop
Write-Host ″After error.″

обработка исключения с помощью trap без доп. параметров

Если использовать ключевое слово Break, то при возникновении ошибки будет выполнены команды в блоке Trap, после чего выполнение скрипта будет прервано:

trap {
Write-Host ″Error was found.″
break
}
Write-Host ″Before error.″
Get-Content -Path C:File* -ErrorAction Stop
Write-Host ″After error.″

Как  видно из примера, команда, следующая за исключением, не отработала и сообщение ″After error.″ выведено не было.

обработка исключения с помощью trap с параметром break

Если же в Trap включено ключевое слово Continue, то выполнение скрипта будет продолжено, так же как и в случае по умолчанию. Единственное отличие в том, что с Continue ошибка не записывается в поток Error и не выводится на экран.

trap {
Write-Host ″Error was found.″
continue
}
Write-Host ″Before error.″
Get-Content -Path C:File* -ErrorAction Stop
Write-Host ″After error.″

обработка исключения с помощью trap с параметром continue

Область действия

При отлове ошибок с помощью Trap надо помнить о такой вещи, как область действия (scope). Оболочка PowerShell является глобальной областью, в которую входят все процессы, запущенный скрипт получает собственную область, а если внутри скрипта определены функции, то внутри каждой определена своя, частная область действия. Это создает своего рода родительско-дочернюю иерархию.

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

Для примера возьмем функцию Content, внутри которой будет выполняться наш код. Область действия внутри функции назовем scope 1, а снаружи scope 2:

trap {
Write-Host ″Error was found.″
continue
}
function Content {
Write-Host ″Before error, scope1.″
Get-Content -Path C:File* -ErrorAction Stop
Write-Host ″After error, scope 1.″
}
Content
Write-Host ″After error, scope 2.″

При возникновении ошибки в функции Content оболочка будет искать ловушку внутри нее. Затем, не найдя ловушку внутри функции, оболочка выйдет из текущей области и будет искать в родительской области. Ловушка там есть, поэтому будет выполнена обработка ошибки, после чего Continue возобновит выполнение скрипта, но уже в родительской области (scope 2), не возвращаясь обратно в функцию (scope 1).

область действия, вариант 1

А теперь немного изменим скрипт, поместив ловушку внутрь функции:

function Content {
trap {
Write-Host ″Error was found.″
continue
}
Write-Host ″Before error, scope 1.″
Get-Content -Path C:File* -ErrorAction Stop
Write-Host ″After error, scope 1.″
}
Content
Write-Host ″After error, scope 2.″

Как видите, теперь при возникновении ошибки внутри функции будет произведена обработка ошибки, после чего выполнение  будет продолжено с той строки, в которой произошла ошибка — не выходя из функции.

область действия, вариант 2

Заключение

Если сравнить Try/Catch и Trap, то у каждого метода есть свои достоинства и недостатки. Конструкцию Try/Catch можно более точно нацелить на возможную ошибку, так как Catch обрабатывает только содержимое блока Try. Эту особенность удобно использовать при отладке скриптов, для поиска ошибок.

И наоборот, Trap является глобальным обработчиком, отлавливая ошибки во всем скрипте независимо от расположения. На мой взгляд это более жизненный вариант, который больше подходит для постоянной работы.

Вообще я редко вижу смысл в том чтобы отлавливать ошибки в скриптах, но недавно ко мне попалась задача, где необходимо было обработать ошибки в скрипте PowerShell. Дело в том что данный скрипт использовался как часть работы System Center Orchestrator. Для этого я использовал Try/Catch/Finaly . Но все по порядку.

Немного про ошибки

Ошибки можно условно разделить на две больших категории.

  1. Прерывающие
  2. Не прерывающие

Первые завершают выполнение конвейера с ошибкой. Т.е. дальнейшие команды в конвейере не выполняются. Например, вы не правильно указали имя команды. Вторая категория лишь генерирует ошибку, но конвейер продолжает выполняться далее. Например, вы запросили содержимое не существующего каталога.

В любом случае всю информацию о всех ошибках можно поглядеть в переменной $error. Последняя ошибка идет с индексом 0, т.е. $error[0] — покажет последнюю ошибку. А $error[0].Exception описание последней ошибки.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

PS C:> Get-Command NoCommand,Dir

Get-Command : Имя «NoCommand» не распознано как имя командлета, функции, файла сценария или выполняемой программы. Пров

ерьте правильность написания имени, а также наличие и правильность пути, после чего повторите попытку.

строка:1 знак:1

+ Get-Command NoCommand,Dir

+ ~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (NoCommand:String) [Get-Command], CommandNotFoundException

    + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand

CommandType     Name                                               ModuleName

                                                   

Alias           dir -> Get-ChildItem

PS C:> $error[0]

Get-Command : Имя «NoCommand» не распознано как имя командлета, функции, файла сценария или выполняемой программы. Пров

ерьте правильность написания имени, а также наличие и правильность пути, после чего повторите попытку.

строка:1 знак:1

+ Get-Command NoCommand,Dir

+ ~~~~~~~~~~~~~~~~~~~~~~~~~

    + CategoryInfo          : ObjectNotFound: (NoCommand:String) [Get-Command], CommandNotFoundException

    + FullyQualifiedErrorId : CommandNotFoundException,Microsoft.PowerShell.Commands.GetCommandCommand

PS C:> $error[0].Exception

Имя «NoCommand» не распознано как имя командлета, функции, файла сценария или выполняемой программы. Проверьте правильн

ость написания имени, а также наличие и правильность пути, после чего повторите попытку.

Этот же пример, но в виде рисунка для наглядности.

Переменная Error

Для того чтобы выяснить была ли в какой-то части кода ошибка необходимо использовать Try. Это помогает избавиться от неожиданного и некорректного завершение вашего скрипта. Позволяя так же корректно завершить его работу.

Синтаксис выглядит в общем случае так.

Try

{

  часть кода в которой ищем ошибку

}

Catch

{

[тип ошибки, которую ищем] код,

     который будет выполнен когда ошибка будет найдена

}

Finally

{

код, который будет выполнен в любом случае

}

Однако использование Finally и определение типа ошибки — опционально и может не использоваться.

Для проверки напишем вот такой код где используем Try в PowerShell для того чтобы обработать ошибку деления на ноль и вывести информацию об ошибке.

try

{

[float](4/0)

}

catch

{

  Write-Host «Error!!!»

  Write-Host $error[0].Exception

}

Как видно я произвожу деление на ноль в блоке try, т.е. совершаю прерывающую ошибку. Только прерывающие ошибку будут отловлены. А далее в блоке catch произвожу уже необходимые отладочные действия, т.е. произвожу обработку ошибки.. В моей исходной задаче я в данном блоке заносил информацию об ошибках в специальный лог файл, но давайте попробуем запустить мой данный пример.

PS C:> .test.ps1

Error!!!

System.Management.Automation.RuntimeException: Попытка деления на нуль. -> System.DivideByZeroException: Попытка делен

ия на нуль.

   Конец трассировки внутреннего стека исключений

   в System.Management.Automation.ExceptionHandlingOps.CheckActionPreference(FunctionContext funcContext, Exception exce

ption)

   в System.Management.Automation.Interpreter.ActionCallInstruction`2.Run(InterpretedFrame frame)

   в System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

   в System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame frame)

Увы в блоке try в PowerShell должна присутствовать прерывающая ошибка.

Преобразуем не прерывающую ошибку в прерывающую

Существует общий параметр для всех командлетов в PowerShell -ErrorAction. Данный параметр может принимать четыре значения

  • Continue — выводит ошибку и продолжает выполнение
  • SilentlyContinue — не выводит ошибку и продолжает выполнение
  • Stop — завершает выполнение
  • Inquire — спрашивает у пользователя как поступить

В нашем случае подходит действие stop. Если использовать параметр со значением -ErrorAction Stop, то при возникновении ошибки при выполнении команды, данная ошибка прерывает выполнение команды. Ниже пример скрипта, использующего -ErrorAction Stop.

try

{

  Dir NoFolder -ErrorAction Stop

}

catch

{

  Write-Host «Error!!!»

  Write-Host $error[0].Exception

}

Ниже результат выполнения данного скрипта

PS C:> .test.ps1

Error!!!

System.Management.Automation.ItemNotFoundException: Не удается найти путь «C:NoFolder»,

так как он не существует.

   в System.Management.Automation.SessionStateInternal.GetChildItems(String path, Boolean recurse, C

mdletProviderContext context)

   в Microsoft.PowerShell.Commands.GetChildItemCommand.ProcessRecord()

В моей исходной задаче try в PowerShell необходимо было использовать для всех команд в скрипте. Поскольку обработка ошибки была общей для любой ошибки в скрипте весь скрипт можно поместить в один Try, конечно это не совсем правильно, но зато просто. Чтобы каждый раз не писать -ErrorAction Stop. Можно воспользоваться переменной $ErrorActionPreference, которая имеет те же значения и сходна по действию, однако распространяет свое действие на все командлеты в скрипте.

$ErrorActionPreference = «stop»

try

{

  Dir Folder

  Get-Process -ComputerName TestPC

}

catch

{

  Write-Host «Error!!!»

  Write-Host $error[0].Exception

}

Вместо заключения

Конечно, по началу вы мало будете задумываться об поиске ошибок, используя множество условных конструкций вы их минимизируете. Однако использование try в PowerShell позволит минимизировать случаи неожиданного завершения скрипта.

description Locale ms.date online version schema title

Describes how to use the `try`, `catch`, and `finally` blocks to handle terminating errors.

en-US

11/12/2021

https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.2&WT.mc_id=ps-gethelp

2.0.0

about Try Catch Finally

Short description

Describes how to use the try, catch, and finally blocks to handle
terminating errors.

Long description

Use try, catch, and finally blocks to respond to or handle terminating
errors in scripts. The Trap statement can also be used to handle terminating
errors in scripts. For more information, see about_Trap.

A terminating error stops a statement from running. If PowerShell does not
handle a terminating error in some way, PowerShell also stops running the
function or script using the current pipeline. In other languages, such as C#,
terminating errors are referred to as exceptions.

Use the try block to define a section of a script in which you want
PowerShell to monitor for errors. When an error occurs within the try block,
the error is first saved to the $Error automatic variable. PowerShell then
searches for a catch block to handle the error. If the try statement does
not have a matching catch block, PowerShell continues to search for an
appropriate catch block or Trap statement in the parent scopes. After a
catch block is completed or if no appropriate catch block or Trap
statement is found, the finally block is run. If the error cannot be handled,
the error is written to the error stream.

A catch block can include commands for tracking the error or for recovering
the expected flow of the script. A catch block can specify which error types
it catches. A try statement can include multiple catch blocks for different
kinds of errors.

A finally block can be used to free any resources that are no longer needed
by your script.

try, catch, and finally resemble the try, catch, and finally
keywords used in the C# programming language.

Syntax

A try statement contains a try block, zero or more catch blocks, and zero
or one finally block. A try statement must have at least one catch block
or one finally block.

The following shows the try block syntax:

The try keyword is followed by a statement list in braces. If a terminating
error occurs while the statements in the statement list are being run, the
script passes the error object from the try block to an appropriate catch
block.

The following shows the catch block syntax:

catch [[<error type>][',' <error type>]*] {<statement list>}

Error types appear in brackets. The outermost brackets indicate the element is
optional.

The catch keyword is followed by an optional list of error type
specifications and a statement list. If a terminating error occurs in the
try block, PowerShell searches for an appropriate catch block. If
one is found, the statements in the catch block are executed.

The catch block can specify one or more error types. An error type is a
Microsoft .NET Framework exception or an exception that is derived from a .NET
Framework exception. A catch block handles errors of the specified .NET
Framework exception class or of any class that derives from the specified
class.

If a catch block specifies an error type, that catch block handles that
type of error. If a catch block does not specify an error type, that catch
block handles any error encountered in the try block. A try statement can
include multiple catch blocks for the different specified error types.

The following shows the finally block syntax:

finally {<statement list>}

The finally keyword is followed by a statement list that runs every time the
script is run, even if the try statement ran without error or an error was
caught in a catch statement.

Note that pressing CTRL+C stops the pipeline. Objects
that are sent to the pipeline will not be displayed as output. Therefore, if
you include a statement to be displayed, such as «Finally block has run», it
will not be displayed after you press CTRL+C, even if the
finally block ran.

Catching errors

The following sample script shows a try block with a catch block:

try { NonsenseString }
catch { "An error occurred." }

The catch keyword must immediately follow the try block or another catch
block.

PowerShell does not recognize «NonsenseString» as a cmdlet or other item.
Running this script returns the following result:

When the script encounters «NonsenseString», it causes a terminating error. The
catch block handles the error by running the statement list inside the block.

Using multiple catch statements

A try statement can have any number of catch blocks. For example, the
following script has a try block that downloads MyDoc.doc, and it contains
two catch blocks:

try {
   $wc = new-object System.Net.WebClient
   $wc.DownloadFile("http://www.contoso.com/MyDoc.doc","c:tempMyDoc.doc")
}
catch [System.Net.WebException],[System.IO.IOException] {
    "Unable to download MyDoc.doc from http://www.contoso.com."
}
catch {
    "An error occurred that could not be resolved."
}

The first catch block handles errors of the System.Net.WebException and
System.IO.IOException types. The second catch block does not specify an
error type. The second catch block handles any other terminating errors that
occur.

PowerShell matches error types by inheritance. A catch block handles errors
of the specified .NET Framework exception class or of any class that derives
from the specified class. The following example contains a catch block that
catches a «Command Not Found» error:

catch [System.Management.Automation.CommandNotFoundException]
{"Inherited Exception" }

The specified error type, CommandNotFoundException, inherits from the
System.SystemException type. The following example also catches a Command
Not Found error:

catch [System.SystemException] {"Base Exception" }

This catch block handles the «Command Not Found» error and other errors that
inherit from the SystemException type.

If you specify an error class and one of its derived classes, place the catch
block for the derived class before the catch block for the general class.

[!NOTE]
PowerShell wraps all exceptions in a RuntimeException type. Therefore,
specifying the error type System.Management.Automation.RuntimeException
behaves the same as an unqualified catch block.

Using Traps in a Try Catch

When a terminating error occurs in a try block with a Trap defined within
the try block, even if there is a matching catch block, the Trap statement
takes control.

If a Trap exists at a higher block than the try, and there is no matching
catch block within the current scope, the Trap will take control, even if
any parent scope has a matching catch block.

Accessing exception information

Within a catch block, the current error can be accessed using $_, which
is also known as $PSItem. The object is of type ErrorRecord.

try { NonsenseString }
catch {
  Write-Host "An error occurred:"
  Write-Host $_
}

Running this script returns the following result:

An Error occurred:
The term 'NonsenseString' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path
was included, verify that the path is correct and try again.

There are additional properties that can be accessed, such as ScriptStackTrace,
Exception, and ErrorDetails. For example, if we change the script to the
following:

try { NonsenseString }
catch {
  Write-Host "An error occurred:"
  Write-Host $_.ScriptStackTrace
}

The result will be similar to:

An Error occurred:
at <ScriptBlock>, <No file>: line 2

Freeing resources using finally

To free resources used by a script, add a finally block after the try and
catch blocks. The finally block statements run regardless of whether the
try block encounters a terminating error. PowerShell runs the finally block
before the script terminates or before the current block goes out of scope.

A finally block runs even if you use CTRL+C to stop the
script. A finally block also runs if an Exit keyword stops the script from
within a catch block.

See also

  • about_Break
  • about_Continue
  • about_Scopes
  • about_Throw
  • about_Trap
description Locale ms.date online version schema title

Describes how to use the `try`, `catch`, and `finally` blocks to handle terminating errors.

en-US

11/12/2021

https://learn.microsoft.com/powershell/module/microsoft.powershell.core/about/about_try_catch_finally?view=powershell-7.3&WT.mc_id=ps-gethelp

2.0.0

about Try Catch Finally

Short description

Describes how to use the try, catch, and finally blocks to handle
terminating errors.

Long description

Use try, catch, and finally blocks to respond to or handle terminating
errors in scripts. The Trap statement can also be used to handle terminating
errors in scripts. For more information, see about_Trap.

A terminating error stops a statement from running. If PowerShell does not
handle a terminating error in some way, PowerShell also stops running the
function or script using the current pipeline. In other languages, such as C#,
terminating errors are referred to as exceptions.

Use the try block to define a section of a script in which you want
PowerShell to monitor for errors. When an error occurs within the try block,
the error is first saved to the $Error automatic variable. PowerShell then
searches for a catch block to handle the error. If the try statement does
not have a matching catch block, PowerShell continues to search for an
appropriate catch block or Trap statement in the parent scopes. After a
catch block is completed or if no appropriate catch block or Trap
statement is found, the finally block is run. If the error cannot be handled,
the error is written to the error stream.

A catch block can include commands for tracking the error or for recovering
the expected flow of the script. A catch block can specify which error types
it catches. A try statement can include multiple catch blocks for different
kinds of errors.

A finally block can be used to free any resources that are no longer needed
by your script.

try, catch, and finally resemble the try, catch, and finally
keywords used in the C# programming language.

Syntax

A try statement contains a try block, zero or more catch blocks, and zero
or one finally block. A try statement must have at least one catch block
or one finally block.

The following shows the try block syntax:

The try keyword is followed by a statement list in braces. If a terminating
error occurs while the statements in the statement list are being run, the
script passes the error object from the try block to an appropriate catch
block.

The following shows the catch block syntax:

catch [[<error type>][',' <error type>]*] {<statement list>}

Error types appear in brackets. The outermost brackets indicate the element is
optional.

The catch keyword is followed by an optional list of error type
specifications and a statement list. If a terminating error occurs in the
try block, PowerShell searches for an appropriate catch block. If
one is found, the statements in the catch block are executed.

The catch block can specify one or more error types. An error type is a
Microsoft .NET Framework exception or an exception that is derived from a .NET
Framework exception. A catch block handles errors of the specified .NET
Framework exception class or of any class that derives from the specified
class.

If a catch block specifies an error type, that catch block handles that
type of error. If a catch block does not specify an error type, that catch
block handles any error encountered in the try block. A try statement can
include multiple catch blocks for the different specified error types.

The following shows the finally block syntax:

finally {<statement list>}

The finally keyword is followed by a statement list that runs every time the
script is run, even if the try statement ran without error or an error was
caught in a catch statement.

Note that pressing CTRL+C stops the pipeline. Objects
that are sent to the pipeline will not be displayed as output. Therefore, if
you include a statement to be displayed, such as «Finally block has run», it
will not be displayed after you press CTRL+C, even if the
finally block ran.

Catching errors

The following sample script shows a try block with a catch block:

try { NonsenseString }
catch { "An error occurred." }

The catch keyword must immediately follow the try block or another catch
block.

PowerShell does not recognize «NonsenseString» as a cmdlet or other item.
Running this script returns the following result:

When the script encounters «NonsenseString», it causes a terminating error. The
catch block handles the error by running the statement list inside the block.

Using multiple catch statements

A try statement can have any number of catch blocks. For example, the
following script has a try block that downloads MyDoc.doc, and it contains
two catch blocks:

try {
   $wc = new-object System.Net.WebClient
   $wc.DownloadFile("http://www.contoso.com/MyDoc.doc","c:tempMyDoc.doc")
}
catch [System.Net.WebException],[System.IO.IOException] {
    "Unable to download MyDoc.doc from http://www.contoso.com."
}
catch {
    "An error occurred that could not be resolved."
}

The first catch block handles errors of the System.Net.WebException and
System.IO.IOException types. The second catch block does not specify an
error type. The second catch block handles any other terminating errors that
occur.

PowerShell matches error types by inheritance. A catch block handles errors
of the specified .NET Framework exception class or of any class that derives
from the specified class. The following example contains a catch block that
catches a «Command Not Found» error:

catch [System.Management.Automation.CommandNotFoundException]
{"Inherited Exception" }

The specified error type, CommandNotFoundException, inherits from the
System.SystemException type. The following example also catches a Command
Not Found error:

catch [System.SystemException] {"Base Exception" }

This catch block handles the «Command Not Found» error and other errors that
inherit from the SystemException type.

If you specify an error class and one of its derived classes, place the catch
block for the derived class before the catch block for the general class.

[!NOTE]
PowerShell wraps all exceptions in a RuntimeException type. Therefore,
specifying the error type System.Management.Automation.RuntimeException
behaves the same as an unqualified catch block.

Using Traps in a Try Catch

When a terminating error occurs in a try block with a Trap defined within
the try block, even if there is a matching catch block, the Trap statement
takes control.

If a Trap exists at a higher block than the try, and there is no matching
catch block within the current scope, the Trap will take control, even if
any parent scope has a matching catch block.

Accessing exception information

Within a catch block, the current error can be accessed using $_, which
is also known as $PSItem. The object is of type ErrorRecord.

try { NonsenseString }
catch {
  Write-Host "An error occurred:"
  Write-Host $_
}

Running this script returns the following result:

An Error occurred:
The term 'NonsenseString' is not recognized as the name of a cmdlet, function,
script file, or operable program. Check the spelling of the name, or if a path
was included, verify that the path is correct and try again.

There are additional properties that can be accessed, such as ScriptStackTrace,
Exception, and ErrorDetails. For example, if we change the script to the
following:

try { NonsenseString }
catch {
  Write-Host "An error occurred:"
  Write-Host $_.ScriptStackTrace
}

The result will be similar to:

An Error occurred:
at <ScriptBlock>, <No file>: line 2

Freeing resources using finally

To free resources used by a script, add a finally block after the try and
catch blocks. The finally block statements run regardless of whether the
try block encounters a terminating error. PowerShell runs the finally block
before the script terminates or before the current block goes out of scope.

A finally block runs even if you use CTRL+C to stop the
script. A finally block also runs if an Exit keyword stops the script from
within a catch block.

See also

  • about_Break
  • about_Continue
  • about_Scopes
  • about_Throw
  • about_Trap

Errors in your PowerShell script can stop the execution of your script, but sometimes that is completely unnecessary. Try Catch blocks in PowerShell help you to handle those errors properly.

Take the following example; You need to update the job titles of 20 employees. The HR department has given you a list with names and the new job titles, but they misspelled one of the names.

Without a PowerShell Try Catch block, your script would stop somewhere in the middle, leaving you with half the records updated. You will now need to figure out where the script has stopped and which user triggered the error.

With Try Catch in PowerShell, we can process the whole list and write a proper error message (or even send an email) when something is wrong.

In this article, we are going to take a look at how to use Try, Catch, Finally in PowerShell and how to find the correct error message to catch.

Let’s first take a look at the basics of the Try Catch Finally block. A Try Catch block in Powershell always exists of one Try block and atleast one Catch block. The Finally block is optional, the code in this block will always run, no matter the outcome of the Try block.

Try
{
    # Try something that could cause an error
    1/0
}
Catch
{
    # Catch any error
    Write-Host "An error occurred"
}
Finally
{
    # [Optional] Run this part always
    Write-Host "cleaning up ..."
}

In the Try block, you place the script that could cause an error. Keep this part small and don’t combine too many functions into it. The goal with a PowerShell Try Catch block is to implement proper error handling, and you can only do that when you Try one tricky function at a time.

If we take the following (simplified) example to update the job titles:

Try{
	# Find the user to update
	$ADUser = Get-AzureAdUser -SearchString $user.name

	# Update the job title
	Set-AzureAdUser -ObjectId $ADUser.ObjectId -JobTitle $user.jobtitle

	# Send an email that the job title is updated
	Send-MailMessage -SmtpServer $smtp.address -To $user.mail -From $smtp.from -Subject $smtp.subject -Body "Your jobtitle is updated"
}
Catch{
	Write-Host ("Failed to update " + $($user.name)) -ForegroundColor Red
}

The problem here is that if something goes wrong in the Try block, we only get an error that the update is failed. But you don’t know which part. Maybe the user is updated but was the script unable to send to mail.

A better option here is to split the Try into finding and updating the user and create another function with a Try-Catch to send the email.

PowerShell Finally block

The finally block is optional, so you don’t need to use it every time. Code inside the finally block is always executed, no matter the outcome of the Try block. You can use the finally block to close a connection for example, or as part of your logging.

Catching Terminating and non-terminating Errors

When it comes to catching errors in PowerShell, there is one thing really important, non-terminating errors. These are errors that won’t terminate (stop) the script. These kinds of errors can’t be caught with a catch block by default.

Most cmdlets in PowerShell are non-terminating. They will output an error, which you will see in red in your console, if you use them wrong, but they won’t stop the script. The reason for this the default ErrorAction in your PowerShell profile, which is set to Continue.

# To show your default error action type
$ErrorActionPreference

Take the following example, opening a non-existing directory in PowerShell with a Try Catch block:

Try {
    dir "c:somenon-existingpath"
    
}
Catch {
    Write-host "Directory does not exist"
}

You expect to the see “Directory does not exist”, but instead you get the normal red error message. The reason for this is that the non-existing path isn’t a terminating error, and the default error action is to continue.

To catch the error you will need toadd the -ErrorAction Stop parameter behind your action.

Try {
    dir "c:somenon-existingpath" -ErrorAction stop
    
}
Catch {
    Write-host "Directory does not exist"
}

Another option is to change the ErrorActionPreference at the beginning of your script or PowerShell session. But keep in mind that the preference will reset to continue when you start a new session.

$ErrorActionPreference = "Stop"

Powershell Try Catch Exception

Exceptions in PowerShell allow you to handle errors even better. Until now we just used a simple catch, that will basically catch any errors. A good way to start, but if you want to improve your PowerShell error handling even further, then you can use Exceptions.

As I mentioned in the beginning, a Try Catch block should have atleast one catch block. That means that we can have multiple catch blocks to catch different errors and handle them differently.

If we take the following example with updating the job titles:

Try{
	# Find the user to update
	$ADUser = Get-AzureAdUser -ObjectId $user.UserPrincipalName -ErrorAction Stop

	# Update the job title
	Set-AzureAdUser -ObjectId $ADUser.ObjectId -JobTitle $user.jobtitle -ErrorAction Stop
}
Catch [Microsoft.Open.Azure.AD.CommonLibrary.AadNeedAuthenticationException]{
	# Catch a connection error
	Write-Warning "You need to Connect to AzureAD first"
}
Catch [Microsoft.Open.AzureAD16.Client.ApiException] {
	# Catch the when we are unable to find the user
	Write-Warning "Unable to find the user"
}
Catch {
	Write-Warning "An other error occured"
}

What we can do here, for example, catch the error when the connection to AzureAD hasn’t been made yet, or the error when we are unable to find the user in the AzureAD. We do this by defining an exception to a catch block.

The difficult part here is finding the exception that you need to catch. The only way to find them is to run your script, and make sure you trigger the error that you want to catch. You can then find the exception in the error message or in the $Error variable

powershell try catch

As you can see in the screenshot, we can find the Authentication exception in the error message (1). Another option is to take a look at the $Error variable. If you run $Error[0].Exception.GetType().FullName directly after the error, then you will get the full exception name that you can use in the catch block.

Powershell Error Variable

Let’s explain a bit more about the PowerShell Error variable. When an error occurs in PowerShell it will be appended to the $error variable. This variable will contain all the errors that occurred during your PowerShell session.

Instead of $error, you can also use $._ or $PSitem inside the catch block to show details about the error.

The $error variable is really useful and contains a lot of information about the error. We can not only use this information to handle the error properly but also to inform the user better.

Finding the location of the error

When you are writing a larger script, it’s handy to know the exact location of the function that causes the error. In the error variable, you will find the ScriptStackTrace. This will output the exact location of the error and the origin.

Take the following example:

$ErrorActionPreference = "Stop"


Function OpenPath($path) {
    Try {
        dir $path 
    }
    Catch {
        Write-host "Directory does not exist" -ForegroundColor Red
        Write-Host $_.ScriptStackTrace
    }
}

OpenPath("c:somenon-existingpath")

When you run this code you will get the following stack trace:

powershell try catch error handling

As you can the error occured on line 6, in the OpenPath function, which was called by the script on line 15.

This kind of information can really help you with debugging your scripts.

Show Proper Error Messages

You can write your own error messages in the PowerShell Catch blocks, but sometimes the exception messages are more than enough.

Function OpenPath($path) {
    Try {
        dir $path -ErrorAction Stop
    }
    Catch {
        Write-Host $_.Exception.Message -ForegroundColor Red
    }
}

#Outputs:
Cannot find path 'C:somenon-existingpath' because it does not exist.

Couting Errors in PowerShell

A small feature of the Error variable, but sometime really handy, counting how many error occurred

$Error.count

Clearing the PowerShell Error Variable

The error variable contains a lot of information, you can see all properties of the Error variable by using the get-member cmdlet. The last thing I want to point out is clearing the error variable. When you are trying to find the correct exception it’s really handy to clear the variable before you run your script again.

$Error.clear()

Wrapping up

Try Catch blocks in PowerShell helps you to write better scripts, scripts that do what you want, even if something goes wrong. The hardest part is writing good catch blocks, because you will need to figured out what can possibly go wrong with your script.

I hope this article helped you to get started with the Try-Catch blocks, if you have any questions, just drop a comment below.

You may also like the following articles:

  • Connect to Exchange Online with PowerShell
  • Enable MFA for Office 365 User with PowerShell

Error handling is just part of life when it comes to writing code. We can often check and validate conditions for expected behavior. When the unexpected happens, we turn to exception handling. You can easily handle exceptions generated by other people’s code or you can generate your own exceptions for others to handle.

Index

  • Index
  • Basic terminology
    • Exception
    • Throw and Catch
    • The call stack
    • Terminating and non-terminating errors
    • Swallowing an exception
  • Basic command syntax
    • Throw
      • Write-Error -ErrorAction Stop
      • Cmdlet -ErrorAction Stop
    • Try/Catch
    • Try/Finally
    • Try/Catch/Finally
  • $PSItem
    • PSItem.ToString()
    • $PSItem.InvocationInfo
    • $PSItem.ScriptStackTrace
    • $PSItem.Exception
      • $PSItem.Exception.Message
      • $PSItem.Exception.InnerException
      • $PSItem.Exception.StackTrace
  • Working with exceptions
    • Catching typed exceptions
    • Catch multiple types at once
    • Throwing typed exceptions
      • Write-Error -Exception
      • The big list of .Net exceptions
    • Exceptions are objects
    • Re-throwing an exception
      • Re-throwing a new exception
      • $PSCmdlet.ThrowTerminatingError()
  • Try can create terminating errors
    • $PSCmdlet.ThrowTerminatingError() inside try/catch
    • Public function templates
  • Trap
  • Closing remarks

Basic terminology

We need to cover some basic terms before we jump into this one.

Exception

An Exception is like an event that is created when normal error handling can not deal with the issue. Trying to divide a number by zero or running out of memory are examples of something that will create an exception. Sometimes the author of the code you are using will create exceptions for certain issues when they happen.

Throw and Catch

When an exception happens, we say that an exception is thrown. To handle a thrown exception, you need to catch it. If an exception is thrown and it is not caught by something, the script will stop executing.

The call stack

The call stack is the list of functions that have called each other. When a function is called, it gets added to the stack or the top of the list. When the function exits or returns, it will be removed from the stack.

When an exception is thrown, that call stack is checked in order for an exception handler to catch it.

Terminating and non-terminating errors

An exception is generally a terminating error. A thrown exception will either be caught or it will terminate the current execution. By default, a non-terminating error is generated by Write-Error and it adds an error to the output stream without throwing an exception.

I point this out because Write-Error and other non-terminating errors will not trigger the catch.

Swallowing an exception

This is when you catch an error just to suppress it. Do this with caution because it can make troubleshooting issues very difficult.

Basic command syntax

Here is a quick overview of the basic exception handling syntax used in PowerShell.

Throw

To create our own exception event, we throw an exception with the throw keyword.

function Do-Something
{
    throw "Bad thing happened"
}

This creates a runtime exception that is a terminating error. It will be handled by a catch in a calling function or exit the script with a message like this.

PS:> Do-Something

Bad thing happened
At line:1 char:1
+ throw "Bad thing happened"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (Bad thing happened:String) [], RuntimeException
    + FullyQualifiedErrorId : Bad thing happened

Write-Error -ErrorAction Stop

I mentioned that Write-Error does not throw a terminating error by default. If you specify -ErrorAction Stop then Write-Errorgenerates a terminating error that can be handled with a catch.

Write-Error -Message "Houston, we have a problem." -ErrorAction Stop

Thank you to Lee Daily for reminding about using -ErrorAction Stop this way.

Cmdlet -ErrorAction Stop

If you specify -ErrorAction Stop on any advanced function or Cmdlet, it will turn all Write-Error statements into terminating errors that will stop execution or that can be handled by a catch.

Do-Something -ErrorAction Stop

Try/Catch

The way exception handling works in PowerShell (and many other languages) is that you first try a section of code and if it throws an error, you can catch it. Here is a quick sample.

try
{
    Do-Something
}
catch
{
    Write-Output "Something threw an exception"
}

try
{
    Do-Something -ErrorAction Stop
}
catch
{
    Write-Output "Something threw an exception or used Write-Error"
}

The catch script only runs if there is a terminating error. If the try executes correctly, then it will skip over the catch.

Try/Finally

Sometimes you don’t need to handle an error but still need some code to execute if an exception happens or not. A finally script does exactly that.

Take a look at this example:

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
$command.Connection.Open()
$command.ExecuteNonQuery()
$command.Connection.Close()

Any time you open or connect to a resource, you should close it. If the ExecuteNonQuery() throws an exception, the connection will not get closed. Here is the same code inside a try/finally block.

$command = [System.Data.SqlClient.SqlCommand]::New(queryString, connection)
try
{
    $command.Connection.Open()
    $command.ExecuteNonQuery()
}
finally
{
    $command.Connection.Close()
}

In this example, the connection will get closed if there is an error. It will also get closed if there is no error. The finally script will run every time.

Because you are not catching the exception, it will still get propagated up the call stack.

Try/Catch/Finally

It is perfectly valid to use catch and finally together. Most of the time you will use one or the other, but you may find scenarios where you will use both.

$PSItem

Now that we got the basics out of the way, we can dig a little deeper.

Inside the catch block, there is an automatic variable ($PSItem or $_) of type ErrorRecord that contains the details about the exception. Here is a quick overview of some of the key properties.

For these examples, I used an invalid path in ReadAllText to generate this exception.

[System.IO.File]::ReadAllText( '\testnofilefound.log')

PSItem.ToString()

This will give you the cleanest message to use in logging and general output. ToString() is automatically called if $PSItem is placed inside a string.

catch
{
    Write-Output "Ran into an issue: $($PSItem.ToString())"
}

catch
{
    Write-Output "Ran into an issue: $PSItem"
}

$PSItem.InvocationInfo

This property contains additional information collected by PowerShell about the function or script where the exception was thrown. Here is the InvocationInfo from the sample exception that I created.

PS:> $PSItem.InvocationInfo | Format-List *

MyCommand             : Get-Resource
BoundParameters       : {}
UnboundArguments      : {}
ScriptLineNumber      : 5
OffsetInLine          : 5
ScriptName            : C:blogthrowerror.ps1
Line                  :     Get-Resource
PositionMessage       : At C:blogthrowerror.ps1:5 char:5
                        +     Get-Resource
                        +     ~~~~~~~~~~~~
PSScriptRoot          : C:blog
PSCommandPath         : C:blogthrowerror.ps1
InvocationName        : Get-Resource

The important details here show the ScriptName, the Line of code and the ScriptLineNumber where the invocation started.

$PSItem.ScriptStackTrace

This property will show the order of function calls that got you to the code where the exception was generated.

PS:> $PSItem.ScriptStackTrace
at Get-Resource, C:blogthrowerror.ps1: line 13
at Do-Something, C:blogthrowerror.ps1: line 5
at <ScriptBlock>, C:blogthrowerror.ps1: line 18

I am only making calls to functions in the same script but this would track the calls if multiple scripts were involved.

$PSItem.Exception

This is the actual exception that was thrown.

$PSItem.Exception.Message

This is the general message that describes the exception and is a good starting point when troubleshooting. Most exceptions have a default message but can also be set to something custom when the exception is thrown.

PS:> $PSItem.Exception.Message

Exception calling "ReadAllText" with "1" argument(s): "The network path was not found."

This is also the message returned when calling $PSItem.ToString() if there was not one set on the ErrorRecord.

$PSItem.Exception.InnerException

Exceptions can contain inner exceptions. This is often the case when the code you are calling catches an exception and throws a different exception. They will place the original exception inside the new exception.

PS:> $PSItem.Exception.InnerExceptionMessage
The network path was not found.

I will revisit this later when I talk about re-throwing exceptions.

$PSItem.Exception.StackTrace

This is the StackTrace for the exception. I showed a ScriptStackTrace above, but this one is for the calls to managed code.

at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileStream.Init(String path, FileMode mode, FileAccess access, Int32 rights, Boolean useRights, FileShare share, Int32 bufferSize, FileOptions options, SECURITY_ATTRIBUTES secAttrs, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.FileStream..ctor(String path, FileMode mode, FileAccess access, FileShare share, Int32 bufferSize, FileOptions options, String msgPath, Boolean bFromProxy, Boolean useLongPath, Boolean checkHost)
at System.IO.StreamReader..ctor(String path, Encoding encoding, Boolean detectEncodingFromByteOrderMarks, Int32 bufferSize, Boolean checkHost)
at System.IO.File.InternalReadAllText(String path, Encoding encoding, Boolean checkHost)
at CallSite.Target(Closure , CallSite , Type , String )

You will only get this stack trace when the event is thrown from managed code. I am calling a .Net framework function directly so that is all we can see in this example. Generally when you are looking at a stack trace, you are looking for where your code stops and the system calls begin.

Working with exceptions

There is more to exceptions than the basic syntax and exception properties.

Catching typed exceptions

You can be selective with the exceptions that you catch. Exceptions have a type and you can specify the type of exception you want to catch.

try
{
    Do-Something -Path $path
}
catch [System.IO.FileNotFoundException]
{
    Write-Output "Could not find $path"
}
catch [System.IO.IOException]
{
     Write-Output "IO error with the file: $path"
}

The exception type is checked for each catch block until one is found that matches your exception. It is important to realize that exceptions can inherit from other exceptions. In the example above, FileNotFoundException inherits from IOException. So if the IOException was first, then it would get called instead. Only one catch block will be invoked even if there are multiple matches.

If we had a System.IO.PathTooLongException then the IOException would match but if we had a InsufficientMemoryException then nothing would catch it and it would propagate up the stack.

Catch multiple types at once

It is possible to catch multiple exception types with the same catch statement.

try
{
    Do-Something -Path $path -ErrorAction Stop
}
catch [System.IO.DirectoryNotFoundException],[System.IO.FileNotFoundException]
{
    Write-Output "The path or file was not found: [$path]"
}
catch [System.IO.IOException]
{
    Write-Output "IO error with the file: [$path]"
}

Thank you /u/Sheppard_Ra for suggesting this addition.

Throwing typed exceptions

You can throw typed exceptions in PowerShell. Instead of calling throw with a string:

throw "Could not find: $path"

Use an exception accelerator like this:

throw [System.IO.FileNotFoundException] "Could not find: $path"

But you have to specify a message when you do it that way.

You can also create a new instance of an exception to be thrown. The message is optional when you do this because the system has default messages for all built in exceptions.

throw [System.IO.FileNotFoundException]::new()
throw [System.IO.FileNotFoundException]::new("Could not find path: $path")

If you are not yet using PowerShell 5.0, you will have to use the older New-Object approach.

throw (New-Object -TypeName System.IO.FileNotFoundException )
throw (New-Object -TypeName System.IO.FileNotFoundException -ArgumentList "Could not find path: $path")

By using a typed exception, you (or others) can catch the exception by the type as mentioned in the previous section.

Write-Error -Exception

We can add these typed exceptions to Write-Error and we can still catch the errors by exception type. Use Write-Error like in these examples:

# with normal message
Write-Error -Message "Could not find path: $path" -Exception ([System.IO.FileNotFoundException]::new()) -ErrorAction Stop

# With message inside new exception
Write-Error -Exception ([System.IO.FileNotFoundException]::new("Could not find path: $path")) -ErrorAction Stop

# Pre PS 5.0
Write-Error -Exception ([System.IO.FileNotFoundException]"Could not find path: $path") -ErrorAction Stop

Write-Error -Message "Could not find path: $path" -Exception ( New-Object -TypeName System.IO.FileNotFoundException ) -ErrorAction Stop

Then we can catch it like this:

catch [System.IO.FileNotFoundException]
{
    Write-Log $PSItem.ToString()
}

The big list of .Net exceptions

I compiled a master list with the help of the Reddit/r/PowerShell community that contains hundreds of .Net exceptions to complement this post.

  • The big list of .Net exceptions

I start by searching that list for exceptions that feel like they would be a good fit for my situation. You should try to use exceptions in the base System namespace.

Exceptions are objects

If you start using a lot of typed exceptions, remember that they are objects. Different exceptions have different constructors and properties. If we look at the documentation for System.IO.FileNotFoundException, we will see that we can pass in a message and a file path.

[System.IO.FileNotFoundException]::new("Could not find file", $path)

And it has a FileName property that exposes that file path.

catch [System.IO.FileNotFoundException]
{
    Write-Output $PSItem.Exception.FileName
}

You will have to consult the .Net documentation for other constructors and object properties.

Re-throwing an exception

If all you are going to do in your catch block is throw the same exception, then don’t catch it. You should only catch an exception that you plan to handle or perform some action when it happens.

There are times where you want to perform an action on an exception but re-throw the exception so something downstream can deal with it. We could write a message or log the problem close to where we discover it but handle the issue further up the stack.

catch
{
    Write-Log $PSItem.ToString()
    throw $PSItem
}

Interestingly enough, we can call throw from within the catch and it will re-throw the current exception.

catch
{
    Write-Log $PSItem.ToString()
    throw
}

We want to re-throw the exception to preserve the original execution information like source script and line number. If we throw a new exception at this point it will hide where the exception started.

Re-throwing a new exception

If you catch an exception but you want to throw a different one, then you should nest the original exception inside the new one. This allows someone down the stack to access it as the $PSItem.Exception.InnerException.

catch
{
    throw [System.MissingFieldException]::new('Could not access field',$PSItem.Exception)
}

$PSCmdlet.ThrowTerminatingError()

The one thing that I don’t like about using throw for raw exceptions is that the error message points at the throw statement and indicates that line is where the problem is.

Unable to find the specified file.
At line:31 char:9
+         throw [System.IO.FileNotFoundException]::new()
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (:) [], FileNotFoundException
    + FullyQualifiedErrorId : Unable to find the specified file.

Having the error message tell me that my script is broken because I called throw on line 31 is a bad message for users of your script to see. It does not tell them anything useful.

Dexter Dhami pointed out that I can use ThrowTerminatingError() to correct that.

$PSCmdlet.ThrowTerminatingError(
    [System.Management.Automation.ErrorRecord]::new(
        ([System.IO.FileNotFoundException]"Could not find $Path"),
        'My.ID',
        [System.Management.Automation.ErrorCategory]::OpenError,
        $MyObject
    )
)

If we assume that ThrowTerminatingError() was called inside a function called Get-Resource, then this is the error that we would see.

Get-Resource : Could not find C:Program Files (x86)Reference
AssembliesMicrosoftFramework.NETPortablev4.6System.IO.xml
At line:6 char:5
+     Get-Resource -Path $Path
+     ~~~~~~~~~~~~
    + CategoryInfo          : OpenError: (:) [Get-Resource], FileNotFoundException
    + FullyQualifiedErrorId : My.ID,Get-Resource

Do you see how it points to the Get-Resource function as the source of the problem? That tells the user something useful.

Because $PSItem is an ErrorRecord, we can also use ThrowTerminatingError this way to re-throw.

catch
{
    $PSCmdlet.ThrowTerminatingError($PSItem)
}

This will change the source of the error to the Cmdlet and hide the internals of your function from the users of your Cmdlet.

Try can create terminating errors

Kirk Munro points out that some exceptions are only terminating errors when executed inside a try/catch block. Here is the example he gave me that generates a divide by zero runtime exception.

function Do-Something { 1/(1-1) }

Then invoke it like this to see it generate the error and still output the message.

&{ Do-Something; Write-Output "We did it. Send Email" }

But by placing that same code inside a try/catch, we see something else happen.

try
{
    &{ Do-Something; Write-Output "We did it. Send Email" }
}
catch
{
    Write-Output "Notify Admin to fix error and send email"
}

We see the error become a terminating error and not output the first message. What I don’t like about this one is that you can have this code in a function and it will act differently if someone is using a try/catch.

I have not ran into issues with this myself but it is corner case to be aware of.

$PSCmdlet.ThrowTerminatingError() inside try/catch

One nuance of $PSCmdlet.ThrowTerminatingError() is that it creates a terminating error within your Cmdlet but it turns into a non-terminating error after it leaves your Cmdlet. This leaves the burden on the caller of your function to decide how to handle the error. They can turn it back into a terminating error by using -ErrorAction Stop or calling it from within a try{...}catch{...}.

Public function templates

One last take a way I had with my conversation with Kirk Munro was that he places a try{...}catch{...} around every begin, process and end block in all of his advanced functions. In those generic catch blocks, he as a single line using $PSCmdlet.ThrowTerminatingError($PSitem) to deal with all exceptions leaving his functions.

function Do-Something
{
    [cmdletbinding()]
    param()

    process
    {
        try
        {
            ...
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSitem)
        }
    }
}

Because everything is in a try statement within his functions, everything acts consistently. This also gives clean errors to the end user that hides the internal code from the generated error.

Trap

I focused on the try/catch aspect of exceptions. But there is one legacy feature I need to mention before we wrap this up.

A trap is placed in a script or function to catch all exceptions that happen in that scope. When an exception happens, the code in the trap will get executed and then the normal code will continue. If multiple exceptions happen, then the trap will get called over and over.

trap
{
    Write-Log $PSItem.ToString()
}

throw [System.Exception]::new('first')
throw [System.Exception]::new('second')
throw [System.Exception]::new('third')

I personally never adopted this approach but I can see the value in admin or controller scripts that will log any and all exceptions, then still continues to execute.

Adding proper exception handling to your scripts will not only make them more stable, but it will also make it easier for you to troubleshoot those exceptions.

I spent a lot of time talking throw because it is a core concept when talking about exception handling. PowerShell also gave us Write-Error that handles all the situations where you would use throw. So don’t think that you need to be using throw after reading this.

Now that I have taken the time to write about exception handling in this detail, I am going to switch over to using Write-Error -Stop to generate errors in my code. I am also going to take Kirk’s advice and make ThrowTerminatingError my goto exception handler for every funciton.

Понравилась статья? Поделить с друзьями:
  • Powerpoint связанный файл недоступен ошибка диаграмма
  • Powerpoint произошла серьезная ошибка во время последнего запуска
  • Powerpoint ошибка сертификата
  • Powerpoint ошибка при запуске приложения 0xc0000142
  • Powerpoint ошибка печати