Reputation: 15561
An Outlook message can contain attachments (see fig., borrowed from http://blogs.mccombs.utexas.edu/the-most/2011/01/28/email-attachments-in-the-body-of-outlook-messages/):
A set of inline attachments (IA, left fig.), understood as any object besides text
A set of bar attachments (BA, right fig.)
I have several questions on accessing them via VBA. They are cross-related, so it is worth posting them all together.
Is there a comprehensive way to access IA?
In many cases, I found that the collection MailItem.Inspector.WordEditor.InlineShapes
(IS) is IS=IA. Is this always true?
Is there a comprehensive way to access BA?
In many cases, I found that the collection MailItem.attachments
(AT) is AT=IA+BA. But I have found exceptions: emails with nonempty IA and empty AT. Some use of AT might perhaps help, even with what I found.
Having a reference to an item in IA, is there any way of knowing if there is a corresponding item in AT (there may be not, according to #2), and if so identify it, IA->AT?
Reversing the question in #3:
Having a reference to an item in AT, is there any way of inquiring if it is an InlineShape
, and if so knowing which item in IA it corresponds to, AT->IA?
Is there any way of establishing the connections BA<->AT, similarly as in questions #3 and #4 for IA<->AT?
PS: I am using Outlook 2010, and according to http://www.msoutlook.info/question/261 and http://support.microsoft.com/kb/222330 that may bring about different results from Outlook 2007, etc.
Upvotes: 3
Views: 2395
Reputation: 15561
There appears to be a large number of possible cases, depending on the combination of the following Enum
s (and perhaps some others):
MailItem.BodyFormat
.
An OlBodyFormat
, for the body text.
Possible values:
olFormatHTML
,
olFormatPlain
,
olFormatRichText
,
olFormatUnspecified
InlineShape.Type
.
An WdInlineShapeType
, for each shape.
Possible values:
wdInlineShapeChart
,
wdInlineShapeDiagram
,
wdInlineShapeEmbeddedOLEObject
,
wdInlineShapeHorizontalLine
,
wdInlineShapeLinkedOLEObject
,
wdInlineShapeLinkedPicture
,
wdInlineShapeLinkedPictureHorizontalLine
,
wdInlineShapeLockedCanvas
,
wdInlineShapeOLEControlObject
,
wdInlineShapeOWSAnchor
,
wdInlineShapePicture
,
wdInlineShapePictureBullet
,
wdInlineShapePictureHorizontalLine
,
wdInlineShapeScriptAnchor
Attachment.Type
.
An OlAttachmentType
, for each attachment.
Possible values:
olByReference
,
olByValue
,
olEmbeddeditem
,
olOLE
I have examined some of these cases. The information provided is based on this limited information.
Is there a comprehensive way to access IA?
I would say YES, with MailItem.Inspector.WordEditor.InlineShapes
(IA).
Depending on the personal definition of "attachment", there might be some items in this collection that are not regarded as such. The access is comprehensive though: there may be more items than looked for, but none is left out.
Is there a comprehensive way to access BA?
I would say YES, with MailItem.Attachments
(AT).
I found an explanation for emails with nonempty IA and empty AT.
They have InlineShapes
of type wdInlineShapeLinkedPicture
. But even in this case, there are at least two possibilities, according to InlineShape.Hyperlink
:
1) There is no Hyperlink
.
This is the case when images are embedded, adding one item to IA per InlineShape
, and at least one item to AT (there may be many InlineShape
s, linked to a single Attachment
).
In this case, properties of LinkFormat
reveal useful info, including the name of the item in AT related the item in IA.
2) There is an Hyperlink
(case partly the culprit for the different findings).
This is the case when images are hyperlinked and not embedded. Images show up in the email, adding one item to IA per InlineShape
, but they do not add items to AT.
In this case, properties of Hyperlink
and LinkFormat
reveal useful info.
Case 1 appears to be the standard way of attaching images (up to Office 2013?). Using case 2 appears to require some tweaking (see this, this, this, and this).
Having a reference to an item in IA, is there any way of knowing if there is a corresponding item in AT (there may be not, according to #2), and if so identify it, IA->AT?
YES, at least in some cases.
When InlineShape.Type
=wdInlineShapeLinkedPicture
, if there is no InlineShape.Hyperlink
then there is an item in AT. It can be found by pairing InlineShape.LinkFormat.SourceName
(up to character @) with some Attachment.DisplayName
.
On the other hand, I could not do this when InlineShape.Type
=wdInlineShapeEmbeddedOLEObject
(e.g., an inline attached Excel workbook).
I will edit with additional findings.
PS1: http://www.msoutlook.info/ has very useful info. E.g., http://www.msoutlook.info/question/126, http://www.msoutlook.info/question/205, http://www.msoutlook.info/question/21, http://www.msoutlook.info/question/261.
PS2: perhaps using some developers interface provides some access beyond VBA. I.e., some YESes to teh questions, which for VBA are NOs. See http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.inlineshape.hyperlink%28v=office.14%29.aspx and http://msdn.microsoft.com/es-es/microsoft.office.interop.outlook.attachment_members.
Upvotes: 1
Reputation: 53623
Is there a comprehensive way to access IA? In many cases, I found that the collection MailItem.Inspector.WordEditor.InlineShapes (IS) is IS=IA. Is this always true?
NO. InlineShapes
may contain items that you would not likely consider "attachments" per se, for instance, a corporate logo embedded in your signature, etc. will appear as an InlineShape
which is not an "attachment" (although plain text email may include this as an attachment...)
Inline "Attachments" (inserted as object | create from file, for example) appear as Type = wdInlineShapePicture
and they do not have an OLEFormat
property (which surprises me...)
Is there a comprehensive way to access BA? In many cases, I found that the collection MailItem.attachments (AT) is AT=IA+BA. But I have found exceptions: emails with nonempty IA and empty AT. Some use of AT might perhaps help, even with what I found.
Per my second comment, above, the inline shape "attachments" appear as wdInlineShapePicture
but I believe they are treated as "attachments" in a roundabout way. Iterating the attachments collection you may notice items with generic names like "image002.png"
I believe these are essentially "linked" through a metadata file (which may also show as an attachment item "oledata.mso"
, to another generically named attachment (like "image001.wmz"
. None of these attachments, which are part of the .Attachments
collection, will appear as a "bar attachment".
The above screenshot comes from this email where I created a dictionary object to store the attachments by name (key). This email has 2 "bar attachments" and 2 "inline attachments". Note that the "bar attachments" are represented by their real "name", whereas the "inline attachments" are split in to the wmz/png files.
Having a reference to an item in IA, is there any way of knowing if there is a corresponding item in AT (there may be not, according to #2), and if so identify it, IA->AT?
I don't believe so. The "oledata.mso"
file is not something that you can read or parse, as far as I'm aware, and this seems to be the only thing that connects the wmz/png files.
Reversing the question in #3: Having a reference to an item in AT, is there any way of inquiring if it is an InlineShape, and if so knowing which item in IA it corresponds to, AT->IA?
No. As far as I can tell there is no "correspondence" between the inline shapes and the attachments. Even if you were to insert the same file as both an Attachment and an Object/InlineShape, these are separate and distinct items.
Here is another example email:
And the corresponding items in Attachments
collection:
Is there any way of establishing the connections BA<->AT
Based on my research, possibly.
You can examine the HTML source of the email body, and parse out the inline shapes. Below you can see that the png/wmz from the last screenshot are present.
If you store ALL attachments in a dictionary object (assume m
is a MailItem
object):
Dim dictAttachments as Object
Set dictAttachments = CreateOBject("Scripting.Dictionary")
Dim attch As Attachments
Dim att as Attachment
Dim s as Integer
s = 0
Set attch = m.Attachments
For Each att In attch
Set dictAttachments(att.DisplayName) = att
s = s + 1
Next
Then if you have identified the png/wmz from the HTML Source, you can remove them from the dictAttachments
object using the .Remove
method.
dictAttachments.Remove("image001.wmz") 'etc.
Then, the dictionary object will contain only the "Bar Attachments".
The trouble with this (I have tried to do this now...) is that I'm not able to find a way to parse the HTML (using an HTMLFile object) to get the PNG -- it's in a part of the HTMLBody which is essentially commented out/conditionally rendered and so it does not respond to getElementsByTagName
method, etc.
While I would always prefer to work with the HTML/XML objects (it's usually a bad idea to try and parse HTML with normal string functions), this may be a necessary exception. You could do something simply like:
Dim itm as Variant
For each itm in dictAttachments.Keys()
If Instr(1, m.HtmlBody, "cid:" & itm & "@") > 0 Then
dictAttachments.Remove(itm)
End If
Next
'Ignore the oledata.mso, too:
If dictAttachments.Exists("oledata.mso") Then dictAttachments.Remove("oledata.mso")
Now the dictionary ONLY contains the "Bar Attachment(s)"
Is there any way of establishing the connections BA<->AT, similarly as in questions #3 and #4 for IA<->AT?
I don't think there is any connection that you could possibly make. They are separate items and treated separately. Even when they are the same file, there's just no information in the InlineShape
which would be useful for attempting to do this.
All of the above is for HTML format emails. In the case of HTML mail, the problem is not so much in the Attachments
but rather a limitation of the InlineShapes
and InlineShape
objects in that context.
In the case of RTF mailbody, rather than being split png/wmz files, the attchment does appear by name in the Attachments
collection, the other item 2 is actually a JPG as part of my signature.
HOWEVER, you will be able to observe that attachments in RTF have an OLEFormat
property which and more specifically they have an OLEFormat.ClassType = "Outlook.FileAttach"
So you may be able to do something simple, like:
Select case m.BodyFormat
Case olFormatRichText
For each shp in doc.InlineShapes
If Not shp.OleFormat Is Nothing Then
If shp.OLEFormat.ClassType = "Outlook.FileAttach" Then
'Do something here
End If
End If
Next
Case olFormatHTML
' Do something like the example above for HTML
Case olFormatPlainText
' Do something different, if needed, for plain text emails
Case olFormatUnspecified
' not sure what to do here and it's not really my problem to figure out...
End Select
Now, given it as part of InlineShapes
, I don't think you can "connect" it to a specific item in the Attachments
collection. You will be better suited to simply iterate the Attachments
collection.
Note: In my example (a very simple one) the two dictionaries/collections appear to be indexed the same way, but I would caution against assuming this will always be the case. So while you may be able to delete by index position, I am not sure that is a safe/reliable assumption.
It should not be possible in RTF format to have an empty Attachments
collection and an "Inline Attachment".
Upvotes: 4
Reputation: 66215
The first picture is for an email in the RTF format. The attachment icons are rendered inside the message body. The second screenshot is for a plain or HTNML message. In both cases the attachments can be accessed through the MailItem.Attachments collection.
Upvotes: 0