65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 可执行文件通过 Microsoft Exchange Web Services Managed API 2.2 创建 Exchange 日历事件

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2016年3月9日

CPOL

4分钟阅读

viewsIcon

27604

downloadIcon

569

C# .Net 命令行应用程序,可通过模拟身份在 Exchange 日历中创建日历事件

下载 EWSCalendarUpdater.zip
下载 Nuget_packages.zip

引言

在某些情况下,系统服务或管理员可能需要在不同的 Exchange 帐户之间创建日历事件,例如学校课表或任何类型的组织课表。该应用程序通过模拟 Exchange 帐户,并在 Exchange 帐户的日历中创建事件。

提供的代码是一个 C#.Net 命令行应用程序,具有几个参数,例如 CSV 输入文件、线程数、最大尝试次数、租户管理员凭据。它利用 Microsoft Exchange Web Services Managed API 2.2 (EWS),并已在 Office 365 的 Exchange 服务(显然是 2013 版本)上进行了测试。当然,EWS 作为一项 Web 服务,有时会超时并引发异常。代码会重新尝试推送失败的保存,直到成功或达到设定的最大尝试次数。

此项目的最终目的是在数百个帐户之间创建相当数量的事件。到目前为止,已经进行了几次测试,在 599 个用户帐户中处理了 800,000 条记录。在测试期间,有一定比例(10%)的事件创建失败,应用程序进行了 5 次重试以成功推送所有内容。总共花费了近 12 个小时。可能还可以进一步优化,但目前已满足其目的。

此代码使用了 Command Line Parser LibraryCsvHelper 库以及一个名为 "PagingCollection" 的类。感谢各位作者!

使用代码

该命令行应用程序消耗一个包含事件记录的 CSV 文件。记录由 5 个字段组成。虽然这对于本项目来说已经足够,但当然可以添加更多字段来创建更详细的事件。待创建的事件会打包成每批 100 个事件的请求。EWS 服务请求按用户帐户进行多线程处理,并采用指定的线程数。

该应用程序需要以下输入参数。

--user:必需,此应用程序已针对作为 Office 365 服务一部分的 Exchange Server 开发并进行了测试。对于“user”,使用了租户管理员的电子邮件。此代码尚未在独立 Exchange 上进行测试。

--method:创建事件的方法。目前只有一种真正有效的方法:“DeleteFolderAndCreateEventsFromScratch”。这显然是一种非常原始的“更新”事件的方式,通过删除目标文件夹来重新创建一切。然而,这在开发时符合目的。EWS 公开了某些可能通过比较和更新(如果需要)来提供真正更新的方法。

--read: 必需,输入 CSV 文件和路径,示例 CSV 文件内容如下

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: 必需,Exchange 日历文件夹。如果文件夹不存在,将被创建。如果文件夹存在,将被删除并重新创建。这可能取决于“method”参数,但目前只有一个选项:“DeleteFolderAndCreateEventsFromScratch”。

--threads:(默认为 10),在创建大量线程时,此参数可能有助于优化整体性能。请求被分批到指定的线程数。过多的线程数也可能降低性能,因此可能需要单独测试。

--attempts:(默认为 10),总尝试次数。代码会重新尝试创建失败的保存,直到完全成功和/或达到指定的次数。

--verbose: 是否显示所有消息。

从命令行执行已编译程序的示例

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

执行后的控制台窗口。在此示例中,将在 599 个用户之间创建 800000 个事件。

大约 5 小时后,控制台窗口。它在 2 次总尝试内成功创建了所有事件。

 

下面提供的代码是应用程序的核心方法,对应于同名的唯一选项:“DeleteFolderAndCreateEventsFromScratch”。该方法可以分解成功能块,但在此示例中保留为一个方法。它当然可以分解成至少以下更小的功能:

  • 冒充身份
  • 如果 Exchange 文件夹已存在,则删除
  • 创建文件夹
  • 创建事件。

 

        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;
        }

关注点

在开发过程中,我查阅了一些 MSDN 文章,这些文章对读者可能也很有用:

 http://cloudfinder.com/user-impersonation-settings-office-365/
 
 如何:使用 Exchange 模拟添加约会
 http://msdn.microsoft.com/en-us/library/office/dn722379(v=exchg.150).aspx
 
 如何:使用 EWS 在 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
 
 如何:引用 EWS Managed API 程序集
 http://msdn.microsoft.com/en-US/library/dn528373(v=exchg.150).aspx
 
 Exchange 2013:以编程方式创建会议
 http://code.msdn.microsoft.com/exchange/Exchange-2013-Create-79148637
 
 开始使用 EWS Managed API 客户端应用程序
 http://msdn.microsoft.com/library/dn567668(v=exchg.150).aspx
 
 如何:使用 EWS 在 Exchange 中获取约会和会议
 http://msdn.microsoft.com/en-us/library/office/dn495614(v=exchg.150).aspx

© . All rights reserved.