views:

196

answers:

5

I have a batch file that calls a VBScript (.vbs) program. After calling it, my batch script checks errorlevel to see if the .vbs program failed. I can signal failure with an exit code in the .vbs program with WScript.Quit(1).

However, I can only do that explicitly. If some unexpected run-time error happens, the .vbs quits with an error dialog box, however the exit code is zero so my batch file thinks it suceeded! How can I change that behavior?

And if you are thinking of saying, use on error goto, don't bother... that syntax is available in regular VB, but not in VBScript.

+2  A: 

As you say, all that's available is On Error Resume Next, so your forced to use the pattern:

On Error Resume Next
 ThingWithAChanceOfThrowingAnError ...
If (Err.number <> 0) then PrintErrorAndQuitWith1(Err.Description)
Alex K.
But what about the places where you don't think there's a chance of throwing an error but it actually does ( http://en.wikipedia.org/wiki/Unknown_unknown )
JoelFan
@JoelFan - Just put it around every line, simple ;-) Oh and just hope that "Err.number" doesn't throw ;-)
bacar
+1  A: 

You might use the technique described in this article. It requires you to wrap your script inside a VBScript class.

fmunkert
I like the technique and upvoted your answer, but I still accepted my own idea, because it wins on "simplicity"
JoelFan
+1  A: 

I thought of an out-of-the-box solution... Who says 0 has to mean success? VBScript sometimes returns a 0 return code for failures, so why not embrace that? Adopt 0 as (at least one possible) failure code and make up another number (e.g. 10) as the "success code".

At the end of the script, put WScript.Quit(10). That will only be hit if everything succeeded up to that point. Then instead of "if errorlevel 1" in the calling batch file, use "if %errorlevel% == 10"

JoelFan
"Who says 0 has to mean success?"... this is a standard convention, and many tools that launch other processes will assume it. See wikipedia's [Exit status semantics](http://en.wikipedia.org/wiki/Exit_status#Semantics) for more detail.That said, VBscript is sufficiently broken in this regard that it is quite appealing to flout convention given the limited alternatives, _if_ you know what will call your script _and_ that it's OK with that. Alternatively you could wrap your script in something that, say, subtracts 1 from your exit code. As long as you didn't write said wrapper in vbscript ;^]
bacar
+1  A: 

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:

  1. 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.
  2. 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.
  3. 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.
bacar
+1  A: 

You could, if it's an option, use jscript instead which has better support for exception handling, including an easy way to return a non-zero exit code on any exception. See the solution to why does my JScript (windows script host) exit with 0 on an uncaught exception?

This is the #1 reason we're choosing jscript over vbscript (when we have to use one of the two!)

bacar