0

I'm writing a procedure to update the components in one macro-enabled Excel workbook (Destination) from another macro-enabled Excel workbook (Source). The end result is to make the Destination components (worksheets, user forms, modules, etc.) match the Source components.

So far I have successfully (1) Added components from source that are not found in destination, (2) Replaced worksheet(s) with newer versions, (3) Globally updated the code in all modules, class modules and user forms, and (4) Updated miscellaneous cell formulas and values in various worksheets.

Where I have been struggling is deleting components in destination that are not found in source. I've been trying all kinds of approaches and believe I'm close but can't get past various errors in the actual VBComponents.Remove line. Here's my code:

    Sub UpdateDest()
    'Purpose:
    'Sources: (1) https://www.excel-easy.com/vba/examples/import-sheets.html
    '         (2) https://stackoverflow.com/questions/16174469/unprotect-vbDestProject-from-vb-code
    '         (3) https://stackoverflow.com/questions/18497527/copy-vba-code-from-a-sheet-in-one-workbook-to-another

    '=== Declare Variables
        Dim booCompFound As Boolean
        Dim cmSrc As CodeModule, cmDest As CodeModule
        Dim xlWBDest As Excel.Workbook, xlWSDest As Excel.Worksheet
        Dim xlWBSrc As Excel.Workbook, xlWSSrc As Excel.Worksheet
        Dim i As Integer, j As Integer
        Dim lngVBUnlocked As Long
        Dim vbDestComp As Object, vbDestComps As Object, vbDestProj As Object, vbDestMod As Object
        Dim vbSrcComp As Object, vbSrcComps As Object, vbSrcProj As Object, vbSrcMod As Object
        Dim modModule As Object
        Dim strDestName As String, strDestPath As String, strSrcName As String, strSrcPath As String
        Dim strUpdName As String, strUpdPath As String

        'On Error GoTo ErrorHandler
    '=== Initialize Variables and Prepare for Execution
        Application.ScreenUpdating = True
        Application.DisplayAlerts = True
        strUpdPath = ThisWorkbook.Path & "\"

    '=== (Code execution)
        '--- Select Dest and source workbooks for the update, and remove workbook, worksheet and VBA Project protection from both
        strSrcPath = Application.GetOpenFilename(Title:="Select SOURCE workbook for the update", FileFilter:="Excel Files *.xls* (*.xls*),")
        If strSrcPath = "" Then
            MsgBox "No source workbook was selected.", vbExclamation, "Sorry!"
            Exit Sub
        Else
            Set xlWBSrc = Workbooks.Open(strSrcPath)
            UnprotectAll xlWBSrc
            'For Each xlWSSrc In xlWBSrc.Worksheets
            '    xlWSSrc.Visible = xlSheetVisible
            'Next xlWSSrc
            Set vbSrcProj = xlWBSrc.VBProject
            lngVBUnlocked = UnlockProject(vbSrcProj, "FMD090")
            Debug.Print lngVBUnlocked
            If lngVBUnlocked <> 0 And lngVBUnlocked <> 2 Then
                MsgBox "The source VB Project could not be unlocked.", vbExclamation, "Error!"
                Exit Sub
            Else
                Set vbSrcComps = vbSrcProj.VBComponents
            End If
        End If
        strDestPath = Application.GetOpenFilename(Title:="Select DESTINATION workbook to update", FileFilter:="Excel Files *.xls* (*.xls*),")
        If strDestPath = "" Then
            MsgBox "No destination workbook was selected.", vbExclamation, "Sorry!"
            Exit Sub
        Else
            Set xlWBDest = Workbooks.Open(strDestPath)
            UnprotectAll xlWBDest
            'For Each xlWSDest In xlWBDest.Worksheets
            '    xlWSDest.Visible = xlSheetVisible
            'Next xlWSDest
            Set vbDestProj = xlWBDest.VBProject
            lngVBUnlocked = UnlockProject(vbDestProj, "FMD090")
            Debug.Print lngVBUnlocked
            If lngVBUnlocked <> 0 And lngVBUnlocked <> 2 Then
                MsgBox "The destination VB Project could not be unlocked.", vbExclamation, "Error!"
                Exit Sub
            Else
                Set vbDestComps = vbDestProj.VBComponents
            End If
        End If

        '--- Add components from source that are not found in destination
        For Each vbSrcComp In vbSrcComps

            Debug.Print vbSrcComp.Name
            booCompFound = False
            For Each vbDestComp In vbDestComps

                If vbSrcComp.Name = vbDestComp.Name Then
                    booCompFound = True
                    Exit For
                End If

            Next vbDestComp
            If booCompFound = False Then
                Application.EnableEvents = False
                vbSrcComp.Export strSrcPath & vbSrcComp.Name
                vbDestComps.Import strSrcPath & vbSrcComp.Name
                Kill strSrcPath & vbSrcComp.Name
                Application.EnableEvents = True
            End If

        Next vbSrcComp

        '--- Delete components in destination that are not found in source
        Set vbDestComps = vbDestProj.VBComponents
        For i = vbDestComps.Count To 1 Step -1
        'For Each vbDestComp In vbDestComps

            booCompFound = False
            For Each vbSrcComp In vbSrcComps

                Debug.Print "Src: "; vbSrcComp.Name; " Dest: "; vbDestComps(i).Name
                If vbDestComps(i).Name = vbSrcComp.Name Then
                    booCompFound = True
                    Exit For
                End If

            Next vbSrcComp
            If booCompFound = False Then
                Application.EnableEvents = False

    '>>> PROBLEM LINE
                vbDestProj.VBComponents.Remove vbDestComps(i)
    '<<<

                Application.EnableEvents = True
            End If

        'Next vbDestComp
        Next i

        '--- Replace worksheet(s) with newer versions
        strUpdName = "Lists_WS_3_1.xlsx"
        If Dir(strUpdPath & strUpdName) <> "" Then
            Application.EnableEvents = False
            Set xlWBSrc = Workbooks.Open(strUpdPath & strUpdName)
            xlWBDest.Worksheets("Lists").Visible = xlSheetVisible
            Application.DisplayAlerts = False
            xlWBDest.Worksheets("Lists").Name = "Lists_Old"
            xlWBSrc.Worksheets("Lists").Copy After:=xlWBDest.Worksheets("FYMILES")
            xlWBDest.Worksheets("Lists_Old").Delete
            xlWBSrc.Close
            Application.EnableEvents = True
        Else
            MsgBox "The file " & strUpdName & " is missing.", vbExclamation, "File Missing!"
            Exit Sub
        End If

        '--- Globally update code in modules, class modules and user forms
        For Each vbSrcComp In vbSrcComps

            Set cmSrc = vbSrcComp.CodeModule
            Debug.Print vbSrcComp.Name
            Set cmDest = vbDestComps(vbSrcComp.Name).CodeModule
            If cmSrc.CountOfLines > 0 Then
                Application.EnableEvents = False
                cmDest.DeleteLines 1, cmDest.CountOfLines  'Delete all lines in Dest component
                cmDest.AddFromString cmSrc.Lines(1, cmSrc.CountOfLines)  'Copy all lines from source component to Dest component
                Application.EnableEvents = True
            End If

        Next vbSrcComp

        '--- Update miscellaneous cell formulas and values
        Application.EnableEvents = False
        xlWBDest.Sheets("Inventory Data and July").Range("E2").Formula = "=TEXT(Lists!$O$5, " & Chr(34) & "000" & Chr(34) & ")"
        Application.EnableEvents = True

    '=== Error Handling
    ErrorHandler:
        Application.EnableEvents = True

    '=== Release Variables and Cleanup
        Application.ScreenUpdating = True
        Application.DisplayAlerts = True

    End Sub

The problem code is about 2/3rds of the way down following '>>> PROBLEM LINE. This line of code produces a run-time error 5, invalid procedure call or argument when attempting to delete the Sheet18 code module.

During the run the original Sheet16 (Lists) is removed and replaced with a new Lists worksheet that Excel numbers Sheet18. After a weekend of contemplation I believe the issue stems from component naming. Attempting to address this, the code references the component names, but VB Properties for the new sheet are (Name) = (Sheet18) and Name = Lists (note the parentheses).

I've now tried saving the workbook following each operation without any change in the error or on what part of the structure the error occurs.

As it is currently written I'm looping backward through the destination components collection and attempting to delete when a component in Destination isn't found in Source. Commented out are the remnants of the original forward loop that also didn't work. I've tried many variations and either get an invalid procedure call or that the property or method isn't found in the object.

I've spend a day playing with this. Please take a look and help me see the light!

I'm running Excel 2016

spacetanker
  • 119
  • 2
  • 15
  • 2
    Would it not be simpler to delete all the components in the Destination and then copy all of them over from the Source? (instead of copying from the Source to the Destination and trying to sort things out) – cybernetic.nomad Aug 17 '18 at 21:29
  • 1
    Why do you need to run a numbered loop backwards, and reference the component by number? Can't you just do a For Each, and then check each vbDestComp.Name? It looks like you started to do this in your commented lines. What went wrong? – Chris Melville Aug 17 '18 at 21:41
  • 1
    You need to test the `VBComponent.Type` - it includes the workbook and worksheet components. I'm not entirely sure what gets thrown when you try to remove the component for a `vbext_ct_Document`, but I'm guessing it can't be good... – Comintern Aug 17 '18 at 23:04
  • See the answer in this post for deleting VBA components: https://stackoverflow.com/questions/50984324/delete-a-module-in-outlook-project/50984692#50984692 – Kostas K. Aug 18 '18 at 07:31
  • I can't see reference to any of the attempts error messages? What did "doesn't work" mean in those cases? – QHarr Aug 18 '18 at 07:34
  • A word of advice: Read about the Single Responsibility Principle (applies to methods as well). Right now you have a giant method that does everything. This is very hard to debug and maintain. Try to break it down to individual pieces of functionality, for example find, compare, delete. – Kostas K. Aug 18 '18 at 07:39
  • @ cybernetic.nomad: Some of the components are sheet modules. The workbooks will be submitted periodically with data in them, so I can't simply delete those components because I would lose the data. Thank you for the suggestion! – spacetanker Aug 20 '18 at 13:40
  • @ Chris Melville: I get the same error whether I run the loop forward by the components collection (the commented lines) or in reverse by the index (as shown). I've updated the original question to include the error and more detail about where the issue occurs. I believe the Lists worksheet added during the operation is involved in the issue. Thank you. – spacetanker Aug 20 '18 at 14:12
  • @ Comintern: Are you saying that the worksheet code module can't be replaced without touching the worksheet? More, that I should evaluate the type and only delete the non-sheet components? Interesting. – spacetanker Aug 20 '18 at 14:16
  • @ Kostas K.: The answer you referenced looks just like my code (with different variable names). That doesn't really touch the issue. – spacetanker Aug 20 '18 at 14:18
  • @ QHarr: Excellent point! I have updated the original question to include error details and - if I am able - I will attach sample files to experiment with. Thank you. – spacetanker Aug 20 '18 at 14:19
  • @ Kostas K.: I appreciate the advice. To a more experienced eye my approach may seem clunky. I'm entirely self-taught, and while the concepts of Single Responsibility, SOLID, etc. aren't unknown to me I admit this seemed straight-forward enough to approach in a more linear fashion. – spacetanker Aug 20 '18 at 14:32

1 Answers1

1

Following on @ Comintern's comment, I added tests to ensure the Remove method was only applied to non-document modules. This is the rewritten code block for removing the modules:

    '--- Delete non-document components in destination that are not found in source
    Set vbDestComps = vbDestProj.VBComponents
    For Each vbDestComp In vbDestComps

        If vbDestComp.Type >= 1 And vbDestComp.Type <= 3 Then
            booCompFound = False
            For Each vbSrcComp In vbSrcComps

                Debug.Print "Src: "; vbSrcComp.Name; " Dest: "; vbDestComp.Name; " Type: "; vbDestComp.Type
                If vbDestComp.Name = vbSrcComp.Name Then
                    booCompFound = True
                    Exit For
                End If

            Next vbSrcComp
            If booCompFound = False Then
                Application.EnableEvents = False
                vbDestProj.VBComponents.Remove vbDestComp
                Application.EnableEvents = True
            End If
        End If

    Next vbDestComp
spacetanker
  • 119
  • 2
  • 15