Accessing inline and bar attachments - vba

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.

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

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

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.

Related

Microsoft Word VBA - IF & THEN Find word and print in another sheet

I am a bit of novice when it comes to VBA (mostly navigate through the code by Recording actions and then altering it accordingly to what I need).
My current problem is the following:
I am going to compile hundreds of word Docs that contain email addresses from clients that I need. In order to make this as easy as possible, I would like to have some code that finds their emails AND additional information that surrounds the email addresses (Name, Location,Job Title,and [possibly] Phone Number) and then copies and pastes the mentioned info to another designated document. The documents and the abovementioned info are formatted as such :
FirstName LastName
Location
Job title # Company
email address - phone number
Now, I believe this will include and IF/THEN statement since not all clients have their email addresses in the documents.
So, IF there is an email address THEN copy it along with the 3 lines above and the phone number that is separated by "a space" "-" "a space"AND paste it on another sheet. IF there is no email address, then keep going.
This query code will probably include a FIND that needs to have a "#" and ".com" attached to the same string. This will be needed since the document also includes other text that has ".com" and "#" but not together.
This sounds harder than what it really is, but again I'm a novice so not completely sure. Feel free to ask any additional questions!
This is an extremely broad question, but I suggest you do the following:
Use a Scripting.FileSystemObject to iterate through files in a folder, looking for Word documents
Open the document using the Word Automation object model
Application object
Application.Documents property, and Documents collection
Documents.Add method, and the Document object, to create the destination document
Documents.Open method, to open existing documents
Find emails within the document (via the Document.Content property, and the Range object)
Range.Find property and the Find object
If the Find.Execute2007 method returns true, then:
Extend the range to the previous 3 paragraphs
Range.MoveStart method
Copy and paste the range to the destination document
You can write only the text to the destination document — Range.Text property, and the Range.Insert-* methods
Or, you can use the clipboard Range.Copy and Range.Paste
Or, you can export to an external file (Range.ExportFragment) and later import from the external file (Range.ImportFragment)

How can i set the name of a textbox in publisher?

I want to set the name of the text box so it can be easily accessed by code.
e.g
I am looking for an editing field similar to this
Thanks
There's a properties Window that can be accessed for each of the controls on the UI. There you may rename the controls. (Since you do not seem to have a VBA code yet and you want to rename the control from UI)
The other alternative. Record a macro, do some changes to the textbox (e.g. resize, change text etc). Then check the programme assigned default name of the textbox from the VBA editor. As you said, you can access the control via this default name and utilizing your VBA code (as you said), rename the textbox.
If you really want to be editing a worksheet object in Publisher you will have to get the OLEobject of the Shape and interpret it as an Excel.Application.
If you are just looking for a placeholder solution for Publisher documents, you could simply create a textbox that contains a certain string, then loop through all pages, all shapes on each page where HasTextFrame = msoTrue, and compare shape.TextFrame.TextRange.Text to your placeholder string. If it's the one you're after, you can do anything you want with the shape in question.
Sorry for the vague answer, but your images don't work anymore.
Edit: you can work with Shape.Name for your comparison (you mentioned this property in a comment), but I have no idea how you'd set the value from the interface, without using VBA, in the first place, so if you're making templates the approach I outlined above might be easier for users (see https://msdn.microsoft.com/EN-US/library/office/ff939233.aspx for Shape.Name). There is also a .Name property for page objects (https://msdn.microsoft.com/EN-US/library/office/ff940382.aspx), so you should be able to do something like ActiveDocument.Pages("page_name").Shapes("shape_name").TextRange.Text = "your content" once you've figured out how to actually set the name values
Edit 2:
You can also try to use search and replace as per Replacing Text in Microsoft Publisher Using Powershell if you don't need to do anything advanced beyond placing some text
Edit 3: Given the title of your question, unless you can figure something out with Publisher's interface, you can set the .Name property of the selected text box (or other shape) with dim shape = Selection.ShapeRange.TextFrame.Parent and shape.Name = "your_name". You can set the name of the selected page with ActiveDocument.ActiveView.ActivePage.Name="your_name". (Create a VBA macro that prompts you for names and you should be good to go)

Printing custom ranges or items in Word 2010 using VBA

I am fairly new to VBA (Word 2010) and I'm unsure if something I'd like to do is even possible in the way that I want to do it, or if I need to investigate completely different avenues. I want to be able to print ranges (or items) that are not currently enumerated as part of either wdPrintOutRange or wdPrintOutItem. Is it possible to define a member of a wd enumeration?
As an example, I'd like to be able to print comments by a particular user. wdPrintComments is a member of the wdPrintOutItem enumeration, but, I only want comments that have an Initial value of JQC. Can I define a wdPrintCommentsJQC constant? My code is reasonably simple; I have a userform that lets the user pick some settings (comments by user, endnotes only, etc.) and a Run button whose Click event should generate a PrintOut method with the proper attributes. Am I on the wrong track?
(If it matters, the Initial values will be known to me as I write the code. I have a discrete list.)
No, it's not possible to add a constant to a predefined enumeration type.
However, one possible way to do this would be to build a string of page numbers which contain the items you wish to print, open the print dialog in the "dialogs" collection, and set it to print a specified range, andinsert the string containing the list of pages (separate them with commas). Finally, execute the .show method of the print dialog to show it to the user and give them the opportunity to set any other items and click the "ok" button. I've done something very similar when I needed to print a specific chapter of a long document, and so I had to specify the "from" section and page and the "to" section and page for the user. Below I just show how to specify a list of pages instead of the ".form" and "to" I was using:
With Dialogs(wdDialogFilePrint)
.Range = wdPrintRangeOfPages
.Pages = "3,5,7-11"
.show
end with
I'm not sure how you want to print the comments (or other elements), but you could create another document and insert what you want to print on this document.
According to what you want, you could insert them as they were (comments, footnotes, etc) or as plain text, or any other format.

All properties available from a VSTO Outlook AdvancedSearch

I'm using VSTO with Outlook 2007, in c#. I can execute an Outlook.Application.AdvancedSearch(), and get a table. I want to select the columns to access from the table using Outlook.Table.Columns.Add(). I can't seem to find a complete list of property names that I can pass to Add() (I'm only interested in mail items). I've guessed a few of the obvious ones (ReceivedTime, SenderEmailAddress, To, Subject, Body, EntryID). I was hoping to be able to get the (plain text) body of each email, but trying to add the property Body doesn't seem to work. Is it impossible to get Body as a column, or is it just under a different name?
The page Unsupported Properties in a Table Object or Table Filter says that Body should work for the first 255 bytes. That didn't work for me, but even if it did, that's not what I want. Thus, I get the EntryID property, then use mapiNameSpace.GetItemFromID(entryId, Type.Missing) to get the MailItem object, and get the (entire) plain-text body from MailItem.Body.

Using VBA in MS Word 2007 to define page elements?

I'd like to be able to create a page element which I can feed text and it will form itself into the preferred layout. For instance:
{MACRO DocumentIntro("Introduction to Business Studies", "FP015", "Teachers' Guide")}
with that as a field, the output should be a line, the first two strings a certain size and font, centred, another line and then the third string fonted, sized and centred.
I know that's sort of TeX-like and perhaps beyond the scope of VBA, but if anyone's got any idea how it might be possible, please tell!
EDIT:
Ok, if I put the required information into Keyword, as part of the document properties, with some kind of unique separator, then that gets that info in, and the info will be unique to each document. Next one puts a bookmark where the stuff is going to be displayed. Then one creates an AutoOpen macro that goes to that bookmark, pulls the relevants out of the keywords, and forms the text appropriately into the bookmark's .Selection.
Is that feasible?
You're certainly on the right track here for a coding solution. However, there is a simpler way with no code - this is the type of scenario that Content Controls in Word 2007 were built for and with Fields/Properties, you can bind to content controls (CC). These CC can hold styles (like centered, bold, etc.). No VBA required.
The very easiest thing to do is to pick 3 built-in document properties that you will always want these to be. For example, "Title" could be your first string, "Subject" your second string and "Keywords" your third. Then, just go to the Insert ribbon, Quick Parts, Document Properties and insert, place and format those how you like. Then go to Word's start button (the orb thingy) and then under Prepare choose Properties. Here you can type, for example "Introduction to Business Studies", into the Title box and then just deselect it somehow (like click in another box). The Content Control for Title will be filled in automatically with your text.
If you want to use this for multiple files, just create this file as a .dotx (after CC insertion/placement/formatting and before updating the Document Properties' text). Then every time all you'll have to do is set these three properties with each new file.
Well, yes, it did turn out to be feasible.
Sub autoopen()
Dim sKeywords As String
sKeywords = ActiveDocument.BuiltInDocumentProperties(4)
ActiveDocument.Bookmarks("foo").Select
Selection.Text = sKeywords
End Sub
Okay, I have some filling out to do, but at least the guts of it are there.