【问题标题】:Create appointments in other uses' calendars using EWS sometimes fails because server is not available使用 EWS 在其他用户的日历中创建约会有时会失败,因为服务器不可用
【发布时间】:2021-07-27 00:12:33
【问题描述】:

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

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

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

我的问题是: 目前,发生错误时,powershell完全停止,脚本无法继续。是否可以更改此脚本以使其自行重新启动(包括凭据的导入)而不仅仅是失败?

脚本如下:

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

我真的希望这里的任何人都可以帮助我!提前非常感谢。 亲切的问候 蒂姆

【问题讨论】:

    标签: powershell exchange-server exchangewebservices


    【解决方案1】:

    首先,来自this的类似问题:

    首先要获取最新版本的 EWS 来自 Github https://github.com/OfficeDev/ews-managed-api 的托管 API。 您使用的版本 [2.2.0] 自 2015 年以来没有更新过 Microsoft 停止发布该库的编译版本。然而 代码已更新,在 GitHub 上修复了许多错误

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

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

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

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

    [编辑]从您评论中的错误来看,该问题可能与会话超时或受到 EXO 限制有关。您可能想尝试Exchange Team 创建的以下模块:

    RobustCloudCommand - Github:函数Start-RobustCloudCommand 是一个包装脚本,它试图确保脚本块在 O365 中针对大量对象成功完成执行。

    [编辑] 针对您关于使用-credential 进行基本身份验证的评论,我在RobustCloudCommand.psm1(位于(Get-Module RobustCloudCommand).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'}
    

    【讨论】:

    • 嗨,Cpt。鲸鱼,谢谢你的反馈!我使用以下代码行来更新 EWS:1) Register-PackageSource -provider NuGet -name nugetRepository -location nuget.org/api/v2 2) Install-Package Exchange.WebServices.Managed.Api。看起来更新工作 - >新版本是: 2.2.1.2 。这是正确的吗?我还实现了你的代码行。如果脚本下次运行正常,我会告诉你的!我们是否可以添加一些额外的代码行,以便如果连接丢失,脚本会再次导入登录数据?非常感谢!
    • 不幸的是,这并没有解决问题。昨天,该脚本在第一次尝试时运行良好。今天,脚本又失败了。错误消息(从德语翻译 -> 我无法用英语说出确切的错误描述):“在远程服务器上运行命令时出错。错误消息:由于线程或应用程序结束,E/A 进程被取消条件。您可以在帮助主题“about_remote_troubleshooting”中找到更多信息。操作已停止:(outlook.office365.com:String) PSRemotingTransportException.JobFailure.PSComutername:outlook.office365.com."。我真的需要帮助
    • PS:弹出窗口提示输入登录凭据。
    • @TimBauer 我基于该错误添加了另一个建议,这应该有助于允许更长的 exo 脚本在没有while 循环的情况下完成,但我自己没有使用它。您是否希望密码/凭据在命令之间更改?为什么要重新导入它们?
    • 谢谢,我试试这个。 “安装模块”命令只需要运行一次,对吧?我应该把“Start-RobustCloudCommand”放在哪里?第二个脚本的任何地方?我需要 cmdlet 的哪些参数?
    猜你喜欢
    • 2016-10-31
    • 2011-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 1970-01-01
    相关资源
    最近更新 更多