I have created an excel addin but I want to find a way to get the debugging information to myself. My users are very far and run on different OS's and Office versions. I have tried emailing but the outlook security warnings are giving me a hard time and the CDO mail object needs smtp details which will be different for all my users. Is there something like general smtp settings that will always work ? Any other suggestion are welcome.
Any help will be appreciated.
there is code and documentation available on the web for sending mail using SMTP from Excel in VBA:
Chip Pearson: Sending Email with VBA and Collaboration Data Objects
The code requires a reference to Microsoft CDO for Windows 2000 Library
. The typical file location of this file is C:\Windows\system32\cdosys.dll. The GUID of this component is {CD000000-8B95-11D1-82DB-00C04FB1625D}, with Major = 1 and Minor = 0.
' COPIED FROM Chip Pearson Website: http://www.cpearson.com/excel/Email.aspx
'
Function SendEMail(Subject As String, _
FromAddress As String, _
ToAddress As String, _
MailBody As String, _
SMTP_Server As String, _
BodyFileName As String, _
Optional Attachments As Variant = Empty) As Boolean
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SendEmail Function
' By Chip Pearson, chip#cpearson.com www.cpearson.com 28-June-2012
'
' This function sends an email to the specified user.
' Parameters:
' Subject: The subject of the email.
' FromAddress: The sender's email address
' ToAddress: The recipient's email address or addresses.
' MailBody: The body of the email.
' SMTP_Server: The SMTP-Server name for outgoing mail.
' BodyFileName: A text file containing the body of the email.
' Attachments A single file name or an array of file names to
' attach to the message. The files must exist.
' Return Value:
' True if successful.
' False if failure.
'
' Subject may not be an empty string.
' FromAddress must be a valid email address.
' ToAddress must be a valid email address. To send to multiple recipients,
' use a semi-colon to separate the individual addresses. If there is a
' failure in one address, processing terminates and messages are not
' send to the rest of the recipients.
' If MailBody is vbNullString and BodyFileName is an existing text file, the content
' of the file named by BodyFileName is put into the body of the email. If
' BodyFileName does not exist, the function returns False. The content of
' the message body is created by a line-by-line import from BodyFileName.
' If MailBody is not vbNullString, then BodyFileName is ignored and the body
' is not created from the file.
' SMTP_Server must be a valid accessable SMTP server name.
' If both MailBody and BodyFileName are vbNullString, the mail message is
' sent with no body content.
' Attachments can be either a single file name as a String or an array of
' file names. If an attachment file does not exist, it is skipped but
' does not cause the procedure to terminate.
'
' If you want to send ThisWorkbook as an attachment to the message, use code
' like the following:
' ThisWorkbook.Save
' ThisWorkbook.ChangeFileAccess xlReadOnly
' B = SendEmail( _
' ... parameters ...
' Attachments:=ThisWorkbook.FullName)
' ThisWorkbook.ChangeFileAccess xlReadWrite
'
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Required References:
' --------------------
' Microsoft CDO for Windows 2000 Library
' Typical File Location: C:\Windows\system32\cdosys.dll
' GUID: {CD000000-8B95-11D1-82DB-00C04FB1625D}
' Major: 1 Minor: 0
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Dim MailMessage As CDO.Message
Dim N As Long
Dim FNum As Integer
Dim S As String
Dim Body As String
Dim Recips() As String
Dim Recip As String
Dim NRecip As Long
' ensure required parameters are present and valid.
If Len(Trim(Subject)) = 0 Then
SendEMail = False
Exit Function
End If
If Len(Trim(FromAddress)) = 0 Then
SendEMail = False
Exit Function
End If
If Len(Trim(SMTP_Server)) = 0 Then
SendEMail = False
Exit Function
End If
' Clean up the addresses
Recip = Replace(ToAddress, Space(1), vbNullString)
If Right(Recip, 1) = ";" Then
Recip = Left(Recip, Len(Recip) - 1)
End If
Recips = Split(Recip, ";")
For NRecip = LBound(Recips) To UBound(Recips)
On Error Resume Next
' Create a CDO Message object.
Set MailMessage = CreateObject("CDO.Message")
If Err.Number <> 0 Then
SendEMail = False
Exit Function
End If
Err.Clear
On Error GoTo 0
With MailMessage
.Subject = Subject
.From = FromAddress
.To = Recips(NRecip)
If MailBody <> vbNullString Then
.TextBody = MailBody
Else
If BodyFileName <> vbNullString Then
If Dir(BodyFileName, vbNormal) <> vbNullString Then
' import the text of the body from file BodyFileName
FNum = FreeFile
S = vbNullString
Body = vbNullString
Open BodyFileName For Input Access Read As #FNum
Do Until EOF(FNum)
Line Input #FNum, S
Body = Body & vbNewLine & S
Loop
Close #FNum
.TextBody = Body
Else
' BodyFileName not found.
SendEMail = False
Exit Function
End If
End If ' MailBody and BodyFileName are both vbNullString.
End If
If IsArray(Attachments) = True Then
' attach all the files in the array.
For N = LBound(Attachments) To UBound(Attachments)
' ensure the attachment file exists and attach it.
If Attachments(N) <> vbNullString Then
If Dir(Attachments(N), vbNormal) <> vbNullString Then
.AddAttachment Attachments(N)
End If
End If
Next N
Else
' ensure the file exists and if so, attach it to the message.
If Attachments <> vbNullString Then
If Dir(CStr(Attachments), vbNormal) <> vbNullString Then
.AddAttachment Attachments
End If
End If
End If
With .Configuration.Fields
' set up the SMTP configuration
.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTP_Server
.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
.Update
End With
On Error Resume Next
Err.Clear
' Send the message
.Send
If Err.Number = 0 Then
SendEMail = True
Else
SendEMail = False
Exit Function
End If
End With
Next NRecip
SendEMail = True
End Function
Another option is to use MAPI?
This is from the MS Knowledge Base:
Dim objSession As Object
Dim objMessage As Object
Dim objRecipient As Object
'Create the Session Object.
Set objSession = CreateObject("mapi.session")
'Logon using the session object.
'Specify a valid profile name if you want to.
'Avoid the logon dialog box.
objSession.Logon profileName:="MS Exchange Settings"
'Add a new message object to the OutBox.
Set objMessage = objSession.Outbox.Messages.Add
'Set the properties of the message object.
objMessage.subject = "This is a test."
objMessage.Text = "This is the message text."
'Add a recipient object to the objMessage.Recipients collection.
Set objRecipient = objMessage.Recipients.Add
'Set the properties of the recipient object.
objRecipient.Name = "John Doe" '<---Replace this with a valid
'display name or e-mail alias
'Type can be ActMsgTo, mapiTo, or CdoTo for different CDO versions;
'they all have a constant value of 1.
objRecipient.Type = mapiTo
objRecipient.Resolve
'Send the message.
objMessage.Send showDialog:=False
MsgBox "Message sent successfully!"
'Logoff using the session object.
objSession.Logoff
Related
I am creating a macro to send personalised business communications from a shared mailbox.
Below is the code:
Sub EnhancedMailMergeToEmail()
' Macro created by Imnoss Ltd
' Please share freely while retaining attribution
' Last Updated 2021-11-06
' REFERENCES REQUIRED!
' This Macro requires you to add the following libraries:
' "Microsoft Outlook xx.x Object Library" (replace xx.x with version number) and "Microsoft Scripting Runtime"
' To add them, use the "Tools" menu and select "References". Tick the check boxes next to the two libraries and press OK.
' declare variables
Dim outlookApp As Outlook.Application
Dim outlookMail As Outlook.MailItem
Dim outlookAccount As Outlook.Account
Dim fso As FileSystemObject
Dim f As Object
Dim attachFile As File
Dim mm As MailMerge
Dim df As MailMergeDataField
Dim singleDoc As Document
Dim mailBody As String
Dim lastRecordNum As Long
Dim recordCount As Long
Dim sendFlag As Boolean
Dim validRowFlag As Boolean
Dim tempFileName As String
Dim tempFolderName As String
Dim fieldName As String
Dim inputDate As Date
' identify the mail merge of the active document
Set mm = ActiveDocument.MailMerge
' check for the mail merge state being that of a mail merge ready to go
If mm.State <> wdMainAndDataSource Then
If MsgBox("Mailmerge not set up for active document - cannot perform mailmerge. Macro will exit.", vbOKOnly + vbCritical, "Error") = vbOK Then Exit Sub
End If
' set lastRecordNum to the number of the last active record (reached using wdLastRecord
mm.DataSource.ActiveRecord = wdLastRecord
lastRecordNum = mm.DataSource.ActiveRecord
' if the lastRecordNum is less than 50 we assume some may have been deselected so we count only the active records
' counting more than 50 records takes too long
If lastRecordNum < 50 Then
mm.DataSource.ActiveRecord = wdFirstRecord
recordCount = 0
Do While True
' run through the fields to check if a valid email address is provided in any of the "to", "cc" or "bcc" fields (valid address = contains an "#")
' also detect if the row is marked to be ignored
validRowFlag = False
For Each df In mm.DataSource.DataFields
' clean up the provided field name by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "ignore"
Select Case LCase(df.Value)
Case "true", "yes", "y", "ignore"
validRowFlag = False
Exit For
End Select
Case "to", "cc", "bcc"
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then
validRowFlag = True
End If
End Select
Next
If validRowFlag Then
recordCount = recordCount + 1
End If
If mm.DataSource.ActiveRecord = lastRecordNum Then
Exit Do
Else
mm.DataSource.ActiveRecord = wdNextRecord
End If
Loop
Else
recordCount = lastRecordNum
End If
If recordCount = 0 Then
If MsgBox("Cannot find any active / valid / not to be ignored records. Macro will Exit", vbOKOnly + vbCritical, "Error") = vbOK Then Exit Sub
End If
' Give the user an opportunity to abort, and also the option to save the emails in drafts, or send immediately
Select Case MsgBox("MailMerge to email will proceed for " & IIf(recordCount < 50, recordCount & " active", recordCount) & " records." _
+ Chr(10) + Chr(10) + _
"Click 'Yes' to send the emails immediatly, 'No' to save the emails in draft, and 'Cancel' to abort.", _
vbYesNoCancel + vbDefaultButton2 + vbQuestion, "Send Emails")
Case vbCancel
Exit Sub
Case vbYes
sendFlag = True
Case Else
sendFlag = False
End Select
' set variables
' outlookApp is used to control outlook to send an email
' fso is used to read the HTML file with the email content
Set outlookApp = New Outlook.Application
Set fso = New FileSystemObject
' we need to use a temporary file to store the html generated by mail merge
' fso.GetTempName creates a name with the extension tmp. We remove this
' because image files are stored in a folder with the name without the extension and with "_files" at the end
tempFileName = Replace(fso.GetTempName, ".tmp", "")
mm.DataSource.ActiveRecord = wdFirstRecord
recordCount = 0
' loop through all the records
Do While lastRecordNum > 0
' run through the fields to check if a valid email address is provided in any of the "to", "cc" or "bcc" fields (valid address = contains an "#")
' also detect if the row is marked to be ignored
validRowFlag = False
For Each df In mm.DataSource.DataFields
' clean up the provided field name by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "ignore"
Select Case LCase(df.Value)
Case "true", "yes", "y", "ignore"
validRowFlag = False
Exit For
End Select
Case "to", "cc", "bcc"
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then
validRowFlag = True
End If
End Select
Next
' only create an email if there is a valid addressa and the row is not marked as to be ignored
If validRowFlag Then
' use mailmerge to create a new document for one record (defined by mm.DataSource.ActiveRecord)
mm.Destination = wdSendToNewDocument
mm.DataSource.FirstRecord = mm.DataSource.ActiveRecord
mm.DataSource.LastRecord = mm.DataSource.ActiveRecord
mm.Execute Pause:=False
' save the generated doc as a html file in the temp directory
Set singleDoc = ActiveDocument
singleDoc.SaveAs2 FileName:=Environ("Temp") & Application.PathSeparator & tempFileName & ".tmp", FileFormat:=wdFormatFilteredHTML
singleDoc.Close SaveChanges:=wdDoNotSaveChanges
Set singleDoc = Nothing
' read the html from the temp directory using fso
mailBody = fso.OpenTextFile(Environ("Temp") & Application.PathSeparator & tempFileName & ".tmp", 1).ReadAll
' create a new email message in outlook
Set outlookMail = outlookApp.CreateItem(olMailItem)
' display the email so that any images display correctly
outlookMail.Display
' clear the content of the email and remove all attachments (i.e. clear the signature and any images in the signature)
outlookMail.HTMLBody = ""
Do While outlookMail.Attachments.Count > 0
outlookMail.Attachments.Remove 1
Loop
' ensure formatting is HTML
outlookMail.BodyFormat = olFormatHTML
' if the html contains images, then they will be stored in a directory called
' tempFileName followed by the _files in the local language (e.g. _bestanden in Dutch)
' so we need to find the directory, and the loop through each of the files
' checking to see if the files are included in the email as an image (i.e. as 'src="..."')
' if the image is included then the image is attached to the email as a hidden attachment
' and the image path is updated to point to the attached image
' try and find the temporary folder name which would contain any images
tempFolderName = ""
For Each f In fso.GetFolder(Environ("Temp")).SubFolders
If Left(f.Name, Len(tempFileName) + 1) = tempFileName & "_" Then
tempFolderName = f.Name
Exit For
End If
Next
' if the folder has been found, iterate through the files
If tempFolderName <> "" Then
For Each attachFile In fso.GetFolder(Environ("Temp") & Application.PathSeparator & tempFolderName).Files
If InStr(1, mailBody, "src=""" & tempFolderName & "/" & attachFile.Name & """", vbBinaryCompare) > 0 Then
outlookMail.Attachments.Add attachFile.Path, 1, 0
mailBody = Replace(mailBody, "src=""" & tempFolderName & "/" & attachFile.Name & """", "src=""cid:" & attachFile.Name & """")
End If
Next
End If
' add the mail body from the html created via mailmerge and updated for the newly attached images
outlookMail.HTMLBody = mailBody
' run through all the fields in the mail merge data, when an email field is identified add the data to the appropriate field
For Each df In mm.DataSource.DataFields
' first check for the field being populated for the active record (row), only check if there is data provided
If Trim(df.Value) <> "" Then
' try matching the field name to accepted field names
' the field name is cleaned up by running through the name letter by letter and adding only letters to the variable fieldName
fieldName = ""
For i = 1 To Len(df.Name)
Select Case Asc(LCase(Mid(df.Name, i, 1)))
Case 97 To 122
fieldName = fieldName & LCase(Mid(df.Name, i, 1))
End Select
Next i
Select Case fieldName
Case "to"
' add in the to address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.To = outlookMail.To & ";" & df.Value
Case "cc"
' add in the cc address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.CC = outlookMail.CC & ";" & df.Value
Case "bcc"
' add in the bcc address or addresses as they are presented in the data, multiple address should be separated by a semicolon
outlookMail.BCC = outlookMail.BCC & ";" & df.Value
Case "subject"
' add in the subject as it is presented in the data
outlookMail.Subject = df.Value
Case "importance"
' change the importance, accepted input values are "high", "normal", and "low" (not case sensitive)
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically "Normal", but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "high"
outlookMail.Importance = olImportanceHigh
Case "normal"
outlookMail.Importance = olImportanceNormal
Case "low"
outlookMail.Importance = olImportanceLow
End Select
Case "sensitivity"
' change the sensitivity, accepted input values are "confidential", "personal", "private", or "normal" (not case sensitive)
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically "Normal", but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "confidential"
outlookMail.Sensitivity = olConfidential
Case "personal"
outlookMail.Sensitivity = olPersonal
Case "private"
outlookMail.Sensitivity = olPrivate
Case "normal"
outlookMail.Sensitivity = olNormal
End Select
Case "readreceipt"
' request or do not request a read receipt
' if the field contains a boolean TRUE, or any form of "true"/"yes"/"y" (case insensitive) then request a read receipt
' if the field contains a boolean FALSE, or any form of "false"/"no"/"n" (case insensitive) then do not request a read receipt
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically to not request a read receipt, but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "true", "yes", "y"
outlookMail.ReadReceiptRequested = True
Case "false", "no", "n"
outlookMail.ReadReceiptRequested = False
End Select
Case "deliveryreceipt"
' request or do not request a delivery report
' if the field contains a boolean TRUE, or any form of "true"/"yes"/"y" (case insensitive) then request a delivery report
' if the field contains a boolean FALSE, or any form of "false"/"no"/"n" (case insensitive) then do not request a delivery report
' if field is not provided, or an incorrect input value is provided, then the default is used
' default is typically to not request a delivery report, but may have been changed in Outlook Options.
Select Case Trim(LCase(df.Value))
Case "true", "yes", "y"
outlookMail.OriginatorDeliveryReportRequested = True
Case "false", "no", "n"
outlookMail.OriginatorDeliveryReportRequested = False
End Select
Case "deliverytime"
' add in a delivery time (delay delivery)
' checks for the field containin a value or something which looks like a date and/or time
' if a datetime is provided, and that datetime is in the future then the delay is added to that datetime
' if a date is provided, and that date is in the future then the delay is added to midnight at the start of the provided date
' if a time is provided then the next instance of that time will be used to define the delay (so email could be sent "tomorrow" if time already passed)
' if no data, invalid data, or a date/datetime in the past is added then no delivery delay is added
If (IsNumeric(df.Value) Or IsDate(df.Value)) Then
' A date passed from an Excel table through mail merge will be formatted in US format ("m/d/yyyy"), but the function CDate
' uses the local format, e.g. ("d/m/yyyy"). CDate is nice enough to recognise (and not error) when fed a date with the day > 12,
' so both "15/1/2021" and "1/15/2021" will produce the correct date output (15 January 2021).
' The next couple of lines test for whether the date is the wrong way round and flips the month and day if needed
' A date is believed to be wrong if both month and day are 12 or lower, if CDate parses the date 1/2/2020 as 1 February 2020
' and finally if the raw input from Excel is a date string (and not a number, which would be valid)
inputDate = CDate(df.Value)
If Day(inputDate) <= 12 And Month(inputDate) <= 12 And Month(CDate("1/2/2020")) = 2 And _
InStr(1, df.Value, Format(inputDate, "d/m/yyyy")) = 1 Then
inputDate = DateSerial(Year(inputDate), Day(inputDate), Month(inputDate)) + TimeSerial(Hour(inputDate), Minute(inputDate), Second(inputDate))
End If
If inputDate < Now() - Date Then ' time only, time is in the past so set time for "tomorrow"
outlookMail.DeferredDeliveryTime = Date + 1 + inputDate
ElseIf inputDate < 1 Then ' time only, time is in the future so set time for "today"
outlookMail.DeferredDeliveryTime = Date + inputDate
ElseIf inputDate > Now() Then ' date or datetime in the future
outlookMail.DeferredDeliveryTime = inputDate
End If
Debug.Print df.Value, outlookMail.DeferredDeliveryTime
End If
Case "account"
' select the account from which the email is to be sent
' the account is identified by its full email address
' to identify the account, the code cycles through all the accounts available and selects a match
' if no data, or a non-matching email address is provided, then the default account is used
' note! not the same as send as - see below
For Each outlookAccount In outlookApp.Session.Accounts
If outlookAccount.SmtpAddress = df.Value Then
outlookMail.SendUsingAccount = outlookAccount
Exit For
End If
Next
Case "sendas"
' add in an address to send as or send on behalf of
' only added if a valid email address
' if the account does not have permissions, the email will be created but will be rejected by the Exchange server if sent
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then outlookMail.SentOnBehalfOfName = df.Value
Case "replyto"
' add in an address to reply to
' only added if a valid email address
If InStr(1, df.Value, "#", vbTextCompare) > 0 Then outlookMail.ReplyRecipients.Add (df.Value)
Case "attachment"
' add the attachment
outlookMail.Attachments.Add df.Value
End Select ' end test for the field names
End If ' end check for the data value being blank
Next df ' move on to the next record
' check the send flag and send or save
If sendFlag Then
outlookMail.Send
Else
outlookMail.Close (olSave)
End If
Set outlookMail = Nothing
Else
recordCount = recordCount + 1 ' keep a tally of skipped records using recordCount
End If ' end the test for whether a valid address is presented in the data
' test if we have just created a document for the last record, if so we set lastRecordNum to zero to indicate that the loop should end, otherwise go to the next active record
If mm.DataSource.ActiveRecord >= lastRecordNum Then
lastRecordNum = 0
Else
mm.DataSource.ActiveRecord = wdNextRecord
End If
Loop
End Sub
I would need to send from the shared mailbox but I can't use outlookMail.SendUsingAccount = outlookAccount because the mail is not set as a second account but is linked to my personal one.
I can't use outlookMail.SentOnBehalfOfName either because I have permissions to send as and not on behalf.
Isn't there another method?
Thank you in advance
There is no other way using the Outlook object model.
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
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
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
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