Automate Exchange Online Mailbox Permission Security Audits with PowerShell

Mailbox permissions are one of the most sensitive yet frequently overlooked areas in Exchange Online security governance. Excessive or unmanaged mailbox delegation can expose organizations to:

  • unauthorized mailbox access
  • executive impersonation
  • insider threats
  • compliance violations
  • privilege abuse
  • silent mailbox monitoring

Permissions such as:

  • Full Access
  • Send As
  • Send on Behalf

can provide extensive access to sensitive communications and business data if not regularly reviewed.

This PowerShell automation solution helps administrators:

  • Audit mailbox delegation permissions
  • Detect risky mailbox access configurations
  • Identify external or unresolved delegates
  • Review shared mailbox exposure
  • Generate mailbox permission governance reports
  • Automatically email security audit summaries

The solution is ideal for:

  • Exchange Online security governance
  • privileged access reviews
  • mailbox delegation audits
  • compliance monitoring
  • insider threat investigations

🚀 Community Edition Released!

Try the M365Corner Microsoft 365 Reporting Tool — your DIY pack with 20+ out-of-the-box M365 reports for Users, Groups, and Teams.

Why Mailbox Permission Audits Matter

Mailbox delegation permissions are commonly assigned for:

  • executive assistants
  • shared mailbox collaboration
  • service desk operations
  • operational workflows

However, over time these permissions can become:

  • stale
  • excessive
  • undocumented
  • externally exposed

Unreviewed mailbox permissions increase the risk of:

  • unauthorized mailbox access
  • executive impersonation
  • silent data exposure
  • privilege abuse

Regular mailbox permission audits help organizations maintain stronger Exchange Online governance and reduce mailbox exposure risks.


Difference Between Full Access, Send As, and Send on Behalf

Permission Type Description
Full Access Allows opening and reading mailbox contents
Send As Allows sending emails as the mailbox identity
Send on Behalf Allows sending emails on behalf of the mailbox owner

Among these permissions:

  • Send As permissions are treated as the highest risk
  • Full Access permissions expose mailbox contents
  • Send on Behalf permissions are generally less risky but still require governance oversight

Why External or Unresolved Delegates Are Risky

The script treats delegates as higher risk if they cannot be verified as internal Microsoft 365 recipients.

This includes:

  • external accounts
  • unresolved trustees
  • deleted or stale accounts
  • unknown delegate objects

External or unresolved delegates should always be reviewed because they may indicate:

  • stale access
  • unauthorized mailbox delegation
  • orphaned permissions
  • security governance gaps

Prerequisites

Install the required PowerShell modules:

                                
Install-Module ExchangeOnlineManagement -Scope CurrentUser
Install-Module Microsoft.Graph -Scope CurrentUser
                                
                            

Connect to Exchange Online:

Connect-ExchangeOnline

Connect to Microsoft Graph:

                                
Connect-MgGraph -Scopes `
"Domain.Read.All",
"Mail.Send"

                                
                            

The Script

                            
# Import required modules
Import-Module ExchangeOnlineManagement
Import-Module Microsoft.Graph

# Connect to Exchange Online
Connect-ExchangeOnline

# Verify EXO connection
$EXOConnection = Get-ConnectionInformation

if (-not $EXOConnection) {
    Write-Host "Exchange Online connection failed. Please reconnect using Connect-ExchangeOnline." -ForegroundColor Red
    return
}

# Connect to Microsoft Graph
Connect-MgGraph -Scopes `
"Domain.Read.All",
"Mail.Send"

# Report path
$ReportPath = "C:\Reports\MailboxPermissionSecurityAudit.csv"

# Email settings
$Sender = "admin@contoso.com"
$EmailRecipient = "securityteam@contoso.com"

# Internal verified domains
$AcceptedDomains = (
    Get-MgDomain |
    Where-Object {
        $_.IsVerified -eq $true
    }
).Id

$AuditReport = @()

# Get user and shared mailboxes
$Mailboxes = Get-EXOMailbox `
-ResultSize Unlimited `
-RecipientTypeDetails UserMailbox,SharedMailbox `
-Properties GrantSendOnBehalfTo

foreach ($Mailbox in $Mailboxes) {

    Write-Host "Processing mailbox: $($Mailbox.UserPrincipalName)" -ForegroundColor Cyan

    # Full Access permissions
    $FullAccessPermissions = Get-EXOMailboxPermission `
    -Identity $Mailbox.UserPrincipalName `
    -ErrorAction SilentlyContinue |
    Where-Object {
        $_.AccessRights -contains "FullAccess" -and
        $_.IsInherited -eq $false -and
        $_.User -notlike "NT AUTHORITY\SELF"
    }

    foreach ($Permission in $FullAccessPermissions) {

        $DelegateIdentity = $Permission.User.ToString()
        $DelegateName = $DelegateIdentity
        $DelegateAddress = "Unknown"
        $DelegateType = "Unknown"
        $DomainCategory = "Unknown"

        try {
            $ResolvedDelegate = Get-EXORecipient `
            -Identity $DelegateIdentity `
            -ErrorAction Stop

            $DelegateName = $ResolvedDelegate.DisplayName
            $DelegateType = $ResolvedDelegate.RecipientTypeDetails

            if ($ResolvedDelegate.PrimarySmtpAddress) {
                $DelegateAddress = $ResolvedDelegate.PrimarySmtpAddress.ToString()

                $Domain = (
                    $DelegateAddress -split "@"
                )[-1].ToLower()

                if ($AcceptedDomains -contains $Domain) {
                    $DomainCategory = "Internal"
                }
                else {
                    $DomainCategory = "External or Unknown"
                }
            }
        }
        catch {
            $DomainCategory = "Unresolved"
        }

        $RiskScore = 30
        $Severity = "Medium"
        $Recommendation = "Review Full Access permission"

        if ($Mailbox.RecipientTypeDetails -eq "SharedMailbox") {
            $RiskScore += 10
            $Recommendation += "; validate shared mailbox access"
        }

        if ($DomainCategory -ne "Internal") {
            $RiskScore += 30
            $Severity = "High"
            $Recommendation += "; investigate external or unresolved delegate"
        }

        $AuditReport += [PSCustomObject]@{
            Mailbox         = $Mailbox.UserPrincipalName
            DisplayName     = $Mailbox.DisplayName
            MailboxType     = $Mailbox.RecipientTypeDetails
            PermissionType  = "Full Access"
            DelegateName    = $DelegateName
            DelegateAddress = $DelegateAddress
            DelegateType    = $DelegateType
            DomainCategory  = $DomainCategory
            Severity        = $Severity
            RiskScore       = $RiskScore
            Recommendation  = $Recommendation
        }
    }

    # Send As permissions
    $SendAsPermissions = Get-RecipientPermission `
    -Identity $Mailbox.UserPrincipalName `
    -ErrorAction SilentlyContinue |
    Where-Object {
        $_.AccessRights -contains "SendAs" -and
        $_.Trustee -notlike "NT AUTHORITY\SELF"
    }

    foreach ($Permission in $SendAsPermissions) {

        $DelegateIdentity = $Permission.Trustee.ToString()
        $DelegateName = $DelegateIdentity
        $DelegateAddress = "Unknown"
        $DelegateType = "Unknown"
        $DomainCategory = "Unknown"

        try {
            $ResolvedDelegate = Get-EXORecipient `
            -Identity $DelegateIdentity `
            -ErrorAction Stop

            $DelegateName = $ResolvedDelegate.DisplayName
            $DelegateType = $ResolvedDelegate.RecipientTypeDetails

            if ($ResolvedDelegate.PrimarySmtpAddress) {
                $DelegateAddress = $ResolvedDelegate.PrimarySmtpAddress.ToString()

                $Domain = (
                    $DelegateAddress -split "@"
                )[-1].ToLower()

                if ($AcceptedDomains -contains $Domain) {
                    $DomainCategory = "Internal"
                }
                else {
                    $DomainCategory = "External or Unknown"
                }
            }
        }
        catch {
            $DomainCategory = "Unresolved"
        }

        $RiskScore = 40
        $Severity = "High"
        $Recommendation = "Review Send As permission immediately"

        if ($DomainCategory -ne "Internal") {
            $RiskScore += 30
            $Severity = "Critical"
            $Recommendation += "; investigate external or unresolved Send As delegate"
        }

        $AuditReport += [PSCustomObject]@{
            Mailbox         = $Mailbox.UserPrincipalName
            DisplayName     = $Mailbox.DisplayName
            MailboxType     = $Mailbox.RecipientTypeDetails
            PermissionType  = "Send As"
            DelegateName    = $DelegateName
            DelegateAddress = $DelegateAddress
            DelegateType    = $DelegateType
            DomainCategory  = $DomainCategory
            Severity        = $Severity
            RiskScore       = $RiskScore
            Recommendation  = $Recommendation
        }
    }

    # Send on Behalf permissions
    if ($Mailbox.GrantSendOnBehalfTo) {

        foreach ($Delegate in $Mailbox.GrantSendOnBehalfTo) {

            $DelegateIdentity = $Delegate.ToString()
            $DelegateName = $DelegateIdentity
            $DelegateAddress = "Unknown"
            $DelegateType = "Unknown"
            $DomainCategory = "Unknown"

            try {
                $ResolvedDelegate = Get-EXORecipient `
                -Identity $DelegateIdentity `
                -ErrorAction Stop

                $DelegateName = $ResolvedDelegate.DisplayName
                $DelegateType = $ResolvedDelegate.RecipientTypeDetails

                if ($ResolvedDelegate.PrimarySmtpAddress) {
                    $DelegateAddress = $ResolvedDelegate.PrimarySmtpAddress.ToString()

                    $Domain = (
                        $DelegateAddress -split "@"
                    )[-1].ToLower()

                    if ($AcceptedDomains -contains $Domain) {
                        $DomainCategory = "Internal"
                    }
                    else {
                        $DomainCategory = "External or Unknown"
                    }
                }
            }
            catch {
                $DomainCategory = "Unresolved"
            }

            $RiskScore = 25
            $Severity = "Medium"
            $Recommendation = "Review Send on Behalf permission"

            if ($DomainCategory -ne "Internal") {
                $RiskScore += 25
                $Severity = "High"
                $Recommendation += "; investigate external or unresolved delegate"
            }

            $AuditReport += [PSCustomObject]@{
                Mailbox         = $Mailbox.UserPrincipalName
                DisplayName     = $Mailbox.DisplayName
                MailboxType     = $Mailbox.RecipientTypeDetails
                PermissionType  = "Send on Behalf"
                DelegateName    = $DelegateName
                DelegateAddress = $DelegateAddress
                DelegateType    = $DelegateType
                DomainCategory  = $DomainCategory
                Severity        = $Severity
                RiskScore       = $RiskScore
                Recommendation  = $Recommendation
            }
        }
    }
}

# Export report
$AuditReport | Export-Csv `
-Path $ReportPath `
-NoTypeInformation `
-Encoding UTF8

# Summary counts
$TotalFindings = $AuditReport.Count

$CriticalFindings = (
    $AuditReport |
    Where-Object {
        $_.Severity -eq "Critical"
    }
).Count

$HighFindings = (
    $AuditReport |
    Where-Object {
        $_.Severity -eq "High"
    }
).Count

$SendAsFindings = (
    $AuditReport |
    Where-Object {
        $_.PermissionType -eq "Send As"
    }
).Count

$ExternalDelegates = (
    $AuditReport |
    Where-Object {
        $_.DomainCategory -ne "Internal"
    }
).Count

# HTML preview
$HtmlPreview = (
    $AuditReport |
    Select-Object -First 10 |
    ConvertTo-Html -Fragment
)

# Email body
$EmailBody = @"
<html>
<body>

<h2>Exchange Online Mailbox Permission Security Audit</h2>

<p>The automated mailbox permission security audit has completed successfully.</p>

<ul>
<li>Total Permission Findings: $TotalFindings</li>
<li>Critical Findings: $CriticalFindings</li>
<li>High Severity Findings: $HighFindings</li>
<li>Send As Permissions Found: $SendAsFindings</li>
<li>External or Unresolved Delegates: $ExternalDelegates</li>
</ul>

<p>Below is a preview of the first 10 permission findings:</p>

$HtmlPreview

</body>
</html>
"@

# Send report
$params = @{
    message = @{
        subject = "Exchange Online Mailbox Permission Security Audit"

        body = @{
            contentType = "HTML"
            content = $EmailBody
        }

        toRecipients = @(
            @{
                emailAddress = @{
                    address = $EmailRecipient
                }
            }
        )

        attachments = @(
            @{
                "@odata.type" = "#microsoft.graph.fileAttachment"
                name          = "MailboxPermissionSecurityAudit.csv"
                contentBytes  = [System.Convert]::ToBase64String(
                    [System.IO.File]::ReadAllBytes($ReportPath)
                )
            }
        )
    }

    saveToSentItems = "true"
}

Send-MgUserMail `
-UserId $Sender `
-BodyParameter $params

Write-Host "Mailbox permission security audit completed and emailed successfully." -ForegroundColor Green

How the Script Works

  1. Connects to Exchange Online and Microsoft Graph
  2. The script imports:

    • ExchangeOnlineManagement
    • Microsoft.Graph

    and establishes authenticated sessions using:

    
    Connect-ExchangeOnline
    Connect-MgGraph
                                    

    This allows the script to retrieve:

    • mailbox permissions
    • mailbox types
    • verified Microsoft 365 domains
    • email reporting capabilities

    before the security audit begins.

  3. Validates Exchange Online Connectivity
  4. The script verifies whether the Exchange Online session is active using:

    Get-ConnectionInformation

    If the connection fails, the script stops execution to prevent incomplete audit results.

  5. Retrieves Internal Microsoft 365 Domains
  6. The script retrieves verified Microsoft 365 domains using:

    Get-MgDomain

    These domains are treated as trusted internal domains and are later used to classify delegates as:

    • Internal
    • External or Unknown
    • Unresolved

    This improves mailbox permission governance visibility.

  7. Retrieves Exchange Online Mailboxes
  8. The script retrieves:

    • user mailboxes
    • shared mailboxes

    using: Get-EXOMailbox

    The mailbox properties retrieved include:

    • mailbox types
    • Send on Behalf delegates
    • mailbox identity information

    required for the audit.

  9. Audits Full Access Permissions
  10. The script retrieves Full Access mailbox permissions using:

    Get-EXOMailboxPermission

    Only explicit non-inherited permissions are audited.

    Full Access permissions allow delegates to:

    • open mailboxes
    • read emails
    • access mailbox contents

    making them important governance review targets.

  11. Audits Send As Permissions
  12. The script retrieves Send As permissions using:

    Get-RecipientPermission

    Send As permissions allow delegates to send emails as the mailbox identity.

    Because Send As permissions enable identity impersonation, they receive higher default risk scores and severity levels.

  13. Audits Send on Behalf Permissions
  14. The script audits Send on Behalf delegates using:

    GrantSendOnBehalfTo

    Send on Behalf permissions are generally treated as lower risk because recipients can usually see the delegated relationship.

    However, unresolved or external delegates still increase the risk score.

  15. Resolves Delegate Information
  16. For every mailbox delegate, the script attempts to resolve:

    • display names
    • recipient types
    • SMTP addresses

    using: Get-EXORecipient

    Delegates are then classified as:

    • Internal
    • External or Unknown
    • Unresolved

    This helps administrators identify:

    • stale delegates
    • unresolved trustees
    • potentially risky mailbox delegation
  17. Calculates Mailbox Permission Risk Scores
  18. The script assigns risk scores based on:

    • permission sensitivity
    • mailbox type
    • delegate trust level

    Higher risk scores indicate:

    • elevated mailbox exposure
    • privileged mailbox access
    • unresolved or external delegates
    • impersonation risks
  19. Severity Mapping Used by the Script
  20. Permission Scenario Base Score Severity
    Send on Behalf 25 Medium
    Full Access 30 Medium
    Full Access on Shared Mailbox 40 Medium
    Send As 40 High
    Full Access + External/Unresolved Delegate 60+ High
    Send on Behalf + External/Unresolved Delegate 50 High
    Send As + External/Unresolved Delegate 70 Critical

    The script increases severity levels when:

    • external delegates are detected
    • unresolved delegates exist
    • sensitive mailbox permissions are assigned

    This helps prioritize mailbox permission investigations.

  21. Generates Security Governance Reports
  22. The script generates a mailbox permission governance report containing:

    • mailbox names
    • permission types
    • delegate information
    • risk scores
    • severity levels
    • remediation recommendations

    The report is exported using:

    Export-Csv

    This provides:

    • operational visibility
    • mailbox governance reporting
    • auditability
    • compliance tracking
  23. Emails Security Audit Summaries Automatically
  24. The script automatically:

    • exports the CSV report
    • generates HTML summaries
    • emails mailbox permission statistics

    The email includes:

    • total findings
    • critical findings
    • high-risk permissions
    • Send As findings
    • external or unresolved delegates

    This makes the solution ideal for:

    • recurring mailbox permission audits
    • compliance reviews
    • Exchange Online governance monitoring
    • insider threat investigations

Real-World Use Cases

  • Executive Mailbox Reviews
  • Audit executive mailbox delegation permissions for excessive access.

  • Shared Mailbox Governance
  • Review shared mailbox exposure and delegation risks.

  • Insider Threat Monitoring
  • Identify suspicious or unresolved mailbox delegates.

  • Compliance and Security Audits
  • Generate mailbox delegation governance reports for security and compliance teams.

  • Automating Mailbox Permission Security Audits
  • You can automate this solution using:

    • Azure Automation
    • Windows Task Scheduler
    • GitHub Actions
    • Scheduled PowerShell Jobs

    This enables recurring mailbox permission governance reviews across Microsoft 365 environments.

Possible Errors and Solutions

Error Cause Solution
Access Denied The account lacks Exchange Online permissions. Ensure the account has:
  • Exchange Administrator permissions
  • appropriate mailbox audit permissions

before running the script.
Get-EXOMailboxPermission is not recognized Exchange Online PowerShell module is not installed or connected. Reconnect using:
Connect-ExchangeOnline
Insufficient privileges to complete the operation Microsoft Graph permissions are missing. Reconnect using:
Connect-MgGraph -Scopes `
"Domain.Read.All",
"Mail.Send"

Conclusion

Mailbox delegation permissions are one of the most sensitive components of Exchange Online governance. Excessive or unresolved mailbox access can expose organizations to:

  • privilege abuse
  • impersonation risks
  • unauthorized mailbox access
  • compliance violations
  • insider threats

This PowerShell automation solution helps organizations:

  • audit mailbox permissions
  • identify risky delegates
  • prioritize mailbox access reviews
  • improve governance visibility
  • automate Exchange Online security audits

By automating mailbox permission security audits, administrators can strengthen Exchange Online governance and maintain better visibility into sensitive mailbox delegation across Microsoft 365 environments.


Graph PowerShell Explorer Widget

20 Graph PowerShell cmdlets with easily accessible "working" examples.


Permission Required

Example:


                            


                            


                            

© Created and Maintained by LEARNIT WELL SOLUTIONS. All Rights Reserved.