You are on page 1of 9

Debugging Your Scripts

Scripts will seldom be perfect right away.


This page describes some (debugging) techniques that will help you avoid errors in VBScript, or
to find and correct them.

1. Never assume anything


2. Always use Option Explicit and declare all variables
3. (Temporarily) disable all On Error Resume Next lines
4. Modularize your scripts with functions and subroutines
5. Use descriptive names for variables, functions and subroutines
6. Initialize variables
7. Avoid nested functions
8. Display or log intermediate results like variable values and return codes
9. Create and use a debug window
10. Use a VBScript aware editor or IDE with built-in debugger
11. Document your scripts with useful comments
12. Use custom error handling
13. Clean up
14. Check the WSH version
15. Use a debugger, if available

Never assume anything


This may be the most important thing to keep in mind when scripting -- in any language.

 Never assume a WSH version.


Check it using WScript.Version!
Read more about WSH versions.
 Never assume a Windows version.
Check it!
 Never assume .NET Framework is installed.
Check it!
 Never assume a script runs with administrative rights.
Check it!
 Never assume access to WMI is allowed.
Use custom error handling to check it!
 Never assume write access.
Again, check it!
 Never assume an open Internet connection.
Check, check, check!
 If you create a new instance of an object, use custom error handling with Err and
IsObject to check if it was successfully created.
 Always check if required extensions are available before trying to use them!
 Well, you get the idea...

Use common sense.

Make sure you log any requirements that aren't met, and/or display descriptive error messages.

Always use Option Explicit and declare all variables


It may seem a nuisance to force yourself to declare all variables, but trust me, Option Explicit
will save you a lot of time searching for errors caused by typos in variable names.
It will also help you find variables with the wrong scope (local vs. global).

(Temporarily) disable all On Error Resume Next lines


When you're looking for the cause of an error, you do want to see the error messages stating
which line in the code generates the error.
So "comment out" any On Error Resume Next lines while testing/debugging.
And whenever you really do need to use On Error Resume Next, check for errors
(If Err Then...), and switch back to On Error Goto 0 as soon as possible.

Modularize your scripts with functions and subroutines


Any block of code that will be used more than once can be moved into a subroutine or function.
Dividing the code into logical subroutines and functions will improve readability of your script,
and thus will make maintenance a lot easier.
If a "self-contained" subroutine or function has been debugged, it will save debugging time when
you reuse it in another script.

If your function or subroutine receives parameters, use distinctive parameter names to avoid
conflicts with global variables. Do not use an existing variable name for a parameter name.
As you may have noticed, I use the prefix my for parameter names in my own scripts. Choose any
naming system you want, but be consistent, and keep in mind that some day others may need to
understand your code.

To be completely on the safe side, use ByVal as in

Function MyFunc( ByVal varParameter )


to prevent changing the value of an existing global variable named varParameter in the global
scope.

Experiment with Denis St-Pierre's ByVal/ByRef test script to become familiar with the concepts.

Use descriptive names for variables, functions and


subroutines
You are (almost) completely free to use any name for a variable, subroutine or function.
However, instead of using a name like f, why not use objFSO for a FileSystem object? Or
Decode instead of dec as a function name?
Imagine what a difference it will make when someone else needs to read and understand your
code (or you yourself a couple of months from now...).
By choosing logical, descriptive names, you may also save yourself time while debugging.

You may have noticed that many scripters use the first character, or the first 3 characters, of
variable names to specify the data type: objFSO for a (FileSystem) object, intDaysPerWeek for
integers, etc.
Though in VBScript any variable can contain any type of data at any time, this naming
convention helps make clear what type of data a variable is supposed to contain.

For function or subroutine that receive parameters, use distinctive parameter names to avoid
conflicts with global variables. Using existing variable names for parameter names spells trouble.
As you may have noticed, I use the prefix my for parameter names in my own scripts. You can
choose any naming system you want. But do keep it consistent.
Keep in mind that some day others may need to understand your code.

Again, to be completely on the safe side, use ByVal as in

Function MyFunc( ByVal varParameter )

to prevent changing the value of an existing global variable named varParameter in the global
scope.

I urge you to try Denis St-Pierre's ByVal/ByRef test script to build an understanding of the
concepts.
It may save you days of stressful debugging.

Initialize variables
This may be important when you use loop counters other than For loops: make sure the counter
variable has a valid value to start with.
Also watch out for global variables that are used in subroutines or functions.

Avoid nested functions


A one-liner like:

strFullPath = "C:\Documents and Settings\Me\Application Data"


strParentName = Right( Left( strFullPath, InStrRev( strFullPath, "\" ) - 1 ),
_
Len( Left( strFullPath, InStrRev( strFullPath, "\" ) - 1 ) )
- _
InStrRev( Left( strFullPath, InStrRev( strFullPath, "\" ) -
1 ), "\" ) )

is hard to debug if it returns the wrong result.


Split it up in several lines, each without nested functions, and use variables to contain the
intermediate results:

strFullPath = "C:\Documents and Settings\Me\Application Data"


intLastSlash = InStrRev( strFullPath, "\" )
strParentName = Left( strFullPath, intLastSlash - 1 )
intParentLen = Len( strParentName ) - InStrRev( strParentName, "\" )
strParentName = Right( strParentName, intParentLen )

Now, if the code doesn't do what it is supposed to do, you can have a look at the intermediate
results to check where the problem lies.

Display or log intermediate results like variable values and


return codes
To check the script's program flow, and the values of variables during execution, it helps to
display variable names and their values during run time.
If external commands or objects are used, display their return codes as well.

Write every detail in a log file too, preferably with a timestamp in order to detect possible delays.
If subroutines or (user defined) functions are used, log each call to these subroutines, it will help
you follow the program flow.

If I expect problems with a script, I often add an optional /DEBUG command line switch, which
will tell the script to log even more details.
Create and use a debug window
This is a trick I learned from Don Jones, who describes it in his book VBScript, WMI, and ADSI
Unleashed: Using VBScript, WMI, and ADSI to Automate Windows Administration.

Dim objIEDebugWindow

Debug "This is a great way to display intermediate results in a separate


window."

Sub Debug( myText )


' Uncomment the next line to turn off debugging
' Exit Sub

If Not IsObject( objIEDebugWindow ) Then


Set objIEDebugWindow = CreateObject( "InternetExplorer.Application" )
objIEDebugWindow.Navigate "about:blank"
objIEDebugWindow.Visible = True
objIEDebugWindow.ToolBar = False
objIEDebugWindow.Width = 200
objIEDebugWindow.Height = 300
objIEDebugWindow.Left = 10
objIEDebugWindow.Top = 10
Do While objIEDebugWindow.Busy
WScript.Sleep 100
Loop
objIEDebugWindow.Document.Title = "IE Debug Window"
objIEDebugWindow.Document.Body.InnerHTML = _
"<b>" & Now & "</b></br>"
End If

objIEDebugWindow.Document.Body.InnerHTML = _
objIEDebugWindow.Document.Body.InnerHTML _
& myText & "<br>" & vbCrLf
End Sub
objIEDebugWindow must be declared in the main script body, not in the subroutine
Notes: (1)
(must be global)!
Do not discard the objIEDebugWindow object at the end of the script, or your debug
(2)
window will vanish!

And this is what the debug window looks like:


Use a VBScript aware editor or IDE
with built-in debugger and object browser

There are several VBScript aware editors (IDEs) available, some with built-in debugger. The
main advantages of these editors are:

 Different colors for commands and keywords: a typo will result in the wrong color
 IntelliSense ™ like "intelligence" and object browser: type a dot after an object name and
you'll get a drop-down list of avaialble properties and methods
 Built-in debugger: run the script "inside" the editor, add breakpoints, monitor variable
values, get improved error handling

My personal favorite is VbsEdit, which saves me a lot of time and frustration when writing in
VBScript.

Because there are more editors and more scripting languages, I compiled a list of script editors
and IDEs.

Document your scripts with useful comments


It is always wise to add comments explaining what a script, or part of the script, does.
However, make sure the comments are relevant for future use -- scripts need maintenance every
now and then, and comments can help make this easier.
If you intend to reuse code, like subroutines or user defined functions, it is essential to describe
the functionality in comments.
Include a description of what the routine is intended for, its requirements, input parameters,
output and/or return codes.

In short, describe it as a "black box": what goes in, what comes out, and how are the two related.

Use custom error handling


It is ok to use the scripting engine's built-in error handling, but adding your own custom error
handling may result in a better "user experience".

Insert a line On Error Resume Next just before some code that might cause trouble.
Insert another block of code after that suspect code to deal with potential errors.
Use Err or Err.Number and Err.Description to detect and log and maybe even correct errors.
If no more problems are expected, insert a line On Error Goto 0 after the custom error
handling code to restore the default built-in error handling.

On Error Resume Next

' some code that might raise an error

If Err Then
WScript.Echo "Error # " & Err.Number
WScript.Echo Err.Description
' take some action, or in this case, abort the script with return code 1
WScript.Quit 1
End If

Clean up
It is usually advisable to clean up any leftover objects at the end of the script.
Objects like the FileSystem object, Internet Explorer and others may cause memory leaks if they
aren't discarded and new instances are being opened all the time.

Just make sure to add a Set objectName = Nothing line at each "exit" (just before each
WScript.Quit) and end of the program flow.

Objects that are "created" inside a subroutine or function should always be discarded at the end
of the routine.
A known exception to this rule is the Internet Explorer Debug Window discussed before.
Check the WSH version

Ok, so you wrote your script, tested it on your own computer, maybe even on multiple computers,
and everything seems to work fine.
Does that mean your script is ready to be distributed?

Would I ask if it were?

No, we are not done yet.


A final screening is necessary to find out the minimum WSH version required to run the script.

Browse through your script and for each command you used, look up the WSH version required
in MSDN's VBScript Version Information tables (JScript Version Information is available too).
The WSH version required for a particular command can be found in the page's second table.

Besides using the MSDN links above, you can also use the WSH documentation in .chm
Note:
format

Write down any required WSH version greater than 1.0.


The hightest/latest version you wrote down is the minimum WSH version required to run your
script.
Now check the VBScript Version Information page's first table to see if the minimum WSH
version requirement is met by all client computers you had in mind...

In case you aren't sure about the client computers, you can make your script itself perform a
check, using:

intMajorVerion = 0 + CInt( Mid( WScript.Version, 1, InStr( WScript.Version,


"." ) - 1 ) )
intMinorVerion = 0 + CInt( Mid( WScript.Version, InStr( WScript.Version, "." )
+ 1 ) )
intCheckVersion = 1000 * intMajorVerion + intMinorVerion
If intCheckVersion < 5005 Then
WScript.Echo "Sorry, this script requires WSH 5.5 or later"
WScript.Quit intCheckVersion
End If
Yes, this check is safe, all commands and functions used were available in the very first
Note:
WSH version

Use a debugger, if available


This tip was sent by Tom Hoff: use CSCRIPT //X yourscript.vbs to execute your script in a
debugger.

You will be prompted to choose a debugger from a list of available debuggers.

Debuggers that can be used for VBScript include (but are, most likely, not limited to):

 Microsoft Script Debugger


 Microsoft Script Editor (comes with Microsoft Office)
 Visual Studio's debugger