Accessing Text Body of Outlook MailItem Object - HTML and Plaintext - vba

[EDIT] This problem does not appear to exist in C#. See reworked code at the bottom.
This has baffled me for two days now and has finally led me to making my first post on here.
I am coding in the visual basic editor of Excel 2007.
I am using the Outlook 2007 object library, from Excel 2007, not Outlook. Not sure if this matters.
I'm writing a program that will run periodically on a mail folder and parse out important information from emails as they arrive. The emails in question look like plain text, but are identified by the VBA Locals window as being olFormatHTML! To test this, I click "reply" for one of the emails and then attempt to paste an Excel range into the body of the email, and Outlook gives me a popup (Compatibility Checker) that gives me the option to "switch to HTML". Looks like plaintext. Further, opening the message, clicking "Other Actions" --> Encoding yields Unicode (UTF-8). So why in the world, when I expand this MailItem object in the Locals window, does Excel think it is an HTML email?
This MailItem's .Body is empty, and this MailItem's .HTMLBody does not contain the actual contents of the email, which are nonempty when viewed through Outlook. Here's what the HTMLBody value is:
"<html xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40"><head><meta name=ProgId content=Word.Document><met"
Code to create Outlook application object, navigate to desired folder and pass MailItem object to the parser (skip if you're familiar with this part):
' Navigate to desired folder and pass information to text analyzer.
Sub AccessInbox(timeStamp As Date)
Dim olApp As Outlook.Application
Dim objNamespace As Outlook.Namespace
Dim objFolder As Outlook.MAPIFolder
Dim sharedFolder As Outlook.MAPIFolder
Set olApp = New Outlook.Application
Set objNamespace = olApp.GetNamespace("MAPI")
' Explicitly went through hierarchy since I'll be using with a shared Mailbox eventually.
Set objMailbox = objNamespace.Folders("Mailbox - My Name")
Set objFolder = objMailbox.Folders("Inbox")
Set sharedFolder = objFolder.Folders("Folder With Stuff")
'mostly irrelevant, see ParseEmailText code below this
Dim emailTimeStamp As Date
For Each Item In sharedFolder.Items
' Inbox can contain other kinds of objects than MailItem.
If TypeOf Item Is MailItem Then
Dim thisEmail As Object
Set thisEmail = olApp.CreateItem(MailItem)
thisEmail = Item
' Check to see if email has already been analyzed.
emailTimeStamp = thisEmail.ReceivedTime
If emailTimeStamp > timeStamp Then
' Send to email text analyzxer.
ParseEmailText thisEmail
Else
Exit For
End If
End If
Next
End Sub
Code to parse email body:
Sub ParseEmailText(email As Outlook.MailItem)
emBody = email.Body
' This is the part where I wish I could just access the email's body, but it is empty.
End Sub
[EDIT] I reworked this basic code in C# and the MailItem.Body is NOT blank anymore. In fact it works exactly as expected. Any ideas why VBA sucks so much?
class Parser
{
//Outlook variables
Microsoft.Office.Interop.Outlook.Application app = null;
Microsoft.Office.Interop.Outlook._NameSpace ns = null;
Microsoft.Office.Interop.Outlook.MailItem item = null;
Microsoft.Office.Interop.Outlook.MAPIFolder inboxFolder = null;
Microsoft.Office.Interop.Outlook.MAPIFolder atFolder = null;
public Parser()
{
}
public void ParseInbox()
{
//open outlook
//Access Outlook (only need to do this once)
app = new Microsoft.Office.Interop.Outlook.Application();
ns = app.GetNamespace("MAPI"); //Returns a NameSpace object of the specified type. The only supported name space type is "MAPI".
ns.Logon(null, null, false, false); //Namespace.Logon method: Logs the user on to MAPI, obtaining a MAPI session.
inboxFolder = ns.GetDefaultFolder(Microsoft.Office.Interop.Outlook.OlDefaultFolders.olFolderInbox);
atFolder = inboxFolder.Folders["Folder With Stuff"];
for (int i = atFolder.Items.Count; i > 0; i--)
{
item = (Microsoft.Office.Interop.Outlook.MailItem)atFolder.Items[i];
string emailText = item.Body;
}
}
}

You need to use the Set keyword when setting a reference to an object. This line in your code creates a new email object (which is using your default setting of HTML email):
Set thisEmail = olApp.CreateItem(MailItem)
And then this line of code isn't using the Set keyword:
thisEmail = Item
So your variable isn't referncing the object you think but the new email.
Try using:
Set thisEmail = Item
Or, instead, replace both of these lines:
Set thisEmail = olApp.CreateItem(MailItem)
thisEmail = Item
With this line:
Set thisEmail = Item

Related

Outlook VBA Print to PDF

I would like to automatically print emails to PDF from outlook.
I haven't found a way to automate the print dialogue. There are a couple other threads dealing with this same issue in Outlook VBA, but no clear solution (I thought it would be simple!)
For example, I have a rule in outlook that automatically moves receipts to a specific folder. I'd like to automatically print these to PDF. I've tried to accomplish this by...
For Loop: Go through each unread item in the specified folder
Print: MailItem.Printout Method
Print Dialogue: Input path and filename and click OK. I haven't found any means of automating this process
Sub PrintReceipts()
'==============================================
'Declare variables, set namespace, define outlook folder (example names used below)
'==============================================
Dim olApp As Outlook.Application
Dim objNS As Outlook.NameSpace
Dim olFolder As Outlook.MAPIFolder
Dim msg As Outlook.MailItem
Dim Path As String
Dim Name As String
Set olApp = Outlook.Application
Set objNS = olApp.GetNamespace("MAPI")
Set olFldr = objNS.GetDefaultFolder(olFolderInbox).Folders("subfolder 1").Folders("subfolder 2")
'==============================================
'For each unread message save to Path with Name and mark as Read (path is just an example)
'==============================================
For Each msg In olFldr.Items
If msg.UnRead Then
Path = "C:\Users\User\Desktop\"
Name = msg.Subject & ".pdf"
msg.PrintOut
'=================================================
'Here is where I get lost.
'Print Dialogue opens. I have tried SendKeys but it does not work
'=================================================
msg.UnRead = False
End If
Next
End Sub
Alternative: I am wondering if I can do the following...
Save for Word: MailItem.SaveAs, to save the item as an .MHT
Open Word: Somehow open Word and apply ActiveDocument.ExportAsFixedFormat to export as PDF
Close Word and go back to Outlook
I hope someone may have an idea!
First of all, iterating over all items in the folder is not really a good idea in Outlook. Instead, you need to use the Find/FindNext or Restrict methods of the Items class. These methods allow getting items that correspond to your search criteria only. Read more about these methods in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder
To save the message body using the PDF file format there is no need to use the SaveAs method of the MailItem class. The WordEditor property of the Inspector class returns an instance of the Word Document class which represents the message body. You can call the ExportAsFixedFormat method of the Document class directly from Outlook avoiding any disk operations.
Dim objDoc As Object, objInspector As Object
Set objInspector = myItem.GetInspector
Set objDoc = objInspector.WordEditor
objDoc.ExportAsFixedFormat folderPath & fileName & ".pdf", 17
Set objInspector = Nothing
Set objDoc = Nothing
See Chapter 17: Working with Item Bodies for more information.

How to extract "type" - Email or Web from an Outlook Email using VBA?

I tried to extract the data from an Outlook email using VBA:
Sub DLPExtract()
Dim OutlookApp As Outlook.Application
Dim OutlookNamespace As Namespace
Dim Folder As MAPIFolder
Dim OutlookMail As MailItem
Dim i As Integer
Set OutlookApp = New Outlook.Application
Set OutlookNamespace = OutlookApp.GetNamespace("MAPI")
Set Folder = OutlookNamespace.GetDefaultFolder(olFolderInbox).Folders("gfdo#aviva.com\Inbox")
i = 1
For Each OutlookMail In Folder.Items
If InStr(OutlookMail.Subject, "Data Loss Prevention Report: GFDO DLP Daily Report Retrospective") > 0 And OutlookMail.ReceivedTime >= Range("From_date").Value Then
Range("Date").Offset(i, 0).Value = OutlookMail.Date
Range("Type").Offset(i, 0).Value = OutlookMail.
Range("Reference").Offset(i, 0).Value = OutlookMail.ID
Range("eMail_text").Offset(i, 0).Value = OutlookMail.Body
i = i + 1
End If
Next OutlookMail
Set Folder = Nothing
Set OutlookNamespace = Nothing
Set OutlookApp = Nothing
End Sub
I want to extract only IDs in the fourth column but first need to check if that is already in the respective column on Excel sheet. If yes, do nothing and If no, then take that ID out from this fourth column in email and paste it in respective column on Excel sheet.
I believe you are trying to check the email body format.
If that is the case then follow this link which talks about the "Bodyformat" property.
Range("Type").Offset(i, 0).Value = OutlookMail.BodyFormat
It is not clear what "Type" property you are interested in - the Outlook object model doesn't provide the Type property for their items. Instead, you may be interested in the MessageClass property which returns or sets a string representing the message class for the Outlook item. The MessageClass property links the item to the form on which it is based. When an item is selected, Outlook uses the message class to locate the form and expose its properties, such as Reply commands.
Also you may be interested in the BodyFormat property which returns or sets an OlBodyFormat constant indicating the format of the body text. The body text format determines the standard used to display the text of the message. Microsoft Outlook provides three body text format options: Plain Text, Rich Text (RTF), and HTML.
Finally, there is no need to iterate over all items in the folder and checking whether they correspond to the predefined condition:
For Each OutlookMail In Folder.Items
If InStr(OutlookMail.Subject, "Data Loss Prevention Report: GFDO DLP Daily Report Retrospective") > 0 And OutlookMail.ReceivedTime >= Range("From_date").Value Then
Instead, I'd recommend using the Find/FindNext or Restrict methods of the Items class. Read more about them in the following articles:
How To: Use Find and FindNext methods to retrieve Outlook mail items from a folder (C#, VB.NET)
How To: Use Restrict method to retrieve Outlook mail items from a folder

Outlook 2013 Userform with block Error when not "Popped Out"

I have a set of macros that have worked in Outlook 2003, 2007, and 2010. In fact, it still works in 2013 except in a specific case.
The macro brings up a dialog box whenever you try to send an email - to tag the subject line with key words. The problem is, if I just started Outlook, and I bring up a new email or reply - the default in Outlook 2013 is to bring it into the former "Reading Pane" rather than in a new window. If I do not hit "Pop Out" and I try to send, my macro crashes with this error:
"Run-time error '91' Object variable or with block variable not set"
I tried to check for loading the form first - but it seem ANY call to my userform, even userform.show, generates this error.
Oddly, if I remember to "Pop Out" my first email, it runs fine everytime after until I close/reopen Outlook. Even if I don't "Pop Out" other emails. It's only on the very first one that this occurs.
Here's the beginning of my Initialize Event:
Dim Tags() As String
Dim T As Variant
Dim PC As Variant
Dim Rent As String
Dim Child As String
Dim nsourcefile As Integer
Dim email As MailItem
Dim PD As Variant
Dim Proj As String
Dim Desc As String
'Set email = Application.ActiveInspector.CurrentItem
Set email = Application.ActiveExplorer.Selection.Item(1)
'Checks to see if a project number (that's not on the list) may be in the subject already
If Val(email.Subject) > 10000 Then
TagMsg.Height = tall
TagMsg.NewProjID = Format(Val(email.Subject), "00000")
TagMsg.NewProjDesc.SetFocus
Else
'Set height of form (prior to pressing "More" button
TagMsg.Height = short
End If
Noticed I changed Set email = Application.ActiveInspector.CurrentItem to Set email = Application.ActiveExplorer.Selection.Item(1). This seems to have fixed it, but the VBA help states "Do not make any assumptions about the Item method return type; your code should be able to handle multiple item types or a ConversationHeader object."
Note that the form is being invoked by the ItemSend event.
First off, putting that code into the Initialize event wasn't a good move. Needed to be moved into a click event where it was actually needed.
Then, I found the code I needed from two other posts, combined and shortened them.
Working with current open email
https://superuser.com/questions/795831/outlook-2013-vba-refer-to-editor-in-reading-pane
Final result
Dim oInspector As Inspector
Dim email As MailItem
Dim oexp As Explorer
Set oInspector = Application.ActiveInspector
Set oexp = Application.ActiveExplorer
If oInspector Is Nothing Then
'Set email = Application.ActiveExplorer.Selection.Item(1)
Set email = oexp.ActiveInlineResponse
If email Is Nothing Then
'MsgBox "No active inspector or inline response"
Exit Sub
End If
Else
Set email = oInspector.CurrentItem
End If 'oInspector is Nothing
If email.Sent Then
'MsgBox "This is not an editable email"
Else
'Checks to see if a project number (that's not on the list) may be in the subject already
If Val(email.Subject) > 10000 Then
TagMsg.Height = tall
TagMsg.NewProjID = Format(Val(email.Subject), "00000")
TagMsg.NewProjDesc.SetFocus
Else
'Set height of form (prior to pressing "More" button
TagMsg.Height = short
End If
End If 'email.sent
Note: This still relies on the fact that it is called by the ItemSend event and the active or current item will be the email I just pressed "send" on.
Thank you, retailcoder for your comments.

In Outlook 2013, can a macro open a new custom form?

In Outlook 2013 using Developer tab -> Design a Form, I created a custom form (with no mods yet) from the delivered Message form and placed it in my Personal Forms Library. Outlook tells me that the Message class is: IPM.Note.MyForm
I've created a macro and set up a new ribbon button to run the macro. I would like the macro to open a new instance of my custom form, but I can't get it working.
With the following code I can get the macro to open a new instance of the delivered Message Form:
Set newItem = Application.CreateItem(olMailItem)
newItem.Display
Set newItem = Nothing
I can't get it to open my custom form. I've tried the following as arguments to CreateItem: olMailItem.MyForm and IPM.Note.MyForm.
The macro editor intellisense has about 9 options for arguments to CreateItem, all of them appear to be delivered objects/forms, and it errors if one of these options aren't used.
I've done very little vba and office macros, is there some way to get this macro to open my custom form? Thanks.
See Items.Add http://msdn.microsoft.com/en-us/library/office/ff861028(v=office.15).aspx
Sub AddForm()
Dim myNamespace As outlook.NameSpace
Dim myItems As outlook.Items
Dim myFolder As outlook.Folder
Dim myItem As outlook.MailItem
Set myNamespace = Application.GetNamespace("MAPI")
Set myFolder = myNamespace.GetDefaultFolder(olFolderInbox)
Set myItems = myFolder.Items
Set myItem = myItems.Add("IPM.Note.MyForm")
End Sub

Outlook 2013 using VBA to Send Drafts

Good morning,
Using Outlook 2010 I compiled code to send all emails that were saved in a drafts folder of a given account. Now I've upgraded to Office 2013 I am getting an error... It is the .Send bit where it falls over and presents the error message:
"This method can't be used with an inline response mail item."
I am certain that there is a v simple method for sending drafts, but I have scoured the web and can't figure it as yet.
Public Sub SendDrafts()
Dim lDraftItem As Long
Dim myOutlook As Outlook.Application
Dim myNameSpace As Outlook.NameSpace
Dim myFolders As Outlook.Folders
Dim myDraftsFolder As Outlook.MAPIFolder
'Send all items in the "Drafts" folder that have a "To" address filled
'Setup Outlook
Set myOutlook = Outlook.Application
Set myNameSpace = myOutlook.GetNamespace("MAPI")
Set myFolders = myNameSpace.Folders
'Set Draft Folder. This will need modification based on where it's
Set myDraftsFolder = myFolders("accounts#credec.co.uk").Folders("Drafts")
'Loop through all Draft Items
For lDraftItem = myDraftsFolder.Items.count To 1 Step -1
'Check for "To" address and only send if "To" is filled in.
If Len(Trim(myDraftsFolder.Items.Item(lDraftItem).To)) ] 0 Then
'Send Item
myDraftsFolder.Items.Item(lDraftItem).Send
End If
Next lDraftItem
'Clean-up
Set myDraftsFolder = Nothing
Set myNameSpace = Nothing
Set myOutlook = Nothing
End Sub
I know this is old, but in case someone elses is looking for an answer:
"Active Inline Response" refers to a draft that is open in Outlook. So, when you are debugging, close the draft and flip back to a different message. Then see if your code will run.
I found that if you have clicked on the draft folder so that the Draft folder is active, then you get that error message, usually on the email in the folder that is highlighted.
SOLUTION: Click on any other folder, then run the code, should work, mine did!