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'

# Load shared security-config helpers
$_scriptDir = if ($MyInvocation.MyCommand.Path) { Split-Path -Parent $MyInvocation.MyCommand.Path } else { $PSScriptRoot }
. (Join-Path -Path $_scriptDir -ChildPath '..\Common\SecurityConfigHelper.ps1')

$ctx = Initialize-SecurityConfig
$settings = $ctx.Settings
$checkIdCounter = $ctx.CheckIdCounter

function Add-Setting {
    param(
        [string]$Category, [string]$Setting, [string]$CurrentValue,
        [string]$RecommendedValue, [string]$Status,
        [string]$CheckId = '', [string]$Remediation = ''
    )
    $p = @{
        Settings         = $settings
        CheckIdCounter   = $checkIdCounter
        Category         = $Category
        Setting          = $Setting
        CurrentValue     = $CurrentValue
        RecommendedValue = $RecommendedValue
        Status           = $Status
        CheckId          = $CheckId
        Remediation      = $Remediation
    }
    Add-SecuritySetting @p
}

# ------------------------------------------------------------------
# 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
# ------------------------------------------------------------------
Export-SecurityConfigReport -Settings $settings -OutputPath $OutputPath -ServiceLabel 'Purview Retention'