EDIT : Having tentatively (see caveats) proposed this, I am rapidly beginning to think that it is a very bad idea, but I leave it here for posterity. The most compelling reason to not use this comes from Eric Lippert at Microsoft, who worked on the design & implementation of VBScript. He states, in answer to another question: VBScript does not make any guarantee that terminators always run. This can mean that this sometimes does not return a non-0 exit code in the case of an unhandled error.
I think I personally will use a 'wrapper batch file that subtracts 1 from the cscript exit code' solution in future.
I like the solution linked to by fmunkert, but I think it requires you to put your code in a particular Class_Initalize, which is clumsy at best. I've devised a related solution that does not require this; you simply "Commit" a successful result at the end of your code; if it's not called, any exception causes the ExitCodeHandler's Class_Terminate instance to set a non-zero exit code.
Option Explicit
Class ExitCodeHandler
private exit_code
Public Sub Commit()
exit_code = 0
End Sub
Private Sub Class_Initialize()
exit_code = -1 ' this exit code will be returned if Commit is never called
End Sub
Private Sub Class_Terminate()
if exit_code<>0 then WScript.Quit(exit_code)
End Sub
Public Sub Quit(exitCode)
Commit
WScript.Quit(exitCode) ' exit code will be respected since we have committed
End Sub
End Class
' create one of these at the start:
Dim ech: Set ech = New ExitCodeHandler
WSCript.StdOut.WriteLine "Hello"
s = "" ' undeclared variable causes runtime error - comment out to see success.
' WScript.Quit(-4) ' before a commit, -1 is returned due to the Class_Terminate
' Commit at the end
ech.Commit
' WScript.Quit(-5) ' after a commit, -5 is returned
Note that this idiom is used heavily in C++, where it is called RAII (Resource Acquisition Is Initialization)
You could of course embellish the class this to support other exit codes, error messages etc. You may want to put this in a common vbs file and use a mechanism for includes in vbscript to share it.
Caveats
I don't know the full details of downsides to calling WScript.Quit during stack unwinding due to an exeption in VBScript. I've disovered the following:
- Use with caution. I have come up with this and poked around with it when I saw fmunkert's linked suggestion, not used it extensively.
- If you explicitly call WScript.Quit(n), the ExitCodeHandler will replace n with its own exit code. The workaround is to either always call ExitCodeHandler.Commit before calling
WScript.Quit
, or call the supplied ExitCodeHandler.Quit
instead which does it for you. However, relying on either of these methods may not always be practical/possible, and it is fairly non-idiomatic and may not be ovbious to maintainers.
- If any other object with a
Class_Terminate
is terminated (i.e. after ExitCodeHandler's Class_Terminate
calls WScript.Quit), you seem to get an error. You may get similar behaviour with any COM objects that are being destroyed. I don't know in what order VBScript destroys objects (or even if it's guaranteed), so I've asked about it in another question.