Call a macro from ThisOutlookSession - vba

I have a problem with my macros on outlook.
I am currently trying via a batch to call outlook and pass it as a parameter the name of a macro that I get via an environment variable I've set in my batch. However I do get the name of my macro, but the process stops at the time of the Call function. Could someone tell me the right way to proceed?
VBA ThisOutlookSession
Private Sub Application_Startup()
Dim strMacroName As String
strMacroName = CreateObject("WScript.Shell").Environment("process").Item("MacroName")
'MsgBox strMacroName
'MsgBox VarType(strMacroName)
If strMacroName <> "" Then Call strMacroName
End Sub
VBA Modules
Option Explicit
Sub macro1()
MsgBox "macro1"
End Sub
Sub macro2()
MsgBox "macro2"
End Sub
Batch
Set WorkingPath=C:\Temp\Outlook
Set MacroName=%1
start OUTLOOK.EXE
Set MacroName=
Set WorkingPath=
the result

There are several aspects here... The first point is possible security issues when dealing with the Outlook. You can read more about that in the Security Behavior of the Outlook Object Model article.
Another point is that you can call VBA macros declared in the ThisOutlookSession module in the following way (for example, from any other Office application):
Sub test()
Dim OutApp As Object
Set OutApp = CreateObject("Outlook.Application")
OutApp.Session.Logon
OutApp.HelloWorld
End Sub
Where the HelloWorld sub is declared in the ThisOutlookSession module in following way:
Option Explicit
Public Sub HelloWorld()
MsgBox "Hello world !!"
End Sub
Note, you may call any module from the ThisOutlookSession module. There is no need to get access to other modules directly.

Related

Document_Open() does not work in other files

I am writing a VBA project containing 1 module (m1) and 1 userform (uf).
In "ThisDocument" I am calling a public sub from "m1" that initializes a collection I then refer to in the userform. This works perfectly fine until I deploy this project to other files.
I save the file as a .dotm in the %Appdata%/Microsoft/word/startup folder so I can use the project in all of my word files. But as soon as I try to use my project in other files the userform opens itself as designed but the collection is empty.
What could be the problem here?
Manually calling the initialization method from the userform works fine.
'----------------------------------------------ThisDocument
Private Sub Document_Open()
initBetaCollection
End Sub
'----------------------------------------------m1
Option Explicit
Public beta As Collection
Sub initBetaCollection()
Set beta = New Collection
beta.Add Array("0041", "A"), Key:="0041"
'...
End Sub
'----------------------------------------------uf
Option Explicit
Private Sub txtSearch_Change()
Dim arr As Variant
Dim search As String
'Defining the textinput as "search"
search = txtSearch.Value
For Each arr In beta
If search <> "" Then 'No empty search field
If arr(1) Like "*" & search & "*" Then 'Match found
lbResults.AddItem arr(0)
End If
End If
Next
End Sub
I get the: Run Time Error '424' object required
The problem with using Document_Open in the ThisDocument class or a macro named AutoOpen in a regular module is that both execute specifically for this document (or documents created from the template), only.
In order to have an application-level event, that fires for all documents opened, it's necessary to work with application-level events.
For this, first a class module is required. In the class module, the following code is needed:
'Class module named: Class1
Public WithEvents app As Word.Application
Private Sub app_DocumentOpen(ByVal Doc As Document)
'MsgBox Doc.FullName & ": on Open"
'Code here that should fire when a document is opened
'If something needs to be done with this document, use
'the Doc parameter passed to the event, don't try to use ActiveDocument
End Sub
Then, in a regular module, an AutoExec macro can be used to initialize the class with event handling. (AutoExec: fires when Word starts and loads a template with a macro of this name.)
Option Explicit
Dim theApp As New Class1
Sub AutoExec()
'MsgBox "AutoExec"
Set theApp.app = Word.Application
End Sub
'Sub AutoOpen()
' MsgBox "Open in AutoOpen" - fires only for this document
'End Sub

How to schedule an async macro from WORD

I'm facing an annoying issue where I simply want to schedule an asynchronous macro from another instance of Word, which happens to be the same .doc file.
Meaning, in the ThisDocument namespace I have the following code snippet:
Public Sub Document_Open()
Set Obj = New Word.Application
Obj.OnTime Now + TimeValue("00:00:01"), "Module1.Test"
End Sub
I've declared a new object of Word due to the following reasons:
My macro may block user's I\O
The user may close the document before the macro finishes its task
And declared a module named Module1 with a simple MsgBox
Public Sub Test()
MsgBox "hhh"
End Sub
Needless to say, nothing happened, and I'm unable to check what OnTime function returns.
I've also tried the following combinations:
"!Module1.Test"
"c:\\....\\file.doc!Module1.Test"
What am I missing here?
Your newly created Word application, represented by a Word.Application object, doesn't have the document open. Thus there's no "Module1" as far as he's aware. The fact that you want to run code from the same document is immaterial. It's a different instance of Word.
Something like this works:
'''''''' ThisDocument
Option Explicit
Private Sub Document_Open()
Dim res As VbMsgBoxResult
Dim Obj As Word.Application
If Application.Visible Then
res = MsgBox("Hello", vbOKCancel, "Hi!")
Else
Exit Sub
End If
If res = vbOK Then
Set Obj = New Word.Application
Obj.Documents.Open "C:\Users\conio\Desktop\Hello.docm", , True
Obj.OnTime Now + TimeValue("00:00:01"), "Module1.Foo"
End If
End Sub
'''''''' Module1
Public Sub Foo()
If Not Application.Visible Then
MsgBox "Foo"
Application.Quit
End If
End Sub
Maybe you'd want to use a different check to differentiate between the interactive run and the unattended run.

Detect the event ActiveWorkbook.Save from personal.xlsm in excel

I have all macros stored in personal.xlsm and untill now I have used a standard filter to hide/show columns. The new feature I want to implement now is that each user can have their own filter if they would like to. So basically i look in a folder for a personal filter if it exist and if it does it use that filter instead of the standard one.
But my problem is that i want to load a personal filter on workbookOpen event and reset to standard filter on the beforeClose event. My question is if I can do this from personal.xlsm in a way? Or do I have to manually go through all 250 workbooks and add in thisworkbook module onOpen and beforeClose events to call my method createFilter and resetFilter?
Here is a link to personal.xlsm for those who are not familiar with that methodology
In personal.xlsm, create a class module named "Helper". Place the following code in it:
Option Explicit
Public WithEvents xlApp As Application
Private Sub Class_Initialize()
Set xlApp = Application
End Sub
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
'Your code here
End Sub
Your code for loading a filter should go in the 'Your code here bit.
Add a standard code module and add this code:
Public MyHelper As Helper
Finally, in the Workbook_Open event of personal.xlsm, place this code:
Private Sub Workbook_Open()
Set MyHelper = New Helper
End Sub
Save personal.xlsm and restart Excel.
Here is the code in helper if someones needs it:
Private Sub xlApp_WorkbookOpen(ByVal Wb As Workbook)
Dim myPath As String
On error resume next
If InStr(UCase(ActiveWorkbook.Name), "PARTSLIST") And (InStr(UCase(ActiveWorkbook.Name), "FILTERPARTSLIST") = 0) Then
myPath = Left(ThisWorkbook.Path, InStrRev(ThisWorkbook.Path, "\") - 1)
If Dir(myPath & "\filterPartsList.xlsx") <> "" Then '
Call ColumnCreater.updateFilter
Else
Call ColumnCreater.filterCreationStandard
End If
End If
End Sub

How to Call Subs from an Add-In Directly

So it's obviously easy to write code that will call a sub/function that is in an add-in library via VBA code, simply by doing
call myFunctionOrSub
However, is there a way to allow users to directly call public subs in an add-in? For example, when the user goes to Tools -> Macros and pulls up this screen:
I would like to add to the list of macros in that box all Subs which are included in add-ins that are linked to for the file. That is, I have a library (library.xlam) that is referenced by this current workbook. In this library.xlam file, I have Subs (such as copyToResults). I want copyToResults to appear as a runnable macro in this list. Is there a way to do that?
The only solution I could come up with was to create a Sub in my test file for each Sub in library.xlam. This Sub in the test file would do nothing by call library's Sub. However, this is terrible for the purpose of having external libraries and terrible for scalability, so we definitely don't want to go this route.
Make a form in your xlam with a list box.
Use the script from this post to populate your form. You will have to change some excel settings.
Get a list of the macros of a module in excel, and then call all those macros
Here is my code from my form:
Private Sub btnCancel_Click()
Unload Me
End Sub
Private Sub btnExecute_Click()
Application.Run "macros.xlam!" & lstMacros.Value
Unload Me
End Sub
Private Sub UserForm_Initialize()
Dim pj As VBProject
Dim vbcomp As VBComponent
Dim curMacro As String, newMacro As String
Dim x As String
Dim y As String
Dim macros As String
On Error Resume Next
curMacro = ""
Documents.Add
For Each pj In Application.VBE.VBProjects
For Each vbcomp In pj.VBComponents
If Not vbcomp Is Nothing Then
If Not vbcomp.CodeModule = "Utilities" Then
For i = 1 To vbcomp.CodeModule.CountOfLines
newMacro = vbcomp.CodeModule.ProcOfLine(Line:=i, _
prockind:=vbext_pk_Proc)
If curMacro <> newMacro Then
curMacro = newMacro
If curMacro <> "" And curMacro <> "app_NewDocument" Then
frmMacros.lstMacros.AddItem curMacro
End If
End If
Next
End If
End If
Next
Next
End Sub
In the end mine looked like this:
Macros Form

VBA: Global Variable Cleared after 'Workbook_Open' sub

I have set up an application level event class to monitor when new workbooks are created/ opened by following CPearson's guide. This works fine in isolation. However, it is intended as part of add-in I'm writing where several other subs are also called in the 'Workbook_Open' sub, see below code:
Private XLApp As baseCXlEvents
Private Sub Workbook_Open()
Set XLApp = New baseCXlEvents
test
AddLocalReferences
AddModules
AddClassModules
Debug.Print "testing"
End Sub
So the XLApp variable is called in the module scope as a baseCXlEvents class. I have added a Class_Terminate event to this class and this is triggered after the Debug.print "testing" is run, i.e. XLApp is terminated after the Workbook_Open sub has run. This does not happen when I quote out the subs AddLocalReferences, AddModules, and AddClassModules, which do exactly as their names would imply. The sub test only prints a messages in debug to test whether calling additional subs caused XLApp to be terminated.
My current 'hunch' is that adding references, modules, or class modules counts as "editing", causing it to be terminated, as explained in this MS Support document. But, if so, why doesn't XLApp get terminated until the end of the sub? As opposed to as soon as AddLocalReferences is run.
Any suggestions why the class is terminated? I need it to 'stay alive' and also need to load additional modules and references for the addin upon workbook_open. If needed more details of this code can be provided.
I've decided to add my baseCXlEvents class module's code:
Option Explicit
Private WithEvents App As Application
Private Sub App_NewWorkbook(ByVal Wb As Workbook)
MsgBox "New Workbook: " & Wb.Name
End Sub
Private Sub App_WorkbookOpen(ByVal Wb As Workbook)
MsgBox "Workbook opened: " & Wb.Name
End Sub
Private Sub Class_Initialize()
Debug.Print "Initializing baseCXlEvents instance.."
Set App = Application
End Sub
Private Sub Class_Terminate()
Debug.Print "Terminating baseCXlEvents instance.."
End Sub
Well then try to separate the Workbook_Open event handler stuff (where you add references etc.) from the creation of the instance of the class baseCXlEvents using Auto_Open.
Workbook_Open runs first, Auto_Open runs then.
Note: baseCXlEvents Instancing must be set to Public.
ThisWorkbook class
Public XLApp As baseCXlEvents
Private Sub Workbook_Open()
Test
AddLocalReferences
AddModules
AddClassModules
End Sub
Standard module
Sub Auto_Open()
Set ThisWorkbook.XLApp = New baseCXlEvents
End Sub