Run Code In Worksheets Class Code Module In Another Workbook - vba

This code is in a Workbook. ( In a Worksheets Class code Module )
Sub Testie()
Dim FullPathAndName As String
Let FullPathAndName = "'" & ThisWorkbook.Path & "\" & "NeuProAktuelleMakros.xlsm'"
Application.Run Macro:=FullPathAndName & "!FrmProTypeIn", Arg1:=42
End Sub
In another Workbook, named “NeuProAktuelleMakros.xlsm”, which is in the same Folder, I have this code, (in a Normal Code Module):
Sub FrmProTypeIn(MyArg As Long)
MsgBox prompt:="Got Here :). The answer is " & MyArg & " , I forgot the question"
End Sub
If I run the first code, it makes the second code run , ( which tells me I got there and that the answer is 42, I forgot the question )
I would prefer to have the code, FrmProTypeIn() , (which is currently in a Normal Code Module) to be in a Worksheets Code Module. The Worksheet Name is “FoodsLookUpTable”. The Worksheet Code Name is “Tabelle11”
Is this possible and can you give me the syntax?
If that is not possible, what about a simple work around? – The obvious thing I can think of is to have a Call ing code in a normal Module in Workbook “NeuProAktuelleMakros.xlsm” thus:
Sub CallFrmProTypeIn(MyArg As Long)
Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(MyArg)
End Sub
Then I modify the first code slightly to this:
Sub Testies2()
Dim FullPathAndName As String
Let FullPathAndName = "'" & ThisWorkbook.Path & "\" & "NeuProAktuelleMakros.xlsm'"
Application.Run Macro:=FullPathAndName & "!CallFrmProTypeIn", Arg1:=42
End Sub
That workaround works. But maybe there is a simpler way?

The syntax would be:
Let FullPathAndName = "'" & ThisWorkbook.Path & "\" & "NeuProAktuelleMakros.xlsm'"
Application.Run Macro:=FullPathAndName & "!Sheet1.FrmProTypeIn", Arg1:=42

# Peh
Hi Peh
I had not tried that. That and variations of it do work . Great Thanks. I had been told that this was not possible as I was told that a code cannot be run from a Worksheets code module in another Workbook.
I am wondering if this is telling me that technically I the code has become now a Property of the Worksheets Class module.
So the first three lines here are variations of your suggestion that work.
The last two are typical variation that I had been trying which do not work
Sub PehTesties()
Workbooks("NeuProAktuelleMakros.xlsm").Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
Application.Run Macro:=Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
' Application.Run Macro:=Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn Arg1:=42
' Application.Run Macro:="'" & ThisWorkbook.Path & "\" & "NeuProAktuelleMakros.xlsm'!FoodsLookUpTable!CallFrmProTypeIn", Arg1:=42
End Sub
Thanks once again
Alan

Summarising for Prosperity: ( Subject to Edits based on any correcting comments. ;) )
It seems that you can call an Excel code routine ( including those in a Worksheets Class code Module ) from another Workbook. You can pass any optional or required arguments to the called routine.
The documentation is not too clear, or does not explain all, or is wrong..
Using the same example, I can ..
_1) do a simple Call
Call Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn 42
_2) There is an Application.Run Method.
_2a) But I suggest that this code line, although it “works” , probably does not get a chance to use it..
Macro:=Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
I say this as I could just as well do this and it “works” also
Dim vTemp As IBlogPictureExtensibility
Let vTemp = Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
I expect in the latter I did not get a chance to extend the ability to Extensivibly Blog a Picture as the evaluation set off by the = did an auto intensively initiation of the code as I accidently exposed it at the VB Component ( Tabelle11 ) interface, which pseudo made the Object in a pseudo late binding type stylio
This works also
If Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42) Then MsgBox prompt:="It wasn't, but it did, so then perhaps was, was but too late - and then .... like did an auto intensively initiation of the routine as I accidently exposed it at the VB Component ( Tabelle11 ) interface, which pseudo made the Object in a pseudo late binding type stylio. It was not Late, it came too late"
Maybe we could refer to the above as a “pseudo” Run Method, way to Run, or Run way
_2b) Aplication.Run “StringReferrenceToMacroName”, MacroArgument1, MacroArgumet2, .....
I think this is using the Application.Run Method correctly; I believe this is wired to take a string reference to the string name of a macro, and also any arguments therefor. The basic syntax therefore would be
Application.Run Macro:="'NeuProAktuelleMakros.xlsm'!Tabelle11.FrmProTypeIn", Arg1:=42
It is important here not to confuse, as I did, a code bit like Tabelle11.FrmProTypeIn with the last string part in that last string, which is the macro name. This macro Name can be seen, for by example, by hitting Alt+F8 to list the literal names of macros
http://imgur.com/KL9pwFq
An interesting advantage of using the Application.Run Method is that the string reference may be extended to include the full path to the Workbook. This has no effect if the Workbook is open. If however, the Workbook is closed, then the workbook is opened by a code line of this form:
Application.Run Macro:="'H:\ALERMK2014Marz2016\NeueBlancoAb27.01.2014\AbJan2016\OutlineGrouping\RoryAppRun\NeuProAktuelleMakros.xlsm'!Tabelle11.FrmProTypeIn", Arg1:=42
_.....
A few last notes:
_A) These two codes are in fact doing the same thing:
Workbooks("NeuProAktuelleMakros.xlsm").Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
They only work if Workbooks("NeuProAktuelleMakros.xlsm") is Active. This suggests that Application.Run with unqualified Workbook will go to the Active Workbook. I think that this is one of those occasions showing us that VBA is not really a Object Orientated Programming language. The Application can be called at various levels , but effectively goes “back up” the Hierarchy, so the use of Workbooks("NeuProAktuelleMakros.xlsm") has no effect.
Similarly , if “ProAktuellex8600x2.xlsm” is my workbook with the code in it, ( or any other open workbook for that matter ), then this will still work
Workbooks("ProAktuellex8600x2.xlsm").Application.Run Macro:=Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
The important part is the
Workbooks("NeuProAktuelleMakros.xlsm").Worksheets("FoodsLookUpTable").FrmProTypeIn(42)
_ B) use of Run appears to default to Application.Run
_C ) We do not appear to have a Workbooks or Worksheets Run in such a way as we have , for example, a Worksheets Evaluate in addition to an Application Evaluate. This explains why ...Workbooks("NeuProAktuelleMakros.xlsm").Application.Run Macro:=Worksheets("FoodsLookUpTable").FrmProTypeIn(42) ... is “ignoring” the Workbooks("NeuProAktuelleMakros.xlsm")

Related

How to open and activate another workbook in VBA?

I'm creating a macro where I will need to run it in 1 file (called "Masterfile"), it will open and execute the macro on another file ("SurveyReport") and then give me a message box saying "done!".
The code I have to execute on the SurveyReport file works fine when I open that file manually and execute it. The code I need to open SurveyReport from MasterFile is also working it seems, I ran the below with no issues:
Sub PivotTable()
'
' PivotTable Macro
Dim MasterFile As String
MasterFile = ActiveWorkbook.Name
Dim SurveyReport As String
SurveyReport = Application.GetOpenFilename("Excel files (*.xlsx), *xlsx", 1, "Please select the Survey Create Report file", , False)
Workbooks.Open (SurveyReport)
End Sub
But, when I try to activate the SurveyReport file so I can begin executing the macro in it, I get a "Subscript out of range" error. I've tried using the following code after the above block and before the code to execute in the SurveyReport file:
Windows(SurveyReport).Activate
This didn't work, not did:
ThisWorkbook.Activate
...which only had the effect of activating the MasterFile.
SurveyReport file is a .xlsx file. I tried saving it as a .xls file and amending the code, but no joy.
I also tried passing it the file name directly (i.e. Windows("filename.xlsx").Activate), same issue.
ActiveWorkbook is as it says on the tin - whichever workbook happens to be active when the code runs.
ThisWorkbook is always the workbook that the code is sitting in.
You can SET references to specific workbooks rather than just using their names each time. A name can change, or reference the wrong object.... imagine you have a friend called Darren. Each time you mention him you mention him by name. Someone that doesn't know Darren hasn't a clue which Darren out of all the ones available in the world you're talking about. Now imagine you have a little replica of Darren in your pocket... nah, that's a terrible anology - it wouldn't be a replica, it would be a reference to the real Darren... anyway, I digress.
This code sets a reference to the workbook, you can then use that reference any time you want to refer to the correct workbook:
Sub PivotTable()
Dim MasterFile As Workbook
Dim SurveyRptName As String
Dim SurveyReport As Workbook
Set MasterFile = ThisWorkbook '
SurveyRptName = Application.GetOpenFilename("Excel files (*.xlsx), *xlsx", 1, _
"Please select the Survey Create Report file", , False)
If SurveyRptName <> "False" Then
Set SurveyReport = Workbooks.Open(SurveyRptName)
End If
SurveyReport.Activate 'You don't need this line. It doesn't matter if
'the workbook is active, the code knows which one
'you're talking about in the next line.
MsgBox "This is " & SurveyReport.Name & _
" containing " & SurveyReport.Worksheets.Count & " sheets." & vbCr & _
"The value in cell A1 of the first sheet is " & _
SurveyReport.Worksheets(1).Range("A1")
End Sub
Edit: Of course, if you press Cancel when selecting a file then the lines following the IF...THEN code won't have a reference to work on and you'll get a Object Variable or With block variable not set - best not to run the bottom bit of code if you haven't successfully opened the Survey Report file.
The part of the answer that is missing - is that he tried to call a method of an object when his variable was STRING - the open command and subsequent commands will give you an OBJECT - which has properties and methods like .Activate. The STRING type has no such method or property (to be sure - it may have others).
the solution provided by Darren solves this by declaring his SurveyReport as type Workbook - an object of Excel.

Copying sheets while preserving digital signatures

-- Edit: this is now part of the bigger question of how to reliably move sheets about in this question's context --
(Note: during preparing this post and testing solutions, I probably have already answered my own question. Just posting this in the hope anyone smarter than me can come up with something. Anyway, it's still a good resource for future searchers I guess.)
Problem description
I made an Excel solution for one of my customers which has tons of VBA in it. I therefore naturally signed the VBA code, so my customer doesn't get the macro security messages. However, one thing this solution does is making copies of a template sheet in the same workbook. The template sheet is found on it's code name, and all copies of the sheet are from then on recognized by their code name being derived from this (having a trailing sequence nr.) - they need to be identified and handled later on again.
Quite innocent on first sight, but when I demoed the solution and tried to save it I instantly got:
"You have modified a signed project. You do not have the correct key
to sign this project. The signature will be discarded."
after which the signature was discarded, and on re-open the macro security prompts put themselves to good use. Not a good impression :(
The code goes like this in simplified form:
There is a (hidden) "template" sheet in the workbook that acts as the source for new sheets (it has no VBA code behind it nor any ActiveX or form controls on it);
A ribbon button calls VBA code that uses Worksheet.Copy to make a copy of this sheet (and modifies the copy, but that is irrelevant here);
On next save, Excel wants to discard the digital signature.
When I perform the same actions manually on a machine that doesn't have my certificate, I get the same experience. (A lesson: always test on truly blank systems before demoing anything...)
Possible cause
I've searched on this a bit (see e.g. ozgrid.com and answers.microsoft.com), and while remarkedly few people run into this, it seems like a sort-of inevitable thing. The reason behind it I suspect goes like this:
Although the template sheet has no 'real' VBA code on it, the VBA module does exist and has some not-insignificant content;
Copying this sheet creates a new sheet with a thus seemingly 'empty' but still existing and thus significant VBA module;
The hash of the 'total' VBA project is thus altered and the signature is lost.
According to the post on ozgrid.com, this also happens on deletion of sheets, which is explained by the above. It also suggests creating new sheets without the VBA IDE open doesn't trigger this, and deleting these new sheets works too. But once you go to the VBA IDE, all sheets currently present become 'non-deletable' again.
I suspect that when you add a new worksheet without the VBA editor open, Excel adds a worksheet with truly no VBA module added to it, so the project hash will not update. These sheets thus can also be deleted for the same reason. Opening the VBA editor in turn makes the IDE query for the modules in the workbook, at which time these still missing modules get created, baking their presence into the hash, which in turn also makes them uncopyable because their VBA footprint has become non-zero.
Solutions
Now the $1,000,000 question is: how can we work around this? There's some smart people on this site, so maybe we can come up with an out-of-the-box solution?
A useage detail that will make this easier (at least for me): the customer is the only one adding sheets, and he is never going to enter the IDE. It would be nice if I couldn't inadvertently mess up a build just by forgetfully entering the IDE, though.
I've already tried several possible solutions, creating them on a computer with my signature, and testing them on a computer without my signature. For now I'm using Excel 2010 32-bit exclusively for these tests, as that's either all I have, and it is also the version me and my customer are most interested in.
Non-solution 1
Delete all VBA code from the template sheet via the IDE, so it has no contribution to the hash.
If only it were so simple... This didn't work, so probably the existence of the module itself and/or it's meta-data (like it's name) is also hashed, which doesn't sound unreasonable. Or you simply cannot remove all VBA code since the IDE has the tendency to always append an empty line (so a single CrLf is as empty as you can make it this way, though it's CodeModule.CountOfLines return 0 on it). Or the entire VBA code module's content is retrieved and hashed, such that the terminating NULL char or leading 0 byte count contributes to the hash. Anyway, no luck here.
As a test I added a macro that tells which VBA modules there are, and how many lines they contain. Using this, a direct copy of the 'emptied' template sheet still has 0 lines but the signature is lost, while a newly inserted sheet shows up in the VBModules collection and even has 2 lines (the default Option Explicit) and the signature sticks nontheless on save...
But Excel might just be outsmarting us, with that 2-lined Option Explicit being a virtual one, or even the presence of the VBA module in the first place being virtual. When I made the macro also list all sheets with their code names, it turns out these 'safe' sheets have an empty code name (0-length string), indeed indicating they have no code module at all.
Non-solution 2
Create a fresh new sheet instead, and only copy over the contents of the template sheet.
While this does work, it seems a bit iffy to me; I do not believe a mere sourceSheet.Cells.Copy destSheet.Cells will copy absolutely everything the user can throw on it... I'd rather thus keep using the build-in Worksheet.Copy function to be safe and to not have to write piles of special code for every conceivable detail.
As a case on point: sourceSheet.Cells.Copy destSheet.Cells e.g. does copy over worksheet-specific named ranges, but apparently only if they're actually used on the sheet itself. Unreferenced names just vanish in the copy! Talk about special-case copy code I'd have to write...
And then there's the copied sheet not getting any code name assigned at all, which I currently need to recognize them.
Non-solution 3
Create a new temporary workbook, Worksheet.Copy the sheet to there, note it's name, explicitly save it as an .xlsx file to get rid of any VBA module, close and re-open the temp workbook to get rid of any old in-memory cruft, find it again by name, then Worksheet.Move it back to the source workbook.
This works! Without the actual workbook re-open it doesn't, so I guess the in-memory representation just cannot be 'scrubbed' easily enough to not do any harm.
However... The new sheet again doesn't get a code name at all, and even more: I do not like this sheet moving around to unrelated workbooks; while in a quick test any references to other sheets in the original workbook were conserved (and not even got expanded to include the workbook name or path!), I am still a bit uneasy about this... Who knows what type of content users might throw at it...
<Paranoid mode="on">And who knows what type of confidential information will be in there, which I do not want to have the responsibility for when it ends up leaking from the Temp folder without their knowing.</Paranoid>
Non-solution 4
Create a new, empty, temporary sheet as well as a Worksheet.Copy of the template, then replace the true copy's VBA module with the temporary sheet's one. Or just nuke the VBA module as a whole.
I just can't devise a way to do this. VBA itself won't let you do it it seems, and then again I do not want my customers to have to turn on the 'Allow access to the VB project' option for this alone. And I suspect were I able to do this, the damage would already have been done before I could nuke the code module again.
Non-solution 5
Create a macro that is only visible to me (the developer), that creates a perfect copy of the template sheet via either solution 2 or 3, and discards the original template sheet, replacing it with the VBA-scrubbed copy. To be used by me as the last step just before delivering it to the customer.
Solution 2's caveats are less important here because I do know myself what's on the template sheet when I make a new version delivery, so the amount of code needed for a perfect copy is minimal and can be controlled. But then 3 just seems safer and easier... I'll have to pick one.
Since I access the template sheet on it's VBA code name by just using shtTemplate. directly instead of ThisWorkbook.Worksheets("Template")., that apparently complicates it all too much for Excel to switch it in-and-out on the fly. All my attempts so far either failed or just made Excel crash hard on me. No love there :(
I tried this again by manipulating a copy loaded in a second Excel set to msoAutomationSecurityForceDisable, thus avoiding a running VBA host being undermined, also saving and re-opening after almost every update. But that led nowhere either, giving errors like "Automation error - Catastrophic failure" when opening the scrubbed workbook, or mightily corrupting the new workbook (the ThisWorkbook module being duplicated for each sheet module in the project explorer with a derived name).
Maybe-solution 6
Re-write all VBA to not use the hard-coded template sheet's code name, but storing this name on a settings sheet, then applying solution 5 above.
The code finally works, not even having to use a second staging Excel; no crashes nor corruptions! But this code works only insofar that I cannot for the life of me get the code to give the scrubbed sheet a valid code name again; it remains a zero-length string. And no run-time errors to indicate this either. When I have the IDE open during this, the code name is set correctly though.
Which leads me to believe that having a code name on your sheet implies it having a non-null code module, which implies it messing with the digital signature. And that's... not so unexpected really, in hindsight.
Final solution
Which leads me to believe there is just no way whatsoever that I could create a template sheet that both:
Is safe to copy via Worksheet.Copy without losing the signature, and
Has no code module while having a non-null code name.
The only solution I see so far is thus to indeed use a scrubbed template sheet to be able to use Worksheet.Copy, but to find and identify it and it's resulting sheets by other means than by their code name. There is a user-hidden section on it that I might add a "This is the template/copy" status to, though it makes my inner perfectionist cringe.
However, if anyone feels like experimenting, it would be nice to have a few more alternatives! I can post code samples when needed.
It's a lot to take in, and I do not pretnd this will answer will solve all your problems. But I once wrote a function called SoftLink which would take up to 4 parameters (i) Boolean: CellRef (or NamedRange) (ii) String: Range (iii) String: WorksheetName (iv) String: WorkbookName which would break any link with any cells and then you resolve the string parameters in VBA code.
There no doubt a performance hit with this approach but it is one way to solve Link hell.
Example calling formulas
=softlink(FALSE,"Foo")
=softlink(TRUE,"C4","Sheet1","Book2")
=softlink(TRUE,"D5","Sheet2")
and I have knocked up from memory an implementation. I have a phobia of On Errors .... so forgive some strange loopings in the subroutines.
Option Explicit
Function SoftLink(ByVal bIsCell As Boolean, ByVal sRangeName As String, _
Optional sSheetName As String, Optional sBookName As String) As Variant
Dim vRet As Variant
If Len(sRangeName) = 0 Then vRet = "#Cannot resolve null range name!": GoTo SingleExit '* fast fail
Dim rngCaller As Excel.Range
Set rngCaller = Application.Caller
Dim wsCaller As Excel.Worksheet
Set wsCaller = rngCaller.Parent
Dim wbCaller As Excel.Workbook
Set wbCaller = wsCaller.Parent
Dim wb As Excel.Workbook
If Len(sBookName) > 0 Then
vRet = FindWorkbookWithoutOnErrorResumeNext(sBookName, wb)
If Len(vRet) > 0 Then GoTo ErrorMessageExit
Else
Set wb = wbCaller
End If
Debug.Assert Not wb Is Nothing
Dim ws As Excel.Worksheet
If Len(sSheetName) > 0 Then
vRet = FindWorksheetWithoutOnErrorResumeNext(wb, sSheetName, ws)
If Len(vRet) > 0 Then GoTo ErrorMessageExit
Else
Set ws = wsCaller
End If
Dim rng As Excel.Range
If bIsCell Then
vRet = AcquireCellRange(ws, sRangeName, rng)
If Len(vRet) > 0 Then GoTo ErrorMessageExit
Else
vRet = AcquireNamedRangeWithoutOERN(ws, sRangeName, rng)
If Len(vRet) > 0 Then GoTo ErrorMessageExit
End If
SoftLink = rng.Value2
SingleExit:
Exit Function
ErrorMessageExit:
SoftLink = vRet
GoTo SingleExit
End Function
Function AcquireCellRange(ByVal ws As Excel.Worksheet, ByVal sRangeName As String, ByRef prng As Excel.Range) As String
On Error GoTo FailedCellRef
Set prng = ws.Range(sRangeName)
SingleExit:
Exit Function
FailedCellRef:
AcquireCellRange = "#Could not resolve range name '" & sRangeName & "' on worksheet name '" & ws.Name & "' in workbook '" & ws.Parent.Name & "'!"
End Function
Function AcquireNamedRangeWithoutOERN(ByVal ws As Excel.Worksheet, ByVal sRangeName As String, ByRef prng As Excel.Range) As String
'* because I do not like OERN
Dim oNames As Excel.Names
Dim bSheetScope As Long
For bSheetScope = True To False
Set oNames = VBA.IIf(bSheetScope, ws.Names, ws.Parent.Names)
Dim namLoop As Excel.Name
For Each namLoop In oNames
If VBA.StrComp(namLoop.Name, sRangeName, vbTextCompare) = 0 Then
Set prng = ws.Range(sRangeName)
GoTo SingleExit
End If
Next
Next
ErrorMessageExit:
AcquireNamedRangeWithoutOERN = "#Could not resolve range name '" & sRangeName & "' on worksheet name '" & ws.Name & "' in workbook '" & ws.Parent.Name & "'!"
SingleExit:
Exit Function
End Function
Function FindWorksheetWithoutOnErrorResumeNext(ByVal wb As Excel.Workbook, ByVal sSheetName As String, ByRef pws As Excel.Worksheet) As String
'* because I do not like OERN
Dim wsLoop As Excel.Worksheet
For Each wsLoop In wb.Worksheets
If VBA.StrComp(wsLoop.Name, sSheetName, vbTextCompare) = 0 Then
Set pws = wsLoop
GoTo SingleExit
End If
Next wsLoop
ErrorMessageExit:
FindWorksheetWithoutOnErrorResumeNext = "#Could not resolve worksheet name '" & sSheetName & "' in workbook '" & wb.Name & "'!"
SingleExit:
Exit Function
End Function
Function FindWorkbookWithoutOnErrorResumeNext(ByVal sBookName As String, ByRef pwb As Excel.Workbook) As String
'* because I do not like OERN
Dim wbLoop As Excel.Workbook
For Each wbLoop In Application.Workbooks
If VBA.StrComp(wbLoop.Name, sBookName, vbTextCompare) = 0 Then
Set pwb = wbLoop
GoTo SingleExit
End If
Next wbLoop
ErrorMessageExit:
FindWorkbookWithoutOnErrorResumeNext = "#Could not resolve workbook name '" & sBookName & "'!"
SingleExit:
Exit Function
End Function

Excel VBA Self-Destruct Switch

I have had my first "Client-took-my-work-and-then-ghosted-me-without-paying" experience. For the future, I want to put in a killswitch, disguised as a regular macro, which makes the whole thing unusable. That way, even if they hire someone to crack the password and remove my "Your trial has expired..." check, a normal-looking macro (Something like "Fix_Sheet_Formatting") would be easily overlooked and run, destroying everything and saving the changes.
However, that leaves the VBA... We're talking a full purge here, so everything must go. I'll figure out how to do all of this on my own, I just don't want to waste time pursuing something that isn't possible:
Can VBA code delete itself from a workbook while running or does it have to be deleted from a macro on another workbook? And would deleting the code cause the macro to stop running, or can I have it delete everything except a nasty MsgBox after everything is done?
I'll leave the comments to debating whether or not this is a good idea (IMHO it probably isn't). As to the question itself, of course it's possible, and won't interrupt macro execution:
Public Sub Example()
Dim proj As Object
Set proj = ThisWorkbook.VBProject
Dim comp As Object
For Each comp In proj.VBComponents
With comp.CodeModule
.DeleteLines 1, .CountOfLines
If comp.Name = "ThisWorkbook" Then
.InsertLines 1, "Private Sub Workbook_Open()" & vbCrLf & _
vbTab & "MsgBox ""Where's my money, ##$%&!?""" & vbCrLf & _
"End Sub"
End If
End With
Next
ThisWorkbook.Save
End Sub
Obfuscation is an exercise for the reader.
Put a loop with due date as your payment data . Inside write code to get macro into infinity loop or to delete code.
If payment happens on time then just comment above piece of code or else just wait client come back to you on next day defined in loop.

Calling a macro from another workbook not always being executed

In Excel Workbook A, I have a macro which
fills inputs for a series of other workbooks
calls the AUTORUN
collects results in a summary tab
The logic is quite simple but I found that Step 2 is not always being executed, which is quite strange... I have confirmed that inputs have been properly entered and I can go each failed Excel workbook and run the simulation by clicking the button linked to AUTORUN. Does anyone have any suggestions on this (I am using EXCEL 2013)? Thanks in advance! Below is how macro from a different workbook is called.
Workbooks(NewFileName).Activate
Application.Run ("'" & NewFileName & "'!AUTORUN")
I run some tests
File 1 (the caller):
The method AAATEST() is in a Module
Public Sub AAATEST()
Dim file As String: file = "test.xlsm"
call Workbooks.Open("C:\temp\" & file)
Workbooks(file).Activate
Call Application.Run("'" & file & "'!AUTORUN")
End Sub
File 2 (with autorun):
The method AUTORUN() is in a Module
Public Sub AUTORUN()
MsgBox "autorun is my life"
End Sub
Result:
I always receive the message when running the AAATEST() method. I suspect a not raised error in the AUTORUN.
To check if the Autorun is run or not, add Stop as first instruction in your Method and execute your code step by step (with F8)
Public Sub AUTORUN()
Stop
...
MsgBox "autorun is my life"
End Sub

VBA Calling Public Sub in Worksheet Module: Crosstalk between Sheet and ThisWorkbook modules

I was so pleased that this works that I thought I would share it...
It allows me to have generic event handlers in the ThisWorkbook module to initiate worksheet-specific code in an anonymous ActiveSheet.
I use the CalByName function to access user-defined, Public methods in the Sheet objects.
I'm using it to re-start a timer that is killed by auto-recover saves and it works great. I've got it on two sheets.
In ThisWorkbook Module:
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
Dim ws As Worksheet
Set ws = ActiveSheet
On Error GoTo afterSaveFailed
CallByName ws, "afterSave", VbMethod
If debugEvents Then Debug.Print timeStamp & ": " & "AfterSave: afterSave called in sheet: " & ws.Name
On Error GoTo 0
Exit Sub
afterSaveFailed:
If debugEvents Then Debug.Print timeStamp & ": " & "AfterSave: afterSave Failed in sheet: " & ws.Name
Err.Clear
End Sub
then in each applicable Worksheet Module:
Public Sub afterSave()
'sheet specific after save handler
End Sub
CallByName is a very old and well known method. :)
Also in your code
What is debugEvents You might want to use Option Explicit
Why _AfterSave and not _BeforeSave? What if afterSave() makes a change to the workbook? You will have to save again before closing.
Also instead of pasting the code in each applicable worksheet and creating duplicate code why not create a common code in a module?
Public Sub afterSave(oWs as Worksheet)
With oWs
'sheet specific after save handler
End With
End Sub
Worst Case Scenario:
Assuming that the afterSave is different for every worksheet. You could always use a Select Case. This will ensure that you have your code in one place. You don't have to hop around to check the code.
Public Sub afterSave(oWs As Worksheet)
With oWs
Select Case .Name
Case "AAA"
Case "BBB"
Case "CCC", "DDD" '<~~ If two or more worksheet have the same code
End With
End Sub
thanks for the comments. My responses are inserted below:
CallByName is a very old and well known method. :)
Which is why I didn't entitle the post "CallByName".
If you read the title, thats what its about. I couldn't find a solution to this on the web so I had to figure out my own method. My point is not about CallByName, its about cross-talk between the modules and using CallByName to maximum effect to achieve this, I'm not suggesting that CallByName is novel.
I guess I could achieve the same thing using RaiseEvents but I think maybe this is more straight-forward.
I was just happy coz VBA was letting me do what I wanted for a change :q
Also in your code
What is debugEvents You might want to use Option Explicit
It's a global flag to control how noisy the immediate window is. Yes I always use option explicit and do so in this case. I assume that's a given.
Why _AfterSave and not _BeforeSave? What if afterSave() makes a change to the workbook? You will have to save again before closing.
Because the stuff I'm doing needs to be done after the save has completed, not before it starts. The Autorecovery save event kills a timer that I'm running so I need to re-start it after the save.
Also instead of pasting the code in each applicable worksheet and
creating duplicate code why not create a common code in a module?
Public Sub afterSave(oWs as Worksheet)
With oWs
'sheet specific after save handler
End With End Sub
Worst Case Scenario:
Because its "sheet specific"
Assuming that the afterSave is different for every worksheet. You
could always use a Select Case. This will ensure that you have your
code in one place. You don't have to hop around to check the code.
Public Sub afterSave(oWs As Worksheet)
With oWs
Select Case .Name
Case "AAA"
Case "BBB"
Case "CCC", "DDD" '<~~ If two or more worksheet have the same code
End With End Sub
OK, I'll think about that: thanks for the suggestion :)
When I have generic features, I prefer to handle that with Class Modules. To me that's neater: I just instance the object in the sheet to expose the generic features, and as in your suggestion, I only have to check it once. I also use the Class_Initialize and Class_Terminate events to transparently manage life cycle of the generic features. I couldn't do that with generic code in the ThisWorkbook object.
Thats how I handle general functionality, for object-specific features, my preference is to collect the code that is specific to the object, in the actual object: I prefer shy objects. There's really no need to expose that detail to supervisory code. Also, if I'm working on the sheet, I don't have to skip back to a general module and I don't have to try and remember where I put the code or invent some convention, coz it's right there in sheet.
Thank you very much for taking the time to respond in detail!