Accessing inline and bar attachments

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/):

enter image description here

I have several questions on accessing them via VBA. They are cross-related, so it is worth posting them all together.

  1. 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?

  2. 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.

  3. 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?

  4. 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?

  5. 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

Answers (3)

There appears to be a large number of possible cases, depending on the combination of the following Enums (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.

  1. 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.

  2. 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 InlineShapes, 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).

  3. 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

David Zemens
David Zemens

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".

enter image description here

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.

enter image description here

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:

enter image description here

And the corresponding items in Attachments collection:

enter image description here

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.

enter image description here

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)"

enter image description here

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.

Email in HTML Format

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.

Email in RTF Format

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.

enter image description here

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

Dmitry Streblechenko
Dmitry Streblechenko

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

Related Questions