Try..Catch..Finally in ... VBScript? Sure!
Saturday, April 21, 2007 5:01:32 PM
I've been working a lot with VBScript and Windows Scripting Host (WSH) lately. Unfortunately, as many of you VBScript writers know, the error-handling capabilities of VBScript leave a lot to be desired. Luckily, a simple design coding pattern from modern programming languages has revealed a rather unique technique in VBScript that resembles...well, a Try..Catch..Finally block! Yep, you heard correctly. With a liberal dose of multi-line colons ( : ), your VBScript code can attain near perfect Try..Catch..Finally functionality. Note: Guaranteed error-handling, and guaranteed finally block execution in a pseudo-subroutine fashion -- all without requiring an "On Error" statement!
Too good to be true? Here's the catch (no pun intended): You leverage the object lifespan provided by VBScript objects. VBScript supports OOP? Yes sir! Grab a copy of your favorite VBScript documentation, and do a search for "Class".
Basically, here's some pseudo-code that illustrates what we all wish VBScript supported...(yeah, I know VB.NET supports this, bah!)
And here's a reality check...
See a resemblance between the two examples? Sure. And guess what? While the first example doesn't work in VBScript, the second example runs just fine in VBScript 5.6 -- once the DoLog subrountine is defined
So explain, how does it work? Simple, really. You are defining a Class rather than a Sub-routine. Instantiated Classes (called objects) have a predefined lifespan: a constructor is guaranteed to run once the object is created; and a destructor is guaranteed to run once the object is destroyed. You simply leverage these innate object abilities to handle errors gracefully -- all without using "On Error" statements!
I hope you find this exercise fun, if not useful. I've been rewriting some larger WSH VBScripts using this new technique, and so far so good. I've thrown some various crash scenerios (from the past) at them, and not only did they manage to gracefully handle the errors, but the scripts completely finished without dying mid-way through them -- like they used to.
Enjoy.
Too good to be true? Here's the catch (no pun intended): You leverage the object lifespan provided by VBScript objects. VBScript supports OOP? Yes sir! Grab a copy of your favorite VBScript documentation, and do a search for "Class".
Basically, here's some pseudo-code that illustrates what we all wish VBScript supported...(yeah, I know VB.NET supports this, bah!)
Sub Func1
Try
DoLog "Starting"
Dim i : i = 65535 ^ 65535
MsgBox "Should not see this"
Catch e
Select Case e.Number
Case 6 DoLog "Overflow handled!"
Case Else DoLog "Unhandled error " & e.Number & " occurred."
End Select
Finally
DoLog "Exiting"
End Try
End Sub
Call Func1
And here's a reality check...
Class CFunc1
Private Sub Class_Initialize
DoLog "Starting"
Dim i : i = 65535 ^ 65535
MsgBox "Should not see this"
End Sub : Private Sub CatchErr : If Err.Number = 0 Then Exit Sub
Select Case Err.Number
Case 6 DoLog "Overflow handled!"
Case Else DoLog "Unhandled error " & Err.Number & " occurred."
End Select
Err.Clear : End Sub : Private Sub Class_Terminate : CatchErr
DoLog "Exiting"
End Sub
End Class
Dim Func1 : Set Func1 = New CFunc1 : Set Func1 = Nothing
See a resemblance between the two examples? Sure. And guess what? While the first example doesn't work in VBScript, the second example runs just fine in VBScript 5.6 -- once the DoLog subrountine is defined

So explain, how does it work? Simple, really. You are defining a Class rather than a Sub-routine. Instantiated Classes (called objects) have a predefined lifespan: a constructor is guaranteed to run once the object is created; and a destructor is guaranteed to run once the object is destroyed. You simply leverage these innate object abilities to handle errors gracefully -- all without using "On Error" statements!
I hope you find this exercise fun, if not useful. I've been rewriting some larger WSH VBScripts using this new technique, and so far so good. I've thrown some various crash scenerios (from the past) at them, and not only did they manage to gracefully handle the errors, but the scripts completely finished without dying mid-way through them -- like they used to.
Enjoy.

LenChaney # Thursday, August 23, 2007 1:57:05 PM
Can you give me an example of how you would use this in your WSH script. I understand how it works but I was wondering how you would embed it in your script. I'm also looking to use it in ASP. Most of my development is in Classic ASP. We haven't been Dotted yet.
Thanks,
Len
Lee HarveyLee_Harvey # Thursday, August 23, 2007 7:37:37 PM
Option Explicit Dim exitcode : exitcode = 0 Sub HandleError(ByVal c) exitcode = exitcode Or Err.Number If Err.Number = 0 Then Exit Sub Select Case Err.Number Case Else WScript.StdErr.WriteLine "Unhandled " & c & " error " & Err.Number & ": " & Err.Description End Select Err.Clear End Sub Class CWeb Private xml Private Sub Class_Initialize() Set xml = CreateObject("Msxml2.XMLHTTP") End Sub Private Sub Class_Terminate() Set xml = Nothing HandleError "CWeb" End Sub Public Function GetPage(ByVal url) xml.Open "GET", url, False xml.Send GetPage = xml.responseText End Function End Class Class CThreatCon Private regEx Private match Private matches Public level Public msg Private Sub Class_Initialize() Set regEx = New RegExp End Sub Private Sub Class_Terminate() Set regEx = Nothing Set matches = Nothing HandleError "CThreatCon" End Sub Public Sub Parse(ByVal s) regEx.IgnoreCase = False regEx.Global = False regEx.Pattern = "<img src=""/img/threatcon/threatcon_level(%5B0-9%5D)\.gif"" border=0></p>\W+<p>([^<]+)<" Set matches = regEx.Execute(s) For Each match In matches level = match.SubMatches(0) msg = match.SubMatches(1) Next End Sub End Class Class CAllSystems Private web Private threatCon Private Sub Class_Initialize() Err.Clear Set web = New CWeb Set threatCon = New CThreatCon End Sub Private Sub Class_Terminate() Set web = Nothing Set threatCon = Nothing HandleError "CAllSystems" End Sub Public Sub Go() threatCon.Parse web.GetPage("http://www.symantec.com/avcenter/threatcon/") WScript.StdOut.WriteLine "Level=" & threatCon.level & ". " & threatCon.msg End Sub End Class Dim AllSystems : Set AllSystems = New CAllSystems AllSystems.Go Set AllSystems = Nothing WScript.Quit exitcodeNote: In this case, I used a global error-handling subroutine for the classes.Eddie AdamsEddieAdams # Sunday, March 22, 2009 11:05:24 PM
It's been a while and I hope you're still active here.
First off, thanks for the tips.
My question: Is there a way to make a VBscript with classes silent? For instance, if I am now handling my error output to a log file is there a way I can switch off the M$ generated message to the console?
Eddie.
Lee HarveyLee_Harvey # Monday, March 23, 2009 12:07:43 AM
If you are creating, opening, and writing to the log file inside your VBScript using Scripting.FileSystemObject -- rather than using standard console redirection (> or >>) -- then try using the //B command-line option, which runs the script in Batch mode, and suppresses all StdErr and StdOut output during the lifetime of your script. For example:
Otherwise, you can set Interactive mode ON/OFF inside your VBScript using the following line:
...which dynamically controls if/when your script can produce output. Very handy, IMO.
Obviously, you can also use "On Error Resume Next" to suppress error output, then use "On Error Goto 0" to re-enable error reporting. But without explicitly handling suppressed errors, your script may have unexpected results or behaviors.
Another technique is to use a simplified parent script to invoke the main child script, and capture the StdErr and StdOut streams produced by the child script. Here's an example:
Class CMain Private oShell Private oExec Private Sub Class_Initialize() Set oShell = CreateObject("WScript.Shell") Set oExec = oShell.Exec("cmd.exe /C cscript.exe " & _ " //e:vbscript //nologo C:\child.vbs") End Sub Private Sub Class_Terminate() Set oExec = Nothing Set oShell = Nothing End SUb Public Sub Go() Do While oExec.Status = 0 WScript.Sleep 100 Loop Dim e : e = oExec.StdErr.ReadAll Dim s : s = oExec.StdOut.ReadAll WScript.Echo "Errors reported by child script: " & e End Sub End Class Dim Main : Set Main = New CMain : Main.Go : Set Main = NothingHope this helps.
Meindert MeindertsmaMeindert # Sunday, March 13, 2011 8:12:13 PM
with:
Meindert MeindertsmaMeindert # Sunday, March 13, 2011 8:45:41 PM
The standard output should be visible more or less immediately, while the standard error stream could be sent to a log file after the subprocess had finished. But the whole thing got stuck in the while loop when the subprocess produced more than a few lines of output and errors. I suspect the Readline operation lost the proper place to read from when the stream buffers grew too big.
I'm not too enthousiastic about the Exec facility anymore.
irged # Monday, August 8, 2011 3:04:35 AM
Dim ArgObj, var1, var2, var3
Set ArgObj = WScript.Arguments
var1 = ArgObj(0)
var2 = ArgObj(1)
var3 = ArgObj(2)
Class CFunc1
Private Sub Class_Initialize
WScript.Echo "Starting"
Set objExcel1 = CreateObject("Excel.Application")
objExcel1.DisplayAlerts = false
Set objWorkbook1 = objExcel1.Workbooks.Open(var1)
Set objWorkbook2 = objExcel1.Workbooks.Open(var2)
objExcel1.Run (var3)
objWorkbook1.Save
objExcel1.Quit
Set objExcel1 = Nothing
set ArgObj = Nothing
MsgBox "Should not see this"
End Sub : Private Sub CatchErr : If Err.Number = 0 Then Exit Sub
Select Case Err.Number
Case 6 WScript.Echo "Overflow handled!"
Case Else WScript.Echo "Unhandled error " & Err.Number & " occurred."
End Select
Err.Clear : End Sub : Private Sub Class_Terminate : CatchErr
WScript.Echo "Exiting"
End Sub
End Class
Dim Func1 : Set Func1 = New CFunc1 : Set Func1 = Nothing
Lee HarveyLee_Harvey # Monday, August 8, 2011 3:32:14 AM
Thus, try creating a new Public method (eg, Public Sub Main), then place most of your core class logic inside it, and call this new public method at the end. For example:
With New CFunc1 : Call .Main() : End With
I typically try to keep the code inside Private Sub Class_Initialize tight -- leaving little room for error -- just to ensure the custom class gets created/initialized properly.
Hope this helps.
Lee HarveyLee_Harvey # Monday, August 8, 2011 3:35:18 AM
Originally posted by Meindert:
Great tip, btw! Thanks.