1
votes

I am trying to create an appointment in a resource mailbox in Exchange Online using the EWS (Exchange Web Services) API.

I am authenticating with EWS using a O365 global admin account.

I am then impersonating the organiser, and then binding to the mailbox calendar folder. I have setup the appropriate management roles/scopes for this.

When I create the appointment the organiser appears as the room mailbox account, not the impersonated account. I cannot see what I am doing wrong...

I have used a variety of sources to get me as far as I have:

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

The code below successfully creates an appointment in the $roomMailbox calendar, although the organiser is set as room mailbox, not the organiser I am trying to impersonate...

Any guidance much appreciated.

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\2.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   = '[email protected]'
$RoomMailbox = '[email protected]'
$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
} 
2
did you tried to save it to general folder in a general mailbox instead of room-Mailbox, is it created with the right organizer?Avshalom
I am trying to save it to the calendar folder of a resource mailbox. I am impersonating the organizer but it sets the organizer as the room mailbox, not the organizer I am impersonating.supermerio
I know what you're asking but what I said is: did you tried to use general mailbox, not resource mailbox, and check if the impersonation working or not, let me know...Avshalom
I have attempted with a standard mailbox and a room mailbox with the same result although i believe I have come to conclusion/resolution. The organiser is always set as the owner of the mailbox the appointment is created in (unless i am mistaken). I should not be creating the meeting in the room mailbox, I should instead be creating the meeting in the organisers mailbox and adding the room mailbox as an attendee. This achieves what I want.supermerio

2 Answers

0
votes

Why don't you specify organiser on this line?

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

*Now reading that organiser is read-only and set automatically BUT I found this article The Organizer of an Appointment: The Dirty Truth, and here is mentioned, appointment.SentOnBehalfOfName

Also check this link

Add appointments by using Exchange impersonation

0
votes

It seems what I am trying to do is impossible as far as I can tell, the organiser is always set as the owner of the mailbox the appointment is being created in regardless of impersonation/delegation.

I have changed the way I am approaching the problem by creating the appointment in the organisers calendar and adding the room mailbox as an attendee. This achieves my goal with the same right as before, namely impersonation of organiser.

The script below contains Connect-EWS and New-Appointment functions with execution of those functions included beneath them.

It requires that the [email protected] account has impersonation rights on the organisers mailbox.

This will only work for Exchange Online as AutoDiscover is not used, the URL for EWS is set manually.

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.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 = '[email protected]'

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

$Organiser   = '[email protected]'
$RoomMailbox = '[email protected]'
$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
}