Public/Get-TntExchangeMailboxPermissionReport.ps1
|
function Get-TntExchangeMailboxPermissionReport { <# .SYNOPSIS Retrieves mailbox delegation permissions for all users in Microsoft 365 tenant. .DESCRIPTION This function retrieves and analyzes mailbox delegation permissions for all users in a Microsoft 365 tenant including: - Full Access permissions to mailboxes (who can access which mailboxes) - Send As permissions (who can send emails as other users) - Send on Behalf permissions (who can send emails on behalf of other users) Note: This function requires Exchange Online PowerShell for complete delegation permission coverage. .PARAMETER TenantId The Azure AD Tenant ID (GUID) to connect to. .PARAMETER ClientId The Application (Client) ID of the app registration created for mailbox permission reporting. .PARAMETER ClientSecret The client secret for the app registration. Use this for automated scenarios. .PARAMETER CertificateThumbprint The thumbprint of the certificate to use for authentication instead of client secret. .PARAMETER IncludeSystemAccounts Switch to include system and service accounts in the results. By default, these are excluded. .PARAMETER IncludeInheritedPermissions Switch to include inherited permissions in addition to explicitly granted permissions. By default, only explicit permissions are shown. .EXAMPLE Get-TntExchangeMailboxPermissionReport -TenantId $tenantId -ClientId $clientId -ClientSecret $secret Retrieves mailbox delegation permissions for all users. .EXAMPLE Get-TntExchangeMailboxPermissionReport -TenantId $tenantId -ClientId $clientId -ClientSecret $secret -IncludeSystemAccounts Retrieves permissions including known system accounts. .INPUTS None. This function does not accept pipeline input. .OUTPUTS System.Management.Automation.PSCustomObject Returns a structured object containing: - Summary: Statistics on permissions found - MailboxPermissions: Detailed list of delegation permissions .NOTES Author: Tom de Leeuw Website: https://systom.dev Module: TenantReports Required Permissions: - User.Read.All (Application) - Directory.Read.All (Application) - Exchange Online View-Only Recipients role (or higher) .LINK https://systom.dev #> [CmdletBinding(DefaultParameterSetName = 'ClientSecret')] [OutputType([System.Management.Automation.PSCustomObject])] param( [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidateNotNullOrEmpty()] [Alias('Tenant')] [string]$TenantId, [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'ClientSecret')] [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true, ParameterSetName = 'Certificate')] [Parameter(Mandatory = $false, ParameterSetName = 'Interactive')] [ValidatePattern('^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$')] [Alias('ApplicationId')] [string]$ClientId, [Parameter(Mandatory = $true, ParameterSetName = 'ClientSecret', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Secret', 'ApplicationSecret')] [SecureString]$ClientSecret, [Parameter(Mandatory = $true, ParameterSetName = 'Certificate', ValueFromPipelineByPropertyName = $true)] [ValidateNotNullOrEmpty()] [Alias('Thumbprint')] [string]$CertificateThumbprint, # Use interactive authentication (no app registration required). [Parameter(Mandatory = $true, ParameterSetName = 'Interactive')] [switch]$Interactive, [Parameter()] [switch]$IncludeSystemAccounts, [Parameter()] [switch]$IncludeInheritedPermissions ) begin { Write-Information 'Starting mailbox delegation permissions analysis...' -InformationAction Continue } process { try { # Establish connection $ConnectionParams = Get-ConnectionParameters -BoundParameters $PSBoundParameters $ConnectionInfo = Connect-TntGraphSession @ConnectionParams # Initialize results collection $MailboxPermissions = [System.Collections.Generic.List[PSObject]]::new() # Connect to Exchange Online (required - throw on failure) try { if ($PSCmdlet.ParameterSetName -eq 'ClientSecret') { $TokenParams = @{ TenantId = $TenantId ClientId = $ClientId ClientSecret = $ClientSecret Scope = 'Exchange' } $ExchangeToken = Get-GraphToken @TokenParams Connect-ExchangeOnline -Organization $TenantId -AccessToken $ExchangeToken.AccessToken -ShowBanner:$false -ErrorAction Stop } else { Connect-ExchangeOnline -AppId $ClientId -CertificateThumbprint $CertificateThumbprint -Organization $TenantId -ShowBanner:$false -ErrorAction Stop } Write-Verbose 'Successfully connected to Exchange Online.' } catch { $PSCmdlet.ThrowTerminatingError([System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Exchange Online connection required: $($_.Exception.Message)"), 'ExchangeConnectionError', [System.Management.Automation.ErrorCategory]::ConnectionError, $null )) } # Retrieve all mailboxes via Exchange Online Write-Verbose 'Retrieving mailboxes via Exchange Online...' $MailboxParams = @{ ResultSize = 'Unlimited' Properties = 'RecipientTypeDetails', 'PrimarySmtpAddress', 'DisplayName', 'UserPrincipalName', 'GrantSendOnBehalfTo' } $TargetMailboxes = Get-EXOMailbox @MailboxParams | Where-Object { $_.RecipientTypeDetails -in @('UserMailbox', 'SharedMailbox', 'RoomMailbox', 'EquipmentMailbox') } if (-not $IncludeSystemAccounts) { $TargetMailboxes = $TargetMailboxes | Where-Object { $_.Name -notmatch '^(HealthMailbox|SystemMailbox|DiscoverySearchMailbox|Migration\.|FederatedEmail\.)' } } # Process mailbox delegation permissions using parallel processing Write-Verbose "Processing $($TargetMailboxes.Count) mailboxes..." $MailboxPermissions = $TargetMailboxes | ForEach-Object -Parallel { $Mailbox = $_ $Results = [System.Collections.Generic.List[PSObject]]::new() # FullAccess try { $FullAccess = Get-EXOMailboxPermission -Identity $Mailbox.UserPrincipalName -ErrorAction SilentlyContinue | Where-Object { $_.User -notmatch '^(NT AUTHORITY\\SELF|S-1-5-.*)' -and $_.AccessRights -contains 'FullAccess' } if (-not $using:IncludeInheritedPermissions) { $FullAccess = $FullAccess | Where-Object { -not $_.IsInherited } } foreach ($Perm in $FullAccess) { $Results.Add([PSCustomObject]@{ MailboxIdentity = $Mailbox.UserPrincipalName MailboxDisplayName = $Mailbox.DisplayName GrantedTo = $Perm.User AccessRights = 'FullAccess' PermissionType = 'FullAccess' IsInherited = $Perm.IsInherited }) } } catch { Write-Warning "Failed to retrieve FullAccess for $($Mailbox.UserPrincipalName)" } # SendAs try { $SendAs = Get-EXORecipientPermission -Identity $Mailbox.UserPrincipalName -ErrorAction SilentlyContinue | Where-Object { $_.Trustee -notmatch '^(NT AUTHORITY\\SELF|S-1-5-.*)' -and $_.AccessRights -contains 'SendAs' } foreach ($Perm in $SendAs) { $Results.Add([PSCustomObject]@{ MailboxIdentity = $Mailbox.UserPrincipalName MailboxDisplayName = $Mailbox.DisplayName GrantedTo = $Perm.Trustee AccessRights = 'SendAs' PermissionType = 'SendAs' IsInherited = $false # SendAs is rarely inherited in the same way }) } } catch { Write-Warning "Failed to retrieve SendAs for $($Mailbox.UserPrincipalName)" } # SendOnBehalf - No new API call needed; data exists on the $Mailbox object passed in. if ($Mailbox.GrantSendOnBehalfTo) { foreach ($Delegate in $Mailbox.GrantSendOnBehalfTo) { $Results.Add([PSCustomObject]@{ MailboxIdentity = $Mailbox.UserPrincipalName MailboxDisplayName = $Mailbox.DisplayName GrantedTo = $Delegate # Usually returns Name/ID, may need resolution if you want UPN AccessRights = 'SendOnBehalf' PermissionType = 'SendOnBehalf' IsInherited = $false }) } } return $Results } -ThrottleLimit 20 # Adjust based on tenant size/throttling tolerance $Summary = [PSCustomObject]@{ TotalMailboxesAnalyzed = $TargetMailboxes.Count TotalPermissionsFound = $MailboxPermissions.Count FullAccessPermissions = ($MailboxPermissions | Where-Object PermissionType -EQ 'FullAccess').Count SendAsPermissions = ($MailboxPermissions | Where-Object PermissionType -EQ 'SendAs').Count SendOnBehalfPermissions = ($MailboxPermissions | Where-Object PermissionType -EQ 'SendOnBehalf').Count UniqueGrantees = ($MailboxPermissions.GrantedTo | Select-Object -Unique).Count } # Full report object Write-Information "Mailbox delegation permissions analysis completed - $($Summary.TotalPermissionsFound) permissions found" -InformationAction Continue [PSCustomObject]@{ Summary = $Summary MailboxPermissions = $MailboxPermissions } } catch { $errorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Get-TntExchangeMailboxPermissionReport failed: $($_.Exception.Message)", $_.Exception), 'GetTntExchangeMailboxPermissionReportError', [System.Management.Automation.ErrorCategory]::OperationStopped, $TenantId ) $PSCmdlet.ThrowTerminatingError($errorRecord) } finally { # Cleanup connections try { if ($ConnectionInfo.ShouldDisconnect) { Disconnect-TntGraphSession -ConnectionState $ConnectionInfo } } catch { Write-Verbose "Could not disconnect from services: $($_.Exception.Message)" } } } } |