Add references programmatically - ms-access

we have an Access-Application which does not work on some clients, mainly because references are broken. That happens for example when you start the access application with access runtime 2007 but have office in version 2003 or 2000 installed. Functions like Left/Right/Trim etc. just stop working then.
I think the only way to fix this problem is to programmtically check which office version is installed and add the references programmatically as in these heterogenous environments we cannot control what the user has installed. Specifically I need to reference the Microsoft Office Object libraries for Excel and Word.
But I neither have the guids of all office versions nor have a clue how to check them automatically.

So yeah, this answer is a bit late, but just in case someone stumbles across this like I did looking for an answer, I figured out the following bit of code to add an excel reference and it seems to work fine, also in MDE/ACCDE!
If Dir("C:\Program Files (x86)\Microsoft Office\Office12\EXCEL.exe") <> "" And Not refExists("excel") Then
Access.References.AddFromFile ("C:\Program Files (x86)\Microsoft Office\Office12\EXCEL.exe")
End If
If Dir("C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.exe") <> "" And Not refExists("excel") Then
Access.References.AddFromFile ("C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.exe")
End If
If Dir("C:\Program Files (x86)\Microsoft Office\Office12\EXCEL.exe") = "" And Dir("C:\Program Files (x86)\Microsoft Office\Office14\EXCEL.exe") = "" Then
MsgBox ("ERROR: Excel not found")
End If
And the refExists references the following function:
Private Function refExists(naam As String)
Dim ref As Reference
refExists = False
For Each ref In References
If ref.Name = naam Then
refExists = True
End If
Next
End Function

If you ship an MDE/ACCDE you can't update your references.
But what specific references are causing you your problems? Chances are you are referencing Word, Excel or Outlook. If so use late binding so your solution doesn't matter what version is installed on the client system.
Late binding means you can safely remove the reference and only have an error when the app executes lines of code in question. Rather than erroring out while starting up the app and not allowing the users in the app at all. Or when hitting a mid, left or trim function call.
This also is very useful when you don't know what version of the external application will reside on the target system. Or if your organization is in the middle of moving from one version to another.
For more information including additional text and some detailed links see the "Late Binding in Microsoft Access" page.

Here is an example - it check for certain references - deleting them and importing the Access 2000 variant. Just to make sure all clients use the same (lowest) version of the dependencies
Sub CheckReference()
' This refers to your VBA project.
Dim chkRef As Reference ' A reference.
Dim foundWord, foundExcel As Boolean
foundWord = False
foundExcel = False
' Check through the selected references in the References dialog box.
For Each chkRef In References
' If the reference is broken, send the name to the Immediate Window.
If chkRef.IsBroken Then
Debug.Print chkRef.Name
End If
If InStr(UCase(chkRef.FullPath), UCase("MSWORD9.olb")) <> 0 Then
foundWord = True
End If
If InStr(UCase(chkRef.FullPath), UCase("EXCEL9.OLB")) <> 0 Then
foundExcel = True
End If
If InStr(UCase(chkRef.FullPath), UCase("MSWORD.olb")) <> 0 Then
References.Remove chkRef
ElseIf InStr(UCase(chkRef.FullPath), UCase("EXCEL.EXE")) <> 0 Then
References.Remove chkRef
End If
Next
If (foundWord = False) Then
References.AddFromFile ("\\pathto\database\MSWORD9.OLB")
End If
If (foundExcel = False) Then
References.AddFromFile ("\\pathto\database\EXCEL9.OLB")
End If
End Sub

Here is a code sample, which checks for broken references. I know this is not the whole solution for you, but it will give you some clues how to do it.
Public Function CheckRefs()
On Error GoTo Handler
Dim rs As Recordset
Dim ref As Reference
Dim msg As String
For Each ref In Application.References
' Check IsBroken property.
If ref.IsBroken = True Then
msg = msg & "Name: " & ref.Name & vbTab
msg = msg & "FullPath: " & ref.FullPath & vbTab
msg = msg & "Version: " & ref.Major & "." & ref.Minor & vbCrLf
End If
Next ref
If Len(msg) > 0 Then MsgBox msg
Exit Function
Handler:
' error codes 3075 and 3085 need special handling
If Err.Number = 3075 Or Err.Number = 3085 Then
Err.Clear
FixUpRefs
Else
rs.Close
Set rs = Nothing
End If
End Function
Private Sub FixUpRefs()
Dim r As Reference, r1 As Reference
Dim s As String
' search the first ref which isn't Access or VBA
For Each r In Application.References
If r.Name <> "Access" And r.Name <> "VBA" Then
Set r1 = r
Exit For
End If
Next
s = r1.FullPath
' remove the reference and add it again from file
References.Remove r1
References.AddFromFile s
' hidden syscmd to compile the db
Call SysCmd(504, 16483)
End Sub

Related

Detect if certain named workbook is open. error 53

I used the code provided by Siddharth Rout in the following threat.
Detect whether Excel workbook is already open
My goal was to check if a certain named workbook was open and depending on the result perform certain actions.
This was the result.
Function IsWorkBookOpen(FileName As String)
Dim ff As Long, ErrNo As Long
On Error Resume Next
ff = FreeFile()
Open FileName For Input Lock Read As #ff
Close ff
ErrNo = Err
On Error GoTo 0
Select Case ErrNo
Case 0: IsWorkBookOpen = False
Case 70: IsWorkBookOpen = True
Case Else: Error ErrNo
End Select
End Function
The following piece refers back to the function and depending on the result performs certain actions.
Dim xls As Object
Dim Answer As String
Dim Mynote As String
If IsWorkBookOpen(Environ("USERPROFILE") & "\Desktop\Report.xlsm") =
True Then
Mynote = "The Report is still open. Do you want to save the Report ?"
Answer = MsgBox(Mynote, vbQuestion + vbYesNo, "Warning Report open")
If Answer = vbYes Then
MsgBox "Please Save your Report under a new name and close it. then press update again"
Exit Sub
Else
Set xls = GetObject(Environ("USERPROFILE") & "\Desktop\Report.xlsm")
xls.Close True
End If
Else
End If
This used to work perfectly in the past but since today it suddenly gives me error 53.
While trying to resolve the issue I discovered the error only occurs when the named workbook is not on the desktop. Strangely enough it did not have this issue in the past. I specifically tested that because the file will not always be on the desktop.
I tried several backups tracking back 2 months and even those show the same error now.
While searching the internet for this issue i found this thread,
Check if excel workbook is open?
where they suggest to change the following pieces,
(ErrNo = Err) in to (Errno = Err.Number)
(ff = FreeFile_()) in to (ff = FreeFile)
I did both together and independitly. eventhough i dont really see the relation between the error and Freefile.
This did not change the error at all.
While I am currious to why this error suddenly occurs I really do need a solution or alternative.
what i need it tot do again is,
- Check if named workbook is open.
- when it is open a Msgbox with yes and no option should appear.
- On "No" it should close the named workbook and continue with whatever is below of what i posted.
- On yes it should pop a message box and stop.
Any help that can be provided will be highly appreciated.
You need to check if the file exists before checking if it is open;
Function FileExists(ByVal strFile As String, Optional bFindFolders As Boolean) As Boolean
'Purpose: Return True if the file exists, even if it is hidden.
'Arguments: strFile: File name to look for. Current directory searched if no path included.
' bFindFolders. If strFile is a folder, FileExists() returns False unless this argument is True.
'Note: Does not look inside subdirectories for the file.
'Author: Allen Browne. http://allenbrowne.com June, 2006.
Dim lngAttributes As Long
'Include read-only files, hidden files, system files.
lngAttributes = (vbReadOnly Or vbHidden Or vbSystem)
If bFindFolders Then
lngAttributes = (lngAttributes Or vbDirectory) 'Include folders as well.
Else
'Strip any trailing slash, so Dir does not look inside the folder.
Do While Right$(strFile, 1) = "\"
strFile = Left$(strFile, Len(strFile) - 1)
Loop
End If
'If Dir() returns something, the file exists.
On Error Resume Next
FileExists = (Len(Dir(strFile, lngAttributes)) > 0)
End Function

Check permission of the directory in VBA Access before creating folder

I'm trying to implement a certain feature in the Microsoft Access Database using VBA, so when a certain button is pressed, it will check first the availability of the folder in a server. If the folder doesn't exist, the corresponding folder will be created. However, the folders have permissions attached to them, which means only certain users can access it, and hence only certain users should create / access the folder. I have tried the following:
on error resume next
If Dir("Server/Data/Celes", vbDirectory) = "Celes" Then
Else
MkDir ("Server/Data/Celes")
End If
But I'm not sure if it's the best way to handle this problem. I use the "On Error Resume Next", so that if the error occurs due to the lack of permission to the folder (that already exists), it will ignore it. What are some better ways to handle this? Thank you.
I also have checked the following links:
https://social.msdn.microsoft.com/Forums/office/en-US/a79054cb-52cf-48fd-955b-aa38fd18dc1f/vba-verify-if-user-has-permission-to-directory-before-saveas-attempt?forum=exceldev
Check Folder Permissions Before Save VBA
but both of them concerns with saving the file, not creating folder.
After several days without success, finally I found the solution:
Private function canAccess(path as string) as boolean
Dim oShell As Object
Set oShell = CreateObject("WScript.Shell")
Dim result As Integer
Dim command As String
command = "icacls " & """" & pfad & """"
result = oShell.Run(command, 0, True)
'Check privilege; file can be accessed if error code is 0.
'Else, errors are encountered, and error code > 0.
If result <> 5 and result <> 6 Then
KannAufDateiZugreifen = True
Else
KannAufDateiZugreifen = False
End If
end function
private sub button_click()
if canAccess ("Server/Data/Celes") then
If Dir("Server/Data/Celes", vbDirectory) = "Celes" Then
Else
MkDir ("Server/Data/Celes")
end if
End If
end sub
The function "canAccess" will simulate the running of the Windows shell, and execute "icacls" to see if the file can be accessed or not. If the function returns true, it means the "icacls" command is successful, which means the folder can be accessed. Otherwise, the file / folder can not be accessed.
I'm pretty sure this can be improved, but for now, it works.
I use the below function that recursively creates the full path (if required) and returns a value indicating success or failure. It works also with UNCs.
Private Function CreateFolder(ByVal sPath As String) As Boolean
'by Patrick Honorez - www.idevlop.com
'checks for existence of a folder and create it at once, if required
'returns False if folder does not exist and could NOT be created, True otherwise
'sample usage: If CreateFolder("C:\toto\test\test") Then debug.print "OK"
'updated 20130422 to handle UNC paths correctly ("\\MyServer\MyShare\MyFolder")
Dim fs As Object
Dim FolderArray
Dim Folder As String, i As Integer, sShare As String
If Right(sPath, 1) = "\" Then sPath = Left(sPath, Len(sPath) - 1)
Set fs = CreateObject("Scripting.FileSystemObject")
'UNC path ? change 3 "\" into 3 "#"
If sPath Like "\\*\*" Then
sPath = Replace(sPath, "\", "#", 1, 3)
End If
'now split
FolderArray = Split(sPath, "\")
'then set back the # into \ in item 0 of array
FolderArray(0) = Replace(FolderArray(0), "#", "\", 1, 3)
On Error GoTo hell
'start from root to end, creating what needs to be
For i = 0 To UBound(FolderArray) Step 1
Folder = Folder & FolderArray(i) & "\"
If Not fs.FolderExists(Folder) Then
fs.CreateFolder (Folder)
End If
Next
CreateFolder = True
hell:
End Function
'Must set a Reference to the Microsoft Scripting Runtime
Dim fso As FileSystemObject
Dim fil As File
Set fso = New Scripting.FileSystemObject
If fso.FileExists("\\serverName\folderName\fileName.txt") Then
'code execution here
Else
MsgBox "File and/or Path cannot be found", vbCritical, "File Not Found"
End If

Issue with Removing References in Microsoft Access

I'm having an issue where when I remove a reference from Access on close it does not stick. I have the function assigned to a hidden window that is always open and it works when I manually close the window, but when I close the database the change to remove the reference doesn't stick. We are having issues since part of our team is on Office 2013 so we have had to make a dynamic reference point in the VBA Code. The code to add the reference works fine, but removing it is the issue.
Here's the code for adding it
Public Function RunThis()
Dim ref As Reference
'For Each ref In Access.References
'MsgBox ref.Name
'Next
If Dir("C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.exe") <> "" Then
Access.References.AddFromFile ("C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.exe")
End If
If Dir("C:\Program Files (x86)\Microsoft Office\Office16\EXCEL.exe") <> "" Then
Access.References.AddFromFile ("C:\Program Files (x86)\Microsoft Office\Office16\EXCEL.exe")
End If
End Function
Here's the code that is run on exit
Private Sub Form_Close()
Dim ref As Reference
For Each ref In Access.References
If ref.Name = "Excel" Then
Access.Application.References.Remove ref
'MsgBox "Found It"
End If
MsgBox ref.Name
Next
DoCmd.Save
End Sub

Opening PDF on specific page number in VBA

I am trying to create a button on my access form that allows for the user to view the corresponding page that goes with the data within the form (In this case, a part number is displayed on the form, and I want the button to open the Part Standard file to show the blueprint/diagram of said part)
I have tried using Adobe's page parameters #page=pagenum at the end of my filepath, but doing this doesn't work.
Here is the code I have (Basic, I know) but I'm trying to figure out where to go here. I have simple condensed down my filepath, for obvious reasons - Note: It's not a URL, but a file path if this matters.
Private Sub Command80_Click()
Dim loc As String 'location of file
'loc = Me.FileLoc
loc = "G:\*\FileName.pdf#page=1"
Debug.Print loc
'Debug.Print Me.FileLoc
'Debug.Print Me.FileName
Application.FollowHyperlink loc
End Sub
Is this possible to do this way? I will continue to read other users posts in hopes to find a solution, and I'll note here if I do find one.
Thanks!
Update
I've found a way to do this, just I have 1 small complication now. My database will be accessed by many users, possibly with different versions of Acrobat, or different locations. Here is my working code:
Private Sub Command2_Click()
pat1 = """C:\Program Files\Adobe\Reader 9.0\Reader\AcroRd32.exe"""
pat2 = "/A ""page=20"""
pat3 = """G:\*\FileName.pdf"""
Shell pat1 & " " & pat2 & " " & pat3, vbNormalFocus
End Sub
Now, here is my concern. This code opens AcroRd32.exe from a specific file path, if my users have this stored elsewhere or have a different version, this won't work. Does anyone have a suggestion as how to possibly get around this?
Thanks again! :)
The correct way to do this is probably to look up the location of the acrobat reader executable in the system registry. I find that's generally more trouble than it's worth, especially if I have some control over all of the places my program will be installed (within a single intranet, for example). Usually I end up using this function that I wrote:
'---------------------------------------------------------------------------------------
' Procedure : FirstValidPath
' Author : Mike
' Date : 5/23/2008
' Purpose : Returns the first valid path found in a list of potential paths.
' Usage : Useful for locating files or folders that may be in different locations
' on different users' computers.
' Notes - Directories must be passed with a trailing "\" otherwise the function
' will assume it is looking for a file with no extension.
' - Returns Null if no valid path is found.
' 5/6/11 : Accept Null parameters. If all parameters are Null, Null is returned.
'---------------------------------------------------------------------------------------
'
Function FirstValidPath(ParamArray Paths() As Variant) As Variant
Dim i As Integer
FirstValidPath = Null
If UBound(Paths) - LBound(Paths) >= 0 Then
For i = LBound(Paths) To UBound(Paths)
If Not IsNull(Paths(i)) Then
If Len(Dir(Paths(i))) > 0 Then
FirstValidPath = Paths(i)
Exit For
End If
End If
Next i
End If
End Function
The function takes a parameter array so you can pass it as many or as few paths as necessary:
PathToUse = FirstValidPath("C:\Program Files\Adobe\Reader 9.0\Reader\AcroRd32.exe", _
"C:\Program Files\Acrobat\Reader.exe", _
"C:\Program Files (x86)\Acrobat\Reader.exe", _
"C:\Program Files\Acrobat\12\Reader.exe")
pat1 = """" & PathToUse & """"
Registry keys are the better way to go, unlike file locations they have consistency between systems.
Below are three functions, two in support of one, and a macro which tests the functions.
GetARE() (Get Adobe Reader Executable) returns the proper path based on a version search in a pre-defined location passed as the argument. This removes the hassle of typing out many different key locations for each version and provides some amount of coverage should future versions be released and installed on a user's system.
I have installed previous versions of Reader to test whether or not the there is consistency in the InstallPath key location, up until quite outdated versions, there is. In fact, mwolfe02 and I both have our keys in the same location, though I am using version 11 and he, at the time of writing, was using 10. I was only able to test this on a x64 system, but you can easily modify the code below to search for both x64 and x86 keys. I expect a large corporation like Adobe to stick to their conventions, so this will likely work for quite some time without much modification even as new versions of Reader are released.
I wrote this quickly, expect inefficiency and inconsistency in naming conventions.
Truly the best approach to ensure the path is almost-always returned would be to simply run a registry search through VBA in a loop for version numbers using "*/Acrobat Reader/XX.YY/InstallPath/" and then including the executable based on a check for the appropriate candidate in the appropriate directory; however, this isn't exactly a very cost-effective solution. My tests have shown that there is quite a bit of consistency between versions as to where the Install Path can be found, and as to what the executable name may be, so I opted for something more efficient if less lasting.
RegKeyRead() and RegKeyExists() were taken from:
http://vba-corner.livejournal.com/3054.html
I have not modified their code. Take into consideration saying thanks to the author of that post, the code is not complex by any means but it did save me the hassle of writing it myself.
Function RegKeyRead(i_RegKey As String) As String
Dim myWS As Object
On Error Resume Next
'access Windows scripting
Set myWS = CreateObject("WScript.Shell")
'read key from registry
RegKeyRead = myWS.RegRead(i_RegKey)
End Function
Function RegKeyExists(i_RegKey As String) As Boolean
Dim myWS As Object
On Error GoTo ErrorHandler
'access Windows scripting
Set myWS = CreateObject("WScript.Shell")
'try to read the registry key
myWS.RegRead i_RegKey
'key was found
RegKeyExists = True
Exit Function
ErrorHandler:
'key was not found
RegKeyExists = False
End Function
Function GetARE(i_RegKey As String) As String
Dim InPath As String
Dim InKey As String
Dim Ind As Integer
Dim PriVer As String
Dim SubVer As String
Dim Exists As Boolean
Exists = False
PriVer = 1
SubVer = 0
For Ind = 1 To 1000
If SubVer > 9 Then
PriVer = PriVer + 1
SubVer = 0
End If
Exists = RegKeyExists(i_RegKey + "\" + PriVer + "." + SubVer + "\InstallPath\")
SubVer = SubVer + 1
If Exists = True Then
SubVer = SubVer - 1
InKey = i_RegKey + "\" + PriVer + "." + SubVer + "\InstallPath\"
InPath = RegKeyRead(InKey)
GetARE = InPath + "\AcroRd32.exe"
Exit For
End If
Next
End Function
Sub test()
Dim rando As String
rando = GetARIP("HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Adobe\Acrobat Reader")
MsgBox (rando)
End Sub
I remember that Acrobat reader used to include some ActiveX PDF reader object available for further use with Microsoft Office. Other companies have developed similar products, some of them (in their basic form) even available for free.
That could be a solution, couldn't it? You'd have then to check that your activeX PDF reader supports direct page access in its methods, and distribute it with your apps, or have it installed on your user's computers. It will avoid you all the overhead related to acrobat readers versions follow-up, specially when newer versions will be available on the market and you'll have to update your client interface.
Just to add to mwolfe02's answer, here is a function that tries to retrieve the executable for the file type given (it also uses the registry commands Levy referenced) :
Function GetShellFileCommand(FileType As String, Optional Command As String)
Const KEY_ROOT As String = "HKEY_CLASSES_ROOT\"
Dim sKey As String, sProgramClass As String
' All File Extensions should start with a "."
If Left(FileType, 1) <> "." Then FileType = "." & FileType
' Check if the File Extension Key exists and Read the default string value
sKey = KEY_ROOT & FileType & "\"
If RegKeyExists(sKey) Then
sProgramClass = RegKeyRead(sKey)
sKey = KEY_ROOT & sProgramClass & "\shell\"
If RegKeyExists(sKey) Then
' If no command was passed, check the "shell" default string value, for a default command
If Command = vbNullString Then Command = RegKeyRead(sKey)
' If no Default command was found, default to "Open"
If Command = vbNullString Then Command = "Open"
' Check for the command
If RegKeyExists(sKey & Command & "\command\") Then GetShellFileCommand = RegKeyRead(sKey & Command & "\command\")
End If
End If
End Function
so,
Debug.Print GetShellFileEx("PDF")
outputs:
"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe" "%1"
and you just have to replace the "%1" with the file you want to open and add any parameters you need.
Here is code the probably you can use..
Private Sub CommandButton3_Click()
Dim strFile As String
R = 0
If TextBox7 = "CL" Then
R = 2
' Path and filename of PDF file
strFile = "E:\Users\Test\Cupertino Current system.pdf"
ActiveWorkbook.FollowHyperlink strFile
End If
if R = 0 Then
MsgBox "Wrong Code"
ComboBox1 = ""
TextBox1 = Empty
'ComboBox1.SetFocus
End If
End Sub
Just need to the right path.. Hope this can help you

Mail Merge from Access - Save Merged Document

I am attempting to open a document from access, execute a mail merge, and then save the document output from the merge using VBA.
Here is my current attempt:
Dim templateName as String, tempRoot as String
tempRoot = "C:\report\"
templateName = tempRoot & "template.doc"
Dim objDoc As Word.Document
Dim objWord As New Word.Application
Set objDoc = objWord.Documents.Open(templateName)
objWord.Visible = True
exportData "AnnualData", tempRoot & "annualData.txt" 'Outputs query to txt file for merge
objDoc.MailMerge.OpenDataSource NAME:= _
tempRoot & "annualData.txt", ConfirmConversions:=False, ReadOnly _
:=False, LinkToSource:=True, AddToRecentFiles:=False, PasswordDocument:= _
"", PasswordTemplate:="", WritePasswordDocument:="", _
WritePasswordTemplate:="", Revert:=False, Format:=wdOpenFormatAuto, _
Connection:="", SQLStatement:="", SQLStatement1:="", SubType:= _
wdMergeSubTypeOther
objDoc.MailMerge.Execute
objDoc.Close False 'Ideally after closing, the new document becomes the active document?
ActiveDocument.SaveAs tempRoot & "testReport.doc" 'And then save?
Set objWord = Nothing
Set objDoc = Nothing
I get the merged document, however, I am unable to save it. I receive an error stating that the command cannot be performed when no document is open.
If anyone can provide any suggestions, it would be appreciated.
Changed ActiveDocument to objWord.ActiveDocument. Ended up with the desired results.
Thanks Remou.
I just went through this. Here's what I'm doing and it works well. oDocument is the merge form that the user selects via an open dialog box. The excel file is the query that I've previously exported and stuck in the users temp folder. I tried this technique with Access queries and temp tables, but found that using excel was much more trouble free.
The Sleep command is from an imported system dll function ( Public Declare Sub Sleep Lib "kernel32" (ByVal dwMS As Long) ) and gives Word time to run the merge. Actually, that may be all you need. This is using Office 2007.
If Not oDocument Is Nothing Then
' get merge source file
Set oFSO = New FileSystemObject
Set oFolder = oFSO.GetSpecialFolder(TemporaryFolder)
strTempFile = oFolder.Path & "\PTDMergeSource.xls"
' run merge
With oDocument.MailMerge
.MainDocumentType = wdFormLetters
.Destination = wdSendToNewDocument
.OpenDataSource strTempFile, WdOpenFormat.wdOpenFormatDocument, False, False, False, False, , , , , , , "SELECT * FROM `tblMerge$`", , False, WdMergeSubType.wdMergeSubTypeAccess
.Execute True
End With
Sleep 2
oDocument.Close False
Else
MsgBox "Action was cancelled, or there was an error opening that document. Please try again, then try opening that document in Word. It may be someone else has locked that document (they are editing it). If the problem persists, email the document to the support contractor."
End If