Search code examples
powershellcalendarexchange-serverexchangewebservicesimpersonation

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


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://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2010/dd633680(v=exchg.80)

https://learn.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
} 

Solution

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