Workloads/Get-ExchangeData.ps1

# Get-ExchangeData.ps1
# Collects mailboxes, sizes, archives, connectors, transport rules,
# distribution groups, contacts, and hybrid signals.
# Part of the M365-QuickAssess module -- not exported.

function Get-ExchangeData
{
    param
    (
        $Assessment
    )

    Write-Log "Collecting Exchange data"

    # -------------------------------------------------------------------
    # Mailboxes
    # -------------------------------------------------------------------
    try
    {
        $mailboxes            = Get-EXOMailbox -ResultSize Unlimited -PropertySets Minimum,Archive -ErrorAction Stop
        $total                = $mailboxes.Count
        $userMailboxCount     = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "UserMailbox" } ).Count
        $sharedMailboxCount   = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "SharedMailbox" } ).Count
        $resourceMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -in @( "RoomMailbox", "EquipmentMailbox" ) } ).Count
        $archiveMailboxCount  = ( $mailboxes | Where-Object { $_.ArchiveStatus -eq "Active" } ).Count

        Write-Log "Mailboxes: Total=$total User=$userMailboxCount Shared=$sharedMailboxCount Resource=$resourceMailboxCount Archive=$archiveMailboxCount"
    }
    catch
    {
        Write-Log "Mailbox collection failed: $( $_.Exception.Message )" "ERROR"
        return
    }

    # -------------------------------------------------------------------
    # Mailbox Sizes
    # Large tenant sampling logic kicks in above 5000 mailboxes
    # -------------------------------------------------------------------
    $totalSizeGB      = 0
    $largeMailboxCount = 0

    try
    {
        Write-Log "Collecting mailbox statistics"

        if ( $total -le 5000 )
        {
            $stats = foreach ( $mbx in $mailboxes )
            {
                try
                {
                    $stat = Get-EXOMailboxStatistics -Identity $mbx.PrimarySmtpAddress -ErrorAction Stop

                    [PSCustomObject]@{
                        DisplayName        = $mbx.DisplayName
                        UserPrincipalName  = $mbx.UserPrincipalName
                        PrimarySmtpAddress = $mbx.PrimarySmtpAddress
                        TotalItemSize      = $stat.TotalItemSize
                    }
                }
                catch
                {
                    Write-Log "Failed stats for $( $mbx.PrimarySmtpAddress )" "WARN"
                }
            }

            $scale = 1
        }
        else
        {
            Write-Log "Large tenant detected ($total mailboxes) -- using sample of 500 for size estimation"

            $sample = $mailboxes | Get-Random -Count 500

            $stats = foreach ( $mbx in $sample )
            {
                try
                {
                    $stat = Get-EXOMailboxStatistics -Identity $mbx.PrimarySmtpAddress -ErrorAction Stop

                    [PSCustomObject]@{
                        DisplayName        = $mbx.DisplayName
                        UserPrincipalName  = $mbx.UserPrincipalName
                        PrimarySmtpAddress = $mbx.PrimarySmtpAddress
                        TotalItemSize      = $stat.TotalItemSize
                    }
                }
                catch
                {
                    Write-Log "Failed stats for $( $mbx.PrimarySmtpAddress )" "WARN"
                }
            }

            $scale = $total / 500
            if ( $scale -le 0 ) { $scale = 1 }
        }

        foreach ( $stat in $stats )
        {
            $bytes = 0

            try
            {
                if ( $stat.TotalItemSize -and $stat.TotalItemSize.Value )
                {
                    $bytes = $stat.TotalItemSize.Value.ToBytes()
                }
            }
            catch
            {
                Write-Log "Failed to read size for $( $stat.DisplayName )" "WARN"
            }

            if ( $bytes -gt 0 )
            {
                $gb           = $bytes / 1GB
                $totalSizeGB += $gb

                if ( $gb -gt 80 )
                {
                    $largeMailboxCount++

                    $Assessment.Findings += New-Finding `
                        -Type           "LargeMailbox" `
                        -Summary        "Mailbox exceeds 80 GB size threshold" `
                        -Category       "Exchange" `
                        -Severity       "High" `
                        -Details        @( "$( $stat.UserPrincipalName ) - $( [math]::Round( $gb, 2 ) ) GB" ) `
                        -Impact         "Large mailboxes increase migration time and risk of failure." `
                        -Recommendation "Enable online archiving or run cleanup before migration."
                }
            }
        }

        $totalSizeGB       = [math]::Round( $totalSizeGB * $scale, 2 )
        $largeMailboxCount = [math]::Round( $largeMailboxCount * $scale )

        Write-Log "Mailbox sizes: TotalGB=$totalSizeGB Large=$largeMailboxCount"
    }
    catch
    {
        Write-Log "Mailbox statistics collection failed: $( $_.Exception.Message )" "WARN"
    }

    # -------------------------------------------------------------------
    # Archive Mailbox Finding
    # -------------------------------------------------------------------
    if ( $archiveMailboxCount -gt 0 )
    {
        $archiveMailboxes = $mailboxes | Where-Object { $_.ArchiveStatus -eq "Active" }
        $upns             = ( $archiveMailboxes | Select-Object -First 10 -ExpandProperty PrimarySmtpAddress ) -join ", "

        if ( $archiveMailboxCount -gt 10 )
        {
            $upns += " ... and $( $archiveMailboxCount - 10 ) more"
        }

        $Assessment.Findings += New-Finding `
            -Type           "ArchiveMailboxes" `
            -Summary        "$archiveMailboxCount archive mailboxes detected" `
            -Category       "Exchange" `
            -Severity       "High" `
            -Details        @( $upns ) `
            -Impact         "Archive mailboxes must be migrated separately and may require additional tooling or licensing." `
            -Recommendation "Confirm archive migration approach and tooling before beginning migration."
    }

    # -------------------------------------------------------------------
    # Connectors, Transport Rules, Accepted Domains
    # -------------------------------------------------------------------
    try
    {
        Write-Log "Collecting connectors, transport rules, and accepted domains"

        $connectors      = Get-InboundConnector -ErrorAction SilentlyContinue
        $outConnectors   = Get-OutboundConnector -ErrorAction SilentlyContinue
        $transportRules  = Get-TransportRule -ErrorAction SilentlyContinue
        $acceptedDomains = Get-AcceptedDomain -ErrorAction SilentlyContinue

        $connectorCount  = ( ( $connectors | Measure-Object ).Count + ( $outConnectors | Measure-Object ).Count )

        Write-Log "Connectors=$connectorCount TransportRules=$( ( $transportRules | Measure-Object ).Count ) AcceptedDomains=$( ( $acceptedDomains | Measure-Object ).Count )"

        # -------------------------------------------------------------------
        # Finding: Connectors
        # -------------------------------------------------------------------
        if ( $connectorCount -gt 0 )
        {
            $Assessment.Findings += New-Finding `
                -Type           "ExchangeConnectors" `
                -Summary        "$connectorCount mail connectors detected" `
                -Category       "Exchange" `
                -Severity       "Medium" `
                -Impact         "Custom connectors for mail routing or third-party filtering will need to be recreated in the target tenant." `
                -Recommendation "Document all inbound and outbound connectors and plan recreation in the target tenant."
        }

        # -------------------------------------------------------------------
        # Finding: Transport Rules
        # -------------------------------------------------------------------
        if ( ( $transportRules | Measure-Object ).Count -gt 0 )
        {
            $Assessment.Findings += New-Finding `
                -Type           "TransportRules" `
                -Summary        "$( ( $transportRules | Measure-Object ).Count ) transport rules detected" `
                -Category       "Exchange" `
                -Severity       "Medium" `
                -Impact         "Transport rules will not migrate automatically and must be manually recreated in the target tenant." `
                -Recommendation "Export and document all transport rules before migration."
        }
    }
    catch
    {
        Write-Log "Connector and transport rule collection failed: $( $_.Exception.Message )" "WARN"
    }

    # -------------------------------------------------------------------
    # Hybrid Detection
    # -------------------------------------------------------------------
    try
    {
        $remoteMailboxCount = ( $mailboxes | Where-Object { $_.RecipientTypeDetails -eq "RemoteUserMailbox" } ).Count
        $dirSync            = ( Get-MgOrganization ).OnPremisesSyncEnabled
        $isExchangeHybrid   = ( $remoteMailboxCount -gt 0 -or $dirSync )

        Write-Log "Exchange Hybrid: $isExchangeHybrid RemoteMailboxes=$remoteMailboxCount DirSync=$dirSync"

        if ( $isExchangeHybrid )
        {
            $Assessment.Findings += New-Finding `
                -Type           "ExchangeHybrid" `
                -Summary        "Exchange hybrid configuration detected" `
                -Category       "Exchange" `
                -Severity       "High" `
                -Impact         "Hybrid Exchange environments require additional steps to decommission and migrate cleanly. On-premises mailboxes must be migrated before hybrid can be removed." `
                -Recommendation "Engage an Exchange hybrid migration specialist. Plan hybrid decommission as part of the migration project."
        }
    }
    catch
    {
        Write-Log "Hybrid detection failed: $( $_.Exception.Message )" "WARN"
    }

    # -------------------------------------------------------------------
    # Contacts, Mail Users, Distribution Groups
    # -------------------------------------------------------------------
    try
    {
        $mailContacts              = Get-MailContact -ResultSize Unlimited -ErrorAction SilentlyContinue
        $mailUsers                 = Get-MailUser -ResultSize Unlimited -ErrorAction SilentlyContinue
        $distributionGroups        = Get-DistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue
        $mailEnabledSecurityGroups = $distributionGroups | Where-Object { $_.RecipientTypeDetails -eq "MailUniversalSecurityGroup" }
        $dynamicDistributionGroups = Get-DynamicDistributionGroup -ResultSize Unlimited -ErrorAction SilentlyContinue

        $mailboxWithDelegatesCount = ( $mailboxes | Where-Object {
            ( $_.GrantSendOnBehalfTo | Measure-Object ).Count -gt 0
        } ).Count

        Write-Log "Contacts=$( ( $mailContacts | Measure-Object ).Count ) MailUsers=$( ( $mailUsers | Measure-Object ).Count ) DGs=$( ( $distributionGroups | Measure-Object ).Count ) DDGs=$( ( $dynamicDistributionGroups | Measure-Object ).Count ) Delegates=$mailboxWithDelegatesCount"

        # -------------------------------------------------------------------
        # Finding: Dynamic Distribution Groups
        # -------------------------------------------------------------------
        if ( ( $dynamicDistributionGroups | Measure-Object ).Count -gt 0 )
        {
            $Assessment.Findings += New-Finding `
                -Type           "DynamicDistributionGroups" `
                -Summary        "$( ( $dynamicDistributionGroups | Measure-Object ).Count ) dynamic distribution groups detected" `
                -Category       "Exchange" `
                -Severity       "Medium" `
                -Impact         "Dynamic distribution groups use recipient filters that must be manually recreated in the target tenant." `
                -Recommendation "Document all dynamic distribution group filters and plan recreation in the target tenant."
        }

        # -------------------------------------------------------------------
        # Finding: Mailboxes with delegates
        # -------------------------------------------------------------------
        if ( $mailboxWithDelegatesCount -gt 0 )
        {
            $Assessment.Findings += New-Finding `
                -Type           "MailboxDelegates" `
                -Summary        "$mailboxWithDelegatesCount mailboxes have Send on Behalf delegates configured" `
                -Category       "Exchange" `
                -Severity       "Medium" `
                -Impact         "Delegate permissions must be reconfigured after migration as they do not migrate automatically." `
                -Recommendation "Document all delegate configurations and plan post-migration remediation."
        }
    }
    catch
    {
        Write-Log "Contacts and distribution group collection failed: $( $_.Exception.Message )" "WARN"
    }

    # -------------------------------------------------------------------
    # Populate Schema
    # -------------------------------------------------------------------
    $Assessment.Exchange.MailboxCount                  = $total
    $Assessment.Exchange.UserMailboxCount              = $userMailboxCount
    $Assessment.Exchange.SharedMailboxCount            = $sharedMailboxCount
    $Assessment.Exchange.ResourceMailboxCount          = $resourceMailboxCount
    $Assessment.Exchange.LargeMailboxCount             = $largeMailboxCount
    $Assessment.Exchange.ArchiveMailboxCount           = $archiveMailboxCount
    $Assessment.Exchange.TotalMailboxSizeGB            = $totalSizeGB

    $Assessment.Exchange.HasConnectors                 = ( $connectorCount -gt 0 )
    $Assessment.Exchange.ConnectorCount                = $connectorCount
    $Assessment.Exchange.HasTransportRules             = ( ( $transportRules | Measure-Object ).Count -gt 0 )
    $Assessment.Exchange.TransportRuleCount            = ( $transportRules | Measure-Object ).Count
    $Assessment.Exchange.AcceptedDomainCount           = ( $acceptedDomains | Measure-Object ).Count
    $Assessment.Exchange.MailboxWithDelegatesCount     = $mailboxWithDelegatesCount

    $Assessment.Exchange.IsExchangeHybrid              = $isExchangeHybrid
    $Assessment.Exchange.HasRemoteMailboxes            = ( $remoteMailboxCount -gt 0 )
    $Assessment.Exchange.RemoteMailboxCount            = $remoteMailboxCount
    $Assessment.Exchange.HasOnPremMailboxes            = $isExchangeHybrid

    $Assessment.Exchange.MailContactCount              = ( $mailContacts | Measure-Object ).Count
    $Assessment.Exchange.MailUserCount                 = ( $mailUsers | Measure-Object ).Count
    $Assessment.Exchange.HasContacts                   = ( ( $mailContacts | Measure-Object ).Count -gt 0 )

    $Assessment.Exchange.DistributionGroupCount        = ( $distributionGroups | Measure-Object ).Count
    $Assessment.Exchange.MailEnabledSecurityGroupCount = ( $mailEnabledSecurityGroups | Measure-Object ).Count
    $Assessment.Exchange.DynamicDistributionGroupCount = ( $dynamicDistributionGroups | Measure-Object ).Count
    $Assessment.Exchange.HasDistributionLists          = ( ( $distributionGroups | Measure-Object ).Count -gt 0 )

    $Assessment.Summary.Mailboxes                      = $total
    $Assessment.Summary.MailboxCount                   = $total
    $Assessment.Summary.TotalMailboxSizeGB             = $totalSizeGB
}