Purview/Get-AuditRetentionReport.ps1

<#
.SYNOPSIS
    Reports audit log configuration and retention policies from Microsoft Purview.
.DESCRIPTION
    Retrieves the admin audit log configuration and unified audit log retention
    policies using Exchange Online / Purview cmdlets. Reports whether audit logging
    is enabled, the unified audit log ingestion status, and any configured retention
    policies with their durations.
 
    Essential for compliance assessments, security audits, and verifying that audit
    data is being retained per organizational or regulatory requirements.
 
    Requires an active Exchange Online / Purview connection (Connect-IPPSSession or
    Connect-ExchangeOnline) with sufficient permissions.
.PARAMETER OutputPath
    Optional path to export results as CSV. If not specified, results are returned
    to the pipeline.
.EXAMPLE
    PS> . .\Common\Connect-Service.ps1
    PS> Connect-Service -Service ExchangeOnline
    PS> .\Purview\Get-AuditRetentionReport.ps1
 
    Displays audit log config and retention policies in the console.
.EXAMPLE
    PS> .\Purview\Get-AuditRetentionReport.ps1 -OutputPath '.\audit-retention-report.csv'
 
    Exports audit configuration and retention policies to CSV for compliance documentation.
.EXAMPLE
    PS> Connect-IPPSSession
    PS> .\Purview\Get-AuditRetentionReport.ps1 -Verbose
 
    Uses a direct Purview connection and shows verbose progress output.
#>

[CmdletBinding()]
param(
    [Parameter()]
    [ValidateNotNullOrEmpty()]
    [string]$OutputPath
)

$ErrorActionPreference = 'Stop'

# Verify EXO/Purview connection by testing the audit config cmdlet
$auditConfig = $null
try {
    Write-Verbose "Verifying Exchange Online / Purview connection..."
    $auditConfig = Get-AdminAuditLogConfig -ErrorAction Stop
}
catch {
    $cmdAvailable = Get-Command -Name Get-AdminAuditLogConfig -ErrorAction SilentlyContinue
    if (-not $cmdAvailable) {
        Write-Error "Get-AdminAuditLogConfig is not available. Connect to Exchange Online or Purview first (Connect-Service -Service ExchangeOnline or Connect-IPPSSession)."
        return
    }
    else {
        Write-Error "Failed to retrieve admin audit log config: $_"
        return
    }
}

$results = [System.Collections.Generic.List[PSCustomObject]]::new()

# Process audit log configuration
Write-Verbose "Processing admin audit log configuration..."

$unifiedAuditEnabled = $null
if ($auditConfig.PSObject.Properties.Name -contains 'UnifiedAuditLogIngestionEnabled') {
    $unifiedAuditEnabled = $auditConfig.UnifiedAuditLogIngestionEnabled
}

$adminAuditEnabled = $null
if ($auditConfig.PSObject.Properties.Name -contains 'AdminAuditLogEnabled') {
    $adminAuditEnabled = $auditConfig.AdminAuditLogEnabled
}

$configDetails = @()
if ($auditConfig.PSObject.Properties.Name -contains 'AdminAuditLogAgeLimit') {
    $configDetails += "AdminAuditLogAgeLimit=$($auditConfig.AdminAuditLogAgeLimit)"
}
if ($auditConfig.PSObject.Properties.Name -contains 'AdminAuditLogCmdlets') {
    $cmdletSetting = $auditConfig.AdminAuditLogCmdlets -join '; '
    $configDetails += "LoggedCmdlets=$cmdletSetting"
}

$results.Add([PSCustomObject]@{
    ItemType                        = 'AuditConfig'
    Name                            = 'AdminAuditLogConfig'
    UnifiedAuditLogIngestionEnabled = $unifiedAuditEnabled
    AdminAuditLogEnabled            = $adminAuditEnabled
    Details                         = ($configDetails -join ' | ')
})

# Retrieve unified audit log retention policies
$retentionPoliciesAvailable = $true
try {
    Write-Verbose "Retrieving unified audit log retention policies..."
    $retentionPolicies = Get-UnifiedAuditLogRetentionPolicy -ErrorAction Stop
}
catch {
    $cmdAvailable = Get-Command -Name Get-UnifiedAuditLogRetentionPolicy -ErrorAction SilentlyContinue
    if (-not $cmdAvailable) {
        Write-Warning "Get-UnifiedAuditLogRetentionPolicy is not available. The tenant may not have Microsoft 365 E5 or Purview Audit (Premium). Skipping retention policies."
        $retentionPoliciesAvailable = $false
    }
    else {
        Write-Warning "Failed to retrieve unified audit log retention policies: $_"
        $retentionPoliciesAvailable = $false
    }
}

if ($retentionPoliciesAvailable -and $retentionPolicies) {
    $policyList = @($retentionPolicies)
    Write-Verbose "Found $($policyList.Count) audit log retention policy/policies"

    foreach ($policy in $policyList) {
        $policyDetails = @()

        if ($policy.PSObject.Properties.Name -contains 'RetentionDuration') {
            $policyDetails += "RetentionDuration=$($policy.RetentionDuration)"
        }
        if ($policy.PSObject.Properties.Name -contains 'RecordTypes') {
            $recordTypes = if ($policy.RecordTypes) { ($policy.RecordTypes -join '; ') } else { 'All' }
            $policyDetails += "RecordTypes=$recordTypes"
        }
        if ($policy.PSObject.Properties.Name -contains 'Operations') {
            $operations = if ($policy.Operations) { ($policy.Operations -join '; ') } else { 'All' }
            $policyDetails += "Operations=$operations"
        }
        if ($policy.PSObject.Properties.Name -contains 'UserIds') {
            $userIds = if ($policy.UserIds) { ($policy.UserIds -join '; ') } else { 'All' }
            $policyDetails += "UserIds=$userIds"
        }
        if ($policy.PSObject.Properties.Name -contains 'Priority') {
            $policyDetails += "Priority=$($policy.Priority)"
        }
        if ($policy.PSObject.Properties.Name -contains 'Enabled') {
            $policyDetails += "Enabled=$($policy.Enabled)"
        }

        $policyName = if ($policy.PSObject.Properties.Name -contains 'Name') {
            $policy.Name
        }
        elseif ($policy.PSObject.Properties.Name -contains 'Identity') {
            $policy.Identity
        }
        else {
            'UnnamedPolicy'
        }

        $results.Add([PSCustomObject]@{
            ItemType                        = 'RetentionPolicy'
            Name                            = $policyName
            UnifiedAuditLogIngestionEnabled = $null
            AdminAuditLogEnabled            = $null
            Details                         = ($policyDetails -join ' | ')
        })
    }
}
elseif ($retentionPoliciesAvailable -and -not $retentionPolicies) {
    Write-Verbose "No unified audit log retention policies found in the tenant"

    $results.Add([PSCustomObject]@{
        ItemType                        = 'RetentionPolicy'
        Name                            = '(none configured)'
        UnifiedAuditLogIngestionEnabled = $null
        AdminAuditLogEnabled            = $null
        Details                         = 'No custom audit log retention policies are configured. Default retention applies.'
    })
}

$report = @($results)

Write-Verbose "Compiled $($report.Count) audit configuration and retention policy record(s)"

if ($OutputPath) {
    $report | Export-Csv -Path $OutputPath -NoTypeInformation -Encoding UTF8
    Write-Output "Exported audit retention report ($($report.Count) record(s)) to $OutputPath"
}
else {
    Write-Output $report
}