EWS Managed API: Identify deleted email when fetching from "AllItems" folder - c#

I am using EWS managed API with C# to fetch emails from user accounts. I am fetching the emails from "AllItems" folder and getting different email properties such as subject, datetimesent, etc.
"AllItems" folder also contains emails that are deleted and are in "DeletedItems" folder. I would like to identify whether the email is deleted (i.e. it is in "DeletedItems" folder) and if possible, when the email was deleted.
Below is the code I am using. I could not find a property that would identify whether the email is deleted.
FolderView viewFolders = new FolderView(int.MaxValue) { Traversal = FolderTraversal.Deep, PropertySet = new PropertySet(BasePropertySet.IdOnly) };
ItemView viewEmails = new ItemView(int.MaxValue) { PropertySet = new PropertySet(BasePropertySet.IdOnly) };
SearchFilter folderFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "AllItems");
FolderId rootFolderId = new FolderId(WellKnownFolderName.Root);
FindItemsResults<Item> findResults;
FindFoldersResults AllItemsFolder= service.FindFolders(WellKnownFolderName.Root, folderFilter, viewFolders);
if (AllItemsFolder.Count() > 0)//if we have AllItems folder
{
foreach (Folder folder in AllItemsFolder.Folders)
{
ItemView itv = new ItemView(int.MaxValue);
findResults = service.FindItems(folder.Id, itv);
foreach (Item item in findResults)
{
if (item is EmailMessage)
{
MessageBox.Show(item.Subject);
// Show whether the message is in deleted folder and when message was deleted
}
}
}
}

As you state, I don't think there is a property like that for mail items.
I would use the GetFolder operation with the well known folder name "deleteditems" to get the Id of that folder. Then, I would ignore all mail items that has this Id as ParentFolderId.

Related

Get mail from folder within Inbox - EWS

I want to access emails in a folder called "ITServiceDesk" in my exchange inbox.
I can access the folder but i cant figure out how to read the mail inside that folder.
I am accessing the folder here:
var view = new FolderView(100);
view.Traversal = FolderTraversal.Deep;
var fileview = new ItemView(100);
var filter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName, "ITServiceDesk");
// Read 100 mails
foreach (var item in _service.FindFolders(WellKnownFolderName.Inbox, filter, view))
{
MessageBox.Show(item.DisplayName);
foreach (EmailMessage email in _service.FindItems(WellKnownFolderName.Inbox, filter, fileview))
{
email.Load(new PropertySet(EmailMessageSchema.ConversationTopic, ItemSchema.Attachments,
ItemSchema.TextBody));
MessageBox.Show(email.ConversationTopic);
MessageBox.Show(email.TextBody);
}
}
Nothing happens when i get inside the second foreach loop. The message box shows that it can find the folder as the item.displayname is correct.
If you are finding the folder with you code then just call the findItem method on the Folder object that is returned eg
foreach (var Folder in _service.FindFolders(WellKnownFolderName.Inbox, filter, view))
{
MessageBox.Show(Folder.DisplayName);
foreach (EmailMessage email in Folder.FindItems(fileview))
{
email.Load(new PropertySet(EmailMessageSchema.ConversationTopic, ItemSchema.Attachments,
ItemSchema.TextBody));
MessageBox.Show(email.ConversationTopic);
MessageBox.Show(email.TextBody);
}
}
Here is an example from my website:
FindItemsResults<Item> findResults
= service.FindItems(WellKnownFolderName.Inbox, new ItemView( 10 ) );
foreach ( Item item in findResults.Items )
Console.WriteLine( item.Subject );
See C#: Getting All Emails From Exchange using Exchange Web Services

Email to group using EWS C#

I'm using ExchangeWebServices C#.
I'm trying to send an email to distributio list, so
I'm created a group as follow:
private void CreateGroup(ExchangeService service)
{
// Create a new contact group object.
ContactGroup myContactGroup = new ContactGroup(service);
// Give the group a name.
myContactGroup.DisplayName = "TestContactGroup";
// Add some members to the group.
myContactGroup.Members.Add(new GroupMember("Euser#mydomain.com"));
myContactGroup.Members.Add(new GroupMember("Euser1#mydomain.com"));
myContactGroup.Members.Add(new GroupMember("Euser2#mydomain.com"));
// Save the group.
myContactGroup.Save();
}
Now I'm trying to send email to this group, how can i do that?
What i'm tried:
EmailMessage email = new EmailMessage(service);
email.ToRecipients.Add("TestContactGroup");//Throw an exception "At least one recipient isn't valid."
//email.ToRecipients.Add("TestContactGroup#mydomain.com");//"Return" the mail that "The email address you entered couldn't be found."
email.Subject = "MySubject";
email.Body = new MessageBody("MyBody");
// Send the mail
email.Send();
If I'm trying to send to TestContactGroup i'm got an exception:
"At least one recipient isn't valid."
And if I'm trying to send to TestContactGroup#mydomain.com I'm got an email that the mail isn't found.
So, how can i send an email to group list that I'm crated? Or another way to create distribution list with EWS?
Thanks
Problem solved.
I'm just need to add the group id as follow:
myContactGroup.Id = grpId;
I'm get my group id as follow:
// Instantiate the item view with the number of items to retrieve from the Contacts folder.
ItemView view = new ItemView(9999);
// Request the items in the Contacts folder that have the properties that you selected.
FindItemsResults<Item> contactItems = service.FindItems(WellKnownFolderName.Contacts, view);
string groupId = string.Empty;
List<ItemId> groupItemIds = new List<ItemId>();
// Loop through all contacts
foreach (Item item in contactItems)
{
//Check to see if ContactGroup
if (item is ContactGroup)
{
//Get the contact group
ContactGroup contactGroup = item as ContactGroup;
groupItemIds.Add(item.Id);//Using to send an email by item id, can classify by DisplayName etc..
}
}

EWS - Determine if an e-mail is a reply or has been forwarded

I am using the Exchange Web Services Managed API 2.2 to monitor users inboxes and need to determine if an e-mail is a new item, a reply or a forwarded message.
I have seen various articles on SO such as how to notice if a mail is a forwarded mail? and Is there a way to determine if a email is a reply/response using ews c#? which both help in their specific cases but I still cannot work out how to distinguish between a reply and a forwarded item.
In the first article an extra 5 bytes is added each time (forward or reply) so I don't know what the last action was.
The second article suggests using the InReplyTo however when I examine the property for forwarded e-mails it contains the original senders e-mail address (not null).
I have seen other articles such as this or this that suggest using extended properties to examine the values in PR_ICON_INDEX, PR_LAST_VERB_EXECUTED and PR_LAST_VERB_EXECUTION_TIME.
My code looks as follows but never returns a value for lastVerbExecuted
var lastVerbExecutedProperty = new ExtendedPropertyDefinition(4225, MapiPropertyType.Integer);
var response = service.BindToItems(newMails, new PropertySet(BasePropertySet.IdOnly, lastVerbExecutedProperty));
var items = response.Select(itemResponse => itemResponse.Item);
foreach (var item in items)
{
object lastVerb;
if (item.TryGetProperty(lastVerbExecutedProperty, out lastVerb))
{
// do something
}
}
PR_ICON_INDEX, PR_LAST_VERB_EXECUTED and PR_LAST_VERB_EXECUTION_TIME would only work to tell you if the recipient has acted on a message in their Inbox. Eg if the user had replied or forwarded a message in their inbox then these properties get set on the message in their Inbox. On the message that was responded to or forwarded these properties would not be set. I would suggest you use the In-Reply-To Transport header which should be set on any message that is replied to or forwarded, this should contain the internet messageid of the message that was replied to or forwarded eg.
FindItemsResults<Item> fiRs = service.FindItems(WellKnownFolderName.Inbox, new ItemView(10));
PropertySet fiRsPropSet = new PropertySet(BasePropertySet.FirstClassProperties);
ExtendedPropertyDefinition PR_TRANSPORT_MESSAGE_HEADERS = new ExtendedPropertyDefinition(0x007D, MapiPropertyType.String);
fiRsPropSet.Add(PR_TRANSPORT_MESSAGE_HEADERS);
service.LoadPropertiesForItems(fiRs.Items, fiRsPropSet);
foreach (Item itItem in fiRs)
{
Object TransportHeaderValue = null;
if(itItem.TryGetProperty(PR_TRANSPORT_MESSAGE_HEADERS,out TransportHeaderValue)) {
string[] stringSeparators = new string[] { "\r\n" };
String[] taArray = TransportHeaderValue.ToString().Split(stringSeparators, StringSplitOptions.None);
for (Int32 txCount = 0; txCount < taArray.Length; txCount++)
{
if (taArray[txCount].Length > 12)
{
if (taArray[txCount].Substring(0, 12).ToLower() == "in-reply-to:")
{
String OriginalId = taArray[txCount].Substring(13);
Console.WriteLine(OriginalId);
}
}
}
}
}
Apart from the Subject prefix that was discussed in the other link I don't know of any other proprieties that will differentiate between a reply or forward.
Cheers
Glen
The best way to rely is on the ResponeCode of Extended properties
Refer below scripts
private static int IsForwardOrReplyMail(ExchangeService service, EmailMessage messageToCheck)
{
try
{
// Create extended property definitions for PidTagLastVerbExecuted and PidTagLastVerbExecutionTime.
ExtendedPropertyDefinition PidTagLastVerbExecuted = new ExtendedPropertyDefinition(0x1081, MapiPropertyType.Integer);
ExtendedPropertyDefinition PidTagLastVerbExecutionTime = new ExtendedPropertyDefinition(0x1082, MapiPropertyType.SystemTime);
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, EmailMessageSchema.Subject, PidTagLastVerbExecutionTime, PidTagLastVerbExecuted);
messageToCheck = EmailMessage.Bind(service, messageToCheck.Id, propSet);
// Determine the last verb executed on the message and display output.
object responseType;
messageToCheck.TryGetProperty(PidTagLastVerbExecuted, out responseType);
if (responseType != null && ((Int32)responseType) == 104)
{
//FORWARD
return 104;
}
else if (responseType != null && ((Int32)responseType) == 102)
{
//REPLY
return 102;
}
}
catch (Exception)
{
return 0;
//throw new NotImplementedException();
}
}
To determine if it was a reply to a email, you can use the EmailMessage objects InReplyTo property, e.g:
EmailMessage mail = ((EmailMessage)Item.Bind(service, new ItemId(UniqueId)));
if (mail.InReplyTo == null)
return;
else
..your code

Getting unique emails EWS Managed Web API

I am trying to retrieve the emails from the Exchange server using below code:
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
service.Credentials = new WebCredentials("username", "somepassword");
service.TraceEnabled = true;
service.TraceFlags = TraceFlags.All;
service.AutodiscoverUrl("username", RedirectionUrlValidationCallback);
FindItemsResults<Item> findResults = service.FindItems(WellKnownFolderName.Inbox, new ItemView(10));
ServiceResponseCollection<GetItemResponse> items =
service.BindToItems(findResults.Select(item => item.Id), new PropertySet(BasePropertySet.FirstClassProperties, EmailMessageSchema.From, EmailMessageSchema.ToRecipients));
return items.Select(item =>
{
return new MailItem()
{
From = ((Microsoft.Exchange.WebServices.Data.EmailAddress)item.Item[EmailMessageSchema.From]).Address,
Recipients = ((Microsoft.Exchange.WebServices.Data.EmailAddressCollection)item.Item[EmailMessageSchema.ToRecipients]).Select(recipient => recipient.Address).ToArray(),
Subject = item.Item.Subject,
Body = item.Item.Body.ToString(),
};
}).ToArray();
I need to save the subject and body in my database . But i need unique emails becasue i don't want redundant emails to display on my system.
Means every time i synchronize my system with the exchange server , i will get new emails which i hadn't synchronized yet.
If I understand you right, you save the emails obtained by EWS in a Database.
Later you obtain the emails again and so you get the email you already have plus the new ones?
How about working with timestamps?
Get also the CreationTime (or ReceivedTime) of the MailItem and save it in the database too.
After that search in EWS only for mailitems that have CreationTime (or ReceivedTime) later than the last CreationTime (or ReceivedTime) in your Database.
So you only get the new emails.
A possible solution is to move the emails that you processed to the DeletedItems folder by calling
emailMessage.Delete(DeleteMode.MoveToDeletedItems);
Please note that I didn't have to keep a copy of the processed emails within my inbox, so this was a viable solution for me. If you for some reason have to keep a copy within your inbox folder this will not work for you.

SharePoint 2010 - Client Object Model - Add attachment to ListItem

I have a SharePoint List to which I'm adding new ListItems using the Client Object Model.
Adding ListItems is not a problem and works great.
Now I want to add attachments.
I'm using the SaveBinaryDirect in the following manner:
File.SaveBinaryDirect(clientCtx, url.AbsolutePath + "/Attachments/31/" + fileName, inputStream, true);
It works without any problem as long as the item that I'm trying to add the attachment to, already has an attachment that was added through the SharePoint site and not using the Client Object Model.
When I try to add an attachment to a item that doesnt have any attachments yet, I get the following errors (both happen but not with the same files - but those two messages appear consistently):
The remote server returned an error: (409) Conflict
The remote server returned an error: (404) Not Found
I figured that maybe I need to create the attachment folder first for this item.
When I try the following code:
clientCtx.Load(ticketList.RootFolder.Folders);
clientCtx.ExecuteQuery();
clientCtx.Load(ticketList.RootFolder.Folders[1]); // 1 -> Attachment folder
clientCtx.Load(ticketList.RootFolder.Folders[1].Folders);
clientCtx.ExecuteQuery();
Folder folder = ticketList.RootFolder.Folders[1].Folders.Add("33");
clientCtx.ExecuteQuery();
I receive an error message saying:
Cannot create folder "Lists/Ticket System/Attachment/33"
I have full administrator rights for the SharePoint site/list.
Any ideas what I could be doing wrong?
Thanks, Thorben
I struggled for a long time with this problem too, so I thought I'd post a complete code sample showing how to successfully create a list item and add an attachment.
I am using the Client Object API to create the list item, and the SOAP web service to add the attachment. This is because, as noted in other places on the web, the Client Object API can only be used to add attachments to an item where the item's upload directory already exists (eg. if the item already has an attachment). Else it fails with a 409 error or something. The SOAP web service copes with this OK though.
Note that another thing I had to overcome was that even though I added the SOAP reference using the following URL:
https://my.sharepoint.installation/personal/test/_vti_bin/lists.asmx
The URL that VS actually added to the app.config was:
https://my.sharepoint.installation/_vti_bin/lists.asmx
I had to manually change the app.config back to the correct URL, else I would get the error:
List does not exist.
The page you selected contains a list that does not exist. It may have been deleted by another user.
0x82000006
Here is the code:
void CreateWithAttachment()
{
const string listName = "MyListName";
// set up our credentials
var credentials = new NetworkCredential("username", "password", "domain");
// create a soap client
var soapClient = new ListsService.Lists();
soapClient.Credentials = credentials;
// create a client context
var clientContext = new Microsoft.SharePoint.Client.ClientContext("https://my.sharepoint.installation/personal/test");
clientContext.Credentials = credentials;
// create a list item
var list = clientContext.Web.Lists.GetByTitle(listName);
var itemCreateInfo = new ListItemCreationInformation();
var newItem = list.AddItem(itemCreateInfo);
// set its properties
newItem["Title"] = "Created from Client API";
newItem["Status"] = "New";
newItem["_Comments"] = "here are some comments!!";
// commit it
newItem.Update();
clientContext.ExecuteQuery();
// load back the created item so its ID field is available for use below
clientContext.Load(newItem);
clientContext.ExecuteQuery();
// use the soap client to add the attachment
const string path = #"c:\temp\test.txt";
soapClient.AddAttachment(listName, newItem["ID"].ToString(), Path.GetFileName(path),
System.IO.File.ReadAllBytes(path));
}
Hope this helps someone.
I have discussed this question with Microsoft. Looks like that only one way to create attachments remotely is List.asmx web service. I have tried to create this subfolder also and with no success.
With Sharepoint 2010 there was no way to upload a first attachment to a list item using the COM. The recommendation was to use the Lists web service inmstead.
With Sharepoint 2013 it works.
AttachmentCreationInformation newAtt = new AttachmentCreationInformation();
newAtt.FileName = "myAttachment.txt";
// create a file stream
string fileContent = "This file is was ubloaded by client object meodel ";
System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
byte[] buffer = enc.GetBytes(fileContent);
newAtt.ContentStream = new MemoryStream(buffer);
// att new item or get existing one
ListItem itm = list.GetItemById(itemId);
ctx.Load(itm);
// do not execute query, otherwise a "version conflict" exception is rised, but the file is uploaded
// add file to attachment collection
newAtt.ContentStream = new MemoryStream(buffer);
itm.AttachmentFiles.Add(newAtt);
AttachmentCollection attachments = itm.AttachmentFiles;
ctx.Load(attachments);
ctx.ExecuteQuery();
// see all attachments for list item
// this snippet works if the list item has no attachments
This method is used in http://www.mailtosharepoint.net/
It reflects rather poorly on the Microsoft SharePoint team for not coming forward with an acknowledgement of the issue and a usable suggestion on how to resolve it. Here is how I dealt with it:
I am using the new SharePoint 2010 managed client that ships with the product. Hence, I already have a SharePoint ClientContext with credentials. The following function adds an attachment to a list item:
private void SharePoint2010AddAttachment(ClientContext ctx,
string listName, string itemId,
string fileName, byte[] fileContent)
{
var listsSvc = new sp2010.Lists();
listsSvc.Credentials = _sharePointCtx.Credentials;
listsSvc.Url = _sharePointCtx.Web.Context.Url + "_vti_bin/Lists.asmx";
listsSvc.AddAttachment(listName, itemId, fileName, fileContent);
}
The only prerequisite for the code above is to add to the project (I used Visual Studio 2008) a _web_reference_ I called sp2010 which is created from the URL of: http:///_vti_bin/Lists.asmx
Bon Chance...
HTML:
<asp:FileUpload ID="FileUpload1" runat="server" AllowMultiple="true" />
Event in code behind :
protected void UploadMultipleFiles(object sender, EventArgs e)
{
Common.UploadDocuments(Common.getContext(new Uri(Request.QueryString["SPHostUrl"]),
Request.LogonUserIdentity), FileUpload1.PostedFiles, new CustomerRequirement(), 5);
}
public static List<string> UploadDocuments<T>(ClientContext ctx,IList<HttpPostedFile> selectedFiles, T reqObj, int itemID)
{
List<Attachment> existingFiles = null;
List<string> processedFiles = null;
List<string> unProcessedFiles = null;
ListItem item = null;
FileStream sr = null;
AttachmentCollection attachments = null;
byte[] contents = null;
try
{
existingFiles = new List<Attachment>();
processedFiles = new List<string>();
unProcessedFiles = new List<string>();
//Get the existing item
item = ctx.Web.Lists.GetByTitle(typeof(T).Name).GetItemById(itemID);
//get the Existing attached attachments
attachments = item.AttachmentFiles;
ctx.Load(attachments);
ctx.ExecuteQuery();
//adding into the new List
foreach (Attachment att in attachments)
existingFiles.Add(att);
//For each Files which user has selected
foreach (HttpPostedFile postedFile in selectedFiles)
{
string fileName = Path.GetFileName(postedFile.FileName);
//If selected file not exist in existing item attachment
if (!existingFiles.Any(x => x.FileName == fileName))
{
//Added to Process List
processedFiles.Add(postedFile.FileName);
}
else
unProcessedFiles.Add(fileName);
}
//Foreach process item add it as an attachment
foreach (string path in processedFiles)
{
sr = new FileStream(path, FileMode.Open);
contents = new byte[sr.Length];
sr.Read(contents, 0, (int)sr.Length);
var attInfo = new AttachmentCreationInformation();
attInfo.FileName = Path.GetFileName(path);
attInfo.ContentStream = sr;
item.AttachmentFiles.Add(attInfo);
item.Update();
}
ctx.ExecuteQuery();
}
catch (Exception ex)
{
throw ex;
}
finally
{
existingFiles = null;
processedFiles = null;
item = null;
sr = null;
attachments = null;
contents = null;
ctx = null;
}
return unProcessedFiles;
}
I've used and tried this one on my CSOM (SharePoint Client Object Model) application and it works for me
using (ClientContext context = new ClientContext("http://spsite2010"))
{
context.Credentials = new NetworkCredential("admin", "password");
Web oWeb = context.Web;
List list = context.Web.Lists.GetByTitle("Tasks");
CamlQuery query = new CamlQuery();
query.ViewXml = "<View><Where><Eq><FieldRef Name = \"Title\"/><Value Type=\"String\">New Task Created</Value></Eq></Where></View>";
ListItemCollection listItems = list.GetItems(query);
context.Load(listItems);
context.ExecuteQuery();
FileStream oFileStream = new FileStream(#"C:\\sample.txt", FileMode.Open);
string attachmentpath = "/Lists/Tasks/Attachments/" + listItems[listItems.Count - 1].Id + "/sample.txt";
Microsoft.SharePoint.Client.File.SaveBinaryDirect(context, attachmentpath, oFileStream, true);
}
Note: Only works if item folder has been created already

Categories