VBScript Scripting Techniques > 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.
Option Explicit
and declare all variablesOn Error Resume Next
lines
This may be the most important thing to keep in mind when scripting — in any language.
WScript.Version
!Err
and IsObject
to check if it was successfully created.Use common sense.
Make sure you log any requirements that aren't met, and/or display descriptive error messages.
Option Explicit
and declare all variablesIt 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).
On Error Resume Next
linesWhen 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.
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.
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.
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.
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.
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.
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:
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.
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.
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
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.
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 |
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):
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.0015 seconds