Outlook olMailItem.Attachments.Add - does an attachment have to be on the filesystem? - email

Is there a way to add an attachment to an email you're sending out without the attachment being on the filesystem? From reading over the DOM (http://msdn.microsoft.com/en-us/library/bb175153%28office.12%29.aspx) it says that the attachment source can be either a file path or "an Outlook item that constitutes the attachment". I don't use VBA of the office DOMs all that much and I'm not sure what this is. In addition, all the examples I can find are only giving usage examples by using the filesystem path.
I'm calling this from Word for documents that create themselves by filling out some form fields, then print themselves. I'd like them to go out via E-mail as well, but have no need for a permanent copy of the created file. I do realize that I could save them off to a temp directory, attach the saved file, then delete the file once the mail object is sent. It seems a waste to do this though.
Is there an way I can have Word pass the in-memory Document object off to Outlook to attach to the email?

The answer is no, you cannot attach an in-memory document to an outlook mailitem without saving it to disk first.

Oesor,
I made an assumption that email should get sent automatically, so SendMail method is out.
I tried a couple of things to see whether it would work. In both cases code would be embedded in your Word file.
In pre-2007 Word you could use RoutingSlip functionality:
ActiveDocument.HasRoutingSlip = True 'Creates RoutingSlip
With ActiveDocument.RoutingSlip
.Subject = "email Subject"
.AddRecipient = "recipient#domain.com"
.Delivery = wdAllAtOnce
End With
ActiveDocument.Route
Apparently, this code doesn't work in Word 2007. So instead you can use SendForReview functionality:
ActiveDocument.SendForReview "recipient#domain.com", "email subject", False, True
Email gets sent right away (w/o the popup Outlook window), but a couple of caveats exist: the document has to have a corresponding file - it won't work for a new document that has never been saved, and the first time the recipient opens the attached document from e-mail there may be a popup message about starting the review process.
I hope this helps,
Max.

This should work for you, if the relevant attachment source is an inline item, such as an embedded image. I have not tried it with attached files that are not inline but it may work there too. The basic ideas are:
1) Treat the content of the Emails as a Word document because the native Editor for Outlook is Word.
2) Use Word's Copy and Paste to carry everything around with you via the Clipboard because it is a well tested approach. In the example, I have pasted the new section in at the start in a new Paragraph but you could obviously place it anywhere you want.
The strange thing is, though, (see the Debug.Print) that the Attachments Count in the To document does not change, even though the inline images are all where they should be and can be seen and Sent. Have Outlook fun! (The .olm files in the example are simply Outlook.MailItems that have been saved as Template files. They could just as easily be MailItems from an Outlook folder.)
Private Sub TestAttach()
'Places inline Attachment information into a different MailItem
Dim OlTo As Outlook.MailItem
Dim OlFrom As Outlook.MailItem
Dim DocTo As Word.Document
Dim DocFrom As Word.Document
Dim R As Word.Range
Dim R1 As Word.Range
Dim R2 As Word.Range
Dim lStart As Long
Dim lEnd As Long
Set OlFrom = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeOtherAttachments.oft")
Set OlTo = Outlook.CreateItemFromTemplate("C:\Temp\OlTemplateWithSomeAttachments.oft")
Debug.Print "From file starts with " & OlFrom.Attachments.Count & " attachments."
Debug.Print "To file starts with " & OlTo.Attachments.Count & " attachments."
Set DocFrom = OlFrom.GetInspector.WordEditor
Set DocTo = OlTo.GetInspector.WordEditor
OlFrom.Display
OlTo.Display
Set R2 = DocFrom.Content
With R2.Find 'Note: Find settings are 'sticky' and do not need to be repeated on the next find.
.Forward = True
.Wrap = wdFindStop 'Do not loop back to the start of the document
.Format = False
.MatchSoundsLike = False
.MatchAllWordForms = False
.Text = "Start flag for Section with Attachments" 'Find the start of the section to move
.Execute
lStart = R2.Start
.Text = "End flag for Section with Attachments" 'Find the end of the section to move
R2.Collapse wdCollapseEnd
.Execute
lEnd = R2.Start
End With
'OlFrom.Display
Set R2 = DocFrom.Range(lStart, lEnd)
'R2.Select
R2.Copy
Set R = DocTo.Range(1, 1)
R.InsertParagraphBefore
'Place the new inline attachments in the To MailItem
Set R = DocTo.Range(1, 1)
R.Paste
OlTo.Display
Debug.Print OlTo.Attachments.Count; "To file ends with " & OlTo.Attachments.Count & " attachments, the same as the original number but all the inline images show."
End Sub

Related

Quick and easy web automation/data scraping

I need to enter around 3 pieces of data into a website I don't own, press submit, and scrape a few pieces of data. I need to repeat this using different dates around 50 times. I've done this before for stuff I've reused a lot with Powershell. However, I was wondering if there was something simple like point and click I could use as if I spend hours on this I could have just as easily typed in the data and copied the results into a spreadsheet.
Any browser will do.
You can try to use IE automation using VBA to scrape the data from IE and paste it in to Excel Workbook.
Example:
Sub Automate_IE_Load_Page()
'This will load a webpage in IE
Dim i As Long
Dim URL As String
Dim IE As Object
Dim objElement As Object
Dim objCollection As Object
'Create InternetExplorer Object
Set IE = CreateObject("InternetExplorer.Application")
'Set IE.Visible = True to make IE visible, or False for IE to run in the background
IE.Visible = True
'Define URL
URL = "https://www.example.com"
'Navigate to URL
IE.Navigate URL
' Statusbar let's user know website is loading
Application.StatusBar = URL & " is loading. Please wait..."
' Wait while IE loading...
'IE ReadyState = 4 signifies the webpage has loaded (the first loop is set to avoid inadvertently skipping over the second loop)
Do While IE.ReadyState = 4: DoEvents: Loop 'Do While
Do Until IE.ReadyState = 4: DoEvents: Loop 'Do Until
'Webpage Loaded
Application.StatusBar = URL & " Loaded"
'Unload IE
Set IE = Nothing
Set objElement = Nothing
Set objCollection = Nothing
End Sub
References:
(1) Automate Internet Explorer (IE) Using VBA
(2) VBA Web Scraping with GetElementsByTagName
(3) IE (Internet Explorer) Automation using Excel VBA
Further, You can try to modify the code based on your requirement.

Generate new email from a custom outlook form

I have built a form that stores certain contact data on it. I want to include a couple of buttons/functions to keep the user in the form as much as possible versus switching between Outlook components (calendar, mail, etc.).
In this case the user can swap email addresses from separate ListBoxes and when they hit the button it will use the emails within one of them. Using VBS because I'm dealing with custom Outlook forms.
Sub GenerateButton_Click()
'Generates Email with all of the CCs
'Variables
Set FormPage = Item.GetInspector.ModifiedFormPages("Commands")
Set DoSend = FormPage.Controls("DoSendListBox")
mailList = ""
'Generate Email List
For x = 0 to (DoSend.ListCount - 1)
mailList = mailList & DoSend.List(x) & ";"
Next
'Compose Email
Set msg = Application.CreateItem(olMailItem)
msg.Subject = "Hello World!"
msg.To = mailList
End Sub
What Happens
- it compiles
- nothing happens on click
Research
- online forums usually in VBA
- relevant articles use outside connection rather than from within outlook
SOLVED
Note: Click on the "script" option and select the object item. From the new window you can navigate through the classes and from this I was able to find MailItem. You can see all of the methods/properties on the right hand pane.
It Turns out the correct syntax was:
Set msg = Application.CreateItem(MailItem)
msg.Display

FSTrigger triggers incorrectly in jenkins

I have set a rule in the outlook that
apply this rule after arrives with
"xyz"
in the subject and move it to the
"buildme"
folder "buildme" was created as a data file at
C:\Users\myid\AppData\Local\Microsoft\Outlook\builme.pst
In Jenkin under the project, I created build trigger as below:
[FSTrigger] - Monitor files File Path:
C:\Users\myid\AppData\Local\Microsoft\Outlook\builme.pst
Schedule: 55 * * * 1-5
I sent an email with "xyz" in the subject line.
the email then was moved to the "buildme" folder, thus the file C:\Users\myid\AppData\Local\Microsoft\Outlook\builme.pst gets update at, say at "3/24/2016 11:24 AM".
At 11:55 AM, the build was correctly triggered.
However, at 12:55 PM, another build was triggered again, unexpectedly, although there was no new email sent. this goes on for every hour.
What I did wrong?
Outlook probably touched the file in some way, modifying some timestamp which leads FSTrigger to start the build.
For the sake of robustness i suggest to not rely on monitoring the outlook folder file for changes, as it might change unexpectedly. Instead modify your rules to directly trigger the build job on the jenkins server.
I.e. pseudocodeish: IF subject CONTAINS keyword ACCESS jenkinsurl_that_starts_build
How to run a script based on outlook rules seems to be layed out here and
information on how to trigger a build via http request on a jenkins url is explained here
You could even extend it in the future to pass parameters from your email to your build, as these can be set via url access too. More info on this here, section Launching a build with parameters
I changed the rule to:
apply this rule after arrives
with "xyz" in the subject
run projetcs.ThisOutlookSession.WriteStringToFile
and the VBA script:
Sub WriteStringToFile1(MyMail As MailItem)
Const FILEPATH = "c:\buildtrigger\testtest.txt"
Dim strSubject As String
Dim strSender As String
Dim strText As String
Dim strID As String
Dim objMail As Outlook.MailItem
strID = MyMail.EntryID
Set objMail = Application.Session.GetItemFromID(strID)
strSubject = objMail.Subject
strSender = objMail.SenderName
Open FILEPATH For Output As 1
Print #1, "SET XYZ = " & strSubject & ";" & strSender & "--" & Now
Close #1
End Sub
this VBA script will write one line to testtest.txt.
In Jenkins, create a build trigger:
[FSTrigger] - Monitor folder
Path = c:\buildtrigger
Includes = testtest.txt
Exclude check lastModification date = true
Exclude check content = false
Exclude check fewer or more files = true
schedule: * * * * 1-5
Send email with xyz in the subject, a build will be successfully triggered, and no build triggered when no email was received.
As a side note, it looks like the timestamp of the file is modified by FSTrigger, not by Outlook or Windows.

How to display modified body after Outlook ItemLoad

I have code to parse emails and add "tel:" links to phone numbers, but the modified email body doesn't get shown in the Outlook Reading Pane until the user manually reloads it (view another email, come back to this one).
I've tried a few hacks like Inspector.Display, and ActiveExplorer.ClearSelection ActiveExplorer.AddToSelection, but I can't get consistent results (Display will open new Inspectors for some users, very undesirable).
I was also going to investigate hooking the event sooner. Somehow accessing the email body before Outlook renders it, to avoid the need to refresh. I'm very new to VSTO, so I don't know what event would have access to the MailItem but happen after a user selects it and before it is rendered. I have thought about only processing new mail, but that doesn't help when I roll out this addin, only going forward.
Here is my current ItemLoad sub:
Private Sub Application_ItemLoad(Item As Object)
Dim myObj As Outlook.MailItem
Dim ob As Object
ob = GetCurrentItem()
If TypeOf ob Is Outlook.MailItem Then
myObj = ob
Dim oldbody As String = myObj.Body
If myObj.HTMLBody.Length > 0 Then
myObj.HTMLBody = RegularExpressions.Regex.Replace(myObj.HTMLBody, "(?<!tel:)(?<![2-9\.])(?<!\>\ )[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", " ${p1}-${p2}-${p3}")
Else
myObj.Body = RegularExpressions.Regex.Replace(myObj.Body, "(?<!tel:)(?<![2-9\.])[+]?(1-)?(1)?[\(]?(?<p1>\d{3})[\)]?[\.\- ]?(?<p2>\d{3})[\.\- ]?(?<p3>\d{4})(?=[^\d])", "tel:${p1}.${p2}.${p3}")
End If
myObj.Save()
refreshCurrentMessage()
End If
End Sub
GetCurrentItem() just returns either objApp.ActiveExplorer.Selection.Item(1) or objApp.ActiveInspector.CurrentItem based on TypeName(objApp.ActiveWindow)
Outlook doesn't reflect changes made through the OOM immediately. You need to switch to another folder/email to get the item refreshed because there is no way to update the item on the fly.
You can use the CurrentFolder property of the Explorer class which allows to set a Folder object that represents the current folder displayed in the explorer. Thus, the view will be switched to another folder. Then you can set the CurrentFolder folder to initial folder.
Also I'd suggest releasing all underlying COM objects instantly. Use System.Runtime.InteropServices.Marshal.ReleaseComObject to release an Outlook object when you have finished using it. Then set a variable to Nothing in Visual Basic (null in C#) to release the reference to the object.

Microsoft Word 2010 default Save As File Type

If I open a .mhtml file in word, and click the "Save As" option, the default "Save As Type" is .mhtml. But I need the default "Save As Type" to be .doc/.docx. Is there any way to achieve this?
Create handler for saving event, in this event analyse type of the original document and show your own SaveAs dialog.
Details:
1. Handler
In normal template, create a class, for example clsSaveAs:
Public WithEvents appWord As Word.Application
Private Function IsMime(fileName As String)
Dim mimeTag As String
'open document
Open fileName For Input Access Read As #1
On Error Resume Next
'read beginning of the document
Input #1, mimeTag
On Error GoTo 0
Close #1
'MHTM file starts with "MIME" string
IsMime = Left(mimeTag, 4) = "MIME"
End Function
Private Sub appWord_DocumentBeforeSave _
(ByVal Doc As Document, _
SaveAsUI As Boolean, _
Cancel As Boolean)
'get extension
ar = Split(Doc.Name, ".")
ext = LCase(ar(UBound(ar)))
'what is the document MIME type?
If IsMime(Doc.FullName) Then
'my own saveas dialog
With Application.Dialogs(wdDialogFileSaveAs)
.Format = WdSaveFormat.wdFormatXMLDocument 'docx
.Show
End With
Cancel = True 'cancel saving process
Else
'normal saving
Cancel = False
End If
End Sub
2. Using handler
In normal template create a new module:
Dim csa As New clsSaveAs
Sub Register_Event_Save_As_Handler()
Set csa.appWord = Word.Application
End Sub
'autorun for any opening document
'Note: AutoOpen could be only one in normal template
Sub AutoOpen()
'could be run only for mht, mhtm documents, but never mind
Register_Event_Save_As_Handler
End Sub
You need have this code in the MHTML document. You could do it this way (but I haven't tried it):
create a new document in MS Word and save it as normal HTML file
create in this document a new class (clsSaveAs, see above) and in any module the second part of the code
see at saved document: there is a folder named _files and in this folder a file editdata.mso; this is the macro; you have to code it in a MHTML file (when you save it as MHTML file, you will see, how it is done)
in document you have to link this file, look in HTML document and find a row like
Note that all this job is non-standard and you are walking on thin ice, be careful.
Useful links:
Using Events with the Application Object
Application.DocumentBeforeSave Event
Quoting Office's help documents:
1-Click the File tab.
2-Click Options.
3-Click Save.
4-Under Save documents, click "Word 97-2003 Document (*.doc)" or "Word Document (*.docx)" in the Save files in this format.