Click here to Skip to main content
15,881,424 members
Articles / Programming Languages / C# 3.5

Creating Exchange calendar events with C# executable using Microsoft Exchange Web Services Managed API 2.2

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
9 Mar 2016CPOL4 min read 26.6K   542   4  
C# .Net command line application to create calendar events in Exchange calendars with impersonation

Download EWSCalendarUpdater.zip
Download Nuget_packages.zip

Introduction

There might be scenarios where a system service or an administrator needs to create calendar events among different Exchange accounts, for instance it could be a school timetable or any kind of organization's timetable. The application impersonates Exchange accounts and creates events within calendars among Exchange accounts.

The code presented makes up a C#.Net command line application with few parameters like CSV input file, number of threads, maximum number of overall attempts, tenant admin credentials. It utilizes Microsoft Exchange Web Services Managed API 2.2 (EWS) and has been tested against office 365's Exchange service (apparently it is version 2013). Of course EWS being a web service sometime times out throwing exceptions. The code re-attempts to push unsuccessful saves until success or given number of maximum attempts. 

The ultimate purpose of this project was to create rather big number of events among hundreds of accounts. So far it has been tested few times with 800 000 records among 599 user accounts. During the tests some percentage (10%) of events failed to create and the application re-iterated 5 times to get everything pushed successfully. It took nearly 12 hours overall. Probably it can be further optimized however for now it fits it purpose.

This code uses Command Line Parser Library, CsvHelper library and a class "PagingCollection". Thank you authors!

Using the code

The command line application consumes a CSV file with event records. Records are made up of 5 fields. Whilst this is enough for the purpose of this project there certainly can be more fields added to create more detailed events. Events to be created get packed up into batches of 100 events per request. EWS service requests are multithreaded per user account into given number of threads.

The application requires following input parameters.

Image 1

--user: required, this application has been developed and tested against Exchange Server that is a part of Office 365 services.  For the "user" a tenant admin email has been used. This code has not been tested with standalone Exchange.

--method: the method of how events are to be created. Currently there is only one really: "DeleteFolderAndCreateEventsFromScratch". It is obviouslly very prymitive way of "updating" events by re-creating everythnig following deleting destination folder. It however fitted the purpose at the time of development. EWS exposes some methods that might provide a true update by comparing and updating if necessary.

--read: required, the input CSV file and path, the example CSV file content looks like this:

StartDateTime,EndDateTime,Subject,Location,Username
2017-09-09 13:45:00.000,2017-09-09 14:10:00.000,Meeting1,Location1,<enter user's email>
2018-09-16 13:45:00.000,2018-09-16 14:10:00.000,Meeting2,Location2,<enter user's email>
2014-09-29 09:55:00.000,2014-09-29 10:55:00.000,Meeting3,Location3,<enter user's email>

--folder: required, Exchange calendar folder. Folder will be created if not yet exist. If exists it will be deleted and re-created. This might be subject to the "method" parameter but there is only one option now anyway: "DeleteFolderAndCreateEventsFromScratch".

--threads: (default 10), when creating large number of threads this parameter might be usefull to optimize the overall performance. Requests get batched into given number of threads. Too big number of threads might also decrese the performance so it might require indyvidual tests.

--attempts: (default 10), number of overall attempts. The code re-attempts to create failed saves untill overall success and/or given number number.

--verbose: whether to display all messages.

Example of executing the compiled program from command line:

EWSCalendarUpdater.exe --user <tenant admin email> -p <password> --method DeleteFolderAndCreateEventsFromScratch --folder "My Meetings" --read c:\Test1.csv --threads 10 -v true

The console window right after the execution. In this example there is 800000 events to be cretaed among 599 users.

Image 2

The console window after about 5 hours. It had successfully created all events within 2 overall attempts.

Image 3

 

The code presented below is the core method of the application and  corresponds to the only one option with the same name: "DeleteFolderAndCreateEventsFromScratch".  The method could be broken into functional chunks but for this example is left as one method. It certainly could be broken into at least following smaller methods:

  • impersonation
  • delete exchange folder if already exists
  • create folder
  • create events.

 

C#
private static EventsCreationResult
        DeleteFolderAndCreateEventsFromScratch
    (ExchangeService exchangeService,
            GroupedAppointmentEntries appointments,
                string folderName, Options options)
{

    var result = new EventsCreationResult()
        { Responses = new List<ServiceResponseCollection<ServiceResponse>>(),
            Key = appointments.Username };

    var logResult = new StringBuilder();

    var folderView = new FolderView(int.MaxValue, 0, OffsetBasePoint.Beginning);
    folderView.PropertySet = new PropertySet(BasePropertySet.FirstClassProperties);
    folderView.PropertySet.Add(FolderSchema.DisplayName);
    folderView.PropertySet.Add(FolderSchema.EffectiveRights);
    folderView.Traversal = FolderTraversal.Deep;

    bool folderCreated = false;

    var searchFilter = new SearchFilter.IsEqualTo(FolderSchema.DisplayName,
            folderName);

    try
    {
        // Impersonation
        logResult.AppendLine(string.Format("Impersonating {0}...",
            appointments.Username));

        exchangeService.ImpersonatedUserId =
            new ImpersonatedUserId(ConnectingIdType.SmtpAddress,
                    appointments.Username.Trim());

        logResult.AppendLine("OK");



        //Delete folder if it exists
        var destinationFolder = exchangeService.FindFolders
            (WellKnownFolderName.MsgFolderRoot, searchFilter,
            folderView).FirstOrDefault();

        if (destinationFolder == null)
        {
            logResult.Append("Destination folder not found. ");
        }
        else
        {
            logResult.Append("Destination folder exists. ");

            logResult.Append("Deleting destination folder...");

            // Delete the folder.
            // EWS was throwing an exception when there
            // was 2000+ items in the folder.
            // (Microsoft.Exchange.WebServices.Data.ServiceResponseException
            // or ServiceRequestException)
            try
            {
                exchangeService.FindFolders(WellKnownFolderName.MsgFolderRoot,
                    searchFilter, folderView).FirstOrDefault().Delete(DeleteMode.HardDelete);
            }
            catch (Exception ex)
            {
                if (ex is ServiceRequestException || ex is ServiceResponseException)
                {
                    var pauseAfterDeletingFolderInSeconds = 5;

                    logResult.Append(
                     string.Format(" known ServiceResponseException was thrwon, waiting for the folder do disapear..."
                      , pauseAfterDeletingFolderInSeconds));

                    var folderStillExist = true;
                    var numberOfAttempts = 0;

                    // Wiat upto 5 minutes
                    while (folderStillExist && numberOfAttempts <= 30)
                    {
                        System.Threading.Thread.Sleep(pauseAfterDeletingFolderInSeconds
                                * 1000);

                        logResult.Append(".");

                        folderStillExist =
                            exchangeService.FindFolders(WellKnownFolderName.MsgFolderRoot,
                                searchFilter, folderView).FirstOrDefault() != null;

                        numberOfAttempts++;
                    }
                }
                else
                {
                    throw ex;
                }
            }

            destinationFolder = exchangeService.FindFolders(WellKnownFolderName.MsgFolderRoot,
                searchFilter, folderView).FirstOrDefault();

            if (destinationFolder == null)
            {
                logResult.AppendLine(" - deleted OK");
            }
            else
            {
                throw new EWSCalendarUpdaterException(string.Format
                    (" Error. Destination folder {0} could not be deleted from user {1}."
                        , folderName, appointments.Username));
            }

        }

        //Create folder
        logResult.Append(String.Format("Creating destination folder..."));

        var folder = new CalendarFolder(exchangeService)
            { DisplayName = folderName };
                folder.Save(WellKnownFolderName.Calendar);

        logResult.AppendLine("OK");

        folderCreated = true;

        // Create events
        var meetings = new Collection<Appointment>();
        foreach (var item in appointments.Entries)
        {
            meetings.Add(item.ToAppointment(exchangeService));
        }

        destinationFolder = exchangeService.FindFolders
            (WellKnownFolderName.MsgFolderRoot, searchFilter,
                folderView).FirstOrDefault();
        var paginatedMeetings
            = new PagingCollection<Appointment>(meetings);

        ServiceResponseCollection<ServiceResponse> responses = null;

        logResult.Append(String.Format("Creating calendar item(s) ({0}) in batches of {1}...:",
              meetings.Count(),
                paginatedMeetings.PageSize));

        for (int i = 1; i <= paginatedMeetings.PagesCount; i++)
        {
            logResult.Append(string.Format("{0}/{1}...", i, paginatedMeetings.PagesCount));

            var items = paginatedMeetings.GetData(i);

            var saveResult = exchangeService.CreateItems(items,
                    destinationFolder.Id, MessageDisposition.SaveOnly,
                        SendInvitationsMode.SendToNone);

            result.Responses.Add(saveResult);

            if (saveResult.OverallResult == ServiceResult.Success)
            {
                logResult.Append(string.Format("OK, "));
            }
            else if (responses.OverallResult == ServiceResult.Warning)
            {
                logResult.AppendLine("there were warnings when saving items");

                foreach (ServiceResponse response in responses)
                {
                    if (response.Result == ServiceResult.Error)
                    {
                        logResult.AppendLine("Error code: " + response.ErrorCode.ToString());
                        logResult.AppendLine("Error message: " + response.ErrorMessage);
                    }
                }
            }
            else if (responses.OverallResult == ServiceResult.Error)
            {
                Console.WriteLine("there were errors when saving items");

                foreach (ServiceResponse response in responses)
                {
                    if (response.Result == ServiceResult.Error)
                    {
                        logResult.AppendLine("Error code: " + response.ErrorCode.ToString());
                        logResult.AppendLine("Error message: " + response.ErrorMessage);
                    }
                }
            }
            else
            {
                throw new NotImplementedException();
            }

        }

       logResult.AppendLine("");

    }
    catch (Exception e)
    {
        logResult.AppendLine("Error: " + e.Message);
        result.Error = e;
        if(folderCreated)
        {
            logResult.AppendLine("Deleting the forlder...(without waiting for results)");
            try
            {
                exchangeService.FindFolders(WellKnownFolderName.MsgFolderRoot,
                        searchFilter, folderView).FirstOrDefault().Delete(DeleteMode.HardDelete);
            }
            catch
            {
                // Likely (Microsoft.Exchange.WebServices.Data.ServiceResponseException
                // or ServiceRequestException)
            }
        }
    }

    logResult.AppendLine("-----------------------------------------------------------------------");

    result.EventsCreationLog = logResult.ToString();

    return result;
}

Points of Interest

Whilst development I have been through few msdn articles which might be usefull for readers as well:

 http://cloudfinder.com/user-impersonation-settings-office-365/
 
 How to: Add appointments by using Exchange impersonation
 http://msdn.microsoft.com/en-us/library/office/dn722379(v=exchg.150).aspx
 
 How to: Create appointments and meetings by using EWS in Exchange 2013
 http://msdn.microsoft.com/en-us/library/office/dn495611(v=exchg.150).aspx
 
 Microsoft Exchange Web Services Managed API 2.2
 http://www.microsoft.com/en-us/download/confirmation.aspx?id=42951
 
 How to: Reference the EWS Managed API assembly
 http://msdn.microsoft.com/en-US/library/dn528373(v=exchg.150).aspx
 
 Exchange 2013: Create meetings programmatically
 http://code.msdn.microsoft.com/exchange/Exchange-2013-Create-79148637
 
 Get started with EWS Managed API client applications
 http://msdn.microsoft.com/library/dn567668(v=exchg.150).aspx
 
 How to: Get appointments and meetings by using EWS in Exchange
 http://msdn.microsoft.com/en-us/library/office/dn495614(v=exchg.150).aspx

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --