How can I fetch all emails from Exchange 2010 in the least amount of EWS calls?
Our mailbox has 50k+ emails with 2k~ folders. I've tried iterating through each folder but this takes hours to fetch all of my emails. My current approach is to fetch all folders from the mailbox then make a list of search filters essentially filtering all items that have a parent folder id of n.
Here is what I have so far.
var allFolders = exchangeService.FindFolders(folderId,
new FolderView(int.MaxValue) {Traversal = FolderTraversal.Deep});
var searchFilterCollection = new List<SearchFilter>();
foreach(var folder in allFolders)
searchFilterCollection.Add(new SearchFilter.SearchFilterCollection(LogicalOperator.Or,
new SearchFilter.IsEqualTo(ItemSchema.ParentFolderId, folder.Id.ToString())));
var itemView = new ItemView(int.MaxValue)
{
PropertySet = PropertySet.FirstClassProperties
};
var findItems = exchangeService.FindItems(folderId,
new SearchFilter.SearchFilterCollection(LogicalOperator.Or, searchFilterCollection), itemView);
The error I receive it The property can not be used with this type of restriction..
If you use EWS directly instead of the EWS Managed API, you can use the FindItemOperation to do this. The EWS FindItemOperation takes multiple parentFolderIds as input.
http://msdn.microsoft.com/en-us/library/aa566107(v=exchg.140).aspx
http://social.technet.microsoft.com/Forums/en-US/exchangesvrdevelopment/thread/4bd4456d-c859-4ad7-b6cd-42831f4fe7ec/
This seems to say that ParentFolderId cannot be accessed in your filter because it is not yet loaded.
You can instruct EWS to load it by adding it to your FolderView:
FolderView view = new FolderView(int.MaxValue) {Traversal = FolderTraversal.Deep};
view.PropertySet.Add(FolderSchema.ParentFolderId);
var allFolders = exchangeService.FindFolders(folderId,view);
As an alternative to search in a mailbox you can use the AllItems folder and do a searchfilter using the MAPI Property "PR_PARENT_ENTRYID" - https://technet.microsoft.com/de-de/sysinternals/gg158149.
// use MAPI property from Items parent entry id
ExtendedPropertyDefinition MAPI_PARENT_ENTRYID = new ExtendedPropertyDefinition(0x0E09, MapiPropertyType.Binary);
// get the "AllItems" folder from its account
folderResult = service.FindFolders(WellKnownFolderName.Root, new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "allitems"), folderView);
var allItemsFolder = folderResult.FirstOrDefault();
// convert EWS Folder Id to MAPI ENTRYID - parentFolderId is us an AlternateId
var convertedId = service.ConvertIds(parentFolderId, IdFormat.EntryId);
// use the MAPI Property with its converted PARENT_ENTRY_ID in EWS Searchfilters
var parent_entry_id = (ids.ConvertedId as AlternateId).UniqueId;
var searchFilterFolders = new SearchFilter.IsEqualTo(MAPI_PARENT_ENTRYID, parent_entry_id);
// search in "AllItems" using the searchFilter containing the converted PARENT_ENTRY_ID
result = service.FindItems(folderId, searchFilterFolders, view);
Related
I'm trying to fetch all messages from a user-specified date on an Exchange 2016 server using the EWS managed API in C#.
I authenticate with:
public static void Login(string username, string password)
{
service.UseDefaultCredentials = false;
service.Credentials = new WebCredentials(username, password);
service.AutodiscoverUrl(username, RedirectionUrlValidationCallback);
}
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
the SMTP address is stored in Settings.settings .
I then find the desired folder using the following (from this thread):
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
// set folder view
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
foreach(Folder f in folderResults)
{
if(f.DisplayName == "Invoices")
{
targetFolderId = f.Id;
//tried showing a message box here
}
}
And use the following (filter code from here and retrieve details from Exchange server code from here) to get the messages I want:
public static void FetchUnreadMessages(DateTime searchDate)
{
SearchFilter greaterthanfilter = new SearchFilter.IsGreaterThanOrEqualTo(ItemSchema.DateTimeReceived, searchDate);
SearchFilter lessthanfilter = new SearchFilter.IsLessThan(ItemSchema.DateTimeReceived, searchDate.AddDays(1));
SearchFilter dayFilter = new SearchFilter.SearchFilterCollection(LogicalOperator.And, greaterthanfilter, lessthanfilter);
results = service.FindItems(targetFolderId, dayFilter, view);
foreach(var item in results.Items)
{
emails.Add((EmailMessage)item);
}
PropertySet properties = (BasePropertySet.FirstClassProperties);
service.LoadPropertiesForItems(emails, properties);
}
I'm not sure where this is breaking down. I tried showing a message box in the foreach loop that finds the folder with the specified name, and it appears the folder is never found. I know there is a folder with that display name in the shared inbox.
I'm not great at debugging and unfortunately my grasp of the EWS API is pretty shaky. Any suggestions as to what I'm missing are welcome.
I keep everything related to the inbox in a static class so I only have to worry about one instance.
There is some problems with you code first
Then select the appropriate inbox with
sharedMailbox = new Mailbox(Properties.Settings.Default.Inbox);
targetFolderId = new FolderId(WellKnownFolderName.Inbox, sharedMailbox);
Nothing wrong with this code but you probably need to understand what's happening here. This code doesn't make any calls to the server it just setups a FolderId class that you can use in a call to get one of the well known folder.
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(WellKnownFolderName.Inbox, view);
This code would just search the Inbox Subfolder of the Mailbox who's credentials you are using. eg
service.Credentials = new WebCredentials(username, password);
If you wanted to Search the SubFolders of the Inbox of the SharedMailbox you would use
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
view.PropertySet.Add(FolderSchema.DisplayName);
view.Traversal = FolderTraversal.Deep;
folderResults = service.FindFolders(targetFolderId , view);
because you are using the targetFolderId in the FindFolder operations that is telling Exchange to Search the Shared Mailbox rather then the mailbox associated with the credentials you are using.
Is it possible to assign EmailMessage specific GUID/ID, which will be later used for search?
var email = new EmailMessage(_service);
email.ExternalGuid = /*Guid or Identifier*/;
email.Send();
And later I should be able to use it to find if this mail is present:
var isExist = _service.IsExistByExternalGuid(/*Guid or Identifier*/);
Why don't you use the InternetMessageid eg Internet Message ID FROM EWS Managed API Send Email c# this Id will then appear in any tracking logs associated with the Message and you can search for the message at a later date using a SearchFilter eg
ItemView ivew = new ItemView(3);
service.TraceEnabled = true;
ExtendedPropertyDefinition PidTagInternetMessageId = new ExtendedPropertyDefinition(4149, MapiPropertyType.String);
SearchFilter sf = new SearchFilter.IsEqualTo(PidTagInternetMessageId, MessageID);
FindItemsResults<Item> iCol = service.FindItems(WellKnownFolderName.Inbox, sf, ivew);
foreach (Item item in iCol.Items)
{
Console.WriteLine(item.Subject);
}
I'm currently using Exchange Web Services in C#. I basically have a small application that reads emails from an inbox and process them.
I would like to forward those emails I receive as an attachment of an email. This attachment will be an outlook email that will include the original email (including its own attachments if any).
Any ideas?
Thanks!
EDIT:
Not sure why I'm getting the down votes but it seems this is not possible as the EWS API does not provide such functionality
You can create an ItemAttachment with EWS but you can't replicate fully what is possible in Outlook with MAPI. Eg with EWS you can create an ItemAttachment and then use the MIMEContent to create an attachment based on a current message as a workaround eg
FolderId Inboxid = new FolderId(WellKnownFolderName.Inbox, "target#domain.com");
ItemView InboxItemView = new ItemView(1);
FindItemsResults<Item> inFiResuls = service.FindItems(Inboxid,InboxItemView);
if(inFiResuls.Items.Count == 1){
EmailMessage fwdMail = new EmailMessage(service);
EmailMessage orgMail = (EmailMessage)inFiResuls.Items[0];
PropertySet psPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
psPropSet.Add(ItemSchema.MimeContent);
orgMail.Load(psPropSet);
ItemAttachment emAttach = fwdMail.Attachments.AddItemAttachment<EmailMessage>();
emAttach.Item.MimeContent = orgMail.MimeContent;
ExtendedPropertyDefinition pr_flags = new ExtendedPropertyDefinition(3591,MapiPropertyType.Integer);
emAttach.Item.SetExtendedProperty(pr_flags,"1");
emAttach.Name = orgMail.Subject;
fwdMail.Subject = "see Attached";
fwdMail.ToRecipients.Add("user#domain.com");
fwdMail.Send();
}
This however doesn't give full fidelity of all the mapi properties associated with a particular message as the MIMEContent is just that, for most normal email messages this isn't an issue however for a message with an attached Outlook Task or other TNEF attachment then you would loose these of attachments or for other properties like categories,flags you would loose these also (you could copy these manually if needed).
Cheers
Glen
you can forward your email using this way. It first loads and reads the each emails with attachment who has "msg" extension. then forwards it to given address. see the below code
FindItemsResults<Item> findResults = exchange.FindItems(WellKnownFolderName.Inbox, newItemView(50,50));
Item[] msgItems = findResults.Where(msgItem => msgItem.HasAttachments).ToArray();
EmailMessage msgInfo = null;
var fileExtensions = new List<string> { "msg", "MSG", "Msg" };
foreach (MSEWS.Item msgItem in msgItems )
{
msgInfo = EmailMessage.Bind(exchange, msgItem.Id);
FileAttachment fa = msgInfo.Attachments[0] asFileAttachment;
if (fileExtensions.Any(ext => ext.Contains(fa.Name.Substring(fa.Name.Length - 3))))
{
fa.Load();
ResponseMessage responseMessage = msgInfo.CreateForward();
responseMessage.BodyPrefix = "messageBody";
responseMessage.ToRecipients.Add("toAddress");
responseMessage.Subject = "subject";
responseMessage.SendAndSaveCopy();
}
}
This question already has answers here:
Wrong mailbox items being retrieved using Exchange Web Services managed API in C#
(2 answers)
Closed 8 years ago.
I am trying to read a particular email account to find items with attachments that are unread, and have certain words in the subject.
I have the below code that works but only reads my inbox and not the inbox I want to check
static void Main(string[] args)
{
ServicePointManager.ServerCertificateValidationCallback=CertificateValidationCallBack;
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2010_SP2);// .Exchange2007_SP1);
service.UseDefaultCredentials = true;
service.AutodiscoverUrl("bbtest#domainname.com"); //, RedirectionUrlValidationCallback); //bbtest#bocuk.local
ItemView view = new ItemView(1);
string querystring = "HasAttachments:true Kind:email";
//From:'Philip Livingstone' Subject:'ATTACHMENT TEST' Kind:email";
// Find the first email message in the Inbox that has attachments.
// This results in a FindItem operation call to EWS.
FindItemsResults<Item> results = service.FindItems(WellKnownFolderName.Inbox, querystring, view);
foreach (EmailMessage email in results)
if (email.IsRead == false) // && email.HasAttachments == true
{
System.Diagnostics.Debug.Write("Found attachemnt on msg with subject: " + email.Subject);
// Request all the attachments on the email message.
//This results in a GetItem operation call to EWS.
email.Load(new PropertySet(EmailMessageSchema.Attachments));
foreach (Attachment attachment in email.Attachments)
{
if (attachment is FileAttachment)
...
what do I have to change so that my code will read the messages in the inbox of bbtest#domainname.com?
By default your mailbox' inbox folder will be set as root when calling FindItems on ExchangeService object. Assuming your domain account has the necessary permissions to access the other mailbox this below should do the trick. I have ommited the service creation part.
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(#"othermailbox#domain.com");
//creates a folder object that will point to inbox folder
FolderId fid = new FolderId(WellKnownFolderName.Inbox, mb);
//this will bind the mailbox you're looking for using your service instance
Folder inbox = Folder.Bind(service, fid);
//load items from mailbox inbox folder
if(inbox != null)
{
FindItemsResults<Item> items = inbox.FindItems(new ItemView(100));
foreach(var item in items)
Console.WriteLine(item);
}
Im using EWS Managed API to communicate between my c# project and our Exchange 2010 server.
I use this code to get all mails in the inbox from now and three days back.
var ews = new ExchangeService(ExchangeVersion.Exchange2010_SP1);
ews.Credentials = new NetworkCredential(usr, psw, dmn);
ews.AutodiscoverUrl(url);
PropertySet itempropertyset = new PropertySet(BasePropertySet.FirstClassProperties);
itempropertyset.RequestedBodyType = BodyType.Text;
ItemView view = new ItemView(int.MaxValue);
FindItemsResults<Item> findResults;
view.PropertySet = itempropertyset;
do
{
findResults = ews.FindItems(WellKnownFolderName.Inbox, view);
foreach (Item item in findResults.Items)
{
if (item.DateTimeCreated < DateTime.Now.AddDays(-3)) continue;
item.Load(itempropertyset);
var message = EmailMessage.Bind(ews, item.Id,
new PropertySet(BasePropertySet.FirstClassProperties, ItemSchema.Attachments));
string to = message.ToRecipients[0].Address.ToLower();
string body = item.Body;
}
view.Offset += findResults.TotalCount;
} while (findResults.MoreAvailable);
Now the problem. I want to improve this line if (item.DateTimeCreated < DateTime.Now.AddDays(-3)) continue; because when i use this, the api gets all the messages from inbox and just continue if its older then three days. I want specify this filter earlier in the code, so the api dont have to handle all messages.
If I understand the problem correctly, this should work. You can see all search filters available here: EWS Search Filters
ItemView view = new ItemView(int.MaxValue);
FindItemsResults<Item> findResults;
view.PropertySet = itempropertyset;
SearchFilter searchFilter =
new SearchFilter.IsGreaterThan(ItemSchema.DateTimeReceived, DateTime.Now.AddDays(-3));
findResults = service.FindItems(WellKnownFolderName.Inbox, searchFilter, view);