Executing function on hundreds of databases - vba

I want to embed a simple piece of VBA Code in Access 2007. I need to execute this code on hundreds of different Access DBs, so I don't want to manually paste the code into each and every DB. Is it possible to do this? Maybe by way of an add-in?
Thanks
Karl
EDIT
I want to execute the following VBA code:
DoCmd.DeleteObject acTable, "LastNum"
DoCmd.TransferDatabase acLink, "ODBC Database", "ODBC;DSN=myDB;UID=User1;PWD=123;LANGUAGE=u s_english;" & "DATABASE=LastNumber", acTable, "LastNum", "LastNum"
How would I translate this into a VB addin?
The visual studio VB add-in template looks like this:
imports Extensibility
imports System.Runtime.InteropServices
<GuidAttribute("B61E2444-F46E-4591-A8BA-3D06A4E5D84C"), ProgIdAttribute("MyAddin1.Connect")> _
Public Class Connect
Implements Extensibility.IDTExtensibility2
Private applicationObject As Object
Private addInInstance As Object
Public Sub OnBeginShutdown(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub
Public Sub OnAddInsUpdate(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub
Public Sub OnStartupComplete(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnStartupComplete
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnDisconnection
End Sub
Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
applicationObject = application
addInInstance = addInInst
End Sub
End Class
EDIT PART 2:
Ok, so I figured out I should do the following:
imports Extensibility
Imports System.Runtime.InteropServices
Imports Microsoft.Office.Core
Imports Access = Microsoft.Office.Interop.Access
<GuidAttribute("B61E2444-F46E-4591-A8BA-3D06A4E5D84C"), ProgIdAttribute("MyAddin1.Connect")> _
Public Class Connect
Implements Extensibility.IDTExtensibility2
Private applicationObject As Access.Application
Private addInInstance As Microsoft.Office.Core.COMAddIn
Public Sub OnBeginShutdown(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnBeginShutdown
End Sub
Public Sub OnAddInsUpdate(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnAddInsUpdate
End Sub
Public Sub OnStartupComplete(ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnStartupComplete
End Sub
Public Sub OnDisconnection(ByVal RemoveMode As Extensibility.ext_DisconnectMode, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnDisconnection
End Sub
Public Sub OnConnection(ByVal application As Object, ByVal connectMode As Extensibility.ext_ConnectMode, ByVal addInInst As Object, ByRef custom As System.Array) Implements Extensibility.IDTExtensibility2.OnConnection
applicationObject = CType(application, Access.Application)
addInInstance = CType(addInInst, Microsoft.Office.Core.COMAddIn)
' This line enables VBA to call back into this object.
addInInstance.Object = Me
End Sub
Public Sub ChangeLink()
applicationObject.DoCmd.DeleteObject(Access.AcObjectType.acTable, "LastPolNum")
applicationObject.DoCmd.TransferDatabase(Access.AcDataTransferType.acLink, "ODBC Database", "ODBC;DSN=ZACANTDB02;UID=EDIPolicyNumber;PWD=museum123;LANGUAGE=u s_english;" & "DATABASE=EDIPolicyNumber", Access.AcObjectType.acTable, "LastPolnum", "LastPolNum")
End Sub
End Class
Now what I want is to be able to execute ChangeLink() from Access. How do I do that?

Depending on what the code is for, there are (at least) two different ways to do this, without working at the VB Extensions level (which is where you'd copy and paste code, at least in VBA):
Make the code part of an add-in. It can then be loaded, and stay loaded and exposed, to any other application.
If the code is working on data only, consider writing a procedure that links and unlinks the table(s) in each mdb file that needs processing.
The advantage of #2 over #1 (and, indeed, over copying and pasting code) is that you can invoke your simple routine once, not hundreds of times.
EDIT:
There are two ways to invoke an add-in, depending on who is meant to invoke its functionality, and how.
Through the UI. Use this when you want an end user to be able to use it, especially practical for functionality you'll invoke often. How to:
a. Office button > Access options > Add-ins tab
b. If it's a .NET add-in (or any other non-Access add-in), choose COM Add-in from Manage dropdown. If it's Access, choose Access. Click go
c. Click Add button in the form that pops up, browse to the add-in.
You will still need to provide some sort of UI to it: a commandbar, a ribbon button, a form, something. That's a separate discussion
Through code. Use this if you're just wanting to invoke it as part of some other VBA procedure.
a. VBE window > Tools > References
b. Browse button
c. Change the dropdown in file of type if you're setting up an Access add-in.
You can then call the line of code directly, as if it were part of your current project. If you wish to be more explicit, you can also invoke it by library name: MyAddInName.ChangeLink

Seems to me that you don't need to run this code from the Access databases -- all you need to do is run it on all the databases.
To do that, you'd use DAO to open each database in turn, delete the table, then create the link. You'll have to do both of those steps with DAO (deleting from the TableDefs collection, and adding to it) rather than with DoCmd operations (which are unavailable through DAO).
Of course, if the databases are no accessible from a central location, you can't do this. But if that's the case, how would you alter the code or invoke an add-in?
Another way to use a library database is to use Application.Run:
Application.Run "\Server\PathToAddIn\MyLibrary.FixTables"
This assumes:
MyLibrary is an MDE/ACCDE.
in MyLibrary database is a subroutine or function called FixTables that runs the code you want to execute.
Indeed, with that method, you can actually use DoCmd, since the add-in is running within Access and in the context of the currently opened database.
However, note that some people seem to have difficulties executing add-ins in this fashion (I'm currently in a long discussion with somebody in another forum who can't seem to get it to work). I've been using this method for library databases for years and years and have had no problems (as long as you specify a full path), so I'm puzzled why the ones I'm discussing it with can't seem to make it work.

Related

How to instantiate a vba class and call a method from vb.net?

As I guess many are, I'm sitting with an ms access application with a mixture of tables, VBA Modules and VBA Classes. I intend to migrate that application to VB.NET.
However it will take some time and I would like to make use of automation to slowly move the code to VB.NET
Now I can call regular SUB and Functions from my VB.NET application but wonder if there is a way to invoke the methods of user defined objects.
Rough example what I want to do
VBA
'Class1
Public Sub Test()
Print "Hello world"
End Sub
'Module1
Public oClass1 as Class1
Public Sub Init()
Set oClass1 = New Class1
End Sub
VB.Net
' Left out the opening of the access db
oAccess.Run("Init")
oAccess.Run("oClass1.Test())
Is it even possible?
The Application.Run method requires a string containing "The name of the Function or Sub procedure to be run" as its first argument. But "oClass1.Test" is neither.
You could work around that issue by creating another VBA procedure which wraps your oClass1.Test method, and run the wrapper procedure ...
oAccess.Run("Wrap_oClass1_Test") ' no parentheses after procedure name
Public Sub Wrap_oClass1_Test()
oClass1.Test
End Sub
I confirmed that approach worked with the rest of your sample code when called from VBScript so I believe it should also work from VB.Net.
Tim's CallByName suggestion also looks promising, but I didn't test that one.

Hide VBA procedures from Excel application but not from other projects

I know this is a long shot, but with the limitations in "Option Private Module" and even worse "Private Sub/Function", does anyone know if there is a way of hiding VBA procedures from the Excel application but not from other projects?
I have an XLAM with a subset of reusable functionality that I like to include and reference from new Excel projects, but using "Option Private Module" hinders this and if I omit it, a bunch of unusable or obscure functions and subs become visible and available to the application.
Convert your standard modules in the XLAM to class modules (set to
Public Not Creatable);
Create an additional Class Module that returns an instance (with a
bit of additional work, the instance) of each such module; and
Create a single standard module with one property that returns the instance of the main class-entry module.
Class1:
Option Explicit
Public Sub IAmInvisible()
End Sub
ModuleEntry:
Option Explicit
Private mClass As New Class1
Public Property Get TheClass() As Class1
Set TheClass = mClass
End Property

Intellisense in private modules

Assume I have a module named Module1 with the following code:
Private Sub MyPrivateSub()
'do something
End Sub
Public Sub MyPublicSub()
'do something
End Sub
Public Sub test()
End Sub
If I place the cursor inside Test() and start typing "MyPrivateSub" or "MyPublicSub", I don't get any intellisense. I can type "Module1." (or "Me." if Module1 were a class module) to get an intellisense menu, but this only contains the public method MyPublicSub, as shown here:
Is there some way to get an intellisense menu for all members, public and private? I'm working on a project with modules that have many methods, and going up and down continually to copy/paste member names is time consuming.
Short answer
hit a combination of
CTRL+SPACE
and start typing the sub name like shown here
Long answer
Everything you do in programming matters. When you decide to make your sub/function/variable private you do that for a reason and you need to understand how this will affect the access level and "scope".
The scope of a member is dictated by its access level and in VBE anything that is private and qualified with it's parent member does not get intelli-sense. Simply, because you can't (are not supposed to be able to access it) from the outside.
Because your MyPrivateSub access level is Private you can't access it with intelli-sense through qualifying the module with Module1..
If you're starting from fresh, you can do the following:
for all your private sub function, name them into a standard format but as public function at the moment, for e.g.:
Public sub iamprivate_calDate ()
Public sub iamprivate_getsetfunction()
...
after you're completed with your macros, simply find and replace all your "Public sub iamprivate" into "Private sub " in your editor.

Is there a way to determine the value of a property on a form that calls a method in a seperate class library

Specifically aimed at winforms development.
I suspect that the answer to this is probably No but S.O. has a nice way of introducing me to things I didn't know so I thought that I would ask anyway.
I have a class library with a number of defined methods therein. I know from personal experimentation that it is possible to get information about the application within which the class library is referenced. What I would like to know is whether it would be possible to get information about the value of a property of a control on a form when a routine on that form calls a method in my class library without passing a specific reference to that form as a parameter of the method in the class library?
So purely as an example (because it's the only thing I can think of off the top of my head). Is there a way that a message box (if it had been so designed to do so in the first place) could 'know' from which form a call to it had been made without that form being specifically referenced as a parameter of the message box in the first place?
Thanks for any insights you might have.
To address the example of the MessageBox, in many of the cases you can use the active form. You can retrieve it by using Form.ActiveForm. Of course, as regards the properties that you can request, you are limited to the properties provided by the Form or an interface that the Form implements and that the method in the other assembly also knows. To access other properties you can use Reflection, but this approach would neither be straightforward nor would it be clean.
In a more general scenario, you could provide the property value to the method as a parameter. If it is to complex to retrieve the value of the property and the value is not needed every time, you can provide a Func(Of TRESULT) to the method that retrieves the value like this (sample for an integer property):
Public Sub DoSomethingWithAPropertyValue(propValFunc As Func(Of Integer))
' Do something before
If propertyValueIsNeeded Then
Dim propVal = propValFunc()
End If
' Do something afterwards
End Sub
You call the method like this:
Public Sub SubInForm()
Dim x As New ClassInOtherAssembly()
x.DoSomethingWithAPropertyValue(Function() Me.IntegerProperty)
End Sub
I kind of question your intentions. There's no problem sending the information to a function or the constructor.
Instead of giving the information to the class, the class would ask for the information instead using an event.
Module Module1
Sub Main()
Dim t As New Test
AddHandler t.GetValue, AddressOf GetValue
t.ShowValue()
Console.ReadLine()
End Sub
Public Sub GetValue(ByRef retVal As Integer)
retVal = 123
End Sub
End Module
Class Test
Public Delegate Sub DelegateGetValue(ByRef retVal As Integer)
Public Event GetValue As DelegateGetValue
Public Sub ShowValue()
Dim val As Integer
RaiseEvent GetValue(val)
Console.WriteLine(val)
End Sub
End Class

Merged projects, how to access public subs in main project

I have VS 2012 with the main project as a Windows form application and an added project as a excel workbook. I have some code that are exactly the same on both projects so I am trying to save time by sharing that code.
From my excel project I added a reference to my windows form project and imported the namespace. I can access the public functions on my main project, but I cannot seem to be able to access my public subs.
I also tried creating a module a adding a link between the projects but that would cause me to also update the code in two places. Besides, I think that creating a link may also cause some issues at deployment.
For example, in my windows form project I have the following I want to access from my second project
Public Sub closeXLApp()
'This sub is called to close the application without
'saving any changes to the workbook. The sub closes
'the app, workbook and sheet and performs some garbage clean up
'as well making sure that the opened Excel instance is cleared from memory.
xlBook.Close(SaveChanges:=False)
xlApp.Quit()
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheet)
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook)
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
xlSheet = Nothing
xlBook = Nothing
xlApp = Nothing
GC.Collect()
End Sub
On my second project I created a reference and imported the namespace as:
Imports Compensation_Template_Welcome_Page
So when I try to access the above public sub from my second project as:
Private Sub btnMinCloseProject_Click(sender As Object, e As EventArgs) Handles btnMinCloseProject.Click
'This procedure runs when the btnMinCloseProject is clicked. The
'procedure calls the function to close workbook without saving changes.
closeXLApp()
End Sub
I get an error saying that the sub is not declared or not accessible due to its protection level.
Is there a better way to accomplish this? Even if is a longer route, I just want it to make it efficient in the long run.
Thanks
You're sub closeXLApp() is not static (shared), so you need to create an instance of the class where this sub is containted and then call the sub.