Removing specific address from ReplyAll in outlook - vba

EDITED
I wrote macro that finds an e-mail in shared mailbox and then replies to it. The problem is that for some cases I would like to remove my address (shared#mailbox) or some other and I don't know how to do it. I tried some methods that I found but none worked. Sorry for such a basic question.
Const olFolderInbox = 6
Sub Reminder()
On Error Resume Next
Dim olMail As Outlook.MailItem
Dim myNamespace As Outlook.Namespace
Dim myRecipient As Outlook.Recipient
Set Outl = CreateObject("Outlook.Application")
Set myNamespace = Outl.GetNamespace("MAPI")
Set myRecipient = myNamespace.CreateRecipient("shared#inbox")
Set objOutlook = CreateObject("Outlook.Application")
Set objNamespace = objOutlook.GetNamespace("MAPI")
Set objInbox = objNamespace.GetSharedDefaultFolder(myRecipient, olFolderInbox)
strFolderName = objInbox.Parent
Set objMailbox = objNamespace.Folders(strFolderName)
Set objFolder = objMailbox.Folders("Inbox").Folders("AAA").Folders("BBB")
Set colItems = objFolder.Items
Dim i As Long
Dim Folder As Outlook.Folder
i = 1
For Each olMail In objFolder.Items
If olMail.Subject = "AAA" + ActiveSheet.Range("D" & (ActiveCell.Row)) Then
Set oReplyAll = olMail.ReplyAll
oReplyAll.HTMLBody = "<BODY style=font-size:10pt; font-family:Arial>Dear ,<br /> <br />" _
& "Could you please remind the client to do something?<br />" _
& "Thank you in advance.<br />" _
<br /> </BODY>" _
& oReplyAll.HTMLBody
oReplyAll.CC = "xyz#xyz"
'////////////////////////////////////////////////////////////////
'EDIT
'////////////////////////////////////////////////////////////////
For j = 1 To oReplyAll.Recipients.Count
With oReplyAll.Recipients(j)
If .Name = "aaa#bbb" Then
.Delete
j = j - 1
End If
End With
Next j
oReplyAll.Display
i = i + 1
End If
Next olMail
End Sub
Edit: I've added loop iSpain17 wrote3 in a comment. Nothing changed. Reply displays normally with recipient in "To:" part although he should be removed

There is a high probability that the text you want to match is not what you think it is.
Remove On Error Resume Next so you can fix any errors in this untested code.
With this structure you can delete/move more than one match as the index is not corrupted.
For j = oReplyAll.Recipients.Count to 1 step -1
With oReplyAll.Recipients(j)
debug.print "text to match " & .name
If .Name = "text to match" Then
.Delete
End If
End With
Next j
With this structure you can delete/move one match reliably.
For j = 1 To oReplyAll.Recipients.Count
With oReplyAll.Recipients(j)
debug.print "text to match " & .name
If .Name = "text to match" Then
.Delete
' exit now,
' else next item is skipped,
' as it moves up into the position of the deleted item
exit for
End If
End With
Next j

EDITED: Please note, that when you use the Recipients.Add method, you set the Name property of your item. Thus, when you use for loop, you'll have to test against the Name property. Also, deleting an item modifies all other item's index, thus the i=i-1. I tried this, it worked for me.
For i = 1 To email.Recipients.Count
With email.Recipients(i)
If .Name = "address" Then
.Delete
i = i - 1
End If
End With
Next i
This loops through each recipient in the recipients, and if they match the given emailmaddress they are removed from the recipients.
This might not be the exact answer as i am not too familiar with outlook vba, but this is the logic i would use.

Related

Search for sent items with today's date and specific subject

I want when Outlook opens to:
Search sent items with today's date with a specific subject.
If none is found, then send the "Test" email.
If found, display messagebox that says "Email is found".
I have only been able to do #1.
Private Sub Application_Startup()
Dim MItem As MailItem
Set MItem = Application.CreateItem(olMailItem)
MItem.Subject = "Test Alert"
MItem.To = "email#abc.com"
MItem.DeferredDeliveryTime = DateAdd("n", 1, Now) 'n = minute, h=hour
MItem.Send
End Sub
Update:
This is what I've tried. It doesn't seem to be searching the Sent Items folder with the subject.
Public Function is_email_sent()
Dim olApp As Outlook.Application
Dim olNs As Outlook.NameSpace
Dim olFldr As Outlook.Folder
Dim olItms As Outlook.Items
Dim objItem As Outlook.MailItem
On Error Resume Next
Set olApp = New Outlook.Application
Set olNs = olApp.GetNamespace("MAPI")
Set olFldr = olNs.GetDefaultFolder(Outlook.olFolderSentMail)
For Each objItem In olFldr.Items
If objItem.Subject = "Test Alert" And _
objItem.SentOn = Date Then _
MsgBox "Yes. Email found"
Else
MsgBox "No. Email not found"
Exit For
End If
Next objItem
End Function
The main error is misuse of On Error Resume Next. Errors are bypassed, not fixed.
Public Sub is_email_sentFIX()
Dim olFldr As Folder
Dim olItms As Items
Dim objItem As Object
Dim bFound As Boolean
' Not useful here.
' Use for specific purpose to bypass **expected** errors.
'On Error Resume Next
Set olFldr = Session.GetDefaultFolder(olFolderSentMail)
Set olItms = olFldr.Items
olItms.sort "[SentOn]", True
For Each objItem In olItms
If objItem.Class = OlMail Then
Debug.Print objItem.Subject
If objItem.Subject = "Test Alert" Then
Debug.Print objItem.SentOn
Debug.Print Date
If objItem.SentOn > Date Then
MsgBox "Yes. Email found"
bFound = True
Exit For
End If
End If
End If
Next objItem
If bFound = False Then
MsgBox "No. Email not found"
End If
End Sub
If there are an excessive number of items in the Sent folder the "not found" outcome will be slow.
One possible option to the brute force way is to Restrict to the specific item, rather than using If statements.
this is some code ive used;
Sub sendmail10101() 'this is to send the email from contents in a cell
Dim obApp As Object
Dim NewMail As MailItem
Set obApp = Outlook.Application
Set NewMail = obApp.CreateItem(olMailItem)
'You can change the concrete info as per your needs
With NewMail
.Subject = Cells(21, 3).Value
.To = Cells(18, 3).Value
.Body = "Good day" & vbCrLf & "i hope you are keeping well " & vbCrLf & vbCrLf & "please can you assist with the below members infomation;" & vbCrLf & vbCrLf & vbCrLf & Cells(20, 3).Value
'.Attachments.Add ("C:\Attachments\Test File.docx")
.Importance = olImportanceHigh
.Display
End With
Set obApp = Nothing
Set NewMail = Nothing
End Sub
the next part is to search the mail box, which you can also use to search from the first initial cell;
Option Explicit
Public Sub Search_Outlook_Emails()
Dim outApp As Outlook.Application
Dim outNs As Outlook.Namespace
Dim outStartFolder As Outlook.MAPIFolder
Dim foundEmail As Outlook.MailItem
Set outApp = New Outlook.Application
Set outNs = outApp.GetNamespace("MAPI")
'Start at Inbox's parent folder
Set outStartFolder = outNs.GetDefaultFolder(Outlook.olFolderInbox).Parent
'Or start at folder selected by user
'Set outStartFolder = outNs.PickFolder
If Not outStartFolder Is Nothing Then
Set foundEmail = Find_Email_In_Folder(outStartFolder, ThisWorkbook.Sheets("Dashboard").TextBox1.Value)
If Not foundEmail Is Nothing Then
If MsgBox("Email subject: " & foundEmail.Subject & vbNewLine & vbNewLine & _
"Folder: " & foundEmail.Parent.FolderPath & vbNewLine & vbNewLine & _
"Open the email?", vbYesNo, "'" & ThisWorkbook.Sheets("Dashboard").TextBox1.Value & "' found") = vbYes Then
foundEmail.Display
End If
Else
MsgBox "", vbOKOnly, "'" & ThisWorkbook.Sheets("Dashboard").TextBox1.Value & "' not found"
End If
End If
End Sub
Private Function Find_Email_In_Folder(outFolder As Outlook.MAPIFolder, findText As String) As Outlook.MailItem
Dim outItem As Object
Dim outMail As Outlook.MailItem
Dim outSubFolder As Outlook.MAPIFolder
Dim i As Long
Debug.Print outFolder.FolderPath
Set Find_Email_In_Folder = Nothing
'Search emails in this folder
i = 1
While i <= outFolder.Items.Count And Find_Email_In_Folder Is Nothing
Set outItem = outFolder.Items(i)
If outItem.Class = Outlook.OlObjectClass.olMail Then
'Does the findText occur in this email's body text?
Set outMail = outItem
If InStr(1, outMail.Body, findText, vbTextCompare) > 0 Then Set Find_Email_In_Folder = outMail
End If
i = i + 1
Wend
DoEvents
'If not found, search emails in subfolders
i = 1
While i <= outFolder.Folders.Count And Find_Email_In_Folder Is Nothing
Set outSubFolder = outFolder.Folders(i)
'Only check mail item folders
If outSubFolder.DefaultItemType = Outlook.olMailItem Then Set Find_Email_In_Folder = Find_Email_In_Folder(outSubFolder, findText)
i = i + 1
Wend
End Function
the previous code brings us a message box to say if its been found which can be removed but maybe use the message box and an IF statement
such as;
with activeworkbook
if msgbox.value = "yes" then
range("A1:A30") = "COMPLETED" 'ASSUMING THIS IS THE INTIAL TEST RANGE IT WILL CHANGE THE SUBJECT THUS STOPPING THE FIRST MACRO
end if
end with
or if no message box then use something such as IF found then so on...
hope this helps

Targeting specific Outlook Mail folder

I am trying to create an Outlook Macro that will analyze the subject of an Inbox folder and decide where to move them to a subfolder or delete them based on a list of keywords for four different categories.
The problem is that the Inbox I am using is not the regular Inbox (I have two different Inbox folders, and this one is not the default one). So I need to target it in a way similar to writing the full path (Example: "\\xxx#xxx.net\Inbox\"). I tried to find an answer to it but all the info I found here relates to the assumption that we are working from the default Inbox.
Sub CountAttachmentsMulti2()
Dim oItem As Object
Dim iAttachments As Integer
For Each oItem In ActiveExplorer.Selection
iAttachments = oItem.Attachments.Count + iAttachments
If oItem.Attachments.Count <> 0 Then 'Si el mensaje contiene adjuntos
NumofItems = oItem.Attachments.Count + NumofItems
For j = 1 To oItem.Attachments.Count
MsgBox oItem.Attachments.Item(j).DisplayName
Value = oItem.Attachments.Item(j).DisplayName
If InStr(LCase(Value), "su") > 0 Then
MsgBox "Clap"
End If
Next j
Else
MsgBox oItem.Subject 'Get Subject Title
NumofItems = NumofItems + 1
End If
Next
MsgBox "Selected " & ActiveExplorer.Selection.Count & " messages with " & iAttachments & " attachements"
MsgBox "# of items = " & NumofItems
End Sub
This is the code I have tried initially, because before they have already separated by categories. So all that required is to count the total e-mails either by subject or number of attachments.
My issue right now is that I do not know how to target this e-mail account by using a full path.
If I know how to target that folder I think I can solve the rest of the problem myself.
After following the "possible-duplicate" link I was able to complete my code. I apologize because I did not know it was called a reference. Here is my complete solution to the issue:
Sub Test()
Dim objNS As Outlook.NameSpace
Dim objFolder As Outlook.MAPIFolder
Dim Target_Folder As Outlook.MAPIFolder
Dim oItem As Object
Dim iAttachments As Integer
Set objNS = GetNamespace("MAPI")
Set objFolder_root = objNS.Folders("Testing") 'Getting Outlook Container
Set objFolder = objFolder_root.Folders("Inbox") 'Target Inbox of the other container
For Loops = objFolder.Items.Count To 1 Step -1
Set oItem = objFolder.Items(Loops)
If Category1(oItem.Subject) Then
'MsgBox "Clap1"
Set Target_Folder = objFolder_root.Folders("Category 1")
oItem.Move Target_Folder
ElseIf Category2(oItem.Subject) Then
'MsgBox "Clap2"
Set Target_Folder = objFolder_root.Folders("Category 2")
oItem.Move Target_Folder
ElseIf Category3(oItem.Subject) Then
'MsgBox "Clap3"
Set Target_Folder = objFolder_root.Folders("Category 3")
oItem.Move Target_Folder
ElseIf Category4(oItem.Subject) Then
'MsgBox "Clap4"
Set Target_Folder = objFolder_root.Folders("Category 4")
oItem.Move Target_Folder
Else
MsgBox oItem.Subject & " does not belong to any of the 4 categories"
End If
Next
End Sub
Function Category1(value)
Category_1_Keywords = Array("a")
For i = 0 To UBound(Category_1_Keywords)
If InStr(LCase(value), Category_1_Keywords(i)) > 0 Then
Category1 = True
Exit Function
Else
Category1 = False
End If
Next
End Function
There are, of course, more functions, I just posted the Category1 as a reference

MS-Word 2010 - Macro to export table to Outlook Tasks

I'm trying to create a macro in MS-Word VBA to take the contents of a MS-Word table (with a bookmarked name), iterate through the rows of the table and create tasks in MS-Outlook (1 row=1 task).
I have Googled and think I will need to try and mix together the following two scripts I have found:
Script 1 - (For making calendar entries - not wanted, but iteration through rows - wanted)
Sub AddAppntmnt()
'Adds a list of events contained in a three column Word table
'with a header row, to Outlook Calendar
Dim olApp As Object
Dim olItem As Object
Dim oTable As Table
Dim i As Long
Dim bStarted As Boolean
Dim strStartDate As Range
Dim strEndDate As Range
Dim strSubject As Range
On Error Resume Next
Set olApp = GetObject(, "Outlook.Application")
If Err <> 0 Then
Set olApp = CreateObject("Outlook.Application")
bStarted = True
End If
Set oTable = ActiveDocument.Tables(1)
'Ignore the first (header) row of the table
For i = 2 To oTable.Rows.Count
Set strStartDate = oTable.Cell(i, 1).Range
strStartDate.End = strStartDate.End - 1
Set strEndDate = oTable.Cell(i, 2).Range
strEndDate.End = strEndDate.End - 1
Set strSubject = oTable.Cell(i, 3).Range
strSubject.End = strSubject.End - 1
Set olItem = olApp.CreateItem(1)
olItem.Start = strStartDate
olItem.End = strEndDate
olItem.ReminderSet = False
olItem.AllDayEvent = True
olItem.Subject = strSubject
olItem.Categories = "Events"
olItem.BusyStatus = 0
olItem.Save
Next i
If bStarted Then olApp.Quit
Set olApp = Nothing
Set olItem = Nothing
Set oTable = Nothing
End Sub
Script 2 - has the actual task creation bit I think I need although this one is about setting task to remind user to do something in 2 weeks or something:
Sub AddOutlookTask()
Dim olApp As Object
Dim olItem As Object
Dim bStarted As Boolean
Dim fName As String
Dim flName As String
On Error Resume Next
If ActiveDocument.Saved = False Then
ActiveDocument.Save
If Err.Number = 4198 Then
MsgBox "Process ending - document not saved!"
GoTo UserCancelled:
End If
End If
Set olApp = GetObject(, "Outlook.Application")
If Err <> 0 Then
'Outlook wasn't running, start it from code
Set olApp = CreateObject("Outlook.Application")
bStarted = True
End If
Set olItem = olApp.CreateItem(3) 'Task Item
fName = ActiveDocument.name
flName = ActiveDocument.FullName
olItem.Subject = "Follow up " & fName
olItem.Body = "If no reply to" & vbCr & _
flName & vbCr & "further action required"
olItem.StartDate = Date + 10 '10 days from today
olItem.DueDate = Date + 14 '14 days from today
olItem.Importance = 2 'High
olItem.Categories = InputBox("Category?", "Categories")
olItem.Save
UserCancelled:
If bStarted Then olApp.Quit
Set olApp = Nothing
Set olItem = Nothing
End Sub
How do I reference a particular table in MS-Word in code? I have bookmarked it so it has a "name" if that helps!
With Davids help (above) I have got the following solution to my problem. I post here for others if they come across a similar issue:
Sub CreateTasks()
'
' CreateTasks Macro
'
'
'
'Exports the contents of the ACtoins table to MS-Outlook Tasks
' Set Variables
Dim olApp As Object
Dim olItem As Object
Dim oTable As Table
Dim i As Long
Dim strSubject As Range
Dim strDueDate As Range
Dim strBody As Range
Dim strSummary As String
Dim bStarted As Boolean
'Dim strPupil As WdBookmark
Dim strPerson As Range
'Link to Outlook
On Error Resume Next
Set olApp = GetObject(, "Outlook.Application")
If Err <> 0 Then
Set olApp = CreateObject("Outlook.Application")
bStarted = True
End If
'Set table variable to the bookmarked table
Set oTable = ActiveDocument.Bookmarks("Actions").Range.Tables(1)
'Ignore the first (header) row of the table
For i = 3 To oTable.Rows.Count
Set strSubject = oTable.Cell(i, 3).Range
strSubject.End = strSubject.End - 1
Set strBody = oTable.Cell(i, 4).Range
strBody.End = strBody.End - 1
Set strDueDate = oTable.Cell(i, 5).Range
strDueDate.End = strDueDate.End - 1
'next line not working below
'Set strPupil = WdBookmark.Name
'Create the task
Set olItem = olApp.CreateItem(3) 'Task Item
strSummary = Left(strSubject, 30)
olItem.Subject = "CYPP Action for" & " " & strBody & "-" & strSummary & "..."
olItem.Body = strBody & vbNewLine & olItem.Body & vbNewLine & strSubject
olItem.DueDate = strDueDate & olItem.DueDate
olItem.Categories = "CYPP"
olItem.Save
Next i
If bStarted Then olApp.Quit
Set olApp = Nothing
Set olItem = Nothing
Set oTable = Nothing
End Sub
I will be adding to this to deal with empty rows but I am pleased with the functionality so far. The DateDue is not working yet but I think that is a formatting issue.
Thanks again David,
Richard.

Get sender's SMTP email address with Excel VBA

I pull the Subject, received date and sender's name with the following code:
Set InboxSelect = GetObject("", "Outlook.Application").GetNamespace("MAPI").PickFolder
i = 0: EmailCount = 0
EmailCount = InboxSelect.Items.Count
While i < EmailCount
i = i + 1
blastRow = Cells(Rows.Count, 1).End(xlUp).Offset(1).Row
LastRow = Sheets("Body").Cells(Rows.Count, 1).End(xlUp).Offset(1).Row
With InboxSelect.Items(i)
MsgBox (SenderEmailAddress)
'If .senderemailaddress = "*#somethingSpecific.co.uk" Then
'EmailCount = EmailCount + 1
Sheets("Import Data").Range("A" & blastRow).Formula = .SenderName
Sheets("Import Data").Range("B" & blastRow).Formula = Format(.ReceivedTime, "dd/mm/yyyy")
Sheets("Import Data").Range("C" & blastRow).Formula = .Subject
Sheets("Body").Range("A" & LastRow).Formula = .Body
'End If
End With
Wend
What I'm trying to achieve now is an if statement that will say "If the sender's email address is 'anything#somethingSpecific.co.uk' then execute that code.
I've tried SenderEmailAddress but it returns blank when tested in a message box.
EDIT: /O=*SET1*/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=*VARIABLE1* is now being returned in the immediate window every time with the below code:
Set InboxSelect = GetObject("", "Outlook.Application").GetNamespace("MAPI").PickFolder
i = 0: EmailCount = 0
EmailCount = InboxSelect.Items.Count
While i < EmailCount
For Each Item In InboxSelect.Items
Debug.Print Item.senderemailaddress
If Item.senderemailaddress = "/O=SET1/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=*" Then
i = i + 1
blastRow = Cells(Rows.Count, 1).End(xlUp).Offset(1).Row
LastRow = Sheets("Body").Cells(Rows.Count, 1).End(xlUp).Offset(1).Row
With InboxSelect.Items(i)
Sheets("Import Data").Range("A" & blastRow).Formula = .SenderName
Sheets("Import Data").Range("B" & blastRow).Formula = Format(.ReceivedTime, "dd/mm/yyyy")
Sheets("Import Data").Range("C" & blastRow).Formula = .Subject
'PASTING BODY IS SLOW
Sheets("Body").Range("A" & LastRow).Formula = .Body
'End If
End With
End If
Next Item
Wend
What I've attempted to do is use a wildcard symbol (the *) to act as the variation in the returned message but that hasn't worked, is there a better way to do this?
An example of when using the SenderEmailAddress property returns the e-mail string as required.
Dim outlookApp As outlook.Application, oOutlook As Object
Dim oInbox As outlook.Folder, oMail As outlook.MailItem
Set outlookApp = New outlook.Application
Set oOutlook = outlookApp.GetNamespace("MAPI")
Set oInbox = oOutlook.GetDefaultFolder(olFolderInbox)
For Each oMail In oInbox.Items
Debug.Print oMail.SenderEmailAddress
Next oMail
EDIT:
The issue is that what the .SenderEmailAddress property is returning the EX address, whereas we want the SMTP address. For any internal e-mail addresses, it will return the EX type address.
To get the SMTP address from an internal e-mail, you can use the below.
Dim outlookApp As Outlook.Application, oOutlook As Object
Dim oInbox As Outlook.Folder, oMail As Outlook.MailItem
Dim strAddress As String, strEntryId As String, getSmtpMailAddress As String
Dim objAddressentry As Outlook.AddressEntry, objExchangeUser As Outlook.ExchangeUser
Dim objReply As Outlook.MailItem, objRecipient As Outlook.Recipient
Set outlookApp = New Outlook.Application
Set oOutlook = outlookApp.GetNamespace("MAPI")
Set oInbox = oOutlook.GetDefaultFolder(olFolderInbox)
For Each oMail In oInbox.Items
If oMail.SenderEmailType = "SMTP" Then
strAddress = oMail.SenderEmailAddress
Else
Set objReply = oMail.Reply()
Set objRecipient = objReply.Recipients.Item(1)
strEntryId = objRecipient.EntryID
objReply.Close OlInspectorClose.olDiscard
strEntryId = objRecipient.EntryID
Set objAddressentry = oOutlook.GetAddressEntryFromID(strEntryId)
Set objExchangeUser = objAddressentry.GetExchangeUser()
strAddress = objExchangeUser.PrimarySmtpAddress()
End If
getSmtpMailAddress = strAddress
Debug.Print getSmtpMailAddress
Next oMail
If the e-mail is already SMTP it will just use the .SenderEmailAddress property to return the address. If the e-mail is EX then it will find the SMTP address by using the .GetAddressEntryFromID() Method.
The above is modified code from what I found on this answer. Here is also a link with how to do this within C#.
Public Function GetSenderAddrStr(objMail As Outlook.MailItem) As String
If objMail.SenderEmailType = "SMTP" Then
GetSenderAddrStr = objMail.SenderEmailAddress
Else
GetSenderAddrStr = objMail.Sender.GetExchangeUser().PrimarySmtpAddress
End If
End Function
In most cases, the sender's SMTP address will be available in a separate property, you can access it using MailItem.PropertyAccessor - take a look at an existing message using OutlookSpy (I am its author) - click IMessage button.
Otherwise you can use ExchangeUser.PrimarySmtpAddress
Off the top of my head:
on error resume next 'PropertyAccessor can raise an exception if a property is not found
if item.SenderEmailType = "SMTP" Then
strAddress = item.SenderEmailAddress
Else
'read PidTagSenderSmtpAddress
strAddress = item.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x5D01001F")
if Len(strAddress) = 0 Then
set objSender = item.Sender
if not (objSender Is Nothing) Then
'read PR_SMTP_ADDRESS_W
strAddress = objSender.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001F")
if Len(strAddress) = 0 Then
'last resort
set exUser = objSender.GetExchangeUser
if not (exUser Is Nothing) Then
strAddress = exUser.PrimarySmtpAddress
End If
End If
End If
En If
End If
Cant you just use send keys to force "Control+k" in outlook? Seems like this would solve your issue and probably make for an easy slice of code.
try adding this somewhere?
Application.SendKeys("^k") 'i believe this is correct syntax, never used this yet but i think it works
I ended up doing
varTest = Item.senderemailaddress
If InStr(varTest, "BE WISER INSURANCE") > 0 Then
which detected the set section that wouldn't be in any emails I didn't want. Thanks very much for your help, #Iturner!
In most cases, the sender's SMTP address will be available on the mesage itself in a separate property (PidTagSenderSmtpAddress = 0x5D01001F, DASL name "http://schemas.microsoft.com/mapi/proptag/0x5D01001F"), you can access it using MailItem.PropertyAccessor - take a look at an existing message using OutlookSpy (I am its author) - click IMessage button.
Otherwise you can use ExchangeUser.PrimarySmtpAddress: it is more expensive than reading the PidTagSenderSmtpAddress property. PidTagSenderSmtpAddress will also work if ExchangeUser fails (which can happen if the user was deleted from GAL or if you are looking at the message in a profile different from the one where the message was created)
Off the top of my head:
on error resume next 'PropertyAccessor can raise an exception if a property is not found
if item.SenderEmailType = "SMTP" Then
strAddress = item.SenderEmailAddress
Else
'read PidTagSenderSmtpAddress
strAddress = item.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x5D01001F")
if Len(strAddress) = 0 Then
set objSender = item.Sender
if not (objSender Is Nothing) Then
'read PR_SMTP_ADDRESS_W
strAddress = objSender.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001F")
if Len(strAddress) = 0 Then
'last resort
set exUser = objSender.GetExchangeUser
if not (exUser Is Nothing) Then
strAddress = exUser.PrimarySmtpAddress
End If
End If
End If
En If
End If

VBA Filter only returning exactly half the restricted criteria items

I am writing some VBA for Outlook, which is not something I often do. I have a strange problem with the following code:
Sub Archive()
Dim objSourceFolder As Folder
Dim OldMessages As Outlook.Items
Dim Allmessages As Outlook.Items
Dim objMessage As MailItem
Dim dtDate As Date
Dim strDate As String
Dim strProblemFiles As String
Dim objTargetFolder As Outlook.MAPIFolder
'how old is too old? give a number in months
'-----------------------------------------------
Const iMonthAge = 6
'-----------------------------------------------
strProblemFiles = ""
'locate the sourcefolder as the inbox
Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
'locate the target folder as the only one that can work according to IT - they will make this folder consistent apparently
Set objTargetFolder = Application.Session.Folders.GetFirst
Set objTargetFolder = objTargetFolder.Folders("Archive")
'There is this crappy thing on the filtering of messages where it takes a date string, not a date. So we need to calculate and then convert
'to the format that MS lists on the MSDN site
dtDate = DateAdd("M", -iMonthAge, Now())
strDate = Format(dtDate, "ddddd h:nn AMPM")
'apply a filter to only show messages older than the specified date, which have been read.
Set Allmessages = objSourceFolder.Items
Set OldMessages = Allmessages.Restrict("[Received] <= '" & strDate & "' AND [Unread] = False")
'let the poor user know what's going on - they can bail out now if they want
If MsgBox("There are " & OldMessages.Count & " old items to archive. They will be moved from your " & objSourceFolder.Name & _
" folder to your " & objTargetFolder.Name & " folder.", vbYesNo, "Archive Files Now?") = vbYes Then
'go through all the messages in the big list of messages older than the specified date, moving them if possible.
For Each objMessage In OldMessages
If TypeName(OldMessages.GetFirst) = "MailItem" Then
'do our shizzle
Else
'PRETTY MINIMAL ERROR CATCHING NEEDS IMPROVING
'write down the name of anything that isn't mail, I guess... need to work on this
strProblemFiles = strProblemFiles + vbCrLf + objMessage.Subject
GoTo errorcatch
'GoTo CarryOn
End If
'make a note for anyone who can look
Debug.Print objMessage.Subject
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then
'There's nothing in errorcatch, but there will be
On Error GoTo errorcatch
'Move the item if you can
objMessage.Move objTargetFolder
End If
End If
'after an error, we jump here to go to the noxt item
CarryOn:
Next
Else
'if the user doesn't want to do it, we need to shut up shop and get the hell out of here
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
Exit Sub
End If
'now we have done the whole thing, we can wipe down for fingerprints and exit through the window
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
'reset the errors
On Error GoTo 0
'probably not going to be any that weren't mail items, but didn't cause a real error, but I guess we should show any we skipped.
If strProblemFiles <> "" Then MsgBox strProblemFiles
Exit Sub
'pathetic
errorcatch:
GoTo CarryOn
End Sub
Function FileExists(FileName As String) As Boolean
FileExists = (Dir(FileName) <> "")
End Function
Everything works... nearly. the first time I run the macro, it tells me that there are (e.g. 128 items ready to archive. It runs and I notice that there are still old messages in my inbox, so I run it again and it tells me there are 64 items ready for archive... then 32, 16 etc. halving the number of found messages each time. I cannot see why it would do this. Any ideas?
I should mention that this is running on Outlook 2010, using an Exchange.
Thanks for looking - all answers most appreciated!
Cheers,
Mark
Something like:
'...
Dim colMove As New Collection
'...
For Each objMessage In OldMessages
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then colMove.Add objMessage
End If
Next
For Each objMessage In colMove
objMessage.Move objTargetFolder
Next
'...
The For Each issue is explained, and another method to move or delete items counting backwards is described here.
For Each loop: Just deletes the very first attachment
Option Explicit
Sub Archive()
Dim objSourceFolder As Folder
Dim OldMessages As Outlook.Items
Dim AllMessages As Outlook.Items
Dim objMessage As Object
Dim dtDate As Date
Dim strDate As String
Dim strProblemFiles As String
Dim objTargetFolder As Outlook.MAPIFolder
Dim colMove As New Collection
Dim objFolder As Outlook.MAPIFolder
Dim lngSize As Long
Dim objAnything As Object
Dim iMaxMBSize As Integer
Dim boolSentItems As Boolean
Dim catCategory As category
' Dim boolCatExists As Boolean
' Dim iColour As Integer
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
'iColour = 18
'we are moving files, that's all, so we don't really need to worry too much about errors - if there is a problem, we can just skip the file
'without great negative effects.
On Error Resume Next
'how old is too old? give a number in months
'-----------------------------------------------
Const iMonthAge = 6
iMaxMBSize = 50
'-----------------------------------------------
'locate the sourcefolder as the inbox
boolSentItems = (MsgBox("Your inbox will be archived." & vbCrLf & _
"Do you want to also archive sent items?", vbYesNo, "Archive Options") = vbYes)
Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
'----------------------------------------------------------------------------------------------------------------------------------------
StartAgain:
'If you wish to assign a category to the folders rather than keep the folder structure when you archive, use this code and some other bits
'later on, which mention the categories and the variables mentioned here.
'Set objSourceFolder = Application.Session.GetDefaultFolder(olFolderInbox)
' boolCatExists = False
'For Each catCategory In Application.Session.Categories
' If catCategory.Name = "Archived from " & objSourceFolder.Name Then
' boolCatExists = True
' End If
'Next
'If boolCatExists = False Then
' Application.Session.Categories.Add "Archived from " & objSourceFolder.Name, iColour
'End If
'locate the target folder, which must be either in the same level as the inbox or lower
'----------------------------------------------------------------------------------------------------------------------------------------
Set objTargetFolder = SearchFolders(objSourceFolder.Parent, "Archive")
'if the target folder was not found, then we need to make it, in the root directory (the same level as the inbox - this is stipulated by IT)
If objTargetFolder Is Nothing Then
Set objTargetFolder = Application.Session.GetDefaultFolder(olFolderInbox).Folders.Add("Archive")
End If
'we are going to maintain the folder structure in the archive folder, for the inbox and sent items. This means we know exactly what to look for. If it isn't there,
'we just create it. I have used the search, rather than specifying the folders so that if the archiving is extended to more than just the inbobx and sent items, no
'change is needed.
If SearchFolders(objTargetFolder, objSourceFolder.Name) Is Nothing Then
Set objTargetFolder = objTargetFolder.Folders.Add(objSourceFolder.Name)
Else
Set objTargetFolder = objTargetFolder.Folders(objSourceFolder.Name)
End If
'There is this crappy thing on the filtering of messages where it takes a date string, not a date. So we need to calculate and then convert
'to the format that MS lists on the MSDN site
dtDate = DateAdd("M", -iMonthAge, Now())
strDate = Format(dtDate, "ddddd h:nn AMPM")
'apply a filter to only show messages older than the specified date, which have been read.
Set OldMessages = objSourceFolder.Items
Set OldMessages = OldMessages.Restrict("[Received] <= '" & strDate & "' AND [Unread] = False")
'let the poor user know what's going on - they can bail out now if they want
If OldMessages.Count > 0 Then
' If MsgBox("There are " & OldMessages.Count & " old items in your " & objSourceFolder.Name & ". Do you want to move them from your " & objSourceFolder.Name & _
' " folder to your " & objTargetFolder.Name & " folder.", vbYesNo, UCase(objSourceFolder.Name) + " Archive") = vbYes Then
'----------------------------------------------------------------------------------------------------------------------------------------
'go through all the messages in the big list of messages older than the specified date, moving them if possible.
'StatusForm.Show vbModeless
For Each objMessage In OldMessages
If TypeName(objMessage) = "MailItem" Then
'do our shizzle
Else
'if it is not a mailitem, there may be problems moving it - add it to the list instead.
strProblemFiles = strProblemFiles + vbCrLf + objSourceFolder.Name + ": " + objMessage.Subject
End If
'make a note for anyone who can look
Debug.Print objMessage.Subject
'probably pointless since we are only looking in the inbox and sent items, and making the mirrors ourselves, but check the folder is correct
If objTargetFolder.DefaultItemType = olMailItem Then
If objMessage.Class = olMail Then
'put the message in a nice stable collection for now - that way, we don't have to worry about the count changing etc
colMove.Add objMessage
End If
End If
Next objMessage
'----------------------------------------------------------------------------------------------------------------------------------------
'and here we have the actual move (and some optional text if you are using the categories)
For Each objMessage In colMove
'Move the item if you can
'objMessage.Categories = "Archived from " & objSourceFolder.Name
'objMessage.Save
objMessage.Move objTargetFolder
Next objMessage
'----------------------------------------------------------------------------------------------------------------------------------------
'Else
' 'if the user doesn't want to do it, we need to shut up shop and get the hell out of here
' Set objSourceFolder = Nothing
' Set OldMessages = Nothing
' Set objMessage = Nothing
' Set objTargetFolder = Nothing
' Exit Sub
'End If
Else
'if the count of all the old messages is not greater than 0
MsgBox "There are no messages from more than " & iMonthAge & " months ago in your " & objTargetFolder.Name & _
", so nothing will be archived.", vbExclamation, "Mailbox is Clean"
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'finally, loop through literally all the items in the target folders and add up the sizes to see how much we have archived in total.
For Each objAnything In objTargetFolder.Parent.Items
lngSize = lngSize + objAnything.size
Next
'if they want to include the sent items in the archive, then change over the folder and do it all again
If boolSentItems = True Then
boolSentItems = False
Set objSourceFolder = SearchFolders(objSourceFolder.Parent, "Sent Items")
'iColour = iColour + 1
GoTo StartAgain
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'once we have done all we can, let the user know about all the files that were skipped.
If strProblemFiles <> "" Then
MsgBox "The following items were skipped, so will still be in your mailbox" & vbCrLf & strProblemFiles, vbOKOnly, "Non-Mail Items"
Else
MsgBox "Archive complete", vbOKOnly, "Files Moved"
End If
'----------------------------------------------------------------------------------------------------------------------------------------
'the size of each file is listed in Bytes, so convert to MB to check the MB size and display, for convenience.
If lngSize / (1024 ^ 2) >= iMaxMBSize Then
MsgBox "Your archive folder takes up " & Round(lngSize / (1024 ^ 2), 0) & "MB; it is time to call IT to ask them to clear out the files", vbOKOnly, _
"Archive folder bigger than " & iMaxMBSize & "MB"
End If
'now we have done the whole thing, we can wipe down for fingerprints and exit through the window
Set objSourceFolder = Nothing
Set OldMessages = Nothing
Set objMessage = Nothing
Set objTargetFolder = Nothing
StatusForm.Hide
On Error GoTo 0
Exit Sub
'ErrorCatch:
'If you decide to add some error checking, put it in here, although as I say, I haven't bothered (see Declaration section at top)
End Sub
Public Function SearchFolders(objTopFolder As Outlook.MAPIFolder, strName As String)
Dim objFolder As Outlook.MAPIFolder
'look through all the sub folders at the level we started
For Each objFolder In objTopFolder.Folders
'If we find the one that we are looking for, great! we can get it and get out
If objFolder.Name = strName Then
Set SearchFolders = objFolder
Exit Function
'if we haven't found our magic folder yet, we need to carry on, by looking for any sub-sub folders this is done by calling the function itself on
'the current folder (which is by definition already one level lower than the starting location). if nothing is found, we,ll just carry on
Else
If objFolder.Folders.Count > 0 Then
Call SearchFolders(objFolder, strName)
End If
End If
Next
'the only way to exit the loop at this point is if all the folders have been searched and the folder we were looking for was not found.
Set SearchFolders = Nothing
End Function
the "StatusForm" user form that is referred to is a completely static form that just says "Archiving..." so the user is less likely to try mucking around in Outlook while the macro runs.