To Err Is VBScript – Part 1

By The Microsoft Scripting Guys

Doctor Scripto at work

Doctor Scripto's Script Shop welds simple scripting examples together into more complex scripts to solve practical system administration scripting problems, often from the experiences of our readers. His contraptions aren't comprehensive or bullet-proof. But they do show how to build effective scripts from reusable code modules, handle errors and return codes, get input and output from different sources, run against multiple machines, and do other things you might want to do in your production scripts.

We hope find these columns and scripts useful – please let us know what you think of them. We'd also like to hear about other solutions to these problems that you've come up with and topics you'd like to see covered here in the future.

For an archive of previous columns, see the Doctor Scripto's Script Shop archive.

On This Page

To Err Is VBScript – Part 1
Handling Errors with VBScript
Handling Errors in a Subroutine
Passing Custom Error Messages to Subroutines
Other Ways of Testing for Successful Connection to an Object
Handling Method Return Codes
Interpreting Ping Status Codes
Postscript
Resources

To Err Is VBScript – Part 1

When we run scripts on real networks, sometimes stuff happens: occasionally catastrophic stuff, but more often just stuff we didn't expect to happen, which may be embarrassing for the script or its author.

Doctor Scripto spends most of his time in a parallel reality, but sometimes even his virtual world collides with the cantankerous reality of Windows bits, x86 silicon and Ethernet cables (if you call those reality). Occasionally even his scripts try to run against troubled machines that are wrestling with their inner daemons, or call scripting libraries that are out to a leisurely lunch, or query databases that are busy chatting on their cell phones.

We're not talking about simple syntax errors here, such as misspelling GetObject as GetOjbect. The VBScript engine generally catches these the first time we try to test the script. Oddly, it calls them "compiler errors," although VBScript is interpreted rather than compiled. But even after the script has run successfully on the machine where it was written, accidents out on the information highway may occur that prevent the script from performing the tasks it was designed to accomplish. We refer to these accidents as run-time errors, the different kinds of unexpected or undesired or just plain weird behavior that can happen when the rubber hits the road. Because we don't want our script to be road kill, we need to anticipate possible danger points and do something about them.

Because it's not practical (or necessary) to check for errors after every line of code, part of the art of scripting is determining where errors or unexpected behavior are most likely to occur and the best way to handle these contingencies. Typical areas to consider include places where a script:

  • Binds to a scripting API, such as WMI, ADSI or another COM library, particularly on remote machines that may not be online.

  • Performs input or output operations from or to a device or file.

  • Queries printers or other peripherals that may not be available.

  • Instantiates classes that may not exist on a machine.

  • Retrieves properties or calls methods that may not be available on that version of the operating system.

Even if the script finds the classes, methods and properties it's seeking, it's still not home free. Method calls can fail: when they do, most of them return values that can be used by a script to determine the problem and decide on the next step. Properties, too, can occasionally throw a monkey wrench into your code. For example, if the script is expecting a WMI DATETIME value and the property in question returns a null value, the script may grind gears unless you handle that possibility. (See the WMIDateStringToDate function at the end of any VBScript created by Scriptomatic 2.0.)

The opportunities for mistakes and nasty surprises are many and varied. But as Doctor Scripto points out, the Chinese character for crisis is also the character for "I didn't write that code." What? Bu shi? (No, this is not what you're thinking: it's actually Chinese for "Not so.") OK, so maybe Doctor Scripto needs to work on his calligraphy, but handling errors does present an opportunity to make your scripts more robust and road-worthy.

Before we plunge into the details, here's a public-service announcement: error-handling is not free. The more places we handle errors, the more code we have to write and debug and the more complex and vulnerable to other mistakes our scripts tend to become. Someone out there has probably come up with a sophisticated equation that can help decide the optimum amount of error-checking to do in a script, but we haven't found it yet. Common sense and experience with your particular network environment are the best guides we've come up with. When in doubt, err on the side of simplicity.

In this column, we're going to assume you're familiar with the basics of error handling in VBScript. If you're not, don't despair: you've come to the right Web site. In the eyes of their parents, several of the Scripting Guys were errors (and many of their co-workers agree). So the Script Center is a veritable cornucopia of background information on errors: see the Resources section at the end of this column.

Handling Errors with VBScript

This column being part of the "Doctor Scripto's Script Shop" series, we're going to go out on a limb and assume you've already read the Windows 2000 Scripting Guide sections on error handling, or at least have some experience catching errors.

Just to jog your memory, though, let’s do a quick review. VBScript error-handling requires two elements that work together. You can turn it on with the On Error Resume Next statement and turn it off with On Error GoTo 0. When it's turned on you can use the built-in Err object to get some information on what kind of error occurred.

Before you can check for an error, you have to include the statement On Error Resume Next. If you check the Err object without first turning on error handling with On Error Resume Next, VBScript assumes that Err.Number is 0; in other words, that no error has occurred. The script will then continue to do whatever comes next, assuming that all is well. If an error has in fact occurred, it may cause the script to fail with an unhandled run-time error that brings everything grinding to a halt.

Putting On Error Resume Next at the beginning of the script, as we often do, makes it apply to the entire body of the script. But, as we'll see in later examples, its scope does not include functions or subroutines. If you want to handle errors within a function or subroutine, you must also include On Error Resume Next in each of them before checking the Err object.

You can turn error-handling off with On Error GoTo 0. So it's possible to turn error-handling on with On Error Resume Next just before you want to check the Err object, and turn it off after with On Error GoTo 0. This makes more explicit exactly where errors are being handled, but to the jaded eyes of the Scripting Guys it seems like a lot of work for minimal returns in most cases.

On Error Resume Next can hide syntax errors, but you can avoid that problem by commenting out On Error Resume Next when debugging the script:

'On Error Resume Next

This ensures that syntax and other errors can be seen and handled. Just remember to remove the comment delimiter before using the script.

The VBScript Err object is a unique kind of object that you don't have to create or get: it is instantiated automatically by VBScript when the script runs.

Err has three properties that are generally useful:

  • Number (the default property) - integer

  • Source - string

  • Description - string

It also has two other properties that you can ignore unless the application in question has created custom errors and tied them to help topics:

  • HelpFile

  • HelpContext

Err also provides two methods:

  • Clear

  • Raise(lngNumber, strSource, strDescription)

Clear takes no parameters. It simply clears the values of all the properties of the previous error. It's very important to use Clear after each time you check Err. Otherwise, the information from the previous error will persist in the Err object and if you check again but no intervening error has occurred, the same error information will still be there and you may get a false positive on your error check. If you don't believe us, check out this free movie: Hey, Scripting Guy! The Movie. (Who knew errors could inspire great cinema?)

With the Raise method, VBScript offers a little-known capability: you can use this method to create a VBScript error in one part of the script if something untoward occurs that would not cause the script engine to raise an error. Only the error number, lngNumber, is required; the other parameters are optional. The error number variable is called lngNumber here because user-defined VBScript errors (as well as VBScript-defined ones) are in the range 0 to 65535 (decimal). We've never used this capability ourselves, but it could come in handy if you have a working scripting library or application that doesn't offer thorough error-handling mechanisms.

To put these concepts together and complete our review, let's peruse a couple of very simple error-handling scripts. The Err object’s Number property returns a decimal integer, but the WMI SDK generally uses hexadecimal values, so these scripts take a bilingual approach. The scripts use a built-in VBScript function, Hex, to convert the decimal number.

In Listing 1, make sure that strComputer is the name of a non-existent or inaccessible computer so the script will produce an error. We check for an error after trying to bind to WMI on the machine specified in strComputer. Notice that after displaying the error information, we call the Clear method of the Err object. In a script this short where there are no other error checks, this is not necessary, but Doctor Scripto, ever obsessive, puts Clear into all error-handling code in case it gets copied and pasted into another script.

Listing 1: Handle Basic VBScript Error – Example 1

On Error Resume Next
strComputer = "fictional"
Set objWMIService = GetObject("winmgmts:\\" & strComputer)
If Err.Number <> 0 Then
    WScript.Echo "Error: " & Err.Number
    WScript.Echo "Error (Hex): " & Hex(Err.Number)
    WScript.Echo "Source: " &  Err.Source
    WScript.Echo "Description: " &  Err.Description
    Err.Clear
End If

Typical output:

C:\scripts>eh-displayerror-basic1.vbs
Error: 462
Error (Hex): 1CE
Source: Microsoft VBScript runtime error
Description: The remote server machine does not exist or is unavailable

VBScript itself returns this error when it can't contact a remote machine. VBScript error numbers are all less than 10,000 decimal. Run-time errors are either less than 1,000 or between 5,000 and 5,100, while syntax errors are between 1,000 and 1,100. WMI and ADSI errors use larger numbers, generally 8-digit hex numbers.

For scripts designed to run against multiple machines on the network, it is particularly important to handle failures in making a remote connection. If a script is supposed to run against 100 machines, but errors out on the second, it gets no data back or makes no changes on the remaining 98. Because of this, any script that runs against more than one machine is likely to use some variant of this kind of error checking.

In Listing 2, assign the name of a non-existent printer to strPrinter so that the script will produce an error. Because printers may not be installed or may be unavailable for other reasons, code that works with them is also a good candidate for error checking.

Listing 2: Handle Basic VBScript Error – Example 2

On Error Resume Next
strPrinter = "TestPrinter"
Set objPrinter = GetObject _
  ("winmgmts:root\cimv2:Win32_Printer.Name='" & strPrinter & "'")
If Err.Number <> 0 Then
    Wscript.Echo "Error: " & Err.Number
    Wscript.Echo "Error (Hex): " & Hex(Err.Number)
    Wscript.Echo "Source: " &  Err.Source
    Wscript.Echo "Description: " &  Err.Description
    Err.Clear
End If

Typical output:

C:\scripts>eh-displayerror-basic2.vbs
Error: -2147217350
Error (Hex): 8004103A
Source:
Description:

Why doesn't this error return a source or description? Man, people like you just want to take all the mystery and romance out of scripting. Is it really that important to know? OK, OK, we'll throw a bit more light on this in the next example.

Handling Errors in a Subroutine

If a script checks for errors in more than one place, it may make the script easier to read and reduce script length to handle the potential errors in a subroutine or function.

Using a procedure for error-handing code is made easier by a convenient quality of the VBScript Err object: it automatically has global scope. Any error anywhere in the script — the main body, a subroutine or a function — can be accessed in any other part of the script, so you don't need to pass the Err object to the handler procedure. If you want to check for errors in another procedure, as we mentioned, you have to turn on On Error Resume Next for that procedure.

In this example, the script checks for errors in the same two places we saw in Listings 1 and 2: after attempting to bind to WMI on a remote computer, and after trying to get a specific instance of Win32_Printer. Note that here we check for Err = 0 rather than Err.Number = 0. We can do this because the default property of the Err object is Number, so VBScript allows us to use Err by itself as shorthand for Err.Number.

Listing 3: Subroutine - Handle Basic VBScript Errors

On Error Resume Next
strComputer = "fictional"
strPrinter = "TestPrinter"
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
If Err = 0 Then
    WScript.Echo "No error binding to " & strComputer
    strPrinter = "TestPrinter"
    Set objPrinter = objWMIService.Get _
      ("Win32_Printer.Name='" & strPrinter & "'")
    If Err = 0 Then
        WScript.Echo "No error connecting to " & strPrinter
    Else
        DisplayErrorInfo
    End If
Else
    DisplayErrorInfo
End If

'******************************************************************************

Sub DisplayErrorInfo

    WScript.Echo "Error:      : " & Err
    WScript.Echo "Error (hex) : &H" & Hex(Err)
    WScript.Echo "Source      : " & Err.Source
    WScript.Echo "Description : " & Err.Description
    Err.Clear

End Sub

Typical output:

C:\scripts>eh-sub-displayerror.vbs
Error:      : 462
Error (hex) : &H1CE
Source      : Microsoft VBScript runtime error
Description : The remote server machine does not exist or is unavailable

In VBScript, hexadecimal numbers are represented with a prefix of "&H". The script would recognize &H1CE as a hexadecimal value that could be used in calculations, so we’ve added that prefix to our error number.

If the machine specified in strComputer is not available, the script errors out after failing to find it. To see the printer connection error, change the value of strComputer to the name of an accessible computer on which you have administrative privileges, for example:

strComputer = "localhost"

The output should resemble this:

C:\scripts>eh-sub-displayerror.vbs
No error binding to localhost
Error:      : -2147217350
Error (hex) : &H8004103A
Source      : SWbemServicesEx
Description : Invalid object path

If you're one of those people who noticed that in Listing 2 the Source and Description properties were empty, but here for the same error they return values, you're probably scratching your head right about now. So are we. Just remember, scripting without mysteries would be insipid and boring. But since you ask, Doctor Scripto is going out on a limb and speculating that it may have something to do with how Listing 2 instantiated the particular Win32_Printer object directly without first creating a WMI service object:

Set objPrinter = GetObject _
  ("winmgmts:root\cimv2:Win32_Printer.Name='" & strPrinter & "'")

Listing 3 first binds to the WMI service, then tries to get the printer instance. Because SWbemServicesEx is an object included in the WMI Scripting API, it would appear that you need to bind to WMI directly before you can access its Scripting API. Scripting: eternally fascinating, eternally new.

Passing Custom Error Messages to Subroutines

So far we've looked at very simple examples of error handling. However, there may be times when the VBScript Err properties don’t give us enough information for effective troubleshooting. For example, what if we check for a WMI remote binding error or a printer connection error in more than one place in the script? Or what if the error source and description returned by VBScript aren’t very helpful in debugging? The more potential places errors can occur, the more we can profit from displaying our own custom error message to explain more fully where the problem occurred and what may have caused it. Furthermore, if the script runs against multiple machines, we can also use the custom message to indicate on which machine the error occurred.

Listing 4 adds to the previous examples by trying to retrieve the state of a service specified by strService. Because the name "Alerter" is misspelled, an error is generated. For each error check, the script creates a custom error message and assigns it to strMessage. The message is passed as a parameter to the DisplayCustomError subroutine, which displays this message along with the data from the Err object.

Listing 4: Subroutine – Handle Basic VBScript Errors with Custom Error Messages

On Error Resume Next
strComputer = "." 'Change to non-existent host to create binding error.
strService = "Alerte"
strPrinter = "FakePrinter"
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")

If Err = 0 Then
    WScript.Echo vbCrLf & "Bind success"
    WScript.Echo vbCrLf & "Computer: " & strComputer
Else
    strMessage = "ERROR: Unable to bind to WMI provider on " & strComputer & "."
    DisplayCustomError(strMessage)
    WScript.Quit
End If

Set objService = objWMIService.Get("Win32_Service.Name='" & strService & "'")

If Err = 0 Then
    WScript.Echo vbCrLf & "Service success"
    WScript.Echo "Service Name" & objService.Name
    WScript.Echo "Service State" & objService.State
Else
    strMessage = "ERROR: Unable to retrieve state of " & strService & " service."
    DisplayCustomError(strMessage)
End If

Set objPrinter = objWMIService.Get("Win32_Printer.Name='" & strPrinter & "'")

If Err = 0 Then
    WScript.Echo vbCrLf & "Printer success"
    WScript.Echo "Printer Name: " & objPrinter.Name
    WScript.Echo "Printer State: " & objPrinter.PrinterStatus
Else
    strMessage = "ERROR: Unable to retrieve state of " & strPrinter & " printer."
    DisplayCustomError(strMessage)
End If

'******************************************************************************

Sub DisplayCustomError(strMessage)
    'Display custom message and information from VBScript Err object.

    strError = VbCrLf & strMessage & VbCrLf & _
      "Number (dec) : " & Err.Number & VbCrLf & _
      "Number (hex) : &H" & Hex(Err.Number) & VbCrLf & _
      "Description  : " & Err.Description & VbCrLf & _
      "Source       : " & Err.Source
    Err.Clear
    WScript.Echo strError

End Sub

Typical output when the script can bind to WMI on the specified computer:

C:\scripts>eh-sub-displaycustomerror.vbs

Bind success

Computer: .

ERROR: Unable to retrieve state of Alerte service.
Number (dec) : -2147217406
Number (hex) : &H80041002
Description  : Not found
Source       : SWbemServicesEx

ERROR: Unable to retrieve state of FakePrinter printer.
Number (dec) : -2147217350
Number (hex) : &H8004103A
Description  : Invalid object path
Source       : SWbemServicesEx

The source of both errors is SWbemServicesEx. The two descriptions, "Not found" and "Invalid object path," by themselves might not be clear enough about what caused the error. The custom message displayed at the beginning of the data for each helps communicate exactly what the problem was.

Here’s the output if the computer is not found:

C:\scripts>eh-sub-displaycustomerror.vbs

ERROR: Unable to bind to WMI provider on sea-wks-5.
Number (dec) : 462
Number (hex) : &H1CE
Description  : The remote server machine does not exist or is unavailable
Source       : Microsoft VBScript runtime error

Other Ways of Testing for Successful Connection to an Object

In our peregrinations in search of new and better scripting techniques, the Scripting Guys try to leave no stone unturned. During the current journey, we've run across a couple of other ways to check for contingencies that don't rely on Err. We haven't used them a lot in our scripts, but we've seen them in the code of others, so Doctor Scripto wanted to mention them in the interest of thoroughness.

You can use two other capabilities of VBScript to test that outcome of an operation is as expected, and handle cases when it's not: IsObject and Is Nothing. These techniques work with object references, so you would want to use them after GetObject, CreateObject or any other call that returns an object. The only downside appears to be that in case of failure they don't return detailed error codes, as the Err object can. They simply verify whether the object reference was successfully created. So if you want details for troubleshooting in case of a run-time error, Err may be a better way to go.

Is Nothing

You can use the Is operator to compare an object with the Nothing keyword. If the object has not been instantiated, it Is Nothing. Is compares an object reference with another object reference or a keyword that can refer to an object to see if they are the same. Nothing is the equivalent of Null for an object reference.

One obvious place to use Is Nothing is after trying to bind to WMI, which is what Listing 5 does. If the objWMIService object reference Is Nothing, this means that the script was unable to bind to WMI on the given computer. In this case, the Set statement did not successfully assign an object reference to objWMIService, so objWMIService Is Nothing.

Listing 5: Test for WMI Binding with Is Nothing

On Error Resume Next
strComputer = "fictional"
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
If objWMIService Is Nothing Then
    WScript.Echo "Unable to bind to WMI on " & strComputer
Else
    WScript.Echo "Successfully bound to WMI on " & strComputer
End If

Typical output:

C:\scripts>eh-testobject-isnothing.vbs
Unable to bind to WMI on fictional

IsObject

Another technique similar to Is Nothing is the IsObject function, which is built into VBScript. IsObject also works with an object reference, verifying whether or not it is an object. In this case there's no comparison: IsObject is true if objPrinter refers to a valid object, and false if not.

Listing 6 uses IsObject rather than Err to check whether the given Win32_Printer instance was instantiated. If so, objPrinter is a valid object reference and IsObject(objPrinter) is True.

Listing 6: Test for WMI Binding with IsObject

On Error Resume Next
strPrinter = "TestPrinter"
Set objPrinter = GetObject _
 ("winmgmts:root\cimv2:Win32_Printer.Name='" & strPrinter & "'")
If IsObject(objPrinter) Then
    WScript.Echo "Connected to printer " & strPrinter
Else
    WScript.Echo "Unable to connect to printer " & strPrinter
End If

Typical output:

C:\scripts>eh-testobject-isobject.vbs
Unable to connect to printer TestPrinter

Handling Method Return Codes

With Is Nothing and IsObject, we've been skirting the border between checking for errors and simply checking the state of some indicator. Method return codes also fall somewhere on that semantic frontier. They are like error codes in that they can provide information if something goes wrong with a method call, but they are also merely the response of a method to a request to do something.

When you call most methods in WMI, the method returns a numeric code that indicates the outcome of the call. For all methods, 0 means success. The other return codes are positive integers, varying from one method to another, that indicate the particular kind of failure. The WMI SDK lists return code values and meanings for most methods of WMI classes.

The next script, Listing 7, terminates a process by using a process object passed as parameter.

This script also demonstrates another kind of check for a condition that is not an error, but that you may want the script to handle. After calling ExecQuery to request any instance of Win32_Process whose Name property is the value of strTargetProc, the script checks whether colProcesses.Count = 0. If this is true and the collection of processes has 0 members, this means that no processes with this name were found, so there's no point in trying to terminate them.

If on the other hand the Count value is greater than 0, then one or more target processes are running. The script then loops through the collection, calling the TerminateProcess function for each member of the collection. The script passes a single parameter to TerminateProcess: an object reference to the process instance.

The TerminateProcess function calls the Terminate method of Win32_Process on the object reference passed to it. It then checks the return code of the method with a Select Case decision-making structure. Select Case displays a message that depends on the value of intReturn. The explanation for each return code is taken from the topic on Win32_Process.Terminate in the WMI SDK.

TerminateProcess also returns the Terminate return value to the calling statement. In this case the script doesn't do anything with the return value of TerminateProcess, but it could branch and perform different operations depending on that value.

Listing 7: Terminate Process and Handle Return Code

On Error Resume Next
strComputer = "."
arrTargetProcs = Array("calc.exe","freecell.exe")
Set objWMIService = GetObject("winmgmts:" _
 & "{impersonationLevel=impersonate}!\\" & strComputer & "\root\cimv2")
For Each strTargetProc In arrTargetProcs
    Set colProcesses = objWMIService.ExecQuery _
      ("SELECT * FROM Win32_Process WHERE Name='" & strTargetProc & "'")
    If colProcesses.Count = 0 Then
        WScript.Echo VbCrLf & "No processes named " & strTargetProc & " found."
    Else
        For Each objProcess in colProcesses
            WScript.Echo VbCrLf & "Process Name: " & objProcess.Name
            Wscript.Echo "Process ID: " & objProcess.Handle
            Wscript.Echo "Attempting to terminate process ..."
            intTermProc = TerminateProcess(objProcess)
        Next
    End If
Next


'******************************************************************************

Function TerminateProcess(objProcess)

    On Error Resume Next
    intReturn = objProcess.Terminate
    Select Case intReturn
        Case 0 Wscript.Echo "Return code " & intReturn & " - Terminated"
        Case 2 Wscript.Echo "Return code " & intReturn & " - Access denied"
        Case 3 Wscript.Echo "Return code " & intReturn & " - Insufficient privilege"
        Case 8 Wscript.Echo "Return code " & intReturn & " - Unknown failure"
        Case 9 Wscript.Echo "Return code " & intReturn & " - Path not found"
        Case 21 Wscript.Echo "Return code " & intReturn & " - Invalid parameter"
        Case Else Wscript.Echo "Return code " & intReturn & _
          " - Unable to terminate for undetermined reason"
    End Select
    TerminateProcess = intReturn

End Function

Typical output looks like this:

C:\scripts>eh-sub-terminateprocess-returncode.vbs

Process Name: calc.exe
Process ID: 4076
Attempting to terminate process ...
Return code 0 - Terminated

Process Name: freecell.exe
Process ID: 4028
Attempting to terminate process ...
Return code 0 - Terminated

If no processes are found that match the target process names, the output looks like this:

C:\scripts>eh-sub-terminateprocess-returncode.vbs

No processes named calc.exe found.

No processes named freecell.exe found.

Yes, you could just call the Terminate method on the current process object if there is a match with a target process and interpret the return codes there. Or if you wanted to isolate the return code handling, you could put just that into a sub or function and call that after calling Terminate, passing it the return code for interpretation. However, putting the functionality that terminates the process into a function abstracts it, making it more portable and reusable.

Interpreting Ping Status Codes

In several listings so far, we tried to bind to WMI on a remote machine and used the success or failure of this operation to determine whether the machine was accessible on the network. Depending on the speed of the network and the machines involved, this may take a few seconds or more per machine. This amount of time is reasonable for checking a moderate number of machines when a script is running as a scheduled job and time is not of the essence. But if you run the script against 1,000 machines and you're waiting on the results, this approach may be painfully slow.

An alternative way to check for connectivity is to ping each machine before trying to bind to WMI on it. This can cut down the time to test each machine to a second or two and significantly speed up execution against a large OU, subnet, or other set of machines.

In previous columns and webcasts, we've shown how to ping a remote computer by running Ping.exe with the Exec method of WshShell. The techniques for doing this are explained in some detail in "Automating TCP/IP Networking on Clients - Part 3: Scripting Remote Network Management."

With the Win32_PingStatus class, WMI provides a way to use ping to check remote connectivity that does not depend on a command-line tool (also explained in Part 3 of Automating TCP/IP Networking on Clients). This class was recently added to WMI, so the host running the script must be running Windows XP or Windows Server 2003. The target host can be running any version of any operating system that can respond to Internet Control Message Protocol (ICMP), the protocol used by ping. The default timeout is 1000 milliseconds, considerably faster than the WMI binding approach in most cases.

Win32_PingStatus has a unique way of calling the equivalent of a method: the ping runs when you call ExecQuery with a WQL query, filtering with WHERE for an Address property whose value is a particular machine, for example:

SELECT * FROM Win32_PingStatus WHERE Address = 'sea-wks-4'

After the query executes, the instance returns information on the ping attempt in a property called StatusCode. This is not exactly a return code, but it's analogous and provides another way of handling contingencies in our scripts. This class is probably the only one in WMI that works this way.

The script in Listing 8 pings a remote machine with Win32_PingStatus and reports whether the ping was successful. If not, it interprets the ping status code returned in the StatusCode property with a Select Case structure to explain why the ping failed. The explanation for each status code is taken from the Win32_PingStatus topic in the WMI SDK

Listing 8: Ping Remote Machine and Display Ping Status

On Error Resume Next
strComputer = "sea-wks-1"
strPingStatus = PingStatus(strComputer)
If strPingStatus = "Success" Then
    Wscript.Echo "Success pinging " & strComputer
Else
    Wscript.Echo "Failure pinging " & strComputer & ": " & strPingStatus
End If

'******************************************************************************

Function PingStatus(strComputer)

    On Error Resume Next
    strWorkstation = "."
    Set objWMIService = GetObject("winmgmts:" _
      & "{impersonationLevel=impersonate}!\\" & strWorkstation & "\root\cimv2")
    Set colPings = objWMIService.ExecQuery _
      ("SELECT * FROM Win32_PingStatus WHERE Address = '" & strComputer & "'")
    For Each objPing in colPings
        Select Case objPing.StatusCode
            Case 0 PingStatus = "Success"
            Case 11001 PingStatus = "Status code 11001 - Buffer Too Small"
            Case 11002 PingStatus = "Status code 11002 - Destination Net Unreachable"
            Case 11003 PingStatus = "Status code 11003 - Destination Host Unreachable"
            Case 11004 PingStatus = _
              "Status code 11004 - Destination Protocol Unreachable"
            Case 11005 PingStatus = "Status code 11005 - Destination Port Unreachable"
            Case 11006 PingStatus = "Status code 11006 - No Resources"
            Case 11007 PingStatus = "Status code 11007 - Bad Option"
            Case 11008 PingStatus = "Status code 11008 - Hardware Error"
            Case 11009 PingStatus = "Status code 11009 - Packet Too Big"
            Case 11010 PingStatus = "Status code 11010 - Request Timed Out"
            Case 11011 PingStatus = "Status code 11011 - Bad Request"
            Case 11012 PingStatus = "Status code 11012 - Bad Route"
            Case 11013 PingStatus = "Status code 11013 - TimeToLive Expired Transit"
            Case 11014 PingStatus = _
              "Status code 11014 - TimeToLive Expired Reassembly"
            Case 11015 PingStatus = "Status code 11015 - Parameter Problem"
            Case 11016 PingStatus = "Status code 11016 - Source Quench"
            Case 11017 PingStatus = "Status code 11017 - Option Too Big"
            Case 11018 PingStatus = "Status code 11018 - Bad Destination"
            Case 11032 PingStatus = "Status code 11032 - Negotiating IPSEC"
            Case 11050 PingStatus = "Status code 11050 - General Failure"
            Case Else PingStatus = "Status code " & objPing.StatusCode & _
               " - Unable to determine cause of failure."
        End Select
    Next

End Function

If the ping succeeds, the output looks like this:

C:\scripts>eh-sub-displaypingstatuscode.vbs
Success pinging sea-wks-1

If the ping fails, the output could look like this:

C:\scripts>eh-sub-displaypingstatuscode.vbs
Failure pinging sea-wks-1: Status code  - Unable to determine cause of failure.

Postscript

In this first part of the Scripting Guys reign of error, we've looked at several different ways to parry the slings and arrows that outrageous fortune can aim at our scripts. But we have yet to talk about two other important areas of error-handling functionality: the WMI Scripting API's SWbemLastError object and ADSI's error codes. So don't touch that dial: stay tuned for Part 2 of "To Err Is VBScript."

Resources

Windows 2000 Scripting Guide - VBScript Overview – Error Handing
- VBScript Reference – Error Handing

VBScript documentation
- VBScript Errors
- On Error Statement
- Err Object

Tales from the Script: Bugs check in but they don’t check out

Hey, Scripting Guy! The Movie