VBA for Outlook not parsing email correctly - vba

I am writing a VBA for outlook that will go through emails in my specific folder and go through the email's body and parse a specific line and then save it to an excel file. So far I am not getting any errors and when I run it, it saves an Excel file, but its only prints out an "email" string that I echo within the program, it's not parsed.
So I am having a bit of a problem parsing the proper information from the emails in the outlook folder. In matter of fact, I'm not sure if it's even parsing anything at all.
For iCtr = 1 To OutlookNameSpace.Folders.Item(1).Folders.Count
' handle case sensitivity as I can't type worth a crap
If LCase(OutlookNameSpace.Folders.Item(1).Folders(iCtr).Name) = LCase(strTargetFolder) Then
'found our target :)
Set outlookFolder = OutlookNameSpace.Folders.Item(1).Folders(iCtr)
Exit For ' found it so lets move on
End If
Next
'set up a header for the data dump, this is for CSV
strEmailContents = "Email" & vbCrLf
'likely should have some error handling here, in case we have found no target folder
'Set myFolderItem = outlookFolder.Items
' I have commenteted out some items to illustrate the call to Sue'strEmailContents Function
If Not outlookFolder Is Nothing Then
For Each outlookMessage In outlookFolder.Items
If TypeOf outlookMessage Is MailItem Then
strMsgBody = outlookMessage.Body ' assign message body to a Var
' then use Sue Moshers code to look for stuff in the body
' all of the following stuff in the quotes "" is specific to your needs
strEmailContents = strEmailContents & ParseTextLinePair(strMsgBody, "E-mail: ")
strEmailContents = strEmailContents & "," & ParseTextLinePair(strMsgBody, "")
'add the email message time stamp, just cause i want it
'debug message comment it out for production
'WScript.echo strEmailContents
End If
Next
End If
Here is my function to parse the lines:
Function ParseTextLinePair(strSource, strLabel)
' Sue Moshers code
'commented out type declaration for VBS usgage take out fer VB usage
Dim intLocLabel 'As Integer
Dim intLocCRLF 'As Integer
Dim intLenLabel 'As Integer
Dim strText 'As String
' locate the label in the source text
intLocLabel = InStr(strSource, strLabel)
intLenLabel = Len(strLabel)
If intLocLabel > 0 Then
intLocCRLF = InStr(intLocLabel, strSource, vbCrLf)
If intLocCRLF > 0 Then
intLocLabel = intLocLabel + intLenLabel
strText = Mid(strSource, _
intLocLabel, _
intLocCRLF - intLocLabel)
Else
intLocLabel = Mid(strSource, intLocLabel + intLenLabel)
End If
End If
ParseTextLinePair = Trim(strText) ' this i like
End Function
Here is an example of an email I am trying to parse; i have put it in code format so it is easier to read.
Vendor: 22***********
Your company may be interested in the following advertisement(s).
To learn more about the advertisements below, please visit the
******** Vendor Bid System (VBS) at
http://www.****************.com. For specific
questions about the solicitation, each advertisement includes
contact information for the agency representative who issued it.
to view additional information on the advertisement(s) listed
below.
____________________________________________________________
Agency: ***************************************
Agency Ads: http://www.*************.com
Advertisement Number: ******BLACKEDOUT INFO***********
Advertisement Type: Informational Notice
Title: Centralized Customer Service System (CCSS) - Notice of Public Meeting
Advertisement Status: New
Agency Contact: Sheree *****
E-mail: blah#aol.com
Telephone: (000)-000-0000
Thank you in advanced!!

EDIT
Alright sir, give this a shot. Make sure you designate your folder and searchtext at the top. A message box will popup once the email has been extracted.
Sub ParseContents()
Dim strTargetFolder : strTargetFolder = "Inbox"
Dim SearchText: SearchText = "Email: "
Dim NS As outlook.NameSpace
Dim oFld As outlook.Folder
Set NS = Application.GetNamespace("MAPI")
For ifld = 1 To NS.Folders.Count
For ictr = 1 To NS.Folders.Item(ifld).Folders.Count
' handle case sensitivity as I can't type worth a crap
If LCase(NS.Folders.Item(ifld).Folders(ictr).Name) = LCase(strTargetFolder) Then
'found our target :)
Set oFld = NS.Folders.Item(ifld).Folders(ictr)
Exit For ' found it so lets move on
End If
Next
Next
'set up a header for the data dump, this is for CSV
strEmailContents = "Email" & vbCrLf
Dim EscapeLoops: EscapeLoops = False
'likely should have some error handling here, in case we have found no target folder
'Set myFolderItem = outlookFolder.Items
' I have commenteted out some items to illustrate the call to Sue'strEmailContents Function
If Not oFld Is Nothing Then
For Each outlookMessage In oFld.Items
If TypeOf outlookMessage Is MailItem Then
If InStr(outlookMessage.Body, SearchText) Then
strMsgBody = outlookMessage.Body ' assign message body to a Var
' then use Sue Moshers code to look for stuff in the body
' all of the following stuff in the quotes "" is specific to your needs
Dim splitter, parsemail: splitter = Split(strMsgBody, vbCrLf)
For Each splt In splitter
If InStr(splt, SearchText) Then
parsemail = splt
EscapeLoops = True
Exit For
End If
Next
strEmailContents = strEmailContents & "Date/Time: " & outlookMessage.CreationTime & vbCrLf
strEmailContents = strEmailContents & ParseTextLinePair(parsemail, SearchText)
MsgBox strEmailContents
If EscapeLoops Then Exit For
End If
End If
Next
End If
End Sub
Function ParseTextLinePair(strSource, strLabel)
Dim Rturn
If InStr(strSource, vbCrLf) Then
Rturn = Mid(strSource, InStr(strSource, strLabel) + Len(strLabel), InStr(strSource, vbCrLf) - InStr(strSource, strLabel) + Len(strLabel)):
Else
Rturn = Mid(strSource, InStr(strSource, strLabel) + Len(strLabel))
End If
ParseTextLinePair = Trim(Rturn)
End Function

Related

There is a way to change the author of revisions in a Word document?

I would like to know if there is a way to change the author of revisions in a document, I've found the way to change the author of comments but no for the revisions part. I've tried to find a property/method in the Revisions.object documentation to change that but I find nothing.
As I said I've already tried to do this:
Sub ChangeCommentCreator()
Dim I As Long
Dim Novo As String
Dim Corto As String
If Selection.Comments.Count = 0 Then
MsgBox "No comments in your selection!", vbInformation, "Alerta"
Exit Sub
End If
Novo = InputBox("New author name?", "Alerta")
Corto = InputBox("New author initials?", "Alerta")
If Novo = "" Or Corto = "" Then
MsgBox "The author name/initials can’t be empty.", vbInformation, "Alerta"
Exit Sub
End If
With Selection
For I = 1 To .Comments.Count
.Comments(I).Author = Novo
.Comments(I).Initial = Corto
Next I
End With
End Sub
I'm going in the right way or there is just no way to change this?
Revisions automatically use the selected User Name in the UI. The language reference for the Revisions's Author property states:
Returns the name of the user who made the specified tracked change. Read-only String.
So there's no direct way using VBA and the object model to change that.
It is possible to change it by editing the underlying Word Open XML, as illustrated in the code that follows. I notice, however, that this appears to confuse Word - after running the macro no revisions are recognized in the document. Only after saving, closing and re-opening does Word "see" the revisions, again.
Sub ChangeAuthorName()
Dim sWOOXML As String
Dim findAuthor As String
Dim replaceAuthor As String
findAuthor = "w:author=" & Chr(34) & "Cindy Meister" & Chr(34)
replaceAuthor = "w:author=" & Chr(34) & "unknown" & Chr(34)
sWOOXML = ActiveDocument.content.WordOpenXML
sWOOXML = Replace(sWOOXML, findAuthor, replaceAuthor)
ActiveDocument.content.InsertXML sWOOXML
End Sub
Note that this will likely also change the author's name for comments. The more "elegant" way to do this would be to leverage an XML parser (such as MSXML) and work with the specific nodes. Or even use a package that works on the closed document and edits the Word Open XML. But this is the simplest way using straight Word VBA.
I picked up Cindy Meister's suggestion and cleared the Word confusion that he reported. All revision and comment authors, creators, and lastmodied will be changed by the name entered.
Sub ChangeCommentAndRevisionAuthor()
Dim j, jmax As Long
Dim Author(99) As String
Dim WXML, NewAuthor, FindAuthor, ReplaceAuthor As String
Dim BNew, StatusTrackRevision As Boolean
Dim Rev As Revision
Dim Cmt As Comment
' set some variables and put trackrevision to false
StatusTrackRevision = ActiveDocument.TrackRevisions
ActiveDocument.TrackRevisions = False
jmax = -1
' checks and input new author name
If ActiveDocument.Range.Revisions.count = 0 Then
MsgBox "No revisions in your document!", vbInformation, "Change comment and revision author"
Exit Sub
End If
NewAuthor = InputBox("New author name?", "Change comment and revision author")
If NewAuthor = "" Then
MsgBox "The author name can’t be empty.", vbInformation, "Change comment and revision author"
Exit Sub
End If
' Loop through all revisions and get all authors of revisions (maximum 100)
With ActiveDocument.Range
If .Revisions.count > 1000 Then
If MsgBox("The number of revisions it large. " & .Revisions.count & vbCr & " Do you want to continue?", vbOKCancel + vbQuestion + vbDefaultButton2, "Change comment and revision author") > 1 Then Exit Sub
End If
For Each Rev In .Revisions
BNew = True
For j = 0 To jmax
If Author(j) = Rev.Author Then
BNew = False
Exit For
End If
Next
If BNew Then
jmax = jmax + 1
If jmax > UBound(Author) Then jmax = UBound(Author)
Author(jmax) = Rev.Author
End If
Next
' change all comments
For Each Cmt In ActiveDocument.Comments
Cmt.Author = NewAuthor
Cmt.Initial = NewAuthor
Next
End With
' read XML and change all authors to the new author
WXML = ActiveDocument.Content.WordOpenXML
For j = 0 To jmax
WXML = Replace(WXML, "w:author=" & Chr(34) & Author(j) & Chr(34), "w:author=" & Chr(34) & NewAuthor & Chr(34))
Next
' change "last modified by" to new author
WXML = Replace(WXML, "<cp:lastModifiedBy>" & ActiveDocument.BuiltInDocumentProperties(7) & "</cp:lastModifiedBy>", "<cp:lastModifiedBy>" & NewAuthor & "</cp:lastModifiedBy>")
' save modified XML
ActiveDocument.Content.InsertXML WXML
' Change Creator of Document to new author
ActiveDocument.BuiltInDocumentProperties(3) = NewAuthor
' restore original status of track revision
ActiveDocument.TrackRevisions = StatusTrackRevision
End Sub

VBA to Copy Text String from PDF

I've started using the following code to identify a text string in a PDF. It works great, however, I'm wondering if there is a way to copy the entire row from the PDF into excel once the text has been found? I'm not very familiar with using VBA code to pull from PDFs so I'm kind of stuck at the moment. Any help is appreciated!!
Sub AcrobatFindText2()
'variables
Dim Resp 'For message box responses
Dim gPDFPath As String
Dim sText As String 'String to search for
Dim sStr As String 'Message string
Dim foundText As Integer 'Holds return value from "FindText" method
'hard coding for a PDF to open, it can be changed when needed.
gPDFPath = "C:\Users\Me\Documents\test.pdf"
'Initialize Acrobat by creating App object
Set gApp = CreateObject("AcroExch.App", "")
gApp.Hide
'Set AVDoc object
Set gAvDoc = CreateObject("AcroExch.AVDoc")
' open the PDF
If gAvDoc.Open(gPDFPath, "") Then
sText = "Designation"
'FindText params: StringToSearchFor, caseSensitive (1 or 0), WholeWords (1 or 0), 'ResetSearchToBeginOfDocument (1 or 0)
foundText = gAvDoc.FindText(sText, 1, 0, 1) 'Returns -1 if found, 0 otherwise
Else ' if failed, show error message
Resp = MsgBox("Cannot open" & gPDFPath, vbOKOnly)
End If
If foundText = -1 Then
'compose a message
sStr = "Found " & sText
Resp = MsgBox(sStr, vbOKOnly)
Else ' if failed, 'show error message
Resp = MsgBox("Cannot find" & sText, vbOKOnly)
End If
gApp.Show
gAvDoc.BringToFront
End Sub

replace the third line of each text files to string using a VBA script

I am using the below code to export emails as individual text files to system folder.I need to replace the third line in the text file as a string for all the text files each time in the loop.any one can suggest a solution
' General Declarations
Option Explicit
' Public declarations
Public Enum olSaveAsTypeEnum
olSaveAsTxt = 0
olSaveAsRTF = 1
olSaveAsMsg = 3
End Enum
Sub COBExport_MailasMSG()
' Routine will take all selected mails and export them as .MSG files to the
' directory defined by
' Error Handling
On Error Resume Next
' Varaiable Declarations
Dim objItem As Outlook.MailItem
Dim strExportFolder As String: strExportFolder = "I:\Documents\"
Dim strExportFileName As String
Dim strExportPath As String
Dim objRegex As Object
Dim OldName As String, NewName As String
' Initiate regex search
Set objRegex = CreateObject("VBScript.RegExp")
With objRegex
.Pattern = "(\s|\\|/|<|>|\|\|\?|:)"
.Global = True
.IgnoreCase = True
End With
' Check if any objects are selected.
If Application.ActiveExplorer.Selection.Count = 0 Then
MsgBox ("No item has been selected.")
Else
' Cycle all selected objects.
For Each objItem In Application.ActiveExplorer.Selection
' If the currently selected item is a mail item we can proceed
If TypeOf objItem Is Outlook.MailItem Then
' Export to the predefined folder.
strExportFileName = objRegex.Replace(objItem.Subject, "_")
strExportPath = strExportFolder & strExportFileName & ".txt"
objItem.SaveAs strExportPath, olSaveAsTxt
'MsgBox ("Email saved to: " & strExportPath)
OldName = Dir(strExportPath)
NewName = Left(strExportPath, Len(strExportPath) - Len(OldName)) & _
Left(OldName, Len(OldName) - 4) & "Dir" & _
CStr(Format(FileDateTime(strExportPath), "ddmmyyhhmmss")) & ".txt"
Name strExportPath As NewName
Else
' This is not an email item.
End If
Next 'objItem
End If
' Clear routine memory
Set objItem = Nothing
Set objRegex = Nothing
End Sub
This solution might cut out the middle man of what you are trying to do. Instead of updating the file after it is exported why dont we just edit the body of the email before hand!
BE WARNED that this will temporarily change the body of your emails. If the process fails or the code is not used properly you can damage email permanently. You should test this on mail you don't care about.
I did try to copy the mail so that we could edit a copy but that ended up with another copy of the mail, in Outlook, that i could not programically delete. Therefore this solution seemed cleaner.
' declaration to go with the others
Dim strEmailBodybackup As String
' this will go in your for loop
' Save the body so that we can restore it after.
strEmailBodybackup = objItem.Body
' Edit the body of the mail to suit needs.
objItem.Body = Replace(objItem.Body, "scantext", "Tscanfile", , 1, vbTextCompare)
' Process the export like in your question
' Restore the body of the original mail
objItem.Body = strEmailBodybackup
You can look up the Replace command here

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

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

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