Src/Private/Get-AbrExoRetentionPolicies.ps1

function Get-AbrExoRetentionPolicies {
    <#
    .SYNOPSIS
    Documents Exchange Online retention policies, retention tags, and journal rules.
    Covers MRM (Messaging Records Management) retention policies, journal rules,
    and litigation hold / in-place hold overview.
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

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

    begin {
        Write-PScriboMessage -Message "Collecting Exchange Online Retention and Journaling configuration for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'RetentionPolicies'
    }

    process {
        Section -Style Heading2 'MRM Policies & Journaling' {
            Paragraph "The following section documents MRM retention policies, journal rules, and litigation hold configuration for tenant $TenantId."
            BlankLine

            #region MRM Retention Policies
            try {
                Write-Host " - Retrieving retention policies (MRM)..."
                $RetentionPolicies = Get-RetentionPolicy -ErrorAction Stop | Sort-Object Name

                if ($RetentionPolicies -and @($RetentionPolicies).Count -gt 0) {
                    Section -Style Heading3 'MRM Retention Policies' {
                        Paragraph "The following $(@($RetentionPolicies).Count) MRM retention policy/policies are configured in tenant $TenantId."
                        BlankLine

                        $RetObj = [System.Collections.ArrayList]::new()
                        foreach ($Policy in $RetentionPolicies) {
                            $TagCount = if ($Policy.RetentionPolicyTagLinks) { @($Policy.RetentionPolicyTagLinks).Count } else { 0 }
                            $retInObj = [ordered] @{
                                'Policy Name'       = $Policy.Name
                                'Default Policy'    = $Policy.IsDefault
                                'Tag Count'         = $TagCount
                                'GUID'              = $Policy.Guid
                            }
                            $RetObj.Add([pscustomobject](ConvertTo-HashToYN $retInObj)) | Out-Null
                        }

                        $RetTableParams = @{ Name = "MRM Retention Policies - $TenantId"; List = $false; ColumnWidths = 40, 15, 10, 35 }
                        if ($Report.ShowTableCaptions) { $RetTableParams['Caption'] = "- $($RetTableParams.Name)" }
                        if ($RetObj.Count -gt 0) { $RetObj | Table @RetTableParams }
                        $script:ExcelSheets['Retention Policies'] = $RetObj
                    }

                    #region Retention Tags (InfoLevel 2)
                    if ($InfoLevel.RetentionPolicies -ge 2) {
                        try {
                            $RetentionTags = Get-RetentionPolicyTag -ErrorAction Stop | Sort-Object Name
                            if ($RetentionTags -and @($RetentionTags).Count -gt 0) {
                                Section -Style Heading3 'MRM Retention Tags' {
                                    Paragraph "The following $(@($RetentionTags).Count) retention tag(s) are configured in tenant $TenantId."
                                    BlankLine

                                    $TagObj = [System.Collections.ArrayList]::new()
                                    foreach ($Tag in $RetentionTags) {
                                        $tagInObj = [ordered] @{
                                            'Tag Name'          = $Tag.Name
                                            'Tag Type'          = $Tag.Type
                                            'Retention Action'  = $Tag.RetentionAction
                                            'Retention Days'    = if ($Tag.RetentionEnabled -and $Tag.AgeLimitForRetention) {
                                                                      [int]$Tag.AgeLimitForRetention.TotalDays
                                                                  } else { 'Not Set' }
                                            'Retention Enabled' = $Tag.RetentionEnabled
                                            'System Tag'        = $Tag.IsDefaultModeratedRecipientTag -or $Tag.MustDisplayCommentEnabled
                                        }
                                        $TagObj.Add([pscustomobject](ConvertTo-HashToYN $tagInObj)) | Out-Null
                                    }

                                    $null = (& {
                                        if ($HealthCheck.ExchangeOnline.AuditLogging) {
                                            $null = ($TagObj | Where-Object { $_.'Retention Enabled' -eq 'No' } | Set-Style -Style Warning | Out-Null)
                                        }
                                    })

                                    $TagTableParams = @{ Name = "Retention Tags - $TenantId"; List = $false; ColumnWidths = 28, 16, 16, 14, 14, 12 }
                                    if ($Report.ShowTableCaptions) { $TagTableParams['Caption'] = "- $($TagTableParams.Name)" }
                                    if ($TagObj.Count -gt 0) { $TagObj | Table @TagTableParams }
                                    $script:ExcelSheets['Retention Tags'] = $TagObj
                                }
                            }
                        } catch {
                            Write-ExoError 'RetentionPolicies' "Unable to retrieve retention tags: $($_.Exception.Message)"
                        }
                    }
                    #endregion
                } else {
                    Paragraph "No MRM retention policies are configured in tenant $TenantId. Microsoft 365 compliance retention labels (Purview) may be in use instead."
                }
            } catch {
                Write-ExoError 'RetentionPolicies' "Unable to retrieve retention policies: $($_.Exception.Message)"
                Paragraph "Unable to retrieve MRM retention policy data: $($_.Exception.Message)"
            }
            #endregion

            #region Journal Rules
            try {
                Write-Host " - Retrieving journal rules..."
                $JournalRules = Get-JournalRule -ErrorAction Stop | Sort-Object Name

                Section -Style Heading3 'Journal Rules' {
                    if ($JournalRules -and @($JournalRules).Count -gt 0) {
                        Paragraph "The following $(@($JournalRules).Count) journal rule(s) are configured in tenant $TenantId. Journal rules capture copies of email messages for compliance and archiving purposes."
                        BlankLine

                        $JrnObj = [System.Collections.ArrayList]::new()
                        foreach ($Rule in $JournalRules) {
                            $jrnInObj = [ordered] @{
                                'Rule Name'          = $Rule.Name
                                'Enabled'            = $Rule.Enabled
                                'Journal Email'      = $Rule.JournalEmailAddress
                                'Scope'              = $Rule.Scope
                                'Recipient'          = if ($Rule.Recipient) { $Rule.Recipient } else { 'All Messages' }
                            }
                            $JrnObj.Add([pscustomobject](ConvertTo-HashToYN $jrnInObj)) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.AuditLogging) {
                                $null = ($JrnObj | Where-Object { $_.Enabled -eq 'No' } | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $JrnTableParams = @{ Name = "Journal Rules - $TenantId"; List = $false; ColumnWidths = 25, 8, 30, 12, 25 }
                        if ($Report.ShowTableCaptions) { $JrnTableParams['Caption'] = "- $($JrnTableParams.Name)" }
                        if ($JrnObj.Count -gt 0) { $JrnObj | Table @JrnTableParams }
                        $script:ExcelSheets['Journal Rules'] = $JrnObj
                    } else {
                        Paragraph "No journal rules are configured in tenant $TenantId."
                    }
                }
            } catch {
                Write-ExoError 'RetentionPolicies' "Unable to retrieve journal rules: $($_.Exception.Message)"
                Paragraph "Unable to retrieve journal rules: $($_.Exception.Message)"
            }
            #endregion

            #region Litigation Hold Summary
            try {
                Write-Host " - Retrieving litigation hold summary..."
                $LitigationHoldMailboxes = Get-Mailbox -ResultSize Unlimited -Filter { LitigationHoldEnabled -eq $true } -ErrorAction Stop
                $LitigHoldCount = @($LitigationHoldMailboxes).Count

                Section -Style Heading3 'Litigation Hold Summary' {
                    Paragraph "Litigation hold preserves all mailbox content for legal and compliance purposes."
                    BlankLine

                    $LitObj = [System.Collections.ArrayList]::new()
                    $litInObj = [ordered] @{
                        'Mailboxes on Litigation Hold' = $LitigHoldCount
                        'Total Mailboxes'              = @(Get-Mailbox -ResultSize Unlimited -ErrorAction SilentlyContinue).Count
                    }
                    $LitObj.Add([pscustomobject]$litInObj) | Out-Null

                    $LitTableParams = @{ Name = "Litigation Hold Summary - $TenantId"; List = $true; ColumnWidths = 50, 50 }
                    if ($Report.ShowTableCaptions) { $LitTableParams['Caption'] = "- $($LitTableParams.Name)" }
                    $LitObj | Table @LitTableParams

                    if ($InfoLevel.RetentionPolicies -ge 2 -and $LitigHoldCount -gt 0) {
                        BlankLine
                        $LitDetailObj = [System.Collections.ArrayList]::new()
                        foreach ($Mbx in ($LitigationHoldMailboxes | Sort-Object DisplayName)) {
                            $litDetailInObj = [ordered] @{
                                'Display Name'          = $Mbx.DisplayName
                                'UPN'                   = $Mbx.UserPrincipalName
                                'Hold Duration (days)'  = if ($Mbx.LitigationHoldDuration -and "$($Mbx.LitigationHoldDuration)" -match '^\d') {
                                                              try { [int]([timespan]::Parse($Mbx.LitigationHoldDuration)).TotalDays } catch { $Mbx.LitigationHoldDuration }
                                                          } else { 'Indefinite' }
                                'Hold Owner'            = if ($Mbx.LitigationHoldOwner) { $Mbx.LitigationHoldOwner } else { 'Not Set' }
                                'Hold Date'             = if ($Mbx.LitigationHoldDate) { $Mbx.LitigationHoldDate.ToString('yyyy-MM-dd') } else { 'Not Set' }
                            }
                            $LitDetailObj.Add([pscustomobject]$litDetailInObj) | Out-Null
                        }

                        $LitDetailTableParams = @{ Name = "Mailboxes on Litigation Hold - $TenantId"; List = $false; ColumnWidths = 22, 30, 16, 20, 12 }
                        if ($Report.ShowTableCaptions) { $LitDetailTableParams['Caption'] = "- $($LitDetailTableParams.Name)" }
                        if ($LitDetailObj.Count -gt 0) { $LitDetailObj | Table @LitDetailTableParams }
                        $script:ExcelSheets['Litigation Hold'] = $LitDetailObj
                    }
                }
            } catch {
                Write-ExoError 'RetentionPolicies' "Unable to retrieve litigation hold data: $($_.Exception.Message)"
                Paragraph "Unable to retrieve litigation hold data: $($_.Exception.Message)"
            }
            #endregion
        }
    }

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