As an Exchange administrator, a long-standing source of frustration for me has been the fact that Microsoft does not provide tools for administratively inspecting and repairing delegate settings. Apart from granting Full Access permissions on the mailbox and logging into it with Outlook, all an Exchange admin can really do is inspect the GrandSendOnBehalfTo attribute (a.k.a. publicDelegates), tinker with relevant folder sharing permissions, and use MfcMAPI to nuke the hidden delegate forwarding rule when it is misbehaving, which is rather drastic as it stops ALL delegate forwarding for that mailbox.
Well, there is another option: turning to (or building) the tools to do the job. A fine example of just such a tool is the MessageOps EWS PowerShell Module, which builds upon Exchange Web Services to add a host of Exchange commands to PowerShell, including tools for dealing with delegates. While it is a fine tool which I’ve found quite useful, I got it into my head to write my own set of delegate management tools, if for no other reason than to get a better handle on how delegates actually work under the hood. In short, I’m a glutton for punishment, so I set about re-inventing the wheel….
The EWS Managed API
Since I’m not really well-versed in writing code that directly utilizes MAPI, it seemed a no-brainer for me to instead use the other MS-blessed Exchange protocol: Exchange Web Services (EWS). Conveniently, Microsoft provides a handy tool for leveraging that protocol, the EWS Managed API, a .NET-based DLL (downloadable from GitHub) which can be loaded into PowerShell and handles the grunt work of constructing and interpreting the SOAP/XML blobs that make up EWS. While Microsoft doesn’t do such a great job of documenting how to leverage this tool from within PowerShell, developer Glen Scales (who was apparently involved in creating the MessageOps tools) has filled the gap wonderfully with code examples on his blog.
Another convenient thing about the EWS Managed API is that it provides access to EWS-native calls for dealing with delegates, so this looked like it was exactly what I was needing. As it turned out (as we’ll shortly see), this wasn’t exactly the case, but it is worth taking a look at it anyway.
Load ‘Em Up
To make the tools I wanted to create convenient to use, I decided early on to implement them as standalone global functions as part of a single dot-sourced PowerShell script. Furthermore, since each of those functions would have to go through the process of loading the Managed API dll and creating a web services object, it made since to factor out that functionality into a local utility function, so we’ll start with that.
function Load-EWS { ## Load Managed API dll
First up, we need to search for and load the dll:
<#First, check for the EWS MANAGED API. If it is present, import the highest version. Otherwise, exit. #> $EWSDLL = (($(Get-ItemProperty -ErrorAction SilentlyContinue -Path Registry::$(Get-ChildItem -ErrorAction SilentlyContinue -Path 'Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Exchange\Web Services'|Sort-Object Name -Descending| Select-Object -First 1 -ExpandProperty Name)).'Install Directory') + "Microsoft.Exchange.WebServices.dll") if (Test-Path $EWSDLL) { Write-Verbose " Loading EWS Managed API..." Import-Module $EWSDLL } else { "This script requires the EWS Managed API." "Please download and install the current version of the EWS Managed API from" "https://github.com/OfficeDev/ews-managed-api" "" "Exiting Script." exit }
Now, create the service object. Note that I prefix the service object variable with “script:” when I instantiate it. This scopes the variable to make it available to whatever function calls this one.
# Set the Exchange Version variable $ExchangeVersion = [Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP2 $script:service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService($ExchangeVersion)
Now we need to get a little creative in terms of the credentials that EWS will use to access the remote mailbox. (I won’t be discussing impersonation here, since I haven’t really delved into it.) The credentials being used by EWS to access the target mailbox will need to be for an account which has been granted Full Access to the mailbox in question. Now, we could just tell EWS to use whatever credentials the script is running under (setting $service.UseDefaultCredentials = $true), but that presents a problem in our environment. We currently have a hybrid Exchange deployment, partly on-premise, partly in Office 365 Exchange Online. The UseDefaultCredentials option works fine for on-prem, but breaks in the cloud. EWS needs to be passed credentials where the username is in appropriate UserPrincipalName format to work in the cloud. For the purposes of my script, I’ve assumed that the appropriate credentials have been stashed in a variable called $EWScred by using the Get-Credential command.
# Tell the service object to use credentials stored in $EWScred variable, defined before invocation by running $EWScred=get-credential #$service.UseDefaultCredentials = $true $service.Credentials = New-Object Microsoft.Exchange.WebServices.Data.WebCredentials -ArgumentList $EWScred.UserName, $EWScred.GetNetworkCredential().Password
For the next step, we have to determine where the mailbox is in order to set the EWS connection endpoint. As mentioned previously, I wrote this script to run in a hybrid environment, so the target mailbox could exist either in our on-premise Exchange 2010 environment or in the cloud. There are two ways to accomplish this. One is to simply use autodiscover to find the mailbox and retrieve the endpoint URL. The other is to perform a Get-Recipient command in the local Exchange Management Shell, look at the recipient type returned, and set a hard-coded endpoint URL as appropriate. Although I was able to successfully use the autodiscover approach (and the appropriate code is shown below, commented-out), that slows down execution, so I opted for hard-coded URLs.
##CAS URL Option 1 Autodiscover #$Service.EnableSCPLookup = $False #Skip SCP lookups to make autodiscover of cloud mailboxes work. #$service.AutodiscoverUrl($Identity,{$True}) #The above line will only work if $Identity holds the SMTP address of the target mailbox. #CAS URL Option 2 Hardcoded $localuri=[system.URI] "https://myexchangeCAS.mydomaindomain.com/ews/exchange.asmx" $clouduri=[system.URI] "https://outlook.office365.com/ews/exchange.asmx" # Here is the reason for running this in an EMS session. $script.rec=get-recipient $Identity if ($rec.RecipientType -eq "MailUser") { $script:CloudUser = $True $Script:MailboxType = "Remote Mailbox" $service.Url = $clouduri} else { $script:CloudUser = $False $Script:MailboxType = "Local Mailbox" $service.Url = $localuri} } # End of utility function Load-EWS()
And that is all there is too it for our initial setup. We’ll be calling this utility function at the beginning of each of the global functions demonstrated throughout this series of articles. With that housekeeping code out of the way, we can get down to the heavy lifting.
Invoking and Parsing GetDelegates()
Using the EWS GetDelegates() function is actually quite straightforward. First, we’ll define our function and its one input parameter, the UPN identifier for the mailbox we are looking at. After that, we’ll call the utility function that we created to load EWS, then we’ll invoke the GetDelegates() method on our service object.
function Global:Get-EWSDelegates { param ( [parameter( Mandatory=$true, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Position=0, HelpMessage=’Identity of target mailbox’)] [Alias("Id")] [STRING]$Identity ) Load-EWS Write-Verbose "Using CAS URI $($Service.url) for $($MailboxType) $($rec.PrimarySmtpAddress)" #Get Delegates for Mailbox Write-Verbose "Getting delegates for $($rec.PrimarySmtpAddress):" $EWSdelegates = $service.getdelegates($rec.PrimarySmtpAddress,$true)
That really is it. All of our delegate info has been stuffed into the variable $EWSdelegates. Now let’s parse it and display the contents.
# Parse returned object and display results Write-Host "MeetingRequestsDeliveryScope: $($EWSdelegates.MeetingRequestsDeliveryScope)" # Enumerate Delegates and their settings foreach($Delegate in $EWSdelegates.DelegateUserResponses){ write-host "`n`r`tDelegate: $($Delegate.DelegateUser.UserId.DisplayName) <$($Delegate.DelegateUser.UserId.PrimarySmtpAddress)>" write-host "`tReceives copies of meeting messages: $($Delegate.DelegateUser.ReceiveCopiesOfMeetingMessages)" write-host "`tDelegate can view private items: $($Delegate.DelegateUser.ViewPrivateItems)" $Delegate.DelegateUser.Permissions | select CalendarFolderPermissionLevel,TasksFolderPermissionLevel,InboxFolderPermissionLevel,ContactsFolderPermissionLevel,NotesFolderPermissionLevel | ft if ($Delegate.Result -like "Error") { Write-Host "Result: $($Delegate.Result)" -ForegroundColor Red Write-Host "ErrorCode: $($Delegate.ErrorCode)" -ForegroundColor Red Write-Host "ErrorMessage: $($Delegate.ErrorMessage)" -ForegroundColor Red } # End if error } # End foreach Delegate in $EWSDelegates return $EWSDelegates # Return the object to the EMS command line. } # End function Get-EWSDelegates()
Note the error handling code. That is pretty important, as we shall see very shortly when we look at how all of this flies off the rails. For now, though, here is some sample output:
PS C:\> Get-EWSDelegates -Id "User, Joe Sr." MeetingRequestsDeliveryScope: DelegatesAndSendInformationToMe Delegate: User, Jane <jane.user@utexas.edu> Receives copies of meeting messages: True Delegate can view private items: True CalendarFolderPermissionLevel TasksFolderPermissionLevel InboxFolderPermissionLevel ContactsFolderPermissionLevel NotesFolderPermissionLevel ----------------------------- -------------------------- -------------------------- ----------------------------- -------------------------- Editor Editor None None None DelegateUserResponses MeetingRequestsDeliveryScope --------------------- ---------------------------- {Microsoft.Exchange.WebServices.Data.DelegateUserResponse} DelegatesAndSendInformationToMe
Modifying or Adding Delegates
Adding or modifying delegates is just as straightforward. All we really have to do is perform a GetDelegates() operation, modify the resulting object as needed, and write it back to the server. Here, I’ve documented the steps taken by the function with inline comments. Note that I’ve included a help block to enable Get-Help for the function. This function also provides an example of input validation in the parameter block.
function Global:Add-EWSDelegate { <# .SYNOPSIS Adds delegate to target mailbox or modifies existing delegate. .DESCRIPTION Takes the identity of the user and adds a delegate with the specified settings, or modifies the delegate settings if it is already present. This script must be run in an Exchange Management Shell session. Full Access permissions must have been granted on the mailbox prior to running the script. Also, the credentials for the Full Access accunt must be placed in a variable called $EWScred via the Get-Credential command, specifying the username in UserPrincipalName format. .PARAMETER Identity Identity of target mailbox .PARAMETER Delegate Identity of the delegate being added .PARAMETER GetsMeetingRequests Boolean value specifying whether or not meeting requests are forwarded to this delegate .PARAMETER CanSeePrivate Boolean value specifying whether or not this delegate can see the delegator's private items .PARAMETER CalendarRights Delegate permissions on the Calendar folder. Allowed values include None, Editor, Reviewer, Author .PARAMETER TasksRights Delegate permissions on the Tasks folder. Allowed values include None, Editor, Reviewer, Author .PARAMETER InboxRights Delegate permissions on the Inbox folder. Allowed values include None, Editor, Reviewer, Author .PARAMETER ContactsRights Delegate permissions on the Contacts folder. Allowed values include None, Editor, Reviewer, Author .PARAMETER NotesRights Delegate permissions on the Notes folder. Allowed values include None, Editor, Reviewer, Author .EXAMPLE Add-EWSDelegate.ps1 -Identity userA@yoyodyne.com -Delegate userB@yoyodyne.com -CalendarRights Editor -GetsMeetingRequests #> param ( [parameter( Mandatory=$true, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Position=0, HelpMessage=’Identity of target mailbox’)] [Alias("Id")] [STRING]$Identity, [parameter( Mandatory=$true, Position=1, HelpMessage='Identity of delegate')] [STRING]$Delegate, [BOOLEAN]$GetsMeetingRequests, [BOOLEAN]$CanSeePrivate, [ValidateSet('None','Editor','Reviewer','Author')] [STRING]$CalendarRights, [ValidateSet('None','Editor','Reviewer','Author')] [STRING]$TasksRights, [ValidateSet('None','Editor','Reviewer','Author')] [STRING]$InboxRights, [ValidateSet('None','Editor','Reviewer','Author')] [STRING]$ContactsRights, [ValidateSet('None','Editor','Reviewer','Author')] [STRING]$NotesRights ) # End parameters Write-Verbose "Loading EWS" Load-EWS <# Let's get the primarySmtpAddress of the new delegate so that we can properly compare with what is on the list, just in case what has been provided is a secondary address. #> $NewDelegate=$Null $NewDelegate = Get-Recipient $Delegate $Delegate = $NewDelegate.PrimarySmtpAddress if ($NewDelegate -eq $null) { Write-Host "Requested delegate $($Delegate) does not appear to be valid!" -ForegroundColor Red return $error[0] } <# Now, we need to make sure the delegate is in the same place as the target mailbox. Delegation does not work across orgs. For example, an on-premise user cannot be made a delegate for a cloud mailbox. #> if ($NewDelegate.RecipientType -eq "MailUser") {$DelegateMailboxType = "Remote Mailbox" } elseif ($NewDelegate.RecipientType -eq "UserMailbox") {$DelegateMailboxType = "Local Mailbox" } elseif ($NewDelegate.RecipientType -eq "MailUniversalSecurityGroup") { Write-Host "Groups are not supported for delegation via EWS." -Foregroundcolor Red # More about this in a bit. Return } else { Write-Host "Mailbox type $($NewDelegate.RecipientType), $($NewDelegate.RecipientTypeDetails) unsupported for delegation in EWS." -ForegroundColor Red return } if (($DelegateMailboxType -ne "Group") -and ($DelegateMailboxType -ne $MailboxType)) { Write-Host "Delegate and Delegator are not in the same place!" -ForegroundColor Red Write-Host "Mailbox $($rec.PrimarySmtpAddress) is a $($MailboxType), but $($Delegate) is a $($DelegateMailboxType)" -ForegroundColor Red return } Write-Verbose "Good news, everyone! $($MailboxType) $($rec.PrimarySmtpAddress) and $($DelegateMailboxType) $($Delegate) are in the same place!" # Our proposed delegate is legit. Let's get on with it. # We'll retrieve a copy of the existing delegates. $EWSdelegates = $service.GetDelegates($rec.PrimarySmtpAddress,$true) # Let's see if the delegate is already present. If so, we'll just modify the settings. Otherwise, add.... $exists = $false Write-Verbose "Checking to see if delegate $($Delegate) is already in the list of $($EWSdelegates.Count) delegates" foreach ($ExistingDelegate in $EWSdelegates.DelegateUserResponses) { if ($ExistingDelegate.DelegateUser.UserId.PrimarySmtpAddress.ToLower() -eq $Delegate.ToLower()) { Write-Host "Delegate $($Delegate) is already present in delegates list. Updating permissions." $exists = $True # Here we modify the permissions and settings for this delegate. if ($CalendarRights -notlike $null) { $ExistingDelegate.DelegateUser.Permissions.CalendarFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$CalendarRights } if ($TasksRights -notlike $null) { $ExistingDelegate.DelegateUser.Permissions.TasksFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$TasksRights } if ($InboxRights -notlike $null) { $ExistingDelegate.DelegateUser.Permissions.InboxFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$InboxRights } if ($ContactsRights -notlike $null) { $ExistingDelegate.DelegateUser.Permissions.ContactsFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$ContactsRights } if ($NotesRights -notlike $null) { $ExistingDelegate.DelegateUser.Permissions.NotesFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$NotesRights } if ($GetsMeetingRequests -notlike $null) { $ExistingDelegate.DelegateUser.ReceiveCopiesOfMeetingMessages = $GetsMeetingRequests} if ($CanSeePrivate -notlike $null) { $ExistingDelegate.DelegateUser.ViewPrivateItems = $CanSeePrivate } try { $update= $service.UpdateDelegates($rec.PrimarySmtpAddress,$EWSdelegates.MeetingRequestsDeliveryScope,$ExistingDelegate.DelegateUser)} catch {write-host "Update delegates failed!" -ForegroundColor Red return $error[0] } } # End of section for modifying existing delegateee } # end foreach existingdelegate # Here we add the delegate if it isn't already on the list. if ($exists -eq $false) { Write-Host "Adding delegate $($Delegate)" $dgUser = new-object Microsoft.Exchange.WebServices.Data.DelegateUser($Delegate) # Use standard defaults on the following if they are not specified in parameters passed to the function. # For ViewPrivateItems, the default is false. For ReceiveCopiesOfMeetingMessages, the default is true. if ($CanSeePrivate -notlike $null) { $dgUser.ViewPrivateItems = $CanSeePrivate } else { $dgUser.ViewPrivateItems = $null } if ($GetsMeetingRequests -notlike $null) { $dgUser.ReceiveCopiesOfMeetingMessages = $GetsMeetingRequests } else { $dgUser.ReceiveCopiesOfMeetingMessages = $true } if ($CalendarRights -notlike $null) {$dgUser.Permissions.CalendarFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$CalendarRights } else {$dgUser.Permissions.CalendarFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None } if ($TasksRights -notlike $null) {$dgUser.Permissions.TasksFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$TasksRights } else {$dgUser.Permissions.TasksFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None } if ($InboxRights -notlike $null) { $dgUser.Permissions.InboxFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$InboxRights } else { $dgUser.Permissions.InboxFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None } if ($ContactsRights -notlike $null) { $dgUser.Permissions.ContactsFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$ContactsRights } else { $dgUser.Permissions.ContactsFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None } if ($NotesRights -notlike $null) { $dgUser.Permissions.NotesFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::$NotesRights } else { $dgUser.Permissions.NotesFolderPermissionLevel = [Microsoft.Exchange.WebServices.Data.DelegateFolderPermissionLevel]::None } $dgArray = new-object Microsoft.Exchange.WebServices.Data.DelegateUser[] 1 $dgArray[0] = $dgUser $service.AddDelegates($rec.PrimarySmtpAddress,$EWSdelegates.MeetingRequestsDeliveryScope,$dgArray) } # End of section adding new delegate } # End global function Add-EWSdelegate
And here is some sample output:
PS C:\> Add-EWSDelegate joe.user@austin.utexas.edu -Delegate "User, Todd L" -InboxRights Reviewer -GetsMeetingRequests $false Adding delegate toddlocaluser@austin.utexas.edu DelegateUser : Microsoft.Exchange.WebServices.Data.DelegateUser Result : Success ErrorCode : NoError ErrorMessage : ErrorDetails : {} ErrorProperties : {}
Removing a delegate is even more simple.
function Global:Remove-EWSDelegate { <# .SYNOPSIS Removes specified delegate from target mailbox .DESCRIPTION Takes the identifier of the target mailbox and removes the specified delegate from that mailbox. This script must be run in an Exchange Management Shell session. Full Access permissions must have been granted on the mailbox prior to running the script. Also, the credentials for the Full Access accunt must be placed in a variable called $EWScred via the Get-Credential command, specifying the username in UserPrincipalName format. .PARAMETER Identity Identifier of the target mailbox .PARAMETER Delegate Identifier of delegate to be removed .EXAMPLE Remove-EWSDelegate.ps1 -Id joe.user@mydomain.com -Delegate my.delegate@mydomain.com #> param ( [parameter( Mandatory=$true, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$True, Position=0, HelpMessage=’Identifier of target mailbox’)] [Alias("Id")] [STRING]$Identity, [parameter( Mandatory=$true, Position=1, HelpMessage='Identifier of delegate to be removed')] [STRING]$Delegate ) $DelegateToBeRemoved=$Null $DelegateToBeRemoved = Get-Recipient $Delegate if ($DelegateToBeRemoved.PrimarySmtpAddress -eq $null) { Write-Host "Specified delegate $($Delegate) does not appear to be valid!" -ForegroundColor Red return $error[0] } $Delegate = $DelegateToBeRemoved.PrimarySmtpAddress Write-Verbose "Loading EWS" Load-EWS Write-Verbose "Retrieving current list of delegates." $EWSdelegates = $service.GetDelegates($rec.PrimarySmtpAddress,$true) $removed=$false Write-Verbose "Searching current delegate list for delegate to be removed." foreach($CurrentDelegate in $EWSdelegates.DelegateUserResponses){ if($CurrentDelegate.DelegateUser.UserId.PrimarySmtpAddress.ToLower() -eq $Delegate.ToLower()){ Write-Host "Removing Delegate $($Delegate)!" $service.RemoveDelegates($rec.PrimarySmtpAddress,$Delegate) $removed=$true } # End of handler for matching delegate. } # End iterating through list of delegates. if ($removed -eq $false) {Write-Host "Delegate $($Delegate) not found! Verify address." -ForegroundColor Yellow } } # End of global function Remove-EWSDelegate()
And here is some sample output:
PS C:\> Remove-EWSDelegate joe.user -Delegate "User, Todd L" Removing Delegate toddlocaluser@austin.utexas.edu! DelegateUser : Result : Success ErrorCode : NoError ErrorMessage : ErrorDetails : {} ErrorProperties : {}
One thing we’ve not yet tinkered with is the MeetingRequestsDeliveryScope, which corresponds the settings in Outlook’s delegate window specifying whether meeting requests and responses are delivered to delegate only but with a copy of requests and responses delivered to delegator, delegates only, or both. That is simple enough to modify.
function Global:Set-EWSDelegates { <# .SYNOPSIS Modifies global delegate settings on target mailbox. .DESCRIPTION Sets global delegate settings on target mailbox. This corresponds to the settings in Outlook's delegate window specifying whether meeting requests and responses are delivered to delegate only but with a copy of requests and responses delivered to delegator, delegates only, or both. This script must be run in an Exchange Management Shell session. Full Access permissions must have been granted on the mailbox prior to running the script. Also, the credentials for the Full Access accunt must be placed in a variable called $EWScred via the Get-Credential command, specifying the username in UserPrincipalName format. .PARAMETER Identifier Identifier of target mailbox. .PARAMETER DeliveryScope Determines to whom meeting requests and responses are sent. This parameter can take the following values: * DelegatesOnly * DelegatesAndMe * DelegatesAndSendInformationToMe * NoForward .EXAMPLE Set-EWSDelegates.ps1 -UPN mbx@myDomain.com -DeliveryScope DelegatesAndMe #> param ( [parameter( Mandatory=$true, Position=0, HelpMessage=’Identifier of target mailbox’)] [Alias("Id")] [STRING]$Identity, [parameter( Mandatory=$True, Position=1, HelpMessage=’Who should receive notifications’)] [ValidateSet('DelegatesOnly','DelegatesAndMe','DelegatesAndSendInformationToMe','NoForward')] [STRING]$DeliveryScope ) Write-Verbose "Loading EWS." Load-EWS Write-Verbose "Retrieving current delegate settings." $EWSdelegates = $service.GetDelegates($rec.PrimarySmtpAddress,$true) Write-Verbose "Writing back delegate object with new scope." $EWSupdatedDelegates = $service.UpdateDelegates($rec.PrimarySmtpAddress,$DeliveryScope,$EWSdelegates.DelegateUserResponses[0].DelegateUser) }
So, What’s the Catch?
So far so good. The built-in delegate functions seem to work quite nicely. However, there is one problem. They don’t work with delegation to groups, neither in terms of adding groups as delegates or displaying group delegates. Here is what we see running Get-EWSDelegates if a group has been set as one of the delegates.
PS C:\> Get-EWSDelegates joe.user MeetingRequestsDeliveryScope: DelegatesAndMe Delegate: <> Receives copies of meeting messages: Delegate can view private items: Result: Error ErrorCode: ErrorDelegateNoUser ErrorMessage: The delegate does not map to a user in the Active Directory. Delegate: User, Jane <jane.user@utexas.edu> Receives copies of meeting messages: True Delegate can view private items: True CalendarFolderPermissionLevel TasksFolderPermissionLevel InboxFolderPermissionLevel ContactsFolderPermissionLevel NotesFolderPermissionLevel ----------------------------- -------------------------- -------------------------- ----------------------------- -------------------------- Editor Editor None None None DelegateUserResponses MeetingRequestsDeliveryScope --------------------- ---------------------------- {Microsoft.Exchange.WebServices.Data.DelegateUserResponse, Microsoft.Exchang... DelegatesAndMe
This is a problem in the Exchange environment which I manage, since we routinely set up mail-enabled universal groups as delegates on shared mailboxes to simplify their management. The MAPI protocols used by Windows Outlook happily allows this, but the delegate functions within EWS don’t know how to handle it. (This leads me to wonder how Outlook for Mac, which uses EWS, is able to set delegates. At some point I should decrypt and inspect the transactions between Outlook:mac and the server to see how that is pulled off.)
What to do, what to do? We’ll it seemed like the only solution was to bypass the built-in delegate functions and create my own, a topic which we’ll take up in the next installment.