Update 11/11/11: Minor update made to resolve a potential issue when translating user name to SID. Inline code and download have been updated.
Getting delegate information has always been tricky. Information is stored in multiple places, you have to use different tools to get that information, and business requirements can add to the data needed to paint a complete picture for a given delegate. Taking advantage of the EWS Managed API to do the heavy lifting for information not easily exposed, I wrote this script to report delegate permissions and settings, mostly to help when troubleshooting why a delegate can’t do something or is getting weird behavior.
Using the GetDelegates() method of the managed API not only retrieves the delegates and their permissions to the well-known folders, but also whether private items are visible, if meeting requests are sent to a specific delegate, and the meeting request handling of the owner. In reality, though, that isn’t enough information to give the total picture. Full mailbox access supersedes folder permissions, so knowing who has that is needed. My company uses the registry modifications that control where deleted items and sent items, which means a delegate needs permission to those folders, so getting that is needed. Lastly, delegates are granted send on behalf of permission (which, incidentally, isn’t even required for a delegate to send a meeting request on behalf of the owner), but if a delegate has send as permission, that takes precedence when sending email messages, so knowing if a delegate has that right is also necessary.
The only required argument is an owner’s identity, such as email address, username, alias, etc. An optional switch parameter is -includeSendAs. I made it an opt-in feature because getting the send as information from AD makes the entire script run over 12 times longer (in my company’s infrastructure): 1.7 seconds versus 20.7 seconds for an owner with five delegates. Although I prefer to not hard-code information that can be determined dynamically, I took the poor man’s route for the EWS URL, so you need to enter that on line 26. If you don’t care about any of the details of the script contents, you can simply stop reading, download it, and run with it.
Get-Delegates.zip (2.3 KiB)
Thanks to the managed API, retrieving the delegates can be done in just six lines (four, if you specify the DLL path when loading it instead of putting it in a variable, and the same for setting the EWS URL).
$ewsuri=[system.URI]"https://owa.domain.com/ews/exchange.asmx"
$dllpath = "$env:ProgramFiles\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
[void][Reflection.Assembly]::LoadFile($dllpath)
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.Url = $ewsuri
$service.GetDelegates($owner,$true)
After getting the delegates, full mailbox access and, optionally, send as are retrieved. Because of the added time to get send as, I added a progress bar while information is being retrieved. The delegate properties stored in the owner’s mailbox include email address, display name, and SID. The property returned for full mailbox access and send as is username. Since there isn’t a common property between the two collections, the usernames must be converted to SIDs:
function GetSID($acl)
{
$aSID = @()
$acl | ForEach {
$adUser = [System.Security.Principal.NTAccount]($_.User)
$aSID += $adUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
$aSID
}
Because of the registry changes to store items deleted from the owner’s mailbox in the owner’s Deleted Items folder, and to store items sent by the delegate from the owner in the owner’s Sent Items folder, Author permission or higher is needed for those folders. This is easy to do with Get-MailboxFolderPermission.
Once all the information has been retrieved, you need to loop through the collection of delegates. When an orphaned delegate is on an owner’s mailbox, Exchange returns the delegate in the collection, but only as an error, not with any identifying information. I look for a matching error message and then direct you to the hidden message and property that lists all the delegates to find out which delegate is orphaned:
$delegates.DelegateUserResponses | ForEach {
if ($_.ErrorMessage -eq 'The delegate does not map to a user in the Active Directory.')
{
Write-Output "*Orphaned Delegate"
Write-Output " Check NON_IPM_SUBTREE\Freebusy Data\LocalFreebusy.eml,"
Write-Output " property 0x684A101E to determine orphan entry."
}
To report whether a given delegate has full mailbox access or send as permission, the SID of the delegate is matched against the respective array from the GetSID() function above:
if ($fmaSID -match $_.delegateuser.UserId.SID)
{
Write-Output " Mailbox-level permission: Full Mailbox Access"
}
The collection includes the granted permission to the well-known folders exposed in Outlook’s delegation wizard. Since it is rare for someone to use or grant permission to Journal or Notes, rather than just echo the permission array, I display just the other four folders:
Write-Output " Folder permissions"
Write-Output " Calendar: $($_.DelegateUser.Permissions.CalendarFolderPermissionLevel.ToString())"
Write-Output " Tasks: $($_.DelegateUser.Permissions.TasksFolderPermissionLevel.ToString())"
Write-Output " Inbox: $($_.DelegateUser.Permissions.InboxFolderPermissionLevel.ToString())"
Write-Output " Contacts: $($_.DelegateUser.Permissions.ContactsFolderPermissionLevel.ToString())"
To include the delegate’s potential permission to Deleted Items and Sent Items, I pass the respective folder permission object to the pipeline looking for a display name match:
[array]$delegateDIPerm = $deletedItemsPerm | where {$_.User -eq $delegateDisplayName}
if ($delegateDIPerm.Count -eq 1)
{
Write-Output " Deleted Items: $($delegateDIPerm[0].AccessRights[0].ToString())"
}
else
{
Write-Output " Deleted Items: None"
}
Lastly, whether a delegate receives meeting requests or can view private items is a property of the individual delegate’s entry in the collection. How the owner wants meeting requests handled is not per delegate, so that setting is a property of the root collection. All of this is then output to the success stream, whether that is the screen (the default) or redirection to a file.
You can download the script from the link above, or copy the contents below (double-click in code area to highlight all).
<#
.Synopsis
Display a mailbox's delegates and permissions.
.Description
Retrieve the list of delegates for a mailbox and display the mailbox permission,
folder permissions, meeting invite settings, and (optionally) whether the
delegate has Send As permission.
.Parameter MailboxOwner
Valid identity string of the user whose mailbox has the delegates.
.Parameter IncludeSendAs
Switch to indicate that you want Send As permission to be included.
.Example
get-delegates.ps1 user@domain.com -includesendas
.Example
get-delegates.ps1 domain\username
.Notes
Version: 1.21
Date: 11/9/11
#>
Param (
[Parameter(Position = 0,Mandatory = $true,HelpMessage="Identity of mailbox owner")][string]$mailboxOwner,
[Alias("SA")][switch]$includeSendAs #Perform Send As lookup (takes longer)
)
#Region Variables
$ewsuri=[system.URI]"https://owa.domain.com/ews/exchange.asmx" #EWS URL
#EndRegion
#Region Functions
function LoadAPI
{
$dllpath = "$env:ProgramFiles\Microsoft\Exchange\Web Services\1.1\Microsoft.Exchange.WebServices.dll"
if (-not(Test-Path $dllpath))
{
Write-Output "This system does not have the EWS Managed API installed, which is required to run this script."
Write-Output "The API can be download at http://www.microsoft.com/download/en/details.aspx?id=13480"
exit
}
[void][Reflection.Assembly]::LoadFile($dllpath)
}
function GetSID($acl)
{
$aSID = @()
$acl | ForEach {
$adUser = [System.Security.Principal.NTAccount]($_.User)
$aSID += $adUser.Translate([System.Security.Principal.SecurityIdentifier]).Value
}
$aSID
}
function GetDelegates($owner)
{
$service = new-object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2010_SP1)
$service.Url = $ewsuri
$service.GetDelegates($owner,$true)
}
function GetFMA($identity)
{
Get-MailboxPermission $identity | Where-Object {$_.IsInherited -eq $false}
}
function GetSendAs($identity)
{
Get-ADPermission $identity | Where-Object {$_.IsInherited -eq $false -and $_.ExtendedRights -eq 'Send-As'}
}
function GetFolderPermission($mailbox,$folder)
{
Get-MailboxFolderPermission "$mailbox`:\$folder"
}
#EndRegion
#Region Body
LoadAPI
try
{
$user = Get-User $mailboxOwner -Filter {RecipientType -eq 'UserMailbox'} -ErrorAction Stop
}
catch
{
Write-Output "`"$mailboxOwner`" cannot be found or does not have a mailbox. Verify the entered information."
exit
}
#Get list of delegates and permissions from EWS
Write-Progress -Activity "Getting Permissions for $($user.DisplayName)" -Status "Retrieving Delegates" -PercentComplete 0
$delegates = GetDelegates $user.WindowsEmailAddress.ToString()
#Get list of users with full mailbox access
Write-Progress -Activity "Getting Permissions for $($user.DisplayName)" -Status "Retrieving FMA List" -PercentComplete 40
$fullMailboxAccess = GetFMA $user.Identity
$fmaSID = GetSID $fullMailboxAccess #Convert username to SID
if ($includeSendAs)
{
#Get list of users with send as permission from AD
Write-Progress -Activity "Getting Permissions for $($user.DisplayName)" -Status "Retrieving Send As List" -CurrentOperation "(This part takes the longest.)" -PercentComplete 70
$sendAs = GetSendAs $user.Identity
$saSID = GetSID $sendAs #Convert username to SID
}
#Get permissions for additional folders
Write-Progress -Activity "Getting Permissions for $($user.DisplayName)" -Status "Retrieving additional folder permissions" -PercentComplete 90
$deletedItemsPerm = GetFolderPermission $user.Identity 'Deleted Items'
$sentItemsPerm = GetFolderPermission $user.Identity 'Sent Items'
Write-Progress -Activity "Getting Permissions for $($user.DisplayName)" -Completed $true
Write-Output "`n$($user.FirstName) $($user.LastName) has $($delegates.DelegateUserResponses.Count) delegate(s)"
#Loop through list of delegates
if ($delegates.DelegateUserResponses.Count -gt 0)
{
Write-Output "`nGlobal Delegate Meeting Request Handling: $($delegates.MeetingRequestsDeliveryScope)"
$delegates.DelegateUserResponses | ForEach {
Write-Output `n
#Delegate account deleted in AD but still listed in list
if ($_.ErrorMessage -eq 'The delegate does not map to a user in the Active Directory.')
{
Write-Output "*Orphaned Delegate"
Write-Output " Check NON_IPM_SUBTREE\Freebusy Data\LocalFreebusy.eml,"
Write-Output " property 0x684A101E to determine orphan entry."
}
else
{
$delegateDisplayName = $_.delegateuser.userid.displayname
Write-output "*$delegateDisplayName `($($_.delegateuser.userid.primarysmtpaddress)`)"
if ($fmaSID -match $_.delegateuser.UserId.SID)
{
Write-Output " Mailbox-level permission: Full Mailbox Access"
}
else
{
Write-Output " Mailbox-level permission: None"
}
if ($includeSendAs)
{
if ($saSID -match $_.delegateuser.UserId.SID)
{
Write-Output " Send As permission: True"
}
else
{
Write-Output " Send As permission: False"
}
}
Write-Output " Folder permissions"
Write-Output " Calendar: $($_.DelegateUser.Permissions.CalendarFolderPermissionLevel.ToString())"
Write-Output " Tasks: $($_.DelegateUser.Permissions.TasksFolderPermissionLevel.ToString())"
Write-Output " Inbox: $($_.DelegateUser.Permissions.InboxFolderPermissionLevel.ToString())"
Write-Output " Contacts: $($_.DelegateUser.Permissions.ContactsFolderPermissionLevel.ToString())"
[array]$delegateDIPerm = $deletedItemsPerm | where {$_.User -eq $delegateDisplayName}
if ($delegateDIPerm.Count -eq 1)
{
Write-Output " Deleted Items: $($delegateDIPerm[0].AccessRights[0].ToString())"
}
else
{
Write-Output " Deleted Items: None"
}
[array]$delegateSIPerm = $sentItemsPerm | where {$_.User -eq $delegateDisplayName}
if ($delegateSIPerm.Count -eq 1)
{
Write-Output " Sent Items: $($delegateSIPerm[0].AccessRights[0].ToString())"
}
else
{
Write-Output " Sent Items: None"
}
Write-Output " Receives Meeting Requests: $($_.delegateuser.receivecopiesofmeetingmessages)"
Write-Output " Can View Private Items: $($_.delegateuser.viewprivateitems)"
}
}
}
Write-Output `n
#EndRegion