当前位置: 首页 > 知识库问答 >
问题:

使用EWS在其他用户的日历中创建约会有时会失败,因为服务器不可用

葛桐
2023-03-14

大约一年前,我的任务是使用PowerShell/EWS在公司用户的日历中创建约会。我很幸运,在www上找到了一个很好用的脚本(代码如下)。脚本导入多个CSV文件,并为CSV中的每一行创建一个约会。剧本运行了几个月没有任何问题。但在过去几周里,剧本不断失败。但它并不总是失败。有时候重启一下就够了。有时需要3或4次重启。它不会在同一个文件上失败,而且--正如所说的--它并不总是失败。所以我想脚本和处理的数据必须是有效的。

当脚本失败时,它通常会说“请求失败。远程服务器返回错误(503)。服务器不可用”。错误并不总是相同的,但总是与exchange服务器的连接失败有关。在许多情况下,会显示我应该在其中输入exchange online凭据的弹出窗口。但显然我之前输入了这些凭据(加密密码的导入)。所以我想连接中断了,所以我被要求重新输入它们。

没有防火墙或AV阻止连接。我的网络连接没有中断...

param([string]$CSVFileName,[string]$EmailAddress,[string]$Username,[string]$Password,[string]$Domain,[bool]$Impersonate,[string]$EwsUrl,[string]$EWSManagedApiPath);
 
#
# Import-CalendarCSV.ps1
#
# By David Barrett, Microsoft Ltd. Use at your own risk.
# C:\Program Files\Microsoft\Exchange Server\V14\Bin

Function ShowParams()
{
    Write-Host "Import-CalendarCSV -CSVFileName <string> -EmailAddress <string>";
    Write-Host "                   [-Username <string> -Password <string> [-Domain <string>]]";
    Write-Host "                   [-Impersonate <bool>]";
    Write-Host "                   [-EwsUrl <string>]";
    Write-Host "                   [-EWSManagedApiPath <string>]";
    Write-Host "";
    Write-Host "Required:";
    Write-Host " -CSVFileName : Filename of the CSV file to import appointments for this user from.";
    Write-Host " -EmailAddress : Mailbox SMTP email address";
    Write-Host "";
    Write-Host "Optional:";
    Write-Host " -Username : Username for the account being used to connect to EWS (if not specified, current user is assumed)";
    Write-Host " -Password : Password for the specified user (required if username specified)";
    Write-Host " -Domain : If specified, used for authentication (not required even if username specified)";
    Write-Host " -Impersonate : Set to $true to use impersonation.";
    Write-Host " -EwsUrl : Forces a particular EWS URl (otherwise autodiscover is used, which is recommended)";
    Write-Host " -EWSManagedApiDLLFilePath : Full and path to the DLL for EWS Managed API (if not specified, default path for v1.1 is used)";
    Write-Host "";
}

$RequiredFields=@{
    "Subject" = "Subject";
    "StartDate" = "Start Date";
    "StartTime" = "Start Time";
    "EndDate" = "End Date";
    "EndTime" = "End Time"
}
 
# Check email address
# if (!$EmailAddress)
# {
#   ShowParams;
#    throw "Required parameter EmailAddress missing";
# }
 
# CSV File Checks
if (!$CSVFileName)
{
    ShowParams;
    throw "Required parameter CSVFileName missing";
}
if (!(Get-Item -Path $CSVFileName -ErrorAction SilentlyContinue))
{
    throw "Unable to open file: $CSVFileName";
}
 
# Import CSV File
try
{
    $CSVFile = Import-Csv -Path $CSVFileName;
}
catch { }
if (!$CSVFile)
{
    Write-Host "CSV header line not found, using predefined header: Subject;StartDate;StartTime;EndDate;EndTime";
    $CSVFile = Import-Csv -Path $CSVFileName -header Subject,StartDate,StartTime,EndDate,EndTime;
}

# Check file has required fields
foreach ($Key in $RequiredFields.Keys)
{
    if (!$CSVFile[0].$Key)
    {
        # Missing required field
        throw "Import file is missing required field: $Key";
    }
}
 
# Check EWS Managed API available
 if (!$EWSManagedApiPath)
 {
     $EWSManagedApiPath = "C:\Program Files\Microsoft\Exchange\Web Services\2.0\Microsoft.Exchange.WebServices.dll"
 }
 if (!(Get-Item -Path $EWSManagedApiPath -ErrorAction SilentlyContinue))
 {
     throw "EWS Managed API could not be found at $($EWSManagedApiPath).";
 }
 
# Load EWS Managed API
 [void][Reflection.Assembly]::LoadFile($EWSManagedApiPath);
 
# Create Service Object.  We only need Exchange 2007 schema for creating calendar items (this will work with Exchange>=12)
$service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2)

# Set credentials if specified, or use logged on user.
 if ($Username -and $Password)
 {
     if ($Domain)
     {
         $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password,$Domain);
     } else {
         $service.Credentials = New-Object  Microsoft.Exchange.WebServices.Data.WebCredentials($Username,$Password);
     }
     
} else {
     $service.UseDefaultCredentials = $true;
 }
 

    # Set EWS URL if specified, or use autodiscover if no URL specified.
if ($EwsUrl)
{
    $service.URL = New-Object Uri($EwsUrl);
}
else
{
    try
    {
        Write-Host "Performing autodiscover for $EmailAddress";
        $service.AutodiscoverUrl($EmailAddress, {$true});
    }
    catch
    {
        throw;
    }
}
    

 
# Bind to the calendar folder
 

# Parse the CSV file and add the appointments
foreach ($CalendarItem in $CSVFile)
{ 
    # Create the appointment and set the fields
    $NoError=$true;

    if ($Impersonate)
    {
        $service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $CalendarItem."Email");
    }
    
    
    try {
        $CalendarFolder = [Microsoft.Exchange.WebServices.Data.CalendarFolder]::Bind($service, [Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar);
    } catch {
        throw;
    }

    try
    {
        $Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment($service);
        $Appointment.Subject=$CalendarItem."Subject";
        $StartDate=[DateTime]($CalendarItem."StartDate" + " " + $CalendarItem."StartTime");
        $Appointment.Start=$StartDate;
        $EndDate=[DateTime]($CalendarItem."EndDate" + " " + $CalendarItem."EndTime");
        $Appointment.End=$EndDate;
            $Appointment.LegacyFreeBusyStatus = [Microsoft.Exchange.WebServices.Data.LegacyFreeBusyStatus]::Busy;
            $Appointment.IsAllDayEvent= $CalendarItem."IsAllDayEvent";
            $Appointment.IsReminderSet= $CalendarItem."IsReminderSet";
    }
    catch
    {
        # If we fail to set any of the required fields, we will not write the appointment
        $NoError=$false;
    }
    
    # Check for any other fields
    foreach ($Field in ($CalendarItem | Get-Member -MemberType Properties))
    {
        if (!($RequiredFields.Keys -contains $Field.Name))
        {
            # This is a custom (optional) field, so try to map it
            try
            {
                $Appointment.$($Field.Name)=$CalendarItem.$($Field.Name);
            }
            catch
            {
                # Failed to write this field
                Write-Host "Failed to set custom field $($Field.Name)" -ForegroundColor yellow;
            }
        }
    }

    if ($NoError)
    {
        # Save the appointment
        $Appointment.Save([Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToNone)
        Write-Host "Created $($CalendarItem."Subject")" -ForegroundColor green;
    }
    else
    {
        # Failed to set a required field
        Write-Host "Failed to create appointment: $($CalendarItem."Subject")" -ForegroundColor red;
    }
}
Get-PSSession | Remove-PSSession

$AdminName = “XXXXXXXX@XXXXXXX.COM”
$Pass = Get-Content “C:\Scripte\CalendarUpdate\cred.txt” | ConvertTo-SecureString
$Cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $AdminName, $Pass
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession $Session

$TCredentials = New-Object System.Management.Automation.PSCredential $AdminName, $Pass
$TPassword = $TCredentials.GetNetworkCredential().Password

$Emails = import-csv "C:\Scripte\CalendarUpdate\Users.csv"

ForEach ($i in $Emails) {
    $TEMP = Search-Mailbox $i.Emails -SearchQuery 'Subject:"Frei/Abwesend - Automatische Anlage"' -SearchDumpster:$false -EstimateResultOnly
    While($TEMP.ResultItemsCount -ne 0){
        Search-Mailbox $i.Emails -SearchQuery 'Subject:"Frei/Abwesend - Automatische Anlage"' -SearchDumpster:$false -DeleteContent -Force
        $TEMP = Search-Mailbox $i.Emails -SearchQuery 'Subject:"Frei/Abwesend - Automatische Anlage"' -SearchDumpster:$false -EstimateResultOnly
    }
    $userfilename = 'C:\Scripte\CalendarUpdate\Users\' + $i.Emails + '.csv'
    C:\Scripte\CalendarUpdate\Import-CalendarCSV.ps1 -CSVFileName $userfilename -Username XXXXXXX@XXXXXXX.com -Password $TPassword -Impersonate $true -EwsUrl https://outlook.office365.com/EWS/Exchange.asmx -EWSManagedApiPath "C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll"
    Remove-Item $userfilename
}

stop-Process -Name powershell

我真的希望这里的任何人都能帮我做这件事!提前多谢了。亲切地问候蒂姆

共有1个答案

陈瀚
2023-03-14

第一,从这个类似的问题:

首先要从Github https://Github.com/officedev/ews-managed-api获取最新版本的EWS托管API。您使用的版本[2.2.0]自2015年以来就没有更新过,当时Microsoft停止发布该库的编译版本。但是,在GitHub上,该代码已经更新并修复了许多bug

如果问题通常是暂时的,则可以向第二个脚本添加重试循环

$retries = 0
While ($retries -lt 3) {
  Try   {
    C:\Scripte\CalendarUpdate\Import-CalendarCSV.ps1 @params
    $retries = 3
  }
  Catch { $retries += 1 }
}

如果那些建议都不起作用,那么主脚本的哪一行是失败的呢?确切的错误信息是什么?

这可能是由azureAd或O365中的各种问题引起的,通常是无法与O365正确同步,因此也要检查服务器端的错误。

[编辑]从你的评论中的错误来看,问题可能与会话超时或其他被EXO限制有关。您可能希望尝试Exchange团队创建的以下模块:

robustCloudCommand-github:Functionstart-robustCloudCommand是一个包装脚本,它试图确保脚本块在O365中针对较大的对象计数成功完成执行。

[编辑]针对您关于使用-credential进行基本身份验证的评论,我在RobustCloudCommand.PSM1(位于(Get-moduleRobustCloudCommand).Path)中执行了以下操作:

#lines 104-105

[String]$UserPrincipalName,  ## Removed Mandatory
[pscredential]$credential,   ## Added -credential parameter


#lines 256-260:

# Create the session
Write-Log "Connecting to Exchange Online"
if ($credential) {Connect-ExchangeOnline -Credential $credential}
elseif ($UserPrincipalName) {Connect-ExchangeOnline -UserPrincipalName $UserPrincipalName -ShowBanner:$false}
else {Write-Error 'Either -Credential or -UserPrincipalName must be used to create a session'}
 类似资料:
  • 我们正在尝试使用EWS管理的应用编程接口集成办公365。我们使用办公365管理中心创建了一个具有模拟角色的服务号。现在,我们如何使用该服务号访问应用程序中其他用户的数据(如邮件、联系人、日历)?谢谢,罗希特

  • 您好,我正在使用服务帐户和添加的与会者创建一个活动。回复正确,但与会者没有收到邀请邮件。该服务账户未与任何谷歌账户链接(共享)。不过,为了登录API控制台,我使用了我的gmail帐户。日历Id被指定为主日历Id。活动将在哪里创建,在服务帐户的日历上还是在我的主日历上?。我在日历上看不到,但回复状态为“已确认”。我更感兴趣的是邀请,而不是最初的活动是在哪里创建的。 任何帮助都将不胜感激!!!

  • 我想编写一些代码来查询Exchange服务器,以查找一组用户的日历条目。这可能吗? 我不熟悉使用EWS。我发现很难找到任何好的概述文档来指导我如何使用它。 无论如何,我正在尝试的代码是这样的: 最后一行抛出一个Service响应异常:"在存储区中找不到指定的文件夹。" 上述方法是否接近正确?也许有一种完全不同的更好的方法来实现这一点? 请注意,我上面的服务对象是一个ExchangeService对

  • 我需要在日历上创建谷歌日历活动,并将其他用户添加为该活动的参与者。其目的是向应用程序用户发送日历事件,而无需征得他们的同意(O-Auth)。 在阅读了谷歌的留档后,我发现我需要一个服务号。所以我从我们的G-Suite的一个电子邮件地址创建了一个项目和一个服务号,noreply@xxxxx.com并为其启用了日历应用编程接口。 我创建并下载了一个密钥对(JSON),其内容是, 根据文档,我开始编写身

  • 问题内容: PHP中有没有一种方法可以获取服务器上所有会话的列表(以及每个会话中的变量)? 基本上,我们具有维护功能,该功能需要知道当前哪些用户登录到该站点。我们已经在会话变量中存储了每个用户的数据,但是我希望可以遍历每个会话并提取所需的数据。 我的PHP非常有限(我通常是.NET开发人员),但是如果有人知道这样做是否可行(以及如何做到),我将不胜感激。我用谷歌搜索,发现的结果倾向于表明这是不可能

  • 我正在尝试使用SpringBootInitialize创建一个SpringCloudEureka服务器。 但是我得到了下面的例外。 java.lang.IllegalStateExcture:未能加载应用程序上下文造成的:org.springframework.beans.factory.不满意依赖异常:错误创建名为'org.springframework.cloud.netflix.eureka