Idera nSoftware Compellent

Chapter 11. Finding and Avoiding Errors

The more complex your commands, pipelines, functions, or scripts become, the more often that errors can creep in. PowerShell has its own remedies for finding and correcting errors at various levels of complexity.

In simple cases, use "what-if" scenarios to check whether a command or a pipeline is really doing what you expect it to do. With the help of such scenarios, you can simulate the result of commands without actually executing the commands. You can permit commands to do their work only after you're convinced that the commands will function flawlessly.

If you've written your own functions or scripts, PowerShell can also step through the code and halt its execution at locations called breakpoints, which allow you to examine functions or scripts more closely at these locations. You can verify whether variables actually do contain an expected result. Moreover, PowerShell offers you the option of integrating debugging messages into functions or scripts. This enables your code to output progress reports to you at key locations when your code is in the development stage.

Topics Covered:

"What-if" Scenarios

Automation is enormously convenient, but it you can also automate errors into the process that can wreak total havoc. That's why PowerShell has some mechanisms t to check and protect against potentially dangerous processes: these mechanisms are simulation and stepped confirmation.

Dry Runs: Simulating Operations

If you'd like to first find out what effects a particular command could have when you use it, try simulation. PowerShell will make no changes to your system but show you what would happen if you were to run a command without simulation. Use the -whatif parameter, which many cmdlets support, to turn on simulation.

# What exactly would happen if Stop-Process
# ended all processes beginning with "c"?
Stop-Process -Name c* -WhatIf
WhatIf: "Stop-Process" operation is run for the target "ccApp (920)".
WhatIf: "Stop-Process" operation is run for the target "CCC (5612)".
WhatIf: "Stop-Process" operation is run for the target "ccSvcHst (1848)".
WhatIf: "Stop-Process" operation is run for the target "conime (5280)".
WhatIf: "Stop-Process" operation is run for the target "csrss (632)".
WhatIf: "Stop-Process" operation is run for the target "csrss (688)".

Of course, your own functions and scripts will support simulation only if you integrate them. Do this by simply defining a switch parameter called whatif:

function MapDrive([string]$driveletter, `
[string]$target, [switch]$whatif)
{
If ($whatif)
{
Write-Host "WhatIf: creation of a network drive " + `
"with the letter ${driveletter}: at destination $target"
}
Else
{
New-PSDrive $driveletter FileSystem $target
}
}

# Simulate the command first to see what it does:
MapDrive k \\127.0.0.1\c$ -whatif
WhatIf: creation of a network drive
with letter k: at destination \\127.0.0.1\c$

# Execute command:
MapDrive k \\127.0.0.1\c$
Name Provider Root
---- -------- ----
k FileSystem \\127.0.0.1\c$

Stepped Confirmation: Separate Queries

As you've seen, PowerShell commands, mainly by using wildcards like "*", are capable of carrying out several tasks at once. To prevent unintentional operations from running, you can give the command the task of asking for confirmation before carrying out every single operation. In contrast to simulation, stepped confirmation gives you the option of actually carrying out operations one at a time, or all at once. Use the -Confirm parameter to turn on stepping:

Stop-Service a* -Confirm
Confirm
Are you sure you want to perform this action?
Performing operation "Stop-Service" on Target "ApplicationLookup (AeLookupSvc)".
|Y| Yes |A| Yes to All |N| No |L| No to All |S| Suspend |?| Help :

Confirm
Are you sure you want to perform this action?
Performing operation "Stop-Service" on Target "Agere Modem Call Progress Audio (AgereModemAudio)".
|Y| Yes |A| Yes to All |N| No |L| No to All |S| Suspend |?| Help:

The confirming procedure offers you six options for each action that can be selected by pressing a button.

Option Description
Yes Action will be carried out
Yes to all Action will be carried out and all remaining actions will also be carried out without further queries
No Action will not be carried out
No to all Action will not be carried out and the remaining actions will also not be carried out without further queries (terminate)
Suspend The action will be interrupted and you will be returned to the prompt, where you can carry out additional checks. As soon as you type the command "exit", you will continue the interrupted action
Help Supplies Help information

Table 11.1: Selection options in stepped confirmation

Automatic Confirmation of Dangerous Actions

Because some operations are more critical than others, developers of PowerShell cmdlets have assigned a risk evaluation to each command. There are three settings to choose from: Low, Medium, and High.

The Stop-Process cmdlet, which is used to stop running processes and programs, is set to Medium because while it is somewhat risky to stop processes, you normally shouldn't expect any irreversible damage. The Exchange cmdlet, used to remove a user mailbox, is categorized as High because when a mailbox is deleted all of its contents are lost as well.

You may not change this risk assessment, but you can respond to it. PowerShell's default setting requires that it check with you automatically about operations in the High risk category even if you haven't specified the -Confirm parameter. This standard setting is stored in the $ConfirmPreference variable so you can respond by making the default less or more rigorous. If you set $ConfirmPreference to "Low"(and use quotation marks), PowerShell will automatically question all actions. But if you set $ConfirmPreference to "None", PowerShell will no longer automatically question any actions, even if cmdlets are set to High.

# Calculator may be started and stopped without being called
# into question because Stop-Process is in the Medium category:
Calc
Stop-Process -Name calc

# If the default setting is changed from High to Low,
# PowerShell will automatically question every action:
$ConfirmPreference = "Low"
calc
Stop-Process -Name calc
Confirm
Are you sure you want to perform this action?
"Stop-Process" operation is run for the target "calc(2388)".
|Y| Yes |A| Yes to All |N| No |L| No to All |S| Suspend |?| Help:

Two consequences result from this:

  • High-risk environment: If you are uncertain, or working in an environment in which the slightest error can have far-reaching consequences, set $ConfirmPreference to "Low" so that you will be queried even for actions that aren't so hazardous.
  • Unintentional execution:If actions run unintentionally, then no interactive queries should appear, not even for risky actions. In such cases, turn off querying by using the -Confirm:$false parameter for a single command. Alternatively, turn off automatic querying in general by setting $ConfirmPreference="None". If you'd like to turn off automatic querying for scripts only but still keep the console in interactive mode, then set $script:ConfirmPreference="None" inside your script.

Defining Fault tolerance

PowerShell is very tolerant when errors occur: it simply continues execution—and that is often exactly the mode you want. For example, imagine if you started a file copying action that takes several hours. Returning to your system some time later, you wouldn't be pleased to find out that the operation had already been halted after the fifth data file because of an error. PowerShell default settings take this into account and carry out your tasks to the greatest possible extent rather than stopping them because of errors.

If an error fails to cause PowerShell to halt an entire task, you can end up with undesired consequences. Although PowerShell can't locate the file in the previous example and can't delete it for this reason, the subsequent command is executed anyway, and PowerShell concludes its work with a cheerful "Done!".

Del "nosuchthing"; Write-Host "Done!"
Remove-Item : Cannot find "C:\Users\Tobias Weltner\nosuchthing"
because it does not exist.
At line:1 char:4
+ Del <<<< "nosuchthing"; Write-Host "Done!"
Done!

If you set the $ErrorView automatic variable to the CategoryView value, PowerShell will sum up error messages briefly in just one line, and that's the better policy for real professionals:

$errorview
NormalView

1/$null
Attempted to divide by zero.
At line:1 char:3
+ 1/$ <<<< null

$errorview = "categoryview"
1/$null
NotSpecified: (:) [], RuntimeException

To determine how PowerShell handles errors, use ErrorAction, which specifies whether an error may terminate or may not terminate an operation. The default setting is "Continue", meaning that PowerShell will report an error but continue. Set ErrorAction to "Stop" so that PowerShell doesn't just go on processing the next statement but stops if a terminating error crops up. This setting will be in effect for all subsequent commands or only for a particular one as the case may be.

If the setting is supposed to apply to one particular command, use the -ErrorAction parameter of the command to set ErrorAction. Then the next command will halt the action and will no longer output any messages that the action was successful:

Del "nosuchthing" -ErrorAction "Stop"; Write-Host "Done!"
Remove-Item : Command execution stopped because the shell variable
"ErrorActionPreference" is set to Stop: Cannot find path
"C:\Users\Tobias Weltner\nosuchthing" because it does not exist.
At line:1 char:4
+ Del <<<< "nosuchthing" -ErrorAction "Stop"; Write-Host "Done!"

On the other hand, if you want the setting to apply universally as a new default to all commands, then assign it to the $ErrorActionPreference variable. Take the example of a script: type this statement at the beginning of your script if you prefer actions in general to be stopped when errors occur:

$script:ErrorActionPreference = "Stop"

Whenever you let a cmdlet run, PowerShell checks first to see whether you used the -ErrorAction parameter to set the ErrorAction for the cmdlet. If not, PowerShell will use as an alternative the value deposited in $ErrorActionPreference. If you would like to change the default in scripts only and not in the interactive console, then use a local variable in the script the way you did in the above example. Local variables begin with the "script:" prefix.

Setting Description
SilentlyContinue Suppress error message; continue to run the next command
Continue Output error message; continue to run next command (default)
Stop Halt the execution
Inquire Query

Table 11.2: Setting options for ErrorAction and $ErrorActionPreference

Recognizing and Responding to Errors

If you want to react to errors yourself so you can output your own, more readable error messages, you'll need two things: first, a way to suppress the built-in error message; second, a mechanism telling you whether an error has arisen or not. You already know how to suppress error messages because you have, once again, dealt with by ErrorAction. If you set it to "SilentlyContinue", then PowerShell will no longer output any error messages. You've already taken the first step:

Del "nosuchthing" -ErrorAction "SilentlyContinue"

Error Status in $?

Evaluate the $? variable as well to give the user feedback about whether an action was successful or not. . It will tell you whether an error has occurred. If one has, the variable will contain the value $false. This should give you enough to write a little evaluation script:

Del "nosuchthing" -ErrorAction "SilentlyContinue"
If (!$?) { "Didn't work!"; break }; "Everything's okay!"

If you're now wondering about the peculiarity of the conglomeration "(!$?)", here's a brief refresher: "!" stands for the logical "Not" operator. The condition is met if the $? variable doesn't contain the $true value (meaning that an error has occurred). Break ensures that the string doesn't keep on running. The result is that the text "Everything's okay!" will be output only if an error hasn't occurred.

If you'd like to find out just what sort of error came up, inspect the element 0 in the $error array. PowerShell keeps a record of all errors in $error. The most recent one is in the element 0:

Del "nosuchthing" -ErrorAction "SilentlyContinue"
If (!$?) { "Error: $($error[0])"; break }; "Everything's okay!"
Error: Cannot find path "u:\nosuchthing" because it does not exist.

Using Traps

Alternatively, you can use so-called "traps". If you know that a particular command may not execute successfully under runtime conditions, then note in front of it what should happen if an error occurs:

Trap { "A dreadful error has occurred!"} 1/$null
A dreadful error has occurred!
Attempted to divide by zero.
At line:1 char:53
+ Trap { "A dreadful error has occurred!"} 1/$ <<<< null

This shows that the Trap statement specifies PowerShell code that is meant to run as soon as an error occurs that cannot be handled in any other way.

Traps Require Unhandled Exceptions

To get this to work, an error really does have to occur or, to be precise, an unhandled exception has to come up. In the next example, you'll see that this is not always the case because the code after Trap is not executed, even though an error message pops up:

Trap { "A dreadful error has occurred!"} 1/0
Attempted to divide by zero.
At line:1 char:53
+ Trap { "A dreadful error has occurred!"} 1/0 <<<<

The reason: the 1/0 statement consists exclusively of constant values; that's why PowerShell evaluates it even as it is being compiled. The parser that performs this task recognizes entirely on its own that the statement is an invalid numerical value and handles the error itself. So, it isn't even reported to Trap. Very much the same thing happens with most cmdlets because when a cmdlet triggers an error, the error is likewise processed internally by the cmdlet and is not reported to Trap:

Trap { "A dreadful error has occurred!" } Del "nosuchthing"
Remove-Item : Cannot find path "C:\Users\Tobias Weltner\nosuchthing"
because it does not exist.
At line:1 char:54
+ Trap { "A dreadful error has occurred!"} Del <<<< "nosuchthing"

Surely, it must be possible to catch an error inside a cmdlet? Yes, but only if you set the ErrorAction of the cmdlet to "Stop", which ensures that the error does in fact get reported back to the caller and that the cmdlet doesn't snare it internally:

Trap { "A dreadful error has occurred!" } `
Del "nosuchthing" -ErrorAction "Stop"
A dreadful error has occurred!
Remove-Item : Command execution stopped because the
shell variable "ErrorActionPreference" is set to Stop:
Cannot find path "C:\Users\Tobias Weltner\nosuchthing"
because it does not exist.
At line:1 char:54
+ Trap { "A dreadful error has occurred!"} Del <<<< "nosuchthing"

Using Break and Continue to Determine What Happens after an Error

After Trap has processed the error by executing the code you specified after Trap, it continues execution. That means that PowerShell will output the error message about the current error and continue execution with the next command just as if nothing at all had happened. That's why in the next example your own error message is output first, then PowerShell's, and finally all further commands, which in this case means the text "Hello":

Trap { "A dreadful error has occurred!" } `
Del "nosuchthing" -ErrorAction "Stop"; "Hello"
A dreadful error has occurred!
Remove-Item : Command execution stopped because the shell variable
"ErrorActionPreference" is set to Stop: Cannot find path
"C:\Users\Tobias Weltner\nosuchthing" because it does not exist.
At line:1 char:54
+ Trap { "A dreadful error has occurred!"} Del <<<< "nosuchthing"
Hello

If you would like a different response, use the keyword Break or Continue in the Trap statement. If you specify Continue, Trap will behave to a certain extent like the ErrorAction "SilentlyContinue" and will suppress the integrated PowerShell error message:

Trap {"A dreadful error has occurred!";Continue} `
Del "nosuchthing" -ea "Stop"; "Hello"
A dreadful error has occurred!
Hello

Trap continues execution with the next statement, which is in the same block as Trap itself. That might seem to be hedged in with too many clauses, but that doesn't play any role in this example because there's only one area. A little later on, you'll see that such a subtlety is actually very crucial.

If you use the Break statement instead of Continue, Trap will respond to a certain extent like the ErrorAction "Stop" and will generate its built-in error message. Subsequent statements will no longer be executed.

Finding Out Error Details

Inside the code after Trap, PowerShell automatically inserts the $_ variable, which includes all details about the current error. Your "universal" Trap might look like this if you want to catch an error and output details:

Trap { Write-Host -Fore Red -back White $_.Exception.Message; `
Continue }; 1/$null

Error Records: Error Details

You have just output details about the error using the $error automatic variable and $_ inside the Trap block. Let's take a closer look at what is actually stored in these variables and how an Error Record looks. Error Records are precisely what the name says, namely what you normally see when an error occurs in PowerShell: the actual error message, which PowerShell displays in red:

Dir MacGuffin
Get-ChildItem : Cannot find path "C:\Users\Tobias Weltner\MacGuffin"
because it does not exist.
At line:1 char:4
+ Dir <<<< MacGuffin

You may have already asked yourself where does PowerShell actually set the color of its error message? This setting is located in $host.PrivateData. The following lines will set error message colors to red on a white background:

$host.PrivateData.ErrorForegroundColor = "Red"
$host.PrivateData.ErrorBackgroundColor = "White"

You can also find additional properties in the same location which enable you to change the colors of warning and debugging messages.

However, this is only how Error Records look when you output the records in the console because PowerShell, as always, reduces the object with its wealth of information to text. Just how do you access the actual Error Record object? There are four approaches:

  • Redirection: Redirect the error stream to a variable.
  • The -ErrorVariable parameter: The error record will be stored in this variable if you use the -ErrorVariable parameter to specify a variable name.

If you put a plus sign in front of the variable name, the error will be added to the variable so that you could store several errors in the variable: -ErrorVariable +listing

  • $error: All errors will be stored as an error record in the $error variable. As a result, the last error is in $error[0].
  • Traps and $_:Inside the Trap statement, the current error record is provided in $_.

Error Record by Redirection

If you want to redirect a command result, use the redirection operator ">":

Dir MacGuffin > error.txt
Get-Content error.txt

Unfortunately, that won't help if the command outputs an error, because errors are not written in the standard stream but in the Error stream. If you want to redirect it, the redirection operator must be "2>":

Dir MacGuffin 2> error.txt
Get-Content error.txt
Get-ChildItem : Cannot find path "C:\Users\Tobias Weltner\MacGuffin"
because it does not exist.
At line:1 char:4
+ Dir <<<< MacGuffin 2> error.txt

It works: the error message—the Error Record—was in fact redirected and written in a file that Get-Content subsequently reads. However, Error Record is actually not text at all but, like nearly everything else in PowerShell, an object containing much more information than its plain text representation. When you redirect Error Record to a file, much of the data contained in Error Record gets lost in the process. The clever method is this:

# Redirect Error Record and move it into the pipeline and
# assign it to $myerror afterwards
$myerror = Del "nosuchthing" 2>&1

# $myerror now contains the Error Record in object form
# so that error details can be queried:
# Error message:
$myerror.Exception.Message
Cannot find path "C:\Users\Tobias Weltner\nosuchthing"
because it does not exist.

# Error cause:
$myerror.InvocationInfo
MyCommand : Remove-Item
ScriptLineNumber : 1
OffsetInLine : -2147483648
ScriptName :
Line : $myerror = Del "nosuchthing" 2>&1
PositionMessage :
At line:1 char:15
+ $myerror = Del <<<< "nosuchthing" 2>&1
InvocationName : Del
PipelineLength : 1
PipelinePosition : 1

# Clearly erroneous identification:
$myerror.FullyQualifiedErrorId
PathNotFound,Microsoft.PowerShell.Commands.RemoveItemCommand

The central point of this example is the redirection to &1, a character combination that stands for the output pipeline. Think about this for a moment: If you had passed a valid directory name to the Dir command, the command would have retrieved the directory listing, and you could have stored the result in a variable without any difficulty. Nobody would have wondered:

$result = Dir

By using the "2>&1" redirection operator, you can simply send just the error information— Error Record—over the same route, and that's why you can directly allocate Error Record to a variable and evaluate it afterwards. The properties of Error Record listed in Table 11.3 are available to you.

Property Description
CategoryInfo The error is assigned to broad category, activity, cause, caller, and call type. In this way, similar errors of differing origin can be recognized and jointly handled.
ErrorDetails Often this is null; developers can deposit additional information here about the error.
Exception The underlying .NET exception matching the error. Exception.Message provides you the error message.
FullyQualifiedErrorID Specific and special misidentification allowing you to identify the error and enable appropriate follow-up action.
InvocationInfo Supplies information about where the error occurred, such as the script name and its location in the script.
TargetObject The object that was operated on when the error occurred. Often null or text corresponding to the argument that a cmdlet could not process.

Table 11.3: Properties of an error record

But note that you are given these properties only if the error record was actually output visibly without redirection. The following statement returns "null":

$myerror = Del "nosuchthing" -ea "SilentlyContinue" 2>& amp;1

Error Record(s) Through the -ErrorVariable Parameter

Redirection isn't always necessary. Most cmdlets support the -ErrorVariable parameter after you specify the name of a variable. The Error Record will then be stored in this variable independently of the current ErrorAction:

Del "nosuchthing" -ErrorVariable myError -ErrorAction "SilentlyContinue"

However, what is now present in $myError isn't quite identical to what redirection stored in $myError. In the redirection process, exactly one Error Record was stored—namely the one that would have otherwise been visibly displayed as an error message. The -ErrorVariable parameter always returns an array. The array has only one element, but if you want to evaluate the Error Record and its manifold properties, you will have to specifically access this array element:

$myError[0].Exception.Message
Cannot find path "C:\Users\Tobias Weltner\nosuchthing" because it does not exist.

And why is Error Record stored in an array? Isn't that totally superfluous when, after all, there's only one Error Record? Several Error Records can in fact exist. In the next example, the directory listings will be retrieved from three different directories where none exists. The result is three Error Records:

Dir nosuchthing,notthere,whereisit `
-ErrorVariable myError -ErrorAction "SilentlyContinue"
$myError.Count
3

You can also collect the Error Records of several statements in your variable to document entire sequences of errors. Just type a "+" in front of the variable name for the -ErrorVariable parameter.

Cd notthere -ErrorVariable listing -ErrorAction "SilentlyContinue"
Del nosuchthing -ErrorVariable + listing -ErrorAction "SilentlyContinue"
$listing
Set-Location : Cannot find path "C:\Users\Tobias Weltner\notthere"
because it does not exist.
At line:1 char:3
+ Cd <<<< notthere -ErrorVariable listing -ErrorAction "SilentlyContinue"
Remove-Item : Cannot find path "C:\Users\Tobias Weltner\nosuchthing"
because it does not exist.
At line:1 char:4
+ Del <<<< nosuchthing -ErrorVariable +listing -ErrorAction "SilentlyContinue"

In case you're asking yourself why PowerShell outputs all Error Records at the same time when you're outputting $listing in the example, remember that PowerShell converts the contents of an array into text automatically if you fail to explicitly select a specific element from it.

Error Records Through $Error

There's still another way to get to Error Records. PowerShell keeps exacting records of all errors and stores these records in the $Error variable. So, even if you've forgotten to redirect the Error Record in time or to specify the -ErrorVariable parameter, you can still get to the Error Record of an error.

$Error is an array, too. The most current error always ends up as the first entry (with the index 0) and all other errors move up their position once. The number of errors stored is limited to ensure enough storage space when many errors are recorded. The maximum number is set in $MaximumErrorCount.

Error Record Through Traps

Finally, the Trap statement also offers a way to get to the current Error Record. Access is gained through the $_ variable inside the Trap statement, and you could use Trap to generate your own error messages very easily:

Trap {"Oops, error: $($_.Exception.Message)";Continue} `
Del nosuchthing -ea Stop
Oops, error: Command execution stopped because the shell variable
"ErrorActionPreference" is set to Stop: Cannot find path
"C:\Users\Tobias Weltner\nosuchthing" because it does not exist.

Understanding Exceptions

"Exceptions" are not everyday occurrences. In the contemporary IT world, the terms "errors" or "bugs" tend to be avoided. Instead, a more elegant word, "exception," is used. When an error occurs, an exception is thrown and has to be "remedied." Either the command responsible for the error rectifies the error or the exception escalates and it has to be remedied at the next-highest level. If nobody tackles the error, it will end up highlighted in red in the PowerShell console.

Because there are different types of exceptions, it is interesting to examine the exception type of an error more closely. This is a way for you to initiate different actions according to the approximate cause of an error. Take a look at how you can flush the exception of an error out into the open:

# List exception type of the last error:
$error[0].Exception.GetType().Name
RuntimeException

# Output all exception types for all errors in this PS session:
$error | Foreach-Object { $_.Exception.GetType().FullName }
System.Management.Automation.CommandNotFoundException
System.Management.Automation.RuntimeException
System.Management.Automation.ItemNotFoundException
You cannot use the NULL value to call a method for an expression.
At line:1 char:47
+ $error | Foreach-Object { $_.Exception.GetType( <<<< ).FullName }

Both examples presuppose that errors were actually listed previously, because otherwise $error would be null.

When you get errors in the listing that complain about a NULL value, then you'll know that some error records are contained in $error that were not thrown by an exception.

Handling Particular Exceptions

The code set by Trap is normally executed for any exception. As you've just seen, there are exception groups, and if you'd prefer to use one or several groups of different error handlers, go ahead. Just write several Trap statements and specify for each the type of exception for which the statement is responsible:

function Test
{
Trap [System.DivideByZeroException] {
"Divided by null!"; Continue
}
Trap [System.Management.Automation.ParameterBindingException] {
"Incorrect parameter!"; Continue
}
1/$null
Dir -MacGuffin
}
Test
Divided by null!
Incorrect parameter!

Throwing Your Own Exceptions

If you're writing your own functions or scripts, sooner or later you'll want to output your own error messages in them. You should never output unchangeable error messages to enable your functions and scripts to be inserted like building blocks in PowerShell just like all the previous cmdlets and functions you've already seen.,. Instead, it would be better to use Throw to throw exceptions and to leave it up to the system to handle your exception.

function TextOutput([string]$text)
{
If ($text -eq "")
{
Throw "You have to enter some text."
}
Else
{
"OUTPUT: $text"
}
}

# An error message will be thrown if no text is entered:
TextOutput
You have to enter some text.
At line:5 char:10
+ Throw <<<< "You have to enter some text."

# No error will be output in text output:
TextOutput Hello
OUTPUT: Hello

Of course, you already know from your reading of Chapter 9 that it's best for you to define error messages about the arguments of a function as default values. The previous example was supposed to be just a general demonstration of how exceptions are thrown inside a function. If the objective is merely to validate the correct arguments, the function can be simplified considerably:

function TextOutput([string]$text = $(Throw "You have to enter some text."))
{ "OUTPUT: $text" }

But the main thing is that your function should not output its own error messages in the event of a fault, but instead throw an exception. That leaves it up to the user of the function to decide what to do with the exception you have thrown:

Trap { "Oh, an error."; Continue} ; TextOutput
Oh, an error.

Catching Errors in Functions and Scripts

Error handling basically works in your own functions or scripts just as it does in the console. Use traps if you want to catch errors. In this connection, it isn't important where you exactly situate the Trap statement. No matter if you put the statement at the beginning or at the end, as soon as an error occurs inside the function, the Trap statement code will be executed. This means that the following two functions would behave in exactly the same way:

function malfunction1
{
Trap { "An error occurred." }

1/$null
Get-Process "nosuchthing"
Dir xyz:
}

malfunction1
An error occurred.
Attempted to divide by zero.
At line:3 char:5
+ 1/$ <<<< null
Get-Process : Cannot find a process with the name "nosuchthing".
Verify the process name and call the cmdlet again.
At line:4 char:14
+ Get-Process <<<< "nosuchthing"Get-ChildItem : Cannot find drive.
A drive with name "xyz" does not exist.
At line:5 char:6
+ Dir <<<< xyz:

The result of the function example is interesting and confusing at the same time. The first statement inside the function doesn't cause an error. That's why the code after Trap is executed and returns the error message "An error occurred". PowerShell's error message follows afterwards because ErrorAction is not set to SilentlyContinue. The remaining two faulty commands are also executed. To be precise, they are executed this time without renewed execution of the Trap block.

You already know the reason why: Trap can only capture errors that it can see. Cmdlets use the standard setting Continue as ErrorAction. In this setting, cmdlets do not report errors to the caller but handle errors themselves. If you'd like your trap to deal with such errors, you must reset ErrorAction to Stop:

function malfunction1
{
Trap { "An error occurred."}

1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}

malfunction1
An error occurred.
Attempted to divide by zero.
At line:4 char:5
+ 1/$ <<<< null
An error occurred.
Get-Process : Command execution stopped because the shell variable
"ErrorActionPreference" is set to Stop: Cannot find a process with
the name "nosuchthing". Verify the process name and call the cmdlet again.
At line:5 char:14
+ Get-Process <<<< "nosuchthing" -ea Stop
An error occurred.
Get-ChildItem : Command execution stopped because the shell variable
"ErrorActionPreference" is set to Stop: Cannot find drive. A drive with
name "xyz" does not exist.
At line:6 char:6
+ Dir <<<< xyz: -ea Stop

Now, the function works the way it was expected to originally. The trap is called for every single error. However, internal PowerShell error messages are still generated subsequently. Error messages will no longer appear if you use Continue to instruct your trap to keep on going after the error. Your trap should return an explanatory comment so that you can also find out which error actually occurred.

function malfunction1
{
Trap {"Oops, error: $($_.Exception.Message)";Continue}

1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}

malfunction1
Oops, error: Attempted to divide by zero.
Oops, error: Command execution stopped because the
shell variable "ErrorActionPreference" is set to Stop:
Cannot find a process with the name "nosuchthing". Verify
the process name and call the cmdlet again.
Oops, error: Command execution stopped because the
shell variable "ErrorActionPreference" is set to Stop: Cannot find drive. A drive with name "xyz" does not exist.

If you would prefer that the function stops when the first error occurs, you should use the Break statement inside Trap: instead of Continue.

function malfunction1
{
Trap {"Oops, error: $($_.Exception.Message)"; Break}

1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}

malfunction1
Oops, error: Attempted to divide by zero.
Attempted to divide by zero.
At line:4 char:5
+ 1/$ <<<< null

Now the function is stopped when the first error occurs, but the internal PowerShell message turns up again after your own error message. Break does stop execution in the current area, but does this by throwing the original error again and letting PowerShell handle it. This means that when you use Break you'll always get the PowerShell error message.

If you would prefer that a function stops after the first error without PowerShell adding its own error message, you must do without Break and understand a little better what the Continue statement is actually doing. Continue carries on execution after an error with the next statement that is in the same area as Trap. So, if the Trap statement is inside your function, and if an error occurs, then Continue would carry on with the next command inside the function. However, you must move Trap to the parent area since you want the function to stop after the first faulty command. You could call the function from within a second function:

function Caller
{
Trap {"Oops, error: $($_.Exception.Message)"; Continue}
malfunction
}

function malfunction
{
1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}
Caller
Oops, error: Attempted to divide by zero.

Now the sequence of operations functions as requested. Trap recognizes the first error in the Malfunction function and because of Continue carries on execution with the next statement: not with the next statement in Malfunction but with the next statement in Caller, because that's where Trap was defined.

Of course, this type of call is little peculiar. Do you really have to bother about a second separate caller function just so you can stop a function when the first error occurs and take care of the error yourself? You don't. You just need to make sure that the Trap statement is in another area than the one in which the rest of the function is located. For example, you could define an additional one inside your function and then call it:

function malfunction
{
# Trap is defined in the outer area of the function:
Trap {"Oops, error: $($_.Exception.Message)"; Continue}

# The rest of the function is your own
function InnerCore
{
1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}

InnerCore
}

It is indeed possible for you to nest functions in PowerShell. The InnerCore function is then a private function that is valid only inside the Malfunction function. However, it does not suffice to only define the InnerCore function, which naturally must also be specifically called so that it completes its task. Because the faulty lines now run in their own encapsulated function and Trap is defined outside it, Continue doesn't execute the next statement in InnerCore but the next statement in Malfunction.

In the next chapter, you'll find out in detail how PowerShell really implements functions. You'll learn more about scriptblocks. You don't necessarily have to create your own nested subfunctions. It suffices for you to put critical instruction lines in their own scriptblock and to use the call operator "&" to execute the block:

function malfunction
{
# Trap is defined in the outer area of the function:
Trap {"Oops, error: $($_.Exception.Message)"; Continue}

# The rest of the function is in its own scriptblock
# that is immediately executed by using "&":
& {
1/$null
Get-Process "nosuchthing" -ea Stop
Dir xyz: -ea Stop
}
}
malfunction
Oops, error: Attempted to divide by zero.

Stepping Through Code: Breakpoints

You can stop PowerShell code inside your functions and scripts at any point to see whether everything is working the way it should. To do so, use breakpoints, which are nothing more than commands. In less complicated cases, you can input the breakpoint command right in the PowerShell console:

Write-Debug "I'll just stop for a moment."

Surprisingly, this command appears to have no effect whatsoever. Nothing happens. The reason is that the $DebugPreference variable is set as default for the "SilentlyContinue" variable. "SilentlyContinue" corresponds exactly to what you've already seen it do: output nothing, continue. As long as $DebugPreference is set to this value, debugging is turned off. If you want to turn it on, set $DebugPreference to another one of the values listed in Table 11.4.

Setting Description
SilentlyContinue Debugging is turned off
Stop Execution is stopped because debugging makes less sense
Continue Debugging information is output and the statement immediately continued
Inquire You receive the selection and, you can temporarily suspend execution to examine your code

Table 11.4: Settings for $DebugPreference

There you have the tools you need for a little debugging kit:

  • Installing breakpoints: Use Write-Debug at all locations inside a function or a script that you want to monitor more closely and have Write-Debug output useful comments.
  • Simple debugging: Switch $DebugPreference to "Continue" so that Write-Debug will output its comments, allowing you to follow the sequence of code operations.
  • Extended debugging: Switch $DebugPreference to "Inquire" so that Write-Debug works like a real breakpoint. Execution is then stopped for every Write-Debug, and you can use the suspend option to get back to a prompt and from there use customary PowerShell commands, such as to verify the contents of variables. Execution will be continued as soon as you type the exit command.
  • Turning off debugging again: If you would like to stop debugging again, simply set $DebugPreference to the initial value "SilentlyContinue". All Write-Debug statements will immediately be ignored so that you won't need to remove these statements. Perhaps you would like to debug the code later again. As long as $DebugPreference is set to "SilentlyContinue", these statements will have no effect.

The following loop shows how your breakpoints will respond to different settings for $DebugPreference. If you use the default for execution, 10 numbers will be output:

For ($i=0; $i -lt 10; $i++) { Write-Debug "Counter is at $i"; $i }
0
1
2
3
(...)

If you turn on simple debugging, the Write-Debug comments will be highlighted in yellow:

$Debug-Preference = "Continue"
For ($i=0; $i -lt 10; $i++) { Write-Debug "Counter is at $i"; $i }
DEBUG: Counter is at 0
0
DEBUG: Counter is at 1
1
DEBUG: Counter is at 2
2
(...)

By using extended debugging, you can convert Write-Debug statements into genuine breakpoints. You could also interrupt execution, inspect variables, and even make changes. As soon as you enter the Exit command, execution will continue:

# Activate full debugging:
$Debug-Preference = "Inquire"
For ($i=0; $i -lt 10; $i++) { Write-Debug "Counter is at $i"; $i }
DEBUG: Counter is at 0
Confirm
Continue with this operation?
|Y| Yes |A| Yes to All |B| Suspend command |S| Suspend |?| Help (default is "J"):

(H)
>>
# Execution is suspended; you may inspect variables and make changes:
>> $i
0

>> $i=7

>> # Use the Exit command to continue execution
>> exit
Confirm
Continue with this operation?
|Y| Yes |A| Yes to All |B| Suspend command |S| Suspend |?| Help (default is "Y"):

(A)
7
(...)

Aside from the automatic variable $DebugPreference, which you can use to determine whether and how debugging messages are output, there are a number of additional automatic variables that work in a similar way and define how PowerShell should respond if you make no further specifications (Table 11.5).

Variable Description
ConfirmPreference Specifies when confirmation should be requested. Confirmation is requested when "ConfirmImpact" of the operation is greater than or equal to "$ConfirmPreference". If "$ConfirmPreference" is set to "None", actions will be confirmed only if "Confirm" is specified.
DebugPreference Specifies the action to take when a debugging message is conveyed.
ErrorActionPreference Specifies the action to take when an error message is conveyed.
ErrorView Specifies the display mode for showing errors.
ProgressPreference Specifies the action to take when status files are conveyed.
ReportErrorShowExceptionClass Results in display of errors with a description of the exception class.
ReportErrorShowInnerException Results in display of errors with inner exceptions.
ReportErrorShowSource Results in display of errors with cause of errors.
ReportErrorShowStackTrace Results in display of errors with stack trace.
VerbosePreference Specifies the action to take when a detailed message is conveyed. Permitted values are "SilentlyContinue", "Stop", "Continue" and "Inquire".
WarningPreference Specifies the action to take when a warning message is conveyed.
WhatIfPreference If "true", "WhatIf" is regarded as enabled for all commands.

Table 11.5: Fine adjustments of the PowerShell console

Tracing: Displaying Executed Statements

You don't necessarily have to insert debugging statements into your code since sometimes it's not your code that is even being executed. .However, you do have the option to enable tracing. This allows PowerShell to output each statement automatically as a debugging message. The cmdlet Set-PSDebug manages tracing.

Set-PSDebug -trace 1
Dir *.txt
DEBUG: 1+ Dir *.txt
DEBUG: 1+ $_.PSParentPath
DEBUG: 1+ $catr = "";
DEBUG: 2+ If ( $this.Attributes -band 16 ) { $catr += "d" }
Else { $catr += "-" } ;
DEBUG: 2+ If ( $this.Attributes -band 16 ) { $catr += "d" }
Else { $catr += "-" } ;
DEBUG: 3+ If ( $this.Attributes -band 32 ) { $catr += "a" }
Else { $catr += "-" } ;
DEBUG: 3+ If ( $this.Attributes -band 32 ) { $catr += "a" }
Else { $catr += "-" } ;
DEBUG: 4+ If ( $this.Attributes -band 1 ) { $catr += "r" }
Else { $catr += "-" } ;
DEBUG: 4+ If ( $this.Attributes -band 1 ) { $catr += "r" }
Else { $catr += "-" } ;
DEBUG: 5+ If ( $this.Attributes -band 2 ) { $catr += "h" }
Else { $catr += "-" } ;
DEBUG: 5+ If ( $this.Attributes -band 2 ) { $catr += "h" }
Else { $catr += "-" } ;
DEBUG: 6+ If ( $this.Attributes -band 4 ) { $catr += "s" }
Else { $catr += "-" } ;
DEBUG: 6+ If ( $this.Attributes -band 4 ) { $catr += "s" }
Else { $catr += "-" } ;
DEBUG: 7+ $catr
DEBUG: 2+ [String]::Format("{0,10} {1,8}", $_.LastWriteTime.
ToString("d"), $_.LastWriteTime.ToString("t"))

Directory: Microsoft.PowerShell.Core\FileSystem::C:\Users\Tobias Weltner

Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 19.09.2007 14:30 13386 output.txt

Here, PowerShell lists the PowerShell code of the ScriptProperty Mode, which is executed when you output a directory listing.

Simple tracing will show you only PowerShell statements executed in the current context. If you invoke a function or a script, only the invocation will be shown but not the code of the function or script. If you would like to see the code, turn on detailed traced by using the -trace 2 parameter.

Set-PSDebug -trace 2

If you would like to turn off tracing again, select 0:

Set-PSDebug -trace 0

Stepping: Executing Code Step-by-Step

You don't even need your own breakpoints to run PowerShell code one step at a time. Just turn on automatic stepping:

Set-PSDebug -step

From then on, PowerShell will ask you when every single statement is displayed whether you want to execute the statement, skip it, or temporarily suspend the code.

If you choose Suspend by pressing "H", you will end up in a nested prompt, which you will recognize by the ">>" sign at the prompt. The code will then be interrupted so you could analyze the system in the console or check variable contents. As soon as you enter Exit, execution of the code will continue. Just select the "A" operation for "Yes to All" in order to turn off the stepping mode.

By the way, you can also create your own breakpoints by using nested prompts: call $host.EnterNestedPrompt() inside a script or a function.

Set-PSDebug has another important parameter called -strict. It ensures that unknown variables will throw an error. Without the Strict option, PowerShell will simply set a null value for unknown variables.

Summary

By using "what-if" scenarios, you can validate the consequences of commands in safe dry runs. One option is to specify the -whatif parameter if you'd like to see what a command might do. The other option is to specify the -Confirm parameter if you would like to confirm every single operation manually before execution (Table 11.1). Most cmdlets support both parameters so you can reproduce this functionality in your own functions or scripts by using self-defined switch parameters.

Code in functions and scripts can be provided with debugging messages and breakpoints for diagnostic purposes. Insert Write-Debug statements into the code and use $DebugPreference to determine whether Write-Debug outputs a message or is actually supposed to suspend the code at the location (Table 11.4). If the code is suspended, you can make a detailed examination of the variables of your function or script in the console. Type Exit when you end the breakpoint and want to continue execution of code.

PowerShell includes other automatic variables that enable you to determine whether and when commands have to be confirmed and how detailed error reports should be (Table 11.5). Among these variables is $ErrorActionPreference, which you can use to specify whether PowerShell should continue execution even if errors occur, or stop execution. The default setting does not stop execution when errors occur unexpectedly.

You can evaluate a number of variables if you prefer responding to errors yourself. The $? variable contains $false if the last command caused an error. PowerShell lists error details in the $error array, in which every error is stored as an Error Record.

You can gain more control over errors by using traps, which are statements executed when an error occurs. So that traps work, the error really must throw an exception and may not be caught by the command that caused it. For this reason, traps can react to errors only if you have previously switched ErrorAction from Continue to Stop.

Within the code after Trap, you'll be provided with all the details on the current error in the $_ variable, which contains the Error Record of the error. Your Trap statement can also use the Break and Continue statements to determine what will happen next. If you specify Break, then execution will be ended in the code block where Trap is defined. PowerShell will output the error message of the current error. If you specify Continue, PowerShell will continue execution with the next statement in the same block where Trap is defined. PowerShell will not output any error message.


Posted Mar 30 2009, 08:02 AM by ps1

Comments

Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-01-2009 2:31 PM

Pingback from  Chapter 9. Functions - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com wrote Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com
on 04-01-2009 2:32 PM

Pingback from  Chapter 11. Finding and Avoiding Errors - Master-PowerShell | With Dr. Tobias Weltner - PowerShell.com

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 15. The File System
on 04-01-2009 2:34 PM

The file system has special importance within the PowerShell console. One obvious reason is that administrators perform many tasks that involve the file system. Another is that the file system is the prototype of a hierarchically structured information

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 2. Interactive PowerShell
on 04-30-2009 12:51 AM

PowerShell has two faces: interactivity and script automation. In this chapter, you will first learn how to work with PowerShell interactively. Then, we will take a look at PowerShell scripts.

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 3. Variables
on 04-30-2009 12:52 AM

It is time to combine commands whenever a single PowerShell command can't solve your problem. One way of doing this is by using variables. PowerShell can store results of one command in a variable and then pass the variable to another command. In

Master-PowerShell | With Dr. Tobias Weltner wrote Chapter 6. Using Objects
on 04-30-2009 12:59 AM

PowerShell always works with objects. Whenever you output objects into the PowerShell console, PowerShell automatically converts the rich objects into readable text. In this chapter, you will learn what objects are and how to get your hands on PowerShell

Use PowerShell » Deep Dive: Error Handling – Error Types (part 1) wrote Use PowerShell &raquo; Deep Dive: Error Handling &ndash; Error Types (part 1)
on 07-21-2009 12:41 PM

Pingback from  Use PowerShell » Deep Dive: Error Handling – Error Types (part 1)

KodefuGuru wrote Free PowerShell EBook
on 07-24-2009 3:24 PM

Free PowerShell EBook

Copyright 2010 PowerShell.com. All rights reserved.