Previous articles we demonstrated how to build a fairly advanced inventory Tool using Windows PowerShell. The tool I created provides several options for output, thanks to the built-in functionality of the shell and the application of functions to objects.
The function I created has an undeniable weakness: it does not adequately handle any errors that may occur (such as connection or permission issues). That's what I'm going to address in this installment of the Windows PowerShell column, and I'll explain the error-handling features provided by Windows PowerShell.
Set Trap
In Windows PowerShell, the TRAP keyword defines an error handler. When an exception occurs in your script, the shell checks to see if the Trap has been defined, which means it must appear in the script before any exceptions occur. For this demo, I'll sort out a test script that will create a connectivity problem: I will use Get-wmiobject to connect to a computer name that does not exist in the network. My goal is to have the error Trap write out the invalid computer name to a file that provides me with a file that records invalid computer names. I will also be joining a connection to two valid computers (I will use localhost). Please see the script in Figure 1.
Add Trap
Trap {
write-host "Error connecting to $computer"-fore Red
"$computer" | Out-file c:\demo\errors.txt-append
continue
}
$computer = "localhost"
get-wmiobject win32_operatingsystem-comp $computer
$computer = " Server2 "
get-wmiobject win32_operatingsystem-comp $computer
$computer =" localhost "
get-wmiobject Win32_operatingsystem-comp $computer
The output of this script (shown in Figure 2) does not match my expectations. Please note that "Error Connecting to ..." The message is not displayed. The Errors.txt file is also not created. In other words, my Trap was not executed at all. What the hell happened?
Figure 2 This is not the output I want!
Stop it!
The key is to understand that the normal shell error message differs from the exception, which is divided into a non-terminating error and a termination error. Terminating an error stops the execution of the pipe and produces an exception. Only exceptions can be caught. When an error occurs, the shell checks its built-in $ErrorActionPreference variable to determine what action to perform. The variable defaults to the "Continue" value, which means "Show error message and continue." Changing this variable to Stop causes it to display an error message and produce a catch exception. However, this means that any errors in your script will also perform the operation.
A better approach is to use the stop behavior only for the cmdlet that you think might be causing the problem. You can do this by using the –erroraction (or –ea) parameter (a common parameter that is supported by all the cmdlet). Figure 3 shows a revised version of this script. It will work as expected and produce output as shown in Figure 4.
Using-erroraction
Trap {
write-host "Error connecting to $computer"-fore Red
"$computer" | Out-file c:\demo\errors.txt-append
continue
}
$computer = "localhost"
get-wmiobject win32_operatingsystem-comp $computer-ea stop
$ Computer = "Server2"
get-wmiobject win32_operatingsystem-comp $computer-ea stop
$computer = "localhost"
get-wmiobject win32_operatingsystem-comp $computer-ea Stop
Figure 4 I get more useful results when I use the –erroraction parameter
Use Continue at the end of the Trap to indicate a row after the line of code that caused the exception to continue executing. You can also use the keyword break (which I will discuss later). Also note that the $computer variable (defined in the script) is still valid within the Trap. This is because the trap is a child of the script itself, that is, the trap can view all the variables within the script (I will also explain more about this later).
Complete all actions in scope
A particularly tricky aspect of error trapping in Windows PowerShell is the use of scopes. The shell itself represents the global scope, which contains all the events that occur inside the shell. If you run a script, it gets its own script scope. If you define a function, the interior of the function is its own dedicated scope, and so on. This creates a hierarchy of parent/child types.
When an exception occurs, the shell looks for a Trap within the current scope. This means that an exception within a function will find a Trap inside the function. If the shell finds a trap, the trap is executed. If the Trap ends with Continue, the shell continues to execute the line following the line of code that throws the exception, but it is still in the same scope. Here's a few pseudocode to illustrate this:
Trap {
# LOG error to a file
Continue
}
get-wmiobject Win32_service–comp "Server2" –ea "Stop"
GET-PR Ocess
If the exception occurs on line 5th, the Trap in line 1th is executed. The Trap ends with a Continue, so the line 6th continues to execute.
Now consider the following slightly different scope example:
Trap {
# LOG error to a file
Continue
}
Function myfunction {
get-wmiobject win32_service–comp ' S Erver2 "–ea" "Stop"
get-process
}
myfunction write-host "
testing!"
If the error occurs on line 7th, the shell looks for a Trap within the scope of the function. If it is not found, the shell exits the scope of the function and continues to look for a Trap within the parent scope. Because there is a Trap, it will execute line 1th. In this case, the code is Continue, so the line of code after the exception in the same scope continues, that is, line 12th, not line 8th. In other words, the shell does not re-enter the function after exiting.
Now compare this behavior to the following example:
Function MyFunction {
Trap {
# LOG error to a file
Continue
}
get-wmiobject Win32_service–comp "Serv Er2 "–ea" "Stop"
get-process
}
myfunction write-host "
testing!"
In this case, the error in line 6th executes the Trap in line 2nd and remains within the scope of the function. The Continue keyword will remain within the scope and continue with line 7th. If you put a Trap in a scope that is expected to cause an error, the benefit is that you remain in scope and can continue to execute it. But what if this method does not apply to your situation?
This tool is ideal for managing configuration baselines. Compare-object (or Diff) is designed to compare two sets of objects. By default, it compares all the properties of each object and outputs all the differences by the command. So imagine that you've configured a server's services exactly the way you want them to. You can create a baseline simply by running the following:
Get-service | Export-clixml C:\baseline.xml
Almost all objects can be transported to Export-clixml, which converts objects to XML files. You can then run the same command (such as Get-service) and compare the results to the saved XML. The order is as follows:
Compare-object (Get-service) (Import-clixml
c:\baseline.xml) –property name
Adding the –property parameter forces the comparison to view only the property, not the entire object. In this case, you'll get a list of all the service names that are different from the original baseline, giving you an idea of whether any services have been added or removed after the baseline was created.
Disconnect
I mentioned the break keyword earlier. Figure 5 shows an example of how to use the break keyword.
Use the break keyword
Trap {
# Handle the error
Continue
}
Function myfunction {
trap {
# LOG error to a file
If ($c ondition) {
Continue
} Else {
break
}
}
get-wmiobject win32_service–comp ' Server2 ' –ea ' Stop "
get-process
}
myfunction
write-host" testing! "
The following is a brief overview of the execution chain. First executes line 19th, which calls the function in line 6th. Executes line 15th and generates an exception. The exception is caught on line 7th, and the Trap must make a decision on line 9th. Assume that $condition for True,trap will continue on line 16th.
However, interrupts will occur if $condition is false,trap. This exits the current scope and passes the original exception to the parent. From the shell point of view, this means that line 19th produces an exception and is captured by line 1th. The Continue keyword will force the shell to continue line 20th.
In fact, these two traps contain a little more code to handle errors, record them, and so on. In this case I just omitted the function code to make the actual process easier to see.
Why worry about it?
When do you need to catch errors? There are two scenarios in which a prediction can occur, and when you want something that goes beyond the normal error message (such as logging the error to a file or displaying a more helpful error message).
I usually add error handling to complex scripts to help deal with errors that I can foresee. These errors include, but are not limited to, bad connections or permissions issues.
Error capture will undoubtedly take more time and effort to understand. But when you're dealing with more complex tasks in Windows PowerShell, it's important to implement error trapping to help you build more sophisticated, professional tools.