Public/New-MgkHtmlReport.ps1

function New-MgkHtmlReport {
    <#
    .SYNOPSIS
        Renders objects into a styled HTML report.
    .DESCRIPTION
        Generic object-table renderer with MSP-report styling: title, run
        timestamp, summary line, and severity-aware row colouring — any input
        object with a Severity property of Critical/Warning/Error gets a
        coloured row; objects with Success=$false are highlighted too.
        All values are HTML-encoded.
    .PARAMETER InputObject
        Objects to render (pipeline-friendly). Property names become columns,
        taken from the first object.
    .PARAMETER Title
        Report heading.
    .PARAMETER OutFile
        Destination HTML path. The rendered path is also returned.
    .EXAMPLE
        $sweepResults | New-MgkHtmlReport -Title 'Fleet license check' -OutFile report.html
    #>

    [CmdletBinding(SupportsShouldProcess)]
    [OutputType([string])]
    param(
        [Parameter(Mandatory, ValueFromPipeline)][object[]]$InputObject,
        [Parameter(Mandatory)][string]$Title,
        [Parameter(Mandatory)][string]$OutFile
    )

    begin { $items = [System.Collections.Generic.List[object]]::new() }
    process { $items.AddRange(@($InputObject)) }
    end {
        if ($items.Count -eq 0) { throw 'No input objects to render.' }
        if (-not $PSCmdlet.ShouldProcess($OutFile, 'Write HTML report')) { return }

        $encode = { param($v) [System.Web.HttpUtility]::HtmlEncode("$v") }
        $columns = $items[0].PSObject.Properties.Name

        $rows = foreach ($item in $items) {
            $cls = ''
            if ($item.PSObject.Properties['Severity']) {
                $cls = switch ("$($item.Severity)") {
                    'Critical' { 'crit' } 'Error' { 'crit' } 'Warning' { 'warn' } default { '' }
                }
            }
            elseif ($item.PSObject.Properties['Success'] -and -not $item.Success) { $cls = 'crit' }

            $cells = ($columns | ForEach-Object { "<td>$(& $encode $item.$_)</td>" }) -join ''
            "<tr class='$cls'>$cells</tr>"
        }

        $flagged = @($rows | Where-Object { $_ -match "class='(crit|warn)'" }).Count

        @"
<!DOCTYPE html><html><head><meta charset='utf-8'><title>$(& $encode $Title)</title><style>
body{font-family:Segoe UI,sans-serif;margin:2rem;color:#222}
h1{margin-bottom:.2rem} .meta{color:#777;margin-bottom:1.2rem}
table{border-collapse:collapse;width:100%}th,td{padding:.45rem .7rem;border:1px solid #ddd;text-align:left;font-size:.9rem}
th{background:#2c3e50;color:#fff}
tr.crit td{background:#fdecea}tr.warn td{background:#fef5e7}
</style></head><body>
<h1>$(& $encode $Title)</h1>
<p class='meta'>Generated $(Get-Date -Format 'yyyy-MM-dd HH:mm') · $($items.Count) rows · $flagged flagged</p>
<table><tr>$(($columns | ForEach-Object { "<th>$(& $encode $_)</th>" }) -join '')</tr>
$($rows -join "`n")
</table></body></html>
"@
 | Set-Content -Path $OutFile -Encoding utf8

        (Resolve-Path $OutFile).Path
    }
}