'improve speed of outlook VBA copying from Excel attachment

Over the past years we received thousands of emails with a specific type of attachment which we didn't use until now, but would now like to combine to one file. The outlook inbox has 38.000 unread emails by now. Each Email is sent from the same adress and contains two files, that are always named in a specific way "channelname-yyyy-mm-dd-tagesreport.csv" or "channelname-yyyy-mm-dd-tageskategorien.csv". I only need the "tageskategorien" file, the other one can be ignored. All of the excel files have the same structure - a header in line1 and data in line 2 seperated by ";" :

DATUM;PI;Visit;UC Tag;Usetime Tag;PI laufende Woche;Visit laufende Woche;PI laufender Monat;Visit laufender Monat

I already have a working code (see further down) but it is incredibly slow (9seconds per Email). It looks through the non-default Mailbox which receives all those emails, saves the attachment to a local folder, and copys the second line to another workbook based on some criteria. I know this is a lot to do for one macro, and there might not be a solution. Maybe VBA is simply not made for something like this (if this is the case please let me know).

As mentioned the code runs without errors, but even when I restrict the mail items to a time range of one month, it is very slow (9seconds per Email) and often gets stuck or behaves weirdly (doing some routines only part of the time,...).

I was wondering (as I only started using VBA recently) if there is any advice from the community on how to improve the speed of my code (or any other improvements to be done).

I will include the three parts of the code as a whole, as this question is about optimizing and I think it therefore doesn't make sense to only show a mini example.

The first part of the code accesses the emails one by one and calls two subroutines:

Option Explicit

Sub SearchEmails()

Dim oINS As NameSpace
Dim FolderInbox As MAPIFolder
Dim filtered_items As Items
Dim olMail As MailItem
Dim strFilter As String
Dim olRecip As Recipient

Set oINS = GetNamespace("MAPI")
Set FolderInbox = oINS.Folders("Onlinearchiv - [email protected]")
Set FolderInbox = FolderInbox.Folders("Posteingang")

strFilter = "[ReceivedTime]>'" & Format(Date - 10, "DDDDD HH:NN") & "'"

Set filtered_items = FolderInbox.Items.Restrict(strFilter)

If filtered_items.Count = 0 Then
    GoTo empty_objects
End If

For Each olMail In filtered_items
    Call SaveTagesreport.saveAttachtoDisk(olMail)
    Call mergeReport.Merge_oewaReport(olMail)
    
Next olMail


empty_objects:
    Set FolderInbox = Nothing
    Set oINS = Nothing

End Sub

The Call SaveTagesreport module simply saves one of the two attached files (depending on the Name) to a local folder. I was told that this step is needed as we can only copy a line if the file is saved somewhere. Originally I wanted to directly access it without saving it. This is how this part looks like:

Option Explicit
Function FileExists(FilePath As String) As Boolean
    Dim TestStr As String

    TestStr = ""
    On Error Resume Next
    TestStr = Dir(FilePath)
    On Error GoTo 0
    If TestStr = "" Then
        FileExists = False
    Else
        FileExists = True
    End If
End Function

Public Sub saveAttachtoDisk(itm As Outlook.MailItem)

Dim objAtt As Outlook.Attachment
Dim x As Long
Dim saveFolder As String
Dim Name As String


saveFolder = "Mypath/mylocalfolder"

For Each objAtt In itm.Attachments
x = InStr(1, "tageskategorien.csv", objAtt.DisplayName)
Name = objAtt.DisplayName
If InStr(1, objAtt.DisplayName, "tageskategorien.csv", 1) = 0 Then
    If Not FileExists(saveFolder & objAtt.DisplayName) Then
        objAtt.SaveAsFile saveFolder & objAtt.DisplayName
    End If
End If

Set objAtt = Nothing

Next

End Sub

The last part is about opening the .csv file, and writing the numbers into the AllData.xlsx file, if they are not already included. Then the .csv file is deleted again, because itonly contains this one line that I write to the AllData.xlsx file, so it is not needed afterwards.

Option Explicit

Sub Merge_oewaReport(itm As Outlook.MailItem)

'AllData.file Dims
Dim wb_path As String
Dim app_master As Excel.Application
Dim wb_master As Excel.Workbook
Dim ws_master As Excel.Worksheet

Dim ic_last As Integer
Dim ir_last As Integer
Dim ic_zeitr As Integer
Dim ic_date As Integer
Dim ic_ID As Integer

'EmailFile Dims
Dim objAtt As Outlook.Attachment
Dim FileName As String

Dim app_email As Excel.Application
Dim wb_email As Excel.Workbook
Dim ws_email As Excel.Worksheet

Dim ic_last2 As Integer
Dim ic_Date_e As Integer
Dim headerList() As String
Dim content() As String

'other dims
Dim Path As String
Dim datestr As Date
Dim datetemp As Date

Dim fID() As String
Dim fDay As String
Dim columnHeading As String
Dim i As Integer
Dim j As Integer
Dim Duplicate As Boolean


'Set up identifiers for AllData.file
Path = "mypath/mylocalfolder/"
wb_path = Path & "AllData.xlsx"
Set app_master = CreateObject("Excel.Application")
Set wb_master = app_master.Workbooks.Open(wb_path, ReadOnly:=False)
Set ws_master = wb_master.Sheets(1)

ic_last = ws_master.Cells.Find(What:="*", SearchDirection:=xlPrevious, SearchOrder:=xlByColumns).Column
ir_last = ws_master.Cells(ws_master.Rows.Count, 1).End(-4162).Row
ic_date = ws_master.Cells.Find(What:="DATUM", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
ic_ID = ws_master.Cells.Find(What:="ID", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
ic_zeitr = ws_master.Cells.Find(What:="Zeitraum", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column


For Each objAtt In itm.Attachments

    FileName = objAtt.DisplayName
    If InStr(1, FileName, "tageskategorien.csv", 1) = 0 Then
        Set app_email = CreateObject("Excel.Application")
        Set wb_email = app_email.Workbooks.Open(Path & FileName, True, True)
        Set ws_email = wb_email.Sheets(1)
        
        'find Date and Name in Emailfile
        fID = Split(FileName, " - ")
        headerList = Split(ws_email.Cells(1, 1), ";")
        content = Split(ws_email.Cells(2, 1), ";")
        For i = 0 To UBound(headerList)
            If headerList(i) = "DATUM" Then
                datestr = content(i)
                Exit For
            End If
        Next i
        
        'check ID of every line that matches the date, to find if new Data already exists
        Duplicate = False
        For i = 2 To ir_last
            datetemp = ws_master.Cells(i, ic_date)
            If ws_master.Cells(i, ic_date).Value = datestr Then
                If ws_master.Cells(i, ic_ID) = fID(0) Then
                    Duplicate = True
                    Exit For
                End If
            End If
        
        Next i
        
        'If the new data is not a duplicate, then fill in a new line
        If Not Duplicate = True Then
            j = ws_master.Cells.Find(What:="ID", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
            ws_master.Cells(ir_last + 1, j) = fID(0)
            
            fID = Split(fID(1), "-")
            fDay = fID(UBound(fID))
            fDay = Split(fDay, ".")(0)
            If fDay = "tagesreport" Then
                ws_master.Cells(ir_last + 1, ic_zeitr) = "Tag"
            End If
           ir_last = ir_last + 1
            
            For i = 0 To UBound(headerList)
                columnHeading = headerList(i)
                
                Select Case columnHeading
                        
                    Case "DATUM"
                        ws_master.Cells(ir_last, ic_date) = datestr
                        
                        j = ws_master.Cells.Find(What:="Month", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = Month(datestr)
                        
                        j = ws_master.Cells.Find(What:="Year", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = Year(datestr)
                    
                    Case "PI"
                        j = ws_master.Cells.Find(What:="PI", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "Visit"
                        j = ws_master.Cells.Find(What:="Visit", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "UC Tag"
                        j = ws_master.Cells.Find(What:="UC Tag", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "Usetime Tag"
                        j = ws_master.Cells.Find(What:="Usetime Tag", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "PI laufende Woche"
                        j = ws_master.Cells.Find(What:="PI laufende Woche", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "Visit laufende Woche"
                        j = ws_master.Cells.Find(What:="Visit laufende Woche", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "PI laufender Monat"
                        j = ws_master.Cells.Find(What:="PI laufender Monat", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    Case "Visit laufender Monat"
                        j = ws_master.Cells.Find(What:="Visit laufender Monat", SearchDirection:=xlNext, SearchOrder:=xlByColumns).Column
                        ws_master.Cells(ir_last, j) = content(i)
                        
                    End Select
            Next i
            
        End If
    End If
    Set objAtt = Nothing

Next

wb_master.Close SaveChanges:=True


'Delete the temp file again
For Each objAtt In itm.Attachments
    If FileExists(Path & objAtt.DisplayName) Then
        ' First remove readonly attribute, if set
      SetAttr Path & objAtt.DisplayName, vbNormal
      ' Then delete the file
      Kill Path & objAtt.DisplayName
    End If
Next

End Sub

I know this is a lot of code, but I am really stuck on where I could optimize this. I appreciate any sort of help.



Solution 1:[1]

There are several aspects that could improve the overall performance of your code. VBA is a valid way for implementing such tasks if you don't have any plans for deploying the solution on multiple machines. But if you need to distribute your solution I'd recommend creating a VSTO add-in instead, see Walkthrough: Create your first VSTO Add-in for Outlook for more information.

First, I'd suggest filtering items on the attachments presence, so you can introduce one more condition to the Restrict method. Here is a sample search string which checks the Subject line and attachments:

Filter = "@SQL=" & Chr(34) & "urn:schemas:httpmail:subject" & _
                   Chr(34) & " Like '%training%' AND " & _
                   Chr(34) & "urn:schemas:httpmail:hasattachment" & _
                   Chr(34) & "=1"

Second, I'd recommend keeping the Excel application open while processing Outlook items. There is no need to open and close it each time.

Third, you can try to set up Excel's properties to increase performance such as ScreenUpdating and etc. Read more about them in the Maximizing Excel / VBA Automation Performance article.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eugene Astafiev