Review Delegated Mailbox Permissions in Microsoft 365

A small PowerShell script for Microsoft 365 to review delegated mailbox permissions for a given user.

I had the need recently to find all mailboxes that a given user in Microsoft 365 has delegate access to. Whilst you can check any user or shared mailbox to easily see who has delegated permissions to that mailbox, there’s no straightforward way to show all mailboxes that any given user has been granted permission to.

This is further complicated by the three different types of permissions that a user can be given for a mailbox – Full Access, Send As, and Send on Behalf.

I’ve created a script, Get-UserDelegatedMailboxes.ps1, that will loop through all mailboxes in the tenancy and check if the given user has been granted permissions to the mailbox.

Save this script below as Get-UserDelegatedMailboxes.ps1 and then you can run it like so:

\Get-UserDelegatedMailboxes.ps1 -UserPrincipalName [email protected] -ExportCsv auto

Here’s the script:

<#
.SYNOPSIS
    Reports all mailboxes a specified user has delegated access to.
.DESCRIPTION
    Queries Exchange Online for Full Access, Send As, and Send on Behalf
    permissions granted TO the specified user across all mailboxes.
.PARAMETER UserPrincipalName
    UPN of the user to audit (e.g. [email protected])
.PARAMETER ExportCsv
    Optional. If specified, exports results to CSV.
    Pass 'auto' to generate a timestamped filename in the current directory,
    or provide an explicit path (e.g. C:\Reports\jsmith_access.csv).
.EXAMPLE
    .\Get-UserDelegatedMailboxes.ps1 -UserPrincipalName [email protected]

    Console output only — no file written.
.EXAMPLE
    .\Get-UserDelegatedMailboxes.ps1 -UserPrincipalName [email protected] -ExportCsv auto

    Exports to .\DelegatedAccess_jsmith_contoso_com_20250413.csv
.EXAMPLE
    .\Get-UserDelegatedMailboxes.ps1 -UserPrincipalName [email protected] -ExportCsv "C:\Reports\jsmith_access.csv"

    Exports to the specified path.
#>

param(
    [Parameter(Mandatory = $true)]
    [string]$UserPrincipalName,

    [switch]$ExportCsv
)
# Requires ExchangeOnlineManagement module
# Install-Module -Name ExchangeOnlineManagement -Scope CurrentUser

# Connect if not already connected
try {
    $null = Get-OrganizationConfig -ErrorAction Stop
} catch {
    Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan
    Connect-ExchangeOnline -ShowBanner:$false
}

# Resolve the user to confirm they exist and get display name
Write-Host "`nResolving user: $UserPrincipalName" -ForegroundColor Cyan
try {
    $targetUser = Get-Recipient -Identity $UserPrincipalName -ErrorAction Stop
} catch {
    Write-Error "User '$UserPrincipalName' not found in Exchange Online."
    exit 1
}
$displayName = $targetUser.DisplayName
Write-Host "Auditing delegated access for: $displayName ($UserPrincipalName)`n" -ForegroundColor Green

$results = @()

# --- Full Access ---
Write-Host "Checking Full Access permissions..." -ForegroundColor Cyan
$allMailboxes = Get-EXOMailbox -ResultSize Unlimited -PropertySets Minimum

foreach ($mbx in $allMailboxes) {
    $perms = Get-EXOMailboxPermission -Identity $mbx.Identity -User $UserPrincipalName -ErrorAction SilentlyContinue
    if ($perms | Where-Object { $_.AccessRights -contains 'FullAccess' -and $_.Deny -eq $false }) {
        $results += [PSCustomObject]@{
            MailboxDisplayName = $mbx.DisplayName
            MailboxUPN         = $mbx.UserPrincipalName
            PermissionType     = 'Full Access'
        }
    }
}

# --- Send As ---
Write-Host "Checking Send As permissions..." -ForegroundColor Cyan
foreach ($mbx in $allMailboxes) {
    $perms = Get-EXORecipientPermission -Identity $mbx.Identity -Trustee $UserPrincipalName -ErrorAction SilentlyContinue
    if ($perms | Where-Object { $_.AccessRights -contains 'SendAs' }) {
        $results += [PSCustomObject]@{
            MailboxDisplayName = $mbx.DisplayName
            MailboxUPN         = $mbx.UserPrincipalName
            PermissionType     = 'Send As'
        }
    }
}

# --- Send on Behalf ---
Write-Host "Checking Send on Behalf permissions..." -ForegroundColor Cyan
foreach ($mbx in $allMailboxes) {
    $grantedTo = $mbx | Select-Object -ExpandProperty GrantSendOnBehalfTo -ErrorAction SilentlyContinue
    if ($grantedTo) {
        $resolved = $grantedTo | ForEach-Object { Get-Recipient $_ -ErrorAction SilentlyContinue }
        if ($resolved | Where-Object { $_.PrimarySmtpAddress -eq $UserPrincipalName }) {
            $results += [PSCustomObject]@{
                MailboxDisplayName = $mbx.DisplayName
                MailboxUPN         = $mbx.UserPrincipalName
                PermissionType     = 'Send on Behalf'
            }
        }
    }
}

# --- Output ---
Write-Host "`n--- Delegated Mailbox Access for $displayName ---`n" -ForegroundColor Yellow

if ($results.Count -eq 0) {
    Write-Host "No delegated permissions found for this user." -ForegroundColor Gray
} else {
    $results | Sort-Object PermissionType, MailboxDisplayName | Format-Table -AutoSize
    Write-Host "Total: $($results.Count) permission entr$(if ($results.Count -eq 1) {'y'} else {'ies'}) found.`n" -ForegroundColor Green
}

# Optional CSV export
if ($ExportCsv) {
    $exportPath = ".\DelegatedAccess_$($UserPrincipalName -replace '@','_' -replace '\.','_')_$(Get-Date -Format 'yyyyMMdd').csv"
    $results | Export-Csv -Path $exportPath -NoTypeInformation -Encoding UTF8
    Write-Host "Exported to: $exportPath" -ForegroundColor Cyan
}

Keep This Useful

Spotted something outdated or unclear?

If a step has changed, a screenshot no longer matches, or something here just does not work the way it should, get in touch and we will take a look.