How to move all messages in a conversation? - vba

I need to know how to move all of the messages in a conversation at once.
My macro currently reads
Sub Archive()
Set ArchiveFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Parent.Folders("Archive")
For Each Msg In ActiveExplorer.Selection
Msg.UnRead = False
Msg.Move ArchiveFolder
Next Msg
End Sub
But that only moves the latest message... and only when the conversation is fully collapsed! I can't Archive when the conversation is expanded.

Paul-Jan put me on the right path, so I gave him the answer. Here's my really poor VBA version (I'm missing some type casting, checking). But it does work on collapsed and expanded conversations of mail.
Sub ArchiveConversation()
Set ArchiveFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Parent.Folders("Archive")
Set Conversations = ActiveExplorer.Selection.GetSelection(Outlook.OlSelectionContents.olConversationHeaders)
For Each Header In Conversations
Set Items = Header.GetItems()
For i = 1 To Items.Count
Items(i).UnRead = False
Items(i).Move ArchiveFolder
Next i
Next Header
End Sub

Anthony's answer almost works for me. But it doesn't work on both messages and conversations. Here's my modification:
Sub Archive()
Set ArchiveFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderInbox).Parent.Folders("Archive")
Dim IsMessage As Integer
IsMessage = 0
For Each Msg In ActiveExplorer.Selection
Msg.Move ArchiveFolder
IsMessage = 1
Next Msg
If IsMessage = 0 Then
Set Conversations = ActiveExplorer.Selection.GetSelection(Outlook.OlSelectionContents.olConversationHeaders)
For Each Header In Conversations
Set Items = Header.GetItems()
For i = 1 To Items.Count
Items(i).UnRead = False
Items(i).Move ArchiveFolder
Next i
Next Header
End If
End Sub

If you want to handle conversations, you'll have to do so explicitly. You can go from MailItem to its Conversation using MailItem.GetConversation, but you'd be better off working with conversations directly.
What you do is:
Get all conversation headers from the current selection
For each conversation, get the individual items
Do your archiving thing with them.
The following C# code illustrates this, and should be trivial to port to VBA.
Outlook.Selection selection = Application.ActiveExplorer().Selection;
Outlook.Selection convHeaders = selection.GetSelection( Outlook.OlSelectionContents.olConversationHeaders) as Outlook.Selection;
foreach (Outlook.ConversationHeader convHeader in convHeaders)
{
Outlook.SimpleItems items = convHeader.GetItems();
for (int i = 1; i <= items.Count; i++)
{
if (items[i] is Outlook.MailItem)
{
Outlook.MailItem mail = items[i] as Outlook.MailItem;
mail.UnRead = false;
mail.Move( archiveFolder );
}
// else... not sure how if you want to handle different types of items as well }
}

Related

Find out if an attachment is embedded or attached

I am coding a small VBA to show all attachments of an email in a list box.
The user can select attachments that should be removed from the email and stored on a target folder.
I am also adding a HTML file to the email that contains a list of all removed files (including a link to each file to the target folder).
I have a problem with images, because they can be
Attached as a normal file to the email
Embedded to the email body (like a company logo in the signature)
I want to show in my list box only those images, that are attached as files to the email.
Embedded mails should be ignored.
Sub SaveAttachment()
Dim myAttachments As Outlook.Attachments
Dim olMailItem As Outlook.MailItem
Dim lngAttachmentCount As Long
Dim Attachment_Filename As String
Select Case True
Case TypeOf Application.ActiveWindow Is Outlook.Inspector
Set olMailItem = Application.ActiveInspector.CurrentItem
Case Else
With Application.ActiveExplorer.Selection
If .Count Then Set olMailItem = .Item(1)
End With
If olMailItem Is Nothing Then Exit Sub
End Select
Set myAttachments = olMailItem.Attachments
If myAttachments.Count > 0 Then
For lngAttachmentCount = myAttachments.Count To 1 Step -1
'-------------------------------------------------------------------------
' Add the attachment to the list of attachments (form)
'-------------------------------------------------------------------------
Attachment_Filename = myAttachments(lngAttachmentCount).FileName
With UserForm1.lstAttachments
.AddItem (Attachment_Filename)
.List(lngAttachmentListPos, 1) = Attachment_Type_Text
.List(lngAttachmentListPos, 2) = FormatSize(myAttachments(lngAttachmentCount).Size) & " KB"
End With
Next lngAttachmentCount
End If
End Sub
I added only the relevant parts of the code, so I hope I have not forgotten anything.
At the moment I show all attachments (also embedded images).
How would I find out if an attachment is embedded?
I found a possible solution here:
Distinguish visible and invisible attachments with Outlook VBA
The source code provided is not working, it seems like the two URLs in line 2 and 3 no longer exist.
I'm not sure if this is a solution that is valid in all cases, but it works in my environment. That means "test it properly".
Const PR_ATTACH_CONTENT_ID = "http://schemas.microsoft.com/mapi/proptag/0x3712001F"
Function IsEmbedded(Att As Attachment) As Boolean
Dim PropAccessor As PropertyAccessor
Set PropAccessor = Att.PropertyAccessor
IsEmbedded = (PropAccessor.GetProperty(PR_ATTACH_CONTENT_ID) <> "")
End Function
Call it with
If IsEmbedded(myAttachments(lngAttachmentCount)) Then
...
End If
The cryptic url-looking constant is not a url, but a property identifier. You can find a list of them here: https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf
That property is set to the url of the attachment if embedded. If not embedded, then it is empty.
In the Outlook object model it's very important to marshal your objects correctly. Leaving a PropertyAccessor hanging about is not good, so I would suggest a minor modification to the accepted answer as follows:
Const PR_ATTACH_CONTENT_ID = "http://schemas.microsoft.com/mapi/proptag/0x3712001F"
Function IsEmbedded(Att As Attachment) As Boolean
Dim PropAccessor As PropertyAccessor = Nothing
Try
PropAccessor = Att.PropertyAccessor
Return (PropAccessor.GetProperty(PR_ATTACH_CONTENT_ID) <> "")
Catch
Return False
Finally
If PropAccessor IsNot Nothing
Marshal.ReleaseCOMObject(PropAccessor)
End If
End Catch
End Function
With the help of the answer and comment from #DinahMoeHumm we went with this solution which seems to work:
Function outlook_att_IsEmbedded(Att As outlook.Attachment) As Boolean
Dim PropAccessor As outlook.PropertyAccessor
On Error GoTo outlook_att_IsEmbedded_error
outlook_att_IsEmbedded = False
Set PropAccessor = Att.PropertyAccessor
If PropAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3712001E") <> "" Or _
PropAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x3713001E") <> "" Then
If PropAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x37140003") = 4 Then
outlook_att_IsEmbedded = True
End If
End If
outlook_att_IsEmbedded_exit:
Set PropAccessor = Nothing
Exit Function
outlook_att_IsEmbedded_error:
outlook_att_IsEmbedded = False
Resume outlook_att_IsEmbedded_exit
End Function
I don't know what the different probtags mean. Or what the 4 is. It seems like you could find a list of them here: https://interoperability.blob.core.windows.net/files/MS-OXPROPS/%5bMS-OXPROPS%5d.pdf (but I didn't)

getting the outlook mail's latest status(replied or forwarded)

I am working on an automation on mails in outlook VBA. I want to use PR_VERB_EXECUTION_TIME property to get the latest status of mail and check whether the mail has already been replied or forwarded and send mail if the mail is unattended. Any help in
Sub Test(Item AS MailItem)
//Item is my incoming mail
Dim Obj As Outlook.MailItem
Dim str As String
Dim propaccessor As Outlook.Propertyaccessor
Set propaccessor = Item.propertyAccessor
str = propaccessor.Getproperty("http://schemas.microsoft.com/mapi/proptag/0x10820040")
'Str value is setting to null due to which error is thrown
'but other properties are working fine
'i want to use this string and compare current time and then reply if it is equal to current time
End Sub
how to use the property is appreciated!.
for that you need to use PropertyAccessor.GetProperty Method
For Each mailItem In mailitems
If mailItem.Class <> olMail Then Exit For
Set propertyAccessor = mailItem.propertyAccessor
LastVerbExecuted = CheckBlankFields("PR_LAST_VERB_EXECUTED", propertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x10810003"))
Select Case LastVerbExecuted
Case Last_Verb_Reply_All, Last_Verb_Reply_Sender, Last_Verb_Reply_Forward
Subject = mailItem.Subject
'This appears to be local time
RecievedTime = mailItem.ReceivedTime
'This appears to be GMT
strRepliedTime = CheckBlankFields("PR_LAST_VERB_EXECUTION_TIME", propertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x10820040"))
OriginalAuthor = mailItem.Sender
'Replier = ...
If strRepliedTime <> "" Then
'Convert string strRepliedTime to time format here...using a custom function
End If
LogData Subject, OriginalAuthor, Replier, RecievedTime, RepliedTime
Case Else
'in case you want to do something here
End Select
Next mailItem
Refer http://www.tek-tips.com/viewthread.cfm?qid=1739523
you can do this way
Const Last_Verb_Reply_All = 103
Const Last_Verb_Reply_Sender = 102
Const Last_Verb_Reply_Forward = 104
For Each mailItem In mailitems
If mailItem.Class <> olMail Then Exit For
Set propertyAccessor = mailItem.propertyAccessor
LastVerbExecuted = CheckBlankFields("PR_LAST_VERB_EXECUTED", propertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x10810003"))
Select Case LastVerbExecuted
Case Last_Verb_Reply_All, Last_Verb_Reply_Sender, Last_Verb_Reply_Forward
'it means email already responded
exit sub
'i dont think there is need to check time
'strRepliedTime = CheckBlankFields("PR_LAST_VERB_EXECUTION_TIME", propertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x10820040"))
Case Else
'in case you want to do something here
End Select
Next mailItem

Outlook VBA: Using Word Inspector to create a Follow Up Meeting Invite

I am creating a VBA Macro in Outlook that will copy an existing meeting invite and create a follow up meeting invite. It should be fairly easy since I have all the parts to this puzzle.
My problem is with the body of the invite; all formatting and pictures are lost. For this reason, I need to use the Word Inspector object to preserve any special formatting and images. I figured out the code using Word and recording a macro.
So I have figured out the code for copying text using the Word Inspector, but I am not sure on how to paste it in another invite.
Sub copyPaste()
Dim objOL As Outlook.Application
Dim objNS As Outlook.NameSpace
On Error Resume Next
Set objOL = Application
If objOL.ActiveInspector.EditorType = olEditorWord Then
Set objDoc = objOL.ActiveInspector.WordEditor
Set objNS = objOL.Session
Set objSel = objDoc.Windows(1).Selection
objSel.WholeStory
objSel.Copy
objSel.PasteAndFormat (wdFormatOriginalFormatting)
End If
Set objOL = Nothing
Set objNS = Nothing
End Sub
Please see my current Outlook code:
Sub scheduleFollowUpMeeting()
'Declarations
Dim obj As Object
Dim Sel As Outlook.Selection
'Selecting the Email
If TypeOf Application.ActiveWindow Is Outlook.Inspector Then
Set obj = Application.ActiveInspector.currentItem
Else
Set Sel = Application.ActiveExplorer.Selection
If Sel.Count Then
Set obj = Sel(1)
End If
End If
If Not obj Is Nothing Then
MsgBox "The original meeting has been copied." & vbCrLf & "Please kindly update any new details like date/time.", , "Follow Up Meeting - Amit P Shah"
Dim objFollowUp As Outlook.AppointmentItem
Set objFollowUp = Application.CreateItem(olAppointmentItem)
'Copies existing details from original Invite
With objFollowUp
.MeetingStatus = olMeeting
.Subject = "Follow Up: " & obj.Subject
.Body = obj.Body
.Start = Now + 1 'Takes today's date and adds 1 day
.End = DateAdd("n", obj.Duration, .Start)
'Other
.AllDayEvent = obj.AllDayEvent
.BusyStatus = obj.BusyStatus
.Categories = obj.Categories
.Companies = obj.Companies
.ForceUpdateToAllAttendees = obj.ForceUpdateToAllAttendees
.Importance = obj.Importance
.Location = obj.Location
.OptionalAttendees = obj.OptionalAttendees
.ReminderMinutesBeforeStart = obj.ReminderMinutesBeforeStart
.ReminderOverrideDefault = obj.ReminderOverrideDefault
.ReminderPlaySound = obj.ReminderPlaySound
.ReminderSet = obj.ReminderSet
.ReminderSoundFile = obj.ReminderSoundFile
.ReplyTime = obj.ReplyTime
.RequiredAttendees = obj.RequiredAttendees
.Resources = obj.Resources
.ResponseRequested = obj.ResponseRequested
.Sensitivity = obj.Sensitivity
.UnRead = obj.UnRead
.Display
End With
End If
End Sub
Any help would greatly be appreciated. Many thanks in advance!
I'm not a specialist on this subject but i used to work and manipulate Outlook's AppointmentItem in C# and that's how i see the thing.
Actually, if you try to copy the body of a meeting on another meeting, like you said, you will lose all the special formating, images, etc.
The new body will only contain the caracters without format.
I think you can't put formatted text on the body property, you have to use the rtfbody property or like you did when you copy the body of your original appointment, use the WordEditor property in the Inspector object.
So, try to use the WordEditor of the new Item you're creating (like you did to take the original content) and to add content in it.
That's what i had to do for putting formatted text in the body of an AppointmentItem in C#.
I did something like that :
Word.Document myDoc = myItem.GetInspector.WordEditor;
Word.Paragraphs paragraphs = myDoc.Content.Paragraphs;
Word.Paragraph para = paragraphs.Add();
para.Range.Text = yourFormattedTextHere;
After that, you may need to release the variables created, but i'm not sure about that.

VBA - Outlook Not Removing Attachments

I am writing a macro that should remove attachments. From my debugging sessions, it appears as if it should work. The breakpoint is hit and it recognizes the message object:
I know this sounds a bit silly, but, oddly enough, it seems to work if I set a breakpoint, and open the expression/watch, but not otherwise.
I have been struggling with this for quite some time; I would appreciate any guidance.
Private Sub Application_NewMailEx(ByVal EntryIDCollection As String)
Dim header As String
Dim objNewMail As Outlook.MailItem
Dim Item As Object
Dim count As Integer
Dim objInbox As Outlook.Folder
Set objInbox = Outlook.Session.GetDefaultFolder(olFolderInbox)
Dim entryIDs
entryIDs = Split(EntryIDCollection, ",")
Dim i As Integer
For i = 0 To UBound(entryIDs)
Set objNewMail = Application.Session.GetItemFromID(entryIDs(i))
If objNewMail.Attachments.count > 0 Then
header = GetHeader(objNewMail)
If DoesIPMatch(header) <> True Then
DeleteMessage (objNewMail)
ElseIf IsAttachmentPDF(objNewMail) <> True Then
For count = 1 To objNewMail.Attachments.count
objNewMail.Attachments.Remove (count)
Next
End If
End If
Next
End Sub
Try this, as a most likely culprit if you are removing items from a collection it should always be done in reverse order, otherwise you have to re-index your counter variable, and that makes for messy code:
It may also be necessary to Save the objNewMail item after you've modified it (e.g., by removing attachments)
For count = objNewMail.Attachments.count to 1 Step - 1
objNewMail.Attachments.Remove count
Next
objNewMail.Save '## Not sure if this is necessary
Alternatively:
With objNewMail.Attachments
While .Count > 0
.Remove 1
Wend
End With
objNewMail.Save

Forward a highlighted email to a specific recipient

How do I forward a highlighted email to a specific recipient?
Is it possible to link that to a CTRL-ALT- shortcut?
I'm on Windows 7, Outlook 2010
Ctrl+Alt does not appear to be a feature of Outlook 2003 so I cannot comment on it. I can run the following macro by adding the macro to a toolbar using Tools, Customise.
This macro will forward all selected items. If you want to ensure only one item is selected, test SelectedItems.Count. If the current folder is empty, nothing happens because SelectedItems.Count = 0
Sub ForwardSelectedItems()
Dim Inx As Integer
Dim NewItem As MailItem
Dim SelectedItems As Outlook.Selection
Set SelectedItems = ActiveExplorer.Selection
For Inx = 1 To SelectedItems.Count
Set NewItem = SelectedItems.Item(Inx).Forward
With NewItem
' ########## Remove following block if attachments are to be sent.
' Delete any attachments.
With .Attachments
While .Count > 0
.Remove 1
Wend
End With
' ########## Remove above block if attachments are to be sent.
With .Recipients
' Add new recipient. Note Forward deletes existing recipients.
.Add "my.friend#isp.com"
End With
' Choices ######
'.Display ' Show to user and let them send if they wish
.Send ' Send automatically without showing to user
' End of choices ######
End With
Next
End Sub
Forward a highlighted email to a specific recipient
Return the currently highlighted email:
GetCurrentItem will return the currently selected or opened email to the calling procedure.
Function GetCurrentItem() As Object
' returns reference to current item, either the one
' selected (Explorer), or the one currently open (Inspector)
Select Case True
Case IsExplorer(Application.ActiveWindow)
Set GetCurrentItem = ActiveExplorer.Selection.item(1)
Case IsInspector(Application.ActiveWindow)
Set GetCurrentItem = ActiveInspector.CurrentItem
End Select
End Function
Function IsExplorer(itm As Object) As Boolean
IsExplorer = (TypeName(itm) = "Explorer")
End Function
Function IsInspector(itm As Object) As Boolean
IsInspector = (TypeName(itm) = "Inspector")
End Function
Forward the selected email:
This uses the above functions to forward and display an email. The rest is up to you.
Sub FwdMail()
Dim obj As Object
Dim msg As Outlook.MailItem
Set obj = GetCurrentItem
If TypeName(obj) = "MailItem" Then
Set msg = obj
With msg
.Forward
.Display
End With
End If
End Sub