A little background:
I am writing some very basic error handling for a macro that runs some pretty important company processes.
As nobody here at work is a true software engineer, the time we can devote to writing error handling routines is not significant.
Anyway, there's a routine that runs every night on a schedule and it processes several reports (in excel) that were completed throughout the day. This routine runs fine for the most part, but since it occurs in the background I have no way of knowing if something went wrong during the previous night's update unless I check on it every morning.
So the error handling I have in place at the moment is that on error - any error - it sends me an email saying "hey something went wrong" (or something to that effect). This lets me aware of any intermittent errors that pop up before the other departments see them, without having to "helicopter parent" the system, so to speak.
My question is:
I have this fantasy in my head that within the email, it would have the text of the line where the error occurred. It's not the biggest deal not to have this, but it would speed up the process of troubleshooting a little bit.
Googling this is proving a little frustrating... does anyone know if this is possible, and how I would go about doing this?
You can send yourself a line number so that you could find the offending line
The line numbers can be added easily using Mz-Tools
Sub ErrTest()
10 On Error GoTo ErrHandler
20 Range("Derp").Select
30 On Error GoTo 0
40 Exit Sub
ErrHandler:
50 Email "example#email.com", _
Subject:="Error " & Err.Number, _
Body:="Error " & Err.Number & "(" & Err.Description & ") occured in ErrTest on line " & Erl & "."
End Sub
You can retrieve a line of code if you have "Trust access to the VBA project object model" checked in the trust center.
Sub Test()
Dim LineCount As Long
LineCount = ThisWorkbook.VBProject.VBComponents("Module1").CodeModule.CountOfLines
'Print every line in Module1 (lines 1 to LineCount)
Debug.Print ThisWorkbook.VBProject.VBComponents("Module1").CodeModule.Lines(1, LineCount)
End Sub
This could be used to email yourself the specific line number. You would have to add line numbers to every line of code and use Erl in order for this to work.
Related
I've written a macro which sums numbers, grouping by year and by month based on our projection model.
It runs from start to end when stepping through with F8.
It stops immediately in standard execution with F5.
The first trouble is
Runtime error 91
in
issmIndex = Range("A1:Z1").Find("ck.IssMon").Column 'issmIndex an integer
Originally I tried Application.WorksheetFunction.Match(...) but had the same problem: runs in debug, but not in execute (Error 1004 instead).
I considered it could have been an Excel version issue (the Match function has a different name in the Italian version). I switched to a more neutral Find, but still no luck.
When you have an error with a line that is a combination of several commands, try breaking it down into the individual steps.
For example, this works:
Sub findDemo()
Const toFind = "blah"
Dim rg As Range, f As Range
Set rg = Range("A2:C5")
Set f = rg.Find(toFind)
If f Is Nothing Then
Stop 'not found
Else
Debug.Print "found in column #" & f.Column
End If
End Sub
Also see the example in the documentation for Range.Find().
Welcome to SO. Sometimes Excel reads code faster than executing, so when reading a command there is a previous one not finished. IT's weird but it happens a lot if your code does a lot of stuff and calculus.
Besides, when debugging, every command line is executed before reading next one, so you cannot detect this just debugging.
So if your code runs perfect when debugged but errors if executed as normal, try to add the command DoEvents right before the problematic line. Something like this:
' your previous code
'
'
'
Doevents
issmIndex = Range("A1:Z1").Find("ck.IssMon").Column 'issmIndex an integer
'
'rest of your code
This commands forces Excel to make sure everything has been executed before reading. It's kind of like a checkpoint, something like make sure you've done everything before going to next line.
DoEvents
function
Cleanly cancelling a long API-Ridden procedure is hellish, and I'm attempting to work out the best way to navigate the hellishness.
I'm using excel 2016 (with manual calculations and no screen updates) - I may take some time to attempt to run the procedure on 2010 to see if any issues resolve in the coming days (I'm aware of the slowdowns).
Over time, my procedure LongProcedure has lost its ability to successfully use its cancel feature (presumably due to increasing complexity). It was initially inconsistent and required significant spam-clicking to cancel, and now it fails altogether
Here's the setup:
First, LongProcedure is in a class module LongClass with a public property checked against for early cancelling, allowing it to clean up.
Public Sub LongProcedure()
' [Set up some things] '
For Each ' [Item In Some Large Collection (Est. 300 Items)] '
' [Some Code (ETA 5 Seconds) Sprinkled with 3-4 DoEvents] '
' [Export workbook (ETA 10 Seconds)] '
If (cancelLongProcedure) Then Exit For
Next
' [Clean up some things] '
GeneratorForm.Reset ' Let the UserForm know we're finished
End Sub
Second, I have a UserForm shown from a macro, which instantiates the procedure class, and runs the procedure. It contains a run button, a status label, and a cancel button.
Private MyLong As LongClass
Public Sub ButtonRunLongProcedure_Click()
Set myLong = New LongClass
myLong.LongProcedure()
End Sub
So the issue overall is twofold.
The ExportAsFixedFormat call opens a "Publishing..." progress bar which freezes excel for around ten seconds - fine. In all of my efforts, I haven't found a single way to process user input while this is happening.
On top of this, the DoEvents calls seemingly no longer do anything to allow the cancel button to be clicked. The process inconsistently freezes excel, tabs into other open programs, and (when not freezing) updates the status label.
I've Tried:
Appending DoEvents to the SetStatusLabel method instead of sprinkling - while the form still often freezes, it otherwise updates the status label consistently (while still not allowing the cancel button)
Using winAPI Sleep in place of, and in addition to DoEvents with delays of 1, 5, 10, 50, and 250ms - The form simply stopped updating at all without doevents, and with both it froze more.
Using a Do While loop to run DoEvents constantly for one second (Froze)
Overriding QueryClose to cancel the form. This one helped significantly. For some reason, the close [x] button can be clicked far more consistently than the userform buttons - Still not as consistently as I'd like. The problem? during publishing, Excel stops responding, and as such, modern windows will end the process if you click the close button twice... without cleanup.
Using Application.OnTime to regularly call DoEvents. Didn't seem to improve the situation overall
Alt-Tabbing. No, really. for some reason, while alt-tabbing occasionally just makes the UserForm freeze harder, sometimes it makes it stop freezing and update.
This is an issue I'm willing to do significant refactor work for, including smashing up the idea of the long procedure into separate methods, performing setup initially, and cleanup on class termination. I'm looking for something that provides consistent results. - I'll accept anything from excel versions to excel settings to refactors to winAPI calls.
Thanks for any insight into this one.
As it turns out simply combining together some of the useful improvements, along with a new one, made all the difference.
QueryClose is up to personal preference. Leave it in to catch more terminations, leave it out to ensure users use the new solution
Stick to sprinkling doEvents in places you feel are logical (not just when the status bar updates - like before and after an Application.Calculate call)
Optimize the long-running process as best you can, avoiding excel calls
And, most significantly
The integrated cancel key feature (CTRL+Break by default) is significantly more responsive than UserForm buttons and the form close button, without the chance of accidentally ending the excel task.
Here's the process to polish that for a finished product
First, set up a debugMode, or the inverse handleErrors, module-level variable to control whether to implement break-to-cancel and error handling. (Error handling will make your code harder to debug, so you'll appreciate the switch)
If your process is handling errors, you'll set Application.EnableCancelKey to xlErrorHandler, and On Error GoTo [ErrorHandlingLabel]. The error handling label should be directly before cleanup, and immediately set EnableCancelKey to xlDisabled to avoid bugs. Your handler should check the stored Err.Number and act accordingly, before continuing on to the cleanup steps.
Ensure that if you defer to any other complex vba in your script (such as using Application.Calculate on a sheet with UDFs), you set On Error GoTo 0 beforehand, and On Error GoTo [ErrorHandlingLabel] after, to avoid catching cellbound errors.
Unfortunately, the drawback is that for the UX to be consistently readable, you'll have to leave the cancel key on xlDisabled until the form is closed.
And in code:
Public Sub LongProcedure()
If handleErrors Then
On Error GoTo ErrorHandler
Application.EnableCancelKey = xlErrorHandler
End If
' [Set up some things] '
For Each ' [Item In Some Large Collection (Est. 300 Items)] '
' [Some Code (ETA 5 Seconds) Sprinkled with 3-4 DoEvents] '
' [Export workbook (ETA 10 Seconds)] '
Next
ErrorHandler:
If handleErrors Then
Application.EnableCancelKey = xlDisabled
If (Err.Number <> 0 And Err.Number <> 18) Then
MsgBox Err.Description, vbOKOnly, "Error " & CStr(Err.Number)
End If
Err.Clear
On Error GoTo 0
End If
' [Clean up some things] '
GeneratorForm.Reset ' Let the UserForm know we're finished
End Sub
and in the UserForm
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If MyLong.handleErrors Then Application.EnableCancelKey = xlInterrupt
End Sub
A small note that this method will likely generate a few bugs you weren't expecting to encounter because the execution jumps directly to the specified label. Your cleanup code will need to have required variables instantiated from the start.
Overall, once these issues are fixed, this setup ensures that the user can click CTRL+Break as many times as they could possibly want without causing crashes or popups.
I'm currently using VBA to automate pulling of large data-sets. However, it often happens that either
the sub simply gets stuck and does not update any more/connection breaks (application is still responsive) or
the application becomes totally unresponsive.
Is there a way to implement a time-out function which automatically closes the application after say 15 minutes elapse?
In other programming languages, I would have solved this using multi-threading. But not sure if something similar is feasible in VBA given that it is entirely single-threaded.
The basic algorithm I employed to automate the data looks like this:
Sub automateFetchData()
Dim requestTable As Workbook
Set requestTable = Workbooks.Open(requestTablePath)
Run ("'" & requestTable.Name & "'" & "!fetchData")
' This is where the application starts fetching data and occasionally gets stuck
debug.print "Data retrieval complete"
End Sub
For those familiar with Thomson Reuters Datastream, I am using request tables and the pre-programmed macro within the request table file to connect and fetch data from the datastream server.
Therefore, I unfortunately do not know/understand in detail what is happening when I run:
Run ("'" & requestTable.Name & "'" & "!fetchData")
I just know that once I call it, a progress bar appears looking like this:
Which then transitions to:
If the data retrieval is successful, the progress bar disappears, output is pasted to a worksheet and the code above reaches the
debug.print "Data retrieval complete"
Statement.
If it is not successful, it just freezes with the progress bar from the second screenshot still open and running. At this point, I would want the time-out to kick-in and exit the function which called
Run ("'" & requestTable.Name & "'" & "!fetchData")
Please excuse me for being so vague in the description of the issue. I'd be glad if someone could nonetheless help me out! Always happy to share more info where I can. Cheers!
I'm looking to disable to the BREAK (or pause) key in VBA, but I have come up against 2 walls.
How do you tell VBA that the function key is being pressed
What is the On.Key {NAME} for interrupt?
My workbook forces users to have macros enabled by setting every sheet to xlVeryHidden when the workbook is closed, and setting them to visible when opened (and various other bits centered around logging in to certain areas of the workbook), meaning the VBA has to be used. However, at certain points in the "Workbook_open" routine, if someone interrupts the program, it leaves the login access vunerable and I do not want this.
I want to be able to bind the interrupt command to any key combination I desire, so only I know what it is, leaving the break key disabled.
Thanks for your help in advance
You can do this via the command Application.EnableCancelKey = xlDisabled.
This link explains it pretty well.
Using Manu's link, I resolved the problem by using this code:
On Error GoTo errorhandler
Application.EnableCancelKey = xlErrorHandler
'***Code that you don't want interupted goes here***
Exit Sub
errorhandler:
If Err.Number = 18 Then
Call error_handler_message
Resume
Else
MsgBox "Something went wrong!"
Exit Sub
End If
in the "error_handler_message" procedure is:
Application.EnableCancelKey = xlDisabled
MsgBox "Breaking Of Code Execution Has Been Disabled", vbOKOnly, "Unable To Stop Process!"
The reason there is the xlDisabled is because without it, the user could press ctrl + break again and break that operation, if you disable it, they can't break the anti-break code too.
I have not had much experience with VBA, but I sometimes use it at work. Recently, I have encountered a problem that shouldn't happen, and that neither my boss nor myself can figure out.
Basically, the issue is that the Application property DisplayAlerts is set to True by default and can't be changed for some reason. Possibly related is that when I hit an error, it always display the End|Debug|Help alert and never hits the applied error handling.
I am running 64-bit Office 2010 on a 64-bit Windows 7 machine. However, I do not believe it to be a platform issue, as I have tested on multiple different platforms, operating systems and software permutations and no other machine has this error; just mine.
I have created some sample code in case anyone has encountered this before or has any ideas. The only thing I can think of, is that I have something installed on my machine that is causing this. But after a program purge and many restarts, I am no closer to deciphering what it might be.
Public Sub TestErrorHandler()
' Suppress alerts
Application.DisplayAlerts = False
Dim strArray(1) As String
strArray(0) = "Hello"
strArray(1) = "World"
' Set up error handler
On Error GoTo ErrHandler
For i = 0 To 3
MsgBox strArray(i)
Next
' Strip the error handler
On Error GoTo 0
' Unsuppress alerts
Application.DisplayAlerts = True
Exit Sub
ErrHandler:
MsgBox "Error: " & Err.Description
Resume Next
End Sub
The error is thrown on the third enumeration of the for-loop (as it should). The type of the error is irrelevant, what is relevant is that I get the error and never hit the error handler.
Any suggestions or help on this would be greatly appreciated.
Many thanks!
Press ALT + F11 from within Excel to reach VBA editor.
Goto Tools menu -> Options item -> General tab.
Set the error trapping to "Break on unhandled errors"
(source: microsoft.com)