Read email from outlook - problem with XML - c#

I'm trying to connect to outlook online and download some emails with predefined conditions.
in browser when I go to https://outlook.office.com/mail/ and logon with specific email and pass, I can see all mails.
I have c# console application that should connect, search and manipulate with emails.
public void EmailReceiver()
{
try
{
ExchangeService exchangeService = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
exchangeService.Credentials = new NetworkCredential("username", "pass");
exchangeService.Url = new Uri("https://outlook.office.com/mail/");
var inbox = Folder.Bind(exchangeService, WellKnownFolderName.Inbox);
var sf1 = new SearchFilter.ContainsSubstring(EmailMessageSchema.From, "searchMail");
SearchFilter.SearchFilterCollection searchFilterCollection = new SearchFilter.SearchFilterCollection(LogicalOperator.Or);
searchFilterCollection.Add(sf1);
var view = new ItemView(1000);
var findResults = service.FindItems(WellKnownFolderName.Inbox, searchFilterCollection, view);
return;
//foreach (Item item in findResults)
//{
// GetAttachmentsFromEmail(item.Id);
//}
}
catch (Exception ex)
{
throw;
}
}
But cannot connect to outlook
Any suggestions?

Office 365 no longer allows basic authentication, you need to switch to OAuth2. You can still allow basic auth, but you must explicit do so in the Exchange admin console for your tenant. But even in that case, you need to use WebCredentials class, not NetworkCredential. In case of OAuth, you need OAuthCredentials class.
You might also want to set TraceEnabled property to true and provide your class (assign it to the TraceListener property) to log all EWS calls.
exchangeService.TraceEnabled = true;
exchangeService.TraceListener = new MyTraceListener(this);
exchangeService.TraceFlags = TraceFlags.All;

Related

Fetch emails received on certain day from non-Well-known folder in shared inbox, Exchange 2016 EWS in C#

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.

C# Console Application - Parsing Office 365 Inbox

I was able to succeed via a package I found called EAGetMail. Unfortunately, I realized soon after that they have a token system and this is not a free approach.
There are a couple other choices available, like using Outlook Mail REST API, and MimeKit, but I'm lost on how to achieve my end result because no "start to finish" code is available on either of these references that demonstrates how to parse an Inbox for an account.
I've started to write this with the help of Mimekit, but am not sure if this is the proper way at all.
I must imagine it looks something like:
using (var client = new SmtpClient ())
{
client.Connect("outlook.office365.com", 587);
client.Authenticate("myemail#office365account.com", "mypassword");
var message = MimeMessage.Load(stream);
}
I don't know how to setup the stream mentioned above, and I don't know if it's possible to do this with Mimekit and Office 365.
I'm open to seeing a solution for this in any other approach that's not through EAGetMail. If anyone has a lightweight solution ranging from actual establishing a connection, to pulling messages from the inbox, would be great to see!
I've got it using EWS (Exchange Web Services). Here's my code:
private static bool RedirectionUrlValidationCallback(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
static void Main(string[] args)
{
ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2013_SP1);
service.Credentials = new WebCredentials("email#myemail.com", "myPassword");
service.AutodiscoverUrl("email#myemail.com", RedirectionUrlValidationCallback);
//creates an object that will represent the desired mailbox
Mailbox mb = new Mailbox(#"email#myemail.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)
{
item.Load();
Console.WriteLine("Subject: " + item.Subject);
}
}
}

Exchange Server doesn't support the requested version

I get this error because the FindItemsResult isn't compatible with exchange version I'm using which is 2013.
Exchange Server doesn't support the requested version.
My codes:
SearchFilter sf = new SearchFilter.SearchFilterCollection(LogicalOperator.And, new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead, false));
FindItemsResults<Item> items = service.FindItems(WellKnownFolderName.Inbox, sf, new ItemView(10));
foreach (Item item in items.Items)
{
PropertySet propSet = new PropertySet(BasePropertySet.IdOnly, ItemSchema.TextBody);
EmailMessage email = EmailMessage.Bind(service, item.Id, propSet);
Program.SearchItems(email);
}
I could just change it into Exchange 2010 but I get an error in TextBody since this is only for Exchange 2013 and later versions.
Is there any way to convert the code which can work in Exchange 2013?
You need to show more of the code your using as your question doesn't really make sense. The ItemSchema.TextBody was added in Exchange 2013 so as long you are running Exchange 2013 and you have set the Initial server version correctly it will work (So you are either not running 2013 or you have other issue in the code you haven't show). If your looking for something that will work on both Exchange 2007,2010 and 2013 the I would suggest you use.
String MailboxToAccess = "user#domain.com";
ExchangeService service = new Microsoft.Exchange.WebServices.Data.ExchangeService(ExchangeVersion.Exchange2010_SP1);
SearchFilter sfSearchFilter = new SearchFilter.IsEqualTo(EmailMessageSchema.IsRead,false);
service.Credentials = new NetworkCredential("user#domain.com", "password");
service.AutodiscoverUrl(MailboxToAccess, adAutoDiscoCallBack);
FolderId FolderToAccess = new FolderId(WellKnownFolderName.Inbox, MailboxToAccess);
ItemView ivItemView = new ItemView(10);
FindItemsResults<Item> FindItemResults = service.FindItems(FolderToAccess, sfSearchFilter, ivItemView);
PropertySet ItemPropertySet = new PropertySet(BasePropertySet.IdOnly);
ItemPropertySet.Add(ItemSchema.Body);
ItemPropertySet.RequestedBodyType = BodyType.Text;
if (FindItemResults.Items.Count > 0)
{
service.LoadPropertiesForItems(FindItemResults.Items, ItemPropertySet);
}
foreach (Item item in FindItemResults.Items)
{
Console.WriteLine(item.Body.Text);
}
internal static bool adAutoDiscoCallBack(string redirectionUrl)
{
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https")
{
result = true;
}
return result;
}
That will return just the Text body and will work on any version of EWS.
Cheers
Glen

Retrieve suggested contacts of Outlook

My company is under Office 365.
My goal is to retrieve Outlook's suggested contacts of the user in my asp .net MVC app (the contacts displayed in the autocomplete list).
The website is configured for automatic logon with Windows Authentication and I don't want to ask the user his credentials.
I have to tried to retrieve the suggested contacts by using Exchange Web Service but I only succeed to retrieve "real" contacts by using this code :
public List<Contact> GetContacts()
{
ContactsFolder.Bind(this._service, WellKnownFolderName.Contacts);
ItemView itemView = new ItemView(1000);
itemView.PropertySet = new PropertySet(BasePropertySet.IdOnly, new PropertyDefinitionBase[4]
{
(PropertyDefinitionBase) ContactSchema.DisplayName,
(PropertyDefinitionBase) ContactSchema.Surname,
(PropertyDefinitionBase) ContactSchema.GivenName,
(PropertyDefinitionBase) ContactSchema.EmailAddress1
});
FindItemsResults<Item> items = this._service.FindItems(WellKnownFolderName.Contacts, (ViewBase) itemView);
List<Contact> list = new List<Contact>();
foreach (Item obj in items)
{
if (obj is Contact)
list.Add(obj as Contact);
}
return list;
}
Then, I tried by using the People Api of Office 365 REST API but I don't know how to make the call without asking for the user login/password. This is a sample of a try (if I don't use the proxy, I receive an HTTP 407 Error) :
public async Task Try()
{
var proxy = WebRequest.GetSystemWebProxy();
proxy.Credentials = new NetworkCredential("foo", "1234");
// Now create a client handler which uses that proxy
HttpClient client = null;
HttpClientHandler httpClientHandler = new HttpClientHandler()
{
Proxy = proxy,
PreAuthenticate = true,
UseDefaultCredentials = false,
Credentials = new NetworkCredential("foo#foo.com", "1234")
};
var httpClient = new HttpClient(httpClientHandler);
var result = await httpClient.GetAsync("https://outlook.office.com/api/beta/me/people");
var stringContent = await result.Content.ReadAsStringAsync();
}
About the Suggested Contact problem
What I am thinking is that you are not looking in the proper folder. From what I have seen by googling the suggested contacts are not in the Contacts directory but in Suggested Contacts. In your EWS sample you are looking in Contacts...
See this discussion. Look also at this guy post, he manages to have access to the Suggested Contacts folder with EWS and powershell so there is no doubt this is feasible with C# and EWS .NET SDK. My advice is to continue trying with your sample 1.
About the Authentication problem
Let me emphasize the fact that your requests should be authorized to access both Exchange Web Services (code sample 1) or the outlook REST API (code sample 2).
In sample 1 we do not see how the _service field is instantiated but I bet there is a piece of code that looks more or less the lines below so your allowed to request EWS.
ExchangeService service = new ExchangeService();
service.Credentials = new OAuthCredentials(token);
service.Url = new Uri(ewsUrl);
The token can be probably reuse for the Outlook REST API, try to set it in the bearer in the httpClient
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "Your Oauth token");
Now your request should be authorized but you still have your proxy problem. I bet that this happen only within your organisation because your IT set up a proxy. You probably won't need it in production. You can use a debug statement to make it work while your developing locally.
#if DEBUG
IWebProxy webProxy = System.Net.WebRequest.DefaultWebProxy;
if (webProxy!=null && !webProxy.IsBypassed(new Uri(endpoint)))
{
client.Proxy = webProxy;
}
#endif
I never found the "Suggested Contacts" folder of the post.
I ended by using the folder "AllContacts" that seems to do the job.
public List<Contact> GetSuggestedContacts()
{
// Instantiate the item view with the number of items to retrieve from the Contacts folder.
ItemView view = new ItemView(1000);
// To keep the request smaller, request only the display name property.
view.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties, ContactSchema.DisplayName, ContactSchema.Surname, ContactSchema.GivenName, ContactSchema.EmailAddress1);
// Retrieve the RecipientCache folder in the Contacts folder that have the properties that you selected.
var contactFolders = _service.FindFolders(new FolderId(WellKnownFolderName.Root), new FolderView(500));
var folder = contactFolders.Folders.SingleOrDefault(x => x.DisplayName == "AllContacts");
if(folder == null) return new List<Contact>();
//Cast Item in Contact and filtered only real adresses
var cacheContacts = folder.FindItems(view).Items.OfType<Contact>().Where(x => x.EmailAddresses.Contains(0) && x.EmailAddresses[0].Address != null && x.EmailAddresses[0].Address.Contains('#')).ToList();
return cacheContacts;
}
I also found the service ResolveName of Exchange that I could have use for the autocomplete.

Allow impersonation using ews

I'm trying to create a program in C# which should make it able to create appointments in someone else's Outlook calendar. I have code which makes it able to create appointments in my own calendar. I searched on Google and I found I should use impersonation. So I added the line:
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, emailAddress);
This is my code:
private void button2_Click(object sender, EventArgs e) {
try{
ExchangeService service = new ExchangeService();
service.UseDefaultCredentials = true;
service.Credentials = new WebCredentials("Test#domain.com", "password");
service.AutodiscoverUrl("Test#domain.com", adAutoDiscoCallBack);
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, "Test2#domain.com");
Appointment appointment = new Appointment(service);
// Set the properties on the appointment object to create the appointment.
appointment.Subject = "Tennis lesson";
appointment.Body = "Focus on backhand this week.";
appointment.Start = DateTime.Now.AddDays(2);
appointment.End = appointment.Start.AddHours(1);
appointment.Location = "Tennis club";
appointment.ReminderDueBy = DateTime.Now;
// Save the appointment to your calendar.
appointment.Save(SendInvitationsMode.SendToNone);
// Verify that the appointment was created by using the appointment's item ID.
Item item = Item.Bind(service, appointment.Id, new PropertySet(ItemSchema.Subject));
}
}catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
internal static bool adAutoDiscoCallBack(string redirectionUrl) {
// The default for the validation callback is to reject the URL.
bool result = false;
Uri redirectionUri = new Uri(redirectionUrl);
// Validate the contents of the redirection URL. In this simple validation
// callback, the redirection URL is considered valid if it is using HTTPS
// to encrypt the authentication credentials.
if (redirectionUri.Scheme == "https") {
result = true;
}
return result;
}
The problem is that I keep getting this error ""The SMTP-address has no mailbox associated with it."
Is it because impersonation isn't allowed on the server? If so how do I allow it?
I hope someone can help.
Ps: Sorry for the bad english
A few suggestions if this is Office365 or Exchange 2013 you first need the PrimarySMTP address of the Mailbox you wish to access eg
String MailboxToAccess = "PrimarySMTP#domain.demo";
Note for some tenants the SMTP and UPN/Logon are the same but that is not always the case.
This is what user you are going to be impersonating eg
service.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, MailboxToAccess);
You should also add in the X-AnchorMailbox header https://learn.microsoft.com/en-us/archive/blogs/webdav_101/best-practices-ews-authentication-and-access-issues eg
service.HttpHeaders.Add("X-AnchorMailbox", MailboxToAccess);
Also when you go to save the Appointment use the FolderId class with the Mailbox overload to ensure your hitting the correct Mailbox eg
FolderId CalendarFolderId = new FolderId(WellKnownFolderName.Calendar, MailboxToAccess);
appointment.Save(CalendarFolderId,SendInvitationsMode.SendToNone);
Cheers
Glen

Categories

Resources