VBA / Excel - 2007
I want to create one (possibly many) .xlam file(s) to hold common modules accessible across projects. Along the way I have received a number of errors but through the creation of a "mickey-mouse" scenario I have boiled it down to one error condition (as shown in actual result):-
I have seen several problems reported here related to this which in the end have either not been answered or the solution has not quite hit the mark
As a precursor to this I was able to put my common module into another .xlsm project and successfully execute it by using a reference to that called project. However it would be preferable not to create a workbook for the sole purpose of housing common modules
'Caller VBAProject (Caller.xlsm):
Public Sub Caller()
Dim i As Integer
i = 0
'*** Error in line below***
Application.Run "C:\Users\IT\AppData\Roaming\Microsoft\AddIns\Common.xlam!Test", i
End Sub
'Called Test (Common.xlam) -- different project, obviously
Function Test(ByRef i As Integer) As Boolean
If i = 0 Then
Test = False
Else
Test = True
End If
End Function
Actual Result
Run-time error '1004':
Cannot run the macro
'C:\Users\IT\AppData\Roaming\Mirosoft\AddIns\Common.xlam!Test'
The macro may not be available in this workbook or all macros may
be disabled.
Steps Undertaken (in Excel Options)
Trust Center
a) Macro Settings
-- both i) and ii) (at different times)
i) Disabled all macros with notification
ii) Enabled all macros
iii) set Trust access to the VBA project object model
b) Add-ins -- left as default ie no option ticked
c) Trusted Locations -- have added the following
C:\Users\IT\AppData\Roaming\Microsoft\AddIns\
Add-Ins
As an Active Application Add-in I have
C:\Users\IT\AppData\Roaming\Microsoft\AddIns\Common.xlam
Can anybody please tell me what I might have missed?
So just to summarise I don't have a direct answer to my question thus far, that is how to avoid the 1004 error when calling a macro within a .xlam file. However, I do have a more than adequate alternative which is to import the common file into a different module within the same project. What I'm realising is that as I write this it isn't an import at runtime or late binding as Zac implied. I'm not sure I'm too worried though (at least at the moment).
Related
I'm having this staggering problem in creating a reference at runtime.
In short: I have an addin referenced in another addins, all created by myself and all working fine (except for some annoying popup which appears on loading Excel). As a workaround to this annoyance, I removed the existing reference to create it later, at runtime. But when I try creating the reference, it returns an "Name conflicts with existing module, project, or object library" error popup, and the reference is not created--which makes no sense, because if I create the reference at design time it works perfectly. There is no module or project with repeated name.
Now explaining a little further.
I have four kinda-complex Excel applications I'd had developed for my job. Each one is a VBA project, distributed as Excel Addins (.xlam).
Additionally, I have a fifth project with functions common to the other four. This fifth project is referenced in the other ones (via Tools -> References). So now, all I have to do is to call them as any API outside referenced function (commonProject.Function (arg1, arg2, arg3 etc)).
Unfortunately, I had some problems with a popup message (here) and the workaround seems to be referencing the fifth project in the other ones at runtime.
In order to do this, I uninstalled three of the other four Addins, remaining only one, and used the following code in its Workbook_Open event:
Private Sub Workbook_Open()
Dim strNamePath As String
Dim bolAddinIsRefered as Boolean
Dim oRef As Variant
strNamePath = "c:\etc\etc\etc\Filename.xlam"
For Each oRef In ThisWorkbook.VBProject.References
If oRef.FullPath = strNamePath Then bolAddinIsRefered = True
Next oRef
If bolAddinIsRefered = False Then ThisWorkbook.VBProject.References.AddFromFile strNamePath
End Sub
The problem is, when I used to create the reference at design time by hand, it worked mostly fine (except for the annoying popup I'm now trying to circumvent). But when I try creating the Reference at runtime, it returns an "Name conflicts with existing module, project, or object library" error popup, and the reference is not created.
I have verified the module and worksheet names and there is no duplicated one.
Does anyone have any idea how to prevent this error?
it seems after one and a half day struggling with a problem, when you ask the question on StackOverflow the solution just pops in your mind.
I'd already tried to change both projects names, even internal functions or subs resembling the same name. But I had not looked upon the FILE NAME!
As my solution is called Sisyphus, the filenames of all addins started with "Sisyphus"--"Sisyphus Common Functions.xlam", "Sisyphus DocMerge.xlam" etc.
The problem was VBA was comparing the first word in filename. I removed spaces and it worked well. Now my filenames are "SisyphusCommonFunctions.xlam", "SisyphusDocMerge.xlam" etc. and the referencing in runtime works all right.
Thank you for your time, I'll let this Question and Answer here, because it can be usefull to someone.
I have a VBA Excel model which I have separated into two separate workbooks:
InputsWB, which contains all the inputs for the model, and
RunnerWB, which contains the bulk of the VBA code (and all the class modules live here).
Depending on the needs of the user, the InputsWB can either call the macro in the RunnerWB or the RunnerWB can call multiple versions of the InputsWB. The detail doesn't matter for this question but the two workbooks need to be separated.
Mysteriously, after the split I sometimes get a Run-time 1004 error with the message Cannot Run Visual Basic Editor Because of a Syntax error. If I Debug + F5 then the code runs until it encounters the next Application.Calculate.
I have seen many similar questions while recording macros, or on Application.Calculate. I think mine is related to the Application.Calculate, but those answers don't explain why this happens. Some reasons I can think off, but cannot confirm online is:
There are Modules in the two workbooks with the same names (although I've changed the modules names in the InputsWB but still get the error on occasion.
There are VBA functions in the two workbooks with the same names
Are there any other reasons why I could be getting these errors?
UPDATE (2019/12/02)
I finally realised why I got this error. I am calling a macro in my RunnerWB from my InputsWB. However, there was a duplicate function declared in my RunnerWB. Usually, this would give a Compile error: ambigious name detected when the macro was initiated from ThisWorkbook. However, because it was initiated from another workbook I got the Syntax error explained above.
UPDATE (2020/07/22)
I am now getting this error again on Application.Calculate right at the end of the run. Which breaks me is that I use Application.Calculate many times during the run.
UPDATE (2020/07/23)
I found the issue that caused the bug at last.
I had a sub called Main in a Module called Main
One of the buttons in my workbook referred to this button, and because the names are the same it links it as Main!Main
Later I renamed the module to MainSub. The button still worked, but something inside VBA was broken which was triggered in some Application.Calculate executions.
Conclusion
Never give your sub the same name as your module in VBA, as this may cause problems down the line.
Any help with dynamically loading of classes /variables in VBA?
I have an excel sheet and other few dependent sheets, depending on the requirement, on click of a button I am loading other sheets.
Say Main workbook -> Loads sheet1.xlam and related classes ( cls_one.cls) on click of button.
I am using below code to load sheet1.xlam and cls_one.cls
Step 1: Load Main.xlsm
Step 2 load - sheet1.xlsm
Set addedWs = ActiveWorkbook.Sheets.Add(Type:=path, after:=ActiveWorkbook.Sheets(ActiveWorkbook.Sheets.count))
Step 3: Load Cls_one.cls dynamically using the below method.
Public Function InsertClass(ByRef oWB As Workbook, ByVal className As String, ByVal filePath As String, Optional bUAT As Boolean = False) As Boolean
On Error GoTo Errhandler
oWB.VBProject.VBComponents.Import filePath
InsertClass = True
Exit Function
Errhandler:
If err <> 0 Then
If Not bUAT Then
MsgBox (err.Description)
End If
err.Clear
End If
InsertClass = False
End Function
Above code works fine, however, I have a reference to cls_one in sheet1.xlsm which never works, on loading or give error Undefined object error.
Public Myclassone As cls_one
this variable declaration is in sheet1.xlsm.
I tried flipping steps loading class first and sheet1.xlam next, but still getting the same error with Excel 2013, this piece of code works fine with 2010.
Trying to understand what is the best way to reference dynamically loaded classes in forms or other classes?
Also, I tried changing error preferences in tools -> Options -> Break on Unhandledexception.
You can't early-bind to something that's only going to exist at run-time, by definition - there's no way that could have worked:
Public Myclassone As cls_one
If cls_one doesn't exist at compile-time, then the module can't be compiled.
this piece of code works fine with 2010
No. This piece of code works fine if it's not in any execution path that involves the module declaring a public variable of a type that doesn't exist... regardless of what version the host application is (this behavior is purely VBA, nothing to do with Excel).
That's kind of a hack though: a VBA project will not compile (through Debug -> Compile), but will happily run anyway if the entry point doesn't involve loading the module: that's because of the somewhat-interpreted nature of VBA.
Proof:
Module1
Option Explicit
Public foo As Something '<~ undefined, won't compile
Module2
Option Explicit
Public Sub Test()
Debug.Print "I can run even if the project doesn't compile!"
End Sub
You can run Module2.Test regardless of whether the project compiles, because Module1 isn't in the picture at all. Now change Module2.Test to this:
Public Sub Test()
foo.DoStuff '<~ expect fireworks
End Sub
When I ran this, Excel just outright crashed.
So the bottom line is this: you can reference a non-existing class in a module. The project won't be compilable, but if no code references the non-existing class then the project will be executable anyway, and the non-compilable module can then be executed in another execution context (i.e. from a separate entry point), after the class is added.
I would not recommend using non-compilable code for anything remotely important though, since doing that takes away the only compile-time validation you have in the VBE for your code - consider reviewing Rubberduck inspections in that case (several inspections flag run-time error situations statically).
A better, more viable solution would be to reconsider the dependency chain and the overall approach.
First three elements of the background:
I have created a Excel Template which is used, in turn, to create a 'personalised template' where user name, user comment, and save data pathnames are embedded in the personalised template.
The personalised template is used to produce monthly workbooks (actually, time sheets)
The template has been created in Office/Excel 2007 running under Vista but the (current) target environment is a corporate network running Office/Excel 2003 under XP Professional. Both templates are therefore created as Excel 2003 templates (which in the development environment run in compatibility mode)
So far, so good - the templates work well in the development environment - the 'personalisation' code is in a WorkBook_Open() routine under 'ThisWorkbook' - it runs, DELETES ITSELF, and saves as the personalised template. Both templates have the 'process macros' in the Sheet1 code (the principle here is that the initial template has macros in 'ThisWorkbook' and 'Sheet1'; the personalised template has macros only in 'Sheet1' and the workbooks created from the personalised template have NO MACROS AT ALL.
In 'beta testing' I had problems with Excel2000 under XP (fileformats) and on a target machine my WorkBook_Open routine was deleted in the main template (instead of in the personalised template) - even though everything worked OK on Excel2007/Vista.
I felt at the time that the invalid deleting of Workbook_Open was probably a coding error -but whilst researching the issues I found a number of references to 'Macafee deleting VBA modules'. I did not look into these - but was conscious that the target machine (and environment) run Macafee whilst all my computers run Norton.
Having recoded, I successfully retested in the following:
a) Excel 2007/Vista/Norton
b) Excel 2007/XP Professional/Norton
c) Excel 2000/XP Home/Norton
so felt comfortable to re-test in Excel 2003/XP Professional/Macafee
This time I was watching for it - so, once again, saw the WorkBook_Open routine incorrectly deleted from the main template - unfortunately this was only moments before the USB Memory Key (where the template was running from) was completely destroyed.
So to the question(s) - before I go through the whole process again:
Is there any objective evidence of Macafee removing 'auto-run' VBA modules?
If yes, is there any work-around (this template will be use by three people in a enterprise of ten thousand - so there is no way I will be able to influence/modify the security policies!!! :( ) - if there is a problem with Macafee then I will have to re-think the whole thing!
To answer your actual question... YES, many antivirus programs absolutely consider autorun code that runs on opening Excel or Word docs to be unsafe, and deletes it, because for a brief moment ten years ago this was actually the way some worms worked. I don't know if MacAfee in particular does this but I've definitely heard of it happening.
JUst in case anyone else comes to this thread - I have now fully checked out my template - and re-run it in the target environment - and the Workbook_Open routine was again deleted. after doing some digging around on the target machine I found the Macafee event log and lo and behold...
Event Type: Warning
Event Source: McLogEvent
Event Category: None
Event ID: 258
Date: 09/07/2011
Time: 15:45:40
User: NT AUTHORITY\SYSTEM
Computer:xxxxxxxxxx
Description:
The file F:\Timesheets\Timesheet Generator.xlt\1.OLE contained X97M/Generic Virus. The file was successfully cleaned with Scan engine version 5400.1158 DAT version 6400.0000.
So there is the definitive proof.
Same thing happens with Workbook_Activate()
The 'offending' lines appear to be
With ThisWorkbook.VBProject.VBComponents("ThisWorkbook").CodeModule
.DeleteLines 1, _
.CountOfLines
I have managed to get to the client machine again - and added a new simple template to prove or otherwise my issue. The new WorkBook_Open routine is...
Private Sub WorkBook_Open()
MsgBox "Hello World"
With ThisWorkbook.VBProject.VBComponents("ThisWorkbook").CodeModule
.DeleteLines 1, _
.CountOfLines
End With
MsgBox "Goodbye to all that!"
End Sub
...and this works exactly as required i.e. the routine executes then deletes itself (the second msgbox displays even though the code has been deleted!
So, this test invalidates my question - McAfee is NOT deleting the routine as part of AV protection - so I am no longer looking for a workaround!
Now all I need to do is to figure out what my problem really is!
Thanks for the comments
I am (unfortunately) developing an application in Excel 2000 VBA. I believe I have discovered that any error raised within a Custom Class property, function, or sub debugs as if the error were raised at the point in the VBA code where the property is called. That is, the VBE debugger does not take me to the point in the Class property where the error occurred, but instead where the property was first entered (from a Module Sub or Function, e.g.) This makes it frustrating to develop anything more than the most shallow OO Excel 2000 VBA code since I have to step line-by-line through every Class method to discover the instructions causing an error.
Am I missing something or is this a known bug I have to deal with in Excel 2000? Has this been fixed in 2003 or 2007?
Example code:
'''''''''''''''
'In Module1:
Public Sub TestSub1()
Dim testClass As Class1
Dim testVariant As Variant
Set testClass = New Class1
testVariant = testClass.Property1 'Debugger takes me here...
End Sub
''''''''''''''
' In Class1
Property Get Property1() As Variant
Err.Raise 666, , "Excel 2000 VBA Sux!" 'But error is actually thrown here.
End Property
For Office 2003 you will get this behaviour when the debugger is configured to break on unhandled errors (the default configuration).
If you want it to break on the Err.Raise line, you need to configure it to break on all errors (Tools/Options/General/Error Trapping/Break on All Errors).
I believe it's the same for Office 2000 but don't have a copy to check.
This page is a very good resource on error handling in VBA:
Error Handling and Debugging Tips and Techniques for Microsoft Access, VBA, and Visual Basic 6
This "feature" is the same in Excel 2003 and I'd be surprised if it's different in 2007.
The same still holds true in Excel 2010 - that's where I met this behaviour.
To quote Chip Pearson's site:
There is absolutely no reason to use an error trapping setting other than Break In Class Module.
His description of the difference between the error modes:
When you are testing and running your code, you have three error trapping modes. The first is Break On All Errors. This will cause the debugger to open if any error occurs, regardless of any On Error handling you might have in the code. The second option is Break On Unhandled Errors. This will cause the debugger to open if the error is not handled by an existing On Error directive. This is the most often used option and is the default setting. The third option, Break In Class Module is the most important and least used. It is not the default error trapping mode, so you have to set it manually.
The Break In Class Module is the most important because it will cause the debugger to break on the line of code within an object module that is actually causing the problem. The Break In Class Module setting is in the Options dialog accessible on the Tools menu. It is on the General tab of the Options dialog, as shown below.