How to close file handle after Application.run in Access - ms-access

I have a kind of complex scenario
The Access-Application we are developing calls a VBA Function in another Access-Application which then triggers an Install Routine by calling the "calling" Access-Application.
Both Applications are either accda (for Access-AddIn) or compiled accde files.
Admin.accda -> PlugIn.accda (InstallPlugIn) -> Admin.accda (RegisterPlugIn)
The corresponding line in Admin.accda looks like:
Application.Run("\\Path\To\PlugIn\PlugIn.InstallPlugin", "Parameters")
Everything works as expected except for one thing. Access keeps the file handle on the PlugIn.accda open until I close the Admin.accda.
When I debug my Code I can see that after calling Application.Run() in Admin.accda the VBA-Code of PlugIn.accda gets loaded into Admin.accda and a Lockfile for PlugIn.accda (PlugIn.laccda) gets created. When my code finishes the Lockfile has 0 Bytes but still exists.
By using the ProcessExplorer from Sysinternals I can see that the Process in which the Admin.accda is running still has a Filehandle open to the PlugIn.accda.
I want to close that file handle because there is nothing more to do with the PlugIn.accda and the open Filehandle could interfere with Process in the Clients Client/Server deployment like copying the PlugIn to the Client Computer.
Until now I have tried two different approaches but without success:
First:
I tried to call the Run on Access-Application-Object and close that Object afterwards
Dim oApplication As Object
Set oApplication = CreateObject("Access.Application")
oApplication.OpenCurrentDatabase Me.txtPathPlugIn
If oApplication.Run("\\Path\To\PlugIn\PlugIn.InstallPlugin", "Params") Then
'Do something
End If
oApplication.CloseCurrentDatabase
Set oApplicaton = Nothing
With that approach the PlugIn was unable to call the RegisterPlugIn Function in the Admin.accda because it was exclusivly opened in another Process.
Second:
I tried to close the PlugIn Database after the call to RegisterPlugIn in the Admin.accda by using:
CodeProject.CloseCurrentDatabase
But that had no effect what so ever :-(
Is there a way to close File handles by using some Windows-API functions? Or does anyone has an better approach to dynamically register additional Access files?

Maybe this is a dumb solution but just throwing it out there, you could use the taskkill command ?
http://technet.microsoft.com/en-us/library/bb491009.aspx

Related

Running Microsoft Access as a Scheduled Task

I am seeking comments on how to schedule auto updates of a database (.accdb) since I am not very comfortable with the process I have set up.
Currently, it works as follow:
Task Scheduler calls a .bat
.bat calls a .vbs
.vbs opens the database and calls a macro
The macro calls a function (VBA Level)
The function calls the update Subroutine
I consider there are too many steps and the fact that it requires 2 external files (.Bat and .vbs) related to the database and stored on the system increase the risk that the procedure would break.
Apparently (but please tell me that I am wrong and how I can change it) .vbs cannot call a subroutine but only a macro. Identically, an access macro cannot call a subroutine but only a function if the user is expecting to enter the VB environment of the database. This is the reason why I called a function (VBA Level) that then calls the subroutine.
Hope some of you know how to shorten the steps and eventually get ride of the .bat and .vbs
To the best of my knowledge the shortest path for a Windows Scheduled Task to "do something useful in Access VBA" is:
Create a Public Function (not Sub) in the database. For example:
Option Compare Database
Option Explicit
Public Function WriteToTable1()
Dim cdb As DAO.Database
Set cdb = CurrentDb
cdb.Execute "INSERT INTO Table1 (textCol) VALUES ('sched test')", dbFailOnError
Set cdb = Nothing
Application.Quit
End Function
Create a Macro in the database to invoke the function:
Create a Windows Scheduled Task to invoke MSACCESS.EXE with the appropriate parameters
In the above dialog box the values are:
Program/script:
"C:\Program Files\Microsoft Office\Office14\MSACCESS.EXE"
Add arguments (optional):
C:\Users\Public\schedTest.accdb /x DoSomething
A VBS script can call any standard VBA SUBROUTINE with the following:
dim accessApp
set accessApp = createObject("Access.Application")
accessApp.OpenCurrentDataBase("C:\MyApp\MultiSelect.mdb")
accessApp.Run "TimeUpDate"
accessApp.Quit
set accessApp = nothing
Note that the sub TimeUpDate is a standard VBA subroutine. This means no Autoexec macros, and no macros at all - only pure VBA sub calls + this VBS script.
There is a little known trick dating back to the earliest years of access to allow it to run as a process which still works. Access will always on startup look for a macro called "Autoexec". If it finds it it will immediately start executing this macro. I find this is extremely useful if I need to initialise the program before opening forms or, as in the case of the original questioner, run access as a scheduled process with no user I/O.
After beating my head against the wall for about four hours, I finally got this to work:
1) Create a DOS batch file with one line it. The line is composed of three parts a) the full path to Microsoft Access (msaccess.exe), b) the full path of the Microsoft Access database with the code in it, and c) the Access command line argument "/x MacroName". The first two items should be surrounded with quotes. Mine looks like this:
"C:\Program Files (x86)\Microsoft Office\Office14\MSACCESS.EXE" "C:\MyPrograms\ProdDB Reports\ProdDB Reports.accdb" /X DailyTestReportsRun
2) Create a macro inside of Access with the name you used in your batch file. It has one command, RunCode, with an argument of the name of a VBA function you want to call. This should be followed by open/close parenthesis "()". I didn't try passing any parameters to the function; I think this would be problematic.
4) Make sure the VBA function you call has a Docmd.Quit command at the end, or that you add this as a second line to your macro. These will make sure that Access doesn't stay open after your process runs.
5) In Windows Task Scheduler, select "create a basic task" (which invokes a wizard). Set the program name to the name of your DOS batch file. There's a helpful check box labeled something like "Open the properties window when I'm finished." Check that so you that go to the properties window.
6) Set the task to run regardless of whether the user is logged on or not. Also check on the "Run with highest privileges" box, which one friend on here suggested.
You can now test everything by right-clicking the scheduled task and selecting the Run command.
I liked Albert Kallal's script and tried it. Everything worked great until I tried to schedule it. Then, for some mysterious reason the scheduler would not kick it off.
none of the above work
ms access DB with task scheduler will not work as to open the db run code and quit the application
the solution I found is to avoid task scheduler and have the ms access db open all time and have a timer in msaccess do the job

Getting at VBA/Access Source Code

I'm rewriting a legacy access / VBA application in C#. While I am limited as a VBA programmer I am under the impression that the code is minimally compiled and runs almost as a script ? Obviously no security / VBA you can just hit alt + f11 to get at the source code is there a good way to decompile / get at this code?
So I tried this: http://forums.databasejournal.com/showthread.php?t=34222
which appears to be about how to decompile .mdb files.
However the program quickly recompiled itself - or at least says it is recompiling itself in the lower left status bar. Any ideas?
Here are some suggestions (some of which I'm repeating from comments I've made above):
Alt-F11 is not my usual method of opening the VBE, because I usually want to go to the Immediate Windows. Try Ctrl-G, instead.
If both Alt-F11 and Ctrl-G fail to open the VBE, then perhaps the AllowBypassKey property of the database has been changed to False. To get code to change this, search the Access help file for AllowBypassKey (in the VBE, from the help menu, search for "AllowBypassKey"). However, you won't be able to run the code within the database you're trying to investigate if AllowBypassKey is turned OFF, so you can run this code:
//
On Error GoTo Change_Err
Dim db As DAO.Database
Dim prp As Variant
Const conPropNotFoundError = 3270
Set db = DBEngine.OpenDatabase("C:\Databases\MyDatabase.mdb")
db.Properties("AllowBypassProperty") = True
exitRoutine:
If Not (db Is Nothing) Then
db.Close
Set db = Nothing
End If
Exit Sub
errHandler:
If Err = conPropNotFoundError Then ' Property not found.
' do nothing and exit
Resume exitRoutine
End If
Then you should be able to open the database when holding down the SHIFT key (which bypasses any defined startup routines, which might have been shutting off access to the VBE).
If the file is an MDE, there is no source code. You can find out if it's an MDE by checking this property:
?CurrentDB.Properties("MDE")
If it's an MDE (the file can have any extension), this will return "T". If it's not an MDE, it will throw an error (because the property doesn't exist).
Other things to check might be how many modules there are. If you have the database open and can get to the Immediate Windows (Ctrl-G), then this will tell you if there are any modules:
//
?CurrentProject.AllModules.Count
You also might be able to see what's in the database by opening up the Object Browser in the VBE (F2) and selecting the project name in the dropdown at the top (it will say "" by default
Last of all, you may think that it could be protected by Jet ULS, but starting with Access 2000, that's not a big possible, as there's nothing but a password on the VBA project available (i.e., it's no longer covered under Jet ULS). I would expect that if it were password-protected, you'd be prompted for the password somewhere along the line, so you'd already know that.
If it's an .mde you are out of luck. You need to get hold of the .mdb containing the source VBA code that was compiled to create the .mde
HOW TO VIEW SOURCE CODE (.mdb FILE)
Select the file
Hold the 'shift' key
Then double click the file without releasing the 'shift' key
Navigate to the Program Folders on your computer.
Open the MS Office Folder
Navigate to MSAccess.exe and make a shortcut of it.
Move the shortcut to where your database resides (makes life easier and you'll need to do this for each database to decompile).
(optional) Rename the shortcut to something like "decompiler for my database whose name is...." so you know that opening it blows away your compiled code
Right-click on the shortcut to view its properties. In the Target field of the properties dialog box for the shortcut you'll see something like "C:\Program Files\Microsoft Office\Office\MSACCESS.EXE"
In the target field, right-arrow to the end of the pathname, type a space, and then, in quotes, type (or paste) the full pathname of the location of your MS Access file to decompile (easiest way is to copy from the address bar on your window where the file resides, if you have the address bar showing).
Still in the Target field, type a space, and then with no quotes, type /decompile
Click OK to accept the changes and to close the dialog box.
Hold the Shift key down and keep it down.
Wiggle your ears
Double-click on the shortcut.
Go to any module in your database.

Access 2003: run code on database close

In Access 2003, there are ways of running code when a form or database is opened, but what about when the database is closed?
My motivation is the unavoidable use of a somewhat buggy third-party COM library. Releasing the COM reference (by setting the variable to Nothing) causes it (correctly) to disconnect from its server. The trouble is it can't then re-connect without exiting the process and starting a new one (which is a known bug). In this case, the process is the whole Access IDE :(
Ideally I'd like to store the COM reference somewhere that would be protected from the VBA "Reset" action which clears global variables (and is common during debugging, sometimes forced by a code edit). But then I would like to have the chance to clean up before the database is closed.
You could run code at database close if you have a form which you set to automatically open at database startup ... and leave the form open. Then you can use the form's On Close event to run your cleanup code:
Private Sub Form_Close()
'do your stuff here '
End Sub
don't release the reference. Let Access do that when it closes.

Running an external script from Access

I want my Access application to run an external program (in this case a R script) after the user clicks a button. I use this code:
Dim RetVal
RetVal = Shell("""C:\Program Files\R\R-2.10.1\bin\R.exe"" CMD BATCH --no-environ --silent --no-restore --no-save ""c:\test.R"" ""c:\test-result.txt""", vbHide)
MsgBox RetVal
This works fine, but the VBA code keeps on running while my script is executed. How can I make Access waiting for the script to be finished? Has anybody suggestions about how to give an error message of the script back to Access?
The OpenProcess and WaitForSingleObject combo that #Remou links to, is probably your best bet for doing this. You should take a look at this, it's a nice drop in module for shell and wait.
For returning a message back from the script, you could mess around with redirecting the scripts input and output. This is not for the faint of heart. As an alternative I would redirect the output of the script to a text file, then read in that file after it exits.

VBA File Open is slow

I'm trying to open a series of Excel spreadsheets using an instance of Excel created inside of a module in an Access database. I can get the files to open properly; however, the actual call to make Excel start takes quite a while, and to open the files takes even longer. The location of the files doesn't matter (same time to open on a local HDD as a network drive).
In an attempt to figure out what was taking so long, I added a timer to the logging module. Opening the files takes approximately 2m30s, during which the host application (Access) is entirely unresponsive to user input); the rest of the script executes in less than 10 seconds.
I'm using the standard Excel.Workbooks.Open call as follows
Set OpenSpreadsheet = Excel.Workbooks.Open(Name, 2, False)
Using Debug.Print methods around this line says it can take up to 2 1/2 minutes for this one line to execute.
Is there anything I can do to make the Excel files open quicker?
EDIT: When opening, UpdateLinks is False and ReadOnly is True; all other options are left to their defaults.
First idea: Can you use a jet driver with an ODBC connection to Excel, instead of opening it in an Excel object? Might be much faster.
Second idea: Make sure to create and instantiate the Excel application object just once at the beginning of the routine, then use the Excel.Workbooks.Open() and Excel.ActiveWorkbook.Close() for each spreadsheet. That way you're not "re-launching" the MS Excel application each time.
To draw out the second of #BradC's well-advised recommendations, if you need to use Excel in more than one procedure, create a self-initializing global function. I always use late binding for automating Office apps.
Public Function Excel(Optional bolCleanup As Boolean = False) As Object
Static objExcel As Object
If bolCleanup Then
If Not objExcel Is Nothing Then
Set objExcel = Nothing
Exit Function
End If
End If
If objExcel Is Nothing Then
Set objExcel = CreateObject("Excel.Application")
End If
Set Excel = objExcel
End Function
Then you can use it in code without needing to initialize it, and the single instance will remain available to any code that needs to use Excel.
And when you shut down your app, you'd call Excel(True) to clean up.
I do this with Outlook and Word all the time. However, there are some COM apps that it works poorly with, such as PDF Creator, which doesn't take kindly to this kind of treatment (you end up in an endless loop with it shutting down and re-initializing itself if you try to destroy the instance this way).
Another approach depends on your setup and process.
In my case I need read-only access to a set of Excel documents stored on SharePoint. My code is currently like:
For each path in paths
set wb = Workbooks.open(path,false)
next
In this case, each workbook is individually downloaded each time a workbook is opened. It would be significantly more efficient if the files were downloaded asyncronously and after the download is complete, the rest of the process executes on the local disk.
My idea is to use CopyFileEx() and pass a callback function. Excel will then download the Excel documents to disk asynchronously and call a VBA function regarding progress. When all files are completed, we can launch the next part of the process, which opens the local workbooks, scans them and then removes them from the local drive.
I'll post code later if I manage to implement it.