Word VBA AddIn pops up again after deletion - vba

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

Related

Install add-in with Document Open function in dotm template

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

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

How to deploy word 2007 macro

I have used Custom Fields in my DOCX file and update them using OpenXML, but custom fields are not getting updated in document.
So I have write following macro to update field, it run well on my machine, Now i want to deploy it on each machine at client side, the machine count is 500 I can not go on each machine and paste macro code in each normal.dot file
what is easy step to do it ? Or when I open word file, will application ask for installing macro ? like addin ?
Here is my macro
Private Sub Document_Open()
Dim aStory As Range
Dim aField As Field
For Each aStory In ActiveDocument.StoryRanges
For Each aField In aStory.Fields
aField.Update
Next aField
Next aStory
End Sub
I don't know how to deploy your macro; however, in a comparable situation I sent users a template with the new macros and asked them to run it. The template would distribute the new macros. I ued the following code for that. Note that it only copies Macro Proejct Items (modules), not single macros:
Sub AutoNew()
Deploy
End Sub
Sub AutoOpen()
Deploy
End Sub
Sub Deploy()
Dim src, dst
'
If (MsgBox("This will deploy new macros to your Normal.dot. Continue?", vbYesNo, "Deploy new macros") _
= vbNo) Then Exit Sub
On Error Resume Next
src = ActiveDocument.AttachedTemplate.FullName
dst = NormalTemplate.FullName
'
' Copy a macro/module
'
Application.OrganizerCopy Source:=src, Destination:=dst, _
Object:=wdOrganizerObjectProjectItems, Name:="Document_Open"
'
MsgBox "New macros have been copied to your Normal.dot. You can close this document now."
End Sub

Excel VBA: How to implement timer to check for code timeout

I have some code that runs on workbook open that uses a form to request that the user select the drive to which a shared directory is mapped.
This is because the workbook uses VBA code to retrieve and save data to a shared workbook located in this shared directory, but the local drive changes by user, so they need to select it.
The problem I've run into occurs when the user has mapped multiple shared directories to their computer and thus have multiple drives... ex: 1 directory is on drive G: and the other is on X:.
If they select the drive for the shared directory in which the workbook resides, there is no problem. However, if they accidentally choose the drive for the other shared directory, the code hangs.
I have a loop setup that checks to see they've chosen the correct drive... IE: If they chose A: (a non-existent drive in my example), then the code will note that they chose the incorrect drive and prompt them again.
However, instead of creating an error when another shared directory is chosen, the code just hangs.
In the below code, cell AD3 on sheet one contains true or false (gets set to false in the beginning of the sub). It gets set to true if they've chosen correct drive as Module6.PipelineRefresh will no longer cause an error (this sub attempts to open the workbook in the shared drive... and if the chosen drive is incorrect it obviously returns an error)
Codes is as below:
Do While Sheet1.Range("ad3") = False
On Error Resume Next
Call Module6.PipelineRefresh '~~ I'm guessing the code hangs here. Instead of returning an error immediately, as it would if they simply chose a non-existant drive, it appears to get stuck trying to open the workbook, even though it's not located in the shared directory they've selected.
If Err.Number = 0 Then
Sheet1.Range("ad3") = True
Err.Clear
Else
MsgBox "Invalid Network Drive."
DriverSelectForm.Show
Err.Clear
End If
Loop
If anyone knows how to implement a timer so I can shutdown the code after some amount of time, that'd be great.
Alternatively, if you know how to get around this error, that'd also be great!
EDIT as per comment:
This is the specific code in Module6.PipelineRefresh that hangs. The DriverSelectForm (shown above) amends the value in cell o1 to the chosen drive string (ie: X:)
Dim xlo As New Excel.Application
Dim xlw As New Excel.Workbook
Dim xlz As String
xlz = Sheet1.Range("o1").Value & "\Region Planning\Created Pipeline.xlsx"
Dim WS As Worksheet
Dim PT As PivotTable
Application.DisplayAlerts = False
Set xlw = xlo.Workbooks.Open(xlz)
Application.DisplayAlerts = True
Note: As stated above, if the user selects a non-existent directory, the above code returns an error immediately because it cannot open the file... if they have a shared directory mapped to the chosen drive (but it's the wrong directory), the code will hang and does not appear to return an error.
I've answered my own question by working around the problem. Instead of checking that the user has selected the correct drive letter, I am now using the CreatObject function to find the drive letter associated with the drive name (as drive name will not change).
Example code for this:
Dim objDrv As Object
Dim DriveLtr As String
For Each objDrv In CreateObject("Scripting.FileSystemObject").Drives
If objDrv.ShareName = "Shared Drive Name" Then
DriveLtr = objDrv.DriveLetter
End If
Next
If Not DriveLtr = "" Then
MsgBox DriveLtr & ":"
Else
MsgBox "Not Found"
End If
Set objDrv = Nothing
The solution to stop some code by timer. The code must be placed in a module.
Private m_stop As Boolean
Sub stop_timer(p_start_time As Variant)
Application.OnTime p_start_time, "stop_loop"
End Sub
Sub signal_timer(p_start_time As Variant)
Application.OnTime p_start_time, "signal_in_loop"
End Sub
Sub test_loop()
Dim v_cntr As Long
m_stop = False
v_cntr = 0
stop_timer Now + TimeValue("00:00:05")
signal_in_loop
While Not m_stop
v_cntr = v_cntr + 1
DoEvents
Wend
Debug.Print "Counter:", v_cntr
End Sub
Sub stop_loop()
m_stop = True
End Sub
Sub signal_in_loop()
Debug.Print "timer:", Timer
If Not m_stop Then
signal_timer Now + TimeValue("00:00:01")
End If
End Sub
Output:
timer: 50191.92
timer: 50192
timer: 50193
timer: 50194
timer: 50195
timer: 50196
Counter: 67062
timer: 50197.05
m_stop controls the loop. DoEvents calls event handlers such as stop_loop and signal_in_loop as defered procedures.

Excel crashing due to missing referenced xlam

I have two Excel files (one a xlam and the other a xlsm). The xlsm references the xlam.
If I open the xlsm before opening the xlam, Excel crashes.
From with the xslm (using any programmatic method) is there a way I can check to see if the xlam is open and if not, either load it dynamically or show a warning that the xlam needs to be opened first before exiting.
I crafted some code which gets called from the Workbook_Open sub in the xlsm
Public Function checkReferences() As Boolean
On Error Resume Next
Dim retVal As Boolean
retVal = False
Dim i As Integer
For i = 1 To ThisWorkbook.VBProject.References.Count
With ThisWorkbook.VBProject.References(i)
If StrComp(.name, "PreTradeServices") = 0 Then
retVal = True
Exit For
End If
End With
Next i
checkReferences = retVal
End Function
Unfortunately Excel crashes before that Workbook_Open is reached
Something like this?
'/**
'
' VBA Function to check whether required addin is installed...
' #version 1.0
' #author Ilyas Kazi http://ilyaskazi.com
'
' #param string str_filename (to parse file name to lookup for the addin)
'
' #return boolean (true/false)
'
'**/
Function IsAddin_Installed(str_filename As String) As Boolean
Dim aiwb As AddIn 'addin workbook
For Each aiwb In Application.AddIns 'Loop through each addin workbook
If UCase(aiwb.Name) = UCase(str_filename) Then
IsAddin_Installed = True 'found
Exit Function
Else
IsAddin_Installed = False
End If
Next
End Function
What about adding the XLAM as a VBA reference? Is there a way you can keep the XLAM in a centralized location?