3

I have a VBA script for Excel that adds a small procedure in the active workbook. The final version will add a procedure that auto-saves backup copies of the workbook.

The code requires the Microsoft Visual Basic for Applications Extensibility 5.3 library which I can add manually, but I'm interested in having a single script that can add that library into Excel and then use it.

Here's the code that adds the library reference to Excel. It works.

On Error Resume Next    ' If library already referenced, ignore the error
    ThisWorkbook.VBProject.References.AddFromGuid _
        GUID:="{0002E157-0000-0000-C000-000000000046}", _
        Major:=5, Minor:=3
On Error GoTo 0         ' Resume normal error handling

Below is the code that uses this library to add a VBA procedure into the active workbook. It works, but only if the required library reference has first been added to Excel:

Sub AddProcedureToModule()
' This script adds a sample VBA procedure to the active workbook

    ' Add Library Reference:  Microsoft Visual Basic for Applications
    '   Extensibility 5.3
    On Error Resume Next    ' If library already referenced, ignore the error
        ThisWorkbook.VBProject.References.AddFromGuid _
            GUID:="{0002E157-0000-0000-C000-000000000046}", _
            Major:=5, Minor:=3
    On Error GoTo 0         ' Resume normal error handling

    Dim VBProj As VBIDE.VBProject   ' requires Ref: MS VB for Apps Extensibility 5.3
    Dim VBComp As VBIDE.VBComponent ' requires Ref: MS...5.3
    Dim CodeMod As VBIDE.CodeModule ' requires Ref: MS...5.3

    Dim LineNum As Long
    Const DQUOTE = """" ' one " character

    Set VBProj = ActiveWorkbook.VBProject       ' requires Ref: MS...5.3
    Set VBComp = VBProj.VBComponents("Module1") ' requires Ref: MS...5.3
    Set CodeMod = VBComp.CodeModule             ' requires Ref: MS...5.3

    ' Insert code into workbook
    With CodeMod
        LineNum = .CountOfLines + 1
        .InsertLines LineNum, "Public Sub SayHello()"
        LineNum = LineNum + 1
        .InsertLines LineNum, "    MsgBox " & DQUOTE & "Hello World" & DQUOTE
        LineNum = LineNum + 1
        .InsertLines LineNum, "End Sub"
    End With
End Sub

The problem arises when I try to combine the two in a single procedure. Excel throws compile error "User defined type not defined". If I understand correctly, my code uses something called early binding. Excel looks for the library before any code is executed and can't find it.

The answer may be to adapt my code to use late binding so that Excel won't look for that library until after part of the script has executed and the library is available.

Using this post as a guide I modified part of the code to look like this:

Dim VBProj As Object
Dim VBComp As Object
Dim CodeMod As Object
Dim LineNum As Long
Const DQUOTE = """" ' one " character

Set VBProj = CreateObject("ActiveWorkbook.VBProject")
Set VBProj = ActiveWorkbook.VBProject
Set VBComp = CreateObject("VBProj.VBComponents(""Module1"")")
Set CodeMod = CreateObject("VBComp.CodeModule")

Excel threw error "Run-time error '429': ActiveX component can't create object".

Any idea how I can either modify this code to utilize late binding, or otherwise load the library reference and run the rest of the code in the same procedure/module?

TylerH
  • 20,799
  • 66
  • 75
  • 101
ChrisB
  • 3,024
  • 5
  • 35
  • 61
  • 1
    AFAIK, it can't be done. You can't late bind because `CreateObject` will only work on registered types. You can't early bind because that requires a recompile. – Comintern Sep 15 '16 at 20:35
  • 1
    You do not use any `CreateObject`s. You declare all as `Object`, then you keep the original assignments (`Set VBProj = ActiveWorkbook.VBProject` etc). However the entire thing is a big security concern and requires the "Trust access to the VBA project object model" tick to be set. – GSerg Sep 15 '16 at 20:36
  • @GSerg declaring at 'Object' and keeping the original assignments worked but can you explain the security concern? – ChrisB Sep 15 '16 at 20:44
  • 1
    BTW, just to be clear, in this case declaring as object works ***without*** a reference ***or*** `CreateObject`. The only reason it works is that you can request an object with `ActiveWorkbook.VBProject` as opposed to creating one. – Comintern Sep 15 '16 at 20:56

1 Answers1

4

There is a false premise in your original code:

Set VBProj = ActiveWorkbook.VBProject       ' requires Ref: MS...5.3
Set VBComp = VBProj.VBComponents("Module1") ' requires Ref: MS...5.3
Set CodeMod = VBComp.CodeModule             ' requires Ref: MS...5.3

This code does not require any references per se. What does require a reference is the variable declaration above:

Dim VBProj As VBIDE.VBProject
Dim VBComp As VBIDE.VBComponent 
Dim CodeMod As VBIDE.CodeModule 

As soon as you have replaced them with As Object you can run the rest of the code as is without adding a reference. Make sure you have Option Explicit at the top to catch any now-undeclared constants you might be using.

However I would advise you consider a different approach, such as moving the code you want to putting into files into an adding that the files can refer to. Automatically adding VBA code to files is a security concern because it is a common way of spreading malware, it might set antiviruses off, and it requires the Trust access to the VBA project object model setting to be set in the user interface (which I would personally never set).

GSerg
  • 76,472
  • 17
  • 159
  • 346
  • declaring at 'Object' and keeping the original assignments worked but can you explain the security concern? – ChrisB Sep 15 '16 at 20:45
  • 2
    From the link "This security option makes it more difficult for unauthorized programs to build "self-replicating" code that can harm end-user systems." – Tim Williams Sep 15 '16 at 21:37