I've been experiencing some strange quirks in Excel while programatically removing modules then reimporting them from files. Basically, I have a module named VersionControl that is supposed to export my files to a predefined folder, and reimport them on demand. This is the code for reimporting (the problem with it is described below):
Dim i As Integer
Dim ModuleName As String
Application.EnableEvents = False
With ThisWorkbook.VBProject
For i = 1 To .VBComponents.Count
If .VBComponents(i).CodeModule.CountOfLines > 0 Then
ModuleName = .VBComponents(i).CodeModule.Name
If ModuleName <> "VersionControl" Then
If PathExists(VersionControlPath & "\" & ModuleName & ".bas") Then
Call .VBComponents.Remove(.VBComponents(ModuleName))
Call .VBComponents.Import(VersionControlPath & "\" & ModuleName & ".bas")
Else
MsgBox VersionControlPath & "\" & ModuleName & ".bas" & " cannot be found. No operation will be attempted for that module."
End If
End If
End If
Next i
End With
After running this, I've noticed that some modules don't appear anymore, while some have duplicates (e.g. mymodule and mymodule1). While stepping through the code, it became obvious that some modules still linger after the Remove call, and they get to be reimported while still in the project. Sometimes, this only resulted having the module suffixed with 1, but sometimes I had both the original and the copy.
Is there a way to flush the calls to Remove and Import so they apply themselves? I'm thinking to call a Save function after each, if there's one in the Application object, although this can cause losses if things go wrong during import.
Ideas?
Edit: changed tag synchronization to version-control.
This is a live array, you are adding and removing items during iteration thereby changing the index numbers. Try processing the array backwards. Here is my solution without any error handling:
Private Const DIR_VERSIONING As String = "\\VERSION_CONTROL"
Private Const PROJ_NAME As String = "PROJECT_NAME"
Sub EnsureProjectFolder()
' Does this project directory exist
If Len(Dir(DIR_VERSIONING & PROJ_NAME, vbDirectory)) = 0 Then
' Create it
MkDir DIR_VERSIONING & PROJ_NAME
End If
End Sub
Function ProjectFolder() As String
' Ensure the folder exists whenever we try to access it (can be deleted mid execution)
EnsureProjectFolder
' Create the required full path
ProjectFolder = DIR_VERSIONING & PROJ_NAME & "\"
End Function
Sub SaveCodeModules()
'This code Exports all VBA modules
Dim i%, sName$
With ThisWorkbook.VBProject
' Iterate all code files and export accordingly
For i% = 1 To .VBComponents.count
' Extract this component name
sName$ = .VBComponents(i%).CodeModule.Name
If .VBComponents(i%).Type = 1 Then
' Standard Module
.VBComponents(i%).Export ProjectFolder & sName$ & ".bas"
ElseIf .VBComponents(i%).Type = 2 Then
' Class
.VBComponents(i%).Export ProjectFolder & sName$ & ".cls"
ElseIf .VBComponents(i%).Type = 3 Then
' Form
.VBComponents(i%).Export ProjectFolder & sName$ & ".frm"
ElseIf .VBComponents(i%).Type = 100 Then
' Document
.VBComponents(i%).Export ProjectFolder & sName$ & ".bas"
Else
' UNHANDLED/UNKNOWN COMPONENT TYPE
End If
Next i
End With
End Sub
Sub ImportCodeModules()
Dim i%, sName$
With ThisWorkbook.VBProject
' Iterate all components and attempt to import their source from the network share
' Process backwords as we are working through a live array while removing/adding items
For i% = .VBComponents.count To 1 Step -1
' Extract this component name
sName$ = .VBComponents(i%).CodeModule.Name
' Do not change the source of this module which is currently running
If sName$ <> "VersionControl" Then
' Import relevant source file if it exists
If .VBComponents(i%).Type = 1 Then
' Standard Module
.VBComponents.Remove .VBComponents(sName$)
.VBComponents.Import fileName:=ProjectFolder & sName$ & ".bas"
ElseIf .VBComponents(i%).Type = 2 Then
' Class
.VBComponents.Remove .VBComponents(sName$)
.VBComponents.Import fileName:=ProjectFolder & sName$ & ".cls"
ElseIf .VBComponents(i%).Type = 3 Then
' Form
.VBComponents.Remove .VBComponents(sName$)
.VBComponents.Import fileName:=ProjectFolder & sName$ & ".frm"
ElseIf .VBComponents(i%).Type = 100 Then
' Document
Dim TempVbComponent, FileContents$
' Import the document. This will come in as a class with an increment suffix (1)
Set TempVbComponent = .VBComponents.Import(ProjectFolder & sName$ & ".bas")
' Delete any lines of data in the document
If .VBComponents(i%).CodeModule.CountOfLines > 0 Then .VBComponents(i%).CodeModule.DeleteLines 1, .VBComponents(i%).CodeModule.CountOfLines
' Does this file contain any source data?
If TempVbComponent.CodeModule.CountOfLines > 0 Then
' Pull the lines into a string
FileContents$ = TempVbComponent.CodeModule.Lines(1, TempVbComponent.CodeModule.CountOfLines)
' And copy them to the correct document
.VBComponents(i%).CodeModule.InsertLines 1, FileContents$
End If
' Remove the temporary document class
.VBComponents.Remove TempVbComponent
Set TempVbComponent = Nothing
Else
' UNHANDLED/UNKNOWN COMPONENT TYPE
End If
End If
Next i
End With
End Sub
OP here... I managed to work around this weird issue, but I haven't found a true solution. Here's what I did.
My first attempt after posting the question was this (spoiler: it almost worked):
Keep removing separate from importing, but in the same procedure. This means that I had 3 loops - one to store a list of the module names (as plain strings), another to remove the modules, and another to import the modules from files (based on the names that were stored in the aforementioned list).
The problem: some modules were still in the project when the removal loop ended. Why? I cannot explain. I'll mark this as stupid problem no. 1. I then tried placing the Remove call for every module inside a loop that kept trying to remove that single module until it couldn't find it in the project. This got stuck in an infinite loop for a certain module - I can't tell what's so special about that particular one.
I eventually figured out that the modules were only truly removed after Excel finds some time to clear its thoughts. This didn't work with Application.Wait(). The currently running VBA code actually needed to end for this to happen. Weird.
Second work-around attempt (spoiler: again, it almost worked):
To give Excel the required time to breathe after removals, I placed the removing loop inside a button click handler (without the "call Remove until it's gone" loop), and the importing loop in the click handler of another button. Of course, I needed the list of module names, so I made it a global array of strings. It was created in the click handler, before the removal loop, and it was supposed to be accessed by the importing loop. Should have worked, right?
The problem: The aforementioned string array was empty when the importing loop started (inside the other click handler). It was definitely there when the removal loop ended - I printed it with Debug.Print. I guess it got de-allocated by the removals (??). This would be stupid problem no. 2. Without the string array containing the module names, the importing loop did nothing, so this work-around failed.
Final, functional workaround. This one works.
I took Work-around number 2 and, instead of storing the module names in a string array, I stored them in a row of an auxiliary sheet (I called this sheet "Devel").
This was it. If anyone can explain stupid problem no. 1 and stupid problem no. 2, I beg you, do so. They're probably not that stupid - I'm still at the beginning with VBA, but I have solid knowledge of programming in other (sane and modern) languages.
I could add the code to illustrate stupid problem no. 2, but this answer is already long. If what I did was not clear, I will place it here.
To avoid duplicate when importing, I modified the script with following strategy :
Rename existing module
Import module
Delete renamed module
I don't have anymore duplicate during import.
Sub SaveCodeModules()
'This code Exports all VBA modules
Dim i As Integer, name As String
With ThisWorkbook.VBProject
For i = .VBComponents.Count To 1 Step -1
name = .VBComponents(i).CodeModule.name
If .VBComponents(i).Type = 1 Then
' Standard Module
.VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".module"
ElseIf .VBComponents(i).Type = 2 Then
' Class
.VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".classe"
ElseIf .VBComponents(i).Type = 3 Then
' Form
.VBComponents(i).Export Application.ThisWorkbook.Path & "\trunk\" & name & ".form"
Else
' DO NOTHING
End If
Next i
End With
End Sub
Sub ImportCodeModules()
Dim i As Integer
Dim delname As String
Dim modulename As String
With ThisWorkbook.VBProject
For i = .VBComponents.Count To 1 Step -1
modulename = .VBComponents(i).CodeModule.name
If modulename <> "VersionControl" Then
delname = modulename & "_to_delete"
If .VBComponents(i).Type = 1 Then
' Standard Module
.VBComponents(modulename).name = delname
.VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".module"
.VBComponents.Remove .VBComponents(delname)
ElseIf .VBComponents(i).Type = 2 Then
' Class
.VBComponents(modulename).name = delname
.VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".classe"
.VBComponents.Remove .VBComponents(delname)
ElseIf .VBComponents(i).Type = 3 Then
' Form
.VBComponents.Remove .VBComponents(modulename)
.VBComponents.Import Application.ThisWorkbook.Path & "\trunk\" & modulename & ".form"
Else
' DO NOTHING
End If
End If
Next i
End With
End Sub
Code to be pasted in a new module "VersionControl"
I've been struggling with this issue for days now. I built a crude version control system similar to this, though not using arrays. The version control module is imported on Workbook_Open and then the startup procedure is called to import all of the modules listed in the version control module. Everything works great except Excel started creating duplicate version control modules because it would import the new module before the deletion of the existing one finished. I worked around that by appending Delete to the previous module. The problem then being that there were still two procedures with the same name. Chip Pearson has some code for deleting a procedure programmatically, so I deleted the startup code from the older version control module. Still, I ran into an issue where the procedure had not been deleted by the time the startup procedure was called. I finally found a solution on another stack overflow thread that is so simple it makes me want to put my head through a wall. All I had to do was change the way I call my startup procedure by using
Application.OnTime Now + TimeValue("00:00:01"), "StartUp"
Everything works perfectly now. Though, I will probably go back and delete out the now redundant renaming of the module and deleting the second procedure and see if this alone solves my original problem. Here is the other thread with the solution...
Source control of Excel VBA code modules
Not sure if it helps somebody ...
In an xlsm container (if you open it as a zip-file) you can find a file called like vbaproject. You can overwrite this in order to "update" the whole content of the VBProject (all standardmodules, classmodules, userforms, table-modules and thisworkbook-module).
With this aproach you can create the "new" code etc. in a copy of your workbook and finally overwrite the vbproject file in the destination workbook. This maybe avoid the need to export / import modules and suffering of duplicates. (Sorry for posting no code due to lack of time)
The rename, import and delete workaround didn't work in my case. It seems (but this is pure speculation) that Excel might save the compiled objects in its .XLMS file, and when this file is re-opened these objects are reloaded in memory before the ThisWorkbook_open function occurs. And this leads the renaming (or removal) of certain modules to fail or be delayed (even when trying to force it with the DoEvents call). The only workaround I found is to use the .XLS binary format. For some obscure reason (I suspect the compiled objects are not bundled in the file), it works for me.
You have to know that you won't be able to re-import any module being/having been used or referenced at the time your import code runs (renaming will fail with error 32813/removal of the module will be delayed until after you will try to import, adding annoying '1's at the end of your module names). But for any other module, it should work.
If all of your source code needs to be managed, a better solution would be to "build" your workbook from scratch using some script or tool, or switch to a better suited programming language (i.e. one that doesn't live inside an Office suite software ;) I haven't tried it but you could look here: Source control of Excel VBA code modules.
I've spent quite some time on this issue. The point is that any VBComponent deleted is in fact not deleted before the process which did it has ended. Because rename is done immediately this is the method of choice to get the new imported component out of the way.
By the way: For the versioning I use Github which is easy to use and just perfect, specifically when only changed components are exported - what I do.
Those interested may have a look at the public Github repo Excel-VB-Components-Management-Services or at the related post Programmatically updating or synchronizing VBA code of Excel VB-Project Components
Another situation related to the title of this question:
I was adding components in a loop (from python):
for file in files_to_import:
project.VBComponents.Import(file)
This sometimes corrupted the workbook, sometimes did not.
It appears something as simple as this:
for file in files_to_import:
project.VBComponents.Import(file)
print(project.VBComponents.Count)
helps "flush" the vbproject and fix the issue 🤞
Related
We will put 100s of Excel worksheets out in the field this year. The code periodically needs to be updated when bugs are found. For last year's effort, I was able to dynamically have workbooks pull updates for .bas files. This year I want to dynamically have workbooks pull updates for the code embedded in the worksheets too.
EXPORT CODE
The export code is pretty simple, but there are artifacts in the .txt files
Sub SaveSoftwareFile(path$, name$, ext$)
ThisWorkbook.VBProject.VBComponents(name).Export path & name & ext
Example Call: SaveSoftwareFile path, "ThisWorkbook", ".txt"
The problem is that the export has a lot of header information that I don't care about (in red). I just want the part in blue. Is there switch that allows me not to save it, or do I have to manually go into the export and remove it myself?
IMPORT CODE
The import code is pretty straight forward too, but it causes the error "Can't enter break mode at this time", and I'm struggling to figure out the right path forward. If I manually try and delete this code, Excel is also unhappy. So maybe my approach is altogether incorrect. Here's the code:
Sub UpgradeSoftwareFile(path$, name$, ext$)
Dim ErrorCode%, dest As Object
On Error GoTo errhandler
Select Case ThisWorkbook.VBProject.VBComponents(name).Type
Case 1, 3 'BAS, FRM
<Not relevant for this discussion>
Case 100 'Worksheets
Set dest = ThisWorkbook.VBProject.VBComponents(name).codemodule
dest.DeleteLines 1, dest.CountOfLines 'Erase existing | Generates breakpoint error
dest.AddFromFile path & name & ext '| Also generates breakpoint error
End Select
Example Call: UpgradeSoftwareFile path, "ThisWorkbook", ".txt"
Thanks in advance for your help
Please, try the next way of exporting and you will not have the problem any more:
Sub SaveSoftwareFile(path$, sheetCodeModuleName$, FileName$)
Dim WsModuleCode As String, sCM As VBIDE.CodeModule, strPath As String, FileNum As Long
Set sCM = ThisWorkbook.VBProject.VBComponents(sheetCodeModuleName).CodeModule
WsModuleCode = sCM.Lines(1, sCM.CountOfLines)
'Debug.Print WsModuleCode
strPath = ThisWorkbook.path & "\" & FileName
FileNum = FreeFile
Open strPath For Output As #FileNum
Print #FileNum, WsModuleCode
Close #FileNum
End Sub
You can use the above Sub as following:
Sub testSaveSheetCodeModule()
Dim strPath As String, strFileName As String, strCodeModuleName As String
strPath = ThisWorkbook.path
strFileName = "SheetCode_x.txt"
strCodeModuleName = Worksheets("Test2").codename 'use here your sheet name
SaveSoftwareFile strPath, strCodeModuleName, strFileName
End Sub
Now, the created text file contains only the code itself, without the attributes saved by exporting the code...
Import part:
"Can't enter break mode at this time" does not mean that it is an error in the code. There are some operations (allowed only if a reference to Microsoft Visual Basic for Applications Extensibility ... exists) in code module manipulation, which cannot simple be run step by step. VBA needs to keep references to its VBComponents and it looks, it is not possible when changes in this area and in this way are made.
The import code is simple and it must run without problems. You must simple run the code and test its output...
I've written a program that runs and messages Skype with information when if finishes. I need to add a reference for Skype4COM.dll in order to send a message through Skype. We have a dozen or so computers on a network and a shared file server (among other things). All of the other computers need to be able to run this program. I was hoping to avoid setting up the reference by hand. I had planned on putting the reference in a shared location, and adding it programmatically when the program ran.
I can't seem to figure out how to add a reference programmatically to Excel 2007 using VBA. I know how to do it manually: Open VBE --> Tools --> References --> browse --_> File Location and Name. But that's not very useful for my purposes. I know there are ways to do it in Access Vb.net and code similar to this kept popping up, but I'm not sure I understand it, or if it's relevant:
ThisWorkbook.VBProject.References.AddFromGuid _
GUID:="{0002E157-0000-0000-C000-000000000046}", _
Major:=5, Minor:=3
So far, in the solutions presented, in order to add the reference programmatically I will need to add a reference by hand and change the Trust Center - which is more than just adding the reference. Though I guess if I follow through with the solutions proposed I will be able to add future references programmatically. Which probably makes it worth the effort.
Any further thoughts would be great.
Ommit
There are two ways to add references via VBA to your projects
1) Using GUID
2) Directly referencing the dll.
Let me cover both.
But first these are 3 things you need to take care of
a) Macros should be enabled
b) In Security settings, ensure that "Trust Access To Visual Basic Project" is checked
c) You have manually set a reference to `Microsoft Visual Basic for Applications Extensibility" object
Way 1 (Using GUID)
I usually avoid this way as I have to search for the GUID in the registry... which I hate LOL. More on GUID here.
Topic: Add a VBA Reference Library via code
Link: http://www.vbaexpress.com/kb/getarticle.php?kb_id=267
'Credits: Ken Puls
Sub AddReference()
'Macro purpose: To add a reference to the project using the GUID for the
'reference library
Dim strGUID As String, theRef As Variant, i As Long
'Update the GUID you need below.
strGUID = "{00020905-0000-0000-C000-000000000046}"
'Set to continue in case of error
On Error Resume Next
'Remove any missing references
For i = ThisWorkbook.VBProject.References.Count To 1 Step -1
Set theRef = ThisWorkbook.VBProject.References.Item(i)
If theRef.isbroken = True Then
ThisWorkbook.VBProject.References.Remove theRef
End If
Next i
'Clear any errors so that error trapping for GUID additions can be evaluated
Err.Clear
'Add the reference
ThisWorkbook.VBProject.References.AddFromGuid _
GUID:=strGUID, Major:=1, Minor:=0
'If an error was encountered, inform the user
Select Case Err.Number
Case Is = 32813
'Reference already in use. No action necessary
Case Is = vbNullString
'Reference added without issue
Case Else
'An unknown error was encountered, so alert the user
MsgBox "A problem was encountered trying to" & vbNewLine _
& "add or remove a reference in this file" & vbNewLine & "Please check the " _
& "references in your VBA project!", vbCritical + vbOKOnly, "Error!"
End Select
On Error GoTo 0
End Sub
Way 2 (Directly referencing the dll)
This code adds a reference to Microsoft VBScript Regular Expressions 5.5
Option Explicit
Sub AddReference()
Dim VBAEditor As VBIDE.VBE
Dim vbProj As VBIDE.VBProject
Dim chkRef As VBIDE.Reference
Dim BoolExists As Boolean
Set VBAEditor = Application.VBE
Set vbProj = ActiveWorkbook.VBProject
'~~> Check if "Microsoft VBScript Regular Expressions 5.5" is already added
For Each chkRef In vbProj.References
If chkRef.Name = "VBScript_RegExp_55" Then
BoolExists = True
GoTo CleanUp
End If
Next
vbProj.References.AddFromFile "C:\WINDOWS\system32\vbscript.dll\3"
CleanUp:
If BoolExists = True Then
MsgBox "Reference already exists"
Else
MsgBox "Reference Added Successfully"
End If
Set vbProj = Nothing
Set VBAEditor = Nothing
End Sub
Note: I have not added Error Handling. It is recommended that in your actual code, do use it :)
EDIT Beaten by mischab1 :)
There are two ways to add references using VBA. .AddFromGuid(Guid, Major, Minor) and .AddFromFile(Filename). Which one is best depends on what you are trying to add a reference to. I almost always use .AddFromFile because the things I am referencing are other Excel VBA Projects and they aren't in the Windows Registry.
The example code you are showing will add a reference to the workbook the code is in. I generally don't see any point in doing that because 90% of the time, before you can add the reference, the code has already failed to compile because the reference is missing. (And if it didn't fail-to-compile, you are probably using late binding and you don't need to add a reference.)
If you are having problems getting the code to run, there are two possible issues.
In order to easily use the VBE's object model, you need to add a reference to Microsoft Visual Basic for Application Extensibility. (VBIDE)
In order to run Excel VBA code that changes anything in a VBProject, you need to Trust access to the VBA Project Object Model. (In Excel 2010, it is located in the Trust Center - Macro Settings.)
Aside from that, if you can be a little more clear on what your question is or what you are trying to do that isn't working, I could give a more specific answer.
Browsing the registry for guids or using paths, which method is best. If browsing the registry is no longer necessary, won't it be the better way to use guids?
Office is not always installed in the same directory. The installation path can be manually altered. Also the version number is a part of the path.
I could have never predicted that Microsoft would ever add '(x86)' to 'Program Files' before the introduction of 64 bits processors.
If possible I would try to avoid using a path.
The code below is derived from Siddharth Rout's answer, with an additional function to list all the references that are used in the active workbook.
What if I open my workbook in a later version of Excel? Will the workbook still work without adapting the VBA code?
I have already checked that the guids for office 2003 and 2010 are identical. Let's hope that Microsoft doesn't change guids in future versions.
The arguments 0,0 (from .AddFromGuid) should use the latest version of a reference (which I have not been able to test).
What are your thoughts? Of course we cannot predict the future but what can we do to make our code version proof?
Sub AddReferences(wbk As Workbook)
' Run DebugPrintExistingRefs in the immediate pane, to show guids of existing references
AddRef wbk, "{00025E01-0000-0000-C000-000000000046}", "DAO"
AddRef wbk, "{00020905-0000-0000-C000-000000000046}", "Word"
AddRef wbk, "{91493440-5A91-11CF-8700-00AA0060263B}", "PowerPoint"
End Sub
Sub AddRef(wbk As Workbook, sGuid As String, sRefName As String)
Dim i As Integer
On Error GoTo EH
With wbk.VBProject.References
For i = 1 To .Count
If .Item(i).Name = sRefName Then
Exit For
End If
Next i
If i > .Count Then
.AddFromGuid sGuid, 0, 0 ' 0,0 should pick the latest version installed on the computer
End If
End With
EX: Exit Sub
EH: MsgBox "Error in 'AddRef'" & vbCrLf & vbCrLf & err.Description
Resume EX
Resume ' debug code
End Sub
Public Sub DebugPrintExistingRefs()
Dim i As Integer
With Application.ThisWorkbook.VBProject.References
For i = 1 To .Count
Debug.Print " AddRef wbk, """ & .Item(i).GUID & """, """ & .Item(i).Name & """"
Next i
End With
End Sub
The code above does not need the reference to the "Microsoft Visual Basic for Applications Extensibility" object anymore.
Here is how to get the Guid's programmatically! You can then use these guids/filepaths with an above answer to add the reference!
Reference: http://www.vbaexpress.com/kb/getarticle.php?kb_id=278
Sub ListReferencePaths()
'Lists path and GUID (Globally Unique Identifier) for each referenced library.
'Select a reference in Tools > References, then run this code to get GUID etc.
Dim rw As Long, ref
With ThisWorkbook.Sheets(1)
.Cells.Clear
rw = 1
.Range("A" & rw & ":D" & rw) = Array("Reference","Version","GUID","Path")
For Each ref In ThisWorkbook.VBProject.References
rw = rw + 1
.Range("A" & rw & ":D" & rw) = Array(ref.Description, _
"v." & ref.Major & "." & ref.Minor, ref.GUID, ref.FullPath)
Next ref
.Range("A:D").Columns.AutoFit
End With
End Sub
Here is the same code but printing to the terminal if you don't want to dedicate a worksheet to the output.
Sub ListReferencePaths()
'Macro purpose: To determine full path and Globally Unique Identifier (GUID)
'to each referenced library. Select the reference in the Tools\References
'window, then run this code to get the information on the reference's library
On Error Resume Next
Dim i As Long
Debug.Print "Reference name" & " | " & "Full path to reference" & " | " & "Reference GUID"
For i = 1 To ThisWorkbook.VBProject.References.Count
With ThisWorkbook.VBProject.References(i)
Debug.Print .Name & " | " & .FullPath & " | " & .GUID
End With
Next i
On Error GoTo 0
End Sub
I used to use something like this:
Dim vbaComponent As Variant
For Each vbaComponent In inputWorkbook.VBProject.VBComponents
vbaComponent.CodeModule.DeleteLines 1, vbaComponent.CodeModule.CountOfLines
vbaComponent.CodeModule.AddFromFile importComponentFileName
Next vbaComponent
This worked perfectly for some time but now it crashes when the Excel file gets saved. I guess the files got too big or something.
Is there better way to do this?
EDIT:
The problem seems to be frm and cls files. The replacement of bas files works perfectly.
EDIT2:
On some machines even bas files don't work.
EDIT3 (My current solution):
So my current solution was simply doing it by hand once and recording all mouse and keyboard input and then replaying this over and over again.
If there is no proper solution to this I plan on creating an AutoIt script for this.
you will have to export/import components, because not all lines are exposed to CodeModule, here is sample
Private Sub exportImportComponent(Project1 As VBIDE.VBProject, Project2 As VBIDE.VBProject)
Dim i As Long, sFileName As String
With Project1.VBComponents
For i = 1 To .Count
sFileName = "C:\Temp\" & .Item(i).Name
Select Case .Item(i).Type
Case vbext_ct_ClassModule
.Item(i).Export sFileName & ".cls"
Project2.VBComponents.Import sFileName & ".cls"
Case vbext_ct_StdModule
.Item(i).Export sFileName & ".bas"
Project2.VBComponents.Import sFileName & ".bas"
Case vbext_ct_MSForm
.Item(i).Export sFileName & ".frm"
Project2.VBComponents.Import sFileName & ".frm"
Case Else
Debug.Print "Different Type"
End Select
Next
End With
End Sub
I can assure everybody because I am working on this subject for years now (I gave up several times). When the code is programmatically modified either line-based or - what my preferred approach is 1. rename, 2. delete the renamed, 3. re-import from export file, Workbook Save will crash, will say Excel closes the Workbook. In fact my approach works most of the time but since it is unpredictable I learned to live with it. In most cases the code change has already successfully been done. So I just reopen the Workbook and continue.
The code I use. I just removed all the execution trace and execution log code lines but some lines may still look a bit cryptic:
With rn_wb.VBProject
'~~ Find a free/unused temporary name and re-name the outdated component
If mComp.Exists(wb:=rn_wb, comp_name:=rn_comp_name) Then
sTempName = mComp.TempName(tn_wb:=rn_wb, tn_comp_name:=rn_comp_name)
'~~ Rename the component when it already exists
.VBComponents(rn_comp_name).Name = sTempName
.VBComponents.Remove .VBComponents(sTempName) ' will not take place until process has ended!
End If
'~~ (Re-)import the component
.VBComponents.Import rn_raw_exp_file_full_name
'~~ Export the re-newed Used Common Component
Set Comp = New clsComp ' class module provides the export files full name
With Comp
Set Comp.Wrkbk = rn_wb
.CompName = rn_comp_name
End With
.VBComponents(rn_comp_name).Export Comp.ExpFileFullName
'~~ When Excel closes the Workbook with the subsequent Workbook save it may be re-opened
'~~ and the update process will continue with the next outdated Used Common Component.
'~~ The (irregular) Workbook close however may leave the renamed components un-removed.
'~~ When the Workbook is opened again these renamed component may cause duplicate declarations.
'~~ To prevent this the code in the renamed component is dleted.
' EliminateCodeInRenamedComponent sTempName ' this had made it much less "reliablele" so I uncommented it
SaveWbk rn_wb ' This "crahes" every now an then though I've tried a lot
End With
Private Sub SaveWbk(ByRef rs_wb As Workbook)
Application.EnableEvents = False
DoEvents ' no idea whether this helps. coded in desparation. at least it doesn't harm
rs_wb.Save
DoEvents ' same as above, not executed when Excel crashes
Application.EnableEvents = True
End Sub
I created a command button that auto saves the workbook into one of two file paths depending on info in the spreadsheet. This works fine for me but my colleague is gettting the follwing error everytime.
The method is extending the file path and adding \0BA1700 in this instance (this changes every time). This doesn't happen on my computer and the code works as it should. Here is the sub:
Private Sub CommandButton21_Click()
Dim pathUnder As String
Dim pathOver As String
Dim file As String
file = Range("D2").Value
pathUnder = "G:\Technical Services\LARGE CORPORATE UW\Full Insurance\FI Quote Spreadsheets (below 500)\"
pathOver = "G:\Technical Services\LARGE CORPORATE UW\Full Insurance\FI Quote Spreadsheets (above 500)\"
If Range("D4").Value = "" Or Range("D2").Value = "" Then
MsgBox ("Save Failed. Please ensure there are values in both cells D2 and D4")
Else
If Range("D4").Value >= 500 Then
ActiveWorkbook.SaveAs fileName:=pathOver & file & ".xlsm", FileFormat:=52
Else
ActiveWorkbook.SaveAs fileName:=pathUnder & file & ".xlsm", FileFormat:=52
End If
End If
End Sub
Help much appreciated.
Maybe some kind of exception? Are you sure nothing is currently using this file? Maybe you own program does?
Solved. My colleague's computer didn't have the G drive mapped to the correct server so remapped it and now working fine.
I've written a program that runs and messages Skype with information when if finishes. I need to add a reference for Skype4COM.dll in order to send a message through Skype. We have a dozen or so computers on a network and a shared file server (among other things). All of the other computers need to be able to run this program. I was hoping to avoid setting up the reference by hand. I had planned on putting the reference in a shared location, and adding it programmatically when the program ran.
I can't seem to figure out how to add a reference programmatically to Excel 2007 using VBA. I know how to do it manually: Open VBE --> Tools --> References --> browse --_> File Location and Name. But that's not very useful for my purposes. I know there are ways to do it in Access Vb.net and code similar to this kept popping up, but I'm not sure I understand it, or if it's relevant:
ThisWorkbook.VBProject.References.AddFromGuid _
GUID:="{0002E157-0000-0000-C000-000000000046}", _
Major:=5, Minor:=3
So far, in the solutions presented, in order to add the reference programmatically I will need to add a reference by hand and change the Trust Center - which is more than just adding the reference. Though I guess if I follow through with the solutions proposed I will be able to add future references programmatically. Which probably makes it worth the effort.
Any further thoughts would be great.
Ommit
There are two ways to add references via VBA to your projects
1) Using GUID
2) Directly referencing the dll.
Let me cover both.
But first these are 3 things you need to take care of
a) Macros should be enabled
b) In Security settings, ensure that "Trust Access To Visual Basic Project" is checked
c) You have manually set a reference to `Microsoft Visual Basic for Applications Extensibility" object
Way 1 (Using GUID)
I usually avoid this way as I have to search for the GUID in the registry... which I hate LOL. More on GUID here.
Topic: Add a VBA Reference Library via code
Link: http://www.vbaexpress.com/kb/getarticle.php?kb_id=267
'Credits: Ken Puls
Sub AddReference()
'Macro purpose: To add a reference to the project using the GUID for the
'reference library
Dim strGUID As String, theRef As Variant, i As Long
'Update the GUID you need below.
strGUID = "{00020905-0000-0000-C000-000000000046}"
'Set to continue in case of error
On Error Resume Next
'Remove any missing references
For i = ThisWorkbook.VBProject.References.Count To 1 Step -1
Set theRef = ThisWorkbook.VBProject.References.Item(i)
If theRef.isbroken = True Then
ThisWorkbook.VBProject.References.Remove theRef
End If
Next i
'Clear any errors so that error trapping for GUID additions can be evaluated
Err.Clear
'Add the reference
ThisWorkbook.VBProject.References.AddFromGuid _
GUID:=strGUID, Major:=1, Minor:=0
'If an error was encountered, inform the user
Select Case Err.Number
Case Is = 32813
'Reference already in use. No action necessary
Case Is = vbNullString
'Reference added without issue
Case Else
'An unknown error was encountered, so alert the user
MsgBox "A problem was encountered trying to" & vbNewLine _
& "add or remove a reference in this file" & vbNewLine & "Please check the " _
& "references in your VBA project!", vbCritical + vbOKOnly, "Error!"
End Select
On Error GoTo 0
End Sub
Way 2 (Directly referencing the dll)
This code adds a reference to Microsoft VBScript Regular Expressions 5.5
Option Explicit
Sub AddReference()
Dim VBAEditor As VBIDE.VBE
Dim vbProj As VBIDE.VBProject
Dim chkRef As VBIDE.Reference
Dim BoolExists As Boolean
Set VBAEditor = Application.VBE
Set vbProj = ActiveWorkbook.VBProject
'~~> Check if "Microsoft VBScript Regular Expressions 5.5" is already added
For Each chkRef In vbProj.References
If chkRef.Name = "VBScript_RegExp_55" Then
BoolExists = True
GoTo CleanUp
End If
Next
vbProj.References.AddFromFile "C:\WINDOWS\system32\vbscript.dll\3"
CleanUp:
If BoolExists = True Then
MsgBox "Reference already exists"
Else
MsgBox "Reference Added Successfully"
End If
Set vbProj = Nothing
Set VBAEditor = Nothing
End Sub
Note: I have not added Error Handling. It is recommended that in your actual code, do use it :)
EDIT Beaten by mischab1 :)
There are two ways to add references using VBA. .AddFromGuid(Guid, Major, Minor) and .AddFromFile(Filename). Which one is best depends on what you are trying to add a reference to. I almost always use .AddFromFile because the things I am referencing are other Excel VBA Projects and they aren't in the Windows Registry.
The example code you are showing will add a reference to the workbook the code is in. I generally don't see any point in doing that because 90% of the time, before you can add the reference, the code has already failed to compile because the reference is missing. (And if it didn't fail-to-compile, you are probably using late binding and you don't need to add a reference.)
If you are having problems getting the code to run, there are two possible issues.
In order to easily use the VBE's object model, you need to add a reference to Microsoft Visual Basic for Application Extensibility. (VBIDE)
In order to run Excel VBA code that changes anything in a VBProject, you need to Trust access to the VBA Project Object Model. (In Excel 2010, it is located in the Trust Center - Macro Settings.)
Aside from that, if you can be a little more clear on what your question is or what you are trying to do that isn't working, I could give a more specific answer.
Browsing the registry for guids or using paths, which method is best. If browsing the registry is no longer necessary, won't it be the better way to use guids?
Office is not always installed in the same directory. The installation path can be manually altered. Also the version number is a part of the path.
I could have never predicted that Microsoft would ever add '(x86)' to 'Program Files' before the introduction of 64 bits processors.
If possible I would try to avoid using a path.
The code below is derived from Siddharth Rout's answer, with an additional function to list all the references that are used in the active workbook.
What if I open my workbook in a later version of Excel? Will the workbook still work without adapting the VBA code?
I have already checked that the guids for office 2003 and 2010 are identical. Let's hope that Microsoft doesn't change guids in future versions.
The arguments 0,0 (from .AddFromGuid) should use the latest version of a reference (which I have not been able to test).
What are your thoughts? Of course we cannot predict the future but what can we do to make our code version proof?
Sub AddReferences(wbk As Workbook)
' Run DebugPrintExistingRefs in the immediate pane, to show guids of existing references
AddRef wbk, "{00025E01-0000-0000-C000-000000000046}", "DAO"
AddRef wbk, "{00020905-0000-0000-C000-000000000046}", "Word"
AddRef wbk, "{91493440-5A91-11CF-8700-00AA0060263B}", "PowerPoint"
End Sub
Sub AddRef(wbk As Workbook, sGuid As String, sRefName As String)
Dim i As Integer
On Error GoTo EH
With wbk.VBProject.References
For i = 1 To .Count
If .Item(i).Name = sRefName Then
Exit For
End If
Next i
If i > .Count Then
.AddFromGuid sGuid, 0, 0 ' 0,0 should pick the latest version installed on the computer
End If
End With
EX: Exit Sub
EH: MsgBox "Error in 'AddRef'" & vbCrLf & vbCrLf & err.Description
Resume EX
Resume ' debug code
End Sub
Public Sub DebugPrintExistingRefs()
Dim i As Integer
With Application.ThisWorkbook.VBProject.References
For i = 1 To .Count
Debug.Print " AddRef wbk, """ & .Item(i).GUID & """, """ & .Item(i).Name & """"
Next i
End With
End Sub
The code above does not need the reference to the "Microsoft Visual Basic for Applications Extensibility" object anymore.
Here is how to get the Guid's programmatically! You can then use these guids/filepaths with an above answer to add the reference!
Reference: http://www.vbaexpress.com/kb/getarticle.php?kb_id=278
Sub ListReferencePaths()
'Lists path and GUID (Globally Unique Identifier) for each referenced library.
'Select a reference in Tools > References, then run this code to get GUID etc.
Dim rw As Long, ref
With ThisWorkbook.Sheets(1)
.Cells.Clear
rw = 1
.Range("A" & rw & ":D" & rw) = Array("Reference","Version","GUID","Path")
For Each ref In ThisWorkbook.VBProject.References
rw = rw + 1
.Range("A" & rw & ":D" & rw) = Array(ref.Description, _
"v." & ref.Major & "." & ref.Minor, ref.GUID, ref.FullPath)
Next ref
.Range("A:D").Columns.AutoFit
End With
End Sub
Here is the same code but printing to the terminal if you don't want to dedicate a worksheet to the output.
Sub ListReferencePaths()
'Macro purpose: To determine full path and Globally Unique Identifier (GUID)
'to each referenced library. Select the reference in the Tools\References
'window, then run this code to get the information on the reference's library
On Error Resume Next
Dim i As Long
Debug.Print "Reference name" & " | " & "Full path to reference" & " | " & "Reference GUID"
For i = 1 To ThisWorkbook.VBProject.References.Count
With ThisWorkbook.VBProject.References(i)
Debug.Print .Name & " | " & .FullPath & " | " & .GUID
End With
Next i
On Error GoTo 0
End Sub