How to list DataMacro objects in an Access database? - vba

Is it possible to programmatically enumerate the Data Macros in an Access 2010+ database? If so, how?
Note: Data Macros are trigger-like procedures that are created in the context of the table designer UI. They were new in Acces 2010. They are NOT the same thing as normal macros, which are easy to enumerate.
They have their own new AcObjectType enumeration value : acTableDataMacro, but I can find no other aspect of the Access or DAO object model that refers to them. They do not even appear in the MSysObjects table.

This code will export DataMacro metadata to an XML Document (Source):
Sub DocumentDataMacros()
'loop through all tables with data macros
'write data macros to external files
'open folder with files when done
' click HERE
' press F5 to Run!
' Crystal
' April 2010
On Error GoTo Proc_Err
' declare variables
Dim db As DAO.Database _
, r As DAO.Recordset
Dim sPath As String _
, sPathFile As String _
, s As String
' assign variables
Set db = CurrentDb
sPath = CurrentProject.Path & "\"
s = "SELECT [Name] FROM MSysObjects WHERE Not IsNull(LvExtra) and Type =1"
Set r = db.OpenRecordset(s, dbOpenSnapshot)
' loop through all records until the end
Do While Not r.EOF
sPathFile = sPath & r!Name & "_DataMacros.xml"
'Big thanks to Wayne Phillips for figuring out how to do this!
SaveAsText acTableDataMacro, r!Name, sPathFile
'have not tested SaveAsAXL -- please share information if you do
r.MoveNext
Loop
' give user a message
MsgBox "Done documenting data macros for " & r.RecordCount & " tables ", , "Done"
Application.FollowHyperlink CurrentProject.Path
Proc_Exit:
' close and release object variables
If Not r Is Nothing Then
r.Close
Set r = Nothing
End If
Set db = Nothing
Exit Sub
Proc_Err:
MsgBox Err.Description, , _
"ERROR " & Err.Number _
& " DocumentDataMacros"
Resume Proc_Exit
Resume
End Sub
EDIT: Gord pointed out that you wanted the DataMacros opposed to standard macros. I found some code and tested it (it works) here
I tested the top function when you follow that link and it saves information regarding your table macros for each table in an XML document. It works nicely, props to whoever wrote it.

Related

MS-Access - MailMerge specific record from a form

I am creating an Access 2019 database for small family business (dog breeding) so I setup some tables containing all details on the dogs and the owners. Just to give an idea (simplistic description of the situation):
Dogs
Name
Birth
Microchip
Etc…
Owners
Name
Address
Etc…
I was now trying to create a "Contract composer" for when we sell the dogs. So I made a new table "Contract" and a related form
Contract
Seller ->linked to Owners table
Buyer ->linked to Owners table
Dog ->linked to Dogs table
Price
And made a query to pull all relevant information from the related tables so that I can have
ContractQuery
Seller!Name
Seller!Address
Buyer!Name
Buyer!Address
Dog!Name
Dog!Birthdate
Dog!Microchip
Contract!Price
Everything so far is working perfectly fine.
Now I need to convert the ContractQuery fields in a form of "human readable" contract. I think the best way to do so is the MailMerge to a specific Word document, and I've already setup one. My problem is: how can I set a button into the Contract form so that the "contract.doc" is populated with the specific record I'm seeing now in the form?
I had made some researches and the most relevant information I've found is this
https://www.access-programmers.co.uk/forums/threads/run-mail-merge-from-vba.158126/
and this https://www.tek-tips.com/faqs.cfm?fid=3237
But they are related to old MS-Access so when I tried to apply it I had errors all around. Unluckily my VBA knowledge is far from being proficient and I was not able to make it work.
Can anyone help me, or address me to a solution?
Thanks in advance for any advice
OK I got it working thanks to Kostas K, pointing me in the fight direction. This is my final code, it might need some cleanup and tweaking (for example, the loop within the resulst is now redundant as I only have one result), but it is working :)
The solution is based on this post, should anyone need please have a look at it as reference for the template docx etc
Generating completed PDF forms using word docs and ms access
Option Explicit
Private Sub cmdMergeIt_Click()
On Error GoTo Trap
' **** defining project path as string to make this portable
Dim CurPath As String
CurPath = CurrentProject.path & "\"
' MsgBox (CurPath) 'debug
Dim TEMPLATE_PATH As String
TEMPLATE_PATH = CurPath & "Contratto.dotx"
Dim wApp As Word.Application
Dim wDoc As Word.Document
Dim rs As DAO.Recordset
Dim idx As Long
' *** intercepting the contract ID field from the launching form
Dim checkID As String
checkID = ID.Value
'MsgBox (checkID) 'debug
' **** defining a SQL query on my Access query
Dim strSQL As String
strSQL = "Select * from qContratto where ID =" & checkID & ""
' MsgBox (strSQL) 'debug
Set wApp = New Word.Application
wApp.Visible = False
' ***** changed the OpenRecordset to call my strSQL query insetad than reading the whole Access query
Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenSnapshot)
If rs.EOF Then GoTo Leave
With rs
.MoveLast
.MoveFirst
End With
For idx = 1 To rs.RecordCount
Set wDoc = wApp.Documents.Add(TEMPLATE_PATH)
With wDoc
.Bookmarks("Prezzo").Range.Text = Nz(rs!Prezzo, vbNullString)
.Bookmarks("Venditore").Range.Text = Nz(rs!Venditore, vbNullString)
.Bookmarks("Acquirente").Range.Text = Nz(rs!Acquirente, vbNullString)
.Bookmarks("Cessione").Range.Text = Nz(rs!Cessione, vbNullString)
.Bookmarks("NomeCane").Range.Text = Nz(rs!NomeCane, vbNullString)
.Bookmarks("Riproduzione").Range.Text = Nz(rs!Riproduzione, vbNullString)
.Bookmarks("Sesso").Range.Text = Nz(rs!Sesso, vbNullString)
.ExportAsFixedFormat CurPath & rs!Acquirente & ".pdf", wdExportFormatPDF, False, wdExportOptimizeForOnScreen
.Close wdDoNotSaveChanges
' in the ExportAsFixedFormat here above called one of the SQL query values to make a unique and distinctive name. Also please note use of CurPath for portability
End With
Set wDoc = Nothing
rs.MoveNext
Next
Leave:
On Error Resume Next
If Not rs Is Nothing Then rs.Close
If Not wDoc Is Nothing Then wDoc.Close wdDoNotSaveChanges
If Not wApp Is Nothing Then wApp.Quit wdDoNotSaveChanges
On Error GoTo 0
Exit Sub
Trap:
MsgBox Err.Description, vbCritical
Resume Leave
End Sub

My Access Vba is giving an automation error '-2147023170 (800706be)' when automating word

I am using a database to extract data that I need to present. Because I need to present the data in both landscape and portrait an access report will not do the trick. I am therefore using Access vba to put the data into a word template. However when I run the code over the full data I keep encountering an automation error. The word application ceases to exist.
I have queried and I don't think I have any of the common code errors associated with this problem, so no unreferenced word objects etc. I have also replaced all references to word constants like wdcell with their integer values. I have run through the code in debug, and when I run through in debug, the code never fails. I have put in some pauses (a function that waits a specific time and performs DoEvents) to see if that helps
Function MoveMergePaste(wdInputName As String, oWord As Word.Application, oWdocMerged As Word.Document) As Long
Dim oWdocMove As Word.Document
Dim oWdocMoveMerged As Word.Document
Dim rst As Recordset
Dim strSQL As String
On Error GoTo ERR_MoveMergePaste
Pause 1
strSQL = "SELECT T.* FROM tblMovementTemp AS T INNER JOIN tblMovementHeader AS H "
strSQL = strSQL & "ON T.FundId = H.FundId ORDER BY tblPosition"
Set rst = CurrentDb.OpenRecordset(strSQL)
Pause 0.5
Set oWdocMove = oWord.Documents.Open(wdInputName)
Pause 0.5
oWdocMove.Bookmarks("PremiumDataStart").Select
rst.MoveFirst
oWord.Selection.TypeText rst!tblElement
oWord.Selection.MoveRight Unit:=12 '(wdcell)
oWord.Selection.TypeText rst!TotalUnits
rst.MoveNext
While Not rst.EOF
oWord.Selection.MoveRight Unit:=12 '(wdcell)
oWord.Selection.TypeText rst!tblElement
oWord.Selection.MoveRight Unit:=12 '(wdcell)
oWord.Selection.TypeText rst!TotalUnits
rst.MoveNext
Wend
'Start mail merge Claims
'------------------------------------------------
With oWdocMove.MailMerge
.MainDocumentType = 0 'wdFormLetters
.OpenDataSource _
Name:=CurrentProject.FullName, _
AddToRecentFiles:=False, _
LinkToSource:=True, _
Connection:="QUERY mailmerge", _
SQLStatement:="SELECT * FROM [tblMovementHeader] "
.Destination = 0 'wdSendToNewDocument
.Execute Pause:=False
End With
'Copy movement data into merged document
'------------------------------------------------
Set oWdocMoveMerged = oWord.ActiveDocument
oWdocMoveMerged.Select
oWord.Selection.WholeStory
oWord.Selection.Copy
oWdocMerged.Select
oWord.Selection.EndKey Unit:=6 '(wdstory)
oWord.Selection.PasteAndFormat (wdFormatOriginalFormatting)
MoveMergePaste = 0
EXIT_MoveMergePaste:
On Error Resume Next
'Close files
'------------------------------------------------
oWdocMove.Close SaveChanges:=False
oWdocMoveMerged.Close SaveChanges:=False
'Release objects
'------------------------------------------------
Set oWdocMove = Nothing
Set oWdocMoveMerged = Nothing
Set rst = Nothing
Exit Function
ERR_MoveMergePaste:
MoveMergePaste = Err.Number
MsgBox Err.Description
Resume EXIT_MoveMergePaste
End Function
I pass a string which is for the path of a word template, a word application object, and an existing open word document. the routine builds a record set of data, opens the word template, finds a bookmark in the template and then writes data from the recordset to the template. Once it has done this it performs a mail merge on the template and copies the data from the newly created merged document into the open document. It returns 0 if successful. It closes the template and merged document and releases the objects.
Mostly it works, but far too often to make it usable I get the automation error. '-2147023170 (800706be)'. The error usually occurs because the word has ceased to exist. If I break the code the line of code that returns the error is
oWdocMove.Bookmarks("PremiumDataStart").Select
But the line is OK, the error is because word no longer exists.

Run VBA Code in Excel to obtain VBA in Access Databases

I would like to create a program in Excel that loops through a list of Access databases and writes the VBA that exists in the Access modules. I have found some code that I can run from Access which writes the VBA that exists in the Access modules. I am trying to figure out how to reference the database files from Excel and run the program on each database file. I will probably be able to figure out how to loop through the database files. I just need help with referencing the database file in the below code.
I can open the database with something like this:
Dim cstrDbFile As String = "C:\Database51.accdb"
Dim objShell As Object
Set objShell = CreateObject("WScript.Shell")
objShell.Run cstrDbFile
I also tried to set up a reference to Access like this:
Dim appAccess As Object
Set appAccess = CreateObject("Access.Application")
appAccess.OpenCurrentDatabase ("C:\Database51.accdb")
I need to figure out how to refer to the Access database in:
Application.VBE.ActiveVBProject.VBComponents
I probably need to figure out how to create a reference to replace ActiveVBProject.
Below is some code I found which writes the contents of VBA modules. I don't remember where I found it.
For Each Component In Application.VBE.ActiveVBProject.VBComponents
With Component.CodeModule
'The Declarations
For Index = 1 To .CountOfDeclarationLines
Debug.Print .Lines(Index, 1)
Next Index
'The Procedures
For Index = .CountOfDeclarationLines + 1 To .CountOfLines
Debug.Print .Lines(Index, 1)
Next Index
End With
Next Component
The following code will let you see Access database objects, but I don't know how to export the code (DoCmd not in Excel?). Your task would be VERY simple to do from Access, so I would reconsider...
Option Explicit
' Add a reference to the DAO Object Library
Sub Read_Access_VBA()
Dim dbs As DAO.Database
Dim ctr As DAO.Container
Dim doc As DAO.Document
Dim iC As Integer
Dim iD As Integer
Dim i As Integer
Dim mdl As Module
Set dbs = DBEngine.OpenDatabase("c:\TEMP\106thRoster.mdb", False, False, _
"MS Access;")
Debug.Print "----------------------------------------"
For iC = 0 To dbs.Containers.Count - 1
Debug.Print "Container: " & dbs.Containers(iC).Name
If dbs.Containers(iC).Documents.Count > 0 Then
For iD = 0 To dbs.Containers(iC).Documents.Count - 1
Debug.Print vbTab & "Doc: " & dbs.Containers(iC).Documents(iD).Name
Next iD
Else
Debug.Print " No Documents..."
End If
Next iC
'Set ctr = dbs.Containers!Modules
dbs.Close
Set doc = Nothing
Set ctr = Nothing
Set dbs = Nothing
End Sub
I was able to find some code that will assist me with my final goal: Exporting MS Access Forms and Class / Modules Recursively to text files?
Below are the most significant lines that will allow me to make progress with the project.
LineCount = oApp.Forms(Name).Module.CountOfLines
FileName = Path & "\" & Name & ".vba"
F = FreeFile
Open FileName For Output Access Write As #F
Print #F, oApp.Forms(Name).Module.Lines(1, LineCount)
Close #F

Access 2007 to 2010 Conversion Compile Error: User-defined type not defined

I recently converted (exported/imported) a 2007 Access file to 2010. Everything works fine except one form. I keep getting the error:
Compile error: User-defined type not defined
I tried adding "Microsoft ActiveX Data Objects 2.8" to my References, but the problem still exists. Sub ClearTreeView(tvwTree As TreeView) is what Access highlighted as the issue.
Option Compare Database
Option Explicit
' Clears all nodes on a treeview control
Sub ClearTreeView(tvwTree As TreeView) 'what Access highlighted as the issue
On Error GoTo EH
tvwTree.Nodes.Clear
Exit Sub
EH:
MsgBox "Error " & Err.Number & ": " & Err.Description
End Sub
' Calls functions to clear and populate a treeview control
' Parameters:
' strForm Name of the form
' strTV TreeView control name
' strSourceName Name of the table or query containing the data used to populate the treeview
' strChildField ID field for the child records
' strParentField Parent ID Field
' strTextField Field containing text that will be used as node labels
'
Sub FillTreeView(tvwTree As Object, strSourceName As String, strChildField As String, strParentField As String, strTextField As String)
Dim strSQL As String
Dim rs As DAO.Recordset
On Error GoTo EH
' Open the recordset using table and fields specified in Sub parameters
strSQL = "SELECT " & strChildField & ", " & strParentField & ", " & strTextField & " FROM " & strSourceName
Set rs = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset)
' Clear any existing data out of the treeview
ClearTreeView tvwTree
' Call recursive function to fill in treeview
AddTreeData tvwTree, rs, strChildField, strParentField, strTextField
' Close the recordset
rs.Close
Set rs = Nothing
Exit Sub
EH:
MsgBox "Error " & Err.Number & ": " & Err.Description
End Sub
' Recursive function to populate a treeview control
' Parameters:
' strFormName Name of the form
' strTreeViewName TreeView control name
' rs Recordset containing the data used to populate the treeview
' strChildField ID field for the child records
' strParentField Parent ID Field
' strTextField Field containing text that will be used as node labels
' varParentID Optional parameter that only gets passed for recursive calls to this function. Specifies the ID of the current record to be used as a
' ParentID when searching the recordset for "grand-children", etc.
Sub AddTreeData(objTV As TreeView, rs As DAO.Recordset, strChildField As String, strParentField As String, strTextField As String, Optional varParentID As Variant)
Dim nodChild As Node
Dim nodParent As Node
Dim strLabel As String
Dim strNodeID As String
Dim strCriteria As String
Dim strBookmark As String
On Error GoTo EH
' Test for a circular reference
If rs(strChildField) = rs(strParentField) Then GoTo EH_CircularReference
' If the optional parameter is missing, then this is the first(non-recursive) call to this function.
' Set the critieria to look for a parent id of 0.
If IsMissing(varParentID) Then
strCriteria = strParentField & " = 0 "
Else
' Otherwise, extract the childID portion of the node ID, which was passed as an optional parameter.
strCriteria = strParentField & " = " & Mid(varParentID, InStr(1, varParentID, "C") + 1)
' Define the parent node
Set nodParent = objTV.Nodes("node" & varParentID)
End If
' Look for records having the specified "parent"
rs.FindFirst strCriteria
Do Until rs.NoMatch
' Read node caption from the text field
strLabel = rs(strTextField)
' Create a new node ID in the format ParentID &"C" & ChildID (eg: 4C12)
strNodeID = "node" & rs(strParentField) & "C" & rs(strChildField)
' If optional parameter is missing (first call to this function)...
If Not IsMissing(varParentID) Then
'add new node to the next higher node for this record
Set nodChild = objTV.Nodes.Add(nodParent, tvwChild, strNodeID, strLabel)
Else
' Otherwise, add new node to the top level of the tree
Set nodChild = objTV.Nodes.Add(, , strNodeID, strLabel)
End If
' Bookmark our place in the recordset so that we can resume the search from the same point after the recursive call to this function.
strBookmark = rs.Bookmark
' call this function recursively for "children"
AddTreeData objTV, rs, strChildField, strParentField, strTextField, rs(strParentField) & "C" & rs(strChildField)
' Return to bookmared place in the recordset
rs.Bookmark = strBookmark
' Find the next record having the same parentID
rs.FindNext strCriteria
Loop
Exit Sub
EH_CircularReference:
MsgBox "Exiting because of a circular reference in which a child record was determined to be it's own parent."
Exit Sub
EH:
MsgBox "Error " & Err.Number & ": " & Err.Description
End Sub
I received the same error on one of my User Forms in Excel!
If your new 2010 Office package is 64Bit, then the previous 32-Bit ActiveX Controls used in the 2007 version will not be compatible.
Refer to this link: http://msdn.microsoft.com/en-us/library/ee691831(office.14).aspx#odc_office2010_Compatibility32bit64bit_ActiveXControlCOMAddinCompatibility
If you try opening the form in design view and the TreeView control doesn't exist on the form, then this is likely to be the issue.
To find if you installed the 64-bit version:
Open Access -> File -> Help -> and look under "About Microsoft Access" on the right - it should say 32-Bit or 64-Bit in brackets
As the link explains, you will have to replace the incompatible functionality - so you will have to use a new control.
Possible Solution:
If you open the form in design view, and select the down arrow on the Controls Group in the Ribbon Tab - there should be an option named "ActiveX Controls" (You have to be in design view to select it)
Search here for the "CTreeView" Control and try working with that instead of the traditional Microsoft TreeView Control (which shouldn't be listed on 64-Bit Access).
If you have 32-Bit office installed, then I can't figure out the problem with what you have posted - but I suspect it's the case.
Best Regards,

MS Access: how to compact current database in VBA

Pretty simple question, I know.
If you want to compact/repair an external mdb file (not the one you are working in just now):
Application.compactRepair sourecFile, destinationFile
If you want to compact the database you are working with:
Application.SetOption "Auto compact", True
In this last case, your app will be compacted when closing the file.
My opinion: writting a few lines of code in an extra MDB "compacter" file that you can call when you want to compact/repair an mdb file is very usefull: in most situations the file that needs to be compacted cannot be opened normally anymore, so you need to call the method from outside the file.
Otherwise, the autocompact shall by default be set to true in each main module of an Access app.
In case of a disaster, create a new mdb file and import all objects from the buggy file. You will usually find a faulty object (form, module, etc) that you will not be able to import.
If you have the database with a front end and a back end. You can use the following code on the main form of your front end main navigation form:
Dim sDataFile As String, sDataFileTemp As String, sDataFileBackup As String
Dim s1 As Long, s2 As Long
sDataFile = "C:\MyDataFile.mdb"
sDataFileTemp = "C:\MyDataFileTemp.mdb"
sDataFileBackup = "C:\MyDataFile Backup " & Format(Now, "YYYY-MM-DD HHMMSS") & ".mdb"
DoCmd.Hourglass True
'get file size before compact
Open sDataFile For Binary As #1
s1 = LOF(1)
Close #1
'backup data file
FileCopy sDataFile, sDataFileBackup
'only proceed if data file exists
If Dir(sDataFileBackup, vbNormal) <> "" Then
'compact data file to temp file
On Error Resume Next
Kill sDataFileTemp
On Error GoTo 0
DBEngine.CompactDatabase sDataFile, sDataFileTemp
If Dir(sDataFileTemp, vbNormal) <> "" Then
'delete old data file data file
Kill sDataFile
'copy temp file to data file
FileCopy sDataFileTemp, sDataFile
'get file size after compact
Open sDataFile For Binary As #1
s2 = LOF(1)
Close #1
DoCmd.Hourglass False
MsgBox "Compact complete. " & vbCrLf & vbCrLf _
& "Size before: " & Round(s1 / 1024 / 1024, 2) & "MB" & vbCrLf _
& "Size after: " & Round(s2 / 1024 / 1024, 2) & "MB", vbInformation
Else
DoCmd.Hourglass False
MsgBox "ERROR: Unable to compact data file."
End If
Else
DoCmd.Hourglass False
MsgBox "ERROR: Unable to backup data file."
End If
DoCmd.Hourglass False
Try adding this module, pretty simple, just launches Access, opens the database, sets the "Compact on Close" option to "True", then quits.
Syntax to auto-compact:
acCompactRepair "C:\Folder\Database.accdb", True
To return to default*:
acCompactRepair "C:\Folder\Database.accdb", False
*not necessary, but if your back end database is >1GB this can be rather annoying when you go into it directly and it takes 2 minutes to quit!
EDIT: added option to recurse through all folders, I run this nightly to keep databases down to a minimum.
'accCompactRepair
'v2.02 2013-11-28 17:25
'===========================================================================
' HELP CONTACT
'===========================================================================
' Code is provided without warranty and can be stolen and amended as required.
' Tom Parish
' TJP#tomparish.me.uk
' http://baldywrittencod.blogspot.com/2013/10/vba-modules-access-compact-repair.html
' DGF Help Contact: see BPMHelpContact module
'=========================================================================
'includes code from
'http://www.ammara.com/access_image_faq/recursive_folder_search.html
'tweaked slightly for improved error handling
' v2.02 bugfix preventing Compact when bAutoCompact set to False
' bugfix with "OLE waiting for another application" msgbox
' added "MB" to start & end sizes of message box at end
' v2.01 added size reduction to message box
' v2.00 added recurse
' v1.00 original version
Option Explicit
Function accSweepForDatabases(ByVal strFolder As String, Optional ByVal bIncludeSubfolders As Boolean = True _
, Optional bAutoCompact As Boolean = False) As String
'v2.02 2013-11-28 17:25
'sweeps path for .accdb and .mdb files, compacts and repairs all that it finds
'NB: leaves AutoCompact on Close as False unless specified, then leaves as True
'syntax:
' accSweepForDatabases "path", [False], [True]
'code for ActiveX CommandButton on sheet module named "admin" with two named ranges "vPath" and "vRecurse":
' accSweepForDatabases admin.Range("vPath"), admin.Range("vRecurse") [, admin.Range("vLeaveAutoCompact")]
Application.DisplayAlerts = False
Dim colFiles As New Collection, vFile As Variant, i As Integer, j As Integer, sFails As String, t As Single
Dim SizeBefore As Long, SizeAfter As Long
t = Timer
RecursiveDir colFiles, strFolder, "*.accdb", True 'comment this out if you only have Access 2003 installed
RecursiveDir colFiles, strFolder, "*.mdb", True
For Each vFile In colFiles
'Debug.Print vFile
SizeBefore = SizeBefore + (FileLen(vFile) / 1048576)
On Error GoTo CompactFailed
If InStr(vFile, "Geographical Configuration.accdb") > 0 Then MsgBox "yes"
acCompactRepair vFile, bAutoCompact
i = i + 1 'counts successes
GoTo NextCompact
CompactFailed:
On Error GoTo 0
j = j + 1 'counts failures
sFails = sFails & vFile & vbLf 'records failure
NextCompact:
On Error GoTo 0
SizeAfter = SizeAfter + (FileLen(vFile) / 1048576)
Next vFile
Application.DisplayAlerts = True
'display message box, mark end of process
accSweepForDatabases = i & " databases compacted successfully, taking " & CInt(Timer - t) & " seconds, and reducing storage overheads by " & Int(SizeBefore - SizeAfter) & "MB" & vbLf & vbLf & "Size Before: " & Int(SizeBefore) & "MB" & vbLf & "Size After: " & Int(SizeAfter) & "MB"
If j > 0 Then accSweepForDatabases = accSweepForDatabases & vbLf & j & " failures:" & vbLf & vbLf & sFails
MsgBox accSweepForDatabases, vbInformation, "accSweepForDatabases"
End Function
Function acCompactRepair(ByVal pthfn As String, Optional doEnable As Boolean = True) As Boolean
'v2.02 2013-11-28 16:22
'if doEnable = True will compact and repair pthfn
'if doEnable = False will then disable auto compact on pthfn
On Error GoTo CompactFailed
Dim A As Object
Set A = CreateObject("Access.Application")
With A
.OpenCurrentDatabase pthfn
.SetOption "Auto compact", True
.CloseCurrentDatabase
If doEnable = False Then
.OpenCurrentDatabase pthfn
.SetOption "Auto compact", doEnable
End If
.Quit
End With
Set A = Nothing
acCompactRepair = True
Exit Function
CompactFailed:
End Function
'source: http://www.ammara.com/access_image_faq/recursive_folder_search.html
'tweaked slightly for error handling
Private Function RecursiveDir(colFiles As Collection, _
strFolder As String, _
strFileSpec As String, _
bIncludeSubfolders As Boolean)
Dim strTemp As String
Dim colFolders As New Collection
Dim vFolderName As Variant
'Add files in strFolder matching strFileSpec to colFiles
strFolder = TrailingSlash(strFolder)
On Error Resume Next
strTemp = ""
strTemp = Dir(strFolder & strFileSpec)
On Error GoTo 0
Do While strTemp <> vbNullString
colFiles.Add strFolder & strTemp
strTemp = Dir
Loop
If bIncludeSubfolders Then
'Fill colFolders with list of subdirectories of strFolder
On Error Resume Next
strTemp = ""
strTemp = Dir(strFolder, vbDirectory)
On Error GoTo 0
Do While strTemp <> vbNullString
If (strTemp <> ".") And (strTemp <> "..") Then
If (GetAttr(strFolder & strTemp) And vbDirectory) <> 0 Then
colFolders.Add strTemp
End If
End If
strTemp = Dir
Loop
'Call RecursiveDir for each subfolder in colFolders
For Each vFolderName In colFolders
Call RecursiveDir(colFiles, strFolder & vFolderName, strFileSpec, True)
Next vFolderName
End If
End Function
Private Function TrailingSlash(strFolder As String) As String
If Len(strFolder) > 0 Then
If Right(strFolder, 1) = "\" Then
TrailingSlash = strFolder
Else
TrailingSlash = strFolder & "\"
End If
End If
End Function
For Access 2013, you could just do
Sendkeys "%fic"
This is the same as typing ALT, F, I, C on your keyboard.
It's probably a different sequence of letters for different versions, but the "%" symbol means "ALT", so keep that in the code. you may just need to change the letters, depending on what letters appear when you press ALT
Letters that appear when pressing ALT in Access 2013
In response to the excellent post by jdawgx:
Please be aware of a flaw in the code for CompactDB() above.
If the database's "AppTitle" property is defined (as happens when an "Application title" is defined in the database properties), this invalidates the "default window title" logic shown, which can cause the script to fail, or "behave unpredictably". So, adding code to check for an AppTitle property - or using API calls to read the Window title text from the Application.hWndAccessApp window could both be much more reliable.
Additionally, in Access 2019, we have observed that:
SendKeys "multi-key-string-here"
... may also not work reliably, needing to be replaced with:
SendKey (single-character)
'put a DoEvents or Sleep 150 here
SendKey (single-character)
'put a DoEvents or Sleep 150 here
SendKey (single-character)
'put a DoEvents or Sleep 150 here
SendKey (single-character)
...to get proper responses from the Access UI.
ALSO for Access 2019:
Sendkeys "%yc" ( <-- works for Access 2016)
is no longer correct.
it is now:
Sendkeys "%y1c"
...and if that little change wasn't enough - try to determine (in code) how to tell the difference between Access 2016 and 2019 - Good Luck!! because
Application.Version alone won't help, and even combining Application.Version and Application.Build is not a guarantee (unless you are in a controlled-release enterprise environment, and then it may work as the possible version/build #s in circulation should be more limited).
Yes it is simple to do.
Sub CompactRepair()
Dim control As Office.CommandBarControl
Set control = CommandBars.FindControl( Id:=2071 )
control.accDoDefaultAction
End Sub
Basically it just finds the "Compact and repair" menuitem and clicks it, programatically.
I did this many years back on 2003 or possibly 97, yikes!
If I recall you need to use one of the subcommands above tied to a timer. You cannot operate on the db with any connections or forms open.
So you do something about closing all forms, and kick off the timer as the last running method. (which will in turn call the compact operation once everything closes)
If you haven't figured this out I could dig through my archives and pull it up.
When the user exits the FE attempt to rename the backend MDB preferably with todays date in the name in yyyy-mm-dd format. Ensure you close all bound forms, including hidden forms, and reports before doing this. If you get an error message, oops, its busy so don't bother. If it is successful then compact it back.
See my Backup, do you trust the users or sysadmins? tips page for more info.
DBEngine.CompactDatabase source, dest
Application.SetOption "Auto compact", False '(mentioned above)
Use this with a button caption: "DB Not Compact On Close"
Write code to toggle the caption with "DB Compact On Close"
along with Application.SetOption "Auto compact", True
AutoCompact can be set by means of the button or by code, ex: after importing large temp tables.
The start up form can have code that turns off Auto Compact, so that it doesn't run every time.
This way, you are not trying to fight Access.
If you don't wish to use compact on close (eg, because the front-end mdb is a robot program that runs continually), and you don't want to create a separate mdb just for compacting, consider using a cmd file.
I let my robot.mdb check its own size:
FileLen(CurrentDb.Name))
If its size exceeds 1 GB, it creates a cmd file like this ...
Dim f As Integer
Dim Folder As String
Dim Access As String
'select Access in the correct PF directory (my robot.mdb runs in 32-bit MSAccess, on 32-bit and 64-bit machines)
If Dir("C:\Program Files (x86)\Microsoft Office\Office\MSACCESS.EXE") > "" Then
Access = """C:\Program Files (x86)\Microsoft Office\Office\MSACCESS.EXE"""
Else
Access = """C:\Program Files\Microsoft Office\Office\MSACCESS.EXE"""
End If
Folder = ExtractFileDir(CurrentDb.Name)
f = FreeFile
Open Folder & "comrep.cmd" For Output As f
'wait until robot.mdb closes (ldb file is gone), then compact robot.mdb
Print #f, ":checkldb1"
Print #f, "if exist " & Folder & "robot.ldb goto checkldb1"
Print #f, Access & " " & Folder & "robot.mdb /compact"
'wait until the robot mdb closes, then start it
Print #f, ":checkldb2"
Print #f, "if exist " & Folder & "robot.ldb goto checkldb2"
Print #f, Access & " " & Folder & "robot.mdb"
Close f
... launches the cmd file ...
Shell ExtractFileDir(CurrentDb.Name) & "comrep.cmd"
... and shuts down ...
DoCmd.Quit
Next, the cmd file compacts and restarts robot.mdb.
Try this. It works on the same database in which the code resides. Just call the CompactDB() function shown below. Make sure that after you add the function, you click the Save button in the VBA Editor window prior to running for the first time. I only tested it in Access 2010. Ba-da-bing, ba-da-boom.
Public Function CompactDB()
Dim strWindowTitle As String
On Error GoTo err_Handler
strWindowTitle = Application.Name & " - " & Left(Application.CurrentProject.Name, Len(Application.CurrentProject.Name) - 4)
strTempDir = Environ("Temp")
strScriptPath = strTempDir & "\compact.vbs"
strCmd = "wscript " & """" & strScriptPath & """"
Open strScriptPath For Output As #1
Print #1, "Set WshShell = WScript.CreateObject(""WScript.Shell"")"
Print #1, "WScript.Sleep 1000"
Print #1, "WshShell.AppActivate " & """" & strWindowTitle & """"
Print #1, "WScript.Sleep 500"
Print #1, "WshShell.SendKeys ""%yc"""
Close #1
Shell strCmd, vbHide
Exit Function
err_Handler:
MsgBox "Error " & Err.Number & ": " & Err.Description
Close #1
End Function
Please Note the following - all of you who favor doing a "Compact on Close" solution for MS-Access.
I used to prefer that option too, until one day, when I received the WORST error message possible from the DBEngine during a Compress & Repair operation:
"Table MSysObjects is corrupt - Table Truncated."
Now, you have probably never realized that THAT error is even a possibility.
Well, it is. And if you ever see it, your ENTIRE DATABASE, and EVERYTHING IN IT is now simply GONE. poof!
What is funny about that is that Access will let you actually reopen the "fixed" database, only, the Access window and menu items are all now utterly useless (except to close the DB and exit access again) because ALL the tables (including the other MSYS* tables, forms, queries, reports, code modules, & macros) are simply gone - and with the disk space previously allocated to them released to the tender mercies of the Windows OS - unless you have additional protection than the bog-standard recycle bin, which won't help you either.
So, if you REALLY want to accept the risk of Compact on Close completely clobbering your database - with NO POSSIBILITY of recovering it, then please...do carry on.
If, OTOH, like me you find that risk an unacceptable one, well, don't enable C&R-on-Close - ever again.
Check out this solution VBA Compact Current Database.
Basically it says this should work
Public Sub CompactDB()
CommandBars("Menu Bar").Controls("Tools").Controls ("Database utilities"). _
Controls("Compact and repair database...").accDoDefaultAction
End Sub
There's also Michael Kaplan's SOON ("Shut One, Open New") add-in. You'd have to chain it, but it's one way to do this.
I can't say I've had much reason to ever want to do this programatically, since I'm programming for end users, and they are never using anything but the front end in the Access user interface, and there's no reason to regularly compact a properly-designed front end.