Understanding ON ERROR in VBScript - vbscript

I am trying to modify a vbscript and convert it to Powershell as instructed. I have a block of code on my function SearchAD with On Error.
on error resume next
Set objRS = command.execute
SearchAD = objRS.RecordCount
on error goto 0
My question would be is what part of the code can trigger RESUME Next and what part is for GOTO 0.

In VBScript there are two error states (three in other VBs).
On Error Goto 0
vbscript handles errors. Your program crashes on errors.
On Error Resume Next
VBScript sets the err object but doesn't raise an error. You are required to put after every line that may raise an error
If err.number <> 0 then
FreakoutAndFixTheError
err.clear
wscript.quit 'if you can't fix
End If
In VB6 and VBA there is also
On Error Goto LineNumber (or a label)

Simply put, On Error Resume Next disables error reporting. And On Error GoTo 0 restores it. It's often used when a programmer expects an error to occur but doesn't want that error to go unhandled and halt their script. For example:
' This statement will cause VBScript to throw an error and halt.
i = 1 / 0
' Instead, let us handle the error and decide if its important...
On Error Resume Next ' Turn off error reporting
i = 1 / 0
' Now, we can test the Err object to see if any errors were thrown...
If Err.Number = 0 Then
' Success. No error occurred.
ElseIf Err.Number = 11 Then
' Error #11 is 'Division by zero'. Do we want to allow it?
End If
' We're through the risky section. Restore error reporting.
On Error GoTo 0
Now time for a soap box rant. Don't use it. There are almost always better ways than using On Error Resume Next. Assert variable values. Check your array bounds before trying to access array elements. Do QA testing. Validate user input. Be a good programmer and cover all your angles. Don't just close your eyes to any errors and assume everything is going to work. It's abused all too often by VB beginners and, unfortunately, even some of the experts! (end rant)

Here is the VB6 (more depth than vbscript's) help on it. VBA is the core language of VB6.
Visual Basic for Applications Reference
On Error Statement
Enables an error-handling routine and specifies the location of the routine within a procedure; can also be used to disable an error-handling routine.
Syntax
On Error GoTo line
On Error Resume Next
On Error GoTo 0
The On Error statement syntax can have any of the following forms:
Statement Description
On Error GoTo line
Enables the error-handling routine that starts at line specified in the required line argument. The line argument is any line label or line number. If a run-time error occurs, control branches to line, making the error handler active. The specified line must be in the same procedure as the On Error statement; otherwise, a compile-time error occurs.
On Error Resume Next
Specifies that when a run-time error occurs, control goes to the statement immediately following the statement where the error occurred where execution continues. Use this form rather than On Error GoTo when accessing objects.
On Error GoTo 0
Disables any enabled error handler in the current procedure.
Remarks
If you don't use an On Error statement, any run-time error that occurs is fatal; that is, an error message is displayed and execution stops.
An "enabled" error handler is one that is turned on by an On Error statement; an "active" error handler is an enabled handler that is in the process of handling an error. If an error occurs while an error handler is active (between the occurrence of the error and a Resume, Exit Sub, Exit Function, or Exit Property statement), the current procedure's error handler can't handle the error. Control returns to the calling procedure. If the calling procedure has an enabled error handler, it is activated to handle the error. If the calling procedure's error handler is also active, control passes back through previous calling procedures until an enabled, but inactive, error handler is found. If no inactive, enabled error handler is found, the error is fatal at the point at which it actually occurred. Each time the error handler passes control back to a calling procedure, that procedure becomes the current procedure. Once an error is handled by an error handler in any procedure, execution resumes in the current procedure at the point designated by the Resume statement.
Note An error-handling routine is not a Sub procedure or Function procedure. It is a section of code marked by a line label or line number.
Error-handling routines rely on the value in the Number property of the Err object to determine the cause of the error. The error-handling routine should test or save relevant property values in the Err object before any other error can occur or before a procedure that might cause an error is called. The property values in the Err object reflect only the most recent error. The error message associated with Err.Number is contained in Err.Description.
On Error Resume Next causes execution to continue with the statement immediately following the statement that caused the run-time error, or with the statement immediately following the most recent call out of the procedure containing the On Error Resume Next statement. This statement allows execution to continue despite a run-time error. You can place the error-handling routine where the error would occur, rather than transferring control to another location within the procedure. An On Error Resume Next statement becomes inactive when another procedure is called, so you should execute an On Error Resume Next statement in each called routine if you want inline error handling within that routine.
Note The On Error Resume Next construct may be preferable to On Error GoTo when handling errors generated during access to other objects. Checking Err after each interaction with an object removes ambiguity about which object was accessed by the code. You can be sure which object placed the error code in Err.Number, as well as which object originally generated the error (the object specified in Err.Source).
On Error GoTo 0 disables error handling in the current procedure. It doesn't specify line 0 as the start of the error-handling code, even if the procedure contains a line numbered 0. Without an On Error GoTo 0 statement, an error handler is automatically disabled when a procedure is exited.
To prevent error-handling code from running when no error has occurred, place an Exit Sub, Exit Function, or Exit Property statement immediately before the error-handling routine, as in the following fragment:
Sub InitializeMatrix(Var1, Var2, Var3, Var4)
On Error GoTo ErrorHandler
. . .
Exit Sub
ErrorHandler:
. . .
Resume Next
End Sub
Here, the error-handling code follows the Exit Sub statement and precedes the End Sub statement to separate it from the procedure flow. Error-handling code can be placed anywhere in a procedure.
Untrapped errors in objects are returned to the controlling application when the object is running as an executable file. Within the development environment, untrapped errors are only returned to the controlling application if the proper options are set. See your host application's documentation for a description of which options should be set during debugging, how to set them, and whether the host can create classes.
If you create an object that accesses other objects, you should try to handle errors passed back from them unhandled. If you cannot handle such errors, map the error code in Err.Number to one of your own errors, and then pass them back to the caller of your object. You should specify your error by adding your error code to the vbObjectError constant. For example, if your error code is 1052, assign it as follows:
Err.Number = vbObjectError + 1052
Note System errors during calls to Windows dynamic-link libraries (DLL) do not raise exceptions and cannot be trapped with Visual Basic error trapping. When calling DLL functions, you should check each return value for success or failure (according to the API specifications), and in the event of a failure, check the value in the Err object's LastDLLError property.
Send feedback to MSDN.Look here for MSDN Online resources.

Related

VBScript alternative of PHP's debug_backtrace

I have an old VBScript project I need to debug and due to it's complexity I cannot see where are some functions being called from. Is there any VBScript alternative to PHP's debug_backtrace function or similar to see the stack trace?
So at this point, after a lot of searching, I am going to answer my own question as follows: VBScript is relatively old language and does not seem to have any built-in function/method or other means which would reliably return stack trace of function call (similar to PHP's debug_backtrace() function.
I managed to track the execution path with a simple, custom Function logBreakPointToFile() which I wrote and I placed throughout 100s of .asp files (everywhere where the method in question was referenced directly and indirectly - nested). This gave me a very messy idea what was actually happening. However is cost me a lot of prep. time and clean-up time which I was trying to avoid in the first place.
If anyone finds a better solution, please share.
Thx
VBScript does not throw any exception with stacktrace information in case of error.
#Runtime, in case of error, VBScript throws an Err object.
Ex: Below script will throw 'Division by zero' error & script will not get executed further.
Msgbox 5/0
Msgbox "Hello" 'This will not execute
On Error:
VBScript uses 'On Error Resume Next' statement which starts the error handler. It means, in case of error just skip current line and goto the next line. 'On Error GoTo 0' disables the current error handler. (like you are closing an 'if' block). Please check MSDN for more details.
Consider this example.
On Error Resume Next
Msgbox 5/0
Msgbox "Hello" 'This will say "Hello"
Msgbox Err.Number '11
Msgbox err.Description 'division by zero
On Error GoTo 0 'stops the error handler. any run time error after this will stop the program
Err.Number will be 0 in case no error is found.
So, You can have function like
Function Divide(ByVal A, ByVal B)
On Error Resume Next
Dim Result
Result = A/B
If Err.Number = 0 Then
Divide = Result
Else
Divide = "Infinite"
End If
On Error GoTo 0
End function
Remember this - as it simply goes to the next line in case of error,
On Error Resume Next
If 5/0 Then
Msgbox "???" 'This will get executed. It does not go to Else
Else
Msgbox "Hello" 'It will not show this
End If
On Error GoTo 0
You need to configure IIS to send errors to the browser. Its under Classic ASP settings on IIS 7 - 8. You have the option to send errors to browser on local requests, or all requests. For testing on a dev box usually its set for local. It will tell you what line number you are erroring out on.
As for getting more detail on what functions are slowing your application down. With classic asp you need to write your own timing code to measure such metrics.

Retry on Runtime Errors

I have come across this problem a few times and never been able to resolve it but now I need to solve it once and for all.
I have a procedure which has been throwing runtime errors. This is not a problem as I have an error handler defined at the top of the function and the handler at the bottom something like this:
retryConcat:
On Local Error GoTo concatErr
'Some Code here
Exit Sub
concatErr:
If MsgBox("Could not append the receipt for this transaction to the Receipt viewer logs.", vbExclamation + vbRetryCancel, "Warning - Missing Receipt") = vbRetry Then
err.Clear
GoTo retryConcat
End If
The error handler contains a message box allowing the user to retry if required. Now here is the part which confuses me. The first time an error is thrown it shows the message box and allows the user to retry as expected. The program then jumps to the appropriate line and tries again. However the second time through when the error is thrown it does not jump to the error handler, it jumps out of the procedure and the error handler in the parent catches it instead!
So my question is why does it jump to the parent error handler on subsequent throws. This happens in many places all over my code. In many cases where I can manually check for errors I can stick the code in a while loop to solve it but with runtime errors I am forced to use the error trapping which acts in this rather annoying way.
Any help or advice would be appreciated.
You need ot use Resume retryConcat.
When an error occurs, it jumps into the error handle to concatErr:. You then show the message box, and if the user chooses to retry, the code then jumps to retryConcat. As this you used Goto, it DOES NOT exit the error handler, and so next time the error occurs, it's already in the error handler and has no choice but to raise the error up the chain to the calling procedure.
Using Resume concatRetry allows it to exit the error handler and resume at the required point, meaning next time the error occurs, it can handle is again.
It probably makes it easier to understand, if you imagine the error handler is a state, not a section of code.

VB6: does "On Error Goto 0" affect the error handler for the calling function?

If On Error Goto 0 is called in a VB6 Sub, will this switch off error handling even when control goes back to the calling Function/Sub?
EDIT: If I have output in an error-handler block that is showing an error number of 0, what does that indicate?
No. The VB6 manual makes it clear that On Error Goto 0 only affects the current procedure:
On Error Goto 0 disables any enabled
error handler in the current
procedure.
EDIT There's now an addition to the question, which wasn't there when I posted this answer. The question is "If I have output in an error-handler block that is showing an error number of 0, what does that indicate?". For the answer, see Mike's answer.
No.
http://www.vb-helper.com/tut6.htm
When a program encounters an error, Visual Basic
checks to see if an error handler is presently installed
in the current routine. If so, control passes to that error handler.
If no error handler is in effect, Visual Basic moves
up the call stack to the calling routine to see if
an error handler is currently installed there. If so,
the system resumes execution at that error handler.
If no error handler is installed in the calling routine
either, Visual Basic continues moving up the call stack
until it finds a routine with an error handler installed.
If it runs off the top of the stack before it finds an
active error handler, the program crashes.
If I have output in an error-handler block that is showing an error number of 0, what does that indicate?
This means the Err object did not contain error information at the point in the code where you checked the Err.Number property. This can happen for a number of different reasons:
The Err object has been explicitly cleared by a previous call to Err.Clear
The Err object was cleared by calling On Error Goto. An On Error Goto statement will clear the current Err object
The Err object was cleared by a Resume X statement. Unlike a normal Goto X statement, a Resume will clear the current Err object (and raise an error of its own if the Err object was already empty)
You forgot to exit the current Sub/Function/Property prior to reaching the error handler, for example:
Public Sub SampleRoutine
On Error Goto ErrorHandler
DoSomething
DoSomethingElse
' You need an Exit Sub here so that the code does not reach the error handler'
'in the event no error occurs.'
ErrorHandler:
MsgBox Err.Number & "-" & Err.Description
End Sub
This is a pretty common mistake in my experience. If you don't explicitly exit the routine before reaching the error handler label, the code in the error handler will still run even when no errors occur. In this case, Err.Number will be 0 since no error occurred.

How do you handle errors in error handlers in VB6?

I frequently encounter this situation in my VB6 applications
Private Sub DoSomething
On Error Goto err1
Call ProcessLargeBatch1
Call ProcessLargeBatch2
'... more ...'
Exit Sub
err1:
Call Cleanup 'Specific for DoSomething'
Call HandleError 'General error handling: Logging, message box, ...'
End Sub
The Cleanup procedure sometimes reverts actions, rolls back a transaction, deletes temporary files, and so on. In most cases this operation can also fail.
What do I do in this case? I'd add an On Error Resume Next into the error handler but that deletes the existing Err object. Adding an error handler to Cleanup has the same problem.
What is the best way to ensure that the original errors still gets processed/logged?
EDIT: One additional problem is that I also want to notify the user of the error. Sometimes it is important, that the cleanup happens fast and I don't want the message box block the application for a long time and do the cleanup after the user acknowledges the error.
Firstly, read all the information out of the Err object that you will need, i.e. number, description, etc., then clear the error and do what you want.
Change the way you inform the user to use the values you have cached, and not to use the Err object itself.
From your example you are doing the cleanup properly. Your HandleError should only log the error not do any UI. The UI is handled up at the form level.
What you need to when an error occurs is
Clean Up
Log the Error
Raise the Error Again via Err.Raise
This will work it's way up the call stack to the event that called the original code. Then the sequence will become
Clean Up
Log the Error
Display the the Error Notification Dialog
Note that your Error Logging can be intelligent in that subsequent logs of the same error can just add to the recorded call stack.
You want to make sure that EVERY event has a error handler. Not every procedures needs one but definitely every event. Unhandled errors in a event will cause a VB6 application to shut down unexpectedly.
If I can handle all errors in one place, I'll usually put in into a structure something like this:
Public Sub SubThatShouldHandleErrors()
Const ROUTINE_NAME = "SubThatShouldHandleErrors"
On Error Goto Catch
' "normal" processing here...
Finally:
' non-error case falls through to here
' perform clean-up that must happen even when an error occurred
On Error Goto 0 ' reset: not really needed any more, but it makes me feel more comfortable
Exit Sub
Catch:
' Error handling here, I may have logging that uses ROUTINE_NAME
Resume Finally
End Sub
If I need more than one error-handler, I'll try very hard to restructure my code to make that not be the case but if absolutely necessary I'll write a custom handler; my template is only a guideline.
Log your error first. Then do an On Error Resume Next. Have your cleanup encapsulated in methods that have their own error handling. This should be yourbest bet.
I really really don't like error handlers. This is what I do;
Create a Error class or module that contains all properties that the built in one contains, as well as a CopyError-method that fills these properties from the err-object.
Watch for errors where they can appear:
.
' lots of code that will probably work
On Error Resume Next
Open "c:\filethatdoesntexist.txt" For Input As #1
Error.CopyError
On Error Goto 0
Select Case Error.Number
Case 53'File doesn't exist
' handle that error here
Case 0
' no error
Case Else
' Just throw the error on
Err.Raise Error.Number, Error.Description, ...
End Select
' more code that will probably work

How to re-enable the default error handling in VB6

I have some code with various "On Error Goto" error handlers in a few places to handle some broken third party hardware. I was getting an overflow error (read from the Err variable) in a routine that doesn't have an error trap but is called by a routine that does. I always thought error traps were only valid in the routine they were declared, but it looks like an error in a subroutine can cause it to go to the calling function's error trap.
So I turned off the calling function's error trap and found my overflow and all is well. But before I did that, I spent some time trying to find a programatic way to get VB to return to its default error handling inside that routine (so I wouldn't have to modify outside code to debug), but I couldn't. The only error commands I could find:
On Error GoTo [label]
On Error Resume Next
On Error Goto 0
On Error GoTo -1
all turn on the manual error handling - is there a way to turn it off (back to the VB6 default)?
This is explained thoroughly in the VB6 manual under Error Handling Hierarchy. On Error Goto 0 disables the error handler in the current procedure, not in the procedures that called it.
If an error occurs in a procedure and
this procedure doesn't have an enabled
error handler, Visual Basic searches
backward through the pending
procedures in the calls list — and executes the first
enabled error handler it finds. If it
doesn't encounter an enabled error
handler anywhere in the calls list, it
presents a default unexpected error
message and halts execution.
As others have said, you can go to Tools-Options-General tab and choose Break on all errors. That effectively disables all your On Error statements - the IDE will break immediately on every error.
That can be irritating if your VB6 code throws errors as part of normal operation. For instance when you check whether a file exists, or when the user presses cancel in a common dialogue. You don't want the IDE to break every time on those lines. But you might have boilerplate error handlers in all your event handling procedures, to stop the program crashing out on unexpected errors. But they are a nuisance when you're debugging problems because the IDE doesn't break on the line with the error. One trick is to switch off those error handlers when running in the IDE, but keep them in the built executable. You do it like this.
Drop these functions into a module.
Public Function InIDE() As Boolean
Debug.Assert Not TestIDE(InIDE)
End Function
Private Function TestIDE(Test As Boolean) As Boolean
Test = True
End Function
Then you can write your error handlers like this.
Private Sub Form_Load()
If Not InIDE() Then On Error Goto PreventCrashes
<lots of code>
Exit Sub
PreventCrashes:
<report the error>
End Sub
Pinched from here. Another tip - use the free add-in MZTools to automatically add these boilerplate error handlers. For production-quality code, you could go further and put an error handler in every routine to create a ghetto stack trace. You might also log the errors immediately in every error handler.
EDIT: Ant has correctly pointed out that On Error Goto -1 is a VB.Net statement and isn't valid in VB6.
EDIT: Arvo and OneNerd have written answers with some interesting discussion of emulating Finally teardown blocks in VB6 error handling. The discussion in this question is also worth a look.
There's clear and simple way to reset error status - use keyword Resume. There are three possibilities:
Resume
Resume Next
Resume <Label>
Resume continues execution at errored line, Resume Next at next line and least talked Resume Label continues at label. Very useful to create try-catch-finally like constructs in VB6. Borrowed and modified from OneNerd answer:
Function MyFunction() as String
'-- start of error block
'
On Error Goto Catch
' do something here that might cause an error
MyFunction = "IT WORKED"
Goto Finally
Catch:
' error occured - do something else
MyFunction = Err.Description
Err.Clear
Resume Finally ''added to clear error status
Finally:
On Error Resume Next ''added to avoid repeated errors
' put your finally code here
'
'-- end of error block
End Function
Simple Err.Clear doesn't help, if some subsequential error occurs in Finally block; Resume Finally does reset internal error state though.
There is a handy right-click menu that lets you turn error handling on and off. Just right-click on a code window and select Toggle, then you can choose "Break on all errors". This will have the effect of disabling all your "On Error" statements.
Here is what I do:
First turn on error handling like this if necessary in your Sub Main() or Sub Form_Load() Sub:
'-- turn on error handling
'
On Error GoTo 0
'
'-------------------------
Now errors will be turned on.
Next, Use the On Error Resume Next and On Error GoTo {label} commands in combination with the Err object. Here is an example of emulating a try/catch/finally:
Function MyFunction() as String
'-- start of error block
'
On Error Goto Catch
' do something here that might cause an error
MyFunction = "IT WORKED"
Goto Finally
Catch:
' error occured - do something else
MyFunction = Err.Description
Err.Clear
Finally:
' put your finally code here
'
'-- end of error block
End Function
/Tools/Options/General/Error Handling
on error goto 0
Should be what you want... It should cause the error to the thrown, and in turn probably unwind up to the RTL...
It's been a long time, but I'm pretty sure that's what you want.
on error resume next
will just continue to the next statement, so you NEED to have plenty of
if err.Number <> 0 then
statements in your code where errors CAN occur...
Have to agree with LarryF, On Error Goto 0 should turn off explicit error-handling that has been turned on by On Error Resume Next. Functions and subroutines do have their own scope for this though. From Dr. Scripto at Microsoft:
Putting On Error Resume Next at the
beginning of the script, as we often
do, makes it apply to the entire body
of the script. But, as we'll see in
later examples, its scope does not
include functions or subroutines. If
you want to handle errors within a
function or subroutine, you must also
include On Error Resume Next in each
of them before checking the Err
object.
You can turn error-handling off with
On Error GoTo 0. So it's possible to
turn error-handling on with On Error
Resume Next just before you want to
check the Err object, and turn it off
after with On Error GoTo 0.
There is no "On Error GoTo -1" so I have no idea where you got that.
VB6 exception handling is covered very thoroughly in the manual.

Resources