Common/Build-IntuneOverviewHtml.ps1
|
function Build-IntuneOverviewHtml { <# .SYNOPSIS Builds the Intune Overview HTML page for the assessment report. .DESCRIPTION Renders a summary page covering managed device count, compliance rate, policy inventory, per-category security check status, and a filterable findings table for all INTUNE-* checks. .PARAMETER Findings All CIS findings from the assessment ($allCisFindings). .PARAMETER AssessmentFolder Path to the assessment output folder. Used to read device summary and policy CSVs. .EXAMPLE $intuneOverviewHtml = Build-IntuneOverviewHtml -Findings $allCisFindings -AssessmentFolder $AssessmentFolder #> [CmdletBinding()] [OutputType([string])] param( [Parameter()] [PSCustomObject[]]$Findings = @(), [Parameter()] [string]$AssessmentFolder = '' ) $intuneFindings = @($Findings | Where-Object { $_.CheckId -like 'INTUNE-*' }) if ($intuneFindings.Count -eq 0) { return '' } # ------------------------------------------------------------------ # Metric data from CSVs # ------------------------------------------------------------------ $deviceCount = 'N/A' $compliantPct = 'N/A' $policyCount = 'N/A' $profileCount = 'N/A' $compliancePctClass = '' if ($AssessmentFolder -and (Test-Path -Path $AssessmentFolder)) { $deviceCsv = Join-Path -Path $AssessmentFolder -ChildPath '13-Device-Summary.csv' if (Test-Path -Path $deviceCsv) { $devices = @(Import-Csv -Path $deviceCsv -ErrorAction SilentlyContinue) if ($devices.Count -gt 0) { $deviceCount = $devices.Count $compliantCount = @($devices | Where-Object { $_.ComplianceState -eq 'Compliant' }).Count $pct = [int][Math]::Round(($compliantCount / $devices.Count) * 100) $compliantPct = "$pct%" $compliancePctClass = if ($pct -ge 80) { 'id-metric-success' } elseif ($pct -ge 60) { 'id-metric-warning' } else { 'id-metric-danger' } } } $policyCsv = Join-Path -Path $AssessmentFolder -ChildPath '14-Compliance-Policies.csv' if (Test-Path -Path $policyCsv) { $policyCount = @(Import-Csv -Path $policyCsv -ErrorAction SilentlyContinue).Count } $profileCsv = Join-Path -Path $AssessmentFolder -ChildPath '15-Config-Profiles.csv' if (Test-Path -Path $profileCsv) { $profileCount = @(Import-Csv -Path $profileCsv -ErrorAction SilentlyContinue).Count } } # Fallback: parse INTUNE-INVENTORY-001 CurrentValue for device count if ($deviceCount -eq 'N/A') { $invCheck = $intuneFindings | Where-Object { $_.CheckId -like 'INTUNE-INVENTORY-001*' } | Select-Object -First 1 if ($invCheck -and $invCheck.CurrentValue -match '(\d+)\s+device') { $deviceCount = $Matches[1] } } # ------------------------------------------------------------------ # Check status summary # ------------------------------------------------------------------ $passCount = @($intuneFindings | Where-Object { $_.Status -eq 'Pass' }).Count $failCount = @($intuneFindings | Where-Object { $_.Status -eq 'Fail' }).Count $warnCount = @($intuneFindings | Where-Object { $_.Status -eq 'Warning' }).Count $reviewCount = @($intuneFindings | Where-Object { $_.Status -eq 'Review' }).Count $total = $intuneFindings.Count $passRatePct = if ($total -gt 0) { [int][Math]::Round(($passCount / $total) * 100) } else { 0 } $passRateClass = if ($passRatePct -ge 80) { 'id-metric-success' } elseif ($passRatePct -ge 60) { 'id-metric-warning' } else { 'id-metric-danger' } # ------------------------------------------------------------------ # Category groups sorted by worst status # ------------------------------------------------------------------ $statusPriority = @{ 'Fail' = 0; 'Warning' = 1; 'Review' = 2; 'Pass' = 3; 'Info' = 4; 'Skipped' = 5 } $categories = $intuneFindings | Group-Object -Property Category | ForEach-Object { $worstStatus = ($_.Group | Sort-Object -Property @{ Expression = { if ($statusPriority.ContainsKey($_.Status)) { $statusPriority[$_.Status] } else { 99 } } } | Select-Object -First 1).Status [PSCustomObject]@{ Category = $_.Name Count = $_.Group.Count WrstStatus = $worstStatus Priority = if ($statusPriority.ContainsKey($worstStatus)) { $statusPriority[$worstStatus] } else { 99 } } } | Sort-Object -Property Priority $catIcons = @{ 'Compliance' = '📋' 'Enrollment' = '📱' 'Inventory' = '📦' 'Automated Discovery' = '🔍' 'Portable Storage' = '💾' 'Security' = '🛡' 'Personal Device Enrollment' = '👤' 'Application Control' = '⚙️' 'Encryption' = '🔐' 'Windows Update' = '🔄' 'Mobile Encryption' = '📱' 'FIPS Cryptography' = '🔒' } $sortedFindings = $intuneFindings | Sort-Object -Property @( @{ Expression = { if ($statusPriority.ContainsKey($_.Status)) { $statusPriority[$_.Status] } else { 99 } } } @{ Expression = { $_.Category } } @{ Expression = { $_.CheckId } } ) $html = [System.Text.StringBuilder]::new() $null = $html.AppendLine("<details class='section' id='intune-overview-section' open>") $null = $html.AppendLine("<summary><h2>Intune Overview</h2></summary>") # ------------------------------------------------------------------ # Metric cards # ------------------------------------------------------------------ $null = $html.AppendLine("<div class='email-metrics-grid'>") $null = $html.AppendLine("<div class='email-metric-card'><div class='email-metric-icon'>📱</div><div class='email-metric-body'><div class='email-metric-value'>$deviceCount</div><div class='email-metric-label'>Managed Devices</div></div></div>") $null = $html.AppendLine("<div class='email-metric-card $compliancePctClass'><div class='email-metric-icon'>✅</div><div class='email-metric-body'><div class='email-metric-value'>$compliantPct</div><div class='email-metric-label'>Compliant Devices</div></div></div>") $null = $html.AppendLine("<div class='email-metric-card'><div class='email-metric-icon'>📋</div><div class='email-metric-body'><div class='email-metric-value'>$policyCount</div><div class='email-metric-label'>Compliance Policies</div></div></div>") $null = $html.AppendLine("<div class='email-metric-card'><div class='email-metric-icon'>⚙️</div><div class='email-metric-body'><div class='email-metric-value'>$profileCount</div><div class='email-metric-label'>Config Profiles</div></div></div>") $null = $html.AppendLine("<div class='email-metric-card $passRateClass'><div class='email-metric-icon'>🛡</div><div class='email-metric-body'><div class='email-metric-value'>$passRatePct%</div><div class='email-metric-label'>Checks Passing</div></div></div>") $null = $html.AppendLine("</div>") # ------------------------------------------------------------------ # Category coverage grid # ------------------------------------------------------------------ $null = $html.AppendLine("<details class='collector-detail' open>") $null = $html.AppendLine("<summary><h3>Security Check Coverage by Category</h3><span class='row-count'>($($categories.Count) categories, $total checks)</span></summary>") $null = $html.AppendLine("<div class='intune-category-grid'>") foreach ($cat in $categories) { $icon = if ($catIcons.ContainsKey($cat.Category)) { $catIcons[$cat.Category] } else { '🔒' } $badgeCls = switch ($cat.WrstStatus) { 'Fail' { 'badge-failed' } 'Warning' { 'badge-warning' } 'Review' { 'badge-review' } 'Pass' { 'badge-success' } default { 'badge-neutral' } } $borderCls = switch ($cat.WrstStatus) { 'Fail' { 'intune-cat-fail' } 'Warning' { 'intune-cat-warning' } 'Review' { 'intune-cat-review' } 'Pass' { 'intune-cat-pass' } default { '' } } $catEncoded = ConvertTo-HtmlSafe -Text $cat.Category $checkWord = if ($cat.Count -eq 1) { 'check' } else { 'checks' } $null = $html.AppendLine("<div class='intune-cat-card $borderCls'>") $null = $html.AppendLine("<div class='intune-cat-icon'>$icon</div>") $null = $html.AppendLine("<div class='intune-cat-body'><div class='intune-cat-name'>$catEncoded</div>") $null = $html.AppendLine("<div class='intune-cat-meta'><span class='badge $badgeCls'>$($cat.WrstStatus)</span> <span class='intune-cat-count'>$($cat.Count) $checkWord</span></div>") $null = $html.AppendLine("</div></div>") } $null = $html.AppendLine("</div>") # intune-category-grid $null = $html.AppendLine("</details>") # collector-detail # ------------------------------------------------------------------ # Status chip filter + findings table # ------------------------------------------------------------------ $null = $html.AppendLine("<div class='remediation-chip-bar'>") $null = $html.AppendLine("<div class='rem-chip-section'>") $null = $html.AppendLine("<span class='rem-filter-label'>Status:</span>") $null = $html.AppendLine("<div class='rem-chip-group' id='intuneStatusChips'>") foreach ($se in @( @('Fail',$failCount), @('Warning',$warnCount), @('Review',$reviewCount), @('Pass',$passCount) )) { if ($se[1] -gt 0) { $null = $html.AppendLine("<label class='fw-checkbox active rem-sev-chip' data-intune-status='$($se[0])' onclick='toggleIntuneChip(this); return false;'><input type='checkbox' checked hidden>$($se[0]) <span class='rem-chip-count'>$($se[1])</span></label>") } } $null = $html.AppendLine("</div>") $null = $html.AppendLine("<span class='fw-selector-actions'><button type='button' class='fw-action-btn rem-intune-all' onclick='setAllIntuneChips(this)'>All</button><button type='button' class='fw-action-btn rem-intune-none' onclick='setAllIntuneChips(this)'>None</button></span>") $null = $html.AppendLine("</div></div>") $findingWord = if ($sortedFindings.Count -eq 1) { 'check' } else { 'checks' } $null = $html.AppendLine("<details class='collector-detail' id='intuneTableDetail' open>") $null = $html.AppendLine("<summary><h3>All Intune Checks</h3><span class='row-count' id='intuneMatchCount'>($($sortedFindings.Count) $findingWord)</span></summary>") $null = $html.AppendLine("<div class='col-picker-bar'>") $null = $html.AppendLine("<button type='button' class='col-picker-toggle'>Columns ▾</button>") $null = $html.AppendLine("<div class='col-picker-panel' hidden>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneCategory' checked> Category</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneCheck' checked> Check</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneCheckId' data-col-default='hidden'> Check ID</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneStatus' checked> Status</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneSeverity' checked> Severity</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneValue' checked> Current Value</label>") $null = $html.AppendLine("<label class='col-picker-item'><input type='checkbox' data-col-key='IntuneRemediation' checked> Remediation</label>") $null = $html.AppendLine("</div></div>") $null = $html.AppendLine("<div class='table-wrapper'>") $null = $html.AppendLine("<table class='data-table' id='intuneTable'>") $null = $html.AppendLine("<thead><tr>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneCategory'>Category</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneCheck'>Check</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneCheckId' style='display:none'>Check ID</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneStatus'>Status</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneSeverity'>Severity</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneValue'>Current Value</th>") $null = $html.AppendLine("<th scope='col' data-col-key='IntuneRemediation'>Remediation</th>") $null = $html.AppendLine("</tr></thead><tbody>") foreach ($finding in $sortedFindings) { $statusBadge = switch ($finding.Status) { 'Fail' { 'badge-failed' } 'Warning' { 'badge-warning' } 'Review' { 'badge-review' } 'Pass' { 'badge-success' } default { 'badge-neutral' } } $sevBadge = switch ($finding.RiskSeverity) { 'Critical' { 'badge-fail' } 'High' { 'badge-warning' } 'Medium' { 'badge-review' } default { 'badge-neutral' } } $rowCls = switch ($finding.Status) { 'Fail' { 'cis-row-fail' } 'Warning' { 'cis-row-warning' } 'Review' { 'cis-row-review' } 'Pass' { 'cis-row-pass' } default { '' } } $sev = if ($finding.RiskSeverity) { $finding.RiskSeverity } else { 'Low' } $catEnc = ConvertTo-HtmlSafe -Text $finding.Category $checkEnc = ConvertTo-HtmlSafe -Text $finding.Setting $checkIdEnc = ConvertTo-HtmlSafe -Text $(if ($finding.CheckId) { $finding.CheckId } else { '' }) $valueEnc = ConvertTo-HtmlSafe -Text $finding.CurrentValue $remEnc = ConvertTo-HtmlSafe -Text $(if ($finding.Remediation) { $finding.Remediation } else { '' }) $null = $html.AppendLine("<tr class='$rowCls' data-intune-status='$($finding.Status)'>") $null = $html.AppendLine("<td data-col-key='IntuneCategory'>$catEnc</td>") $null = $html.AppendLine("<td data-col-key='IntuneCheck'>$checkEnc</td>") $null = $html.AppendLine("<td data-col-key='IntuneCheckId' style='display:none'>$checkIdEnc</td>") $null = $html.AppendLine("<td data-col-key='IntuneStatus'><span class='badge $statusBadge'>$(ConvertTo-HtmlSafe -Text $finding.Status)</span></td>") $null = $html.AppendLine("<td data-col-key='IntuneSeverity'><span class='badge $sevBadge'>$(ConvertTo-HtmlSafe -Text $sev)</span></td>") $null = $html.AppendLine("<td data-col-key='IntuneValue'>$valueEnc</td>") $null = $html.AppendLine("<td data-col-key='IntuneRemediation'><span class='rem-text'>$remEnc</span></td>") $null = $html.AppendLine("</tr>") } $null = $html.AppendLine("</tbody></table></div>") $null = $html.AppendLine("<p id='intuneNoResults' class='no-results' style='display:none'>No checks match the current filter selection.</p>") $null = $html.AppendLine("</details>") # collector-detail table $null = $html.AppendLine("</details>") # section return $html.ToString() } |