Purview/Get-PurviewRetentionConfig.ps1

<#
.SYNOPSIS
    Collects Microsoft Purview data lifecycle retention compliance policy configuration.
.DESCRIPTION
    Queries the Security & Compliance Center for retention compliance policies and
    their associated rules. Reports on policy existence, workload coverage (Exchange,
    Teams, SharePoint/OneDrive), and enforcement mode — essential for verifying that
    data lifecycle management requirements are met per regulatory and organizational
    standards.
 
    Requires an active Security & Compliance (Purview) connection via Connect-IPPSSession
    or Connect-Service -Service Purview.
.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 Purview
    PS> .\Purview\Get-PurviewRetentionConfig.ps1
 
    Displays Purview retention compliance policy configuration settings.
.EXAMPLE
    PS> .\Purview\Get-PurviewRetentionConfig.ps1 -OutputPath '.\purview-retention-config.csv'
 
    Exports retention policy configuration to CSV.
.NOTES
    Author: Daren9m
    Settings checked are aligned with CIS Microsoft 365 Foundations Benchmark v6.0.1
    and NIST SP 800-53 AU-11 (Audit Record Retention) recommendations.
#>

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

# Continue on errors: non-critical checks should not block remaining assessments.
$ErrorActionPreference = 'Continue'

$settings = [System.Collections.Generic.List[PSCustomObject]]::new()
$checkIdCounter = @{}

function Add-Setting {
    param(
        [string]$Category,
        [string]$Setting,
        [string]$CurrentValue,
        [string]$RecommendedValue,
        [string]$Status,
        [string]$CheckId = '',
        [string]$Remediation = ''
    )
    # Auto-generate sub-numbered CheckId for individual setting traceability
    $subCheckId = $CheckId
    if ($CheckId) {
        if (-not $checkIdCounter.ContainsKey($CheckId)) { $checkIdCounter[$CheckId] = 0 }
        $checkIdCounter[$CheckId]++
        $subCheckId = "$CheckId.$($checkIdCounter[$CheckId])"
    }
    $settings.Add([PSCustomObject]@{
        Category         = $Category
        Setting          = $Setting
        CurrentValue     = $CurrentValue
        RecommendedValue = $RecommendedValue
        Status           = $Status
        CheckId          = $subCheckId
        Remediation      = $Remediation
    })
    if ($CheckId -and (Get-Command -Name Update-CheckProgress -ErrorAction SilentlyContinue)) {
        Update-CheckProgress -CheckId $subCheckId -Setting $Setting -Status $Status
    }
}

# ------------------------------------------------------------------
# 1. Retention Compliance Policies — existence and workload coverage
# ------------------------------------------------------------------
$policies = $null
try {
    Write-Verbose "Checking Purview retention compliance policies..."
    $retentionCmdAvailable = Get-Command -Name Get-RetentionCompliancePolicy -ErrorAction SilentlyContinue
    if ($retentionCmdAvailable) {
        $policies = @(Get-RetentionCompliancePolicy -ErrorAction Stop)
    }
    else {
        Write-Warning "Get-RetentionCompliancePolicy is not available. Connect to Security & Compliance PowerShell: Connect-Service -Service Purview."
    }
}
catch {
    Write-Warning "Could not retrieve retention compliance policies: $($_.Exception.Message)"
}

if ($null -ne $policies) {
    $enabledPolicies = @($policies | Where-Object { $_.Enabled -ne $false })

    # Check 1: Any enabled retention policies exist
    if ($enabledPolicies.Count -gt 0) {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Retention Policies Configured'
            CurrentValue     = "$($enabledPolicies.Count) enabled (of $($policies.Count) total)"
            RecommendedValue = 'At least 1 enabled'
            Status           = 'Pass'
            CheckId          = 'PURVIEW-RETENTION-001'
            Remediation      = 'No action needed.'
        }
        Add-Setting @settingParams
    }
    else {
        $currentVal = if ($policies.Count -eq 0) { 'None configured' } else { "$($policies.Count) policies (none enabled)" }
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Retention Policies Configured'
            CurrentValue     = $currentVal
            RecommendedValue = 'At least 1 enabled'
            Status           = 'Fail'
            CheckId          = 'PURVIEW-RETENTION-001'
            Remediation      = 'Microsoft Purview > Data lifecycle management > Retention policies > Create a retention policy to preserve or delete content per your requirements.'
        }
        Add-Setting @settingParams
    }

    # Check 2: Exchange covered by a retention policy
    $exchangePolicies = @($enabledPolicies | Where-Object {
        ($_.ExchangeLocation -and @($_.ExchangeLocation).Count -gt 0) -or
        ($_.Workload -and $_.Workload -match 'Exchange')
    })
    if ($exchangePolicies.Count -gt 0) {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Exchange Covered by Retention'
            CurrentValue     = "$($exchangePolicies.Count) policies cover Exchange"
            RecommendedValue = 'At least 1 policy covers Exchange'
            Status           = 'Pass'
            CheckId          = 'PURVIEW-RETENTION-002'
            Remediation      = 'No action needed.'
        }
        Add-Setting @settingParams
    }
    else {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Exchange Covered by Retention'
            CurrentValue     = 'No retention policies cover Exchange'
            RecommendedValue = 'At least 1 policy covers Exchange'
            Status           = 'Fail'
            CheckId          = 'PURVIEW-RETENTION-002'
            Remediation      = 'Microsoft Purview > Data lifecycle management > Retention policies > Create or edit a retention policy to include Exchange email and mailboxes.'
        }
        Add-Setting @settingParams
    }

    # Check 3: Teams covered by a retention policy
    $teamsPolicies = @($enabledPolicies | Where-Object {
        ($_.TeamsChannelLocation -and @($_.TeamsChannelLocation).Count -gt 0) -or
        ($_.TeamsChatLocation    -and @($_.TeamsChatLocation).Count    -gt 0) -or
        ($_.Workload -and $_.Workload -match 'Teams')
    })
    if ($teamsPolicies.Count -gt 0) {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Teams Covered by Retention'
            CurrentValue     = "$($teamsPolicies.Count) policies cover Teams"
            RecommendedValue = 'At least 1 policy covers Teams'
            Status           = 'Pass'
            CheckId          = 'PURVIEW-RETENTION-003'
            Remediation      = 'No action needed.'
        }
        Add-Setting @settingParams
    }
    else {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Teams Covered by Retention'
            CurrentValue     = 'No retention policies cover Teams'
            RecommendedValue = 'At least 1 policy covers Teams'
            Status           = 'Fail'
            CheckId          = 'PURVIEW-RETENTION-003'
            Remediation      = 'Microsoft Purview > Data lifecycle management > Retention policies > Create or edit a retention policy to include Teams channel messages and chats.'
        }
        Add-Setting @settingParams
    }

    # Check 4: SharePoint/OneDrive covered by a retention policy
    $sharepointPolicies = @($enabledPolicies | Where-Object {
        ($_.SharePointLocation -and @($_.SharePointLocation).Count -gt 0) -or
        ($_.OneDriveLocation   -and @($_.OneDriveLocation).Count   -gt 0) -or
        ($_.Workload -and $_.Workload -match 'SharePoint')
    })
    if ($sharepointPolicies.Count -gt 0) {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'SharePoint/OneDrive Covered by Retention'
            CurrentValue     = "$($sharepointPolicies.Count) policies cover SharePoint/OneDrive"
            RecommendedValue = 'At least 1 policy covers SharePoint/OneDrive'
            Status           = 'Pass'
            CheckId          = 'PURVIEW-RETENTION-004'
            Remediation      = 'No action needed.'
        }
        Add-Setting @settingParams
    }
    else {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'SharePoint/OneDrive Covered by Retention'
            CurrentValue     = 'No retention policies cover SharePoint/OneDrive'
            RecommendedValue = 'At least 1 policy covers SharePoint/OneDrive'
            Status           = 'Fail'
            CheckId          = 'PURVIEW-RETENTION-004'
            Remediation      = 'Microsoft Purview > Data lifecycle management > Retention policies > Create or edit a retention policy to include SharePoint sites and OneDrive accounts.'
        }
        Add-Setting @settingParams
    }

    # Check 5: All active policies are in Enforce mode (not simulation/test)
    $testModePolicies = @($enabledPolicies | Where-Object { $_.Mode -ne 'Enforce' })
    if ($testModePolicies.Count -eq 0 -and $enabledPolicies.Count -gt 0) {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Retention Policies in Enforce Mode'
            CurrentValue     = "All $($enabledPolicies.Count) enabled policies in Enforce mode"
            RecommendedValue = 'All policies in Enforce mode'
            Status           = 'Pass'
            CheckId          = 'PURVIEW-RETENTION-005'
            Remediation      = 'No action needed.'
        }
        Add-Setting @settingParams
    }
    elseif ($testModePolicies.Count -gt 0) {
        $testNames = ($testModePolicies | Select-Object -ExpandProperty Name -First 5) -join ', '
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Retention Policies in Enforce Mode'
            CurrentValue     = "$($testModePolicies.Count) policies in simulation/test mode: $testNames"
            RecommendedValue = 'All policies in Enforce mode'
            Status           = 'Warning'
            CheckId          = 'PURVIEW-RETENTION-005'
            Remediation      = 'Microsoft Purview > Data lifecycle management > Retention policies > Edit each policy in simulation mode and switch it to Enforce once validated.'
        }
        Add-Setting @settingParams
    }
    else {
        $settingParams = @{
            Category         = 'Retention Policies'
            Setting          = 'Retention Policies in Enforce Mode'
            CurrentValue     = 'No enabled policies to evaluate'
            RecommendedValue = 'At least 1 policy in Enforce mode'
            Status           = 'Review'
            CheckId          = 'PURVIEW-RETENTION-005'
            Remediation      = 'Create and enforce retention policies in Microsoft Purview > Data lifecycle management.'
        }
        Add-Setting @settingParams
    }
}
else {
    # Cmdlet not available -- Purview connection required
    $settingParams = @{
        Category         = 'Retention Policies'
        Setting          = 'Retention Policies Configured'
        CurrentValue     = 'Cmdlet not available'
        RecommendedValue = 'At least 1 enabled'
        Status           = 'Review'
        CheckId          = 'PURVIEW-RETENTION-001'
        Remediation      = 'Connect to Security & Compliance PowerShell to check retention policies: Connect-Service -Service Purview.'
    }
    Add-Setting @settingParams
}

# ------------------------------------------------------------------
# Output
# ------------------------------------------------------------------
$report = @($settings)
Write-Verbose "Collected $($report.Count) Purview retention configuration settings"

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