Private/Analyze/Invoke-PpaRetentionAnalyzer.ps1
|
# Invoke-PpaRetentionAnalyzer.ps1 - analyzer for section 03 (Retention & Records). # Produces RET-01..03 per CHECK_CATALOG.md. Pure function of its input. # ASCII-only source (Windows PowerShell 5.1). Depends on New-PpaFinding/New-PpaSection. Set-StrictMode -Off function Invoke-PpaRetentionAnalyzer { [CmdletBinding()] param( [Parameter(Mandatory = $true)] $Raw, # Parsed Data/license-requirements.json (static annotation map, not detection). $LicenseMap ) $mid = Get-PpaMidDot $pols = @($Raw.policies.items) $labels = @($Raw.labels.items) $adaptiveScopeCount = [int]$Raw.adaptiveScopes.count $findings = New-Object System.Collections.Generic.List[object] # --- RET-01: inventory of policies & labels --- # F-001: a failed policy read must not report "0 policies" as fact (unknown != empty). $lm01 = @(@{ label = 'Learn about retention policies & labels'; url = 'https://learn.microsoft.com/en-us/purview/retention'; tag = 'docs' }) if ([string]$Raw.policies.status -ne 'Ok') { $findings.Add((New-PpaFinding -Id 'RET-01' -DomId 'f-ret-1' -Title 'Retention policies not readable this session' -Status 'Verify manually' ` -Whyline 'Retention policies could not be enumerated this session, so retention posture was not evaluated - confirm in the Purview portal.' ` -Table (New-PpaTable -Columns @('Configuration', 'Setting', 'Status') -Rows @((New-PpaRow -Cells @('Retention policies', 'Not readable this session') -Status 'Verify manually' -Remark ([string]$Raw.policies.error)))) -LearnMore $lm01)) } elseif ($pols.Count -eq 0) { $findings.Add((New-PpaFinding -Id 'RET-01' -DomId 'f-ret-1' -Title 'No retention policies configured' -Status 'Improvement' ` -Whyline 'With no retention policies, data is neither retained for compliance nor disposed of on a schedule.' ` -Table (New-PpaTable -Columns @('Configuration', 'Setting', 'Status') -Rows @((New-PpaRow -Cells @('Retention policies', '0') -Status 'Improvement'))) -LearnMore $lm01)) } else { $rows01 = foreach ($p in $pols) { $scopeType = if ($p.adaptive) { 'Adaptive' } else { 'Static' } $remark = "$scopeType $mid " + (@($p.locations) -join ', ') New-PpaRow -Cells @($p.name, (@($p.labels) -join ', '), $remark) -Status 'Informational' } $findings.Add((New-PpaFinding -Id 'RET-01' -DomId 'f-ret-1' -Title "$($pols.Count) retention policies, $($labels.Count) retention labels" -Status 'Informational' ` -Whyline "Baseline of what's being retained and where." ` -Table (New-PpaTable -Columns @('Retention Policy', 'Labels', 'Remarks', 'Status') -Rows @($rows01)) -LearnMore $lm01)) } # --- RET-02: adaptive scopes --- $lm02 = @(@{ label = 'Adaptive vs. static scopes'; url = 'https://learn.microsoft.com/en-us/purview/retention-policies-adaptive'; tag = 'docs' }) if ([string]$Raw.adaptiveScopes.status -ne 'Ok') { # F-001: the adaptive-scope count is 0 only because the read did not complete. $findings.Add((New-PpaFinding -Id 'RET-02' -DomId 'f-ret-2' -Title 'Adaptive scopes not readable this session' -Status 'Verify manually' -Requires (Get-PpaRequirement $LicenseMap 'RET-02') ` -Whyline 'Adaptive scopes could not be enumerated this session - confirm in the Purview portal.' ` -Table (New-PpaTable -Columns @('Configuration', 'Setting', 'Status') -Rows @((New-PpaRow -Cells @('Adaptive scopes', 'Not readable this session') -Status 'Verify manually'))) -LearnMore $lm02)) } else { $staticCount = @($pols | Where-Object { -not $_.adaptive }).Count $rows02 = @( New-PpaRow -Cells @('Static scopes', [string]$staticCount) -Status 'Informational' New-PpaRow -Cells @('Adaptive scopes', [string]$adaptiveScopeCount) -Status ($(if ($adaptiveScopeCount -gt 0) { 'OK' } else { 'Improvement' })) ) $findings.Add((New-PpaFinding -Id 'RET-02' -DomId 'f-ret-2' -Title ($(if ($adaptiveScopeCount -gt 0) { 'Adaptive scopes in use' } else { 'No adaptive scopes' })) -Status ($(if ($adaptiveScopeCount -gt 0) { 'OK' } else { 'Improvement' })) -Requires (Get-PpaRequirement $LicenseMap 'RET-02') ` -Whyline 'Static scopes drift as the org changes; adaptive scopes keep coverage current by attribute/query.' ` -Table (New-PpaTable -Columns @('Scope type', 'Count', 'Status') -Rows $rows02) ` -LearnMore $lm02)) } # --- RET-03: manual-apply retention labels --- $manualNames = @($labels | Where-Object { -not $_.autoApply } | ForEach-Object { $_.name }) $manualCount = $manualNames.Count $lm03 = @(@{ label = 'Auto-apply retention labels'; url = 'https://learn.microsoft.com/en-us/purview/apply-retention-labels-automatically'; tag = 'docs' }) if ([string]$Raw.labels.status -ne 'Ok') { # F-001: a failed retention-rule read must not report "0 labels" as fact. $findings.Add((New-PpaFinding -Id 'RET-03' -DomId 'f-ret-3' -Title 'Retention labels not readable this session' -Status 'Verify manually' ` -Whyline 'Retention labels could not be enumerated this session - confirm in the Purview portal.' ` -Table (New-PpaTable -Columns @('Retention label', 'Auto-apply rule', 'Status') -Rows @((New-PpaRow -Cells @('Retention labels', 'Not readable this session') -Status 'Verify manually'))) -LearnMore $lm03)) } elseif ($labels.Count -eq 0) { # Zero labels is NOT "all auto-apply" - there is simply nothing to judge here. $findings.Add((New-PpaFinding -Id 'RET-03' -DomId 'f-ret-3' -Title 'No retention labels defined' -Status 'Informational' ` -Whyline 'There are no retention labels, so auto-apply does not come into play; retention relies on policies alone.' ` -Table (New-PpaTable -Columns @('Retention label', 'Auto-apply rule', 'Status') -Rows @((New-PpaRow -Cells @('Retention labels', '0') -Status 'Informational'))) -LearnMore $lm03)) } elseif ($manualCount -eq 0) { $findings.Add((New-PpaFinding -Id 'RET-03' -DomId 'f-ret-3' -Title 'Retention labels have auto-apply conditions' -Status 'OK' ` -Whyline 'Auto-apply conditions remove reliance on users tagging content by hand.' ` -Table (New-PpaTable -Columns @('Retention label', 'Auto-apply rule', 'Status') -Rows @((New-PpaRow -Cells @('Retention labels', 'All auto-apply') -Status 'OK'))) -LearnMore $lm03)) } else { # Show the in-use (policy-attached) manual labels as rows; summarize the full total in a remark. $attached = @($pols | ForEach-Object { $_.labels } | Where-Object { $_ } | Select-Object -Unique) $displayNames = @($attached | Where-Object { $manualNames -contains $_ }) if ($displayNames.Count -eq 0) { $displayNames = @($manualNames | Select-Object -First 5) } $rows03 = New-Object System.Collections.Generic.List[object] for ($i = 0; $i -lt $displayNames.Count; $i++) { $remark = $null if ($i -eq $displayNames.Count - 1) { $remark = "$manualCount of $($labels.Count) retention labels have no auto-apply (SIT / KQL / trainable) condition." } $rows03.Add((New-PpaRow -Cells @($displayNames[$i], 'None (manual)') -Status 'Improvement' -Remark $remark)) } $findings.Add((New-PpaFinding -Id 'RET-03' -DomId 'f-ret-3' -Title 'Retention labels are manual-apply only' -Status 'Improvement' ` -Whyline 'Retention outcomes depend on users tagging content, which is inconsistent at scale.' ` -Table (New-PpaTable -Columns @('Retention label', 'Auto-apply rule', 'Status') -Rows $rows03.ToArray()) -LearnMore $lm03)) } # --- glance --- if ([string]$Raw.policies.status -ne 'Ok') { $glance = New-PpaGlance -Name 'Retention & Records' -Metric 'not readable' -Sub 'confirm in portal' } else { $scopeState = if ($adaptiveScopeCount -gt 0) { 'adaptive + static' } else { 'static only' } $glance = New-PpaGlance -Name 'Retention & Records' -Metric "$($pols.Count) policies" -Sub "$($labels.Count) labels $mid $scopeState" } return New-PpaSection -Id 'Retention' -Title 'Retention & Records' -Group 'Data Lifecycle & Records' ` -GroupIcon 'fas fa-archive' -Glance $glance -Findings $findings.ToArray() } |