Centralized VBA code for multiple Word files - vba

I have multiple files with the same VBA code in them, which will most likely have to be changed over time. I don't want to go one by one and c/p the code, so one obvious idea that came to mind is to have one centralized document with the code (template?) that all files refer to.
I found a few topics here at stackoverflow, but none of them work for me or are inconclusive:
Run external vba-code in MS Word
Centralized VBA code (one file) for multiple workbooks
Calling an External VBA from VBScript
Any ideas?

Yes, your idea of centralizing the code in a template and attaching that template to the various Word docs will work. These pics are using Word 2007 but I think it's pretty similar for newer versions. First create a new document and put the code in a Module (named "CommonFunctions" in the picture):
Save the doc as a macro-enabled template:
Now make a new document, save it as a *.docm (necessary for it to run code because a *.docx is macro-free), and attach the document template as shown below. (An alternative is to put the *.dotm file in the startup directory C:\Users\<username>\AppData\Roaming\Microsoft\Word\STARTUP, which loads it automatically.) If you don't put it in the Startup, you'll see the name but it won't be loaded (ie, checked), but you can load it in code, as I'll show.
Make a user form in this document:
Put this code for the button's click event:
If AddIns("c:\_b\MasterDocWithFunctions.dotm").Installed = False Then
AddIns("c:\_b\MasterDocWithFunctions.dotm").Installed = True
End If
Application.Run "CommonFunctions.Test1"
Application.Run "CommonFunctions.Test2"
Notice how the code can load the AddIn for you, because it won't be automatically loaded unless you put it in the Startup directory. This pic shows how the AddIn is referenced but not loaded. You need the checkbox to be able to call code in it. If you don't load it using code and don't put it in the Startup, then the user will have to manually put a checkmark everytime the document opens.
Now the form should work when you click the button. Notice how it can call both public and private functions. The Private keyword definitely shields one module from another, but it seems like modules that are called from an AddIn are considered to be part of the same module, not sure why? Also if you don't put Public or Private at all then VBA considers it Public, I'm pretty sure.

Related

How to keep executing code after a Documents.add in VBA (Word 2016)?

In our office we are currently using templates containing macro's. We are about to upgrade to Office 2016, but unfortunately the macro's don't work completely as they used to.
The current implentation is that a template is opened from a custom dialogue, and that a Document_New() is called in the template. This does not seem to work anymore: the Document_New() is only called when a template is opened from the file explorer, not when it's opened by a Documents.Add() in another macro.
Alternatively, I found a lot of solutions where Documents.Add is called, and then other functions are being invoked on that new document. For example
Set doc = Documents.Add(Template:=strSkeuze, NewTemplate:=True)
Call MsgBox(doc.Name)
In Word 2016 this doesn't seem to work. The MsgBox isn't invoked and when I step through the code in debugging mode, the code stops executing after the Documents.Add().
However I cannot find anywhere that this is a known change and I am looking for a workaround to still execute code, either from the template like with the Document_New() or from the parent Macro that opens the document.
Could someone tell me whether this is still possible and how to solve this?
You ought to be able to detect the added document using the Application's Document_New event, either that it fires (presuming that you have been using the Document's Document_New event) or by generating the event artificially by counting open documents on the first action taken after the document is added.

How to call a VBA-macro in another MS Word document?

in MS Excel I have been distributing a xls-file that contains a macro. From the users "random" current open workbook I have successfully been calling this macro by telling Word in which file to look for it. Like this, when the user clicks a button on the ribbon:
C:\\"'MyExcelFileWithTheMacro.xls'!MyMacroName"
Can I do the same in MS Word?
That is, to call a macro in a Word-file located in any given folder from an instance of Word that has not opened that file?
I have tried, of course, but Word keeps saying it can't find the macro.
The reason I want to do it this way is that it is makes for easy distribution and updating of the macro. Next up is signing the macro. I hope it can be done when doing things this way, but not sure.
No, it's not possible to call and run a macro in any Office application if the file that contains the macro is not loaded in the application interface.
That being said, if you place the macro in a *.dotm template and have the user put the template in the STARTUP folder used by the Word application, then Word will load the template as an "Add-in". And in that case you should be able to access the macro.
Or, if you don't want it in the Startup folder and you have code that automates the Word.Application, anyway, then that code can load the *.dotm as an "Addin", which should make the macro available (and remove it when you're done with it). Research Application.Addins.Load and Addin.Installed in the Word language reference as well as on-line for examples.

Prevent Word macro in Normal.dotm for some templates

I have a Normal.dotm file that contains an AutoNew macro.
This macro is automatically executed each time a new document is created using any other template.
Is there any way I can prevent this automatic behavior for a specific template?
I have a Word VSTO add-in running, so I can hook into Word's events, but so far I havn't found a way to prevent this.
I do know that I can prevent macro execution when using templates programmatically, for example like this:
' Disable auto-macros before opening document
wordApplication.WordBasic.DisableAutoMacros(1)
' Open document
newWordDocument = wordApplication.Documents.Open(template.FullName, ConfirmConversions:=False, [ReadOnly]:=True, AddToRecentFiles:=False, Revert:=True)
' Re-enable auto-macros
wordApplication.WordBasic.DisableAutoMacros(0)
But this solution doesn't work when the user uses a Word template from Windows explorer or the Open-dialog in Word, since in those cases I can't execute code before it's too late already.
Or can I?
I hope someone has a trick for me :-)
-
Edit: While trying different solutions, I discovered something that might help others in similar situations, though unfortunately it doesn't help me.
It seems that if a template contains a module containing an AutoNew (or AutoOpen for that matter), that local macro is executed instead of the one in Normal.dotm.
Example:
Normal.dotm contains the following macro:
Sub AutoNew()
MsgBox "Normal.dotm"
End Sub
Test.dotm contains the following macro:
Sub AutoNew()
MsgBox "Test.dotm"
End Sub
When executing Test.dotm the message "Test.dotm" is displayed, while the message "Normal.dotm" is not displayed.
If the AutoNew macro is removed from the Test.dotm template, the message "Normal.dotm" is indeed displayed.
So it is possible to easily override the auto-macros.
The local versions of AutoNew and AutoOpen can even be empty subs that do nothing. It still works.
This is not possible in my case though, since the template I use is generated by code, and cannot contain macros (because adding macros to templates programmatically requires the user to manually activate the option "Trust access to the VBA project object model", and that's something I cannot ask my customers to do for all users. It's also a security risk.)
Based on the workaround described in the "Edit" part of the question - providing a template with "empty" Auto-macros - it's possible to use the Open XML SDK to create a template and add the VBA project to it in order to provide this functionality. This approach avoids the user needing to allow access to the VBA project on his installation. The only "macro security" that could be triggered is that for not allowing macros to run. But since the client uses macros, anyway, this should not be a major obstacle.
The simplest method is to create as much of the basic template as possible in the Word UI and use this as a starting point.
Since you're unfamiliar with the Open XML SDK, the next step would be to create one (or more) templates in the Word UI using the basic template as the starting point, saving under a different file name.
You can then use Open XML SDK Productivity Tool to see the code required to generate any one of these files, as well as, using the Compare tool, the code for converting the basic template to the derived version. This should give you a decent start with the SDK and it's object model. Once you get a feel for how the Open XML SDK works, if you're familiar with Word's object model, using that provided by the SDK is relatively straight-forward as an effort was made to make it correspond as closely as possible to the "COM" object model.
The VBA project can be added later, but you can also include it in the basic template. That would be the simplest approach.
Include this "starting point" basic template as part of your solution, installing it as part of the solution.
Within the AutoNew macro you can check the AttachedTemplate property. Only if it is a template where you want to apply the cleaning you can execute the respective macros.
Sub AutoNew()
If ActiveDocument.AttachedTemplate <> "Normal.dotm" Then
Exit Sub
End If
' rest of the macro
End Sub
If you don't control the Normal.dotm you can put an empty AutoNew macro in your own templates. As Word only executes the auto macro in the closest context, the macro in the Normal.dotm file would not be executed.
If you don't control the other templates either, you can tell your users to hold down the SHIFT key while creating a document. This prevents the execution of the auto macro.
Probably it is best, however, if you ask the owner of the other system to find another solution that does not rely on polluting the Normal.dotm file.

How to check if code that is run belongs to active sheet

I have written some code for an excel spreadsheet. This sub has been added to the ribbon using the standard customizations found in File -> Options -> Customize Ribbon. What is funny though, is that the customization will run the Sub on the EXACT EXCEL FILE that the Sub is written in, i.e. the original.
My users copy the excel file and make adjustments and then run the sub (by clicking the customized button in the ribbon), which then opens the ORIGINAL template file and runs the code. This actually works fine - and I like it because it runs the TEMPLATE code on the ACTIVE sheet. But the only problem is it is also OPENING the original template file. How do I get it to close? Remember it is not the ActiveSheet anymore.
One possible answer is to check if the template is open, then close it (using a static reference), but I would prefer something a little more elegant than that.
You should be able to find the template/original using ThisWorkbook.
Also, if you deliver your workbook's code as a .xlam instead of .xlsm, it will be opened, but there won't be a window for it, so users won't see it.
Not sure if you're using this already, but you might also want to use "for this workbook only" when adding ribbon buttons for a .xlam . (Delivering as .xlam allows you to install buttons from a workbook into the ribbon, without relying on file path and without needing your users to also install buttons (they install .xlam instead).)

Using Word automation, is it possible to find out if the active document contains VBA code?

Using Word Automation, I want to save the active document programmatically. In Office 2007/2010, the document needs to be saved explicitly as "macro enabled" to preserve any VBA code in that document.
Rather than asking the user to choose, I would like my application to be able to determine if there is VBA code in the active document. Is that possible?
Yes this can be determined via the HasVBProject property. For example:
If ActiveDocument.HasVBProject = True Then
'Code to save as .dotm
Else
'Code to save as .dotx
End If
For those who might stumble upon this post later, it is worth noting that this code should be placed outside the document being tested for the presence of macros (otherwise it would detect itself). Two often-used options would be to access the code from an external application or from a template stored in Word's Startup folder.