创建约会时使用 PowerShell 和 EWS 模拟原始会议组织者

Use PowerShell and EWS to impersonate original meeting organiser when creating appointment

我正在尝试使用 EWS(Exchange Web 服务)在 Exchange Online 的资源邮箱中创建约会 API。

我正在使用 O365 全局管理员帐户对 EWS 进行身份验证。

然后我冒充主办方,然后绑定邮箱日历文件夹。我已经为此设置了适当的管理 roles/scopes。

当我创建约会时,组织者显示为房间邮箱帐户,而不是模拟帐户。我看不出我做错了什么...

据我所知,我使用了多种资源:

https://docs.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd633680(v=exchg.80)

https://docs.microsoft.com/en-us/exchange/client-developer/exchange-web-services/delegate-access-and-ews-in-exchange

下面的代码成功地在 $roomMailbox 日历中创建了一个约会,尽管组织者被设置为会议室邮箱,而不是我试图模拟的组织者...

非常感谢任何指导。

using namespace Microsoft.Exchange.WebServices.Data

Set-StrictMode -Version 5.1

$ErrorActionPreference = 'Stop'

function Connect-EWS
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$false)]
        [System.Management.Automation.PSCredential]$Credential
    )

    try
    {
        [void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services.0\Microsoft.Exchange.WebServices.dll")
    }
    catch
    {
        throw "Could not import Microsoft Exchange web services library: $($_.Exception.Message)"
    }

    try
    {
        $ews = [ExchangeService]::New()
    }
    catch
    {
        throw "Could not create Microsoft.Exchange.WebServices.Data.ExchangeService object: $($_.Exception.Message)"
    }

    if($credential)
    {
        $ews.Credentials = $Credential.GetNetworkCredential()
    }
    else
    {
        $ews.UseDefaultCredentials = $true
    }

    $validateRedirectionUrlCallback = {

        Param([String]$Url)

        if($Url -eq "https://autodiscover-s.outlook.com/autodiscover/autodiscover.xml")
        {
            return $true
        } 
        else 
        {
            return $false
        }
    }

    try
    {
        $ews.AutodiscoverUrl($Credential.UserName,$validateRedirectionUrlCallback)
    }
    catch
    {
        throw "Autodiscover failed: $($_.Exception.Message)"
    }

    return $ews
}

function New-Appointment
{
    Param
    (
        [Parameter(Mandatory = $true)]
        [String]$Organiser
        ,
        [Parameter(Mandatory = $true)]
        [String]$RoomMailbox
        ,
        [Parameter(Mandatory = $true)]
        [DateTime]$Start
        ,
        [Parameter(Mandatory = $true)]
        [DateTime]$End
        ,
        [Parameter(Mandatory = $true)]
        [String]$Subject
        ,
        [Parameter(Mandatory = $true)]
        [String]$Location
    )


    try # Resolve the organiser ID
    {
        [Void]$ews.ResolveName($Organiser,[ResolveNameSearchLocation]::DirectoryOnly, $false)
    }
    catch
    {
        throw "Could not resolve Organiser identity: $Organiser : $($_.Exception.Message)"
    }

    try # Attempt to enable impersonation as the organiser
    {
        $ews.ImpersonatedUserId = [ImpersonatedUserId]::New([ConnectingIdType]::SmtpAddress, $Organiser)
    }
    catch
    {
        throw "Could not impersonate user $Organiser : $($_.Exception.Message)"
    }

    try # Create a new appointment object
    {
        $appointment = [Appointment]::New($ews)
    }
    catch
    {
        throw "Could not create appointment object: $($_.Exception.MEssage)"
    }

    # Add each of the properties below associated values into the appointment object

    $setProperties = 'Start','End','Subject','Location'

    foreach($p in $setProperties)
    {
        $appointment.$p = Get-Variable $p -ValueOnly
    }

    try # Set the folder ID as the calendar of the room mailbox
    {
        $folderId = [FolderId]::New([WellKnownFolderName]::Calendar, $RoomMailbox)
    }
    catch
    {
        throw "Could not generate target calendar folder id: $($_.Exception.Message)"
    }

    try # Try and bind the EWS connection to the folder
    {
        $folder = [Folder]::Bind($ews, $folderId)
    }
    catch
    {
        throw "Could not bind to user $($folderId.FolderName) $($_.Exception.Message)"
    }

    try # Save the appointment
    {
        $appointment.Save($folderId, [SendInvitationsMode]::SendToAllAndSaveCopy)
    }
    catch
    {
        throw "Could not save appointment as organiser: $Organiser : $($_.Exception.Message)"
    }
}

if(!$credential)
{
    $credential = Get-Credential -UserName $globalAdminUPN -Message "Please enter O365 credentials for user $globalAdminUPN"
}

$Organiser   = 'organiser@domain.com'
$RoomMailbox = 'roommailbox@domain.com'
$Start       = '01/02/2019 22:00'
$End         = '01/02/2019 23:00'
$Subject     = 'Test Appointment'
$Location    = 'Test Location'

$ews = Connect-EWS -Credential $credential

try
{
    New-Appointment -Organiser   $Organiser `
                    -RoomMailbox $RoomMailbox `
                    -Start       $Start `
                    -End         $End `
                    -Subject     $Subject `
                    -Location    $Location
}
catch
{
    Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
} 

你为什么不在这一行指定组织者?

$setProperties = 'Start','End','Subject','Location'

*现在阅读管理器是只读的并且自动设置但是我找到这篇文章The Organizer of an Appointment: The Dirty Truth,这里提到,appointment.SentOnBehalfOfName

同时检查这个 link

Add appointments by using Exchange impersonation

据我所知,我尝试做的事情似乎是不可能的,无论 impersonation/delegation.

我已经通过在组织者日历中创建约会并将会议室邮箱添加为与会者来改变解决问题的方式。这样就达到了我的目的,和之前一样的权利,也就是冒充组织者。

下面的脚本包含 Connect-EWS 和 New-Appointment 函数,并执行它们下面包含的这些函数。

需要admin@domain.onmicrosoft.com账号有主办方邮箱的模拟权限。

这仅适用于 Exchange Online,因为未使用自动发现,EWS 的 URL 是手动设置的。

using namespace Microsoft.Exchange.WebServices.Data

Set-StrictMode -Version 5.1

$ErrorActionPreference = 'Stop'

function Connect-EWS
{
    [CmdletBinding()]
    Param
    (
        [Parameter(Mandatory=$true)]
        [System.Management.Automation.PSCredential]$Credential
    )

    try
    {
        [void][Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services.2\Microsoft.Exchange.WebServices.dll")
    }
    catch
    {
        throw "Could not import Microsoft Exchange web services library: $($_.Exception.Message)"
    }

    try
    {
        $ews = [ExchangeService]::New()
    }
    catch
    {
        throw "Could not create Microsoft.Exchange.WebServices.Data.ExchangeService object: $($_.Exception.Message)"
    }

    if($credential)
    {
        $ews.Credentials = $Credential.GetNetworkCredential()
    }
    else
    {
        $ews.UseDefaultCredentials = $true
    }

    try # Set EWS URL
    {
        $ews.Url = 'https://outlook.office365.com/EWS/Exchange.asmx'
    }
    catch
    {
        throw "Could not set EWS URL: $($_.Exception.Message)"
    }

    return $ews
}

function New-Appointment
{
    Param
    (
        [Parameter(Mandatory = $true)]
        [String]$Organiser
        ,
        [Parameter(Mandatory = $true)]
        [DateTime]$Start
        ,
        [Parameter(Mandatory = $true)]
        [DateTime]$End
        ,
        [Parameter(Mandatory = $true)]
        [String]$Subject
        ,
        [Parameter(Mandatory = $false)]
        [String]$Location
        ,
        [Parameter(Mandatory = $false)]
        [Array]$RequiredAttendees
    )


    try # Resolve the organiser ID
    {
        [Void]$ews.ResolveName($Organiser,[ResolveNameSearchLocation]::DirectoryOnly, $false)
    }
    catch
    {
        throw "Could not resolve Organiser identity: $Organiser : $($_.Exception.Message)"
    }

    try # Attempt to enable impersonation as the organiser
    {
        $ews.ImpersonatedUserId = [ImpersonatedUserId]::New([ConnectingIdType]::SmtpAddress, $Organiser)
    }
    catch
    {
        throw "Could not impersonate user $Organiser : $($_.Exception.Message)"
    }

    try # Create a new appointment object
    {
        $appointment = [Appointment]::New($ews)
    }
    catch
    {
        throw "Could not create appointment object: $($_.Exception.MEssage)"
    }

    try # Add each required attendee to appointment
    {
        foreach($ra in $requiredAttendees)
        {
            [Void]$appointment.RequiredAttendees.Add($ra)
        }
    }
    catch
    {
        throw "Failed to add required attendee: $ra : $($_.Excecption.Message)"
    }

    # Add each of the properties below associated values into the appointment object

    $setProperties = 'Start','End','Subject','Location'

    foreach($p in $setProperties)
    {
        $appointment.$p = Get-Variable $p -ValueOnly
    }

    try # Set the folder ID as the calendar of the room mailbox
    {
        $folderId = [FolderId]::New([WellKnownFolderName]::Calendar, $Organiser)
    }
    catch
    {
        throw "Could not generate target calendar folder id: $($_.Exception.Message)"
    }

    try # Try and bind the EWS connection to the folder
    {
        $folder = [Folder]::Bind($ews, $folderId)
    }
    catch
    {
        throw "Could not bind to mailbox $($folderId.Mailbox) $($_.Exception.Message)"
    }

    try # Save the appointment
    {
        $appointment.Save($folderId, [SendInvitationsMode]::SendToAllAndSaveCopy)
    }
    catch
    {
        throw "Could not save appointment as organiser: $Organiser : $($_.Exception.Message)"
    }
}

$admin = 'admin@domain.onmicrosoft.com'

$credential = Get-Credential -UserName $admin -Message "Please enter O365 credentials for user $admin"

$Organiser   = 'organiser@domain.onmicrosoft.com'
$RoomMailbox = 'roommailbox@domain.onmicrosoft.com'
$Start       = '02/01/2019 12:00'
$End         = '02/01/2019 13:00'
$Subject     = 'Test Appointment'
$Location    = 'Test Location'

$requiredAttendees = $RoomMailbox

$ews = Connect-EWS -Credential $credential

try
{
    New-Appointment -Organiser         $Organiser `
                    -Start             $Start `
                    -End               $End `
                    -Subject           $Subject `
                    -Location          $Location `
                    -RequiredAttendees $requiredAttendees
}
catch
{
    Write-Host "ERROR: $($_.Exception.Message)" -ForegroundColor Red
}