В Powershell существует несколько уровней ошибок и несколько способов их обработать. Проблемы одного уровня (Non-Terminating Errors) можно решить с помощью привычных для Powershell команд. Другой уровень ошибок (Terminating Errors) решается с помощью исключений (Exceptions) стандартного, для большинства языков, блока в виде Try, Catch и Finally.
Как Powershell обрабатывает ошибки
До рассмотрения основных методов посмотрим на теоретическую часть.
Автоматические переменные $Error
В Powershell существует множество переменных, которые создаются автоматически. Одна из таких переменных — $Error хранит в себе все ошибки за текущий сеанс PS. Например так я выведу количество ошибок и их сообщение за весь сеанс:
Get-TestTest
$Error
$Error.Count
При отсутствии каких либо ошибок мы бы получили пустой ответ, а счетчик будет равняться 0:
Переменная $Error являет массивом и мы можем по нему пройтись или обратиться по индексу что бы найти нужную ошибку:
$Error[0]
foreach ($item in $Error){$item}
Свойства объекта $Error
Так же как и все что создается в Powershell переменная $Error так же имеет свойства (дополнительную информацию) и методы. Названия свойств и методов можно увидеть через команду Get-Member:
$Error | Get-Member
Например, с помощью свойства InvocationInfo, мы можем вывести более структурный отчет об ошибки:
$Error[0].InvocationInfo
Методы объекта $Error
Например мы можем очистить логи ошибок используя clear:
$Error.clear()
Критические ошибки (Terminating Errors)
Критические (завершающие) ошибки останавливают работу скрипта. Например это может быть ошибка в названии командлета или параметра. В следующем примере команда должна была бы вернуть процессы «svchost» дважды, но из-за использования несуществующего параметра ‘—Error’ не выполнится вообще:
'svchost','svchost' | % {Get-Process -Name $PSItem} --Error
Не критические ошибки (Non-Terminating Errors)
Не критические (не завершающие) ошибки не остановят работу скрипта полностью, но могут вывести сообщение об этом. Это могут быть ошибки не в самих командлетах Powershell, а в значениях, которые вы используете. На предыдущем примере мы можем допустить опечатку в названии процессов, но команда все равно продолжит работу:
'svchost111','svchost' | % {Get-Process -Name $PSItem}
Как видно у нас появилась информация о проблеме с первым процессом ‘svchost111’, так как его не существует. Обычный процесс ‘svchost’ он у нас вывелся корректно.
Параметр ErrorVariable
Если вы не хотите использовать автоматическую переменную $Error, то сможете определять свою переменную индивидуально для каждой команды. Эта переменная определяется в параметре ErrorVariable:
'svchost111','svchost' | % {Get-Process -Name $PSItem } -ErrorVariable my_err_var
$my_err_var
Переменная будет иметь те же свойства, что и автоматическая:
$my_err_var.InvocationInfo
Обработка некритических ошибок
У нас есть два способа определения последующих действий при ‘Non-Terminating Errors’. Это правило можно задать локально и глобально (в рамках сессии). Мы сможем полностью остановить работу скрипта или вообще отменить вывод ошибок.
Приоритет ошибок с $ErrorActionPreference
Еще одна встроенная переменная в Powershell $ErrorActionPreference глобально определяет что должно случится, если у нас появится обычная ошибка. По умолчанию это значение равно ‘Continue’, что значит «вывести информацию об ошибке и продолжить работу»:
$ErrorActionPreference
Если мы поменяем значение этой переменной на ‘Stop’, то поведение скриптов и команд будет аналогично критичным ошибкам. Вы можете убедиться в этом на прошлом скрипте с неверным именем процесса:
$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem}
Т.е. скрипт был остановлен в самом начале. Значение переменной будет храниться до момента завершения сессии Powershell. При перезагрузке компьютера, например, вернется значение по умолчанию.
Ниже значение, которые мы можем установить в переменной $ErrorActionPreference:
- Continue — вывод ошибки и продолжение работы;
- Inquire — приостановит работу скрипта и спросит о дальнейших действиях;
- SilentlyContinue — скрипт продолжит свою работу без вывода ошибок;
- Stop — остановка скрипта при первой ошибке.
Самый частый параметр, который мне приходится использовать — SilentlyContinue:
$ErrorActionPreference = 'SilentlyContinue'
'svchost111','svchost' | % {Get-Process -Name $PSItem}
Использование параметра ErrorAction
Переменная $ErrorActionPreference указывает глобальный приоритет, но мы можем определить такую логику в рамках команды с параметром ErrorAction. Этот параметр имеет больший приоритет чем $ErrorActionPreference. В следующем примере, глобальная переменная определяет полную остановку скрипта, а в параметр ErrorAction говорит «не выводить ошибок и продолжить работу»:
$ErrorActionPreference = 'Stop'
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'SilentlyContinue'}
Кроме ‘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
}
Такой подход не рекомендуется использовать часто, так как вы можете пропустить что-то важное.
Мы можем вывести в блоке catch текст ошибки используя $PSItem.Exception:
try {
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch {
Write-Host "Какая-то неисправность" -ForegroundColor RED
$PSItem.Exception
}
Переменная $PSItem хранит информацию о текущей ошибке, а глобальная переменная $Error будет хранит информацию обо всех ошибках. Так, например, я выведу одну и ту же информацию:
$Error[0].Exception
Создание отдельных исключений
Что бы обработать отдельную ошибку сначала нужно найти ее имя. Это имя можно увидеть при получении свойств и методов у значения переменной $Error:
$Error[0].Exception | Get-Member
Так же сработает и в блоке Catch с $PSItem:
Для вывода только имени можно использовать свойство FullName:
$Error[0].Exception.GetType().FullName
Далее, это имя, мы вставляем в блок Catch:
try {
'svchost111','svchost' | % {Get-Process -Name $PSItem -ErrorAction 'Stop'}
}
catch [Microsoft.PowerShell.Commands.ProcessCommandException]{
Write-Host "Произошла ошибка" -ForegroundColor RED
$PSItem.Exception
}
Так же, как и было описано выше мы можем усложнять эти блоки как угодно указывая множество исключений в одном catch.
Выброс своих исключений
Иногда нужно создать свои собственные исключения. Например мы можем запретить добавлять через какой-то скрипт названия содержащие маленькие буквы или сотрудников без указания возраста и т.д. Способов создать такие ошибки — два и они тоже делятся на критические и обычные.
Выброс с throw
Throw — выбрасывает ошибку, которая останавливает работу скрипта. Этот тип ошибок относится к критическим. Например мы можем указать только текст для дополнительной информации:
$name = 'AD.1'
if ($name -match '.'){
throw 'Запрещено использовать точки в названиях'
}
Если нужно, то мы можем использовать исключения, которые уже были созданы в Powershell:
$name = 'AD.1'
if ($name -like '*.*'){
throw [System.IO.FileNotFoundException]'Запрещено использовать точки в названиях'
}
Использование Write-Error
Команда Write-Error работает так же, как и ключ ErrorAction. Мы можем просто отобразить какую-то ошибку и продолжить выполнение скрипта:
$names = @('CL1', 'AD.1', 'CL3')
foreach ($name in $names){
if ($name -like '*.*'){
Write-Error -Message 'Обычная ошибка'
}
else{
$name
}
}
При необходимости мы можем использовать параметр ErrorAction. Значения этого параметра были описаны выше. Мы можем указать значение ‘Stop’, что полностью остановит выполнение скрипта:
$names = @('CL1', 'AD.1', 'CL3')
foreach ($name in $names){
if ($name -like '*.*'){
Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'
}
else{
$name
}
}
Отличие команды Write-Error с ключом ErrorAction от обычных команд в том, что мы можем указывать исключения в параметре Exception:
Write-Error -Message 'Обычная ошибка' -ErrorAction 'Stop'
Write-Error -Message 'Исключение' -Exception [System.IO.FileNotFoundException] -ErrorAction 'Stop'
В Exception мы так же можем указывать сообщение. При этом оно будет отображаться в переменной $Error:
Write-Error -Exception [System.IO.FileNotFoundException]'Моё сообщение'
…
Теги:
#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.″
}
Для блока 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.″
}
Если для блока 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.″
}
Trap
Еще один способ обработки ошибок — это установка ловушки (trap). В этом варианте обработчик ошибок задается с помощью ключевого слова Trap, определяющего список команд, которые должны выполниться при возникновении прерывающей ошибки и исключения. Когда происходит исключение, то оболочка PowerShell ищет в коде инструкции Trap для данной ошибки, и если находит, то выполняет их.
Возьмем наиболее простой пример ловушки, которая обрабатывает все произошедшие исключения и выводит сообщение об ошибке:
trap {
Write-Host ″Error was found.″
}
Get-Content -Path C:\File\* -ErrorAction Stop
Так же, как и для try\catch, ключевое слово 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.″
Если использовать ключевое слово 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 включено ключевое слово 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 надо помнить о такой вещи, как область действия (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).
А теперь немного изменим скрипт, поместив ловушку внутрь функции:
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.″
Как видите, теперь при возникновении ошибки внутри функции будет произведена обработка ошибки, после чего выполнение будет продолжено с той строки, в которой произошла ошибка — не выходя из функции.
Заключение
Если сравнить Try/Catch и Trap, то у каждого метода есть свои достоинства и недостатки. Конструкцию Try/Catch можно более точно нацелить на возможную ошибку, так как Catch обрабатывает только содержимое блока Try. Эту особенность удобно использовать при отладке скриптов, для поиска ошибок.
И наоборот, Trap является глобальным обработчиком, отлавливая ошибки во всем скрипте независимо от расположения. На мой взгляд это более жизненный вариант, который больше подходит для постоянной работы.
You have had it happen before. You run a PowerShell script, and suddenly the console is full of errors. Did you know you can handle these errors in a much better way? Enter PowerShell try catch blocks!
Using error handling with PowerShell try catch blocks allows for managing and responding to these terminating errors. In this post, you will be introduced to PowerShell try catch blocks and learn how to handle specific exception messages.
Understanding PowerShell Try Catch Syntax
The PowerShell try catch block syntax is straightforward. It is composed of two sections enclosed in curly brackets. The first identified section is the try
block, and the second section is the catch
block.
try { # Command(s) to try } catch { # What to do with terminating errors }
The try block can have as many statements in it as you want; however, keep the statements to as few as possible, probably just a single statement. The point of error handling is to work with one statement at a time and deal with anything that occurs from the error.
Here is an example of an error occurring in the PowerShell console. The command is creating a new file using the New-Item cmdlet and specifying a non-existent folder for Path.
If this command was in a script, the output wastes some screen space, and the problem may not be immediately visible. Using a PowerShell try catch block, you can manipulate the error output and make it more readable.
Here is the same New-Item command in a try catch block. Note that line 5 uses the -ErrorAction parameter with a value of Stop to the command. Not all errors are considered “terminating,” so sometimes you need to add this bit of code to terminate into the catch block properly.
try { New-Item -Path C:\doesnotexist ` -Name myfile.txt ` -ItemType File ` -ErrorAction Stop } catch { Write-Warning -Message "Oops, ran into an issue" }
Instead of a block of red angry-looking text, you have a simple warning message that it ran into an issue. The non-existent Path name along with forcing -ErrorAction Stop drops the logic into the catch block and displays the custom warning.
Adding the $Error Variable to Catch Output
While more readable, this is not very helpful. All you know is the command did not complete successfully, but you don’t know why. Instead of displaying my custom message, you can display the specific error message that occurred instead of the entire exception block.
When an error occurs in the try block, it is saved to the automatic variable named $Error. The $Error variable contains an array of recent errors, and you can reference the most recent error in the array at index 0.
try { New-Item -Path C:\doesnotexist ` -Name myfile.txt ` -ItemType File ` -ErrorAction Stop } catch { Write-Warning $Error[0] }
The warning output is now more descriptive showing that the command failed because it couldn’t find part of the path. This message was a part of our original error message but is now more concise.
Alternatively, you can save the incoming error to a variable using $_
. The dollar sign + underscore in PowerShell indicates the current item in the pipeline. In this case, the current item is the error message coming out of the try block.
Once you have saved this incoming message, you use it in a custom output message, like so:
try { New-Item -Path C:\doesnotexist ` -Name myfile.txt ` -ItemType File ` -ErrorAction Stop } catch { $message = $_ Write-Warning "Something happened! $message" }
Adding Exception Messages to Catch Blocks
You can also use multiple catch blocks to handle specific errors differently. This example displays two different custom messages:
- One for if the path does not exist
- One for if an illegal character is used in the name
Note that the following screenshot shows the script running twice with two different commands in the try block. Each command, catch block, and the orange and green arrows indicate the final output.
Looking at lines 14-16, there is a third catch block without an exception message. This is a “catch-all” block that will run if the error does not match any other exceptions. If you are running this script and seeing the message in the last catch block, you know the error is not related to an illegal character in the file name or part of the path not being valid.
Now how do you find the exception messages to use in the first two catch blocks? You find it by looking at the different information attached to the $Error
variable. After a failed command occurs, you can run $Error[0].Exception.GetType().FullName to view the exception message for the last error that occurred.
Going back to the PowerShell console, rerun the New-Item
command with a non-existent path, then run the $Error command to find the exception message.
The red text immediately following the failed command also contains the exception message but does not contain which module it is from. Looking at the $Error variable shows the full message to be used for a catch block.
Getting Exception Messages in PowerShell 7
PowerShell version 7 introduced the Get-Error
command. This command displays the most recent error from the current session. After encountering an error, run Get-Error to show the exception type to use in the catch
block.
Summary
Using PowerShell try catch blocks gives additional power to handle errors in a script and take different actions based on the error. The catch block can display more than just an error message. It can contain logic that will resolve the error and continue executing the rest of the script.
Do you have any tips on using try/catch blocks? Leave a comment below or find me on Twitter or LinkedIn for additional discussion.
Enjoyed this article? Check out more of my PowerShell articles here!
References:
Microsoft Docs: About Try Catch Finally
Microsoft Docs: About Automatic Variables ($Error)
Microsoft Blog: Understanding Non-Terminating Errors in 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
- Throw
- $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-Error
generates 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( '\\test\no\filefound.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:\blog\throwerror.ps1
Line : Get-Resource
PositionMessage : At C:\blog\throwerror.ps1:5 char:5
+ Get-Resource
+ ~~~~~~~~~~~~
PSScriptRoot : C:\blog
PSCommandPath : C:\blog\throwerror.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:\blog\throwerror.ps1: line 13
at Do-Something, C:\blog\throwerror.ps1: line 5
at <ScriptBlock>, C:\blog\throwerror.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
Assemblies\Microsoft\Framework\.NETPortable\v4.6\System.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.
Have you ever run a script or a PowerShell cmdlet and get confronted with a screaming wall of text – in red – like the one shown below?
Not a reader? Watch this related video tutorial!
Not seeing the video? Make sure your ad blocker is disabled.
Errors can become overwhelming and confusing. And most of all, errors are often hard to read, which makes determining what and where the script went wrong near impossible.
Luckily, you have some options in PowerShell to make this better through error handling. Using error handling, errors can be filtered and displayed in such a way that it’s easier to make sense of. And, understanding the error makes it easy to add more logic to error handling.
In this article, you will learn about errors in PowerShell and how they can be intercepted to perform error handling using the PowerShell Try Catch
blocks (and finally
blocks).
Understanding How Errors Work in PowerShell
Before diving into error handling, let’s first cover a few concepts around errors in PowerShell. Understanding errors can lead to better error handling strategies.
The $Error
Automatic Variable
In PowerShell, there are a lot of automatic variables, and one of them is the $Error
automatic variable. PowerShell uses the $Error
variable to store all errors that are encountered in the session. The $Error
variable is an array of errors sorted by most recent.
When you first open a PowerShell session, the $Error
variable is empty. You can check it so by calling the $Error
variable.
As you can see, the $Error
variable starts off empty. But, once an error is generated, the error will be added and stored into the $Error
variable.
In the example below, the error is generated by deliberately getting a service name that does not exist.
PS> Get-Service xyz
PS> $Error
PS> $Error.Count
As you can see from the output above, the generated error was added to the $Error
variable.
The $Error variable contains a collection of errors generated in the PowerShell session. Each error can be access by calling its array position. The latest error will always be at index 0.
For example, the latest error can be retrieved using
$Error[0]
.
The $Error
Object Properties
Since everything in PowerShell is an object, the $Error
variable is an object, and objects have properties. By piping the $Error
variable to the Get-Member
cmdlet, you should see the list of the properties available.
To determine the reason for the error, you can view the content of the InvocationInfo
property using the command below.
Now, you could do the same with the other properties and discover what other information you can find!
Terminating Errors
Terminating errors stop the execution flow when it is encountered by PowerShell vs non-terminating errors. There are several ways a terminating error can occur. One example is when you call a cmdlet with a parameter that does not exist.
As you’ll from the screenshot below, when the command Get-Process notepad
runs, the command is valid, and the details of the notepad process are displayed.
But, when a parameter that does not exist is used like Get-Process notepad -handle 251
, the cmdlet displays an error that the handle
parameter is not valid. Then, the cmdlet exits without showing the details of the notepad
process.
Non-Terminating Errors
Non-terminating errors are errors that do not stop the execution of the script or command. For example, check out the code below. This code gets the list of file names from the fileslist.txt file. Then, the script goes through each file name, read the contents of each file, and outputs it on the screen.
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file
}
The contents of the filelist.txt file are the file names shows in the list below.
File_1.log
File_2.log
File_3.log
File_4.log
File_5.log
File_6.log
File_7.log
File_8.log
File_9.log
File_10.log
But what if File_6.log didn’t actually exist? When you run the code, you’d expect an error will happen because the script cannot find the File_6.log. You’ll see a similar output shown below.
As you can see from the screenshot of the result above, the script was able to read the first five files in the list, but when it tried to read the file File_6.txt, an error is returned. The script then continued to read the rest of the files before exiting. It did not terminate.
The $ErrorActionPreference
Variable
So far, you’ve learned about the terminating and non-terminating errors and how they differ from each other. But, did you know that a non-terminating error can be forced to be treated as a terminating error?
PowerShell has a concept called preference variables. These variables are used to change how PowerShell behaves many different ways. One of these variables is called $ErrorActionPreference
.
The $ErrorActionPreference
variable is used to change the way PowerShell treats non-terminating errors. By default, the $ErrorActionPreference
value is set to Continue
. Changing the value of the $ErrorActionPreference
variable to STOP
forces PowerShell to treat all errors as terminating errors.
Use the code below to change the $ErrorActionPreference
value.
$ErrorActionPreference = "STOP"
To learn more about other valid $ErrorActionPreference variable values, visit PowerShell ErrorActionPreference.
Now, refer back to the example used in the Non-Terminating Errors section in this article. The script can modified to include the change in $ErrorActionPreference
like the code shown below:
# Set the $ErrorActionPreference value to STOP
$ErrorActionPreference = "STOP"
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file
}
Running the modified code above will behave differently than before when the $ErrorActionPreference
value is set to the default value of Continue
.
$ErrorActionPreference
variableAs you can see from the screenshot of the result above, the script was able to read the first five files in the list, but when it tried to read the file File_6.txt, an error is returned because the file was not found. Then, the script terminated, and the rest of the files are not read.
The
$ErrorActionPreference
value is only valid in the current PowerShell session. It resets to the default value once a new PowerShell session is started.
The ErrorAction
Common Parameter
If the $ErrorActionPreference
value is applied to the PowerShell session, the ErrorAction
parameter applies to any cmdlet that supports common parameters. The ErrorAction
parameter accepts the same values that the $ErrorActionPreference
variable does.
The ErrorAction
parameter value takes precedence over the $ErrorActionPreference
value.
Let’s go back and use the same code in the previous example. But, this time, the ErrorAction
parameter is added to the Get-Content
line.
# Set the $ErrorActionPreference value to default (CONTINUE)
$ErrorActionPreference = "CONTINUE"
$file_list = Get-Content .\filelist.txt
foreach ($file in $file_list) {
Write-Output "Reading file $file"
# Use the -ErrorAction common parameter
Get-Content $file -ErrorAction STOP
}
After running the modified code, you’ll see that even though the $ErrorActionPreference
is set to Continue
, the script still terminated once it encountered an error. The script terminated because the PowerShell ErrorAction
parameter value in Get-Content
is set to STOP
.
ErrorAction
parameterUsing PowerShell Try Catch Blocks
At this point, you’ve learned about PowerShell errors and how the $ErrorActionPreference
variable and PowerShell ErrorAction
parameters work. Now, it’s time you learn about the good stuff – the PowerShell Try Catch Finally
blocks.
PowerShell try catch
blocks (and optional finally block
) are a way to cast a net around a piece of code and catch any errors that return.
The code below shows the syntax of the Try
statement.
try {
<statement list>
}
catch [[<error type>][',' <error type>]*]{
<statement list>
}
finally {
<statement list>
}
The Try
block contains the code that you want PowerShell to “try” and monitor for errors. If the code in the Try
block encounters an error, the error is added to the $Error
variable and then passed to the Catch
block.
The Catch
block contains the actions to execute when it receives an error from the Try
block. There can be multiple Catch
blocks in a Try
statement.
The Finally
block contains that code that will at the end of the Try
statement. This block runs whether or not an error was uncounted.
Catching Non-Specific Errors (Catch-All) with PowerShell ErrorAction
A simple Try
statement contains a Try
and a Catch
block. The Finally
block is optional.
For example, to catch a non-specific exception, the Catch
parameter should be empty. The example code below is using the same script that was used in the The $ErrorActionPreference Variable section but modified to use the Try Catch
blocks.
As you can see from the code below, this time, the foreach
statement is enclosed inside the Try
block. Then, a Catch
block contains the code to display the string An Error Occurred
if an error happened. The code in the Finally
block just clears the $Error
variable.
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch {
Write-Host "An Error Occured" -ForegroundColor RED
}
finally {
$Error.Clear()
}
The code above, after running in PowerShell, will give you this output shown below.
The output above shows that the script encountered an error, ran the code inside the Catch
block, and then terminated.
The error was handled, which was the point of error handling. However, the error displayed was too generic. To show a more descriptive error, you could access the Exception
property of the error that was passed by the Try
block.
The code below is modified, specifically the code inside the Catch
block, to display the exception message from the current error that was passed down the pipeline – $PSItem.Exception.Message
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch {
Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
$Error.Clear()
}
This time, when the modified code above is run, the message displayed is a lot more descriptive.
Catching Specific Errors
There are times when a catch-all error handling is not the most appropriate approach. Perhaps, you want your script to perform an action that is dependent on the type of error that is encountered.
How do you determine the error type? By checking the TypeName
value of the Exception
property of the last error. For example, to find the error type from the previous example, use this command:
$Error[0].Exception | Get-Member
The result of the code above would look like the screenshot below. As you can see, the TypeName
value is displayed – System.Management.Automation.ItemNotFoundException
.
Now that you know the error type that you need to intercept, modify the code to catch it specifically. As you see from the modified code below, there are now two Catch
blocks. The first Catch
block intercepts a specific type of error (System.Management.Automation.ItemNotFoundException
). In contrast, the second Catch
block contains the generic, catch-all error message.
$file_list = Get-Content .\filelist.txt
try {
foreach ($file in $file_list) {
Write-Output "Reading file $file"
Get-Content $file -ErrorAction STOP
}
}
catch [System.Management.Automation.ItemNotFoundException]{
Write-Host "The file $file is not found." -ForegroundColor RED
}
catch {
Write-Host $PSItem.Exception.Message -ForegroundColor RED
}
finally {
$Error.Clear()
}
The screenshot below shows the output of the modified code above.
Conclusion
In this article, you’ve learned about errors in PowerShell, its properties, and how you can determine an error’s specific type. You’ve also learned the difference between how the $ErrorActionPreference
variable and the PowerShell ErrorAction
parameter affects how PowerShell treats non-terminating errors.
You have also learned how to use the PowerShell Try Catch Finally
blocks to perform error handling, whether for specific errors or a catch-all approach.
The examples that are shown in this article only demonstrates the basics of how the Try Catch Finally
blocks work. The knowledge that I hope you have gained in this article should give you the starting blocks to start applying error handling in your scripts.
Further Reading
- About_Try_Catch_Finally
- About_Automatic_Variables
- Back to Basics: The PowerShell foreach Loop
- Back to Basics: Understanding PowerShell Objects