Private/New-HtmlDashboard.ps1
|
function New-HtmlDashboard { <# .SYNOPSIS Generates an HTML dashboard report from audit sections. #> [CmdletBinding()] param( [Parameter(Mandatory)] [array]$Sections, [Parameter(Mandatory)] [string]$OutputFile, [Parameter()] [string]$ReportTitle = 'Entra ID Security Audit' ) $timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss' $hostname = $env:COMPUTERNAME $sectionHtml = foreach ($section in $Sections) { $rows = foreach ($item in $section.Data) { $cells = foreach ($prop in $item.PSObject.Properties) { $value = $prop.Value $class = if ($prop.Name -eq 'Finding' -and $value -ne 'OK' -and $value -ne 'PIM Eligible (Good)') { ' class="finding-bad"' } elseif ($prop.Name -eq 'Finding' -and ($value -eq 'OK' -or $value -eq 'PIM Eligible (Good)')) { ' class="finding-ok"' } else { '' } " <td$class>$([System.Web.HttpUtility]::HtmlEncode($value))</td>" } " <tr>`n$($cells -join "`n")`n </tr>" } $headers = if ($section.Data.Count -gt 0) { $section.Data[0].PSObject.Properties.Name | ForEach-Object { " <th>$_</th>" } } @" <div class="section"> <h2>$($section.Title)</h2> <p class="summary">$($section.Summary)</p> <div class="table-wrapper"> <table> <thead> <tr> $($headers -join "`n") </tr> </thead> <tbody> $($rows -join "`n") </tbody> </table> </div> </div> "@ } $totalFindings = ($Sections | ForEach-Object { $_.Data | Where-Object { $_.Finding -and $_.Finding -ne 'OK' -and $_.Finding -ne 'PIM Eligible (Good)' } } | Measure-Object).Count $html = @" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>$ReportTitle</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, sans-serif; background: #0d1117; color: #c9d1d9; padding: 2rem; } .header { background: linear-gradient(135deg, #1a1f2e 0%, #0f3460 100%); padding: 2rem; border-radius: 8px; margin-bottom: 2rem; } .header h1 { color: #58a6ff; font-size: 1.8rem; margin-bottom: 0.5rem; } .header .meta { color: #8b949e; font-size: 0.9rem; } .summary-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .card { background: #161b22; border: 1px solid #30363d; border-radius: 8px; padding: 1.5rem; text-align: center; } .card .number { font-size: 2.5rem; font-weight: 700; } .card .label { color: #8b949e; font-size: 0.85rem; margin-top: 0.5rem; } .card.findings .number { color: #f85149; } .card.clean .number { color: #3fb950; } .section { background: #161b22; border: 1px solid #30363d; border-radius: 8px; margin-bottom: 1.5rem; padding: 1.5rem; } .section h2 { color: #58a6ff; font-size: 1.3rem; margin-bottom: 0.5rem; } .section .summary { color: #8b949e; margin-bottom: 1rem; font-size: 0.9rem; } .table-wrapper { overflow-x: auto; } table { width: 100%; border-collapse: collapse; font-size: 0.85rem; } th { background: #21262d; color: #58a6ff; padding: 0.75rem; text-align: left; border-bottom: 2px solid #30363d; white-space: nowrap; } td { padding: 0.6rem 0.75rem; border-bottom: 1px solid #21262d; } tr:hover { background: #1c2128; } .finding-bad { color: #f85149; font-weight: 600; } .finding-ok { color: #3fb950; } .footer { text-align: center; color: #484f58; margin-top: 2rem; font-size: 0.8rem; } </style> </head> <body> <div class="header"> <h1>$ReportTitle</h1> <div class="meta">Generated $timestamp | Host: $hostname | EntraID-SecurityAudit v1.0.0</div> </div> <div class="summary-cards"> <div class="card"> <div class="number">$($Sections.Count)</div> <div class="label">Audit Sections</div> </div> <div class="card$(if ($totalFindings -gt 0) { ' findings' } else { ' clean' })"> <div class="number">$totalFindings</div> <div class="label">Total Findings</div> </div> </div> $($sectionHtml -join "`n") <div class="footer"> Generated by EntraID-SecurityAudit | github.com/larro1991/EntraID-SecurityAudit </div> </body> </html> "@ $html | Out-File -FilePath $OutputFile -Encoding UTF8 -Force } |