January 2004

SG090301

By The Scripting Guys

For a list and additional information on all Tales from the Script columns, click here.

On This Page

What’s Wrong with My Script?
The Fine Art of Script Debugging
Duct Tape is No Answer
Step 1: Get Rid of On Error Resume Next
Step 2: Debug the Individual Parts of the Script
Step 3: Implement a Trace Routine
Step 4: Make Sure the Simple Stuff Works First
And in Conclusion …

What’s Wrong with My Script?

We hope you all are sitting down while reading this, because we know that what we are about to tell you will come as a real shock: the Scripting Guys aren’t perfect. (There, there, go ahead and cry; you’ll feel better once the tears have dried.)

We know you don’t believe us, but it’s true: we’re not perfect. (Well, one of us is awfully close, but other than that ….) The truth is, we make mistakes all the time; it’s unlikely that any Scripting Guy has ever written a script that, on the first draft, wasn’t riddled with errors. You know how they say the two things you should never watch being made are hot dogs and laws? Add scripting to the list; it’s not always a pretty sight.

So if we’re such lousy scripters, how did we get to be the legendary Scripting Guys? Well, remember, in scripting the only thing that counts is the end result; nobody care whether the first version of your script worked as long as the last version of your script works. And that’s perhaps the only advantage we Scripting Guys have over some of you: we aren’t necessarily better at writing scripts, but we might be a little bit better and troubleshooting and debugging scripts.

How do we know that? Well, we get scores of emails each month like this:

Dear Scripting Guys. I tried to run that one script from the Script ** Center and it didn’t work. What’s wrong with it?

Now, we don’t mind getting emails like this; after all, sometimes the reason the script didn’t work is because we screwed up and the version in the Script Center doesn’t work. Most of the time, however, we are at a loss when we get mail like this. Why didn’t your script work? To be honest, we don’t know; sometimes we don’t even know what script you’re talking about. We don’t know what operating system you’re using, we don’t know what changes you’ve made to the script, we don’t know if you have administrative rights to the computer the script is running against, etc. Without knowing the answers to questions like those, it’s difficult for us to figure out what the problem is. (On top of that, we get so many emails like this that’s it’s just plain impossible for us to try and answer them all.)

What we do know, however, is that if a script fails many people tend to give up right then and there. We find that a bit odd, because people typically don’t do that with other things. Suppose you turn the ignition key and your car doesn’t immediately start. Do you just get out and abandon the car there forever? Of course not; instead, you try it again, and then you check the battery and check to see if there’s gas in the car and try to determine what could be wrong, even if you aren’t a master mechanic. The same thing should be true with scripting. You might not consider yourself a master script writer, but you might be surprised how many problems you can solve on your own.

And because that might seem like a daunting task, we’re here to help. After all, we’ve made so many mistakes that by now we know all about the fine art of script debugging, and thought we’d pass a few of those insights on to you. What’s wrong with your script? Beats us. But let’s see if we can figure it out.

Note.  What’s the difference between saying “we’d like to pass a few of those insights on to you” and saying, “Hey, you’re on your own, pal.” Well, not much. But our way just sounds so much nicer!

The Fine Art of Script Debugging

Let’s start with a fairly typical script, one you might very well write yourself. This script tries to stop the IMAPI CD-Burning COM Service, and then echoes a message telling you whether the service actually was stopped:

Disclaimer.  Ok, in some respects this isn’t a typical script; for example, although we have an If-Then statement that checks to see if the service was stopped, we put that statement outside the For Each loop. A much better practice would have been to include that statement inside the loop. For educational purposes, however, we wanted to break this script into two distinct sections, which is why If-Then sits out there by itself.

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where Name = " & _
        "'IMAPI CD-Burning COM Service'")
For Each objItem in colItems
    errReturn = objItem.StopService
Next
If errReturn = 0 Then
    Wscript.Echoe "The service was stopped."
Else
    Wscript.Echo "The service could not be stopped."
End If

What happens when you run this script? Well, to tell you the truth, nothing. The service doesn’t get stopped, and we don’t see a message box of any kind. In other words, our script has a bit of a problem: it doesn’t actually do anything. So what can we do about this? What’s the first step in the debugging process?

No, the first step is not to write to the Scripting Guys and ask them what’s wrong. Remember, we’re trying to break you of that habit. Let’s try a different approach instead.

Tip.  Here’s a quick tip for you: most WMI methods return a value that tells you not only whether the operation succeeded but, if it didn’t, why it didn’t. In the preceding script, we assign the return value to the variable errReturn. In general, a return value of 0 means the operation succeeded; if we get any other value, we can refer to the WMI SDK and find out why the operation failed. Any time you use a method, you should grab and report the return value.

Duct Tape is No Answer

There’s an episode of the Simpsons in which the family is driving along when an engine warning light begins flashing on the dashboard. “I’ll take care of that,” says Homer, who promptly grabs a piece of duct tape and tapes over the light so no one can see it. Problem solved.

Scripters often take the same approach to scripts that aren’t working correctly. Suppose you’re writing a script that keeps crashing halfway through. How can you get the script to stop crashing? Well, here’s one surefire way; just make this the first line in the script:

On Error Resume Next

Run the script again, and the odds are it will complete without blowing up and without displaying an error message. Problem solved. On Error Resume Next: the duct tape of the scripting world.

Of course, the only problem here is that On Error Resume Next doesn’t actually fix the problem, it merely removes it from view. The problem that is plaguing your script is still there; it’s just that you no longer know about it.

In other words, On Error Resume Next is no miracle cure for scripting woes. Instead, it works like this: when you start a script (and assuming there are no syntax errors that would cause the script to crash before it ever gets going), Windows Script Host tries to run the first line of code. If the line runs successfully, WSH then tries to run the second line of code. This process continues until each line of code has been executed. At that point, the script automatically terminates.

But what if WSH encounters a line of code that it can’t run successfully? Without On Error Resume Next, the script would come to a crashing halt, the script process would end, and an error message would be displayed. If you’ve included On Error Resume Next, however, the script typically won’t blow up; instead, WSH will simply skip the line it can’t run and try running the next line. Hence the name On Error Resume Next: when an error occurs, resume the script on the next line. Suppose you had a 100-line script and each line had been coded incorrectly. Theoretically, you could start that script and WSH would dutifully try to run each line. Each time it failed, WSH would simply skip to the next line. After all 100 lines had been tried, the script would gracefully end, just as though everything had worked perfectly. In truth, however, none of your 100 lines would have been executed.

There’s definitely value in having a script that can recover from errors, especially when that script is being used by other people. However, when you’re first writing a script you really don’t want to suppress errors; instead, you want to be notified any time your script encounters a problem. In script writing, no news is not necessarily good news.

For example, consider this simple four-line script. Notice the mistake we made in line 3: instead of typing B = 2, we typed B 2:

On Error Resume Next
A = 2
B 2
Wscript.Echo A + B

Now, B 2 is not a valid statement, and when WSH encounters this line an error occurs. However, On Error Resume Next suppresses this error. WSH then attempts to run the next line of code, something which, in this case, leads to problems. Here’s what happens when this script runs:

  1. Line 1 turns on On Error Resume Next.

  2. 2 sets the value of the variable A to 2. So far so good.

  3. Line 3 is supposed to set the value of variable B to 2. However, WSH is unable to interpret this line, and so it just skips it. Because of that, B is not assigned the value 2. And because no value has been assigned to B, it automatically assumes the value of 0.

  4. In line 4, we echo the value of A + B. This ought to be 4 (because 2 + 2 = 4). But as a result of the mistake we made, B is equal to 0 instead of 2. In turn, we get the answer 2 (because 2 + 0 = 2). Thanks to the magic of scripting, we’ve managed to turn our fancy new computer into a machine that can’t even add 2 and 2!

Admittedly, this is a pretty simplistic example, and – hopefully – you are aware that 2 + 2 doesn’t really equal 2. (Trust us, it doesn’t.) Because you know that this answer is wrong and because this script is so short, you probably:

  • Know that the script needs debugging

  • Can find the problem without too much trouble.

In this case, then, debugging the script is easy. But suppose this script was several hundred lines long, and suppose this was a more complicated mathematical equation. In that case, you might not know that the answer was wrong; even if you did, you might have trouble tracking down the problem.

Tip.  In general, it’s not a good idea to assume that you can simply read through the code and find where you made a mistake. Not only are mistakes often very subtle, but if you wrote the script you’re the worst person in the world to proofread the code. Why? Because you know what it’s supposed to say; subconsciously, then, you are prone to overlook errors that would stand out to someone else it. That means that anytime you get really stuck, one of the best things you can do is have someone else look at the script.

But no, not the Scripting Guys! (Actually, we’re always willing to look at scripts. We just can’t promise that we’ll actually be able to do so.)

So what happens if we get rid of On Error Resume Next, and instead run this script:

A = 2
B 2
Wscript.Echo A + B

In that case, we get an error message like this:

Microsoft VBScript runtime error Line: 3 Char: 1

This time there’s no question about whether the script worked or not, and we don’t have to puzzle over whether 2 + 2 actually does equal 2. We know for sure that we have problems.

The moral of the story is this: whenever you are working on a script and whenever you suspect something has gone wrong, your first step should always be to remove On Error Resume Next. (A better moral might be this: On Error Resume Next should typically be the last thing you add to a script, not the first thing.) Now, no doubt you’re thinking, “But Scripting Guys, if there’s a problem with the code, won’t that cause the script to fail?” You bet it will, and that’s exactly what we want it to do. (Of course, this is the standard Scripting Guy response to pretty much everything we’ve ever done: “Yes, we wanted it to fail.”)

Note.  There are, of course, exceptions to the rule about not using On Error Resume Next when you are working on a script. There will often be times when you need to check to see if something is true or not. If this thing (whatever it is) is not true, that might trigger an error. In a case like that, you need On Error Resume Next to prevent the script from blowing up. Of course, you can selectively turn error-handling on and off. For more information about that, click here.

Step 1: Get Rid of On Error Resume Next

Let’s start the debugging process by commenting out On Error Resume Next, giving us a script that looks like this:

'On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where Name = " & _
        "'IMAPI CD-Burning COM Service'")
For Each objItem in colItems
    errReturn = objItem.StopService
Next
If errReturn = 0 Then
    Wscript.Echoe "The service was stopped."
Else
    Wscript.Echo "The service could not be stopped."
End If

Note.  In case you’re a newcomer to scripting, commenting out a line simply means making a single quote (‘) the first character in the line. That will cause WSH to bypass that line without attempting to execute it. Couldn’t we just remove the line altogether? Sure. But commenting it out makes it very easy to put the line back in later on; all we have to do is remove the single quote.

When we try to run this script, we get this error message:

Microsoft VBScript runtime error Line: 11 Char: 5

There are two things we need to do here. First, read the error message. Yes, error messages are occasionally cryptic and sometimes misleading, but at other times they can tell you exactly what the problem is. In this case, the message is fairly straightforward: “Object doesn’t support this property or method: ‘Wscript.Echoe’.” In other words, Wscript.Echoe is not a valid command. That means either our version of WSH doesn’t support Echoe or – as is obviously the case here – we made a mistake, typing Echoe instead of Echo. People often write to us saying things like, “I tried to run this script and it wouldn’t work.” We write back and say, “Well, OK, what error message did you get?” They send us the error message and, like magic, we tell them exactly what the problem is. Here’s a little secret (promise not to tell anyone else): can we do that because we’re some sort of scripting geniuses? What, are you kidding? We can do this simply because, more often than not, the error message will tell you what’s wrong.

Note.  What if Echoe was, as far as you knew, a valid command? How could you verify that, and how could you determine whether it was supported on your version of Windows or your version of WSH? Probably the easiest way is to check the official documentation for the technology and see if Echoe (or whatever) is a real, live command. You can find links to the documentation for WSH, ADSI, WMI, and other scripting technologies here.

Second, pay attention to the line number reported in the error message. Although the script engine can sometimes be fooled, this will usually tell you exactly where the error occurred. According to the message above, the script blew up on line 11. So what do we do with that information? Well, open the script in Notepad, go to line 11 (make sure you have Word Wrap turned off), and you’ll see this:

    Wscript.Echoe "The service was stopped."

Holy smokes, it’s just like the error message predicted! (Kind of spooky, isn’t it?) Let’s fix the typo, changing Echoe to Echo:

'On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where Name = " & _
        "'IMAPI CD-Burning COM Service'")
For Each objItem in colItems
    errReturn = objItem.StopService
Next
If errReturn = 0 Then
    Wscript.Echo "The service was stopped."
Else
    Wscript.Echo "The service could not be stopped."
End If

Incidentally, if you can’t figure out the problem based on the error message, one thing you can do is look up the error code (800A01B6) at the Microsoft Support site on Microsoft.com. This will typically connect you to a more detailed explanation of the error; it might also connect you to knowledge base articles that tell you how to solve the problem.

You can also check the VBScript documentation to see if the error message is described there. That sounds pretty straightforward and it is, except for one thing: while errors are typically returned as hexadecimal numbers (like 800A01B6), the VBScript documentation lists regular old decimal numbers like 13 and 140. What gives?

Well, to tell you the truth, we don’t know. However, we do know how to turn a hexadecimal error code into a VBScript error number. Here’s a very easy way:

  1. Start Calculator, and switch it to Scientific view.

  2. Click Hex.

  3. Type the last four digits of the error code (in this case, 01B6) and then click Dec (this will show you the decimal value of the four hexadecimal digits you typed in).

Your screen will look like this:

Calculator

Now look up error code 438 in the VBScript documentation. When you do, you’ll find this info:

Object doesn't support this property or method

You specified a property or method that does not exist for this automation object. Not all objects support all properties and methods.

To correct this error

  • Check the properties and methods to ensure there were no typing errors.

  • See the object's documentation for more information.

Sure, it’s a bit of a hassle, but more often then not it will provide you with the answer to your question.

Now that we’ve corrected the typo, what happens when we re-run our script? Well, we no longer get an error message; instead we get a message box that says the service was stopped. But don’t congratulate yourself just yet: if we open the Services console and check the status of the IMAPI CD-Burning COM Service, we’ll see that – alas – the service is still running. Not only did the script fail to stop the service, but to make matters worse, it lied to us and said that it did! What kind of a world is this when computers start lying about whether they stopped a service or not?

Tip.  Any time you are developing a script, it’s a good idea to verify that the script is doing what it is supposed to do. The lack of an error message doesn’t mean the script did what it was supposed to; it simply means that WSH didn’t encounter any errors when running the script. (And maybe the reason it didn’t encounter any errors was because the script didn’t do anything.) To resurrect an old phrase, trust but verify.

It’s an even better idea to do this verification using a tool other than a script. In our example, we tried to stop the service using a script and then verified the results using the Services console. Couldn’t we have just written a script that checked the status of this service? Sure, but if we’re not positive that our script is able to stop a service, what makes us think that our script can correctly return the status of that service? Whenever possible, verify the results of a script using something other than a script; for example, if you’re working on a script to create a user account, use Active Directory Users and Computers to make sure that account got created.

Step 2: Debug the Individual Parts of the Script

Ok, things still aren’t going the way we had hoped, but instead of giving up let’s analyze the situation. Our script is supposed to do two things: it’s supposed to stop the CD-Burning service, and it’s supposed to tell us whether it was actually able to stop that service. As near as we can tell, it’s not doing either one. And yet, we don’t get an error message. That can only mean that either the script has some sort of curse on it and will never work, or – gasp! – we made a mistake somewhere.

For now, let’s discount the idea that the script has been cursed (though we might return to that as we get closer and closer to our deadline for having this script up and running). Our script is supposed to do two things, and it can’t do either one. Let’s see if we can at least get one of these functions to work.

Tip.  We can’t overemphasize the importance of taking an incremental approach to problem-solving. Suppose you come in one morning and your computer isn’t working. Most likely you don’t grab a screwdriver and immediately take the entire thing apart. Instead, you probably take a more methodical approach, eliminating potential causes one-by-one. Is the computer on? Is the surge protector on? Is the monitor plugged in? Does the monitor even work? We want to do the same thing when debugging a script.

Our script is supposed to try and stop the CD-Burning service, with the results of that effort captured by the variable errReturn. If errReturn is 0, then the attempt succeeded; if it’s anything but 0, it failed. And yet, even though the attempt fails, the script acts as though it has succeeded. Why?

At this point, we have no idea. But remember, one thing we can do is cheat. There are two possibilities here: either errReturn is being improperly set to 0 (and thus the script is tricked into thinking the service was stopped), or else our code for echoing the success or failure is messed up. This is where we can cheat. Instead of relying on errReturn to be set to a given value, let’s hard code a value into the script and see if the script echoes the correct message. To do that, comment out all the WMI code (we’re not worried about that portion of the script at the moment) and hardcode a value (say, 7) for errReturn into the script. Our modified script now looks like this:

'On Error Resume Next
'strComputer = "."
'Set objWMIService = GetObject _
'    ("winmgmts:\\" & strComputer & "\root\cimv2")
'Set colItems = objWMIService.Execquery _
'    ("Select * From Win32_Service Where Name = " & _
'        "'IMAPI CD-Burning COM Service'")
'For Each objItem in colItems
'    errReturn = objItem.StopService
'Next
errReturn = 7 
If errReturn = 0 Then
    Wscript.Echo "The service was stopped."
Else
    Wscript.Echo "The service could not be stopped."
End If

So what’s going to happen here? Well, when we run the script, WSH will skip all the lines that have been commented out. It will set the value of errReturn to 7, and then hit the If-Then statement. Because errReturn does not equal 0, it should echo a message stating that the script could not be stopped. And here’s what we get when we run the script:

The service could not be stopped

Hey, it worked! Now, change the hardcoded value of errReturn to 0, and try it again. Then change it to 345, then to 16, then back to 0. The script should respond appropriately each time.

This might not seem like a big deal to you, but it actually is. After all, we’ve shown that the second half of the script, the part that echoes the success or failure of trying to stop the CD-Burning service, works just fine, as long as errReturn is correctly set. If we’re getting the wrong message, that must mean that errReturn is not being set correctly.

Step 3: Implement a Trace Routine

So now what do we do? Well, we expected our script to stop the CD-Burning service; it doesn’t. However, the line of code that stops the service looks OK:

errReturn = objItem.StopService 

So what else could be wrong?

Well, maybe that line of code is never being executed. This is one of the most common problems with WMI and ADSI scripts that loop through a collection of items: the script never actually executes the code within the loop. To see if that’s the case, let’s include a little trace routine to let us know that we successfully entered the loop.

Note.  Trace routines sound pretty fancy, but don’t let the name scare you: typically all you do with a trace routine is echo something to the screen. In this case, we’re echoing the fact that we entered a For Each loop. You might also echo the value of a specific variable from time-to-time; if you think the value of x should be 7 and yet halfway through the script we find out that it’s 0, then we know we have a problem with assigning the correct value to x.

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where Name = " & _
        "'IMAPI CD-Burning COM Service'")
For Each objItem in colItems
    Wscript.Echo "We have entered the For loop."
    errReturn = objItem.StopService
Next
'If errReturn = 0 Then
'    Wscript.Echo "The service was stopped."
'Else
'    Wscript.Echo "The service could not be stopped."
'End If

Note.  Why did we comment out the If-Then code? Well, we’re reasonably sure that this part of the script works. Therefore, there’s no need to muddy the waters (or respond to dialog boxes) until we get the first part of the script to work as well. Once both halves are working, then we’ll see if the script as a whole works properly.

What happens when we run the script this time? Nothing. But this time nothing actually means something: now we know that we never entered the loop, which means there is no chance whatsoever that this script will stop the service. Why aren’t we entering the loop? Well, typically that means we’re returning a collection that has 0 items. We can verify that by echoing the value of the collection’s Count property, which tells us how many items are in the collection:

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where Name = " & _
        "'IMAPI CD-Burning COM Service'")
Wscript.Echo "Items in collection: " & colItems.Count
For Each objItem in colItems
    Wscript.Echo "We have entered the For loop."
'    errReturn = objItem.StopService
Next
'If errReturn = 0 Then
'    Wscript.Echo "The service was stopped."
'Else
'    Wscript.Echo "The service could not be stopped."
'End If

And what happens when we run this script? We get this:

Items in collection:0

Believe it or not, we’re making a lot of progress now. Our collection has 0 items in it. That can only mean one thing: the collection has 0 items in it. But why doesn’t it have any items in it? Well, it could be that there are no services installed on the computer (highly unlikely, but ….). Fortunately, that’s easy to check: we just use this modified script, which removes the Where clause and stops before it enters the For-Each loop. (How do we cause the script to stop halfway through? We just use Wscript.Quit.)

Note.  Where clauses can be very tricky; it’s easy to write a Where clause that doesn’t generate an error, but that also doesn’t return the expected data. Whenever possible, it’s a good idea to save the Where clause until the end. Get the script to work against all the items, and once you’re confident that that’s working then try to get it to work against only targeted items. In this case, of course, you have to be careful, because you don’t want to stop every service on the computer. But for scripts that simply return data, make sure the script returns data for all the services before you tinker with it and make it return data for only one service.

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service")
Wscript.Echo "Items in collection: " & colItems.Count
Wscript.Quit
For Each objItem in colItems
    Wscript.Echo "We have entered the For loop."
    errReturn = objItem.StopService
Next
'If errReturn = 0 Then
'    Wscript.Echo "The service was stopped."
'Else
'    Wscript.Echo "The service could not be stopped."
'End If

When we run this script, we get a message box like this:

Items in collection: 86

So we now know that there are services installed on this computer; apparently there just isn’t one named IMAPI CD-Burning COM Service. (What’s that? Yes, you are just like Sherlock Holmes.) Just to verify that our For-Each loop works, let’s try running this next script. Notice we’ve made a few changes here:

  • We’re no longer echoing the Count; we already know that there are services installed on the computer.

  • We’ve commented out the line of code that stops the service. Why? Remember, we’re looping through the collection of all the services. If we leave this line of code in, the script will try to stop every service on the computer, something we probably don’t want it to do.

  • Inside the For-Each loop, we’re echoing the name of the service. There are two reasons for this. First, it verifies that we have entered the loop, and that the loop is working (that is, we’re hitting each and every item in the collection). Second, we’ve pretty much determined what the problem is: our Where clause, which should return all the services with the name IMAPI CD-Burning COM Service, isn’t returning anything. This way we can verify whether or not there really are any services named IMAPI CD-Burning COM Service.

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service ")
For Each objItem in colItems
    Wscript.Echo objItem.Name
'    errReturn = objItem.StopService
Next
'If errReturn = 0 Then
'    Wscript.Echo "The service was stopped."
'Else
'    Wscript.Echo "The service could not be stopped."
'End If

Let’s run this script under CScript and see what happens. (Why CScript? Well, we have 86 services installed on this computer; we don’t want to click 86 message boxes, which is what we’d have to do if we ran the script under WScript.) Here’s a portion of the output we get:

Command Prompt

If we now run a forensic analysis, take a few DNA samples, dust for fingerprints, and then look at the results, we’ll notice something: there is no service named IMAPI CD-Burning COM Service. The plot thickens! Either there is no service named IMAPI CD-Burning COM Service, or the service named IMAPI CD-Burning COM Service is – good heavens! – running under a different name. (If you see a service named John Doe, be very suspicious.)

This, by the way, is another common problem with WMI scripts: the name that you give to something isn’t always the same name that WMI gives to things. For example, here’s a line of code that appears to return information about drive C on a computer:

Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Logical Disk Where DeviceID = 'C'")

How many items do you think will be in this collection? (Careful, this is a trick question.) That’s right: zero. That’s because C is not a valid DeviceID for the Win32_LogicalDisk class; instead, the DeviceID for drive C is actually C:. Our script is looking for a disk with the DeviceID of C, and no matter how hard it looks it’s never going to find one. Thus we get back a collection with 0 items.

Step 4: Make Sure the Simple Stuff Works First

So we now have good reason to suspect that there’s no service with the name IMAPI CD-Burning COM Service. How can we determine the real name of our service? Well, one way is to do what we just did: write a script that echoes the names of all the services. By looking through the output, we might accidentally stumble upon the service name. In this case, for example, we see there’s a service with the name ImapiService. That evidence might not hold up in court, but it looks very similar to IMAPI CD-Burning COM Service. In fact, we could put ImapiService in our script and hope for the best, but seeing as how we’re trying to be at least a little systematic about this, let’s try to verify that this is the correct service.

Because we’re using WMI in this script, doing this kind of verification is pretty easy. Every computer that has WMI installed also has a nifty little utility named Wbemtest.exe that can be very useful in verifying information. If you’d like to know more about Wbemtest, you might check out Part 1 of the WMI Scripting Primer on MSDN. For now, just follow these steps:

  1. In the Run dialog box, type wbemtest and then press ENTER.

  2. In the Windows Management Instrumentation Tester (Wbemtest) click Connect.

  3. In the Connect dialog box, type root\cimv2 in the Namespace box and then click Connect.

  4. Click Enum Classes.

  5. In the Superclass Info dialog box, leave the Enter superclass name box blank, click Recursive, and then click OK.

  6. In the Query Result dialog box, double-click Win32_Service.

  7. In the Object Editor for Win32_Service dialog box, click Instances.

  8. In the Query Result dialog box double-click Win32_Service.Name=”ImapiService”.

Now, start scrolling through the Object Editor for Win32_Service.Name=”ImapiService” dialog box and see if there’s any reason to believe that this is the same thing as the IMAPI CD-Burning COM Service. Let’s see, AcceptPause, AcceptStop – ah-ha! Take a look at the DisplayName:

Object editor for Win_Service.Name="ImapiService"

Your honor, the defense rests.

As it turns out, IMAPI CD-Burning COM Service is the display name for the service (the name that shows up in the Services console). However, in our Where clause we weren’t asking for the DisplayName, we were asking for the Name (which we now know is ImapiService). Let’s take a previous version of our script, and modify it to use DisplayName rather than Name (we’ll also modify it so that it echoes the DisplayName of each service it returns):

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where DisplayName = " & _
        "'IMAPI CD-Burning COM Service'")
Wscript.Echo "Items in collection: " & colItems.Count
For Each objItem in colItems
    Wscript.Echo "DisplayName: " & objItem.DisplayName
'    errReturn = objItem.StopService
Next
'If errReturn = 0 Then
'    Wscript.Echo "The service was stopped."
'Else
'    Wscript.Echo "The service could not be stopped."
'End If

When we run this script, we get two message boxes, the first telling us that there is 1 item in the collection, the second echoing the name IMAPI CD-Burning COM Service. That’s the best news we’ve had all day!

All that’s left now is to see if we can really stop the service and get the correct message echoed to the screen. Let’s uncomment the commented lines and remove the lines that echo the number of items in the collection and the DisplayName. That leaves us with this:

On Error Resume Next
strComputer = "."
Set objWMIService = GetObject _
    ("winmgmts:\\" & strComputer & "\root\cimv2")
Set colItems = objWMIService.Execquery _
    ("Select * From Win32_Service Where DisplayName = " & _
        "'IMAPI CD-Burning COM Service'")
For Each objItem in colItems
    errReturn = objItem.StopService
Next
If errReturn = 0 Then
    Wscript.Echo "The service was stopped."
Else
    Wscript.Echo "The service could not be stopped."
End If

What happens when we run this script? Well, we get a message box saying that the script was stopped. And if we check the status of IMAPI CD-Burning COM Service in the Services console – lo and behold, it’s stopped! Mission accomplished.

Note.  Whoa, wait a second. Remember how, when we first ran the script, it told us that the service was stopped even though it wasn’t? What was the deal there, and did we ever fix that? Well, the deal was this: because we never entered the For Each loop, errReturn was never explicitly assigned a value. Because it was never explicitly assigned a value, it gets assigned a default value of 0 instead. Our If Then statement checks to see if errReturn is 0, and it is. Consequently, the script assumed that the service was stopped. That’s because errReturn was never really assigned the return value.

Of course, that can always be an issue with a script like this. How can you work around that? Well, early on in the script assign some crazy value (like 99999999) to errReturn. If you get this value back, you know your script has failed to assign a value to errReturn.

And in Conclusion …

Admittedly, not all your problems will be this straightforward, and not all your scripts will be this short. That means that the approach we used to fix our sample script won’t always work for you. However, there are a few general rules you can take away from here that will almost always be useful when debugging a script:

  • Get rid of On Error Resume Next. On Error Resume Next is a nice little construct, but in the development phase it typically causes (or at least masks) more problems than it solves. If you’re having problems with a script and you’ve got this implemented, the first thing you need to do is get rid of it.

  • Read the error messages, check the line of code specified and, if necessary, look up the error number (remember, for a VBScript error you must convert the last four digits of the hexadecimal number to a decimal value).

  • Break your script into sections and test each section separately. Make sure each individual section is working before you re-test the script as a whole.

  • Use a separate tool (a command-line tool, the graphical user interface, etc.) to verify your results.

Do you have other tips and tricks useful for debugging scripts? If you do, we’d love to hear them (and we’d love to share them with the world in a future column). Send your tips, tricks, credit card numbers, and comments to scripter@microsoft.com (in English, if possible).

Oh, right: don’t send us your credit card numbers. But cash would be OK ….