Hey, Scripting Guy! QuestionHey Scripting Guy! I have a script that requests user input for a computer name before connecting to a remote computer to perform a WMI query. The problem is that when our help desk people use the script, they invariably type the wrong name of the remote computer. When this happens the script seems to take a really long time to time out. Is there a way to add error handling to the script to prevent the scenario I described?


-- OK

Hey, Scripting Guy! AnswerHello OK,

One problem with living in the Southern portion of the United States during July is that the heat and the humidity tend to become oppressive. I am talking about 80 percent humidity and 92 degrees Fahrenheit (33 degrees Celsius). I had not really noticed this before now, because for the last five years I traveled all over the world teaching scripting classes to Microsoft Premier customers, and I was never home during the summer. In fact, this year was the first time I had been in the United States during the Independence Day celebrations. Last year I was in Lisbon, Portugal, on July 4. Lisbon is a wonderful city with awesome food and lots of opportunities for photographers. I particularly love going down to the water front near the Monument to the Discoverers. As you can see in the following picture, which I snapped as the sun was setting low in the water, it is an awe inspiring place.

Image of Monument to the Discoverers in Lisbon, Portugal

OK, you may be asking, "Dude, what does this have to do with error handling?" Well, to be honest, not much. But I am getting ready to go on vacation for a week, and because I do not want to wimp out and post reruns while I am gone, I am working ahead so that the articles will be ready while I am in New York going to Broadway. To make matters worse, Craig is going on vacation the week I return. In reality, this week I have to write three weeks’ worth of Hey, Scripting Guy! articles. I am therefore burning the midnight oil, sipping a cup of Gunpowder green tea with a Ceylon cinnamon stick in it, and munching on hand-crafted artesan cheese. The cinnamon stick in the tea is a trick I picked up in Lisbon. Because I was in Lisbon last July, and I am drinking tea with cinnamon in it, my mind naturally wandered. Sorry about that. (My mind often wanders when I have a lot of work to do. Go figure.)

Depending on the design of the script, there are several things that can be done to ease the error checking that might be required. For example, if you use the PromptForChoice method of soliciting input from the user, your user has a limited number of options from which to choose. You completely eliminate the problem of bad input. The user prompt from the PromptForChoice method is seen here:

Image of user prompt from PromptForChoice method

The use of the PromptForChoice method is illustrated in the Get-ChoiceFunction.ps1 script. In the Get-Choice function, the $caption variable and the $message variable holds the caption and the message that is used by the PromptForChoice method. The choices that are offered are an instance of the ChoiceDescription .NET Framework class. When you create the ChoiceDescription class, you also supply an array with the choices that will be appear. This is shown here:

$choices = [System.Management.Automation.Host.ChoiceDescription[]] `
 @("&loopback", "local&host", "&127.0.0.1")

You next need to select a number that will be used to represent which choice will be the default choice. When you begin counting, keep in mind that the ChoiceDescription class is an array, and the first option is numbered 0. Next you call the PromptForChoice method and display the options. This is seen here:

[int]$defaultChoice = 0
$choiceRTN = $host.ui.PromptForChoice($caption,$message, $choices,$defaultChoice)

Because the PromptForChoice method returns an integer, you could use the if statement to evaluate the value of the $choiceRTN variable. The syntax of the switch statement is more compact, and is actually a better choice for this application. The switch statement from the Get-Choice function is seen here:

switch($choiceRTN)
 {
  0    { "loopback"  }
  1    { "localhost"  }
  2    { "127.0.0.1"  }
 }

When you call the Get-Choice function, it returns the computer that was identified by the PromptForChoice method. You place the method call in a set of parentheses to force it to be evaluated before the rest of the command. This is seen here:

Get-WmiObject -class win32_bios -computername (Get-Choice)

This solution to the problem of bad input works well when you have help desk personnel who will be working with a limited number of computers. The other caveat to this approach is that you do not want to have to change the choices on a regular basis, so you would want a stable list of computers to avoid creating a maintenance nightmare for yourself.

Get-ChoiceFunction.ps1

Function Get-Choice
{
 $caption = "Please select the computer to query"
 $message = "Select computer to query"
 $choices = [System.Management.Automation.Host.ChoiceDescription[]] `
 @("&loopback", "local&host", "&127.0.0.1")
 [int]$defaultChoice = 0
 $choiceRTN = $host.ui.PromptForChoice($caption,$message, $choices,$defaultChoice)

 switch($choiceRTN)
 {
  0    { "loopback"  }
  1    { "localhost"  }
  2    { "127.0.0.1"  }
 }
} #end Get-Choice function

Get-WmiObject -class win32_bios -computername (Get-Choice)

If you have more than a few computers that need to be accessible, or if you do not have a stable list of computers that you will be working with, one solution to the problem of trying to connect to nonexistent computers is to ping the computer before attempting to make the WMI connection.

You can use the Win32_PingStatus WMI class to send a ping to a computer. The best way to use the Win32_PingStatus WMI class is to create a function that pings the target computer. Because you are interested in a quick reply, the Test-ComputerPath function sends one ping only (sort of like Sean Connery).  The Test-ComputerPath function accepts a single input, which is the name or IP address of the target computer. To help control the information that is passed to the function, the $computer parameter uses a [string] type constraint to ensure that the input to the function is a string. The Test-ComputerPath function is seen here:

Function Test-ComputerPath([string]$computer)
{
 Get-WmiObject -class win32_pingstatus -filter "address = '$computer'"
} #end Test-ComputerPath


The entire Win32_PingStatus object is returned to the calling code. The Win32_PingStatus object is seen here:


__GENUS                        : 2
__CLASS                        : Win32_PingStatus
__SUPERCLASS                   :
__DYNASTY                      : Win32_PingStatus
__RELPATH                      : Win32_PingStatus.Address="localhost",BufferSiz
                                 e=32,NoFragmentation=FALSE,RecordRoute=0,Resol
                                 veAddressNames=FALSE,SourceRoute="",SourceRout
                                 eType=0,Timeout=1000,TimestampRoute=0,TimeToLi
                                 ve=128,TypeofService=128
__PROPERTY_COUNT               : 24
__DERIVATION                   : {}
__SERVER                       : OFFICE
__NAMESPACE                    : root\cimv2
__PATH                         : \\OFFICE\root\cimv2:Win32_PingStatus.Address="
                                 localhost",BufferSize=32,NoFragmentation=FALSE
                                 ,RecordRoute=0,ResolveAddressNames=FALSE,Sourc
                                 eRoute="",SourceRouteType=0,Timeout=1000,Times
                                 tampRoute=0,TimeToLive=128,TypeofService=128
Address                        : localhost
BufferSize                     : 32
NoFragmentation                : False
PrimaryAddressResolutionStatus : 0
ProtocolAddress                : 127.0.0.1
ProtocolAddressResolved        :
RecordRoute                    : 0
ReplyInconsistency             : False
ReplySize                      : 32
ResolveAddressNames            : False
ResponseTime                   : 0
ResponseTimeToLive             : 128
RouteRecord                    :
RouteRecordResolved            :
SourceRoute                    :
SourceRouteType                : 0
StatusCode                     : 0
Timeout                        : 1000
TimeStampRecord                :
TimeStampRecordAddress         :
TimeStampRecordAddressResolved :
TimestampRoute                 : 0
TimeToLive                     : 128
TypeofService                  : 128

In the Test-ComputerPath.ps1 script, the statuscode property from the Win32_PingStatus object is evaluated. If the value is 0, the ping was successful. If the statuscode property is null or is equal to some other number, the ping was not successful. Because the Win32_PingStatus object is returned to the calling script, you can retrieve the statuscode property directly and use the equality operator to see if it is equal to zero. This is seen here:

if( (Test-ComputerPath -computer $computer).statusCode -eq 0 )

If the statuscode property is equal to zero, the Test-ComputerPath.ps1 script uses the Get-WmiObject cmdlet to retrieve the BIOS information from the Win32_Bios WMI class. This is seen here:

Get-WmiObject -class Win32_Bios -computer $computer

If the target computer is unable to be reached, the Test-ComputerPath.ps1 script displays a message to the Windows PowerShell console that states the target computer is unreachable. This is shown here:

Else
 {
  "Unable to reach $computer computer"
 }

The complete Test-ComputerPath.ps1 script is shown here.

Test-ComputerPath.ps1

Param([string]$computer = "localhost")

Function Test-ComputerPath([string]$computer)
{
 Get-WmiObject -class win32_pingstatus -filter "address = '$computer'"
} #end Test-ComputerPath

# *** Entry Point to Script ***

if( (Test-ComputerPath -computer $computer).statusCode -eq 0 )
 {
  Get-WmiObject -class Win32_Bios -computer $computer
 }
Else
 {
  "Unable to reach $computer computer"
 }

Well, OK, we hope you have picked up a new trick or two to aid you in working with command-line input. As we have seen, if you control the input to the script you simplify the potential error-handling scenario. Join us tomorrow as we continue examining error handling in Windows PowerShell scripts. If you would to be among the first to be informed about everything that is happening on the Script Center, you can follow us on Twitter. We also make postings on the Scripting Guys group on Facebook. If you get stuck while you are working on a script, you can post to the Official Scripting Guys Forum. We have an excellent group of moderators and other active members who are always willing to help other scripters. See you tomorrow. Take care.

Ed Wilson and Craig Liebendorfer, Scripting Guys