1

I know that this has been hashed over many times but none of the solutions work for me

This runs from MS Access

Set ExcelApp = CreateObject("Excel.Application")
ExcelApp.Workbooks.Open CurPath & MainProjectName & ".xlsm", True
ExcelApp.Visible = False
ExcelApp.Quit
Set ExcelApp = Nothing

Also, the .xlsm file does the following at the end of the procedure

    ActiveWorkbook.Save
    ActiveWorkbook.Close

End Sub

but the .xlsm file remains open hidden somewhere. i see it as an instance, not as an application and the reason i know that the .xlsm file stays open because sometimes the excel VBA window stays open (just the VBA window, not the Excel window) and in there i can see which file's modules are there.

posting all my code

this is the piece that runs from MS Access and opens the xlsm file

Public Function RunLoadFilesTest()

    ODBCConnString
    RunVariables

    Dim Rs2   As DAO.Recordset
    Dim TABLENAME As String

    Set Rs2 = CurrentDb.OpenRecordset("SELECT * FROM QFilesToExportEMail")

    Do Until Rs2.EOF
        TABLENAME = Rs2("TableName")
        DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel12, TABLENAME, CurPath & MainProjectName & ".xlsm", True
        Rs2.MoveNext
    Loop

    Rs2.Close
    Set Rs2 = Nothing

Set ExcelApp = CreateObject("Excel.Application")
Set ExcelWbk = ExcelApp.Workbooks.Open(CurPath & MainProjectName & ".xlsm", True)
ExcelApp.Visible = False     ' APP RUNS IN BACKGROUND
'ExcelWbk.Close      ' POSSIBLY SKIP IF WORKBOOK IS CLOSED
ExcelApp.Quit

' RELEASE RESOURCES
Set ExcelWbk = Nothing
Set ExcelApp = Nothing
    
End Function

this is the code of the xlsm file. it opens automatically from the ThisWorkbook module. i removed a lot of the code not to clutter the thread but left every piece that opens a workbook, activates a workbook, closes, etc.

Public Sub MainProcedure()

    Application.EnableCancelKey = xlDisabled
    Application.DisplayAlerts = False
    Application.EnableEvents = False

    CurPath = ActiveWorkbook.Path & "\"

    'this is to deselect sheets
    Sheets("QFilesToExportEMail").Select

    Sheets("QReportDates").Activate

    FormattedDate = Range("A2").Value
    RunDate = Range("B2").Value
    ReportPath = Range("C2").Value
    MonthlyPath = Range("D2").Value
    ProjectName = Range("E2").Value
         
    Windows(ProjectName & ".xlsm").Activate
    Sheets("QFilesToExportEMail").Select
    LastRow = Cells(Rows.Count, "A").End(xlUp).Row
    

    Dim i     As Integer

    CurRowNum = 2

    Set CurRange = Sheets("QFilesToExportEMail").Range("B" & CurRowNum & ":B" & LastRow)

    For Each CurCell In CurRange
                     
        If CurCell <> "" Then
                                   
            Windows(ProjectName & ".xlsm").Activate
            Sheets("QFilesToExportEMail").Select
            FirstRowOfSection = ActiveWorkbook.Worksheets("QFilesToExportEMail").Columns(2).Find(ExcelFileName).Row
                                                        
            If ExcelSheetName = "" Then
                ExcelSheetName = TableName
            End If
                                                        
            If CurRowNum = FirstRowOfSection Then
                SheetToSelect = ExcelSheetName
            End If
                                   
            If IsNull(TemplateFileName) Or TemplateFileName = "" Then
                Workbooks.Add
            Else
                Workbooks.Open CurPath & TemplateFileName
            End If
                                   
            ActiveWorkbook.SaveAs MonthlyPath & FinalExcelFileName
                                   
            For i = CurRowNum To LastRowOfSection
                Windows(ProjectName & ".xlsm").Activate
                Sheets("QFilesToExportEMail").Select
            Next i
        End If
                     
        Windows(FinalExcelFileName).Activate
        Sheets(SheetToSelect).Select
                                   
        ActiveWorkbook.Save
        ActiveWorkbook.Close
                     
        If LastRowOfSection >= LastRow Then
            Exit For
        End If
                     
    Next

    Set CurRange = Sheets("QFilesToExportEMail").Range("A2:A" & LastRow)
    For Each CurCell In CurRange
        If CurCell <> "" Then

            CurSheetName = CurCell

            If CheckSheet(CurSheetName) Then
                Sheets(CurSheetName).Delete
            End If

        End If
    Next
   
    Sheets("QFilesToExportEMail").Delete
    Sheets("QReportDates").Delete
                                             
    ActiveWorkbook.Save
    ActiveWorkbook.Close

End Sub
lalachka
  • 403
  • 5
  • 16
  • 36

1 Answers1

1

The underlying process remains since the workbook object was not fully released like you did with the app object. However, this requires you to assign the workbook object in order to release later.

Dim ExcelApp As object, ExcelWbk as Object

Set ExcelApp = CreateObject("Excel.Application")
Set ExcelWbk = ExcelApp.Workbooks.Open(CurPath & MainProjectName & ".xlsm", True)
ExcelApp.Visible = False     ' APP RUNS IN BACKGROUND


'... DO STUFF

' CLOSE OBJECTS
ExcelWbk.Close
ExcelApp.Quit

' RELEASE RESOURCES
Set ExcelWbk = Nothing
Set ExcelApp = Nothing

This is true for any COM-connected language like VBA, including:

As shown, even open source can connect to Excel externally like VBA and should always release initialized objects in their corresponding semantics.


Consider refactoring of Excel VBA code to for best practices:

  • Explicitly declare variables and types;
  • Integrate proper error handling (that without can leave resources running);
  • Use With...End With blocks and avoid Activate, Select, ActiveWorkbook, and ActiveSheet (that can cause runtime errors);
  • Declare and use Cell, Range, or Workbook objects and at end uninitialize all Set objects;
  • Use ThisWorkbook. qualifier where needed (i.e., workbook where code resides).

NOTE: Below is untested. So carefully test, debug especially due to all the names being used.

Option Explicit       ' BEST PRACTICE TO INCLUDE AS TOP LINE AND 
                      ' AND ALWAYS Debug\Compile AFTER CODE CHANGES

Public Sub MainProcedure()
On Error GoTo ErrHandle
    ' EXPLICITLY DECLARE EVERY VARIABLE AND TYPE
    Dim FormattedDate As Date, RunDate As Date

    Dim ReportPath As String, MonthlyPath As String, CurPath As String
    Dim ProjectName As String, ExcelFileName As String, FinalExcelFileName As String
    Dim TableName As String, TemplateFileName As String
    Dim SheetToSelect As String, ExcelSheetName As String
    Dim CurSheetName As String
    
    Dim i As Integer, CurRowNum As Long, LastRow As Long
    Dim FirstRowOfSection As Long, LastRowOfSection As Long
    Dim CurCell As Variant, curRange As Range
    
    Dim wb As Workbook
        
    Application.EnableCancelKey = xlDisabled
    Application.DisplayAlerts = False
    Application.EnableEvents = False

    CurPath = ThisWorkbook.Path & "\"                     ' USE ThisWorkbook

    With ThisWorkbook.Worksheets("QReportDates")          ' USE WITH CONTEXT
        FormattedDate = .Range("A2").Value
        RunDate = .Range("B2").Value
        ReportPath = .Range("C2").Value
        MonthlyPath = .Range("D2").Value
        ProjectName = .Range("E2").Value
    End With
    
    CurRowNum = 2
    With ThisWorkbook.Worksheets("QFilesToExportEMail")   ' USE WITH CONTEXT
        LastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
    
        Set curRange = .Range("B" & CurRowNum & ":B" & LastRow)

        For Each CurCell In curRange
            If CurCell <> "" Then
                FirstRowOfSection = .Columns(2).Find(ExcelFileName).Row
                                                            
                If ExcelSheetName = "" Then
                    ExcelSheetName = TableName
                End If
                                                            
                If CurRowNum = FirstRowOfSection Then
                    SheetToSelect = ExcelSheetName
                End If
                                       
                ' USE WORKBOOK OBJECT
                If IsNull(TemplateFileName) Or TemplateFileName = "" Then
                    Set wb = Workbooks.Add
                Else
                    Set wb = Workbooks.Open(CurPath & TemplateFileName)
                End If
                                       
                wb.SaveAs MonthlyPath & FinalExcelFileName
            End If
                         
            ' USE WORKBOOK OBJECT
            wb.Worksheets(SheetToSelect).Select
            wb.Save
            wb.Close
            Set wb = Nothing                              ' RELEASE RESOURCE
            
            If LastRowOfSection >= LastRow Then
                Exit For
            End If
        Next CurCell

        Set curRange = .Range("A2:A" & LastRow)
        For Each CurCell In curRange
            If CurCell <> "" Then
                CurSheetName = CurCell
    
                If CheckSheet(CurSheetName) Then         ' ASSUMED A SEPARATE FUNCTION
                    ThisWorkbook.Worksheets(CurSheetName).Delete
                End If
    
            End If
        Next CurCell
    End With
    
    ' USE ThisWorkbook QUALIFIER
    ThisWorkbook.Worksheets("QFilesToExportEMail").Delete
    ThisWorkbook.Worksheets("QReportDates").Delete
    ThisWorkbook.Save
    ' ThisWorkbook.Close                                 ' AVOID CLOSING IN MACRO

ExitHandle:
    ' ALWAYS RELEASE RESOURCE (ERROR OR NOT)
    Set curCell = Nothing: Set curRange = Nothing: Set wb = Nothing
    Exit Sub
    
ErrHandle:
    MsgBox Err.Number & " - " & Err.Description, vbCritical
    Resume ExitHandle
End Sub
Parfait
  • 104,375
  • 17
  • 94
  • 125
  • Thank you very much. I will test shortly. But can you please explain why me saving and closing the workbook from excel doesn't do the trick? In the xlsm file, the last 2 lines are SAVE and CLOSE – lalachka Jan 05 '21 at 00:48
  • But you don't release/unintialize the workbook object to `Nothing`. This is different than justing closing the object. – Parfait Jan 05 '21 at 01:02
  • nope, no luck. i'm updating my post with the code i'm using and the code of the xlsm file. i can't understand what's happening – lalachka Jan 05 '21 at 05:15
  • 1
    Before anything read this post: [How to avoid using Select in Excel VBA](https://stackoverflow.com/q/10714251/1422451) (especially the briefer 2nd answer). You should qualify all `.Range`/`.Sheet` with explicit worksheet objects with `Dim` objects and avoid `ActiveWorkbook`/ `ActiveSheet`. Never use `Activate`. You appear to be running through many workbooks/worksheets. These unqualified references can cause many side effect issues. You should release resources in Excel as well. – Parfait Jan 05 '21 at 05:42
  • Really useful. I had no idea. Lol good thing I deleted a lot of my code, I might've given you a heart attack :) I'm def not the best coder out there. My talent is coming up with cool ideas for automation, but my implementation often sucks. It works but glitches here and there, etc. – lalachka Jan 05 '21 at 06:24
  • Understood. Yes, we are all beginners at some point! See extended answer for re-factored Excel code. Note: VBA must be carefully tested on your end. Many of the names are a bit confusing without context. So I omit/adjust from assumptions. Also, I would avoid closing workbook in Excel VBA (since macro did not open it). Have the caller (MS Access VBA, Python, or user) close it since they do open it. So I removed earlier *possibly skip* comment. – Parfait Jan 05 '21 at 17:31
  • Thank you!!! I do have variables, and option explicit but they're declared as public and I didn't include that part for some reason. I always declare everything. Making them public is a habit from ms access, since there i need them to be available between procedures and it's easier to have them all in one place (i have a separate module for variables) – lalachka Jan 05 '21 at 21:10
  • I'm about to test. I always debug, obsessively, id say:) and sorry that you had to type all my variables, I should've posted them. Is there any downside to having them as public? And just wanted to say a huge thank you. This template will be used for 50 or so automations and I'm very thankful that it will now be done correctly. Can I post the full version? Can you see what else looks wrong to you? – lalachka Jan 06 '21 at 08:35
  • I don't use `Public` often so cannot say for your context. But for readability and maintainability, I would declare variables within whatever `Sub` or `Function` they are used rather than having to Ctrl+F their declaration. Do note: Excel has named ranges accessible in VBA. And since we do not want to stray away from this specific question for future readers, consider asking about optimal VBA programming on [CodeReview SE](https://codereview.stackexchange.com/). – Parfait Jan 06 '21 at 17:23
  • hey, the instance isn't closing, i don't know what else to try. i used your exact code and it's still there – lalachka Jan 28 '21 at 01:21
  • This code uses and then releases Excel objects (cell range, workbook, etc.). It does not close the Excel application itself that calls this macro. – Parfait Jan 28 '21 at 13:43
  • Can you please look at the screenshot in this question? It will explain better than I can. https://stackoverflow.com/q/65929789/453116 it's not that excel is open, the .xlsm file isn't closed, for some reason. It's not in the taskbar but there sonewhere – lalachka Jan 28 '21 at 13:45