Src/Private/Get-AbrExoMailboxes.ps1

function Get-AbrExoMailboxes {
    <#
    .SYNOPSIS
    Documents Exchange Online mailbox inventory, shared mailboxes, and resource mailboxes.
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

    [CmdletBinding()]
    param (
        [Parameter(Position = 0, Mandatory)]
        [string]$TenantId
    )

    begin {
        Write-PScriboMessage -Message "Collecting Exchange Online Mailbox information for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'Mailboxes'
    }

    process {
        Section -Style Heading2 'Mailbox Inventory' {
            Paragraph "The following section documents mailbox inventory and configuration for tenant $TenantId."
            BlankLine

            #region Mailbox Summary
            try {
                Write-Host " - Retrieving mailbox inventory..."
                $AllMailboxes = Get-Mailbox -ResultSize Unlimited -ErrorAction Stop

                $UserMailboxes    = @($AllMailboxes | Where-Object { $_.RecipientTypeDetails -eq 'UserMailbox' })
                $SharedMailboxes  = @($AllMailboxes | Where-Object { $_.RecipientTypeDetails -eq 'SharedMailbox' })
                $RoomMailboxes    = @($AllMailboxes | Where-Object { $_.RecipientTypeDetails -eq 'RoomMailbox' })
                $EquipMailboxes   = @($AllMailboxes | Where-Object { $_.RecipientTypeDetails -eq 'EquipmentMailbox' })
                $GuestMailboxes   = @($AllMailboxes | Where-Object { $_.RecipientTypeDetails -eq 'GuestMailbox' })

                $SummaryObj = [System.Collections.ArrayList]::new()
                $summaryInObj = [ordered] @{
                    'Total Mailboxes'       = @($AllMailboxes).Count
                    'User Mailboxes'        = $UserMailboxes.Count
                    'Shared Mailboxes'      = $SharedMailboxes.Count
                    'Room Mailboxes'        = $RoomMailboxes.Count
                    'Equipment Mailboxes'   = $EquipMailboxes.Count
                    'Guest Mailboxes'       = $GuestMailboxes.Count
                }
                $SummaryObj.Add([pscustomobject]$summaryInObj) | Out-Null

                $SumTableParams = @{ Name = "Mailbox Summary - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                if ($Report.ShowTableCaptions) { $SumTableParams['Caption'] = "- $($SumTableParams.Name)" }
                $SummaryObj | Table @SumTableParams
            } catch {
                Write-ExoError 'Mailboxes' "Unable to retrieve mailboxes: $($_.Exception.Message)"
                Paragraph "Unable to retrieve mailbox data: $($_.Exception.Message)"
                return
            }
            #endregion

            #region User Mailbox Detail (InfoLevel 2)
            if ($InfoLevel.Mailboxes -ge 2 -and $UserMailboxes.Count -gt 0) {
                Section -Style Heading3 'User Mailboxes' {
                    Paragraph "The following $($UserMailboxes.Count) user mailbox(es) are configured in tenant $TenantId."
                    BlankLine

                    $MbxObj = [System.Collections.ArrayList]::new()
                    foreach ($Mbx in ($UserMailboxes | Sort-Object DisplayName)) {
                        $mbxInObj = [ordered] @{
                            'Display Name'          = $Mbx.DisplayName
                            'UPN'                   = $Mbx.UserPrincipalName
                            'Primary SMTP'          = $Mbx.PrimarySmtpAddress
                            'Archive Enabled'       = $Mbx.ArchiveStatus -ne 'None'
                            'Litigation Hold'       = $Mbx.LitigationHoldEnabled
                            'Single Item Recovery'  = $Mbx.SingleItemRecoveryEnabled
                            'Audit Enabled'         = $Mbx.AuditEnabled
                            'Forwarding Address'    = if ($Mbx.ForwardingSmtpAddress) { $Mbx.ForwardingSmtpAddress } elseif ($Mbx.ForwardingAddress) { $Mbx.ForwardingAddress } else { 'None' }
                            'Deliver & Forward'     = $Mbx.DeliverToMailboxAndForward
                        }
                        $MbxObj.Add([pscustomobject](ConvertTo-HashToYN $mbxInObj)) | Out-Null
                    }

                    $null = (& {
                        if ($HealthCheck.ExchangeOnline.Mailboxes) {
                            # Flag mailboxes with external forwarding configured
                            $null = ($MbxObj | Where-Object {
                                $_.'Forwarding Address' -ne 'None' -and
                                $_.'Forwarding Address' -notmatch $script:TenantDomain
                            } | Set-Style -Style Critical | Out-Null)
                            # Flag mailboxes without audit enabled
                            $null = ($MbxObj | Where-Object { $_.'Audit Enabled' -eq 'No' } | Set-Style -Style Warning | Out-Null)
                        }
                    })

                    $MbxTableParams = @{ Name = "User Mailboxes - $TenantId"; List = $false; ColumnWidths = 14, 18, 17, 7, 8, 8, 8, 14, 6 }
                    if ($Report.ShowTableCaptions) { $MbxTableParams['Caption'] = "- $($MbxTableParams.Name)" }
                    $MbxObj | Table @MbxTableParams

                    $script:ExcelSheets['User Mailboxes'] = $MbxObj
                }
            }
            #endregion

            #region Shared Mailboxes
            if ($SharedMailboxes.Count -gt 0) {
                Section -Style Heading3 'Shared Mailboxes' {
                    Paragraph "The following $($SharedMailboxes.Count) shared mailbox(es) are configured in tenant $TenantId."
                    BlankLine

                    $SharedObj = [System.Collections.ArrayList]::new()
                    foreach ($Mbx in ($SharedMailboxes | Sort-Object DisplayName)) {
                        # Get permissions for this shared mailbox
                        $FullAccessCount = 0
                        $SendAsCount = 0
                        try {
                            $FullAccessPerms = Get-MailboxPermission -Identity $Mbx.Identity -ErrorAction SilentlyContinue |
                                Where-Object { $_.AccessRights -contains 'FullAccess' -and -not $_.IsInherited -and $_.User -ne 'NT AUTHORITY\SELF' }
                            $FullAccessCount = @($FullAccessPerms).Count
                            $SendAsPerms = Get-RecipientPermission -Identity $Mbx.Identity -ErrorAction SilentlyContinue |
                                Where-Object { $_.AccessRights -contains 'SendAs' -and $_.Trustee -ne 'NT AUTHORITY\SELF' }
                            $SendAsCount = @($SendAsPerms).Count
                        } catch {}

                        $sharedInObj = [ordered] @{
                            'Display Name'          = $Mbx.DisplayName
                            'Primary SMTP'          = $Mbx.PrimarySmtpAddress
                            'Full Access Users'     = $FullAccessCount
                            'Send As Users'         = $SendAsCount
                            'Audit Enabled'         = $Mbx.AuditEnabled
                            'Litigation Hold'       = $Mbx.LitigationHoldEnabled
                            'Forwarding Address'    = if ($Mbx.ForwardingSmtpAddress) { $Mbx.ForwardingSmtpAddress } else { 'None' }
                        }
                        $SharedObj.Add([pscustomobject](ConvertTo-HashToYN $sharedInObj)) | Out-Null
                    }

                    $null = (& {
                        if ($HealthCheck.ExchangeOnline.Mailboxes) {
                            $null = ($SharedObj | Where-Object { $_.'Full Access Users' -eq '0' } | Set-Style -Style Warning | Out-Null)
                            $null = ($SharedObj | Where-Object { $_.'Forwarding Address' -ne 'None' } | Set-Style -Style Critical | Out-Null)
                        }
                    })

                    $SharedTableParams = @{ Name = "Shared Mailboxes - $TenantId"; List = $false; ColumnWidths = 18, 22, 10, 10, 10, 10, 20 }
                    if ($Report.ShowTableCaptions) { $SharedTableParams['Caption'] = "- $($SharedTableParams.Name)" }
                    $SharedObj | Table @SharedTableParams

                    $script:ExcelSheets['Shared Mailboxes'] = $SharedObj
                }
            }
            #endregion

            #region Resource Mailboxes (Rooms + Equipment)
            if ($InfoLevel.ResourceMailboxes -ge 1 -and ($RoomMailboxes.Count -gt 0 -or $EquipMailboxes.Count -gt 0)) {
                Section -Style Heading3 'Resource Mailboxes' {
                    Paragraph "The following resource mailboxes (rooms and equipment) are configured in tenant $TenantId."
                    BlankLine

                    $ResObj = [System.Collections.ArrayList]::new()
                    foreach ($Mbx in (($RoomMailboxes + $EquipMailboxes) | Sort-Object DisplayName)) {
                        $resInObj = [ordered] @{
                            'Display Name'         = $Mbx.DisplayName
                            'Type'                 = $Mbx.RecipientTypeDetails
                            'Primary SMTP'         = $Mbx.PrimarySmtpAddress
                            'Audit Enabled'        = $Mbx.AuditEnabled
                        }
                        $ResObj.Add([pscustomobject](ConvertTo-HashToYN $resInObj)) | Out-Null
                    }

                    $ResTableParams = @{ Name = "Resource Mailboxes - $TenantId"; List = $false; ColumnWidths = 30, 20, 35, 15 }
                    if ($Report.ShowTableCaptions) { $ResTableParams['Caption'] = "- $($ResTableParams.Name)" }
                    $ResObj | Table @ResTableParams

                    $script:ExcelSheets['Resource Mailboxes'] = $ResObj
                }
            }
            #endregion

            #region Mailboxes with External Forwarding (gap report)
            try {
                $ExternalForwardMailboxes = @($AllMailboxes | Where-Object {
                    ($_.ForwardingSmtpAddress -and $_.ForwardingSmtpAddress -notmatch $script:TenantDomain) -or
                    ($_.ForwardingAddress -and $_.ForwardingAddress -notmatch $script:TenantDomain)
                })

                if ($ExternalForwardMailboxes.Count -gt 0) {
                    Section -Style Heading3 'Mailboxes with External Forwarding' {
                        Paragraph "WARNING: The following $($ExternalForwardMailboxes.Count) mailbox(es) are configured to forward email to external addresses. This is a data exfiltration risk and should be reviewed."
                        BlankLine

                        $FwdObj = [System.Collections.ArrayList]::new()
                        foreach ($Mbx in $ExternalForwardMailboxes) {
                            $fwdInObj = [ordered] @{
                                'Display Name'       = $Mbx.DisplayName
                                'UPN'                = $Mbx.UserPrincipalName
                                'Forwarding Address' = if ($Mbx.ForwardingSmtpAddress) { $Mbx.ForwardingSmtpAddress } else { $Mbx.ForwardingAddress }
                                'Deliver & Forward'  = $Mbx.DeliverToMailboxAndForward
                                'Mailbox Type'       = $Mbx.RecipientTypeDetails
                            }
                            $FwdObj.Add([pscustomobject](ConvertTo-HashToYN $fwdInObj)) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.Mailboxes) {
                                $null = ($FwdObj | Set-Style -Style Critical | Out-Null)
                            }
                        })

                        $FwdTableParams = @{ Name = "Mailboxes with External Forwarding - $TenantId"; List = $false; ColumnWidths = 20, 22, 28, 15, 15 }
                        if ($Report.ShowTableCaptions) { $FwdTableParams['Caption'] = "- $($FwdTableParams.Name)" }
                        $FwdObj | Table @FwdTableParams

                        $script:ExcelSheets['External Forwarding'] = $FwdObj
                    }
                }
            } catch {
                Write-ExoError 'Mailboxes' "Unable to check external forwarding: $($_.Exception.Message)"
            }
            #endregion
        }
    }

    end {
        Show-AbrDebugExecutionTime -End -TitleMessage 'Mailboxes'
    }
}