Rob van der Woude's Scripting Pages

VBScript Scripting Techniques > Debugging Your Scripts

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

    For HTAs only:
  16. Test for 32-bit MSHTA.EXE on 64-bit Windows

 

Never assume anything

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

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 (obsolete as of June 15, 2022)

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
  Notes:
Notes: (1) Since Internet Explorer support ended June 15, 2022, this technique is no longer recommended.
  (2) objIEDebugWindow must be declared in the main script body, not in the subroutine (must be global)!
  (3) Do not discard the objIEDebugWindow object at the end of the script, or your debug window will vanish!

And this is what the debug window looks like:

Internet Explorer Debug Window

 

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:

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.

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

Write down any required WSH version greater than 1.0.
The highest/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
  Note:
Note: Yes, this check is safe, all commands and functions used were available in the very first 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):

 

For HTAs only:

Test for 32-bit MSHTA.EXE on 64-bit Windows

Sometimes an HTA can cause trouble when running with 32-bit MSHTA.EXE on a 64-bit Windows.
This is especially true when 64-bit external commands need to be executed: a 32-bit MSHTA process simply cannot start a 64-bit external command.

The following sample code warns the user for this condition when the HTA is loaded:

Sub Window_OnLoad( )
	Dim colItems, intMSHTA, intWindows, objItem, objWMIService
	' intMSHTA will contain MSHTA.EXE's "bittedness"
	intMSHTA = CInt( Right( window.navigator.platform, 2 ) )
	' Use WMI to determine Windows' own "bittedness"
	Set objWMIService = GetObject( "winmgmts://./root/cimv2" )
	Set colItems = objWMIService.ExecQuery( "SELECT * FROM Win32_Processor" )
	For Each objItem in colItems
		' intWindows will contain Windows' "bittedness"
		intWindows = CInt( objItem.AddressWidth )
	Next
	Set objWMIService = Nothing
	' Display a warning message if the "bittednesses" don't match
	If intWindows <> intMSHTA Then
		MsgBox "You are running this HTA with the " _
		     & intMSHTA _
		     & "-bit MSHTA on a " _
		     & intWindows _
		     & "-bit Windows"
	End If
End Sub

 


page last modified: 2022-10-22; loaded in 0.0061 seconds