Partager via


September 2004

SG090301

By The Scripting Guys

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

On This Page

Bugs check in but they don’t check out
Starting the Script Debugger
Stepping through code
One and done
Setting and removing breakpoints
Working with variables
Running script commands
A Scripting Guys’ Exclusive
The last word

Bugs check in but they don’t check out

Ok, so we’ve all heard about how smart dolphins are, and that chimpanzees are capable of using sign language. We’ve even read about birds in New Caledonia that are able to use tools. (Although, in all fairness, they just hold a stick in their beak and try to dig bugs out of cracks and crevices in a tree; it’s not like they’re operating a table saw or a power drill or something.)

Now, this is all well and good, and we’re very happy for our fellow members of the animal kingdom; keep up the good work, guys. And while there’s nothing wrong with either sign language or tool use, there is at least one area in which no other creature can come close to us humans: wiping out entire species.

That’s right. Human beings versus the passenger pigeon? No contest. Human beings versus the dodo? Hasta la vista, birdie. New Caledonian crows might be able to use tools to extract grubs, but big deal; after all, who was it who wiped out the African Blue Antelope? Here’s a hint for you: it sure wasn’t the New Caledonian crows.

Clarification.  The Scripting Guys, of course, are being facetious, and do not favor the mass extermination of other species; as a matter of fact, we do not wish harm to befall any living creature.

Well, OK, maybe the neighbor’s dog, the one that barks all the time. And that creepy cat up the street that wears the blue bandana. But that’s about it.

Of course, as good as humans are at extinction and extermination, there’s at least one creature who has thus far defied our best efforts at liquidation: the computer bug. First reported in 1947, the computer bug hasn’t been wiped out; in fact, if anything each day brings us more and more of these insidious pests. Furthermore, the effect of these bugs seems to get correspondingly worse as well; for example, it has been estimated that the infamous Y2K bug alone might have cost the world upwards of $300 billion.

Historical footnote.  According to legend, the first computer bug was just that: an actual bug. In 1947, problems arose with the Mark II Aiken Relay Calculator, one of the world’s first computers. Upon investigation, programmers found a moth that had become wedged between a relay, shorting out the machine (and doing a number on the moth as well). The moth was removed and taped into the Mark II logbook along with the note that it represented the “First actual case of bug being found.” Believe it or not, that bug has since been enshrined in the Smithsonian Institute. (Apparently, what with Michelangelo and Leonardo being dead all these years, museums are hard-up for things to exhibit.)

We know that bugs – or, perhaps more specifically, errors in your code that prevent your scripts from working properly – are something system administrators are very concerned about. After all, during the recent Scripting Week webcast series we received scores of questions like these:

“Are there any tools I can use to debug code?”

“Do you know of any software that can help with debugging code and fixing problems with scripts?”

“Is there some place where I can download a script debugger?”

As a matter of fact, there is a place where you can download a script debugger: right here on Microsoft.com. Although few people seem to know about it, Microsoft has a (free!) script debugger and – despite a somewhat quirky interface – it actually replicates many of the functions found in full-fledged development environments such as Visual Studio. If you’re looking for a tool to help you debug your scripts, the Microsoft Script Debugger is a good place to start.

Note.  So if Microsoft has a free script debugger, what haven’t we made a big deal about that and advertised the fact? Beats us. Most likely it’s because Microsoft code is always bug-free, so we never think about things like debuggers.

Hey, we said most likely ….

Incidentally, if you have Windows 2000 you already have the Script Debugger; it’s included on the Windows CD as an installation option. The Debugger doesn’t ship with either Windows XP or Windows Server 2003, but you can download a version for those operating systems, as well as a version for Windows 98, Windows ME, and Windows NT 4.0.

And if you’re looking for download locations, try these:

Starting the Script Debugger

The Microsoft Script Debugger is a pretty decent little tool but, as we noted, it has its eccentricities. Let’s start with the big one. After you download and install the Script Debugger, your first inclination will likely be to start the Debugger and load a script into it. Don’t bother trying this; it won’t work. Oh, sure, the Debugger will start and you can load a script into it, but at that point you’ll run into a bit of a problem; notice that all the debugging commands are grayed out, and that you can’t actually do anything:

Script Debugger

Come on, no jokes about how the world would be better off if other Microsoft software didn’t do anything! Don’t worry: the Script Debugger actually works, it’s just that it doesn’t work if you start the thing manually and then load in a script. Instead, you need to start the script from the command line, passing the //x parameter. For example, to load the script my_script.vbs into the Script Debugger, you’d type something like this:

cscript my_script.vbs //x

Note.  Why does the Script Debugger work this way? We have no idea. You might ask a dolphin; after all, they’re supposed to be so smart ….

Actually, the answer likely has to do with the fact that the Script Debugger was originally designed for debugging ASP and HTML pages. The fact that the Debugger also works with standalone VBScript and Jscript files is really something of a bonus.

Admittedly the whole thing sounds a little crazy, but it works. Start the script using the //x parameter, and you’ll notice that all the debugging commands are now available:

werw

Important.  Before we go much further, we should point out that the Script Debugger is a tool that gives you considerable control over how you run your script; however, it’s not some sort of magical environment in which the script runs without affecting anything else. Instead, your script is actually running while you use the Script Debugger; in fact, you can Alt-Tab back and forth between the Debugger and the command window if you want to see the script output as you work. If you are debugging a script that deletes all the user accounts in Active Directory, don’t consider the debugging session a dress rehearsal or a dry run; at the end of the session you’ll have deleted all the user accounts in Active Directory. The Debugger is a troubleshooting tool, but it’s not a sandbox or virtual environment of some kind.

So what do you do after you get a script loaded into the Script Debugger? Although there are several options available, we’ll focus on these tasks:

  • Stepping through code

  • Setting and removing breakpoints

  • Working with variables

  • Running script commands

Stepping through code

Microsoft Word – to name one piece of software – is an example of an event-driven application. When you start up Word, it doesn’t do anything; it just sits there and waits for an event to occur, for you to click a mouse button or press a key on the keyboard or do something. (Of course, once in a great while it just sits there even after you do something, but that’s another story.)

Scripts, by contrast, are usually procedure-driven: after they’ve started they usually don’t wait for an event to occur; instead, they just run. After a script has started it runs the first line of code and then – without even stopping to catch its breath – runs the remaining lines of code in rapid-fire succession. As soon as it runs out of lines of code, the script automatically terminates.

As long as everything works, that’s a good model. The model breaks down a bit, however, when things don’t go exactly as planned. For example, suppose you have a script that’s designed to do the following:

  • Create a text file on the local computer.

  • Retrieve hardware information from several of your servers.

  • Write that retrieved information to the text file.

  • Copy the text file from the local computer to a remote machine.

  • Delete the text file from the local computer.

You run the script and, in the blink of an eye, the script completes its task. You check the local computer, and no text file. That’s OK; after all, the script was supposed to delete the text file from the local machine. Now you check the remote computer: no text file. Uh-oh. Obviously something went wrong, but what, and, where? Even though this is a relatively simple script, there are still plenty of places where the script might have failed. How can you figure out exactly where the problem (or problems) occurred?

Note.  We’re assuming that you included On Error Resume Next in this script to keep the thing from crashing. Bear in mind, though, that even deleting On Error Resume Next won’t necessarily pinpoint where the actual error occurred. Consider this simple script:

intMyNumber = 2
A = intMyNumbr
B = 3
C = B / A

If you run this script, you’ll get an error on line 4, because you’re dividing by 0. But line 4 isn’t really the problem; the problem is really in line 2 where you set the variable A to 0 instead of 2. This is due to a typographical error: you assigned A the value of intNumbr rather than intNumber. Because intNumbr has no value, A gets assigned 0 rather than the 2 you expected to give it.

Admittedly, this one is pretty easy to figure out. The point, however, is that error messages that appear when a script crashes tell you only when the error became apparent; that is, only when the bug in the code actually caused a problem. The root cause of the error (e.g., setting a variable to 0) might have occurred hundreds of lines earlier.

One way to deal with problems like this is to use the Script Debugger to “step through” your code. Stepping through code simply means running the script line-by-line. Granted, that can be a bit tedious depending on the size of the script, and we’ll show you a workaround in a minute. On the other hand, stepping through code enables you to stop and make sure that the script is working properly each, well, step of the way.

For example, our hypothetical script is supposed to start out by creating a text file on the local machine. When we run the script as-is we have no idea whether or not the text file even got created in the first place. By stepping through the code, however, we have an easy way to verify this. We run the line of code that is supposed to create the text file, and then we stop. We then open up Windows Explorer and verify that the text file exists. If it does, we continue stepping through the rest of the script. If it doesn’t exist, then we’ve already pinpointed one trouble spot.

So how do you step through code in the Script Debugger? It’s actually pretty easy: load the script into the Debugger, then press F8. Each time you press F8, the debugger will execute a line of code, jump down to the next line of code, and then wait for you to press F8 again. (Incidentally, if you don’t like using the keyboard, you can also select Step Into from the Debug menu.) Just keep pressing F8 until you get to the end of the script. Or, you can run the rest of the script from the current line on. To do that, either press F5 or select Run from the Debug menu.

Caution.  Let’s say you’re debugging a script that retrieves events from the event logs, and let’s say you have 5,000 events in those event logs. Be careful when you step through the code using a For Each loop; the script won’t just run through the loop one time, it will run through it 5,000 times, once for each item in the collection. In a situation like that, you might want to loop through things once or twice (just to make sure the loop is working) and then press F5 to run the rest of the script. Alternatively, you might want to use breakpoints, something we’ll take about in a minute.

One and done

One last little quirk to warn you about. You’ve run through the script and everything looks pretty good. Still, better safe than sorry, right? Because of that, you’d like to run through things a second time, just to be sure. Hey, no problem … well, no problem as long as you exit the Script Debugger and reload the script. For some reason, you only get one shot at running a script in the Script Debugger; as soon as you’re finished, you need to exit the Debugger and start all over again.

We know, we know. If it’s any consolation, we don’t think that’s a particularly good way of doing things, either; in fact, we’re beginning to suspect it wasn’t human beings that killed off the African Blue Antelope as much as it was software with quirky user interface design. But at least you’ve been warned about the Script Debugger’s eccentricities; the poor Blue Antelope never saw it coming.

Setting and removing breakpoints

Let’s say you have a 1,000-line script, and you’ve been methodically stepping through it, line-by-line. Everything seems to work fine until you hit line 933; there you discover a mistake. You stop the debugger and correct the script. Now you need to load the script back into the debugger and run it again, except that you don’t really want to step through the first 932 lines. After all, you’re pretty sure those lines of code work. But what choice do you have but to start all over again?

Well, no choice at all if you’re a New Caledonian crow. As a human being, though, you’re clever enough to use a shortcut: you can set a breakpoint at line 933. What’s a breakpoint, you ask? Well, for all intents and purposes, a breakpoint is like a stop sign inserted into your code. If you run the script in the Script Debugger (by pressing F5 or by choosing Run from the Debug menu), the script will run until it hits a breakpoint; at that point, it comes to a screeching halt. From there you can either begin stepping through the code, or choose Run to run the rest of the script.

Breakpoints are easy enough to identify in the Script Debugger. In fact, they look an awful lot like this:

identify breakpoints

Note both the red highlighting and the little red stop sign-like icon.

So how do you set a breakpoint? Just place the cursor anywhere in the desired line and press F9 (or select Toggle Breakpoint from the Debug menu). To remove the breakpoint, press F9 again, or press Ctrl-Shift-F9 to get rid of all the breakpoints in the script.

Breakpoints, as you might have guessed, are a welcome alternative to stepping through code line-by-line. Are you convinced that the first 932 lines of your script are A-OK? No problem; just set a breakpoint on line 933 and run the script. The script will zip through all the lines until it hits the breakpoint; there it will stop dead in its tracks. At that point you can start stepping through the rest of the code.

Working with variables

As we noted earlier, one error that crops up over and over in scripts is the problem of variables being set to incorrect or unexpected values. What makes matters worse is that errors of this kind can be very difficult to track down, particularly in a long script in which the value of a variable might change several times. How in the world can you possibly keep tabs on the current value of a variable while a script is running?

One way to do this is to step through the script in the Script Debugger and periodically ask the Debugger for the current value of the variable. What could be easier?

Let’s try this out using a very simple script. The following script (which we saved as Test.vbs) assigns the value 2 to the variable A and assigns the value 3 to the variable B. The script then carries out a few calculations, assigning the cumulative result of those calculations to the variable C. The script itself looks like this:

A = 2
B = 3
C = A + B
C = C * A
C = C^B
Wscript.Echo C

If you run this script, you should get back the answer 1000. That’s great … or is it? After all, is 1000 the answer you should have gotten? Who knows? Even worse, how would you even begin to determine whether or not this is the right answer?

Well, one thing you can do is load the script into the Script Debugger, step through the code, and then periodically query the Debugger to see the value of the different variables. Do this: load the script into the Script Debugger and step through the first three lines. The highlight should now be sitting on C = C * A, and your screen should look something like this:

text

So is our script working so far? Well, here’s a good place to double-check. After all, we know that A is equal to 2 and B is equal to 3, and we’ve just carried out the equation C = A + B. In other words, C = 2 + 3, which means C should be equal to 5. We know that, but does our script know that?

Well, let’s ask it. In the Script Debugger, select Command Window from the View menu. You should now see a little window like this onscreen:

text

The Command Window allows us to interact with our script; we can ask it questions and, as we’ll see in a moment, we can even use it to send commands to the script. Let’s start by asking for the current value of variable C, using the ? as our command:

? C

In other words, type ? C into the Command Window and press ENTER; you should instantly get back the current value of variable C:

text

Is that cool or what? Now press F8 to step through the next line of code (C = C * A). We know that C is equal to 5 and that A is equal to 2; consequently, after running this line of code we’d expect C to equal 10 (5 * 2). So let’s use the Command Window to again find out the current value of variable C:

text

Man, we could do this all day. Step through the next line of code (C = C^B). After this line runs, C should be equal to 1000: 10 (the current value of C) to the third power (because B equals 3) is 1000. And guess what:

text

Could there be anything better than this?

Running script commands

So could there be anything better than being able to query a script, while the script is running, and get back information about what the script is doing? No, of course not … well, unless you could actually send commands to that script while it’s running. But that’s silly; there’s no way to send a command to a script while it’s running. Or is there ….

Let’s take a look at another simple little script, one that returns information about Internet Explorer add-on components:

strComputer = "atl-ws-01"
Set objWMIService = GetObject("winmgmts:\\" & strComputer _
    & "\root\cimv2\Applications\MicrosoftIE")
Set colIESettings = objWMIService.ExecQuery _
    ("Select * from MicrosoftIE_Object")
For Each strIESetting in colIESettings
    Wscript.Echo "Code base: " & strIESetting.CodeBase
    Wscript.Echo "Program file: " & strIESetting.ProgramFile
    Wscript.Echo "Status: " & strIESetting.Status
Next

As written, this script connects to a remote computer (atl-ws-01) and then retrieves some information from the MicrosoftIE_Object class. Pretty cut-and-dried, right? We want to test this script by stepping through it in the Script Debugger, so we load it up, press F8, and run the first line of code, the line that sets the value of the variable strComputer to atl-ws-01.

At that point, however, we realize we have a problem: we suddenly remember that computer atl-ws-01 is offline. Because we won’t be able to connect to the computer, the script is doomed to fail. Guess we’ll have to exit the Script Debugger, change the script (pointing it at another computer), and then re-run it, right?

Wrong. What’s the problem with the script? Well, the problem is that we are about to connect to the computer represented by the variable strComputer. That would be fine, except that the value of strComputer is currently atl-ws-01, a computer that’s offline. But you know what? Turns out that really isn’t a problem. Before we run the line of code that connects to the remote computer we just need to change the value of strComputer to the name of a computer that is online (for example, we could change strComputer to “.” in order to run the script against the local machine). And guess what? Yes, this time you are right: we can do that right from the Command Window:

text

Just type the appropriate command into the Command Window and press ENTER; just like that, the value of strComputer in your script will change to a dot (.). Now that’s really cool!

You can also enter more complex commands. For example, consider this script, which is designed to return the names of all the services installed on the local computer:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer _
    & "\root\cimv2")
For Each objItem in colItems
    Wscript.Echo "Name: " & objItem.Name
Next

Pretty slick, huh? Except for one thing: there’s a line of code missing, the line that actually retrieves the information from the Win32_Service class. The script is supposed to look like this:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer _
    & "\root\cimv2")
Set colItems = objWMIService.ExecQuery _
    ("Select * From Win32_Service")
For Each objItem in colItems
    Wscript.Echo "Name: " & objItem.Name
Next

But hey, no problem. Just load the script into the Script Debugger and step through the first two lines of code. When you get to the missing third line, don’t panic; instead, type the missing line into the Command Window and press ENTER:

text

From there, just step through the code, and the script will report the names of all the services installed on the local computer, just like it was supposed to.

A Scripting Guys’ Exclusive

Now here’s something really cool. The Command Window is designed to run a single line of code at a time; you type in the command, press ENTER, and that line of code is executed. That’s great, but suppose you left out three lines of code, suppose you forgot to put in the For Each loop. No doubt you’re thinking, “That’s OK, I’ll just type the missing code in line-by-line.” Granted, in some cases you can do that. In this case, however, here’s what happens when you type in the first missing line and then press ENTER:

text

What’s the problem? The problem is that you’ve created a For Each loop, but there’s no Next statement (there’s no Next statement because you haven’t had a chance to type it in yet). Because the Command Window works with single lines of code, there’s no way for you to type in the Next statement; an error gets generated long before you get to that line. Looks like you’re out of luck, right?

Wrong (again!). Turns out that VBScript allows you to type multiple lines of code on a single line, providing you use a colon (:) to separate the individual lines. For example, we can put our entire For Each loop into one line of code, like this:

For Each objItem in colItems:Wscript.Echo "Name: " & objItem.Name:Next

And, yes, you’re way ahead of us: if you type that string into the command window and press ENTER, all three lines of code will be executed:

text

Will you ever need this? Maybe not. But, then again, a few months ago you never thought you’d need anything related to scripting, did you?

The last word

As members of the human race we hate to admit this, but we’re probably never going to be able to get rid of computer bugs for good. California condors? Sure, we can wipe out the California condor. But computer bugs? Not likely; there’s just too many of them.

On the other hand, just because the species might survive indefinitely that doesn’t mean we can’t track down and exterminate individual bugs, particularly those lurking within our scripts. And if it helps us to use a tool like the Microsoft Script Debugger, well, there’s no shame in that. After all, if there was any way those computer bugs could use a tool like this against us, well …. Give the Script Debugger a try, and let us know what you think of it.

 

Questions or comments about this month’s column? Send them to scripter@microsoft.com (in English, if possible). Due to the flood of mail we get each day it’s difficult for us to answer individual inquiries, but we do read each piece of email we get.