ISO easy C#/VB code to use OAuth and save emails from Exchange Inbox

Valdez, Timothy (AGR) 21 Reputation points
2022-06-06T22:38:50.673+00:00

This is frustratingly difficult. I cannot find a single webpage that shows working code to use OAuth and read emails from an Exchange account inbox. Why does MS make it so difficult? I've been struggling for weeks to create a console app to simply grab emails from an inbox and save them to a local text file, but can't figure out the authentication issues and since it is not a web app there is no callback web-based endpoint for OAuth2 so not sure what to even do there. Is it not possible to write a quick-n-dirty console app to just grab emails anymore? If anyone could point me to an online source with working code samples I'd be eternally grateful. Even the MS code samples give server errors and are not complete, see https://learn.microsoft.com/en-us/exchange/client-developer/exchange-web-services/get-started-with-ews-client-applications which constantly returns a server 500 error using private static String Office365WebServicesURL = "https://outlook.office365.com/EWS/Exchange.asmx";

Developer technologies | C#
Developer technologies | C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
0 comments No comments
{count} votes

Answer accepted by question author
  1. Glen Scales 4,446 Reputation points
    2022-06-07T05:09:26.633+00:00

    I've been struggling for weeks to create a console app to simply grab emails from an inbox and save them to a local text file,

    If this is all you need to do then and the Mailboxes are on Office365 then EWS isn't the best method to use I would suggest you use the Graph API instead as EWS is now considered legacy. With the Authentication you need to choose a method to use based on your requirements eg a traditional approach would be ROPC userName and password https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Username-Password-Authentication but as the link mentions not recommended. For unattended code usually the best method is https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-credential-flows (this is the same if you where going to use EWS or Graph to access the Mailbox).

    The simplest console app that got the last email from the Inbox and saved it to a file using the Graph SDK https://www.nuget.org/packages/Microsoft.Graph/ and ROPC would look like

    using System;  
    using System.Collections.Generic;  
    using System.Threading.Tasks;  
    using Microsoft.Graph;  
    using System.Net;  
    using System.Net.Http;  
      
      
    namespace TestSimpleGraphApp  
    {  
        internal class Program  
        {  
            static void Main(string[] args)  
            {  
                string fileName = "c:\\temp\\lastEmail.eml";  
      
                var clientId = "xxx-bfc5-41c4-94e0-bdb99fb67cb2";  
      
                var tenantId = "xxx-65e0-4fc3-b967-b14d5057375b";  
      
                var scopes = new[] { "Mail.Read" };  
      
                var credential = new NetworkCredential("******@domain.onmicrosoft.com", "xxxx");  
      
                var graphClient = new GraphServiceClient(new ConsoleAppAuthenticationProvider(scopes, clientId, tenantId, credential));  
                //Get the Last Email in the Mailbox  
      
                var Messages = graphClient.Me.MailFolders["Inbox"].Messages.Request()  
                    .Top(1).OrderBy("receivedDateTime").GetAsync().GetAwaiter().GetResult();  
      
                //Get MIMEcontent from email  
      
                var MimeContent = graphClient.Me.Messages[Messages[0].Id].Content.Request()  
                    .GetAsync().GetAwaiter().GetResult();  
      
                var fileStream = System.IO.File.Create(fileName);  
                MimeContent.CopyTo(fileStream);             
                fileStream.Close();  
      
      
      
      
            }  
        }  
      
        public class ConsoleAppAuthenticationProvider : IAuthenticationProvider  
        {  
      
            private readonly Microsoft.Identity.Client.IPublicClientApplication _app;  
      
            private readonly IEnumerable<string> _scopes;  
      
            private readonly NetworkCredential _networkCredential;  
      
            public ConsoleAppAuthenticationProvider(IEnumerable<string> graphScopes, string clientId, string tenantId,NetworkCredential credential)  
            {  
                _scopes = graphScopes;  
                _app = Microsoft.Identity.Client.PublicClientApplicationBuilder  
                    .Create(clientId)  
                    .WithTenantId(tenantId)  
                    .Build();  
                _networkCredential = credential;  
            }  
      
            public Task AuthenticateRequestAsync(HttpRequestMessage request)  
            {  
                var tokenResult = _app.AcquireTokenByUsernamePassword(_scopes,_networkCredential.UserName,_networkCredential.SecurePassword).ExecuteAsync().Result;  
                request.Headers.Add("Authorization", "Bearer " + tokenResult.AccessToken);  
                return Task.CompletedTask;  
            }  
        }  
    }  
    

    With EWS the link you pointed to was for the WSDL proxy code which doesn't support oAuth at all you an overide it to add in the necessary bearer header https://gist.github.com/gscales/a946025d143436ec64a8cc4cb27693c0. Else most people use the EWS Managed API which does support using oAuth tokens https://github.com/OfficeDev/ews-managed-api but unless you need to support OnPrem mailboxes I would use the Graph.


3 additional answers

Sort by: Most helpful
  1. Valdez, Timothy (AGR) 21 Reputation points
    2022-06-08T17:02:16.76+00:00

    No joy. The next error now says I need to allow it, but searching through the App Registration screens I cannot find any place to do that:
    System.AggregateException Inner Exception 1: MsalUiRequiredException: AADSTS65001: The user or administrator has not consented to use the application with ID 'xxx-798d-4d79-b245-xxx' named 'test#2'. Send an interactive authorization request for this user and resource. See image, does the fact that it is grayed out mean it does not have admin consent?

    209538-image.png

    0 comments No comments

  2. Valdez, Timothy (AGR) 21 Reputation points
    2022-06-08T17:28:05.877+00:00

    Also tried this (gave a ton of permissions to the app) and still same consent needed error:

    209546-image.png


  3. Valdez, Timothy (AGR) 21 Reputation points
    2022-06-13T20:21:36.703+00:00

    So, I got admin approval for the app and IT WORKS!! Thank you so much!

    I'm looking for a list of methods I can use on the var Messages = graphClient.Me.MailFolders["Inbox"].Messages.Request().OrderBy("receivedDateTime").GetAsync().GetAwaiter().GetResult(); line of code (the "dot" functions) to try to grab only the UNREAD messages in the inbox and then delete them. Is there a link to that documentation? Or is it as easy as adding something like .Unread to the line of code? I did the following to try to grab the first few but it isn't working like I hoped:

        //Get MIMEcontent from email  
        for (int aa=0; aa<7; aa++)   
        {   
            var MimeContent = graphClient.Me.Messages[Messages[aa].Id].Content.Request().GetAsync().GetAwaiter().GetResult();  
            {  
                var fileStream = System.IO.File.Create(fileName);  
                MimeContent.CopyTo(fileStream);  
                fileStream.Close();  
            }  
        }  
    

    TIA!


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.