How to search through VBA code files - ms-access

I just started a job with a new company where previous developers had created many automated tasks. Of course, there is virtually no documentation and I didn't have a chance to work with the previous developer so now I'm trying to sift through all these processes looking for one that modifies some specific files.
I've scripted all the stored procs in SQL and used a search tool and didn't find what I was looking for, so now I am wondering if the process I need is located in one of many Access databases that are used. With SQL Server, it was easy to write a C# app to script the procs so I could search through them, but with Access it looks like I'm confined to opening each db individually to search through the code files.
Is there any way to programatically search through VBA code files?

If your interest is searching code modules in an Access database file, you can use the VBE object model. This sample searches for a word in all the modules of the ActiveVBProject of the current database. If the database includes more than one VBProject, you can enumerate the VBProjects collection and search the projects one at a time by name:
For Each objComponent In Application.VBE.VBProjects(ProjName).VBComponents
Or if you prefer to reference the project by number rather than name, just be aware the numbering starts with 1 rather than 0.
Public Sub findWordInModules(ByVal pSearchWord As String)
'Dim objComponent As VBComponent
' VBComponent requires reference to Microsoft Visual Basic
' for Applications Extensibility; use late binding instead:
Dim objComponent As Object
Dim strMessage As String
Dim strModuleList As String
strModuleList = vbNullString
For Each objComponent In Application.VBE.ActiveVBProject.VBComponents
If objComponent.CodeModule.Find(pSearchWord, 1, 1, -1, -1) = True Then
strModuleList = strModuleList & "; " & objComponent.Name
End If
Next objComponent
strMessage = "Text '" & pSearchWord & "' found in "
If Len(strModuleList) > 0 Then
strMessage = strMessage & "modules: " & Mid(strModuleList, 3)
Else
strMessage = strMessage & "no modules"
End If
Debug.Print strMessage
End Sub
Review the Access help topic for that Find method; you may prefer different options than I used.
If you want to target multiple db files and search the modules in each, you could automate this using the OpenDatabase method. I'll leave the details of that part up to you.

Another option, not previously mentioned - is to just print the Project Code, from the context menu, in the VBA Editor. The steps below, can be modified, to suit applications available, but the result is the same - searchable text.
From the VBA Editor, right click the project name, and click print, from the context menu that appears. Make sure "Code" is checked, and click "OK". The printer can be changed, from the "Setup..." menu, available in the last dialog.
On a side note - stand alone modules, SQL, tables, etc, are available for printing, from the "Database Documenter", in the "Analyze" group, of the "DATABASE TOOLS" tab.
Very useful, for sorting and outlining underlying SQL, of Access queries.

Best to download the free MZ-Tools for VBA and use their search/replace function.
Edit
MZ-Tools for VBA is no longer available. The paid version works with newer office installations.

Related

Printing to Dymo LabelWriter 450 from Access VBA

We've recently upgraded some PCs and have subsequently come across some issues with the VBA code in an Access frontend we use to perform operations on a MySQL database. The specific piece of code pulls a serial number from the database and then prints it to a Dymo Labelwriter 450 as a barcode.
Style = vbExclamation + vbYesNo
Title = "Print Label For " & Me.txt_Barcode & " ?"
Response = MsgBox("Do you want to print a label for serial number " & Me.txt_Barcode, Style, Title)
If Response = vbYes Then
Dim myDymo As Object
Dim myLabel As Object
'On Error Resume Next
Set myDymo = CreateObject("Dymo.DymoAddIn") '
Set myLabel = CreateObject("Dymo.DymoLabels")
If (myDymo Is Nothing) Or (myLabel Is Nothing) Then
MsgBox "Unable to create OLE objects"
Exit Sub
End If
The code now breaks at line 8, i.e.
Set myDymo = CreateObject("Dymo.DymoAddIn") '
The error code I was getting was relating to ActiveX (which seemed possible that it might relate to going from 32-bit to 64-bit), but since installing the latest DLS from Dymo (DLS8Setup.8.2.2.996), it seems to have evolved to the following:
Run-time error '-2147467261 (80004003)
Automation error
Invalid pointer
I'm hoping someone can point me to an easy fix so I don't have to totally re-write the code!
Thanks in advance!
I managed to resolve this one in a couple of steps and to be honest, I'm not sure which exactly fixed this specific part of the problem:
First was to run the DYMO_Label_v.8_SDK_Installer
I also switched from the latest Dymo Connect to DYMO Label v.8
I then had to ensure the Barcode font we're using was installed as an admin for all users, rather than just a specific user profile
and finally, and I think this may have been the kicker, the software was looking for a label template called 'Serial.label' which I found on our server and copied and pasted into the C: drive where the software was looking for it.
I'm just glad I didn't have to re-write the code!

Re-map linked tables in Access 2010

I am a relative novice with Access and starting from scratch with coding, so be gentle.
I have an Access 2010 database with dozens of linked tables based on .txt files. Sometimes the database moves, the source files move or the file server just gets re-named. In these events I am looking for a simple way for a user of the database to remap and refresh the linked tables. Ideally, it would be user prompted, i.e. the user pushes a button to refresh from a navigation form or something. Then, the system prompts for the new folder location. The folder location would house all of the necessary files, so it only needs to be selected one time. Once selected, all linked tables should remap and refresh with the user getting an error or success message.
I have seen a lot of these questions asked, but they seem to be in older versions of Access or it is not asking for a user prompt or for a user to browse for the new path.
Thanks.
While I agree with Marc B that this seems like a very oddly constructed database you could use the following code the manually link the tables again to the proper location. You would need to work this into a system that can loop through all tables and do them all one by one or adjust this code to do that automatically. But you may want to just rethink your system.
Function SetTableLinkPath(strTableName As String, strTablePath As String)
If Nz(strTableName, "") <> "" And Nz(strTablePath, "") <> "" Then
Dim cdb As DAO.Database
Set cdb = CurrentDb
cdb.TableDefs(strTableName).Connect = ";DATABASE=" & strTablePath
cdb.TableDefs(strTableName).RefreshLink
MsgBox "Table link for " & strTableName & " has been successfully set to the path: " & strTablePath & "."
Else
MsgBox "You must enter a valid Table path and name!"
End If
End Function

In access 2007, when you split the database into two files, can you tell it to look in the frontend directory for the backend file?

I'm playing around with splitting access databases. It appears that each table contains a hard link to the backend file in the linked table manager.
But if I wanted to send the file pair to someone through email to look at, the hard links will break. Right now we're at separate offices just testing changes to the program. This isn't in production. I don't want them to get into the development mode and edit the linked table manager because it would be too hard.
Is there a way to tell access to simply look in the front end file's directory for the backend file? Is there a way to force a prompt on the front end to let them choose the location of the backend file?
Thanks!
The simple solution is to on startup check if the table link(s) point to the current directory, and if not, then you run re-link code. That way the pair of files will work if you move the pair to a different folder or re-name the folder.
The above is a common setup and EVEN recommend for single user applications that SHOULD be split and benefit by being split.
So on startup, check the path of a linked table. I use the following code to return the path of currently linked tables.
Function strBackEndPath() As String
' returns the path name to the back end
' and includes tralinig \
Dim mytables As TableDef
Dim strTempBack As String
Dim strFullPath As String
strFullPath = ""
For Each mytables In CurrentDb.TableDefs
If Left(mytables.Connect, 10) = ";DATABASE=" Then
strFullPath = Mid(mytables.Connect, 11)
Exit For
End If
Next mytables
strBackEndPath = Left(strFullPath, InStrRev(strFullPath, "\"))
End Function
With above, then on startup I can go:
If CurrentProject.path & "\" <> strBackEndPath Then
' call re-link code
End If
And there are TONS of re-link code examples floating around but here is a link to some re-linking code.
http://access.mvps.org/access/tables/tbl0009.htm
Thus if the links don’t point to the back end database in the same folder, then re-linking will ONLY occur once and after that only the above “test” to ensure that front end and back end are are linked.
If the folder is renamed or as noted a user moves the files to a different location then again the re-link will occur.
Right-click on the front-end tables and select Linked Table Manager. From there, you can browse for the location of the back-end tables

User management in Microsoft Access 2013 databases

I am building a system using Microsoft Access 2013 that uses MySQL as a backend. I am trying to figure out a way to do some kind of basic user management, ideally in such a way that users would have to "log in" when they launch the database, and then their username would be easily accessible by the system while they are using it. I've tried searching for solutions, but most of them just tell me to use Office 365 or sharepoint, which are not options at the moment. Does anyone have an idea of how to accomplish this? Thanks in advance!
I recommend building your own user storage and login system. You'll basically need to create your own users table (in MySQL in your case), make forms to manage users, make a Login form, and write code to control the login process.
Logging in usually consists of checking some kind of credentials they type in against existing data in your users table. You can usually do this in Access with DLookup or DCount statements but I usually end up using a DAO or ADO recordset instead since I like to pull out more than one value from the User's table and I also like to write things back to it right away, like the LastLogin datetime, LastLogin computername, etc.
I actually wrote an example database which you can download here. It needs a rewrite. I've changed quite a few of my practices since Jan, 2011. But give me another year and it would need another rewrite.
I usually program the login form so that the user enters their initials and then a password. If you go this route you need to have a unique index setup on the Initials field to prevent duplicates. If you're going to have a lot of users you need to use a Username instead, which could still theoretically be the users initials.
Here's what my code would look like to authenticate a user. Be aware that this is far from truly secure. This assumes that passwords are stored in plain text. Users could theoretically try to do SQL Inject from here because I'm not use a parametrized query or stripping out special characters from their input such as # or ;.
Private Function AuthenticateUser() As Boolean
Dim sInitials As String
Dim sPassword As String
sInitials = Trim(Nz(Me.txtInitials, ""))
sPassword = Trim(Nz(Me.txtPassword, ""))
If sInitials = "" Or sPassword = "" Then
'Logging in with blank passwords is not allowed
AuthenticateUser = False
Exit Function
End If
If DCount("EmployeeID", "tblEmployees", "[Initials] = '" & Replace(sInitials, "'", "''") & "' AND Password = '" & Replace(sPassword, "'", "''") & "'", True) = 0 Then
MsgBox "Invalid Credentials."
AuthenticateUser = False
Exit Function
Else
Dim rs As New DAO.Recordset
Dim sSQL As String
sSQL = "SELECT * FROM tblEmployees WHERE initials = '" & Replace(sInitials, "'", "''") & "'"
Set rs = CurrentDb.OpenRecordset(sSQL)
If Not (rs.EOF And rs.BOF) Then
'Config is an instance of a User Defined Type. It could also be a class object.
Config.UserInitials = rs("Initials")
Config.UserFullName = rs("EmployeeName")
Config.UserID = rs("EmployeeID")
rs.Edit
rs("LastLoginDateTime") = Now()
rs("LastLoginComputer") = "Function Required to Get Computer Name"
rs("ProgVersion") = "Your Program Version Number"
rs("CurrentDbPath") = Left(CurrentProject.path & "\" & CurrentProject.Name, 254)
rs.Update
End If
rs.Close
Set rs = Nothing
AuthenticateUser = True
End If
End Function
In my applications I use a global object, in this case an instance of a User Defined Type, which I call Config. I store any kind of application runtime related settings in there for the duration of the runtime of the application. Of course this object gets destroyed when the user closes out of the application or when a code reset happens (which cannot happen in Access runtime, but does happen frequently during development). You could use a class object instead of a User Defined Type. Or you could use individual global variables for everything, which I don't recommend (that's what I used to do). A User Defined Type simply allows you to group global variables together and gives you an easy way to refer to them in your code during design time by typing in Config., which then brings up every option using Intellisense (assuming you have it enabled).
If you want your settings to survive a code reset, you need to use TempVars. TempVars became available with Access 2007. I do not use them now (contrary to my example database) because they are not strongly typed. There's no Intellisense to help you get the correct TempVar and you can technically refer to a TempVar that doesn't even exist and Access won't throw an error. I think TempVars is really just a Dictionary object with all of it's shortcomings, and the single benefit of surviving a code reset. I can imagine storing a Connection String in there, but I wonder if it's worth using TempVars for anything at all. If a code reset happens, my entire application needs to be reloaded anyway since I setup a lot of global objects and variables when the application first opens and the user first logs in.
FYI, in previous versions of Access there was user security built in. I think Microsoft discontinued that starting in 2007. I never really used it so I didn't miss it when it got discontinued.

Is there a way to execute VBA code when specific tables and fields in a Microsoft Access database are updated?

I have a program that uses a Microsoft Access database for its back-end. I need to have some VBA code (that calls a web service) execute whenever specific tables/fields are updated by the program. I see this working just like a trigger in SQL Server.
Is it possible to monitor for and act upon changes like this in Access?
Update
The program in question does not run inside of Access (i.e. not a VBA app), it simply uses an MDB file as its back-end storage. Unfortunately I don't have access to the program's code as it is a closed third party application.
This question is old, but the answers are no longer correct. Access 2010 added data macro events that can be run when data is inserted, updated or deleted. The following events are available while using either the table datasheet view or table design view (events are attached directly to table and not through the form macro button):
After Delete Macro Event
After Insert Macro Event
After Update Macro Event
Before Change Macro Event
Before Delete Macro Event
More information is located here:
https://msdn.microsoft.com/en-us/library/office/dn124692.aspx
https://support.office.com/en-us/article/Create-a-data-macro-b1b94bca-4f17-47ad-a66d-f296ef834200
Access the GUI environment vs Jet the database format are separate things.
If you are using an Access database as a backend - it's just the JET functionality you can work with. Access the GUI (which includes VBA) runs on the client machine and there is no automated trigger functionality.
If your program is the only program using the Access file, then it should know when a table is being updated and execute some code in place of a trigger.
Otherwise, you need another application/service running all the time that is checking the access file tables for updates (maybe you have some update_date type of field on your tables?).
When an Access database file gets written to, it's date/time stamp changes. I suppose you could try using a file monitor to detect changes to the file, and then examine the file to see what has changed.
It would help if the Access database has LastModified date/time columns in the tables.
If you are using Jet (i.e. the data is stored in an MDB file back end) then the only places you can run code would be in the After Update Event in a Form. The problem here of course is if the data is changed without using the form then the event will not fire.
If you are using MS Access 2003 then to run a Web Service you can download the Microsoft Office 2003 Web Services Toolkit Click Here to download
If you are stuck in VBA it gets a little rough. One way to go would be to have a form with timer in it (you could have it open invisibly. The timer could check the table, say once a minute (or whatever interval seems suitable) for changes in record count, and verify the table still exists. (code below)
But personally this isn't what I would recommend that you do. Access is notorious for corruption. When used as a simple back end you are fairly safe most of the time, but to have it running a monitor, means the file is always open. This is basically playing Russian Roulette with your database. At minimum I would link to your database from another Access file and monitor the linked tables, that way if your monitor crashes, you don't take the production DB with you. Finally, make sure that you don't query too often, as I'd hate to see you be the sole cause of the website timing out:)
Option Explicit
Private m_lngLstRcrdCnt_c As Long
Private Sub Form_Open(Cancel As Integer)
Const lngOneMinute_c As Long = 60000
Me.TimerInterval = lngOneMinute_c
End Sub
Private Sub Form_Timer()
Const strTblName_c As String = "Foo"
Const strKey_c As String = "MyField1"
Dim rs As DAO.Recordset
Dim lngRcrdCnt As Long
If TableExists(strTblName_c) Then
Set rs = CurrentDb.OpenRecordset("SELECT Count(" & strKey_c & ") FROM " & strTblName_c & ";", dbOpenSnapshot)
If Not rs.EOF Then lngRcrdCnt = Nz(rs.Fields(0&).Value, 0&)
rs.Close
If lngRcrdCnt <> m_lngLstRcrdCnt_c Then
m_lngLstRcrdCnt_c = lngRcrdCnt
'Number of records changed, do something.
End If
Else
'Table is deleted, do something.
m_lngLstRcrdCnt_c = -1
End If
End Sub
Private Function TableExists(ByVal name As String) As Boolean
Dim tdf As DAO.TableDef
On Error Resume Next
Set tdf = CurrentDb.TableDefs(name)
If LenB(tdf.name) Then 'Cheap way to catch broken links.
Set SafeGetTable = tdf
End If
End Function