Install add-in with Document Open function in dotm template - vba

I want to deploy my addin with custom ribbon for a group of users.
To be able to auto update the tool for all my users i have created a Tool-Loader.dotm add-in. This add-in will be stored in the user C:\Users\xxx\AppData\Roaming\Microsoft\Word\STARTUP\ folder.
When the user is starting Word it should enable the real addin which is placed on a networkshare.
This way my users wont have to worry about updates because i only have to replace the read add-in on my networkshare. When the users restart Word is should reload the add-in and people are up to date.
I have added the following code to my Tool-Loader.dotm in STARTUP folder:
ThisDocument:
Option Explicit
Private Sub Document_New()
Dim addinpath As String
addinpath = "\\[NETWORK SHARE]\Tool.dotm"
If Dir(addinpath) <> "" Then
AddIns.Add FileName:=addinpath, Install:=True
Else
MsgBox "Cant load Tool. Please check network connection and VPN")
End If
End Sub
I did the same for :
Private Sub Document_Open()
It works fine when opening the Tool-Loader.dotm file directly. But it is not working when placing the file in the STARTUP folder and normally starting Word.
How can i fix thix problem?

Document_New and Document_Open only work for documents and document templates. A template placed in the Startup folder is termed a "global template" (Normal.dotm is also a global template). Global templates are loaded, not opened.
As there isn't an OnLoad event for a template we have to look for alternatives. If the global template contains a ribbon the ribbon's OnLoad event can be used. Otherwise we have to go old school and use an AutoExec routine, placed in a standard module.
Documentation for the Auto Macros can be found here
Public Sub AutoExec()
Dim addinpath As String
addinpath = "\\[NETWORK SHARE]\Tool.dotm"
If Dir(addinpath) <> "" Then
AddIns.add FileName:=addinpath, Install:=True
Else
MsgBox "Cant load Tool. Please check network connection and VPN"
End If
End Sub

Thank you! Based on your answer i am going to add a ribbon to my Tool-Loader and try to use the OnLoad event.
If that doesnt work i will try the AutoExec
Update for people having the same issue:
With the Custom UI Editor i added the following lines of code:
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="LoadAddin">
</customUI>
Then i opened the Tool-Loader.dotm file and placed the following code in ThisDocument:
Dim MyRibbon As IRibbonUI
Sub LoadAddin(Ribbon As IRibbonUI)
On Error Resume Next
Set MyRibbon = Ribbon
Dim addinpath As String
addinpath = "[networkshare]"
If Dir(addinpath, vbDirectory) = "" Then
MsgBox "Can't load addin, please check internetconnection and VPN."
Exit Sub
Else
AddIns.Add FileName:=addinpath & "Tool.dotm", Install:=True
End If
End Sub
Now it works and when opening Word the latest version of the tool is loaded.

I too use a tool loader to deploy a network shared dotm. I keep my Ribbon in the main file though, not in the tool loader.
I make sure the user has their wdWorkgroupTemplatesPath set to the network location where the main addin is kept. The tooloader then uses autoexec to call the below Addin-loader, if it doesn't find the Addin in word's list of addins it falls back to loading it from the file on the network drive.
Public Sub AutoExec()
Call LoadAddin
End Sub
Public Sub LoadAddin()
Dim vAddin As Variant
Dim bLoaded As Boolean
Dim strWorkgoupTemplatesPath As String
bLoaded = False
On Error GoTo ErrorHandler
For Each vAddin In Application.AddIns
If ADDINNAME = vAddin.Name Then
Application.DisplayAlerts = False
vAddin.Installed = True
bLoaded = True
Application.DisplayAlerts = True
End If
Next vAddin
LoadFromFile:
If Not bLoaded Then
strWorkgoupTemplatesPath = Options.DefaultFilePath(Path:=wdWorkgroupTemplatesPath)
Call InstallMainAddin(strWorkgoupTemplatesPath)
End If
ExitHandler:
Exit Sub
ErrorHandler:
bLoaded = False
If Err.Number = 4706 Then GoTo LoadFromFile
GoTo ExitHandler
End Sub
Function InstallMainAddin(strWorkgoupTemplatesPath As String) As Boolean
Dim bResult As Boolean
Dim vMainAddin As Variant
On Error Resume Next
SetAttr strWorkgoupTemplatesPath & ADDINNAME, vbReadOnly
On Error GoTo ErrorHandler
Application.DisplayAlerts = False
Set vMainAddin = Application.AddIns.Add(strWorkgoupTemplatesPath & ADDINNAME)
vMainAddin.Installed = True
bResult = True
ExitHandler:
Application.DisplayAlerts = True
InstallMainAddin = bResult
Exit Function
ErrorHandler:
bResult = False
GoTo ExitHandler
End Function

Related

Word VBA AddIn pops up again after deletion

This is my first question I ask in this community, maybe you know the answer.
I want a Word macro application “MyToolbox.docm” providing some functionality for the users using a dialog box. The following requirements apply:
• The Toolbox functionality must be available in all documents to the user
• Toolbox functionality must be available as long as the file MyToolbox stays open
• Dialog is started using a button
• Use of separate installation file is not allowed
• Data storage in normal.dot is not allowed
I have solved this problem with the following functionalities:
• The public Macro “Sub MyToolbox” starts the dialog
• This macro is added to the Quick Access Toolbar with a nice Icon.
• Icon is only visible when file “MyToolbox.docm” is open
Starting and using MyToolbox:
• After opening file “MyToolbox.docm” Autoopen macro starts
• it loads “MyToolbox.docm” as AddIn
• Loading as AddIn makes the functionality accessible to other open documents
Finishing the use of MyToolbox:
• Closing Word or the file MyToolbox runs an Autoclose macro
• This macro unloads and deleted the AddIn
In Principle this works fine, after closing MyToolbox the AddIn disappears from the list of AddIns.
However in the overview (File-Options-AddIns) the Addin is still listed as inactive!
After closing Word and Reopening Word the Addin is automatically loaded and the file is active again.
This happens everytime I restart Word.
Now I have programmed a Workaround in the Autoopen Macro which does the following:
• Wait 5 seconds (in case it does not work, increase the wait time to 10 sec)
• Check if file MyToolbox.docm is open
• In case it is not open (meaning the user did not want to start the Toolbox)
the AddIn is removed again
After the implementation of this workaround the Restart of the AddIn happens only one time when I open Word.
After closing Word and reopening a second time the AddIn has disappeared form the list of inactive Addins and word again is clean.
I do not know where Word stores the inactive AddIns, I also looked in the Registry.
Can you help here?
To reproduce the effect you need a Macrofile with an empty Userform1 and a Module containing the following code:
Sub AutoOpen()
Dim MyToolbox_Fullname As String
'Get full document name
MyToolbox_Fullname = ActiveDocument.Fullname
'Load program as AddIn
AddIns.Add Filename:=MyToolbox_Fullname, Install:=True
ActiveDocument.UpdateStylesOnOpen = False
'Workaround
Call time_delay(5)
If file_status(MyToolbox_Fullname) = 1 Then
'File exists but is not open, unload AddIn
AddIns(MyToolbox_Fullname).Installed = False
AddIns(MyToolbox_Fullname).Delete
End If
End Sub
Sub Autoclose()
Dim MyToolbox_Fullname As String, MyToolbox_File As String
'Get document name
MyToolbox_Fullname = ActiveDocument.Fullname
MyToolbox_File = ActiveDocument.name
'Unload AddIn
If CheckAddin(MyToolbox_File) Then
AddIns(MyToolbox_Fullname).Installed = False
AddIns(MyToolbox_Fullname).Delete
End If
'Unload form
Unload UserForm1
End Sub
Public Sub MyToolbox()
'Start menu
UserForm1.Show
End Sub
Function CheckAddin(AddIn_Name As String) As Boolean
Dim oAddin As AddIn
Dim AddIn_Exists As Boolean
'Check if AddInn exists
AddIn_Exists = False
For Each oAddin In AddIns
If oAddin.name = AddIn_Name Then
AddIn_Exists = True
Exit For
End If
Next
CheckAddin = AddIn_Exists
End Function
Public Sub MyToolbox()
'Start menu
UserForm1.Show
End Sub
Private Function GetFilenameFromPath(ByVal strPath As String) As String
If Right$(strPath, 1) <> "\" And Len(strPath) > 0 Then
GetFilenameFromPath = GetFilenameFromPath(Left$(strPath, Len(strPath) - 1)) + Right$(strPath, 1)
End If
End Function
Private Function file_status(Fullname As String) As Integer
'Check if file exists:
'file_status = 0 does not exist
'file_status = 1 does exist on file
'file_status = 2 does exist and is open
file_status = 0
If file_exists(Fullname) Then
file_status = 1
End If
For Each aDoc In Documents
If UCase(aDoc.name) = UCase(GetFilenameFromPath(Fullname)) Then
file_status = 2
End If
Next aDoc
End Function
Private Function file_exists(Fullname As String) As Boolean
Set fs = CreateObject("Scripting.FileSystemObject")
file_exists = fs.fileexists(Fullname)
End Function
Private Sub time_delay(Length As Integer)
Start = Timer
Do While Timer < Start + Length
DoEvents
Loop
End Sub

VBA Word: remove editing restriction at opening and restrict again when closing

Is it possible to set a macro that would trigger each time I open a word document and check if it has an editing restriction. If so, try password from a list of passwords (hardcoded). In case one password is successfull, keep it in memory, remove restriction, and re-apply the restriction when I close the document.
In this way, if I always use the same password for the documents I use and restrict, I could open them on my computer as if there was no restriction, but the restriction would still apply to other users.
Note: the macro in Private Sub Document_Open() would need to trigger on all documents I open from my computer only. Documents must be .docx and not .docm.
Thank you.
Note 1: You will need to put this into a .dotm file and ultimately save as a global template on your PC (google).
Note 2: This will fail if you open more than 1 doc because only 1 password is stored - you could write the password as a document property (which you would retrieve & delete before saving and relocking).
Depending on whether or not you are happy to add code to the Normal.dotm template (personally I'm not) will influence how you do this.
If NOT using Normal.dotm then you will need to setup a global template AND trigger the code by creating your own application events as described here: https://wordmvp.com/FAQs/MacrosVBA/PseudoAutoMacros.htm
If using Normal.dotm then in ThisDocument add:
Private Sub Document_Open()
MsgBox ActiveDocument.Name
Dim oDoc As Object
Set oDoc = ActiveDocument
unlocker oDoc
End Sub
And (for testing) in a regular module add the following (you'll likely want to split this into separate units of code later):
Sub unlocker(ByVal docToUnlock As Document)
If Not docToUnlock.Type = wdTypeDocument Then
' this is a template, don't try anything
MsgBox "Not a doc"
GoTo endOfSub
Else
MsgBox "Is a doc"
End If
Dim passWords() As String
passWords = Split("pw1,pw2,pw3", ",")
Dim iLoop As Long
iLoop = LBound(passWords)
On Error GoTo err_Test:
Do While Not ActiveDocument.ProtectionType = wdNoProtection
If iLoop > UBound(passWords) Then Exit Do
oldpassword = passWords(iLoop)
ActiveDocument.Unprotect oldpassword
iLoop = iLoop + 1
Loop
If Not ActiveDocument.ProtectionType = wdNoProtection Then
' unable to unlock document, quit
oldpassword = vbNullString
MsgBox "Failed to Unlock"
GoTo endOfSub
Else
MsgBox "Unlocked"
End If
' Do Stuff
If Not oldpassword = vbNullString Then
ActiveDocument.Protect wdAllowOnlyReading, Password:=oldpassword
End If
endOfSub:
Exit Sub
err_Test:
If Err.Number = 5485 Then
' ignore error due to wrong password
Err.Clear
Resume Next
Else
' handle unexpected error
End If
End Sub

Excel vba, Opening new Application: Microsoft Excel is waiting for another application to complete an OLE action

I have the following vba code. It creates new Excel application and uses it to open a file. Then it MsgBoxes some cell's value in this file.
Sub TestInvis()
Dim ExcelApp As Object
Set ExcelApp = CreateObject("Excel.Application")
Dim WB As Workbook
Set WB = ExcelApp.Application.Workbooks.Open("Y:\vba\test_reserves\test_data\0503317-3_FO_001-2582480.XLS")
Dim title As String
title = WB.Worksheets(1).Cells(5, 4).Value
MsgBox (title)
WB.Save
WB.Close
ExcelApp.Quit
Set ExcelApp = Nothing
End Sub
The problem is that after MsgBoxing it slows down and eventually gives a Microsoft Excel is waiting for another application to complete an OLE action window. Why does it do this? It's not like there are any hard commands being implemented. And how should I deal with it?
This happens because the Excel instance in ExcelApp is waiting for User Input, most likely.
You can try to add ExcelApp.DisplayAlerts = False to skip any pop-ups that might be there.
Also, while troubleshooting add the line ExcelApp.Visible = True so you can see what's going on in the second instance and troubleshoot there.
I encountered this problem in the following situations:
An alert was opened by the Application Instance and it was awaiting user input.
While opening a file, it was coming up with some message about a crash when the file was previously opened and whether I wanted to open the saved version or the in memory version (although this should happen before the msgBox)
If you run the code multiple times and it crashes, it might have the file open as read only since there's another hidden instance of Excel that locked it (check your task manager for other Excel processes)
Rest assured that in any case the problem is not with your code itself - It runs fine here.
Code that works for me.
You can select the file from FileDialog. In comments You have code that close the workbook without saving changes. Hope it helps.
Option Explicit
Sub Import(Control As IRibbonControl)
Dim fPath As Variant
Dim WB As Workbook
Dim CW As Workbook
On Error GoTo ErrorHandl
Set CW = Application.ActiveWorkbook
fPath = Application.GetOpenFilename(FileFilter:="Excel file, *.xl; *.xlsx; *.xlsm; *.xlsb; *.xlam; *.xltx; *.xls; *.xlt ", Title:="Choose file You want to openn")
If fPath = False Then Exit Sub
Application.ScreenUpdating = False
Set WB = Workbooks.Open(FileName:=fPath, UpdateLinks:=0, IgnoreReadOnlyRecommended:=True)
Set WB = ActiveWorkbook
MsgBox("File was opened.")
'Application.DisplayAlerts = False
'WB.Close SaveChanges:=False
'Application.DisplayAlerts = True
'MsgBox ("File was closed")
Exit Sub
ErrorHandl:
MsgBox ("Error occured. It is probable that the file that You want to open is already opened.")
Exit Sub
End Sub
None of these methods worked for me. I was calling a DLL for MATLAB from VBA and a long simulation would pop up that Excel was waiting on another application OLE action, requiring me to click it off for the routine to continue, sometimes quite a few times. Finally this code worked (saved in a new module): https://techisours.com/microsoft-excel-is-waiting-for-another-application-to-complete-an-ole-action/
The way I used it is a little tricky, as the directions don't tell you (here and elsewhere) which causes various VBA errors, so I add to the description for what works in Excel 365:
Create a new module called "ToggleOLEWarning" (or in any new module, important!) which only contains the following code:
Private Declare Function CoRegisterMessageFilter Lib "ole32" (ByVal IFilterIn As Long, ByRef PreviousFilter) As Long
Public Sub KillOLEWaitMsg()
Dim IMsgFilter As Long
CoRegisterMessageFilter 0&, IMsgFilter
End Sub
Public Sub RestoreOLEwaitMsg()
Dim IMsgFilter As Long
CoRegisterMessageFilter IMsgFilter, IMsgFilter
End Sub
Then in your main function, just decorate the long running OLE action with a couple lines:
Call KillOLEWaitMsg
'call your OLE function here'
Call RestoreOLEwaitMsg
And it finally worked. Hope I can save someone the hour or two it took for me to get it working on my project.

How to use Workbook_BeforeClose from a custom module

I am trying to prompt the user when he tries to close a workbook like this
Private Sub Workbook_BeforeClose(Cancel as Boolean)
MsgBox "Changes has been detected. Do you want to export the data ?"
End Sub
I know that this code need to be placed in ThisWorkbook module.
Is there a way to do that from my custom module ? I need to add this functionality to multiple workbooks used by my client as a part of up-gradation, which is usually done by replacing old modules with new modules.
You can use the Application.VBE object and Workbook.VBProject to modify a file's VBA. Note however that it requires that the Excel performing the upgrade has to have the setting "Trust access to the VBA project" toggled on (it can be found in the Trust center under the tab Macro settings). When not needed anymore, it's an option best left off though for security reasons.
There is a way how you can Import the ThisWorkbook Module. I wrote some Code for that a long Time ago.
So how does it work.
First you have to Export the ThisWorkbook Module. Right click on the Module and Export.
Save the ThisWorkbook.cls on the Server where you have your other Module's or send it with the Modules (Like how you do the Upgrade of the other Modules)
Open the ThisWorkbook.cls File with a Editor (Like Notepad++)
And Delete The First Rows. They Look like This.
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Execute the UpdateThisDocument Subrutine.
The Only Question how have to answer yourself is how you will Execute The Code ^^ (I wrote en Extern Updater that Executed the Code)
Sub UpdateThisDocument()
Dim ModuleName As String
ModuleName = "DieseArbeitsmappe"
Dim aDoc As Workbook
Set aDoc = ThisWorkbook
Dim strPath As String
'Put here the Path to the Folder where the cls File of This Workbook is.
strPath = "C:\Users\z002mkvy\Desktop\"
With aDoc
If ModuleExists(ModuleName) = True Then
Call clsLoeschen
End If
'
With ThisWorkbook.VBProject
.VBComponents(ModuleName).CodeModule.AddFromFile _
strPath & "\DieseArbeitsmappe.cls"
Fehler:
End With
End With
End Sub
Private Function ModuleExists(ModuleName As String) _
As Boolean
On Error Resume Next
ModuleExists = Len(ThisWorkbook.VBProject _
.VBComponents(ModuleName).Name) <> 0
End Function
Private Sub clsLoeschen()
Dim modcls
Dim awcl As Integer
On Error Resume Next
Set modcls = ThisWorkbook.VBProject.VBComponents.Item("DieseArbeitsmappe")
awcl = modcls.CodeModule.CountOfLines
modcls.CodeModule.DeleteLines 1, awcl
Set modcls = Nothing
End Sub
I hope This can Help you

Possible to modify Excel VBA References at runtime (after runtime loading of an Add-In)?

When dynamically loading an Excel 2010 Add-In, one must also alter the VBA references to include the newly added Add-In, after it has been loaded into the workbook.
This code works for programmatically loading the add-in:
Function LoadAddin(strFilePath As String) As Boolean
' Checks whether add-in is in collection, and
' then loads it. To call this procedure, pass
' in add-in's path and file name.
Dim addXL As Excel.AddIn
Dim strAddInName As String
On Error Resume Next
' Call ParsePath function to return file name only.
'strAddInName = ParsePath(strFilePath, FILE_ONLY) 'not available in VBA...so it seems to always physically load it below, which seems to work fine.
' Remove extension from file name to get add-in name.
strAddInName = Left(strAddInName, Len(strAddInName) - 4)
' Attempt to return reference to add-in.
Set addXL = Excel.AddIns(strAddInName)
If err <> 0 Then
err.Clear
' If add-in is not in collection, add it.
Set addXL = Excel.AddIns.Add(strFilePath)
If err <> 0 Then
' If error occurs, exit procedure.
LoadAddin = False
GoTo exit_function
End If
End If
' Load add-in.
If Not addXL.Installed Then addXL.Installed = True
LoadAddin = True
exit_function:
Exit Function
End Function
So is there a way to now add this to the References so VBA code in the host spreadsheet that refers to VBA within this newly included Add-In will execute properly?
It appears that the route to go might be something like:
ThisWorkbook.VBProject.References.AddFromFile ("C:\MyFiles\MyAddin.xlam")
...but this gives me the error:
Microsoft Visual Basic for Applications
Run-time error '32813':
Application-defined or object-defined error
Have you considered using the same code (but slightly modified) in the Workbook open event of the Add-In?
If I understand you correctly then I guess this is what you want?
Public ShouldIContinue As Boolean
Private Sub Workbook_Open()
'~~> This is required to avoid the endless loop
If ShouldIContinue = True Then Exit Sub
Dim addXL As AddIn
Dim strAddInName As String
Dim oTempBk As Workbook
strFilePath = ThisWorkbook.FullName
strAddInName = ThisWorkbook.Name
'~~> This will work for both .xla and .xlam
strAddInName = Left(strAddInName, (InStrRev(strAddInName, ".", -1, _
vbTextCompare) - 1))
On Error Resume Next
Set addXL = Excel.AddIns(strAddInName)
On Error GoTo 0
If Not addXL Is Nothing Then Exit Sub
'~~> This is required to avoid the Run-time error '1004':
'~~> "Unable to get the Add property of the AddIns class"
'~~> OR
'~~> "Add method of addins class failed"
'~~> when there are no workbooks
Set oTempBk = Workbooks.Add
Set addXL = AddIns.Add(strFilePath, True)
addXL.Installed = True
oTempBk.Close
ShouldIContinue = True
End Sub