Private/ConvertTo-DriftGroupedHtmlReport.ps1
|
function ConvertTo-DriftGroupedHtmlReport { <# .SYNOPSIS Converts Management Group / Subscription drift data into a grouped HTML report. #> [CmdletBinding()] param( [Parameter(Mandatory)] [string]$Title, [Parameter(Mandatory)] [System.Collections.IDictionary]$DriftResult, [System.Collections.IDictionary]$ManagementGroupNames = @{}, [System.Collections.IDictionary]$SubscriptionNames = @{}, [string]$GeneratedBy = 'PowerShell.IGA.AccessGuard' ) $generated_on = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $style = @' <style> body { font-family: Segoe UI, Arial, sans-serif; margin: 20px; color: #222; } h1 { margin-bottom: 0; } .subtitle { color: #666; margin-top: 4px; } h2 { border-bottom: 2px solid #444; padding-bottom: 4px; margin-top: 30px; } details { margin-bottom: 8px; border: 1px solid #ccc; border-radius: 4px; padding: 6px 10px; } summary { font-weight: bold; cursor: pointer; } table { border-collapse: collapse; width: 100%; margin-top: 8px; } th, td { border: 1px solid #ddd; padding: 6px 8px; font-size: 13px; text-align: left; } th { background-color: #f2f2f2; } td.added { background-color: #e8f5e9; color: #1b5e20; font-weight: 600; } td.absent { background-color: #ffebee; color: #b71c1c; font-weight: 600; } td.removed { background-color: #ffebee; color: #b71c1c; font-weight: 600; } </style> '@ function ConvertTo-DriftScopeTable { param([array]$Entries) if (-not $Entries -or $Entries.Count -eq 0) { return '<p><em>No changes.</em></p>' } $columns = 'Status', 'SuggestedAction', 'Scope', 'DisplayName', 'SignInName', 'ObjectId', 'ObjectType', 'RoleDefinitionName' $sb = New-Object System.Text.StringBuilder [void]$sb.Append('<table><thead><tr>') foreach ($column in $columns) { [void]$sb.Append("<th>$([System.Net.WebUtility]::HtmlEncode($column))</th>") } [void]$sb.Append('</tr></thead><tbody>') foreach ($entry in $Entries) { $assignment = $entry.Assignment $status_class = switch ($entry.Status) { 'Added' { ' class="added"'; break } 'Absent' { ' class="absent"'; break } 'Removed' { ' class="removed"'; break } default { '' } } [void]$sb.Append('<tr>') foreach ($column in $columns) { $value = if ($column -eq 'Status') { $entry.Status } elseif ($column -eq 'SuggestedAction') { $entry.SuggestedAction } else { $assignment.$column } $cell_class = if ($column -eq 'Status') { $status_class } else { '' } [void]$sb.Append("<td$cell_class>$([System.Net.WebUtility]::HtmlEncode([string]$value))</td>") } [void]$sb.Append('</tr>') } [void]$sb.Append('</tbody></table>') return $sb.ToString() } function ConvertTo-DriftGroupSectionHtml { param([string]$SectionTitle, [System.Collections.IDictionary]$Data, [System.Collections.IDictionary]$Names) $sb = New-Object System.Text.StringBuilder [void]$sb.Append("<h2>$([System.Net.WebUtility]::HtmlEncode($SectionTitle))</h2>") if (-not $Data -or $Data.Keys.Count -eq 0) { [void]$sb.Append('<p><em>No changes.</em></p>') return $sb.ToString() } foreach ($id in $Data.Keys) { $entries = @($Data[$id]) $label = if ($Names -and $Names.Contains($id) -and $Names[$id]) { "$id ($($Names[$id]))" } else { $id } [void]$sb.Append("<details><summary>$([System.Net.WebUtility]::HtmlEncode($label)) ($($entries.Count))</summary>") [void]$sb.Append((ConvertTo-DriftScopeTable -Entries $entries)) [void]$sb.Append('</details>') } return $sb.ToString() } $body = "<h1>$([System.Net.WebUtility]::HtmlEncode($Title))</h1>" $body += "<p class='subtitle'>Generated by $([System.Net.WebUtility]::HtmlEncode($GeneratedBy)) on $generated_on</p>" $body += ConvertTo-DriftGroupSectionHtml -SectionTitle 'Management Groups' -Data $DriftResult.ManagementGroup -Names $ManagementGroupNames $body += ConvertTo-DriftGroupSectionHtml -SectionTitle 'Subscriptions' -Data $DriftResult.Subscription -Names $SubscriptionNames return "<html><head><meta charset='utf-8'><title>$([System.Net.WebUtility]::HtmlEncode($Title))</title>$style</head><body>$body</body></html>" } |