How to edit existing MailItem using WordEditor in Outlook VBA? - vba

So I want to edit my received mails afterwards to add links. If the emails have been received as plain text or HTML I have just edited the appropriate msg.Body or msg.HTMLBody. However, for Rich Text, editing RTFBody directly seems both rather complicated and keeps crashing my Outlook.
I can edit the HTMLBody of Rich Text mails, but it then converts the whole mail to HTML which makes it change appearance and can't handle embedded attachments well.
MSDN talks about MailItem.GetInspector, which returns WordEditor and allows a much easier way of editing documents. Problem is, all examples I've found are of new mails being created, not existing being edited. The following code:
Set objInsp = itm.GetInspector
Set objDoc = objInsp.WordEditor
objDoc.Characters(1).InsertBefore "string"
Generates the following error: Run-time error '4605', This method or property is not available because the document is locked for editing.
Does anyone know a way to unlock the mailitem to allow for editing, alternatively, a way to edit RTFBody that doesn't go belly up? I've tried to set the objDoc.ProtectionType to something that allows writing, but it also says I cannot change the document.

I was facing exactly the same issue (Outlook VBA: Replace inline object with text).
As posted in my comment (soon to be edited to a more polished version, after further testing), you have to use objDoc.UnProtect prior to modifying contents.
I have actually used
'On Error Resume Next ' This will prevent the error message... risky!
Dim odProt As Integer
odProt = objDoc.ProtectionType
If (odProt <> wdNoProtection) Then
Debug.Print "Document is protected with type " & odProt & ", unprotecting temporarily"
objDoc.UnProtect
End If
' ... Write your code
If (odProt <> wdNoProtection) Then
Debug.Print "Restoring protection"
objDoc.Protect (odProt)
End If
objMsg.Display
objMsg.Save
I am not sure if the last two lines are the best, but it worked for me.
Note that having On Error Resume Next will prevent the error message, and you may see that none of your editions has any effect without apparent reason.

Related

How to automate saving attachments AND memo writing inside the messages

I want to automate these actions, on an open message (either received or sent):
save all the attachments in a folder through a popup letting the user to select the destination folder
This command already exist inside the Action > Other Actions submenu, but the problem is that no trace remains visible in the message about the former presence of attachments, so - as in Lotus Notes - I would like to:
edit the message to introduce some text at the beginning, something such a message like "Attachment removed on " and, even better, the path where the attachments have been saved.
I tried to start understanding Outlook VBA but I feel rather uncomfortable with it.
You have asked far too much in one question to hope for a complete answer. This site is for programmers to help one another develop. Your question should include some faulty code so a more experienced programmer can explain where you have gone wrong.
I understand you feeling uncomfortable with Outlook VBA. When I started, I bought a highly recommended book and found it very unhelpful. I learnt through experimentation. I still experiment when I want to understand something new. I also read through the Outlook questions and answers here. There are some extraordinarily knowledgeable people answering questions. I find that continually adding to my understanding of what can be achieved with Outlook helpful even if I do not expect to ever use some of the more exotic functionality.
You need to break your complete task into small sub-tasks. If your sub-task is small enough you can probably find something relevant if you search. My breakdown of your task is:
Allow user to identify Outlook folder from which attachments are to be saved.
Allow user to identify disc folder to which attachments are to be saved.
Read down Outlook folder looking for emails with user attachments.
For each email with user attachments:
4(a) save attachments to disc folder,
4(b) delete attachments from email
4(c) edit email body with details of deleted attachments.
I will provide some code to get you started with sub-tasks 1 and 3. You should be able to find help on sub-task 2 without too much difficulty. I will include some guidance on sub-tasks 4(a), 4(b) and 4(c).
I would have thought the easiest way for the user to identify the Outlook folder would be for the user to open that folder and then start the macro. That is the approach I have taken. Please copy this code to a module, open an appropriate Outlook folder and run the macro. I believe I have included enough comments to explain what the macro is doing but ask questions if necessary:
Option Explicit
Sub DemoSelectFolderFromEmail()
Dim Exp As Explorer
' VBA has two types of Folder: Outlook folders and disc folders.
' Since you will need access to disc folders, it is important to be clear
' which type you mean.
Dim FldrSrc As Outlook.Folder
Dim InxA As Long
Dim InxF As Long
Dim ItemCrnt As MailItem
' Get collection of emails selected by user.
Set Exp = Outlook.Application.ActiveExplorer
' Check that an email has been selected.
If Exp.Selection.Count = 0 Then
Call MsgBox("Please select an email then try again", vbOKOnly)
Exit Sub
ElseIf Exp.Selection.Item(1).Class <> olMail Then
Call MsgBox("Please select an email then try again", vbOKOnly)
Exit Sub
End If
' The parent of a selected email is the Outlook folder that contains it.
' In my view, this is the easiest way for the user to identify the source
' folder.
Set FldrSrc = Exp.Selection.Item(1).Parent
Debug.Print FldrSrc.Name
' FldrSrc.Items is a collection of the items in FldrSrc.
' Probably every itemn is a MailItem but I check to be sure.
' ReceivedTime and Subject are just two of the properties that you can access.
' If there are any attachments, I display their names.
' Note that for VBA, signatures and images count as attachments. You will need
' to add code to idetify these attachments if you do not want to save them.
For InxF = FldrSrc.Items.Count To 1 Step -1
With FldrSrc.Items(InxF)
If .Class = olMail Then
Debug.Print .ReceivedTime & " " & .Subject
If .Attachments.Count > 0 Then
For InxA = 1 To .Attachments.Count
With .Attachments(InxA)
Debug.Print " " & InxA & " " & .DisplayName
End With
Next
End If
End If
End With
Next
End Sub
SaveAsFile is the method that writes an attachment to a disc folder. Note that SaveAsFile will overwrite any existing file with the same name. If the same DisplayName is used for different attachments, you will need to have some system of making names unique.
You will need to edit the emails to delete the attachments and then save the edited emails.
An email can have a text body and or an Html body and or a RTF body. I have never seen a RTF body so you can probably ignore them. If an email has both a text and an Html body, the user sees the Html body. It is rare these days for an email not to have an Html body. You can probably get away with just adding a string at the start of the Html body but you will need to experiment to ensure the appearance is satisfactory. I would probably prefer to insert, at the beginning of the body section, a complete Html string controlling background colour, font colour, font size and font name so that I the appearance of the insert text was always the same.

Outlook ReportItem.Body returning messed up encoding

If certain users automate the Outlook Client to view bounce backs/ReportItems in a shared inbox, rather than returning the clear text of the message as indicated by the documentation there is a unicode string that has been parsed as a UTF-8 string - so it looks like Chinese.
I can get past that with some code, but the additional issue is that this change occurs in Outlook as well for all users with access to that inbox. The message itself as viewed in Outlook appears as Chinese characters - the original unicode html parsed as UTF-8.
We are using the normal methods to access the report item:
For Counter as Integer = Inbox.Items.Count To 1 Step -1
Dim Report As Outlook.ReportItem = Inbox.Items(Counter)
Dim Body As String = Report.Body
The last line is where we get the garbled text In VBA it attempts to parse it as ASCII and returns a large block of "?". In .Net it returns the value parsed as UTF-8 and we get the characters that appear Chinese. In either case the report item in the inbox begins displaying as Chinese characters and continues to do so for all users of that inbox.
I just had this happen to my VBA function in Outlook that processes email bounce backs for orders and marks those orders as requiring attention. The original email in outlook looks fine but when I attempt to process it, the characters change to Chinese and Report.Body just shows question marks.
I found using StrConv to convert to Unicode could get me the correct body contents for processing.
Dim strBody as String
strBody = StrConv(Report.Body, vbUnicode)
Are you sure that the Inbox.Items(Counter) call returns an instance of the ReportItem class? Did you have a chance to check out the MessageClass property?
Most probably you try to cast an instance of the MailItem class to the ReportItem class. Is that the case?
Also I'd suggest using any low-level property viewer such as MFCMAPI or OutlookSpy for observing properties at runtime. Do you see "chinese" charactere there?
I came across this issue and I've written a function that solves the issue for me. I thought I'd share it here in case it's of use to anyone else.
Private Sub Example()
Dim Item As Object
Set Item = Application.ActiveExplorer.Selection(1)
Debug.Print ItemBody(Item)
End Sub
Public Function ItemBody(Item As Variant) As String
On Error Resume Next
If TypeName(Item) = "ReportItem" Then
With Item.GetInspector
ItemBody = .WordEditor.Content
.Close 1
End With
Else
ItemBody = Item.Body
End If
End Function
Yes, there is a problem with ReportItem.Body property in the Outlook Object Model (present in Outlook 2013 and 2016) - you can see it in OutlookSpy (I am its author): select an NDR message, click Item button, select the Body property - it will be garbled. Worse than that, once the report item is touched with OOM, Outlook will display the same junk in the preview pane.
The report text is stored in various MAPI recipient properties (click IMessage button in OutlookSpy and go to the GetRecipientTable tab). The problem is the ReportItem object does not expose the Recipients collection. The workaround is to either use Extended MAPI (C++ or Delphi) or Redemption (any language - I am also its author) - its ReportItem.ReportText property does not have this problem:
set oItem = Application.ActiveExplorer.Selection(1)
set oSession = CreateObject("Redemption.RDOSession")
oSession.MAPIOBJECT = Application.Session.MAPIOBJECT
set rItem = oSession.GetRDOObjectFromOutlookObject(oItem)
MsgBox rItem.ReportText

Send keypress to error messages on opening Word document

I am having some problems on sending key strokes to an error message while opening a document.
The document I am trying to open in a Word for Dos 5.5 document that is missing the style sheet, you can manually click ignore and the file opens, but I have 1000+ more file to do this with so i want to automate this.
I am opening this document up in either Office XP, 2003 or 2010 but i cant get the keystrokes to affect the messages.
The code I have been attempting is as follows:
Dim objWord As New Word.Application
Dim objDoc As New Word.Document
objDoc = objWord.Documents.Open(TempDir + ("\\" + fileInf.Name))
System.Threading.Thread.Sleep(5000)
SendKeys.Send("{Enter}")
System.Threading.Thread.Sleep(5000)
SendKeys.Send("{&I}")
System.Threading.Thread.Sleep(5000)
objWord.WindowState = Word.WdWindowState.wdWindowStateNormal
objWord.Visible = True
I have used the visible = true just to see what i going on, but the messages just sit there.
I think sending keyPress is not the best solution for your problem, and this is something you should avoid if you can.
You could instead try to hide Word messages. Possible ways:
Try to disable messages using Application.DisplayAlerts property before you open the file. So it would be:
objWord.DisplayAlerts = Word.WdAlertLevel.wdAlertsNone
Important note from MSDN :
If you set this property to wdAlertsNone or wdAlertsMessageBox,
Microsoft Word doesn't set it back to wdAlertsAll when execution
stops. You should write your code in such a way that it always sets
the DisplayAlerts property back to wdAlertsAll when it stops running.
Try to disable messages by setting other parameters of the Documents.Open method. I am thinking to the Format paramater that you could try to set to wdOpenFormatAllWord to see if it disables messages.
wdOpenFormatAllWord: A Microsoft Word format that is backward compatible with earlier versions of Microsoft Word.
When you write SendKeys.Send("{Enter}"), I guess you have System.Windows.Forms namespace imported so it is equal to System.Windows.Forms.SendKeys.Send("{Enter}").
In my first answer I have suggested, If you really need to use the SendKeys method, to use the one from Excel.Application. (because I was needing coffee..., I was totally focused on Excel although the question was about Word).
Unfortunately, the method is present In Excel but there is NO SendKeys Method in Word.Application Object.

How do I make Outlook purge a folder automatically when anything arrives in it?

I hope it's okay to ask this kind of question. Attempting to write the code myself is completely beyond me at the moment.
I need a macro for Outlook 2007 that will permanently delete all content of the Sent Items folder whenever anything arrives in it. Is it possible? How do I set everything up so that the user doesn't ever have to click anything to run it?
I know I'm asking for a fish, and I'm embarrassed, but I really need the thing...
edit:
I've pasted this into the VBA editor, into a new module:
Public Sub EmptySentEmailFolder()
Dim outApp As Outlook.Application
Dim sentFolder As Outlook.MAPIFolder
Dim item As Object
Dim entryID As String
Set outApp = CreateObject("outlook.application")
Set sentFolder = outApp.GetNamespace("MAPI").GetDefaultFolder(olFolderSentMail)
For i = sentFolder.Items.Count To 1 Step -1
sentFolder.Items(i).Delete '' Delete from mail folder
Next
Set item = Nothing
Set sentFolder = Nothing
Set outApp = Nothing
End Sub
It's just a slightly modified version of a piece of code I found somewhere on this site deleting Deleted Items. It does delete the Sent Items folder when I run it. Could you please help me modify it in such a way that it deletes Sent Items whenever anything appears in the folder, and in such a way that the user doesn't have to click anything to run it? I need it to be a completely automated process.
edit 2: Please if you think there's a better tool to achieve this than VBA, don't hesitate to edit the tags and comment.
edit 3: I did something that works sometimes, but sometimes it doesn't. And it's ridiculously complicated. I set a rule that ccs every sent email with an attachment to me. Another rule runs the following code, when an email from me arrives.
Sub Del(item As Outlook.MailItem)
Call EmptySentEmailFolder
End Sub
The thing has three behaviors, and I haven't been able to determine what triggers which behavior. Sometimes the thing does purge the Sent Items folder. Sometimes it does nothing. Sometimes the second rule gives the "operation failed" error message.
The idea of acting whenever something comes from my address is non-optimal for reasons that I'll omit for the sake of brevity. I tried to replace it with reports. I made a rule that sends a delivery report whenever I send an email. Then another rule runs the code upon receipt of the report. However, this has just one behavior: it never does anything.
Both ideas are so complicated that anything could go wrong really, and I'm having trouble debugging them. Both are non-optimal solutions too.
Would this be an acceptable solution? Sorry its late but my copy of Outlook was broken.
When you enter the Outlook VB Editor, the Project Explorer will be on the left. Click Ctrl+R if it isn't. It will look something like this:
+ Project1 (VbaProject.OTM)
or
- Project1 (VbaProject.OTM)
+ Microsoft Office Outlook Objects
+ Forms
+ Modules
"Forms" will be missing if you do not have any user forms. It is possible "Modules" is expanded. Click +s as necessary to get "Microsoft Office Outlook Objects" expanded:
- Project1 (VbaProject.OTM)
- Microsoft Office Outlook Objects
ThisOutlookSession
+ Forms
+ Modules
Click ThisOutlookSession. The module area will turn white unless you have already used this code area. This area is like a module but have additional privileges. Copy this code to that area:
Private Sub Application_MAPILogonComplete()
' This event routine is called automatically when a user has completed log in.
Dim sentFolder As Outlook.MAPIFolder
Dim entryID As String
Dim i As Long
Set sentFolder = CreateObject("Outlook.Application"). _
GetNamespace("MAPI").GetDefaultFolder(olFolderSentMail)
For i = sentFolder.Items.Count To 1 Step -1
sentFolder.Items(i).Delete ' Move to Deleted Items
Next
Set sentFolder = Nothing
End Sub
I have taken your code, tidied it up a little and placed it within an event routine. An event routine is automatically called when the appropriate event occurs. This routine is called when the user has completed their log in. This is not what you requested but it might be an acceptable compromise.
Suggestion 2
I have not tried an ItemAdd event routine on the Sent Items folder before although I have used it with the Inbox. According to my limited testing, deleting the sent item does not interfere with the sending.
This code belongs in "ThisOutlookSession".
Option Explicit
Public WithEvents MyNewItems As Outlook.Items
Private Sub Application_MAPILogonComplete()
Dim NS As NameSpace
Set NS = CreateObject("Outlook.Application").GetNamespace("MAPI")
With NS
Set MyNewItems = NS.GetDefaultFolder(olFolderSentMail).Items
End With
End Sub
Private Sub myNewItems_ItemAdd(ByVal Item As Object)
Debug.Print "--------------------"
Debug.Print "Item added to Sent folder"
Debug.Print "Subject: " & Item.Subject
Item.Delete ' Move to Deleted Items
Debug.Print "Moved to Deleted Items"
End Sub
The Debug.Print statements show you have limited access to the sent item. If you try to access more sensitive properties, you will trigger a warning to the user that a macro is assessing emails.

Trying to convert a received outlook e-mail to rich text, and set font

I have a user who receives a system generated e-mail that is formatted with a font such that when it is rendered in outlook the columns of text do not line up. I know what font it needs, and it's installed on the user's PC. In groupwise, he was able to simply change the font on the message after he received it and all was well. However, it seems that there is no functionality for that in Outlook (which we recently moved to).
The fix that was suggested on the Microsoft forum was to use a rule when the message is received to run a VBA script, and convert the message to RTF, then set the font.
This is my first foray into VBA, though I've done a couple small vb scripts in the past, so I'm probably way off base with the start, but here's what I tried.
Sub TestMessageRule(Message As Outlook.MailItem)
Set Message.BodyFormat = 3
End Sub
I got an error with this, "Invalid use of a property", so I'm guessing you can't change it that way. I got 1=Plain text, 2=HTML, and 3=Rich Text by sending myself test e-mails with this code:
Sub TestMessageRule(Message As Outlook.MailItem)
MsgBox Message.BodyFormat
End Sub
So, the question is, how can I change the BodyFormat property properly, or is there some better way I should be going about this?
Then if you're feeling particularly nice, maybe you could point me towards how to change the font of the body text.
Thanks,
Bryan
Here's what finally worked for me:
Sub TestMessageRule(Message As Outlook.MailItem)
Dim BodyString As String
Message.BodyFormat = olFormatHTML
BodyString = Replace(Message.HTMLBody, "<FONT SIZE=2>", "<FONT SIZE=3 FACE=""COMIC SANS MS"">")
Message.HTMLBody = BodyString
Message.Save
End Sub
Obviously Comic Sans MS is not my desired end font, but it stands out great for testing. Thanks again for the help!
Do not use "set" when setting a scalar property:
Message.BodyFormat = 3
That being said, simply read the plain text Body property and use it to construct the suitably formated HTML with the font specified, then set the HTMLBody property.