Providing status updates for macro that goes into not responding state until completion - vba

I have a VBA Macro to search through email archives.
When searching through tens of thousands of emails, (or even just a couple hundred on my test machine) it displays the status for a few seconds, then enters a Not Responding state while running through the rest of the emails.
This has led impatient users to close out of the task prematurely, and I would like to rectify this by providing status updates.
I have coded the following solution, and believe that the problem lies in the way the GarbageCollector functions in VBA during the Loop.
Public Sub searchAndMove()
UserForm1.Show
' Send a message to the user indicating
' the program has completed successfully,
' and displaying the number of messages sent during the run.
End Sub
Private Sub UserForm_Activate()
Me.Width = 240
Me.Height = 60
Me.Label1.Width = 230
Me.Label1.Height = 50
Dim oSelectTarget As Outlook.Folder
Dim oMoveTarget As Outlook.Folder
Dim oSearchCriteria As String
' Select the target folder to search and then the folder to
' which the files should be moved
Set oSelectTarget = Application.Session.PickFolder
Set oMoveTarget = Application.Session.PickFolder
oSearchCriteria = InputBox("Input search string: ")
Dim selectedItems As Outlook.Items
Set selectedItems = oSelectTarget.Items
Dim selectedEmail As Outlook.MailItem
Dim StatusBarMsg As String
StatusBarMsg = ""
Dim initialCount As Long
initialCount = selectedItems.count
Dim movedCounter As Long
movedCounter = 0
Dim x As Long
Dim exists As Long
' Function Loop, stepping backwards
' to prevent errors derived from modifying the collection
For x = selectedItems.count To 1 Step -1
Set selectedEmail = selectedItems.Item(x)
' Test to determine if the subject contains the search string
exists = InStr(selectedEmail.Subject, oSearchCriteria)
If Len(selectedEmail.Subject) > 999 Then
selectedEmail.Move oMoveTarget
Else:
If exists <> 0 Then
selectedEmail.Move oMoveTarget
movedCounter = (movedCounter + 1)
Else: End If
End If
Set selectedEmail = Nothing
StatusBarMsg = "Processing " & x & " out of " & initialCount & " messages."
UserForm1.Label1.Caption = StatusBarMsg
UserForm1.Repaint
Next x
Dim Msg As String
Dim Response
Msg = "SearchAndMove has detected and moved " & movedCounter & _
" messages since last run."
Response = MsgBox(Msg, vbOKOnly)
' Close the References to prevent a reference leak
Set oSelectTarget = Nothing
Set oMoveTarget = Nothing
Set selectedItems = Nothing
Set selectedEmail = Nothing
Unload Me
End Sub

Change the line
UserForm1.Repaint
to
DoEvents
Yes this will increase the execution time but in case there are thousands of emails then you don't have much of an option.
TIP:
Also you might want to change
StatusBarMsg = "Processing " & x & " out of " & initialCount & " messages."
to
StatusBarMsg = "Please do not interrupt. Processing " & x & " out of " & initialCount & " messages."
Also it is advisable to inform your user at the beginning of the process that it might take time and hence they can run the process when they are sure they do not want to work on that pc?
Something like this
Sub Sample()
Dim strWarning As String
Dim Ret
strWarning = "This process may take sometime. It is advisable to run this " & _
"when you don't intend to use the pc for sometime. Would you like to Continue?"
Ret = MsgBox(strWarning, vbYesNo, "Information")
If Ret <> vbYes Then Exit Sub
For x = SelectedItems.Count To 1 Step -1
'~~> Rest of the code
End Sub
HTH
Sid

Related

If statements - msgbox

I'm now trying to write an IF statement to say something to the effect of: If file is more than 5 days old, do not run macro. If more than 5 days old, run macro.
I would like to this to be a yes or no dialogue box. Here is my code. Please help. I'm still trying to learn this vba code.
Sub LastModifiedFile()
'Function FileLastModified(strFullFileName As String)
Dim fs As Object, f As Object, s As String, dtmodpath As String
dtmodpath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(dtmodpath)
's = UCase(strFullFileName) & vbCrLf
s = f.DateLastModified
FileLastModified = s
Set fs = Nothing: Set f = Nothing
Range("E5").Value = FileLastModified
'If FileExists(strFullName) Then
'MsgBox FileLastModified(strFullName)
'Else
'MsgBox "File Older than 5 Years : " & vbNewLine & strFullName
'End If
'End Function
End Sub
Congrats for using correctly the .DateLastModified property!
Instead of the MsgBox-es call a function. The DateAdd() returns date, which is 5 days before the current date, thus it is easy to compare. This shows a MsgBox() which informs whether the file has more or less than 5 days from the last modification:
Option Explicit
Sub LastModifiedFile()
Dim fileObject As Object
Dim file As Object
Dim modPath As String
modPath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fileObject = CreateObject("Scripting.FileSystemObject")
Set file = fileObject.GetFile(modPath)
If DateAdd("d", -5, Now) < file.DateLastModified Then
MsgBox "Less than 5 days."
Else
MsgBox "More than 5 days."
End If
End Sub
If you want to put a MsgBox in the whole story with Yes and No, then this should be ok:
Sub LastModifiedFile()
Dim fileObject As Object
Dim file As Object
Dim modPath As String
modPath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fileObject = CreateObject("Scripting.FileSystemObject")
Set file = fileObject.GetFile(modPath)
Dim msgBoxStatement As String
If DateAdd("d", -5, Now) < file.DateLastModified Then
msgBoxStatement = "This file is NOT older than 5 days!" & vbCrLf & _
"Should it be deleted?"
Else
msgBoxStatement = "This file is older than 5 days!" & vbCrLf & _
"Should it be deleted?"
End If
Select Case MsgBox(msgBoxStatement, vbYesNo Or vbQuestion, "Delete?")
Case vbYes
'run the for deletion
Case vbNo
'do not run the code for deletion
End Select
End Sub
Use DateDiff function to compute your number of days.
Its not totally clear what you want to do with your Yes/No message box, here's an attempt :
Sub LastModifiedFile()
Dim fs As Object, f As Object, s As String, dtmodpath As String
Dim dtLastMod As Date
Dim intDays As Long
dtmodpath = "\\jdshare\pdcmaterials\5_Tools\FTP\Cancelled_Report.txt"
Set fs = CreateObject("Scripting.FileSystemObject")
Set f = fs.GetFile(dtmodpath)
dtLastMod = f.DateLastModified
' Here you compute the number of days between the file last mod date, and the current date
intDays = DateDiff("d", dtLastMod, Now)
Set fs = Nothing: Set f = Nothing
Range("E5").Value = dtLastMod
If intDays > 5 Then
If MsgBox("File is " & intDays & " days old, proceed with macro ?", vbYesNo, "Continue?") = vbYes Then
' RUN MACRO GOES HERE
End If
Else
MsgBox "File is " & intDays & " days old, cancelling"
End If
End Sub

Excel VBA code only works in debug mode

I've already searched in a bunch of topics and no solution seemed to work for me.
I've an Excel macro file that sometimes works fine, but sometimes only works in stepping mode.
This is a sub inside a main sub that passes a value (message) to a spreadsheet from an Outlook Calendar by category (key). (for this code I adapted from Script to total hours by calendar category in Outlook) .The value goes into the row with the same name as the category and the week value in the column. I've tried the DoEvents and I thought it had worked, but when I tried to run it in a different computer it failed again.
Any ideas?
Option Explicit
Public keyArray
Sub totalCategories()
Dim app As New Outlook.Application
Dim namespace As Outlook.namespace
Dim calendar As Outlook.Folder
Dim appt As Outlook.AppointmentItem
Dim apptList As Outlook.Items
Dim apptListFiltered As Outlook.Items
Dim startDate As String
Dim endDate As String
Dim category As String
Dim duration As Integer
Dim outMsg As String
Dim firstDayOfTheYear As Date
'Going to be used to get start and end date
firstDayOfTheYear = Date
firstDayOfTheYear = "01/01/" & Right(firstDayOfTheYear, 4)
' Access appointment list
Set namespace = app.GetNamespace("MAPI")
Set calendar = namespace.GetDefaultFolder(olFolderCalendar)
Set apptList = calendar.Items
' Include recurring appointments and sort the list
apptList.IncludeRecurrences = True
apptList.Sort "[Start]"
' Get selected date
startDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1)
endDate = firstDayOfTheYear + 7 * (CInt(SelectWeek.week) - 1) + 6
startDate = Format(startDate, "dd/MM/yyyy") & " 00:01"
endDate = Format(endDate, "dd/MM/yyyy") & " 11:59 PM"
' Filter the appointment list
Dim strFilter As String
strFilter = "[Start] >= '" & startDate & "'" & " AND [End] <= '" & endDate & "'"
Set apptListFiltered = apptList.Restrict(strFilter)
' Loop through the appointments and total for each category
Dim catHours
Set catHours = CreateObject("Scripting.Dictionary")
For Each appt In apptListFiltered
category = appt.Categories
duration = appt.duration
If catHours.Exists(category) Then
catHours(category) = catHours(category) + duration
Else
catHours.Add category, duration
End If
Next
' Loop through the categories
Dim key
keyArray = catHours.Keys
DoEvents 'prevents a bug from happening --> in some cases the total hours weren't divided by categories
For Each key In keyArray
outMsg = catHours(key) / 60
'Print in Realizado sheet --> activities must be in range (name manager) as "atividades"
writeReport SelectWeek.week, outMsg, key
Next
' Clean up objects
Set app = Nothing
Set namespace = Nothing
Set calendar = Nothing
Set appt = Nothing
Set apptList = Nothing
Set apptListFiltered = Nothing
End Sub
Sub writeReport(week, message As String, key)
Dim ws As Worksheet
Dim i As Integer
Dim Activities, nActivities As Integer
Set ws = Sheets("5")
Activities = Range("activities")
nActivities = UBound(Activities)
DoEvents
For i = 1 To nActivities
DoEvents
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
Next i
End Sub
You need to handle errors explicitly so you know exactly what is going on. Trust me that this will save you HEAPS of time troubleshooting your own code, especially in VBA.
Common practice is something like "try, catch, finally".
Dim position as string
Sub foo()
position = "sub function short description"
On Error GoTo catch
Err.Clear
'do stuff
finally:
On Error Resume Next
'do cleanup stuff
Exit Sub
catch:
Debug.Print Right(ThisWorkbook.FullName, Len(ThisWorkbook.FullName) - 3) & ", _
Position: " & errorPosition & " , Error Code: [ " & Hex(Err.number) & "], _
Description: " & Err.Description & ""
Resume finally
End Sub
Problem solved!
From this:
If key = Cells(i + 8, 2).Value Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If
To this:
If key = Activities(i, 1) Then
ws.Cells(i + 8, week + 3).Value = CDbl(message)
Exit For
End If

Email loop causing Notes to crash (Embed object = issue)

I have the following code which is always causing IBM(LOTUS) Notes to crash at the .EmbedObject line
Call body.EmbedObject(1454, "", Attachment)
This is the part of the main code. At this point there are 2 dictionaries which are converted to arrays and then into e-mail strings. The call to the EMAIL sub-routine is below.
Anyone have any idea what could be causing this or know a fix?? All variables are declared at the public level in the main module with string type
This works fine with a simple loop macro that I used to integrate into my macro (basic for loop calling the email routine every iteration, with declaring the document and body each time)
thank you
Private Sub SaveFilestoDesktop_andEmail()
'Saves file to desktop with date stamp and e-mails to the user
Dim WB As Workbook
Dim wks As String
Dim fname As String, i As Integer
Dim EmailArray_PC() As Variant, EmailArray_PM() As Variant
EmailArray_PM = dict.keys()
EmailArray_PC = dict_2.keys()
i = 1
Subj = "Items to Review"
'EmailBody = "The following items have been flagged as possible cost errors " & _
'"by process of identifying variances of +/- 30 % compared to the current average cost. " & _
'"Please see attachment and review for internal purposes." & vbLf & _
'vbLf & VBA.Format(Now, "m/d/yyyy hh:mm:ss AM/PM")
On Error GoTo errhandlr
For Each WB In Workbooks
'Set the first sheet name of each WB to the wks variable
wks = WB.ActiveSheet.Name
'If unsaved workbook (only part of the above sub procedures)
If Left(WB.Name, 4) = "Book" Then
fname = Application.DefaultFilePath & "\" & Replace(WB.Worksheets(1).Name, ".", "") & "- " & VBA.FormatDateTime(Date, vbLongDate) _
& " (" & Format(Time, "hhmmss AMPM") & ")"
With WB
' If Dir(fname) <> "" Then
Application.DisplayAlerts = False
'Save the file as an .xlsx to the default user path
.SaveAs Filename:=fname, FileFormat:=51
Application.DisplayAlerts = True
On Error Resume Next 'if tries to e-mail but it fails (such as for "blank")
'Setting up parameters for e-mailing
SendTo = Right(EmailArray_PM(i), Len(EmailArray_PM(i)) - WorksheetFunction.Find(",", EmailArray_PM(i)) - 1) & "_" & _
Left(EmailArray_PM(i), WorksheetFunction.Find(",", EmailArray_PM(i)) - 1) & "#quadra.ca"
SendCC = Right(EmailArray_PC(i), Len(EmailArray_PC(i)) - WorksheetFunction.Find(",", EmailArray_PC(i)) - 1) & _
"_" & Left(EmailArray_PC(i), WorksheetFunction.Find(",", EmailArray_PC(i)) - 1) & "#quadra.ca"
Attachment = WB.Name
'Call e-mail maco in Other module
Call Email_using_Notes_Call(SendTo, SendCC, Attachment)
'Increment i by 1
i = i + 1
On Error GoTo 0
'Close the Workbook, go to next WB
.Close
End With
'Clear the filename to save with for next WB
fname = Empty
End If
Next WB
Exit Sub
Erase EmailArray_PC: Erase EmailArray_PM
Set dict = Nothing: Set dict_2 = Nothing 'clear dict objs
errhandlr:
MsgBox err.Number & Space(2) & err.Description
err.Clear
'MsgBox err.Number & Space(2) & err.Description
Application.ScreenUpdating = True
Application.Calculation = xlCalculationAutomatic
End Sub
Call to EMAIL loop:
Sub Email_using_Notes_Call(ByVal SendTo As String, _
Optional ByVal SendCC As String, Optional ByVal Attachment As String)
On Error Resume Next
'Creates the Notes Document (e-mail)
Set doc = db.CreateDocument
With doc
.Subject = Subj
.SendTo = SendTo
.CopyTo = SendCC
.Importance = "1"
End With
'Creating the body of the Notes document
Set body = doc.CreateRichTextItem("Body")
'Formatting the body of the text
Call body.AppendText("The following items have been flagged as possible cost errors by process of identifying variances of +/- 30 %")
Call body.AddNewline(1) '--> This adds a line feed to the body
Call body.AppendText("compared to the current average cost. Please see attachment and review for internal purposes ")
Call body.EmbedObject(1454, "", Attachment) --> this is where it crashes 'EMBED_ATTACHMENT[1454 = embed attachment, 1453 = embed object]
Call body.AddNewline(2)
Call body.AppendText(Now())
Call doc.Send(False) 'False is the variable that indicates attach form or not (always false in our case)
'Clearing for next document
Set body = Nothing
Set doc = Nothing
On Error GoTo -1
End Sub
I think this issue is caused what you are trying to embed.
The document you are trying to Embed is the Excel workbook itself. You have the workbook open, so it cannot necessarily be read due to a lock.
Something that might help you definitely find out if that's the reason:
Try to add another file as the attachment that isn't open and see if it works, as a test.
Change the On Error Resume Next located in your e-mailing function to an error handler, like you have in the function above it.

Slowing down vba to take into account network lag (outlook)

I've written a script to help me a little with resource planning. It's looking through a shared outlook inbox to find out when we receive the most emails, and so when we should avoid any breaks etc.
It seems to be working perfectly, but occasionally throws up a random error (type mismatch, call failed, operation failed etc). When I run the debugger from the error message it carries on as normal. It gets through around 800ish messages each time, between errors, and more if it's in a good mood.
All I can think is that sometimes the different subfolders take a moment to load. My code is below, is there anything that I can add to make it wait for a moment for messages to load from the server?
Thanks in advance.
Calling loop in the sub:
For Each msg In StartFolder.Items
DoEvents
msgData = ripData(msg)
written = toExcel(msgData, strExcelFilePath)
Next
Functions defined below:
Function ripData(msg As Outlook.MailItem) As Variant
Dim V() As Variant
ReDim V(1 To 10)
Dim minutes As Integer
DoEvents
V(1) = msg.Sender
If InStr(1, msg.Sender.Address, "#", 1) > 1 Then
V(2) = Mid(msg.Sender.Address, InStr(1, msg.Sender.Address, "#", 1))
Else
V(2) = "insight.com"
End If
V(3) = Format(msg.ReceivedTime, "short date")
V(4) = Format(msg.ReceivedTime, "DDDD")
V(5) = Format(msg.ReceivedTime, "dd")
V(6) = Format(msg.ReceivedTime, "MMMM")
V(7) = Format(msg.ReceivedTime, "yyyy")
V(8) = Format(msg.ReceivedTime, "hh:mm")
V(9) = Format(msg.ReceivedTime, "hh")
minutes = Split(Format(msg.ReceivedTime, "hh:mm"), ":")(1)
If minutes < 15 Then
V(10) = 1
ElseIf minutes < 30 Then
V(10) = 2
ElseIf minutes < 45 Then
V(10) = 3
Else
V(10) = 4
End If
ripData = V
End Function
Function toExcel(data As Variant, excelFName As String) As Boolean
Dim fso As New FileSystemObject
Dim spath As String, sFileName As String, fileWithoutExt As String, lrow As Long
Dim i As Long
Dim myWB As Object, oXLWs As Object
sFileName = fso.GetFileName(excelFName)
fileWithoutExt = sFileName
Set myWB = FindOpenExcel(excelFName, fileWithoutExt, sFileName)
Set oXLWs = myWB.Sheets("Raw Data")
lrow = oXLWs.Range("A1048576").End(xlUp).Row + 1
'~~> Write to excel
For i = 1 To UBound(data)
oXLWs.Cells(lrow, i).Value = data(i)
Next i
End Function
Type mismatch means you are assuming that all items are MailItem object, but the Inbox folder can have other item typed ReportItem, MeetingItem, etc.). Do check the Class property ot make sure you have the expected object - it will be 43 (olMail) for the MailItem objects.
Secondly, you might be opening too many items - Exchange limits the number of simultaneously open objects. Plus "for each" loop keeps all collection elements referenced until the loop exits. Use the "for" loop
dim oItems
oItems = StartFolder.Items
dim I As Integer
dim msg As Object
For I = 1 to oItems.Count
set msg = oItems.Item(I)
if msg.Class = 43 Then
msgData = ripData(msg)
written = toExcel(msgData, strExcelFilePath)
End If
set msg = Nothing
Next
If caching is not set on (account settings) for the shared folders you can get operation failed errors intermittently.

Creating a Windows application which reads and writes excel spreadsheets + reads and writes emails

I'm an intern in Europe working at a hospital. My daily job is to find replacements for a nurse or doctor or surgeon when one is needed. To do this, I receive a request from a certain department, in the form of an excel spreadsheet with 4 different attributes which determines the time, department and specific type of personnel required.
From that info I look into a fixed database which is also based in an excel spreadsheet, for someone who matches the requirements.
After I send an email/sms or call the department head to get an approval, in which the reply is almost always yes.
Once I get the confirmation, I send the replacement's information to the department which requires the replacement and then my job is done. I do about 150 of these requests a day and if I can write a program for this, I would be able to save the hospital a lot of tax payers money, as they employ 3 other people to do this job.
Therefore, my question:
What is the best language to write this program in?
Would you recommend a scripting language which may make it easier to access files and send emails? or would that we too weak for this task?
The requirements for the language are to do the following:
Access excel spreadsheets
Read the spreadsheet and copy the values from an array of cells
Find a value in the spreadsheet
send emails with the values I obtained in my excel spreadsheet search?
read an email and if value is = to YES, do ... else do ...
finally, send an email with xxxxx information to xxx person
If I were using my mac, I would have gone to a scripting language like applescript combined with automator to access and read the excel files and send emails/sms's.
Thanks for you help in advance.
The code below is a long way from a complete solution. Its purpose is to start you thinking about how your system will function.
Looking to the future, I envisage the need for a text file which I have named HumanActionRequired.txt. The tenth line of code is a constant that specifies the folder in which this file will be created. You must replace "C:\DataArea\Play" with the name of a folder on your system. You may wish to rename the file: see sixth line.
Although I envisage this file to be the destination of error messages, I have used it here to list details of the messages in InBox. I have only output a small selection of the available properties but it should get you thinking about what is possible.
The code below belongs in a Module within OutLook:
Open Outlook.
Select Tools, Macro and Security. You will need to set the security level to Medium. Later you can discuss getting trusted status for your macro with your IT department but this will do for now.
Select Tools, Macro and Visual Basic Editor or click Alt+F11.
You will probably see the Project Explorer down the left (Control+R to display if not). If you have never created an Outlook macro, the area to the right will be grey.
Select Insert, Module. The grey area will go white with the code area above and the Immediate window below.
Copy the code below into the code area.
Position the cursor within the macro LocateInterestingEmails() and click F5. You will be warned that a macro is trying to access your emails. Tick Allow access for and select a time limit then click Yes. The macro will write selected properties of the emails in Inbox to the file HumanActionRequired.txt.
Option Explicit
Sub LocateInterestingEmails()
Dim ErrorDescription As String
Dim ErrorNumber As Long
Static ErrorCount As Integer
Const FileCrnt As String = "HumanActionRequired.txt"
Dim FolderTgt As MAPIFolder
Dim InxAttachCrnt As Long
Dim InxItemCrnt As Long
Dim OutputFileNum As Long
Const PathCrnt As String = "C:\DataArea\Play"
ErrorCount = 0
OutputFileNum = 0
Restart:
' On Error GoTo CloseDown
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileCrnt For Append Lock Write As #OutputFileNum
For InxItemCrnt = 1 To FolderTgt.Items.Count
With FolderTgt.Items.Item(InxItemCrnt)
If .Class = olMail Then
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Subject: " & .Subject
Print #OutputFileNum, "Sender: " & .SenderEmailAddress
Print #OutputFileNum, "Recipient: " & .To
Print #OutputFileNum, "Date sent: " & .SentOn
If .Attachments.Count > 0 Then
Print #OutputFileNum, "Attachments:"
For InxAttachCrnt = 1 To .Attachments.Count
Print #OutputFileNum, " " & .Attachments(InxAttachCrnt).DisplayName
Next
End If
End If
End With
Next
CloseDown:
ErrorNumber = Err.Number
ErrorDescription = Err.Description
Err.Clear
Set FolderTgt = Nothing
If ErrorNumber <> 0 Then
' Here because of an error
If OutputFileNum = 0 Then
' Output file not open
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileCrnt For Append Lock Write As #OutputFileNum
End If
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Error at " & Now()
Print #OutputFileNum, "Error number = " & ErrorNumber & _
" description = " & ErrorDescription
End If
If OutputFileNum <> 0 Then
' File open
Close OutputFileNum
OutputFileNum = 0
End If
End Sub
Version 2
This version includes the code in the first version plus:
It opens an existing workbook to which it saves information about the Excel attachments found.
It identifies attachments with an extension of xls? and saves them to disc with a name based on the date/time received and the sender's name.
It opens each saved attachment. For each worksheet in a saved attachment, it creates a row in the existing workbook containing filenames, sender name and email address, sheet name and the value of cell A1.
I do not think this code will be directly useful but it shows how to save attachments and open workbooks to read from or write to then which I believe you will need.
The only code I know to be missing is:
Move processed email to save folder.
Generate reply email.
However, more code may be necessary depending on how you want to automate the entire process.
The code below is not as neat as I would like. I do not want to add any more until you have fully understood it. I would also like a better understanding of the emails you plan to send and the desired automation of the total process.
Come back with questions on any part of the code you do not understand.
Option Explicit
Sub LocateInterestingEmails()
' I use constants to indentify columns in worksbooks because if I move the
' column I only need to update the constant to update the code. I said the
' same in a previous answer and some one responded that they preferred
' Enumerations. I use Enumerations a lot but I still prefer to use constants
' for column numbers.
Const ColSumFileNameSaved As String = "A"
Const ColSumFileNameOriginal As String = "B"
Const ColSumSenderName As String = "C"
Const ColSumSenderEmail As String = "D"
Const ColSumSheet As String = "E"
Const ColSumCellA1 As String = "F"
' You must change the value of this constant to the name of a folder on your
' computer. All file created by this macro are written to this folder.
Const PathCrnt As String = "C:\DataArea\Play"
' I suggest you change the values of these constants to
' something that you find helpful.
Const FileNameHAR As String = "HumanActionRequired.txt"
Const FileNameSummary As String = "Paolo.xls"
Dim CellValueA1 As Variant
Dim ErrorDescription As String
Dim ErrorNumber As Long
Dim FileNameReqDisplay As String
Dim FileNameReqSaved As String
Dim FolderTgt As MAPIFolder
Dim InxAttachCrnt As Long
Dim InxItemCrnt As Long
Dim InxSheet As Long
Dim OutputFileNum As Long
Dim Pos As Long
Dim ReceivedTime As Date
Dim RowSummary As Long
Dim SenderName As String
Dim SenderEmail As String
Dim SheetName As String
Dim XlApp As Excel.Application
Dim XlWkBkRequest As Excel.Workbook
Dim XlWkBkSummary As Excel.Workbook
' Ensure resource controls are null before macro does anything that can cause
' an error so error handler knows if the resource is to be released.
OutputFileNum = 0
Set XlApp = Nothing
Set XlWkBkRequest = Nothing
Set XlWkBkSummary = Nothing
' Open own copy of Excel
Set XlApp = Application.CreateObject("Excel.Application")
With XlApp
.Visible = True ' This slows your macro but helps during debugging
' Open workbook to which a summary of workbooks extracted will be written
Set XlWkBkSummary = .Workbooks.Open(PathCrnt & "\" & FileNameSummary)
With XlWkBkSummary.Worksheets("Summary")
' Set RowSummary to one more than the last currently used row
RowSummary = .Cells(.Rows.Count, ColSumFileNameSaved).End(xlUp).Row + 1
End With
End With
Restart:
' I prefer to have my error handler switched off during development so the
' macro stops on the faulty statement. If you remove the comment mark from
' the On Error statement then any error will cause the code to junp to label
' CloseDown which is at the bottom of this routine.
' On Error GoTo CloseDown
' Gain access to InBox
Set FolderTgt = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderInbox)
' Open text file for output. I envisage this file being used for error
' messages but for this version of the macro I write a summary of the
' contents of the InBox to it.
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileNameHAR For Output Lock Write As #OutputFileNum
For InxItemCrnt = 1 To FolderTgt.Items.Count
With FolderTgt.Items.Item(InxItemCrnt)
If .Class = olMail Then
' Only interested in mail items. Most of the other items will be
' meeting requests.
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Subject: " & .Subject
' Currently we are within With FolderTgt.Items.Item(InxItemCrnt).
' Values from this mail item are to be written to a workbook
' for which another With will be required. Copy values to
' variables for they are accessable.
' Note: XlApp.XlWkBkSummary.Worksheets("Summary")
' .Cells(RowSummary, ColSumFileNameOriginal).Value = _
' FolderTgt.Items.Item(InxItemCrnt).Attachments(InxAttachCrnt) _
' .DisplayName
' is legal but is not very clear. Code is much clearer will full use
' of With stateents even if it means values must be copied to variable.
SenderName = .SenderName
SenderEmail = .SenderEmailAddress
ReceivedTime = .ReceivedTime
Print #OutputFileNum, "SenderName: " & SenderName
Print #OutputFileNum, "SenderAddr: " & SenderEmail
Print #OutputFileNum, "Received: " & ReceivedTime
Print #OutputFileNum, "Date sent: " & .SentOn
If .Attachments.Count > 0 Then
Print #OutputFileNum, "Attachments:"
For InxAttachCrnt = 1 To .Attachments.Count
With .Attachments(InxAttachCrnt)
' I cannot find an example for which the
' DisplayName and FileName are different
FileNameReqDisplay = .DisplayName
Print #OutputFileNum, " " & FileNameReqDisplay & "|" & .FileName
Pos = InStrRev(FileNameReqDisplay, ".")
' With ... End With and If ... End If must be properly nested.
' Within the If below I want access to the attachment and to the
' workbook. Hence the need to terminate the current With and then
' immediately start it again within the If ... End If block.
End With
If LCase(Mid(FileNameReqDisplay, Pos + 1, 3)) = "xls" Then
With .Attachments(InxAttachCrnt)
' Save the attachment with a unique name. Note this will only be
' unique if you do not save the same attachment again.
FileNameReqSaved = _
Format(ReceivedTime, "yyyymmddhhmmss") & " " & SenderName
.SaveAsFile PathCrnt & "\" & FileNameReqSaved
End With
' Open the saved attachment
Set XlWkBkRequest = _
XlApp.Workbooks.Open(PathCrnt & "\" & FileNameReqSaved)
With XlWkBkRequest
'Examine every worksheet in workbook
For InxSheet = 1 To .Worksheets.Count
With .Worksheets(InxSheet)
' Save sheet name and a sample value
SheetName = .Name
CellValueA1 = .Cells(1, 1).Value
End With
' Save information about this sheet and its workbook
With XlWkBkSummary.Worksheets("Summary")
.Cells(RowSummary, ColSumFileNameSaved).Value = _
FileNameReqSaved
.Cells(RowSummary, ColSumFileNameOriginal).Value = _
FileNameReqDisplay
.Cells(RowSummary, ColSumSenderName).Value = SenderName
.Cells(RowSummary, ColSumSenderEmail).Value = SenderEmail
.Cells(RowSummary, ColSumSheet).Value = SheetName
.Cells(RowSummary, ColSumCellA1).Value = CellValueA1
RowSummary = RowSummary + 1
End With ' XlWkBkSummary.Worksheets("Summary")
Next InxSheet
.Close SaveChanges:=False
Set XlWkBkRequest = Nothing
End With ' XlWkBkRequest
End If
Next
End If
End If
End With
Next
CloseDown:
ErrorNumber = Err.Number
ErrorDescription = Err.Description
Err.Clear
Set FolderTgt = Nothing
If ErrorNumber <> 0 Then
' Have reached here because of an error
If OutputFileNum = 0 Then
' Output file not open
OutputFileNum = FreeFile
Open PathCrnt & "\" & FileNameHAR For Append Lock Write As #OutputFileNum
End If
Print #OutputFileNum, "-----------------------------"
Print #OutputFileNum, "Error at " & Now()
Print #OutputFileNum, "Error number = " & ErrorNumber & _
" description = " & ErrorDescription
End If
' Release resources
If OutputFileNum <> 0 Then
' File open
Close OutputFileNum
OutputFileNum = 0
End If
If Not (XlWkBkRequest Is Nothing) Then
XlWkBkRequest.Close SaveChanges:=False
Set XlWkBkRequest = Nothing
End If
If Not (XlWkBkSummary Is Nothing) Then
XlWkBkSummary.Close SaveChanges:=True
Set XlWkBkSummary = Nothing
End If
If Not (XlApp Is Nothing) Then
XlApp.Quit
Set XlApp = Nothing
End If
End Sub