Src/Private/Get-AbrExoAuditLogging.ps1

function Get-AbrExoAuditLogging {
    <#
    .SYNOPSIS
    Documents Exchange Online audit logging configuration with ACSC E8 and CIS compliance assessments.
    .NOTES
        Version: 0.1.0
        Author: Pai Wei Sing
    #>

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

    begin {
        Write-PScriboMessage -Message "Collecting Exchange Online Audit Logging configuration for $TenantId."
        Show-AbrDebugExecutionTime -Start -TitleMessage 'AuditLogging'
    }

    process {
        Section -Style Heading2 'Audit Configuration' {
            Paragraph "The following section documents audit logging configuration for tenant $TenantId."
            BlankLine

            $AdminAuditLogEnabled    = $false
            $AdminAuditLogAgeLimit   = 0
            $MailboxAuditBypassCount = 0

            #region Admin Audit Log Config
            try {
                Write-Host " - Retrieving admin audit log configuration..."
                $AuditConfig = Get-AdminAuditLogConfig -ErrorAction Stop

                $AdminAuditLogEnabled       = ($AuditConfig.AdminAuditLogEnabled -eq $true)
                $UnifiedAuditLogEnabled     = ($AuditConfig.UnifiedAuditLogIngestionEnabled -eq $true)
                # AdminAuditLogAgeLimit is returned as a string "90.00:00:00" in EXO, not a TimeSpan object.
                # Parse it safely: take the integer part before the first dot/colon.
                $AdminAuditLogAgeLimit = 0
                $ageLimitRaw = $AuditConfig.AdminAuditLogAgeLimit
                if ($ageLimitRaw) {
                    try {
                        if ($ageLimitRaw -is [TimeSpan]) {
                            $AdminAuditLogAgeLimit = [int]$ageLimitRaw.TotalDays
                        } elseif ("$ageLimitRaw" -match '^(\d+)') {
                            $AdminAuditLogAgeLimit = [int]$Matches[1]
                        }
                    } catch { $AdminAuditLogAgeLimit = 0 }
                }

                $AuditObj = [System.Collections.ArrayList]::new()
                $auditInObj = [ordered] @{
                    'Admin Audit Log Enabled'        = $AuditConfig.AdminAuditLogEnabled
                    'Unified Audit Log Ingestion'     = $AuditConfig.UnifiedAuditLogIngestionEnabled
                    'Admin Audit Log Age Limit (days)'= $AdminAuditLogAgeLimit
                    'Admin Audit Log Cmdlets'         = if ($AuditConfig.AdminAuditLogCmdlets -eq '*') { 'All Cmdlets' } else { ($AuditConfig.AdminAuditLogCmdlets -join ', ') }
                    'Admin Audit Log Parameters'      = if ($AuditConfig.AdminAuditLogParameters -eq '*') { 'All Parameters' } else { ($AuditConfig.AdminAuditLogParameters -join ', ') }
                    'Log Level'                       = $AuditConfig.LogLevel
                }
                $AuditObj.Add([pscustomobject](ConvertTo-HashToYN $auditInObj)) | Out-Null

                $null = (& {
                    if ($HealthCheck.ExchangeOnline.AuditLogging) {
                        $null = ($AuditObj | Where-Object { $_.'Admin Audit Log Enabled' -eq 'No' } | Set-Style -Style Critical | Out-Null)
                        $null = ($AuditObj | Where-Object {
                            $_.'Admin Audit Log Age Limit (days)' -ne '--' -and
                            [int]$_.'Admin Audit Log Age Limit (days)' -lt 90
                        } | Set-Style -Style Warning | Out-Null)
                    }
                })

                $AuditTableParams = @{ Name = "Admin Audit Log Configuration - $TenantId"; List = $true; ColumnWidths = 45, 55 }
                if ($Report.ShowTableCaptions) { $AuditTableParams['Caption'] = "- $($AuditTableParams.Name)" }
                $AuditObj | Table @AuditTableParams

                $script:ExcelSheets['Audit Config'] = $AuditObj
            } catch {
                Write-ExoError 'AuditLogging' "Unable to retrieve audit log config: $($_.Exception.Message)"
                Paragraph "Unable to retrieve admin audit log configuration: $($_.Exception.Message)"
            }
            #endregion

            #region Mailbox Audit Bypass
            try {
                Write-Host " - Checking mailbox audit bypass assignments..."
                $AuditBypass = Get-MailboxAuditBypassAssociation -ResultSize Unlimited -ErrorAction Stop |
                    Where-Object { $_.AuditBypassEnabled -eq $true }

                $MailboxAuditBypassCount = @($AuditBypass).Count

                if ($AuditBypass -and $MailboxAuditBypassCount -gt 0) {
                    Section -Style Heading3 'Mailboxes with Audit Logging Bypassed' {
                        Paragraph "WARNING: The following $MailboxAuditBypassCount account(s) have mailbox audit bypass enabled. These accounts will not generate mailbox audit log entries, creating blind spots in forensic investigations."
                        BlankLine

                        $BypassObj = [System.Collections.ArrayList]::new()
                        foreach ($Bypass in $AuditBypass) {
                            $bypassInObj = [ordered] @{
                                'Name'             = $Bypass.Name
                                'Bypass Enabled'   = $Bypass.AuditBypassEnabled
                            }
                            $BypassObj.Add([pscustomobject](ConvertTo-HashToYN $bypassInObj)) | Out-Null
                        }

                        $null = (& {
                            if ($HealthCheck.ExchangeOnline.AuditLogging) {
                                $null = ($BypassObj | Set-Style -Style Warning | Out-Null)
                            }
                        })

                        $BypassTableParams = @{ Name = "Audit Bypass Accounts - $TenantId"; List = $false; ColumnWidths = 70, 30 }
                        if ($Report.ShowTableCaptions) { $BypassTableParams['Caption'] = "- $($BypassTableParams.Name)" }
                        $BypassObj | Table @BypassTableParams

                        $script:ExcelSheets['Audit Bypass Accounts'] = $BypassObj
                    }
                } else {
                    Paragraph "No mailbox audit bypass assignments found. All mailboxes have audit logging enabled."
                }
            } catch {
                Write-ExoError 'AuditLogging' "Unable to retrieve audit bypass assignments: $($_.Exception.Message)"
            }
            #endregion


            BlankLine
            Paragraph "ACSC Essential Eight Assessment"
            BlankLine
            $e8Vars = @{
                AdminAuditLogEnabled    = $AdminAuditLogEnabled
                AdminAuditLogAgeLimit   = $AdminAuditLogAgeLimit
                MailboxAuditBypassCount = $MailboxAuditBypassCount
            }
            $e8Checks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoE8Checks 'AuditLogging') -Framework 'E8' -CallerVariables $e8Vars
            New-AbrExoE8AssessmentTable -Checks $e8Checks -Name 'Audit Logging' -TenantId $TenantId
            if ($e8Checks) {
                foreach ($row in $e8Checks) {
                    $null = $script:E8AllChecks.Add([pscustomobject]@{
                        Section = 'Audit Logging'
                        ML      = $row.ML
                        Control = $row.Control
                        Status  = $row.Status
                        Detail  = $row.Detail
                    })
                }
            }

            BlankLine
            Paragraph "CIS Microsoft 365 Foundations Benchmark Assessment"
            BlankLine
            $cisVars = @{
                AdminAuditLogEnabled    = $UnifiedAuditLogEnabled
                MailboxAuditBypassCount = $MailboxAuditBypassCount
            }
            $cisChecks = Build-AbrExoComplianceChecks -Definitions (Get-AbrExoCISChecks 'AuditLogging') -Framework 'CIS' -CallerVariables $cisVars
            New-AbrExoCISAssessmentTable -Checks $cisChecks -Name 'Audit Logging' -TenantId $TenantId
            if ($cisChecks) {
                foreach ($row in $cisChecks) {
                    $null = $script:CISAllChecks.Add([pscustomobject]@{
                        Section    = 'Audit Logging'
                        CISControl = $row.CISControl
                        Level      = $row.Level
                        Status     = $row.Status
                        Detail     = $row.Detail
                    })
                }
            }
        }

    }



    end {

        Show-AbrDebugExecutionTime -End -TitleMessage 'AuditLogging'

    }

}