Murphy’s Law is an old saying that promises, «Anything that can go wrong will go wrong.» Coders and programmers know this saying particularly well. If you’ve spent any time writing code, you understand why. What coder doesn’t know the feeling of writing the perfect script that accomplishes just what you need, but some external variable pops up that causes it to malfunction?
In this blog post, we’re going to discuss the process for anticipating potential errors, mistakes and problems with your scripts and code: error handling. We’ll look at a small, simple program and insert some key lines and functions that will show you how to spot errors and identify them, rather than let your programs and machines churn along with faulty inputs.
What is a PowerShell Exception?
Quick Definition: A PowerShell exception is an error that happens while running PowerShell scripts, errors that PowerShell needs to be handled for it. PowerShell will try to handle errors on its own, but exceptions are, as the name may suggest, exceptions to its ability to do so. When an exception occurs, the phrase used is «throw». Handling a «thrown» exception means «catching» it, which is telling the script what to do. If a thrown exception isn’t caught, the script stops.
What is PowerShell Error Handling?
Quick Definition: Error handling in PowerShell is a process of analyzing your code for where exceptions could occur, for what exceptions could happen, and then writing code that anticipates and handles those exceptions.
An Overview of PowerShell Error Handling
In this video, Don Jones covers error handling in PowerShell. Something that sets a good script apart from a truly great one is its ability to respond to errors that you anticipated ahead of time. Watch as Don demonstrates the fine art of error handling.
When is Error Handling Necessary?
One of the things that really sets apart a good script from a great script is the script’s ability to respond to errors that you can anticipate ahead of time. It’s called Error Handling.
If you’re writing code or programming behavior for a computer, doing error handling can help in a number of ways. First of all, written well, a program can inform you what’s wrong — like a patient explaining symptoms to their doctor. Not only that, a program that stops at an exception won’t waste time and resources continuing a process that’s doomed to failure.
Don’t mistake error handling with faulty scripting. Errors in code can be a problem too, but usually it’s easy to spot those before running the program. Most people know the old «missing semicolon ruins day» problem. Error handling is about finding externalities — variables that you and your program don’t have control over while the program is doing its job.
Since the goal of error handling is to anticipate errors and then deal with them as they occur rather than just allow PowerShell to explode, the first step is spending some time with the program. Take a look at the following script and try to spot where you think errors might occur.
param (
$computername = 'localhost'
)
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
$props = @ { 'ComputerName'=$computername;
'OSVersion'=$os.caption;
'TotalRAM'=$system.TotalPhysicalMemory;
'Manufacturer'=$system.Manufacturer}
$obj = New-Object =TypeName PowerShellObject -Property $props
Write-Output $obj
Now, because this is a short one, it’s reasonably safe to assume that errors are going to happen on one of these two lines – if they happen anywhere:
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
These are really the only two lines that are doing anything. They’re «leaving» the code and working with data from an external source. They’re working with an external entity. Some of the errors that we can anticipate include the computer in question not being available when we try to query it, or it could be that we don’t have permission to query it. There are a few things that could go wrong, but they’ll likely happen on those lines.
How to Code PowerShell Error Handling
There are three big steps to writing error handling in PowerShell: first is to identify where an error may occur: which command. Then, you put that command inside a try { } block. Third, you put inside a catch { } block what should be done if the error happens. We’ll walk through these three steps next.
First of all, choose one command — typically one. While it’s possible to do error handling for a block of commands, it’s usually best to limit your focus. Plus, the method we’ll be demonstrating below works best on one command. So, choose your one command and wrap it in a try-catch block.
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
That’s part of the first step to writing PowerShell error handling: identify the command you think might cause an error. The second step is to put it inside a try { } block. Although the third step is to put inside the catch { } block whatever we want to do in the event of an error occurring, there’s another tweak we have to do first.
Most PowerShell commands are going to need a little bit of extra work. Still inside the try { } block, you’ll have to add a parameter to your function called ErrorAction and set it to «Stop».
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername
} catch {
}
Sometimes you’ll see that as -EA, which is what we’ll abbreviate it as going forward. Now, it’s possible you might want to take a different action based on the type of error that occurs. And so, you can capture the error in an -ErrorVariable. We’re going to call it «x».
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop
} catch {
}
Sometimes you’ll see that abbreviated -EV, which is again what we’ll abbreviate it as going forward. Also, it’s important to note that the variable name for the error does not include a dollar sign ($).
Next, we have to ask ourselves what we want to do about it when an error occurs? Well, let’s say we want to write the computer name that failed to a log file. To do that, we take the computer name variable $computername, and pump it to a file. And Append it to whatever else is in there:
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
}
Maybe we also want to display a warning on the screen. We can make up our own error message, but for now let’s keep it pretty boilerplate: «Error talking to $computername : $x»
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
$computername | out-File c:errors.txt -Append
}
Note, be sure to keep that additional space behind the first variable «$computername». And, by using double quotation marks, we can include these variables and they’ll be expanded into their values.
How to Optimize Your PowerShell Error Handling
The trick to good PowerShell error handling is applying logic and reasoning to your code. Really good error handling anticipates errors and thinks about their consequences. Before we move on, let’s take a look at what our changes so far have gotten us:
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
$computername | out-File c:errors.txt -Append
Write-Warning "Error talking to $computername : $x"
}
$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
$props = @ { 'ComputerName'=$computername;
'OSVersion'=$os.caption;
'TotalRAM'=$system.TotalPhysicalMemory;
'Manufacturer'=$system.Manufacturer}
$obj = New-Object =TypeName PowerShellObject -Property $props
Write-Output $obj
But let’s think about this. If the computer doesn’t respond to the first query,
try {
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
$computername | out-File c:errors.txt -Append
Write-Warning "Error talking to $computername : $x"
}
Then, down inside the catch { }, we’ll tell the program to set that variable to false if and when we get an error:
try {
$everything_is_ok = $true
$os = Get-WmiObject -Class Win32_OperatingSystem -ComputerName $computername -EA Stop -EV x
} catch {
$computername | out-File c:errors.txt -Append
Write-Warning "Error talking to $computername : $x"
}
And then, wrap the output in an if statement regarding that variable:
$system = Get-WmiObject -Class Win32_ComputerSystem -ComputerName $computername
$props = @ { 'ComputerName'=$computername;
'OSVersion'=$os.caption;
'TotalRAM'=$system.TotalPhysicalMemory;
'Manufacturer'=$system.Manufacturer}
$obj = New-Object =TypeName PowerShellObject -Property $props
Write-Output $obj
So what is this doing? What it’s telling our program is that if our first function fails, the lines inside our catch { } will execute. When that happens, our variable becomes false, and none of the subsequent lines wrapped inside the if { } will ever execute. After all, if the first query failed, there’s no point in trying the second one – it’ll almost certainly fail too.
Once again, this is a small example. But hopefully it helps illustrate one of the approaches you can take to uncovering not just the first thrown exception in a program, but what to do about subsequent functions too.
How to Test Your PowerShell Error Handling
It’s always a good idea to check your code, both for success states and failure states, and this program is no different. If you can do it from your own device, go ahead and save the above code and give it a test. The program we’ve written is going to search for a server «localhost», which should obviously be discoverable on the network. If you’re like us, running the program results in success.
Of course, it’s not enough that your successes succeed correctly. You also want your failures to fail correctly. The way we can test for the errors we’ve anticipated is by changing our first parameter, «localhost» to the name of a server that definitely doesn’t exist on the network. We went with «NOTONLINE».
After we run that, eventually there will be an error. We see: «Error talking to NOTONLINE» followed by the exception that occurred: «The RPC server is unavailable.»
Not only that, but we can pull up errors.txt and see that the name of that computer was successfully logged to the file.
The thing to remember about Error Handling in PowerShell is the -ErrorAction. That tells the command, «Look, if a problem happens, rather than just keep going and trying to continue, I want you to stop and throw a complete exception — a trappable exception.» Without the -EA Stop, no part of the try-catch block would function.
In fact, removing it shows quite a different result. We’ll leave our «NOTONLINE» server name, remove the -EA Stop from our code, and try running the code again.
If you’re doing this too, you might find that your Windows Management Instrumentation takes a while to time out. But once it finally does, you should see an error that’s far less manageable. That’s because this time the error comes directly from the command. It’s not a warning generated by the code which detected an error, it’s a full-on uncaught exception. Not only that, but the second command tried to run, and a different WMI error came through for that. Because the input for the second function was an error, the output of it is gibberish.
The difference should be obvious: if you can avoid a full-blown error and instead catch every exception as a warning and halt the functions, you do a lot for your program. Not only do you prevent time, energy, and resources from being wasted, but you also increase the chances of pinpointing where the error occurred and how you can fix it.
If you leave PowerShell to its own devices, it’ll try to carry out your commands as best it can, with increasingly faulty data coming from error after error. Instead, tell it you want it to stop when there’s an error so that you can fix what caused it. You should see now that the -ErrorAction Stop is the key to making ErrorHandling work in PowerShell.
Wrapping Up
If you’re looking to master PowerShell, CBT Nuggets has training that will do much more than one blog post could. Here, we only had time to address some of the top-level ideas of PowerShell error handling, but consider this PowerShell 7 training that features 9 hours of training and 104 videos about scripts, and automation.
Welcome to my Getting Started with Windows PowerShell series!
Why Handle Errors?
Error handling may take some extra time to create, however, it can really save you time in the long run. Let’s say you have a script that performs the following tasks:
-
Takes information from a database / file and stores it in $userAccounts
-
Uses a loop to perform processing on each $user in $userAccounts
-
Update a user’s AD information if the user is in the list
-
Disable any accounts that are not in the list
-
The script runs, and seems to be working great for a few weeks. Then, all of a sudden, the database server goes down, and $userAccounts is empty. The script runs, doesn’t see any users, and what likely happens next is all user accounts are deleted. While this example is a little extreme, it is something that can happen. There are also a lot of in-between cases that can make error handling worth it. It’s a good mindset to have when you’re writing scripts as you can write to incorporate it off the bat.
Errors in PowerShell
Errors in PowerShell are stored in the automatic variable $error. You can see how many errors you’ve encountered in your session by using:
$error.count
This is essentially an array of errors, to access the first value you can use:
$error[0]
Let’s take a look at $error[0] more closely, via:
$error[0] | Get-Member
We can view information on the command that raised the error via:
$error[0].InvocationInfo
It looks like the line property contains the full command we used when the error was encountered.
$error[0].InvocationInfo.Line
The exception that raised the error can be accessed via:
$error[0].Exception
We can also get more information about the exception via:
$error[0].Exception | Get-Member
Here you can see the TypeName: [System.UnauthorizedAccessException], and the various methods and properties of the exception. The exception TypeName is used later when we catch specific errors.
Looks like to get the exception’s message in string format we’d use:
$error[0].Exception.Message
Now that we’ve dug into the automatic variable $error, let’s move on to the different types of errors we can encounter.
Terminating Errors
Terminating errors in PowerShell mean that the script can no longer continue to run with the information it has encountered, or has been given. If you do not have a way to catch the error, it will likely display the nasty red error text you’ve likely seen before. Terminating errors halt anything running in the pipeline. This terminates output, aside from the error message of the terminating error.
Here’s an example of a terminating error that was caused by calling a command that does not exist:
Get-TerminatingError
Non-Terminating Errors
Non-terminating errors in PowerShell do not stop the pipeline from executing. These errors are handled internally by the Cmdlet, and are not able to be captured via error handling. There are ways, however, to make PowerShell treat non-terminating errors like terminating errors. That way we can use error handling to capture what’s going on.
Here’s an example of a non-terminating error (access denied to a subfolder), when attempting to list all folders and subfolders in «C:Windowsappcompat».
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse
Force Non-Terminating Errors to Terminate
You can force non-terminating errors to become terminating errors in PowerShell. There are a couple ways you can do this. Why would you want to do this, you ask? Since non-terminating errors cannot be handled by us, we need to force them to terminate, and gain the functionality of handling the error ourselves. This is not always needed, but it is good to know that you can do it if you come across a use case for yourself.
$errorActionPreference
You can do it at a global way for the session via the $ErrorActionPreference variable. This is a special variable in PowerShell that allows you to control what happens when a non-terminating error is encountered.
Here are the values for $ErrorActionPreference.
-
Stop
-
Display error, and stop execution.
-
-
Inquire
-
Display error, and ask to continue.
-
-
Continue (Default)
-
This is the default setting. Display error, then continue execution.
-
-
Suspend
-
This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.
-
-
SilentlyContinue
-
No error is displayed, execution is continued.
-
Let’s see this in action.
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse;Write-Host 'Test'
As you can see, with a non-terminating error, the next command in the sequence is executed. Therefore we see ‘Test’. Let’s set the $errorActionPreference automatic variable to Stop, and re-run the same command.
$ErrorActionPreference = 'Stop'
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse;Write-Host 'Test'
Use the Command’s -ErrorAction Parameter
Cmdlet’s and functions/scripts/modules that use [cmdletbinding()] enable utilization of the -ErrorAction common parameter. This parameter allows you to specify different actions to take when an error is encountered.
-
Stop
-
Display error, and stop execution.
-
-
Inquire
-
Display error, and ask to continue.
-
-
Continue (Default)
-
This is the default setting. Display error, then continue execution.
-
-
Suspend
-
This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.
-
-
SilentlyContinue
-
No error is displayed, execution is continued.
-
-
Ignore
-
The same as SilentlyContinue, but as SilentlyContinue still adds the message to the $error automatic variable, Ignore does not do this.
-
Let’s set our $errorActionPreference to Continue, and then look at using the Cmdlet Get-ChildItem‘s -ErrorAction parameter.
We’re setting $errorActionPreference back to Continue as earlier we set it to Stop for our other example. Continue is the default value.
$ErrorActionPreference = 'Continue'
Get-ChildItem -Path 'C:Windowsappcompat' -Recurse -ErrorAction Stop;Write-Host 'Test'
Error Handling
There are a few different ways to to handle errors in PowerShell. The best way of course, is to never let them happen in the first place! To do that, it is good to have some data validation in place via an if statement, or whichever method suites your needs.
If a terminating error occurs, we can use Try/Catch/Finally blocks to perform different actions. If it is non-terminating, we can force it to become a terminating error, and then choose how to continue.
Validation
The simplest method of validation is the if statement.
If you’d like to run these examples yourself, go ahead and fire up the PowerShell ISE. Save a file in C:PowerShell as part11.ps1.
An if statement is constructed as follows:
if (condition -eq $true) {
Do-Stuff
} else {
Do-Other-Stuff
}
The condition can be anything that resolves to a true value. That includes running a command that output (other than and error) occurs! Check this out:
if (Get-ChildItem Z: -ErrorAction SilentlyContinue) { Write-Host 'I can list the contents of Z:!' } else { Write-Host 'I cannot list the contents of Z:!' }
This does not return true, as expected, and thus we see the message from the code in the else portion of the if statement. We use the -ErrorAction common parameter with the value SilentlyContinue to suppress the error from being displayed to the end user of the script. No need in this case, as we’re simply using the command for validation.
You can also use variables with the if statement to see if they are blank.
$myVariable = $null if ($myVariable) { Write-Host "We have information! Let's do stuff." } else { Write-Host "`$myVariable is empty :(" }
The variable is empty, so the code in the else portion of the if statement is executed.
Try/Catch/Finally
The Try, Catch, and Finally blocks in PowerShell allow us to capture terminating errors.
The Try block contains the code you’d like to execute, and catch any potential errors that happen.
The Catch block contains the code you’d like to execute after a terminating error has occurred. The current error will be accessible via the automatic variable $_.
The Finally block contains the code you’d like to run after the event has occurred. This is good for cleanup tasks.
It is worth noting that finally block is not required.
Here is an example using Try/Catch/Finally:
Try { $command = 'Invoke-FakeCommand' Write-Host "Attempting to run: [Invoke-Expression -Command $command]"`n Invoke-Expression -Command $command } Catch { Write-Host $_.Exception.Message`n } Finally { Write-Host "Clean up: `$commmand = `$null"`n $commmand = $null }
The code in the Try block executes and we see the output of Write-Host. We then see the error message that occurs, as our Catch block is writing out $_.Exception.Message. As we learned earlier, that is the string value of the exception that raised the error.
We then see the output from the Write-Host command in our Finally block. We use the finally block to free up the $command variable.
This version of Try/Catch will catch any terminating errors that are raised. To capture specific exceptions, you’ll want to use the exception’s TypeName.
Catch Specific Errors
Let’s take a look at the following:
Try { Get-ThisWontWork } Catch [System.Management.Automation.CommandNotFoundException] { Write-Host "Command not found!"`n -ForegroundColor Red Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue }
The exception was caught, and the code in the Catch block was executed.
Now let’s try this…
Try { Get-ChildItem -Path Z: -ErrorAction Stop Get-ThisWontWork } Catch [System.Management.Automation.CommandNotFoundException] { Write-Host "Command not found!"`n -ForegroundColor Red Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue }
Ahh! An error that wasn’t handled! Luckily we can add multiple catch blocks.
Let’s add another Catch block.
Catch {
Write-Host $_.Exception.Message
}
Try { Get-ChildItem -Path Z: -ErrorAction Stop Get-ThisWontWork } Catch [System.Management.Automation.CommandNotFoundException] { Write-Host "Command not found!"`n -ForegroundColor Red Write-Host "Message: [$($_.Exception.Message)"] -ForegroundColor Red -BackgroundColor DarkBlue } Catch { Write-Host $_.Exception.Message }
Now the exception from Get-ChildItem command is caught since the catch-all Catch block code is executed.
Getting Error Information
It can be handy to have a shortcut that shows you error information, which you can use to create specific Catch blocks. To do this, I created a function that I call in the Catch block which utilizes some validation itself! Here is the function:
function Get-ErrorInformation { [cmdletbinding()] param($incomingError) if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) { Write-Host `n"Error information:"`n Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n if ($incomingError.InvocationInfo.Line) { Write-Host `t"Command : [$($incomingError.InvocationInfo.Line.Trim())]" } else { Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n } Write-Host `t"Exception : [$($incomingError.Exception.Message)]"`n Write-Host `t"Target Object : [$($incomingError.TargetObject)]"`n } Else { Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue } }
Here is the full code I’ll be running for this example:
function Get-ErrorInformation { [cmdletbinding()] param($incomingError) if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) { Write-Host `n"Error information:"`n Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n if ($incomingError.InvocationInfo.Line) { Write-Host `t"Command : [$($incomingError.InvocationInfo.Line.Trim())]" } else { Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n } Write-Host `t"Exception : [$($incomingError.Exception.Message)]"`n Write-Host `t"Target Object : [$($incomingError.TargetObject)]"`n } Else { Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue } } Try { Get-ChildItem -Path Z: -ErrorAction Stop Get-ThisWontWork } Catch [System.Management.Automation.CommandNotFoundException] { Write-Host 'Command not found Catch block executed!' } Catch { Get-ErrorInformation -incomingError $_ }
So now, if we wanted to catch this exception on it’s own, we would need to add a catch block for [System.Management.Automation.DriveNotFoundException].
Catch [System.Management.Automation.CommandNotFoundException] {
Write-Host 'Command not found Catch block executed!'
}
Let’s add that to our code and run the following:
function Get-ErrorInformation { [cmdletbinding()] param($incomingError) if ($incomingError -and (($incomingError| Get-Member | Select-Object -ExpandProperty TypeName -Unique) -eq 'System.Management.Automation.ErrorRecord')) { Write-Host `n"Error information:"`n Write-Host `t"Exception type for catch: [$($IncomingError.Exception | Get-Member | Select-Object -ExpandProperty TypeName -Unique)]"`n if ($incomingError.InvocationInfo.Line) { Write-Host `t"Command : [$($incomingError.InvocationInfo.Line.Trim())]" } else { Write-Host `t"Unable to get command information! Multiple catch blocks can do this :("`n } Write-Host `t"Exception : [$($incomingError.Exception.Message)]"`n Write-Host `t"Target Object : [$($incomingError.TargetObject)]"`n } Else { Write-Host "Please include a valid error record when using this function!" -ForegroundColor Red -BackgroundColor DarkBlue } } Try { Get-ChildItem -Path Z: -ErrorAction Stop Get-ThisWontWork } Catch [System.Management.Automation.CommandNotFoundException] { Write-Host 'Command not found Catch block executed!' } Catch [System.Management.Automation.DriveNotFoundException] { Write-Host 'Get-ChildItem drive not found Catch block executed!' } Catch { Get-ErrorInformation -incomingError $_ }
There we go! Now all our errors are handled, minus the ones we don’t know about yet.
Homework
-
Figure out why when multiple Catch blocks are used, it doesn’t pass along the execution information (unable to get command/line).
-
Let me know why this is!
-
-
Where else could I have added error handling in any of these examples?
I hope you’ve enjoyed the series so far! As always, leave a comment if you have any feedback or questions!
-Ginger Ninja
[Back to Top]
В 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
#ошибки
Controlling Error Reporting Behavior and Intercepting Errors
This section briefly demonstrates how to use each of PowerShell’s statements, variables and parameters that are related to the reporting or handling of errors.
The $Error Variable
$Error is an automatic global variable in PowerShell which always contains an ArrayList of zero or more ErrorRecord objects. As new errors occur, they are added to the beginning of this list, so you can always get information about the most recent error by looking at $Error[0]. Both Terminating and Non-Terminating errors will be contained in this list.
Aside from accessing the objects in the list with array syntax, there are two other common tasks that are performed with the $Error variable: you can check how many errors are currently in the list by checking the $Error.Count property, and you can remove all errors from the list with the $Error.Clear() method. For example:
Figure 2.1: Using $Error to access error information, check the count, and clear the list.
If you’re planning to make use of the $Error variable in your scripts, keep in mind that it may already contain information about errors that happened in the current PowerShell session before your script was even started. Also, some people consider it a bad practice to clear the $Error variable inside a script; since it’s a variable global to the PowerShell session, the person that called your script might want to review the contents of $Error after it completes.
ErrorVariable
The ErrorVariable common parameter provides you with an alternative to using the built-in $Error collection. Unlike $Error, your ErrorVariable will only contain errors that occurred from the command you’re calling, instead of potentially having errors from elsewhere in the current PowerShell session. This also avoids having to clear the $Error list (and the breach of etiquette that entails.)
When using ErrorVariable, if you want to append to the error variable instead of overwriting it, place a + sign in front of the variable’s name. Note that you do not use a dollar sign when you pass a variable name to the ErrorVariable parameter, but you do use the dollar sign later when you check its value.
The variable assigned to the ErrorVariable parameter will never be null; if no errors occurred, it will contain an ArrayList object with a Count of 0, as seen in figure 2.2:
Figure 2.2: Demonstrating the use of the ErrorVariable parameter.
$MaximumErrorCount
By default, the $Error variable can only contain a maximum of 256 errors before it starts to lose the oldest ones on the list. You can adjust this behavior by modifying the $MaximumErrorCount variable.
ErrorAction and $ErrorActionPreference
There are several ways you can control PowerShell’s handling / reporting behavior. The ones you will probably use most often are the ErrorAction common parameter and the $ErrorActionPreference variable.
The ErrorAction parameter can be passed to any Cmdlet or Advanced Function, and can have one of the following values: Continue (the default), SilentlyContinue, Stop, Inquire, Ignore (only in PowerShell 3.0 or later), and Suspend (only for workflows; will not be discussed further here.) It affects how the Cmdlet behaves when it produces a non-terminating error.
- The default value of Continue causes the error to be written to the Error stream and added to the $Error variable, and then the Cmdlet continues processing.
- A value of SilentlyContinue only adds the error to the $Error variable; it does not write the error to the Error stream (so it will not be displayed at the console).
- A value of Ignore both suppresses the error message and does not add it to the $Error variable. This option was added with PowerShell 3.0.
- A value of Stop causes non-terminating errors to be treated as terminating errors instead, immediately halting the Cmdlet’s execution. This also enables you to intercept those errors in a Try/Catch or Trap statement, as described later in this section.
- A value of Inquire causes PowerShell to ask the user whether the script should continue or not when an error occurs.
The $ErrorActionPreference variable can be used just like the ErrorAction parameter, with a couple of exceptions: you cannot set $ErrorActionPreference to either Ignore or Suspend. Also, $ErrorActionPreference affects your current scope in addition to any child commands you call; this subtle difference has the effect of allowing you to control the behavior of errors that are produced by .NET methods, or other causes such as PowerShell encountering a «command not found» error.
Figure 2.3 demonstrates the effects of the three most commonly used $ErrorActionPreference settings.
Figure 2.3: Behavior of $ErrorActionPreference
Try/Catch/Finally
The Try/Catch/Finally statements, added in PowerShell 2.0, are the preferred way of handling terminating errors. They cannot be used to handle non-terminating errors, unless you force those errors to become terminating errors with ErrorAction or $ErrorActionPreference set to Stop.
To use Try/Catch/Finally, you start with the «Try» keyword followed by a single PowerShell script block. Following the Try block can be any number of Catch blocks, and either zero or one Finally block. There must be a minimum of either one Catch block or one Finally block; a Try block cannot be used by itself.
The code inside the Try block is executed until it is either complete, or a terminating error occurs. If a terminating error does occur, execution of the code in the Try block stops. PowerShell writes the terminating error to the $Error list, and looks for a matching Catch block (either in the current scope, or in any parent scopes.) If no Catch block exists to handle the error, PowerShell writes the error to the Error stream, the same thing it would have done if the error had occurred outside of a Try block.
Catch blocks can be written to only catch specific types of Exceptions, or to catch all terminating errors. If you do define multiple catch blocks for different exception types, be sure to place the more specific blocks at the top of the list; PowerShell searches catch blocks from top to bottom, and stops as soon as it finds one that is a match.
If a Finally block is included, its code is executed after both the Try and Catch blocks are complete, regardless of whether an error occurred or not. This is primarily intended to perform cleanup of resources (freeing up memory, calling objects’ Close() or Dispose() methods, etc.)
Figure 2.4 demonstrates the use of a Try/Catch/Finally block:
Figure 2.4: Example of using try/catch/finally.
Notice that «Statement after the error» is never displayed, because a terminating error occurred on the previous line. Because the error was based on an IOException, that Catch block was executed, instead of the general «catch-all» block below it. Afterward, the Finally executes and changes the value of $testVariable.
Also notice that while the Catch block specified a type of [System.IO.IOException], the actual exception type was, in this case, [System.IO.DirectoryNotFoundException]. This works because DirectoryNotFoundException is inherited from IOException, the same way all exceptions share the same base type of System.Exception. You can see this in figure 2.5:
Figure 2.5: Showing that IOException is the Base type for DirectoryNotFoundException
Trap
Trap statements were the method of handling terminating errors in PowerShell 1.0. As with Try/Catch/Finally, the Trap statement has no effect on non-terminating errors.
Trap is a bit awkward to use, as it applies to the entire scope where it is defined (and child scopes as well), rather than having the error handling logic kept close to the code that might produce the error the way it is when you use Try/Catch/Finally. For those of you familiar with Visual Basic, Trap is a lot like «On Error Goto». For that reason, Trap statements don’t see a lot of use in modern PowerShell scripts, and I didn’t include them in the test scripts or analysis in Section 3 of this ebook.
For the sake of completeness, here’s an example of how to use Trap:
Figure 2.6: Use of the Trap statement
As you can see, Trap blocks are defined much the same way as Catch blocks, optionally specifying an Exception type. Trap blocks may optionally end with either a Break or Continue statement. If you don’t use either of those, the error is written to the Error stream, and the current script block continues with the next line after the error. If you use Break, as seen in figure 2.5, the error is written to the Error stream, and the rest of the current script block is not executed. If you use Continue, the error is not written to the error stream, and the script block continues execution with the next statement.
The $LASTEXITCODE Variable
When you call an executable program instead of a PowerShell Cmdlet, Script or Function, the $LASTEXITCODE variable automatically contains the process’s exit code. Most processes use the convention of setting an exit code of zero when the code finishes successfully, and non-zero if an error occurred, but this is not guaranteed. It’s up to the developer of the executable to determine what its exit codes mean.
Note that the $LASTEXITCODE variable is only set when you call an executable directly, or via PowerShell’s call operator (&) or the Invoke-Expression cmdlet. If you use another method such as Start-Process or WMI to launch the executable, they have their own ways of communicating the exit code to you, and will not affect the current value of $LASTEXITCODE.
Figure 2.7: Using $LASTEXITCODE.
The $? Variable
The $? variable is a Boolean value that is automatically set after each PowerShell statement or pipeline finishes execution. It should be set to True if the previous command was successful, and False if there was an error.
When the previous command was a PowerShell statement, $? will be set to False if any errors occurred (even if ErrorAction was set to SilentlyContinue or Ignore).
If the previous command was a call to a native exe, $? will be set to False if the $LASTEXITCODE variable does not equal zero. If the $LASTEXITCODE variable equals zero, then in theory $? should be set to True. However, if the native command writes something to the standard error stream then $? could be set to False even if $LASTEXITCODE equals zero. For example, the PowerShell ISE will create and append error objects to $Error for standard error stream output and consequently $? will be False regardless of the value of $LASTEXITCODE. Redirecting the standard error stream into a file will result in a similar behavior even when the PowerShell host is the regular console. So it is probably best to test the behavior in your specific environment if you want to rely on $? being set correctly to True.
Just be aware that the value of this variable is reset after every statement. You must check its value immediately after the command you’re interested in, or it will be overwritten (probably to True). Figure 2.8 demonstrates this behavior. The first time $? is checked, it is set to False, because the Get-Item encountered an error. The second time $? was checked, it was set to True, because the previous command was successful; in this case, the previous command was «$?» from the first time the variable’s value was displayed.
Figure 2.8: Demonstrating behavior of the $? variable.
The $? variable doesn’t give you any details about what error occurred; it’s simply a flag that something went wrong. In the case of calling executable programs, you need to be sure that they return an exit code of 0 to indicate success and non-zero to indicate an error before you can rely on the contents of $?.
Summary
That covers all of the techniques you can use to either control error reporting or intercept and handle errors in a PowerShell script. To summarize:
- To intercept and react to non-terminating errors, you check the contents of either the automatic $Error collection, or the variable you specified as the ErrorVariable. This is done after the command completes; you cannot react to a non-terminating error before the Cmdlet or Function finishes its work.
- To intercept and react to terminating errors, you use either Try/Catch/Finally (preferred), or Trap (old and not used much now.) Both of these constructs allow you to specify different script blocks to react to different types of Exceptions.
- Using the ErrorAction parameter, you can change how PowerShell cmdlets and functions report non-terminating errors. Setting this to Stop causes them to become terminating errors instead, which can be intercepted with Try/Catch/Finally or Trap.
- $ErrorActionPreference works like ErrorAction, except it can also affect PowerShell’s behavior when a terminating error occurs, even if those errors came from a .NET method instead of a cmdlet.
- $LASTEXITCODE contains the exit code of external executables. An exit code of zero usually indicates success, but that’s up to the author of the program.
- $? can tell you whether the previous command was successful, though you have to be careful about using it with external commands. If they don’t follow the convention of using an exit code of zero as an indicator of success or if they write to the standard error stream then the resulting value in $? may not be what you expect. You also need to make sure you check the contents of $? immediately after the command you are interested in.
Error Handling is a very important concept in every programming language including PowerShell which gives us several possibilities to manage errors in code.
In this article, I will try to cover as many as possible error handling concepts with examples from my practice and one of the important concepts is writing errors in external Error Log text file.
Why Should We Bother Handling Errors In PowerShell
It is no fun to run any code or application full of errors and bugs as the matter a fact it is quite annoying so in order for users to have a pleasant experience handling the errors is one of the essentials in programming.
PowerShell is no exception to that way of thinking and programming ethics although it can take some additional coding and effort but trust me it is always worth it.
I would be more than happy to share my experience with error handling and even more satisfied if I can hear your tips from the field so we can all enrich our knowledge on this very important subject.
Here is a general approach to error handling and logging errors into the external text file. I have an example in the next subheading that further explain each step so everything is much more understandable.
- Identify which Commands need Error Handling in your Function, CmdLet or Script.
- Set the ErrorAction parameter to value Stop for the command that needs Error Handling. This will stop executing the command when an error occurs and it will show the error message. Basically, we are making non-terminating errors into terminating in order to be able to handle the error in the catch block.
- Command that has ErrorAction parameter set to value Stop is wrapped in Try { } block. So when Error occurs handling of the code will jump from the Try block to Catch block.
- In Catch { } block that immediately follows after Try {} block, the error is handled by writing to the error log file on the disk.
- We use our own Write-ErrorLog CmdLet inside Catch{ } block to write the error in a text file on the disk. (See the explanation and code here)
- We have an additional switch error parameter to decide whether we want to write an error in the log file or not. This is totally optional.
- Use the Finally { } block if needed.
- Test the whole setup by intentionally breaking the code while in the development phase.
- Since some of the CmdLets calls are Scheduled we have routine to check external Error Log text file at least once a week and investigate errors that are caught. This step is part of improving the overall quality of the written code.
Example Of PowerShell Error Handling
To show you Error Handling and implement previously defined steps I will use my own Get-CPUInfo CmdLet which is in the Common module of the Efficiency Booster PowerShell Project. Efficiency Booster PowerShell Project is a library of CmdLets that help us IT experts in day to day IT tasks.
In order to follow me along, I highly encourage you to download the zip file with the source code used in this example.
Here is the location of Get-CPUInfo script:
…[My] DocumentsWindowsPowerShellModules3common
Let’s use steps defined in the previous subheading to this example.
Step 1. I have identified the command that needs Error Handling in Get-CPUInfo CmdLet and that is a call to Get-CimInstance CmdLet.
Get-CimInstance @params
Step 2. So I have set up the ErrorAction parameter to the value ‘Stop‘ for Get-CimInstance CmdLet in order to force non-terminating errors into terminating and then to be able to handle such errors.
INFO: I use parameter splatting when running CmdLet. If you want to know more about parameter splating please read this article.
$params = @{ 'ComputerName'=$computer;
'Class'='Win32_Processor';
'ErrorAction'='Stop'}
$CPUInfos = Get-CimInstance @params |
Select-Object @{label="ServerName"; Expression={$_.SystemName}},
@{label="CPU"; Expression={$_.Name}},
@{label="CPUid"; Expression={$_.DeviceID}},
NumberOfCores,
AddressWidth
Step 3. Wrap up the call to Get-CimInstance CmdLet into the Try Block in order to be able to handle the error in a catch block that follows.
try {
Write-Verbose "Start processing: $computer - $env - $logicalname"
Write-Verbose "Start Win32_Processor processing..."
$CPUInfos = $null
$params = @{ 'ComputerName'=$computer;
'Class'='Win32_Processor';
'ErrorAction'='Stop'}
$CPUInfos = Get-CimInstance @params |
Select-Object @{label="ServerName"; Expression={$_.SystemName}},
@{label="CPU"; Expression={$_.Name}},
@{label="CPUid"; Expression={$_.DeviceID}},
NumberOfCores,
AddressWidth
Write-Verbose "Finish Win32_Processor processing..."
foreach ($CPUInfo in $CPUInfos) {
Write-Verbose "Start processing CPU: $CPUInfo"
$properties = @{ 'Environment'=$env;
'Logical name'=$logicalname;
'Server name'=$CPUInfo.ServerName;
'CPU'=$CPUInfo.CPU;
'CPU ID'=$CPUInfo.CPUid;
'Number of CPU cores'=$CPUInfo.NumberOfCores;
'64 or 32 bits'=$CPUInfo.AddressWidth;
'IP'=$ip;
'Collected'=(Get-Date -UFormat %Y.%m.%d' '%H:%M:%S)}
$obj = New-Object -TypeName PSObject -Property $properties
$obj.PSObject.TypeNames.Insert(0,'Report.CPUInfo')
Write-Output $obj
Write-Verbose "Finish processing CPU: $CPUInfo"
}
Write-Verbose "Finish processing: $computer - $env - $logicalname"
}
Step 4. When the error occurs in the try block it is handled in the Catch Block.
It is important to notice following in the catch block of code:
- Get-CPUInfo CmdLet switch parameter $errorlog has been used to decide whether to log the errors in an external text file or not. This is completely optional.
- Certain Error properties are collected using an automatic variable $_ ($PSItem is another name for the same variable). If you want to know more about which properties we collect please read here.
- Collected data about the error that will be handled has been passed to another CmdLet Write-ErrorLog that will write the data in an external text log file. Please read here about Write-ErrorLog CmdLet.
catch {
Write-Warning "Computer failed: $computer - $env - $logicalname CPU failed: $CPUInfos"
Write-Warning "Error message: $_"
if ( $errorlog ) {
$errormsg = $_.ToString()
$exception = $_.Exception
$stacktrace = $_.ScriptStackTrace
$failingline = $_.InvocationInfo.Line
$positionmsg = $_.InvocationInfo.PositionMessage
$pscommandpath = $_.InvocationInfo.PSCommandPath
$failinglinenumber = $_.InvocationInfo.ScriptLineNumber
$scriptname = $_.InvocationInfo.ScriptName
Write-Verbose "Start writing to Error log."
Write-ErrorLog -hostname $computer -env $env -logicalname $logicalname -errormsg $errormsg -exception $exception -scriptname $scriptname -failinglinenumber $failinglinenumber -failingline $failingline -pscommandpath $pscommandpath -positionmsg $pscommandpath -stacktrace $stacktrace
Write-Verbose "Finish writing to Error log."
}
}
Step 5. I have already mentioned that Write-ErrorLog CmdLet has been used to write the error data into an external text log file. Read more about this CmdLet here.
Step 6. I did not need Finally { } block for this example.
Step 7. In the development phase, I was intentionally breaking Get-CPUInfo CmdLet to test my error handling code.
Step 8. If Get-CPUInfo CmdLet is part of the scheduled code I would look regularly Error log file and work on correcting all the bugs in the code produced by Get-CPUInfo CmdLet.
Chain Of Events When PowerShell Error Occurs
Let’s talk a little bit about what is happening when an error occurs in PowerShell and which events are triggered one after another.
- Call to CmdLet is failing and error has just occurred.
- Since we have ErrorAction parameter set to value Stop our non-terminating error has forced into terminating error so the execution of code stops.
- The error that is failing in the CmdLet is written in the $Error automatic variable by the PowerShell.
- We have forced the error to be terminating in order to be able to handle with try catch finally block of error handling.
- Since our CmdLet call is wrapped in Try block and error is terminating PowerShell can trigger error handling looking for a Catch block.
- We can have several Catch blocks for one try block. If we have a Catch block that handles the actual error number that block is executed.
- Otherwise, PowerShell will look Catch block that handles all error numbers.
- Optionally in the Catch block, we can have code that will write the error in the external text Error log file.
- We can use the automatic variable $Error or $_ object to read Error data and write them in the external file.
- If there is no Catch block PowerShell will look for Catch block in parent call if we have nested calls.
- If there are no further Catch block or no Catch block at all that will handle error then PowerShell looks for Finally block to execute which is used to clean up resources as needed.
- After executing Finally block error message will be sent to the error stream for further processing.
In further sections of this article, you can read in more detail about the many terms mentioned (ErrorAction, $Error, Try Catch Finally, Terminating, Non-Terminating, etc ) in this bulleted list in order to better understand them.
How To Write PowerShell Errors Into The External Log File
Here I will explain the code of Write-ErrorLog CmdLet that writes error data that occurs in CmdLets and handle the error data into an external text file.
Error data are written in the file named Error_Log.txt in folder PSlogs.
Write-ErrorLog CmdLet is part of the Efficiency Booster PowerShell Project and if you want to download the source code of this CmdLet please click here.
Here is the location of Write-ErrorLog script which is part of the Utils module:
…[My] DocumentsWindowsPowerShellModules2utils
Write-ErrorLog CmdLet Code
Here is the code of the whole Write-ErrorLog CmdLet.
<#
.SYNOPSIS
Writes errors that occur in powershell scripts into error log file.
.DESCRIPTION
Writes errors that occur in powershell scripts into error log file.
Error log file and error log folder will be created if doesn't exist.
Error log file name is Error_Log.txt and it has been saved into ..DocumentsPSlogs
.PARAMETER hostname
Name of the computer that is failing.
.PARAMETER env
Environment where computer is located. For example: Production, Acceptance, Test, Course etc.
.PARAMETER logicalname
Type of the server that is failing. For example: Application, Web, Integration, FTP, Scan, etc.
.PARAMETER errormsg
Error message.
.PARAMETER exception
Error number.
.PARAMETER scriptname
Name of the powershell script that is failing.
.PARAMETER failinglinenumber
Line number in the script that is failing.
.PARAMETER failingline
Content of failing line.
.PARAMETER pscommandpath
Path to the powershell command.
.PARAMETER positionmsg
Error message position.
.PARAMETER stacktrace
Stack trace of the error.
.EXAMPLE
Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace"
.EXAMPLE
Help Write-ErrorLog -Full
.LINK
Out-File
#>
Function Write-ErrorLog {
[CmdletBinding()]
param (
[Parameter(Mandatory=$false,
HelpMessage="Error from computer.")]
[string]$hostname,
[Parameter(Mandatory=$false,
HelpMessage="Environment that failed. (Test, Production, Course, Acceptance...)")]
[string]$env,
[Parameter(Mandatory=$false,
HelpMessage="Type of server that failed. (Application, Web, Integration...)")]
[string]$logicalname,
[Parameter(Mandatory=$false,
HelpMessage="Error message.")]
[string]$errormsg,
[Parameter( Mandatory=$false,
HelpMessage="Exception.")]
[string]$exception,
[Parameter(Mandatory=$false,
HelpMessage="Name of the script that is failing.")]
[string]$scriptname,
[Parameter(Mandatory=$false,
HelpMessage="Script fails at line number.")]
[string]$failinglinenumber,
[Parameter(Mandatory=$false,
HelpMessage="Failing line looks like.")]
[string]$failingline,
[Parameter(Mandatory=$false,
HelpMessage="Powershell command path.")]
[string]$pscommandpath,
[Parameter(Mandatory=$false,
HelpMessage="Position message.")]
[string]$positionmsg,
[Parameter(Mandatory=$false,
HelpMessage="Stack trace.")]
[string]$stacktrace
)
BEGIN {
$errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
$errorlogfolder = "$homeDocumentsPSlogs"
if ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
Write-Verbose "Create error log folder in: $errorlogfolder"
New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
}
}
}
PROCESS {
Write-Verbose "Start writing to Error log file. $errorlogfile"
$timestamp = Get-Date
#IMPORTANT: Read just first value from collection not the whole collection.
" " | Out-File $errorlogfile -Append
"************************************************************************************************************" | Out-File $errorlogfile -Append
"Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
"Error message: $errormsg" | Out-File $errorlogfile -Append
"Error exception: $exception" | Out-File $errorlogfile -Append
"Failing script: $scriptname" | Out-File $errorlogfile -Append
"Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
"Failing at line: $failingline" | Out-File $errorlogfile -Append
"Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
"Position message: $positionmsg" | Out-File $errorlogfile -Append
"Stack trace: $stacktrace" | Out-File $errorlogfile -Append
"------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append
Write-Verbose "Finish writing to Error log file. $errorlogfile"
}
END {
}
}
#region Execution examples
#Write-ErrorLog -hostname "Server1" -env "PROD" -logicalname "APP1" -errormsg "Error Message" -exception "HResult 0789343" -scriptname "Test.ps1" -failinglinenumber "25" -failingline "Get-Service" -pscommandpath "Command pathc." -positionmsg "Position message" -stacktrace "Stack trace" -Verbose
#endregion
Write-ErrorLog CmdLet Explained
Let’s make our hand’s a little bit “dirty” and dive into PowerShell code.
In the BEGIN block we:
- Check if folder PSlogs exist in the (My) Documents folder of the current user.
- If the PSlogs folder doesn’t exist then create the folder.
- Check if file Error_Log.txt exists in the folder PSlogs.
- If Error_Log.txt doesn’t exist then create the file.
- Now we can move on to PROCESS block code.
BEGIN {
$errorlogfile = "$homeDocumentsPSlogsError_Log.txt"
$errorlogfolder = "$homeDocumentsPSlogs"
if ( !( Test-Path -Path $errorlogfolder -PathType "Container" ) ) {
Write-Verbose "Create error log folder in: $errorlogfolder"
New-Item -Path $errorlogfolder -ItemType "Container" -ErrorAction Stop
if ( !( Test-Path -Path $errorlogfile -PathType "Leaf" ) ) {
Write-Verbose "Create error log file in folder $errorlogfolder with name Error_Log.txt"
New-Item -Path $errorlogfile -ItemType "File" -ErrorAction Stop
}
}
}
In the PROCESS block:
- We format the line of text that we want to write into the log file.
- Then we pipe formatted text to Out-File CmdLet with the Append parameter to write that line of text in the file.
- We repeat the process of formatting the line of text and appending of that line to the Error log file.
PROCESS {
Write-Verbose "Start writing to Error log file. $errorlogfile"
$timestamp = Get-Date
#IMPORTANT: Read just first value from collection not the whole collection.
" " | Out-File $errorlogfile -Append
"************************************************************************************************************" | Out-File $errorlogfile -Append
"Error happend at time: $timestamp on a computer: $hostname - $env - $logicalname" | Out-File $errorlogfile -Append
"Error message: $errormsg" | Out-File $errorlogfile -Append
"Error exception: $exception" | Out-File $errorlogfile -Append
"Failing script: $scriptname" | Out-File $errorlogfile -Append
"Failing at line number: $failinglinenumber" | Out-File $errorlogfile -Append
"Failing at line: $failingline" | Out-File $errorlogfile -Append
"Powershell command path: $pscommandpath" | Out-File $errorlogfile -Append
"Position message: $positionmsg" | Out-File $errorlogfile -Append
"Stack trace: $stacktrace" | Out-File $errorlogfile -Append
"------------------------------------------------------------------------------------------------------------" | Out-File $errorlogfile -Append
Write-Verbose "Finish writing to Error log file. $errorlogfile"
}
$PSItem or $_
$PSItem contains the current object in the pipeline object and we use it to read Error properties in this case.
Just a quick explanation of each Error property from the $_ ($PSItem) automatic variable that we collect and write in Logfile:
- $_.ToString() – This is Error Message.
- $_.Exception – This is Error Exception.
- $_.InvocationInfo.ScriptName – This the PowerShell script name where Error occurred.
- $_.InvocationInfo.ScriptLineNumber – This is line number within the PowerShell script where Error occurred.
- $_.InvocationInfo.Line – This is the line of code within PowerShell script where Error occurred.
- $_.InvocationInfo.PSCommandPath – This is the path to the PowerShell script file on the disk.
- $_.InvocationInfo.PositionMessage – This is a formatted message indicating where the CmdLet appeared in the line.
- $_.ScriptStackTrace – This is the Trace of the Stack.
As you can see on the screenshot below we collect really useful information about the Error that we handle in the Logfile. The pieces of information are presented in very neatly fashion so we can immediately see:
- which error occurred,
- what were the message and exception,
- where the error occurred (script name, script location, line number and line of the code in the script)
- even the call stack is shown if needed.
Here are the final result and an example of one formatted error logged in Error_Log.txt file.
************************************************************************************************************
Error happend at time: 09/11/2019 18:20:41 on a computer: APP01 - -
Error message: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
Error exception: Microsoft.Management.Infrastructure.CimException: The WinRM client cannot process the request. If the authentication scheme is different from Kerberos, or if the client computer is not joined to a domain, then HTTPS transport must be used or the destination machine must be added to the TrustedHosts configuration setting. Use winrm.cmd to configure TrustedHosts. Note that computers in the TrustedHosts list might not be authenticated. You can get more information about that by running the following command: winrm help config.
at Microsoft.Management.Infrastructure.Internal.Operations.CimAsyncObserverProxyBase`1.ProcessNativeCallback(OperationCallbackProcessingContext callbackProcessingContext, T currentItem, Boolean moreResults, MiResult operationResult, String errorMessage, InstanceHandle errorDetailsHandle)
Failing script: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Failing at line number: 214
Failing at line: $CPUInfos = Get-CimInstance @params |
Powershell command path: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Position message: C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1
Stack trace: at Get-CPUInfo, C:UsersdekibDocumentsWindowsPowerShellModules3commonGetCPUInfo.ps1: line 214
at , : line 1
------------------------------------------------------------------------------------------------------------
TIP: If your scripts are scheduled in Task Manager the best practice is to have a routine of regularly checking the Error Log file and investigate the errors that occurred since the last check. I am doing this once a week.
Errors In PowerShell
There are two types of Errors in PowerShell:
- Terminating
- Non-Terminating
Terminating Errors
Here are the important features of Terminating errors:
- terminates execution of command or script
- triggers Catch block and can be error handled by the Catch block.
Examples are syntax errors, non-existent CmdLets, or other fatal errors
Non-Terminating Errors
Here are important features of Non-Terminating Error:
- A non-fatal error.
- Allows execution to continue despite the failure that just occurred.
- It doesn’t trigger the Catch block and cannot be Error Handled in the Catch block by default.
Examples are permission problems, file not found, etc.
How To Force Non-Terminating Errors Into Terminating
Use the ErrorAction parameter with value Stop to force non-terminating error into terminating as in the following example. The reason why we want to make non-termination error into terminating one is to be able to catch the error when occurs.
$AuthorizedUser = Get-Content .DocumentsWindowsPowerShellProfile.ps1 -ErrorAction Stop
Basically, the workflow is as follows.
- When an error occurs,
- a non-terminating error has changed into terminating one since we have Stop value on ErrrorAction parameter,
- then since terminating error has occurred try block will send the error handling to catch block where error can be processed and
- optionally written to the external log,
- optionally error handling can continue in the final block.
NOTE: ErrorAction parameter overrides temporarily ErrorActionPreference variable while the call to CmdLet has been processed.
How To Treat All Errors As Terminating
We use the ErrorActionPreference variable to treat all errors as terminating by setting to the value Stop in the:
- script
- or session
Write the following line of code at the begging of the script to treat all errors as terminating.
$ErrorActionPreference = Stop
Type in Windows PowerShell Console the same command to setup terminating errors for the session.
ErrorAction Common Parameter
ErrorAction parameter belongs to the set of common parameters that we can use with any CmdLet. If we set CmdLetBinding on Advanced Functions than PowerShell automatically makes common parameters available for that command.
This is a parameter that I always use with CmdLets that need error handling since the ErrorAction Parameter determines how the CmdLet responds to a non-terminating error. It has several values that we will talk about in a minute but the value that I like to use is Stop and it is used to make non-terminating errors into terminating errors as written in previous sections.
The ErrorAction parameter overrides the value of the $ErrorActionPreference variable when applied to the specific command.
Here are valid values:
- Continue (Default)
- This is the default setting. Display error then continues execution.
- Stop
- Display error, and stop the execution.
- Inquire
- Displays error message and the user is asked to continue with execution.
- SilentlyContinue
- No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
- Ignore
- The same as SilentlyContinue, No error message is displayed and execution is continued. However, Ignore does not add an error message to the $Error automatic variable.
- Suspend
- This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.
$ErrorActionPreference Preference Variable Explained
$ErrorActionPreference preference variable determines how Windows PowerShell responds to a non-terminating error (an error that does not stop the cmdlet processing) in a script, cmdlet or at the command line
If we want to override the value of the ErrorActionPreference preference variable for the specific command we use the ErrorAction common parameter as explained here.
The valid values for $ErrorActionPreference preference variable are:
- Continue (Default)
- This is the default setting. Display error then continues execution.
- Stop
- Display error message, and stop the execution.
- Inquire
- Displays error message and the user is asked to continue with execution.
- SilentlyContinue
- No error message is displayed and execution is continued. However, the error message is added to the $Error automatic variable.
- Suspend
- This one is for workflows. A workflow job is suspended to investigate what happened, then the workflow can be resumed.
Error Handling With Try/Catch/Finally Blocks
Try, Catch, and Finally, blocks are used to handle terminating errors in the scripts, functions, and CmdLets. A non-terminating error does not trigger Try block and Windows PowerShell will not look for Catch block to handle the error. So we need to force a non-terminating error to become terminating error using ErrorAction parameter with value Stop whenever we call some CmdLet or Advanced function.
Try block is used as part of the code that PowerShell will monitor for errors. The workflow in the Try block is as follows:
- Try block is the section of code that will be monitored for errors by Windows PowerShell.
- When the error occurs within Try block the error is saved to $Error automatic variable first.
- Windows PowerShell searches for a Catch block to handle the error if the error is terminating. If the Catch block has not been found in current scope Windows PowerShell will search for catch block in parent scopes for nested calls.
- Then the Finally block is run if exists.
- If there is no Catch block than the error is not handled and the error is written to the error stream.
One Try block can have several Catch Blocks that will handle different error types.
Catch block usually handles the error.
The Finally block is optional and can be only one. Usually, it is used to clean up and free the resources.
The syntax for Try, Catch, and Finally block:
try { }
catch [],[]
{ }
catch { }
finally { }
Getting Error Information With $Error Automatic Variable
$Error automatic variable is an array of error objects (both terminating and the non-terminating) that occurred in the current PowerShell session. The most recent error that occurred is written with index 0 in the array as $Error[0]. If we have just opened the Windows PowerShell session the $Error variable is an empty array and ready to be used.
Check the number of Errors in $Error variable with the following code:
$Error.Count
To prevent the error from being written in $Error automatic variable set ErrorAction parameter to value Ignore.
$Error variable is a rich object that has many useful properties worth reading and helpful for further understanding of the error that just occurred.
Let’s see some useful properties and in section Write-ErrorLog CmdLet Explained I have explained to you some useful examples of properties that are interesting to be written in an external log file.
$error[0] | Get-Member
$Error.CategoryInfo | Get-Member
$Error[0].Exception
$Error.InvocationInfo | Get-Member
Write-Error CmdLet
Write-Error CmdLet writes an object to the error stream.
Please read this article from Microsoft PowerShell documentation regarding this CmdLet.
Handling Errors from non-PowerShell processes
We can run applications from PowerShell script like for example, PsExec.exe or robocopy.exe and they are external processes for PowerShell. Since it is an external process, errors from it will not be caught by our try/catch blocks in PowerShell script. So how we will know whether our external process was successful or not.
Well, we can use the $LastExitCode PowerShell automatic variable.
PowerShell will write the exit code to the $LastExitCode automatic variable when the external process exits. Check the external tool’s documentation for exit code values but usually, 0 means success and 1 or greater values mean a failure.
Useful PowerShell Error Handling Articles
Here are some useful articles and resources:
- Windows PowerShell Error Reporting
- About Try Catch Finally
- About CommonParameters
- About Automatic Variables
- About Preference Variables
- Write-Error
- About Throw
- About Break
- About Continue
There are a few different kinds of errors in PowerShell, and it can be a little bit of a minefield on occasion.
There are always two sides to consider, too: how you write code that creates errors, and how you handle those errors in your own code.
Let’s have a look at some ways to effectively utilise the different kinds of errors you can work with in PowerShell, and how to handle them.
Different Types of Errors
Terminating Errors
Terminating errors are mostly an «exactly what it sounds like» package in general use.
They’re very similar to C#’s Exception
system, although PowerShell uses ErrorRecord
as its base error object.
Their common characteristics are as follows:
- Trigger
try
/catch
blocks - Return control to the caller, along with the exception, if they’re not handled using a try/catch block.
There are a couple different ways to get a terminating error in PowerShell, and each has its own small differences that make them more useful in a tricky situation.
Throw
First is your typical throw
statement, which simply takes an exception message as its only input.
throw "Something went wrong"
throw
creates an exception and an ErrorRecord (PowerShell’s more detailed version of an exception) in a single, simple statement, and wraps them together.
Its exceptions display the entered error message, a generic RuntimeException
, and the errorID matches the entered exception message.
The line number and position displayed from a throw
error message point back to the throw
statement itself.
throw
exceptions are affected by -ErrorAction
parameters of their advanced functions.
throw
should generally be used for simple runtime errors, where you need to halt execution, but typically only if you plan to catch
it yourself later.
There is no guarantee a user will not elect to simply ignore your error with -ErrorAction Ignore
and continue with their lives if it is passed up from your functions.
ThrowTerminatingError()
Next, we have a more advanced version of the throw statement. ThrowTerminatingError()
is a method that comes directly from PowerShell’s PSCmdlet
.NET class.
It is accessible only in advanced functions via the $PSCmdlet
variable.
using namespace System.Management.Automation;
$Exception = [Exception]::new("error message")
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
$Exception,
"errorID",
[System.Management.Automation.ErrorCategory]::NotSpecified,
$TargetObject # usually the object that triggered the error, if possible
)
$PSCmdlet.ThrowTerminatingError($ErrorRecord)
As you can see, these require a little more manual labor.
You can bypass some of this by deliberately using the throw
statement and then later catch
ing the generated ErrorRecord
to pass it into the ThrowTerminatingError()
method directly.
This saves a decent amount of the code involved, and offloads the work to PowerShell’s engine instead.
However, this will still involve essentially the same work being done, without necessarily giving you a chance to specify the finer details of the error record.
ThrowTerminatingError()
creates a terminating error (as the name implies), however unlike throw
its errors are unaffected by -ErrorAction
parameters applied to the parent function.
It will also never reveal the code around it.
When you see an error thrown via this method, it will always display only the line where the command was called, never the code within the command that actually threw the error.
ThrowTerminatingError()
should generally be used for serious errors, where continuing to process in spite of the error may lead to corrupted data, and you want any specified -ErrorAction
parameter to be ignored.
Non-Terminating Errors
Non-terminating errors are PowerShell’s concession demanded by the fact that it is a shell that supports pipelines.
Not all errors warrant halting all ongoing actions, and only need to inform the user that a minor error occurred, but continue processing the remainder of the items.
Unlike terminating errors, non-terminating errors:
- Do not trigger
try
/catch
blocks. - Do not affect the script’s control flow.
Write-Error
Write-Error
is a bit like the non-terminating version of throw
, albeit more versatile.
It too creates a complete ErrorRecord
, which has a NotSpecified
category and some predefined Exception
and ErrorCode
properties that both refer to Write-Error
.
This is a very simple and effective way to write a non-terminating error, but it is somewhat limited in its use.
While you can override its predefined default exception type, error code, and essentially everything about the generated ErrorRecord
, Write-Error
is similar to throw
in that it too points directly back to the line where Write-Error
itself was called.
This can often make the error display very unclear, which is rarely a good thing.
Write-Error
is a great way to emit straightforward, non-terminating errors, but its tendency to muddy the error display makes it difficult to recommend for any specific uses.
However, as it is itself a cmdlet, it is also possible to make it create terminating errors as well, by using the -ErrorAction Stop
parameter argument.
WriteError()
WriteError()
is the non-terminating cousin of ThrowTerminatingError()
, and is also available as one of the members of the automatic $PSCmdlet
variable in advanced functions.
It behaves similarly to its cousin in that it too hides the code that implements it and instead points back to the point where its parent function was called as the source point of the error.
This is often preferable when dealing with expected errors such as garbled input or arguments, or simple «could not find the requested item» errors in some cases.
$Exception = [Exception]::new("error message")
$ErrorRecord = [System.Management.Automation.ErrorRecord]::new(
$Exception,
"errorID",
[System.Management.Automation.ErrorCategory]::NotSpecified,
$TargetObject # usually the object that triggered the error, if possible
)
$PSCmdlet.WriteError($ErrorRecord)
Emit Errors Responsibly
There are no true hard-and-fast rules here.
If one solution works especially well for what you happen to be putting together, don’t let me deter you.
However, I would urge you to consider both the impact to your users as well as yourself or your team before selecting a single-minded approach to error creation.
Not all error scenarios are created equal, so it’s very rare that I would recommend using one type over all others.
WriteError()
is generally preferred over its cmdlet form Write-Error
, because it offers more control over how you emit the error.
Both tend to be used for expected and technically avoidable errors that occur, but the $PSCmdlet
method has the additional benefit of not divulging implementation details unnecessarily.
If you expect the error to occur sometimes, I would generally advise using WriteError()
unless the error’s presence significantly impairs your cmdlet’s ability to process the rest of its input, in which case I would use ThrowTerminatingError()
.
throw
is generally nice as a quick way to trigger a try
/catch
block in your own code, but I would also recommend using it for: those truly unexpected errors, that last-ditch catch
clause after several more specific catch
blocks have been put in place and bypassed.
At this point, a final ending catch
block that simply triggers a throw $_
call to re-throw the caught exception into the parent scope and quickly pass execution back to the caller.
It’s a very quick and very effective way to have the error be noticed, but should usually be kept internal to the function more than anything else, except in the last scenario mentioned.
Use of Write-Error -ErrorAction Stop
may potentially also be substituted for that purpose if you personally prefer.
Thanks for reading!
You need to understand how PowerShell handles errors and exceptions if you wish to create a script that behaves as you expect. It’s a fact of life that on occasions your PowerShell scripts will throw errors.
Contents
- Non-terminating errors
- Terminating errors
- Turning non-terminating errors into terminating errors
- Wrap-up
- Author
- Recent Posts
Adam Bertram is a 20-year IT veteran, Microsoft MVP, blogger, and trainer.
No one writes perfect code or can account for the slew of environmental changes that may be going on to write a PowerShell script that executes correctly 100% of the time.
The first thing you’ll need to realize when working with errors in PowerShell is that there’s not just one kind of «error,» there are two: terminating and non-terminating errors. You will undoubtedly come across both.
Non-terminating errors
Non-terminating errors are still «errors» in PowerShell but not quite as severe as terminating ones. Non-terminating errors aren’t as serious because they do not halt script execution. Moreover, you can silence them, unlike terminating errors. You can create non-terminating errors with the Write-Error cmdlet. This cmdlet writes text to the error stream.
Write Error
You can also manipulate non-terminating errors with the common ErrorAction and ErrorVariable parameters on all cmdlets and advanced functions. For example, if you’ve created an advanced function that contains a Write-Error reference, you can temporarily silence this as shown below.
function Do-Thing { [CmdletBinding()] param() Write-Error 'An error occurred' } ## Execute on its own PS> Do-Thing
Write Error output
I can silence this error with the built-in ErrorAction parameter and choose to SilentlyContinue as well.
Do-Thing -ErrorAction SilentlyContinue
You see when executing the above code, it will return nothing.
Terminating errors
Terminating errors are those that halt script execution. These errors are meant to be more serious than non-terminating errors. The most common way to create a terminating error is to use the throw command. The throw command «throws» an exception, which is essentially the same thing as a terminating error. For example, the below code will not return anything. You might expect that it would return «the string variable did not equal foo» because I did not include an else condition. However, it does not, because throw halts execution.
$string = 'foo' if ($string -eq 'foo') { throw 'string equaled foo when it was not supposed to' } Write-Output 'the string variable did not equal foo'
Terminating error
Unlike with non-terminating errors, the ErrorAction or ErrorVariable parameters cannot handle terminating errors. Only try/catch blocks can handle them. You can use this pair of blocks together to form a net of sorts around a piece of code. When a try block throws a terminating error, it will immediately halt execution of any further code inside of that try block and begin execution of the code inside of the catch block. The example below shows this behavior.
try { $string = 'foo' if ($string -eq 'foo') { throw 'string equaled foo when it was not supposed to' } Write-Output 'the string variable did not equal foo' } catch { Write-Output 'we ended up in the catch block' }
When run, the code returns the string «we ended up in the catch block.» Without try/catch blocks, the code will simply write out a terminating error (exception) to the host as the first example shows. Here, we did not see any red error text but just the «we ended up in the catch block» string. This is how the code handles terminating errors.
Turning non-terminating errors into terminating errors
Sometimes you have no control over which errors are terminating vs. non-terminating. And you may want to ensure that when a script runs, it always stops for every error regardless of the type. To consider all errors as terminating errors, you can manipulate the $ErrorActionPreference automatic variable. This variable controls what happens when a non-terminating error occurs. The default setting for this variable is Continue. However, if you want to ensure every non-terminating error halts script execution just like a terminating error, you can change this to Stop as the example below shows.
$ErrorActionPreference = 'Stop' $string = 'foo' if ($string -eq 'foo') { Write-Error 'string equaled foo when it was not supposed to' } Write-Output 'we are past the error here'
By executing the above code, you’ll see that it returns both the error and the output. This is because Write-Error returns a non-terminating error or sends a string to the error stream. However, when setting $ErrorActionPreference to Stop, you’ll see that the code never returns «we are past the error here» because it halts script execution.
Subscribe to 4sysops newsletter!
Wrap-up
Understanding how errors occur and how to invoke them at appropriate times is an important skill to master in PowerShell. Proper error handling can make the difference between a script that blows up and a well-structured one that is much more robust.
One of the most important components for creating PowerShell scripts is error and exception handling.
I’ve personally made mistakes while writing scripts without proper exceptions and trying to figure out why it got terminated.😵
Error and exception handling is often a forgotten component of scripting because it’s common to feel that the code should always execute linearly and in an implicit fashion.
This is due to the common practice of taking the small scripts and using them as starting points for more complex scripts.
The more complex you build your scripts, the higher the probability of failure 🠝 and unexpected results.
In this post, you will learn the following:
- Types of error
- Different ways to handle Exceptions
- Error and exception handling with parameters
- Different Actions
- Error and exception handling with Try/Catch
PowerShell has two different types of errors which are terminating and non-terminating.
Terminating errors will stop the script from executing further commands.
The non-terminating errors will call the write-error cmdlet, print an error to the screen, and continue.
Error and Exception Handling
PowerShell offers several different options to achieve error and exception handling.
The most popular method used to catch non-terminating errors is bypassing the error and exception handling parameters while executing PowerShell cmdlets.
You can then call the error variable and execute other actions based on the contents of the $error variable.
The PowerShell parameters that handle error and exceptions are —WarningAction and ErrorAction.
When an issue occurs with your script, the PowerShell CLR will reference the —ErrorAction and —WarningAction arguments to determine what the next step for the script is.
There are five actions that are supported within PowerShell.
- The SilentlyContinue action will suppress the error and warning information, populate the error variables, and continue.
- The Ignore action will suppress the warning and error message and not populate any specified variables.
- The Continue action will write to the screen the warning and error information and attempt to continue with the script.
- The Stop action will write the warning and error information stop the execution of the script.
- The Inquire action will prompt the end-user if they want to Halt, Suspend, Accept the Error, or Accept All Errors.
By default, PowerShell is set to Continue, however, you can set the $errorActionPreference and $warningActionPreference (Global variables) to different values for different default actions.
We will see one example of cmdlet error handling.
Function TestExample($tesparam) {
Get-service $tesparam –ErrorAction SilentlyContinue –ErrorVariable err
If ($err) {
Write-host "Error! Error Details: $err"
return
}
Write-host "Successfully Retrieved Service Information for $svcName. "
}
TestExample "Windows Update"
Write-host ""
TestExample "Does Not Exist"
####################################################
Status Name DisplayName
------ ---- -----------
Stopped wuauserv Windows Update
Successfully Retrieved Service Information for .
Error! Error Details: Cannot find any service with service name 'Does Not Exist'.
If the $err variable has data in it or is implied true, the script will write to the console Error! Error Details: $err followed by return, which will exit out of the function. If the $err variable doesn’t have any error details, it will proceed to write to the console.
Handling error with try/Catch/Finally
One of the more popular error and exception handling techniques is leveraging Try/Catch
methodology.
The Try/Catch
block is used for handling terminating errors and has a very simple structure. You first use the Try { }
section of code and then use Catch { }
to catch any errors and perform actions based on the errors.
try
{
$items = Get-Item -Path C:DoesNotExist, C:Windows, $env:APPDATA -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException]
{
# Specific catch block for the exception type
# PSItem contains the error record, and TargetObject may contain the actual object raising the error
Write-Host ('Could not find folder {0}' -f $PSItem.TargetObject)
}
finally
{
# Regardless of whether an error occurred or not, the optional
# finally block is always executed.
Write-Host 'Always executed'
}
You can find out which type of exception occurred by examining its type, using $Error[0].Exception.GetType().FullName.
One of the best practice techniques for error and exception handling is to combine the use of the Try/Catch
block and cmdlet parameters. This is due to PowerShell being able to gracefully handle terminating and non-terminating error scenarios.
For instance, if you execute a line of code that throws a warning message but doesn’t generate a terminating error, you can catch the warning and perform actions based on that warning.
Try {
Get-process "Doesn't Exist" –ErrorAction SilentlyContinue –ErrorVariable err
}
Catch {
Write-host "Try/Catch Exception Details: $_"
}
if ($err) {
Write-host "Cmdlet Error Handling Error Details: $err"
}
#############################################
Cmdlet Error Handling Error Details: Cannot find a process with the name "Doesn't Exist". Verify the process name and call the cmdlet again.
When you execute the script, you see that the Catch
method doesn’t catch the error message from the get-service
the cmdlet. This is due to the error being a non-terminating error, and so it doesn’t invoke the block.
When you run the script, however, the cmdlet properly handles the error and places the error details in the $err
variable.
Quick Summary:
- There are two types of errors in PowerShell: terminating and nonterminating.
- Error records are written directly to the default output.
- Error records are rich objects.
- The $error variable stores the last 256 errors (by default).
- You can specify a specific variable for errors by using the -ErrorVariable parameter.
- $? stores a Boolean value indicating the execution status of the last command.
- $ErrorActionPreference and the -ErrorAction parameter can be used to control the action taken if an error occurs.
- Terminating errors and exceptions can be managed by the trap statement or the try/catch/finally statements (preferred).
Happy Coding!