Why does my VBA script not continue after debugging? - debugging

Whenever I hit an error with my script, the focus turns to the VBA code and the offending line. I fix it, and hit save. Then I notice that the script is no longer running, even after I make sure that it's not paused.
For example, right now I'm using a Form_Timer() event to do some testing (interval set to 1000ms). To test the script again, I just set it to a minute in the future (e.g. if the current time is 8:54:00 AM I set it to fire at 8:55:00 AM). But this stops working after an error. Does anyone know why this is? I don't want to have to tell my users to close and re-open their copies of the Access DB just to make the script work again.
Code:
Private Sub Form_Timer()
On Error GoTo ErrorHandler
current_date_time = Now
If current_date_time = #6/28/2016 8:52:00 AM# Then
MsgBox ("the current_date_time variable holds: " & current_date_time)
'Declare objects
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim qdf As DAO.QueryDef
Dim oApp As Outlook.Application
Dim oMail As Outlook.MailItem
Dim mail_body As String
'Set objects
Set dbs = CurrentDb
Set qdf = dbs.QueryDefs("qry_BMBFLoc")
Set rst = qdf.OpenRecordset
Set oApp = New Outlook.Application
Set oMail = oApp.CreateItem(olMailItem)
mail_body = "The following jobs do not have the special BF location set in Job Orders: " & vbCrLf
If Not (rst.EOF And rst.BOF) Then
rst.MoveFirst
Do Until rst.EOF = True
mail_body = mail_body & rst!job & "-" & rst!suffix & vbCrLf
rst.MoveNext
Loop
'Email contents
oMail.Body = mail_body
oMail.Subject = "Blah"
oMail.To = "someone#something.com"
oMail.Send
'Close stuff
rst.Close
dbs.Close
Set rst = Nothing
Set oMail = Nothing
Set oApp = Nothing
End If
End If
Exit Sub
ErrorHandler:
Dim msg As String
If Err.Number <> 0 Then
msg = "email Form Timer Error #" & Str(Err.Number) & " error Line: " & Erl & Chr(13) & Err.Description
MsgBox msg, , "Error", Err.HelpFile, Err.HelpContext
End If
Exit Sub
End Sub

In order to reactivate the code, you could close the form when the error is triggered. The user would then have to reload the form to complete the action.
However, without any intervention the error is likely to occur again.
Edit: Or you could write a Function to automatically close, and re-open the offending form. Calling it in the on error command.

When there is an error in access form, the timer will stop working, you don't need to close and reopen the whole database, only the form to start the timer again. Otherwise you can add a button called "refresh" and bind macro to it which will turn the timer on again.

Yeah this sucks. I am writing a vba script for outlook and so the only way to debug is to close and reopen outlook after every error.

Related

Continuously looping VBScript needs to be more robust to errors

I'm writing a program to open and run multiple VBA modules on a server to upload new data to the cloud. I need to run the modules every 5 minutes at least (preferably every 2 minutes) without stopping 24/7 for an extended period of time. So far, my code will work for 1000-4000 loops, but fails every 1-4 days. How can I make my program more robust?
I'm pretty new to VBScripts, but have pieced together some code that works pretty well from other examples I've found through my research. I've added some error handling (On Error Resume Next and an error check after each major operation) but there are still occasional errors that sneak through and stop the program.
The errors I've been seeing include "unknown VBA runtime error," permission errors (when opening the local log), and server errors. I've tried to mitigate or ignore these with pings and error handling, but some still manage to stop the code from looping.
I've also tried using the Windows Task Scheduler, but that can only go at most every 5 minutes and I couldn't get it to run reliably.
On Error Resume Next
Set WshShell = CreateObject("WScript.Shell")
Dim objFSO
Dim myLog
Dim myLocalLog
Dim pingResultV
Dim pingResultN
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objExcel = CreateObject("Excel.Application")
objExcel.DisplayAlerts = False
objExcel.Visible = True
Set myLog = objFSO.OpenTextFile("location of log on server", 8)
Set myLocalLog = objFSO.OpenTextFile("location of log on local hard drive", 8)
Dim i
i = 0
Do While i < 1000000
'Ping the server to check if the connection is open
PINGFlagV = Not CBool(WshShell.run("ping -n 1 server",0,True))
pingResultV = "Null"
'if the ping was successful, open each file in sequence and run the VBA modules
If PINGFlagV = True Then
timeStamp = Now()
Set Wb = objExcel.WorkBooks.Open("excel file on server",,True)
objExcel.Application.Run "VBA module to copy data to cloud"
objExcel.Application.Quit
If Err.Number <> 0 Then
myLocalLog.WriteLine(timeStamp & " Error in running VBA Script: " & Err.Description)
Err.Clear
End If
Set Wb2 = objExcel.WorkBooks.Open("second excel file on server",,True)
objExcel.Application.Run "VBA module to copy this data to cloud"
objExcel.Application.Quit
If Err.Number <> 0 Then
myLocalLog.WriteLine(timeStamp & " Error in running VBA Script 2: " & Err.Description)
Err.Clear
End If
'Write to the server log that it succeeded or failed
pingResultV = "Server Ping Success"
myLog.WriteLine(timeStamp & " i=" & i & " --- " & pingResultV)
Else
pingResultV = "Server Ping Failed"
timeStamp = Now()
End If
'Write to a local log whether the server was available or not
myLocalLog.WriteLine(timeStamp & " i=" & i & " --- " & pingResultV)
If Err.Number <> 0 Then
myLog.WriteLine(timeStamp & " Error in writing to local log: " & Err.Description)
Err.Clear
End If
'Time between iterations in milliseconds. 120000 ms = 2 minutes
WScript.Sleep(30000)
i = i + 1
Loop
I'm not asking for someone to overhaul my code, but I would appreciate any suggestions on improvements. I'm also open to methods other than VBScript if they're more robust.
Some additional background on the system that might be helpful: I have 2 computers that upload data to a server every few minutes, but these computers can't be connected to the internet for security reasons. I need to upload the data these computers generate to an internet database every few minutes so it can be accessed remotely. The code I wrote runs on a separate computer that's connected to the server and internet, and it periodically opens the excel files on the server in read-only mode and runs a VBA script that uploads new data lines to the cloud.
It's hard to tell whether your VBScript or your VBA module or both fail. From your error descriptions, I somewhat expect the latter to be the case. So I'd also suggest to check your VBA coda (again). As for this script, one thing I'd suggest to get rid off the permission / log file error would be to move the logging to its own method, e.g.
' *** VBScript (FSO) ***
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
' Log file
Const LOG_FILE = "C:\MyData\MyLog.txt"
Sub WriteLog (ByVal sLogFile, ByVal sText)
Dim oFso
Dim oLogFile
Set oFso = CreateObject("Scripting.FileSystemObject")
Set oLogFile = oFso.OpenTextFile(sLogFile, ForAppending, True)
' Prefix log message with current date/time
sText = Now & " " & sText
oLogFile.WriteLine sText
oLogFile.Close
' Free resources
Set oLogFile = Nothing
Set oFso = Nothing
End Sub
And call it like
If Err.Number <> 0 Then
WriteLog LOG_FILE, "Error in writing to local log: " & CStr(Err.Number) & ", " & Err.Description
Err.Clear
End If
I also suggest to release all resources at the end of the loop, e.g. the Excel object, for which you need to move the object creation into the loop:
Dim i
i = 0
Do While i < 1000000
' Start of loop
Set objExcel = CreateObject("Excel.Application")
objExcel.DisplayAlerts = False
objExcel.Visible = True
' Do your stuff ...
' End of Loop -> free resources
Set Wb = Nothing
Set Wb2 = Nothing
objExcel.WorkBooks.Close
Set objExcel = Nothing
' Time between iterations in milliseconds. 120000 ms = 2 minutes
WScript.Sleep(30000)
i = i + 1
Loop
And as I'm not an Excel expert, I wonder if objExcel.Visible = True does make sense here or if it would better be set to False?

Scriptcontrol object returns recordset object

I am creating a library using vbscript and using scriptcontrol object to call these routines from vba. I am trying to create a recordset as a routine with the given sql as parameter as below
sub GetData(sql, byref retrst)
stADO = "Provider=SQLOLEDB.1;Persist Security Info=False;Initial
Catalog=CCMSProd;Data Source=sv-hfi-ccms;UID=vc;PWD=dw;"
cnconn as adoconnection
Set cnconn = CreateObject("ADODB.Connection")
cnconn.Open stADO
msgbox cnconn.ConnectionString
msgbox cnconn.state
With cnconn
.CommandTimeout = 0
Set retrst = .Execute(sql)
msgbox retrst.recordcount
End With
cnconn.close
end sub
and calling this routine as below
scr.Run "GetConnection", tempload, rst
but my recordset returns as nothing, eventhough the connection and recordset created within vbscript.
I need help in this.
There are quite a few problems with your script. I'm not going to go in to each one but be aware that the recordset open call has more parameters. They aren't required but the defaults are adForwardOnly for the cursor type, and adLockReadOnly for locking. Using the defaults will not enable you to get a record count from the recordset so the code I provide just shows the value of the first record in the recordset to indicate data was retrieved.
https://support.microsoft.com/en-us/help/272067/how-to-get-a-record-count-from-a-sql-server-ce-recordset
The code below works but you'll need to modify it for your usage.
Batch file to run the script:
GetRecordset.bat
cscript.exe /nologo GetRecordset.vbs
pause
Script to run:
GetRecordset.vbs
Dim objADORecordset
Dim strDBTableName
On Error Resume Next
'*****Modify below to a table name in your database*****
strDBTableName = "SomeDBTableName"
Set objADORecordset = CreateObject("ADODB.Recordset")
If Err.Number = 0 Then
GetRecordset "SELECT * FROM " & strDBTableName, objADORecordset
With objADORecordset
If Not .BOF Then
.MoveFirst
WScript.Echo "Field 0: " & .Fields(0).Value
Else
WScript.Echo "No records in recordset"
End If
End With
Else
WScript.Echo "Unable to create ADO Recordset"
End If
set objADORecordset = Nothing
WScript.Quit
'*********************************************
Sub GetRecordset(astrSQLQuery, aobjRecordset)
Dim strOLEConnect
Dim strUserID
Dim strUserPW
Dim objADOConnection
On Error Resume Next
WScript.Echo "SQLQuery: " & astrSQLQuery
strOLEConnect = "Provider=SQLOLEDB.1;Persist Security Info=False;Initial Catalog=CCMSProd;Data Source=sv-hfi-ccms;UID=vc;PWD=dw;"
strUserID = "vc"
strUserPW = "dw"
Set objADOConnection = CreateObject("ADODB.Connection")
If Err.Number = 0 Then
objADOConnection.Open strOLEConnect, strUserID, strUserPW
If Err.Number = 0 Then
aobjRecordset.Open astrSQLQuery, objADOConnection
If Err.Number <> 0 Then
WScript.Echo "Recordset open failed ERROR=" & Err.Number
End If
Else
WScript.Echo "Connection open failed ERROR=" & Err.Number
End If
Else
WScript.Echo "ADO connection failed ERROR=" & Err.Number
End If
Set objADOConnection = Nothing
End Sub
I left it similar to your code so you can understand it but it should really create and open the ADO connection before the ADORecordset. You would typically open the connection, run all of the required queries and process them before closing the connection. There's no sense in creating a new connection to the same database multiple times for different queries to the same database. It just adds unnecessary overhead. I'll leave it to you to improve the code for your use.

How to fix Wscript.CreateObject returning empty when prefix is specified?

I'm trying to process event handlers for ADODB.Command object. When I specify a prefix, Wscript.CreateObject exits the function without issuing any error message. When the prefix is removed from Wscript.CreateObject, the ADODB.Command object is created.
Additionally when setting On Error Resume Next, Wscript.CreateObject returns and empty object.
Can someone please tell me what I'm doing wrong ?
Code:
DIM tsqlcmd
On Error Goto 0
SET tsqlcmd = WScript.CreateObject("ADODB.Command", "ehdlr_tsqlcmd_")
.....
Private Sub ehdlr_tsqlcmd_ExecuteComplete( RecordsAffected, pError, adStatus, pCommand, pRecordset, pConnection)
' place any code you desire here, for example
If adStatus = STATUS_adStatusOK Then
MsgBox( "tsqlcmd_ExecuteComplete Records affected = " & RecordAffected)
Else
MsgBox( "tsqlcmd_ExecuteComplete ExecuteComplete Status = " & adStatus)
End If
End Sub

Access VBA to Close a Chrome window opened via Shell

I am attempting to close a shell Chrome window via a VBA function. My function runs a URL query that returns a .csv file. The thing is I would like to close the window so that it is not always showing (This process runs every 3 minutes). I haven't been able to find a solution that I can get to work as of yet. I tried adding SendKeys "%{F4}" after as one site suggested. This merely minimizes the window, not close it. I also attempted to try adding DoCmd.Close Shell, "Untitled" after, yet this also did not work. I have spent several hours attempting to do, what I imagine is a simple task, and felt another set of eyes could point me in the right direction. Below is my code that opens Chrome. Any assistance is greatly appreciated.
Public Function RunYahooAPI()
Dim chromePath As String
chromePath = """C:\Program Files\Google\Chrome\Application\chrome.exe"""
Shell (chromePath & " -url http://download.finance.yahoo.com/d/quotes.csv?s=CVX%2CXOM%2CHP%2CSLB%2CPBA%2CATR%2CECL%2CNVZMY%2CMON&f=nsl1op&e=.csv")
End Function
this VBA code will launch (as in your question) chrome, save the Process handle in the variable pHandle, loop all processes with this Handle and then stop the process (after checking user and domain of the process owner) .
Sub LaunchandStopProcess()
'
' As in your Question
'
Dim chromePath As String
Dim pHandle As Variant
chromePath = "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"
'
' Note: Shell pass the Process Handle to the PID variable
'
PHandle = Shell(chromePath & " -url http://download.finance.yahoo.com/d/quotes.csv?s=CVX%2CXOM%2CHP%2CSLB%2CPBA%2CATR%2CECL%2CNVZMY%2CMON&f=nsl1op&e=.csv")
Dim objWMIcimv2 As Object
Dim objProcess As Object
Dim objList As Object
Dim ProcToTerminate As String
Dim intError As Integer
Set objWMIcimv2 = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\\.\root\cimv2")
Set objList = objWMIcimv2.ExecQuery("select * from win32_process where Handle='" & CStr(pHandle) & "'")
'
' ObjList contains the list of all process matching the Handle (normally your chrome App, if running)
'
If objList.Count = 0 Then
' No matching Process
' Set all objects to nothing
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
Exit Sub
Else
'
' Parse all matching Processes
'
For Each objProcess In objList
' additionally check with actual user
colProperties = objProcess.getowner(strNameofUser, strUserdomain)
If strUserdomain + "\" + strNameofUser = Environ$("userdomain") + "\" + Environ$("username") Then
intError = objProcess.Terminate
If intError <> 0 Then
'
' Trap Error or do nothing if code run unattended
'
Else
' Confirm that process is killed or nothing if code run unattended
End If
End If
Next
Set objWMIcimv2 = Nothing
Set objList = Nothing
Set objProcess = Nothing
End If
End Sub

Opening a user form in Powerpoint via VBScript

I am trying to open a user form which I have created in a PPTM file via VBScript. Code for VB script is as below. This does seem to be working. It is simply opening that macro PPTM and closing it. Any suggestions?
Option Explicit
Dim pptApp, pptPresentation, CurrentDirectory
dim fso: set fso = CreateObject("Scripting.FileSystemObject")
CurrentDirectory = fso.GetAbsolutePathName(".")
Set pptApp = CreateObject("PowerPoint.Application")
Set pptPresentation = pptApp.Presentations.Open(CurrentDirectory + "\Revison Macro V1.pptm",True)
On Error Resume Next
pptApp.Run "Revision"
If Err Then
End If
pptPresentation.Close
pptApp.Quit
Set pptPresentation = Nothing
Set pptApp = Nothing
WScript.Quit
A Few code revisions
Set pptPresentation = pptApp.Presentations.Open(CurrentDirectory + "\Revison Macro V1.pptm",True) >> VBScript uses "&" rather than "+" even though this worked fine, it's better to stick to the correct string handling.
The userform needs to be indirectly called to pause the vbscript. So create a separate Sub and call it "Call_Revision". The code will be simple and straightforward as follows:
Sub Call_Revision
Revision.Show
End Sub
When you execute the .Run command, it needs to know how to find the code to run the UserForm. So now that we have established our sub, we can use that to show the form.
From: pptApp.Run "Revision"
To: pptApp.Run "Revison Macro V1.pptm!Module1.Call_Revision"
If you are waiting for the user to close out the userform to execute the rest of the code and exit the PPTM file, you can apply the following OnClose event within the Userform:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
Application.Quit
End Sub
And the Full Code:
Option Explicit
Dim currppt : currppt = "Revison Macro V1.pptm"
Dim ModuleName: ModuleName = "Module1"
Dim OpenUF : OpenUF = "Call_Revision"
Dim pptApp, pptPresentation, CurrentDirectory
dim fso: set fso = CreateObject("Scripting.FileSystemObject")
CurrentDirectory = fso.GetAbsolutePathName(".")
Set pptApp = CreateObject("PowerPoint.Application")
Set pptPresentation = pptApp.Presentations.Open(CurrentDirectory & "\" & currppt,True)
On Error Resume Next
pptApp.Run currppt & "!" & ModuleName & "." & OpenUF
msgbox "Done"
pptPresentation.Close
pptApp.Quit
Set pptPresentation = Nothing
Set pptApp = Nothing

Resources