Private/Export-WorkspaceTestHtmlReport.ps1
|
function Export-WorkspaceTestHtmlReport { <# .SYNOPSIS Writes an HTML validation report. .DESCRIPTION Creates a self-contained, human-readable HTML report from WorkspaceTestingFramework result objects. Results are sorted by group, type, and name. The report shows a pass/fail verdict banner, system context for the validated machine, clickable summary cards that filter by status, a text search, and collapsible groups. Failed checks are expanded on load. The report has no external dependencies, adapts to light and dark color schemes, and includes a print stylesheet. .PARAMETER Results Result objects to include in the report. .PARAMETER Path Destination path for the HTML report. .OUTPUTS None. .NOTES Private report helper. #> [CmdletBinding()] param( [Parameter(Mandatory)] [object[]]$Results, [Parameter(Mandatory)] [string]$Path ) $Results = @($Results | Sort-Object -Property Group, Type, Name) $directory = Split-Path -Path $Path -Parent if ($directory -and -not (Test-Path -LiteralPath $directory)) { New-Item -Path $directory -ItemType Directory -Force | Out-Null } $metaKeys = [System.Collections.Generic.HashSet[string]]([System.StringComparer]::OrdinalIgnoreCase) foreach ($k in @('Name', 'Group', 'Tags', 'OnFailure', 'Severity', 'PassWhen', 'FailWhen')) { $metaKeys.Add($k) | Out-Null } function ConvertTo-JsonObj { param([object]$Value) if ($null -eq $Value) { return $null } $json = $Value | ConvertTo-Json -Depth 5 -Compress try { return $json | ConvertFrom-Json -ErrorAction Stop } catch { return $null } } function Format-CriteriaRuleText { param([object]$Rules) if ($null -eq $Rules) { return '' } $list = @($Rules) $lines = foreach ($rule in $list) { $parts = @($rule.PSObject.Properties | ForEach-Object { $v = if ($_.Value -is [array]) { $_.Value -join ' | ' } else { $_.Value } "$($_.Name) = $v" }) [System.Net.WebUtility]::HtmlEncode($parts -join '; ') } ($lines | ForEach-Object { "<div class=""criteria-rule"">$_</div>" }) -join '' } # Maps Actual field names to their Expected equivalent for alignment $keyAliases = @{ 'DisplayVersion' = 'Version' 'DisplayName' = 'Name' } function ConvertTo-HtmlDetailTable { param([object]$Expected, [object]$Actual, [string]$Status, [string]$OnFailure) $expObj = ConvertTo-JsonObj $Expected $actObj = ConvertTo-JsonObj $Actual # Normalise Actual keys using aliases so they align with Expected rows if ($actObj) { foreach ($alias in $keyAliases.GetEnumerator()) { $actProp = $actObj.PSObject.Properties[$alias.Key] if ($actProp -and -not $actObj.PSObject.Properties[$alias.Value]) { $actObj | Add-Member -NotePropertyName $alias.Value -NotePropertyValue $actProp.Value -Force $actObj.PSObject.Properties.Remove($alias.Key) } } } # Collect all meaningful keys from both sides, excluding meta $allKeys = [System.Collections.Generic.List[string]]::new() $seen = [System.Collections.Generic.HashSet[string]]([System.StringComparer]::OrdinalIgnoreCase) foreach ($src in @($expObj, $actObj)) { if ($null -eq $src) { continue } foreach ($p in $src.PSObject.Properties) { if (-not $metaKeys.Contains($p.Name) -and $seen.Add($p.Name)) { $allKeys.Add($p.Name) } } } # Field names referenced by PassWhen/FailWhen rules; their expectation # lives in the Criteria card, not in a single Expected value $criteriaKeys = [System.Collections.Generic.HashSet[string]]([System.StringComparer]::OrdinalIgnoreCase) if ($expObj) { foreach ($setName in @('PassWhen', 'FailWhen')) { $set = $expObj.PSObject.Properties[$setName] if ($set -and $null -ne $set.Value) { foreach ($rule in @($set.Value)) { foreach ($p in $rule.PSObject.Properties) { $criteriaKeys.Add($p.Name) | Out-Null } } } } } $rows = foreach ($key in $allKeys) { $expProp = if ($expObj) { $expObj.PSObject.Properties[$key] } else { $null } $actProp = if ($actObj) { $actObj.PSObject.Properties[$key] } else { $null } # $expProp present but null means "key exists, no constraint" -> show (any) $expVal = if ($null -eq $expProp) { $null } elseif ($null -eq $expProp.Value) { '' } elseif ($expProp.Value -is [array]) { $expProp.Value -join ', ' } else { $expProp.Value.ToString() } $actVal = if ($null -eq $actProp) { $null } elseif ($null -eq $actProp.Value) { $null } elseif ($actProp.Value -is [array]) { $actProp.Value -join ', ' } else { $actProp.Value.ToString() } # Skip rows where both sides have nothing to show if ($null -eq $expVal -and $null -eq $actVal) { continue } if ([string]::IsNullOrEmpty($expVal) -and [string]::IsNullOrEmpty($actVal)) { continue } $differs = ($null -ne $expVal -and $expVal -ne '' -and $null -ne $actVal -and $expVal -ne $actVal) $rowClass = if ($differs -and $Status -ne 'Pass') { ' class="row-diff"' } else { '' } $kHtml = [System.Net.WebUtility]::HtmlEncode($key) # Empty string means key present but null value -> show (any) to indicate no constraint. # No expectation at all: point to the Criteria card when the field is ruled there. $eHtml = if ($null -eq $expVal) { if ($criteriaKeys.Contains($key)) { '<em class="na">(see criteria)</em>' } else { '<em class="na">—</em>' } } elseif ($expVal -eq '') { '<em class="na">(any)</em>' } else { [System.Net.WebUtility]::HtmlEncode($expVal) } $aHtml = if ($null -ne $actVal) { [System.Net.WebUtility]::HtmlEncode($actVal) } else { '<em class="na">—</em>' } "<tr$rowClass><td class=""dk"">$kHtml</td><td>$eHtml</td><td>$aHtml</td></tr>" } # Side-by-side cards: comparison, criteria, and settings share the panel width $cards = [System.Collections.Generic.List[string]]::new() $rowsHtml = ($rows -join '') if ($rowsHtml) { $cards.Add("<div class=""detail-card""><div class=""detail-card-title"">Expected vs actual</div><table class=""cmp""><thead><tr><th></th><th>Expected</th><th>Actual</th></tr></thead><tbody>$rowsHtml</tbody></table></div>") } # PassWhen / FailWhen criteria (services, scheduled tasks) if ($expObj) { $pw = $expObj.PSObject.Properties['PassWhen'] $fw = $expObj.PSObject.Properties['FailWhen'] if ($pw -or $fw) { $criteriaRows = '' if ($pw) { $criteriaRows += "<tr><td class=""dk"">PassWhen</td><td>$(Format-CriteriaRuleText $pw.Value)</td></tr>" } if ($fw) { $criteriaRows += "<tr><td class=""dk"">FailWhen</td><td>$(Format-CriteriaRuleText $fw.Value)</td></tr>" } $cards.Add("<div class=""detail-card""><div class=""detail-card-title"">Criteria</div><table class=""cmp""><tbody>$criteriaRows</tbody></table></div>") } } if ($OnFailure) { $of = [System.Net.WebUtility]::HtmlEncode($OnFailure) $cards.Add("<div class=""detail-card""><div class=""detail-card-title"">Settings</div><table class=""cmp""><tbody><tr><td class=""dk"">On Failure</td><td>$of</td></tr></tbody></table></div>") } if ($cards.Count -eq 0) { return '' } "<div class=""detail-cols"">$($cards -join '')</div>" } $summary = [PSCustomObject]@{ Total = @($Results).Count Pass = @($Results | Where-Object Status -eq 'Pass').Count Fail = @($Results | Where-Object Status -eq 'Fail').Count Warning = @($Results | Where-Object Status -eq 'Warning').Count Skipped = @($Results | Where-Object Status -eq 'Skipped').Count NotApplicable = @($Results | Where-Object Status -eq 'NotApplicable').Count BlockingFailures = @($Results | Where-Object { $_.Status -eq 'Fail' -and $_.OnFailure -eq 'Fail' }).Count } # Verdict banner if ($summary.Total -eq 0) { $verdictClass = 'verdict-none' $verdictText = 'NO CHECKS' $verdictSub = 'The baseline produced no results.' } elseif ($summary.BlockingFailures -gt 0) { $verdictClass = 'verdict-fail' $verdictText = 'FAILED' $verdictSub = '{0} blocking failure{1} out of {2} checks.' -f $summary.BlockingFailures, $(if ($summary.BlockingFailures -eq 1) { '' } else { 's' }), $summary.Total } else { $verdictClass = 'verdict-pass' $verdictText = 'PASSED' $verdictSub = '{0} of {1} checks passed.' -f $summary.Pass, $summary.Total if ($summary.Warning -gt 0) { $verdictSub += ' {0} warning{1}.' -f $summary.Warning, $(if ($summary.Warning -eq 1) { '' } else { 's' }) } } # System context for the report header (best effort, never fatal) $context = try { Get-WorkspaceTestSystemContext } catch { $null } $moduleVersion = if ($MyInvocation.MyCommand.Module) { [string]$MyInvocation.MyCommand.Module.Version } else { '' } $baselineNames = @( $Results | ForEach-Object { $_.Baseline } | Where-Object { $_ -and $_.Name } | ForEach-Object { if ($_.Version) { '{0} {1}' -f $_.Name, $_.Version } else { [string]$_.Name } } | Select-Object -Unique ) $sources = @($Results | ForEach-Object { $_.Source } | Where-Object { $_ } | Select-Object -Unique) $metaItems = [System.Collections.Generic.List[string]]::new() function Add-MetaItem { param([string]$Label, [string]$Value) if ([string]::IsNullOrWhiteSpace($Value)) { return } $l = [System.Net.WebUtility]::HtmlEncode($Label) $v = [System.Net.WebUtility]::HtmlEncode($Value) $metaItems.Add("<div class=""meta-item""><span class=""meta-label"">$l</span><span class=""meta-value"">$v</span></div>") } Add-MetaItem 'Machine' $env:COMPUTERNAME if ($context) { Add-MetaItem 'Operating system' ('{0} (build {1})' -f $context.Caption, $context.BuildNumber) Add-MetaItem 'Platform' $context.Platform Add-MetaItem 'Hardware' (('{0} {1}' -f $context.Manufacturer, $context.Model).Trim()) } Add-MetaItem 'User' ('{0}\{1}' -f $env:USERDOMAIN, $env:USERNAME) Add-MetaItem 'Module version' $moduleVersion Add-MetaItem 'Baseline' ($baselineNames -join ', ') Add-MetaItem 'Source' ($sources -join ', ') Add-MetaItem 'Generated' (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') $groups = $Results | Group-Object -Property Group | Sort-Object -Property Name $sections = foreach ($group in $groups) { $displayName = if ([string]::IsNullOrWhiteSpace($group.Name)) { 'General' } else { $group.Name } $groupName = [System.Net.WebUtility]::HtmlEncode($displayName) $groupFail = @($group.Group | Where-Object Status -eq 'Fail').Count $groupWarn = @($group.Group | Where-Object Status -eq 'Warning').Count $groupOpen = if ($groupFail -gt 0 -or $groupWarn -gt 0) { ' open' } else { '' } $chips = "<span class=""chip"">$(@($group.Group).Count) checks</span>" if ($groupFail -gt 0) { $chips += "<span class=""chip chip-fail"">$groupFail failed</span>" } if ($groupWarn -gt 0) { $chips += "<span class=""chip chip-warn"">$groupWarn warning$(if ($groupWarn -eq 1) { '' } else { 's' })</span>" } $tableRows = foreach ($result in ($group.Group | Sort-Object Type, Name)) { $detail = ConvertTo-HtmlDetailTable -Expected $result.Expected -Actual $result.Actual -Status $result.Status -OnFailure $result.OnFailure $message = [System.Net.WebUtility]::HtmlEncode($result.Message) $name = [System.Net.WebUtility]::HtmlEncode($result.Name) $type = [System.Net.WebUtility]::HtmlEncode($result.Type) $status = [System.Net.WebUtility]::HtmlEncode($result.Status) $blocking = if ($result.Status -eq 'Fail' -and $result.OnFailure -eq 'Fail') { '1' } else { '0' } $tagChips = (@($result.Tags) | Where-Object { $_ } | ForEach-Object { "<span class=""tag"">$([System.Net.WebUtility]::HtmlEncode([string]$_))</span>" }) -join '' $sourceHtml = if ($result.Source) { "<span class=""detail-src"">Source: $([System.Net.WebUtility]::HtmlEncode([string]$result.Source))</span>" } else { '' } $detailMeta = if ($sourceHtml -or $tagChips) { "<div class=""detail-meta"">$sourceHtml$tagChips</div>" } else { '' } $hasDetail = [bool]($detail -or $detailMeta) $expandClass = if ($hasDetail) { ' expandable' } else { '' } $detailRow = if ($hasDetail) { "<tr class=""detail-row"" hidden><td colspan=""4""><div class=""detail-panel"">$detailMeta$detail</div></td></tr>" } else { '' } "<tr class=""status-$($result.Status)$expandClass"" data-status=""$($result.Status)"" data-blocking=""$blocking""><td><span class=""badge badge-$($result.Status)"">$status</span></td><td>$type</td><td>$name</td><td>$message</td></tr>$detailRow" } @" <details class="group-section"$groupOpen> <summary class="group-heading"><span class="group-name">$groupName</span>$chips</summary> <div class="table-wrap"> <table> <thead> <tr><th>Status</th><th>Type</th><th>Name</th><th>Message</th></tr> </thead> <tbody> $($tableRows -join [Environment]::NewLine) </tbody> </table> </div> </details> "@ } $html = @" <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Workspace Testing Framework Report</title> <style> :root { --bg: #f0f2f5; --surface: #ffffff; --surface-alt: #f6f8fa; --surface-inset: #eaeef2; --text: #1f2328; --text-strong: #0d1117; --muted: #57606a; --faint: #adb5bd; --border: #d0d7de; --border-soft: #eaeef2; --shadow: 0 1px 3px rgba(0,0,0,.08); --pass: #1a7f37; --pass-bg: #dafbe1; --pass-fg: #116329; --fail: #cf222e; --fail-bg: #ffebe9; --fail-fg: #a40e26; --warn: #bf8700; --warn-bg: #fff8c5; --warn-fg: #7d4e00; --skip: #57606a; --skip-bg: #eaeef2; --skip-fg: #424a53; --na: #8250df; --na-bg: #fbefff; --na-fg: #5a32a3; --diff-exp: #fff8c5; --diff-act: #ffebe9; } @media (prefers-color-scheme: dark) { :root { --bg: #0d1117; --surface: #161b22; --surface-alt: #1c2129; --surface-inset: #21262d; --text: #e6edf3; --text-strong: #f0f6fc; --muted: #9ba6b2; --faint: #6e7681; --border: #30363d; --border-soft: #21262d; --shadow: 0 1px 3px rgba(0,0,0,.4); --pass: #3fb950; --pass-bg: #12261e; --pass-fg: #56d364; --fail: #f85149; --fail-bg: #2d1417; --fail-fg: #ff7b72; --warn: #d29922; --warn-bg: #2a2111; --warn-fg: #e3b341; --skip: #8b949e; --skip-bg: #21262d; --skip-fg: #b1bac4; --na: #a371f7; --na-bg: #221a32; --na-fg: #bc8cff; --diff-exp: #2a2111; --diff-act: #2d1417; } } *, *::before, *::after { box-sizing: border-box; } body { font-family: 'Segoe UI', system-ui, Arial, sans-serif; margin: 0; padding: 32px 40px 60px; background: var(--bg); color: var(--text); } header { margin-bottom: 20px; } header h1 { margin: 0 0 4px; font-size: 22px; font-weight: 700; color: var(--text-strong); } /* -- Verdict banner -- */ .verdict { display: flex; align-items: baseline; gap: 14px; border-radius: 10px; padding: 14px 20px; margin-bottom: 20px; box-shadow: var(--shadow); } .verdict strong { font-size: 24px; font-weight: 800; letter-spacing: .03em; } .verdict span { font-size: 13px; } .verdict-pass { background: var(--pass-bg); color: var(--pass-fg); border-left: 6px solid var(--pass); } .verdict-fail { background: var(--fail-bg); color: var(--fail-fg); border-left: 6px solid var(--fail); } .verdict-none { background: var(--skip-bg); color: var(--skip-fg); border-left: 6px solid var(--skip); } /* -- Context meta -- */ .meta-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 8px 20px; background: var(--surface); border-radius: 10px; padding: 14px 20px; margin-bottom: 20px; box-shadow: var(--shadow); } .meta-item { display: flex; flex-direction: column; min-width: 0; } .meta-label { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); } .meta-value { font-size: 13px; color: var(--text); overflow-wrap: anywhere; } /* -- Summary cards (clickable filters) -- */ .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); gap: 12px; margin-bottom: 14px; } .metric { background: var(--surface); border-radius: 10px; padding: 14px 14px 10px; box-shadow: var(--shadow); border: 1px solid transparent; border-top: 4px solid var(--border); cursor: pointer; user-select: none; transition: transform .06s ease; } .metric:hover { transform: translateY(-1px); } .metric.active { border-color: var(--muted); border-top-width: 4px; outline: 2px solid var(--muted); outline-offset: -1px; } .metric strong { display: block; font-size: 26px; font-weight: 700; line-height: 1; margin-bottom: 4px; color: var(--text-strong); } .metric span { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; } .metric.m-pass { border-top-color: var(--pass); } .metric.m-pass strong { color: var(--pass); } .metric.m-fail { border-top-color: var(--fail); } .metric.m-fail strong { color: var(--fail); } .metric.m-warn { border-top-color: var(--warn); } .metric.m-warn strong { color: var(--warn); } .metric.m-skip { border-top-color: var(--skip); } .metric.m-skip strong { color: var(--skip); } .metric.m-na { border-top-color: var(--na); } .metric.m-na strong { color: var(--na); } .metric.m-block { border-top-color: var(--fail); } .metric.m-block strong { color: var(--fail); } /* -- Toolbar -- */ .toolbar { display: flex; gap: 10px; align-items: center; margin-bottom: 20px; flex-wrap: wrap; } .toolbar input[type="search"] { flex: 1 1 260px; max-width: 420px; padding: 8px 12px; font-size: 13px; font-family: inherit; color: var(--text); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; } .toolbar input[type="search"]:focus { outline: 2px solid var(--muted); outline-offset: -1px; } .toolbar button { padding: 8px 14px; font-size: 12px; font-family: inherit; font-weight: 600; color: var(--muted); background: var(--surface); border: 1px solid var(--border); border-radius: 8px; cursor: pointer; } .toolbar button:hover { background: var(--surface-alt); } .filter-hint { font-size: 12px; color: var(--muted); } /* -- Group sections -- */ details.group-section { margin-bottom: 16px; } summary.group-heading { display: flex; align-items: center; gap: 10px; font-size: 14px; font-weight: 700; color: var(--text-strong); padding: 10px 14px; cursor: pointer; user-select: none; background: var(--surface); border-radius: 10px; box-shadow: var(--shadow); position: sticky; top: 0; z-index: 2; list-style: none; } summary.group-heading::-webkit-details-marker { display: none; } summary.group-heading::before { content: '\25B8'; font-size: 11px; color: var(--muted); } details[open] > summary.group-heading::before { content: '\25BE'; } details[open] > summary.group-heading { border-bottom-left-radius: 0; border-bottom-right-radius: 0; } .group-name { flex: 0 1 auto; } .chip { font-size: 11px; font-weight: 600; color: var(--muted); background: var(--surface-inset); border-radius: 12px; padding: 2px 9px; } .chip-fail { background: var(--fail-bg); color: var(--fail-fg); } .chip-warn { background: var(--warn-bg); color: var(--warn-fg); } /* -- Table wrapper -- */ .table-wrap { background: var(--surface); border-radius: 0 0 10px 10px; box-shadow: var(--shadow); overflow-x: auto; } table { border-collapse: collapse; width: 100%; font-size: 13px; } thead tr { background: var(--surface-alt); } th { padding: 10px 12px; text-align: left; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); border-bottom: 2px solid var(--border); white-space: nowrap; } td { padding: 9px 12px; border-bottom: 1px solid var(--border-soft); vertical-align: top; } tbody tr:last-child td { border-bottom: none; } tbody tr:hover:not(.detail-row) { background: var(--surface-alt); } /* -- Row left-border accent -- */ .status-Pass td:first-child { border-left: 3px solid var(--pass); } .status-Fail td:first-child { border-left: 3px solid var(--fail); } .status-Warning td:first-child { border-left: 3px solid var(--warn); } .status-Skipped td:first-child { border-left: 3px solid var(--skip); } .status-NotApplicable td:first-child { border-left: 3px solid var(--na); } /* -- Expandable rows -- */ tr.expandable { cursor: pointer; } tr.expandable td:last-child::after { content: ' \25B8'; font-size: 10px; color: var(--muted); float: right; margin-top: 2px; } tr.expandable.open td:last-child::after { content: ' \25BE'; } tr.detail-row td { padding: 0; border-bottom: 1px solid var(--border-soft); background: var(--surface-alt); } .detail-panel { padding: 12px 16px 14px; } .detail-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-bottom: 8px; } .detail-src { font-size: 11px; color: var(--muted); margin-right: 8px; overflow-wrap: anywhere; } .tag { font-size: 10px; font-weight: 600; color: var(--muted); background: var(--surface-inset); border: 1px solid var(--border); border-radius: 10px; padding: 1px 8px; } /* -- Comparison table -- */ table.cmp { border-collapse: collapse; font-size: 12px; width: auto; } table.cmp thead tr { background: var(--surface-inset); } table.cmp th { padding: 4px 8px; font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); border-bottom: 1px solid var(--border); text-align: left; white-space: nowrap; } table.cmp td { padding: 3px 8px; border-bottom: 1px solid var(--border-soft); vertical-align: top; white-space: nowrap; } table.cmp tr:last-child td { border-bottom: none; } td.dk { font-weight: 600; color: var(--muted); white-space: nowrap; padding-right: 10px; } tr.row-diff td:nth-child(2) { background: var(--diff-exp); } tr.row-diff td:nth-child(3) { background: var(--diff-act); } .detail-cols { display: flex; flex-wrap: wrap; gap: 12px 36px; align-items: flex-start; } .detail-card { min-width: 0; } .detail-card-title { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 4px; } .criteria-rule { font-size: 12px; color: var(--text); padding: 1px 0; } em.na { color: var(--faint); font-style: normal; } /* -- Status badge -- */ .badge { display: inline-block; padding: 2px 9px; border-radius: 12px; font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; } .badge-Pass { background: var(--pass-bg); color: var(--pass-fg); } .badge-Fail { background: var(--fail-bg); color: var(--fail-fg); } .badge-Warning { background: var(--warn-bg); color: var(--warn-fg); } .badge-Skipped { background: var(--skip-bg); color: var(--skip-fg); } .badge-NotApplicable { background: var(--na-bg); color: var(--na-fg); } /* -- Print -- */ @media print { body { padding: 0; background: #fff; color: #000; } .toolbar { display: none; } .metric, .verdict, .meta-grid, .table-wrap, summary.group-heading { box-shadow: none; border: 1px solid #ccc; } .metric { cursor: default; } summary.group-heading { position: static; } details.group-section:not([open]) > .table-wrap { display: block; } tr.detail-row[hidden] { display: table-row; } tr[data-status][hidden] { display: table-row; } tr.expandable td:last-child::after { content: ''; } } </style> </head> <body> <header> <h1>Workspace Testing Framework Report</h1> </header> <div class="verdict $verdictClass"><strong>$verdictText</strong><span>$verdictSub</span></div> <section class="meta-grid"> $($metaItems -join [Environment]::NewLine) </section> <section class="summary"> <div class="metric" data-filter=""> <strong>$($summary.Total)</strong><span>Total</span></div> <div class="metric m-pass" data-filter="Pass"> <strong>$($summary.Pass)</strong><span>Pass</span></div> <div class="metric m-fail" data-filter="Fail"> <strong>$($summary.Fail)</strong><span>Fail</span></div> <div class="metric m-warn" data-filter="Warning"> <strong>$($summary.Warning)</strong><span>Warning</span></div> <div class="metric m-skip" data-filter="Skipped"> <strong>$($summary.Skipped)</strong><span>Skipped</span></div> <div class="metric m-na" data-filter="NotApplicable"> <strong>$($summary.NotApplicable)</strong><span>Not applicable</span></div> <div class="metric m-block" data-filter="Blocking"> <strong>$($summary.BlockingFailures)</strong><span>Blocking</span></div> </section> <div class="toolbar"> <input type="search" id="search" placeholder="Search name or message..." aria-label="Search checks"> <button type="button" id="expand-all">Expand all</button> <button type="button" id="collapse-all">Collapse all</button> <span class="filter-hint" id="filter-hint"></span> </div> $($sections -join [Environment]::NewLine) <script> (function() { var activeFilter = ''; var searchBox = document.getElementById('search'); var hint = document.getElementById('filter-hint'); function setDetailOpen(row, open) { var detail = row.nextElementSibling; if (!detail || !detail.classList.contains('detail-row')) { return; } if (open) { detail.removeAttribute('hidden'); row.classList.add('open'); } else { detail.setAttribute('hidden', ''); row.classList.remove('open'); } } function applyFilters() { var q = searchBox.value.toLowerCase(); var visibleCount = 0; document.querySelectorAll('tr[data-status]').forEach(function(row) { var okStatus = !activeFilter || (activeFilter === 'Blocking' ? row.dataset.blocking === '1' : row.dataset.status === activeFilter); var okText = !q || row.textContent.toLowerCase().indexOf(q) !== -1; var visible = okStatus && okText; if (visible) { visibleCount++; row.removeAttribute('hidden'); } else { row.setAttribute('hidden', ''); setDetailOpen(row, false); } }); document.querySelectorAll('details.group-section').forEach(function(g) { var any = g.querySelector('tbody tr[data-status]:not([hidden])'); g.style.display = any ? '' : 'none'; if ((activeFilter || q) && any) { g.open = true; } }); hint.textContent = (activeFilter || q) ? 'Showing ' + visibleCount + ' checks' : ''; } document.querySelectorAll('.metric[data-filter]').forEach(function(card) { card.addEventListener('click', function() { var filter = card.dataset.filter; activeFilter = (activeFilter === filter) ? '' : filter; document.querySelectorAll('.metric[data-filter]').forEach(function(c) { c.classList.toggle('active', c.dataset.filter === activeFilter && activeFilter !== ''); }); applyFilters(); }); }); searchBox.addEventListener('input', applyFilters); document.getElementById('expand-all').addEventListener('click', function() { document.querySelectorAll('details.group-section').forEach(function(g) { g.open = true; }); document.querySelectorAll('tr.expandable:not([hidden])').forEach(function(row) { setDetailOpen(row, true); }); }); document.getElementById('collapse-all').addEventListener('click', function() { document.querySelectorAll('details.group-section').forEach(function(g) { g.open = false; }); document.querySelectorAll('tr.expandable').forEach(function(row) { setDetailOpen(row, false); }); }); document.querySelectorAll('tr.expandable').forEach(function(row) { row.addEventListener('click', function() { var detail = row.nextElementSibling; if (detail && detail.classList.contains('detail-row')) { setDetailOpen(row, detail.hasAttribute('hidden')); } }); }); // Failures are why the report gets opened: expand them on load document.querySelectorAll('tr.status-Fail.expandable').forEach(function(row) { setDetailOpen(row, true); }); // Open every group while printing, restore afterwards var printState = []; window.addEventListener('beforeprint', function() { printState = []; document.querySelectorAll('details.group-section').forEach(function(g) { printState.push(g.open); g.open = true; }); }); window.addEventListener('afterprint', function() { document.querySelectorAll('details.group-section').forEach(function(g, i) { if (i < printState.length) { g.open = printState[i]; } }); }); })(); </script> </body> </html> "@ Set-Content -LiteralPath $Path -Value $html -Encoding UTF8 } # SIG # Begin signature block # MIInigYJKoZIhvcNAQcCoIInezCCJ3cCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCJcw16F/gwC5E1 # FC4OOFP+WeUs6p3bNaMSxTWva3O09aCCIR0wggZFMIIELaADAgECAhAIMk+dt9qR # b2Pk8qM8Xl1RMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQK # ExhBc3NlY28gRGF0YSBTeXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2Rl # IFNpZ25pbmcgMjAyMSBDQTAeFw0yNDA0MDQxNDA0MjRaFw0yNzA0MDQxNDA0MjNa # MGsxCzAJBgNVBAYTAk5MMRIwEAYDVQQHDAlTY2hpam5kZWwxIzAhBgNVBAoMGkpv # aG4gQmlsbGVrZW5zIENvbnN1bHRhbmN5MSMwIQYDVQQDDBpKb2huIEJpbGxla2Vu # cyBDb25zdWx0YW5jeTCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBAMsl # ntDbSQwHZXwFhmibivbnd0Qfn6sqe/6fos3pKzKxEsR907RkDMet2x6RRg3eJkiI # r3TFPwqBooyXXgK3zxxpyhGOcuIqyM9J28DVf4kUyZHsjGO/8HFjrr3K1hABNUsz # P0o7H3o6J31eqV1UmCXYhQlNoW9FOmRC1amlquBmh7w4EKYEytqdmdOBavAD5Xq4 # vLPxNP6kyA+B2YTtk/xM27TghtbwFGKnu9Vwnm7dFcpLxans4ONt2OxDQOMA5Nwg # cUv/YTpjhq9qoz6ivG55NRJGNvUXsM3w2o7dR6Xh4MuEGrTSrOWGg2A5EcLH1XqQ # tkF5cZnAPM8W/9HUp8ggornWnFVQ9/6Mga+ermy5wy5XrmQpN+x3u6tit7xlHk1H # c+4XY4a4ie3BPXG2PhJhmZAn4ebNSBwNHh8z7WTT9X9OFERepGSytZVeEP7hgypt # SLcuhpwWeR4QdBb7dV++4p3PsAUQVHFpwkSbrRTv4EiJ0Lcz9P1HPGFoHiFAQQID # AQABo4IBeDCCAXQwDAYDVR0TAQH/BAIwADA9BgNVHR8ENjA0MDKgMKAuhixodHRw # Oi8vY2NzY2EyMDIxLmNybC5jZXJ0dW0ucGwvY2NzY2EyMDIxLmNybDBzBggrBgEF # BQcBAQRnMGUwLAYIKwYBBQUHMAGGIGh0dHA6Ly9jY3NjYTIwMjEub2NzcC1jZXJ0 # dW0uY29tMDUGCCsGAQUFBzAChilodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwv # Y2NzY2EyMDIxLmNlcjAfBgNVHSMEGDAWgBTddF1MANt7n6B0yrFu9zzAMsBwzTAd # BgNVHQ4EFgQUO6KtBpOBgmrlANVAnyiQC6W6lJwwSwYDVR0gBEQwQjAIBgZngQwB # BAEwNgYLKoRoAYb2dwIFAQQwJzAlBggrBgEFBQcCARYZaHR0cHM6Ly93d3cuY2Vy # dHVtLnBsL0NQUzATBgNVHSUEDDAKBggrBgEFBQcDAzAOBgNVHQ8BAf8EBAMCB4Aw # DQYJKoZIhvcNAQELBQADggIBAEQsN8wgPMdWVkwHPPTN+jKpdns5AKVFjcn00psf # 2NGVVgWWNQBIQc9lEuTBWb54IK6Ga3hxQRZfnPNo5HGl73YLmFgdFQrFzZ1lnaMd # Icyh8LTWv6+XNWfoyCM9wCp4zMIDPOs8LKSMQqA/wRgqiACWnOS4a6fyd5GUIAm4 # CuaptpFYr90l4Dn/wAdXOdY32UhgzmSuxpUbhD8gVJUaBNVmQaRqeU8y49MxiVrU # KJXde1BCrtR9awXbqembc7Nqvmi60tYKlD27hlpKtj6eGPjkht0hHEsgzU0Fxw7Z # JghYG2wXfpF2ziN893ak9Mi/1dmCNmorGOnybKYfT6ff6YTCDDNkod4egcMZdOSv # +/Qv+HAeIgEvrxE9QsGlzTwbRtbm6gwYYcVBs/SsVUdBn/TSB35MMxRhHE5iC3aU # TkDbceo/XP3uFhVL4g2JZHpFfCSu2TQrrzRn2sn07jfMvzeHArCOJgBW1gPqR3Wr # J4hUxL06Rbg1gs9tU5HGGz9KNQMfQFQ70Wz7UIhezGcFcRfkIfSkMmQYYpsc7rfz # j+z0ThfDVzzJr2dMOFsMlfj1T6l22GBq9XQx0A4lcc5Fl9pRxbOuHHWFqIBD/BCE # hwniOCySzqENd2N+oz8znKooSISStnkNaYXt6xblJF2dx9Dn89FK7d1IquNxOwt0 # tI5dMIIGgjCCBGqgAwIBAgIQNsKwvXwbOuejs902y8l1aDANBgkqhkiG9w0BAQwF # ADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcT # C0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAs # BgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcN # MjEwMzIyMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjBXMQswCQYDVQQGEwJHQjEYMBYG # A1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0aWdvIFB1YmxpYyBU # aW1lIFN0YW1waW5nIFJvb3QgUjQ2MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAiJ3YuUVnnR3d6LkmgZpUVMB8SQWbzFoVD9mUEES0QUCBdxSZqdTkdizI # CFNeINCSJS+lV1ipnW5ihkQyC0cRLWXUJzodqpnMRs46npiJPHrfLBOifjfhpdXJ # 2aHHsPHggGsCi7uE0awqKggE/LkYw3sqaBia67h/3awoqNvGqiFRJ+OTWYmUCO2G # AXsePHi+/JUNAax3kpqstbl3vcTdOGhtKShvZIvjwulRH87rbukNyHGWX5tNK/WA # BKf+Gnoi4cmisS7oSimgHUI0Wn/4elNd40BFdSZ1EwpuddZ+Wr7+Dfo0lcHflm/F # DDrOJ3rWqauUP8hsokDoI7D/yUVI9DAE/WK3Jl3C4LKwIpn1mNzMyptRwsXKrop0 # 6m7NUNHdlTDEMovXAIDGAvYynPt5lutv8lZeI5w3MOlCybAZDpK3Dy1MKo+6aEtE # 9vtiTMzz/o2dYfdP0KWZwZIXbYsTIlg1YIetCpi5s14qiXOpRsKqFKqav9R1R5vj # 3NgevsAsvxsAnI8Oa5s2oy25qhsoBIGo/zi6GpxFj+mOdh35Xn91y72J4RGOJEoq # zEIbW3q0b2iPuWLA911cRxgY5SJYubvjay3nSMbBPPFsyl6mY4/WYucmyS9lo3l7 # jk27MAe145GWxK4O3m3gEFEIkv7kRmefDR7Oe2T1HxAnICQvr9sCAwEAAaOCARYw # ggESMB8GA1UdIwQYMBaAFFN5v1qqK0rPVIDh2JvAnfKyA2bLMB0GA1UdDgQWBBT2 # d2rdP/0BE/8WoWyCAi/QCj0UJTAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUw # AwEB/zATBgNVHSUEDDAKBggrBgEFBQcDCDARBgNVHSAECjAIMAYGBFUdIAAwUAYD # VR0fBEkwRzBFoEOgQYY/aHR0cDovL2NybC51c2VydHJ1c3QuY29tL1VTRVJUcnVz # dFJTQUNlcnRpZmljYXRpb25BdXRob3JpdHkuY3JsMDUGCCsGAQUFBwEBBCkwJzAl # BggrBgEFBQcwAYYZaHR0cDovL29jc3AudXNlcnRydXN0LmNvbTANBgkqhkiG9w0B # AQwFAAOCAgEADr5lQe1oRLjlocXUEYfktzsljOt+2sgXke3Y8UPEooU5y39rAARa # AdAxUeiX1ktLJ3+lgxtoLQhn5cFb3GF2SSZRX8ptQ6IvuD3wz/LNHKpQ5nX8hjsD # LRhsyeIiJsms9yAWnvdYOdEMq1W61KE9JlBkB20XBee6JaXx4UBErc+YuoSb1SxV # f7nkNtUjPfcxuFtrQdRMRi/fInV/AobE8Gw/8yBMQKKaHt5eia8ybT8Y/Ffa6HAJ # yz9gvEOcF1VWXG8OMeM7Vy7Bs6mSIkYeYtddU1ux1dQLbEGur18ut97wgGwDiGin # CwKPyFO7ApcmVJOtlw9FVJxw/mL1TbyBns4zOgkaXFnnfzg4qbSvnrwyj1NiurMp # 4pmAWjR+Pb/SIduPnmFzbSN/G8reZCL4fvGlvPFk4Uab/JVCSmj59+/mB2Gn6G/U # YOy8k60mKcmaAZsEVkhOFuoj4we8CYyaR9vd9PGZKSinaZIkvVjbH/3nlLb0a7SB # IkiRzfPfS9T+JesylbHa1LtRV9U/7m0q7Ma2CQ/t392ioOssXW7oKLdOmMBl14su # VFBmbzrt5V5cQPnwtd3UOTpS9oCG+ZZheiIvPgkDmA8FzPsnfXW5qHELB43ET7HH # FHeRPRYrMBKjkb8/IN7Po0d0hQoF4TeMM+zYAJzoKQnVKOLg8pZVPT8wgganMIIE # j6ADAgECAhEAkKwIciD9xafEa1zHDfc9BjANBgkqhkiG9w0BAQwFADBXMQswCQYD # VQQGEwJHQjEYMBYGA1UEChMPU2VjdGlnbyBMaW1pdGVkMS4wLAYDVQQDEyVTZWN0 # aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5nIFJvb3QgUjQ2MB4XDTI2MDMyNTAwMDAw # MFoXDTQxMDMyNDIzNTk1OVowVTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1NlY3Rp # Z28gTGltaXRlZDEsMCoGA1UEAxMjU2VjdGlnbyBQdWJsaWMgVGltZSBTdGFtcGlu # ZyBDQSBSNDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCu5EqiAa2C # HGL5Zi1bmgPM8NUXwYZJ+BtQqHps43GLTC+sjVLypsBh+8uv+TLkgtVGD//vSmA0 # qrzELf9YRCh2MTAA/aGaQZKGg0BRCmziR3pbCnvgWjtGXBDUyn3j3K2lZAO8KxgF # tlxwOYEAkL+CCqK4v9zzTl8ZwzDpPMiDIFa5THk8an1ieF5I09cXNrPQw+1ER1li # ThaG0z6FrOpqwxZWmPRZQBw2E32878UB1bL0Zp91vuWZgsMpNNiPCoBj0/1F+LE8 # +NRokfqacFI0F2tftrRB2W7HQClLR9zjxFbWb5be2rceIfNyHUUfKGIvMI2NzoxS # lxXnFqUG887D8W1Cj8DFok688JKxWvHR/9aQykSbd+9Vutj36ij2sgq/125wTpUZ # /AgC0ph50bRs7gFrUyaXE9wSsOqMvCCC+sEm7vd/BemSG0TSHNXSmyCba+FCzeke # WX03TRIcF3Laqd0Rw24OH7jpei4zaGhcI7nfdhBA4c8RScxNY6jeHLHHmSMMTk9W # qn7H4dLhUBP5YEwbgbN4uv1i9ltTnHli8t1xHV0StX9BFgrnmunTX19kUXY1H5OR # JbRZyZDdvm1oZyteDj0SnMozr+YSmdIleDUTXdfoY7b2taz8s2+QbOxLxcahEIYG # Wzqu6h955tKwcANHcZ4gTmAhT3btuOiQsQIDAQABo4IBbjCCAWowHwYDVR0jBBgw # FoAU9ndq3T/9ARP/FqFsggIv0Ao9FCUwHQYDVR0OBBYEFDp0pQxnxkJQwv21/Me7 # KTSC9Hq5MA4GA1UdDwEB/wQEAwIBhjASBgNVHRMBAf8ECDAGAQH/AgEAMBMGA1Ud # JQQMMAoGCCsGAQUFBwMIMCMGA1UdIAQcMBowCAYGZ4EMAQQCMA4GDCsGAQQBsjEB # AgEDCDBMBgNVHR8ERTBDMEGgP6A9hjtodHRwOi8vY3JsLnNlY3RpZ28uY29tL1Nl # Y3RpZ29QdWJsaWNUaW1lU3RhbXBpbmdSb290UjQ2LmNybDB8BggrBgEFBQcBAQRw # MG4wRwYIKwYBBQUHMAKGO2h0dHA6Ly9jcnQuc2VjdGlnby5jb20vU2VjdGlnb1B1 # YmxpY1RpbWVTdGFtcGluZ1Jvb3RSNDYucDdjMCMGCCsGAQUFBzABhhdodHRwOi8v # b2NzcC5zZWN0aWdvLmNvbTANBgkqhkiG9w0BAQwFAAOCAgEAMt5SR2bxngNm+N8o # c6Gq76Gx1c235fkX7jw8Ho9MAkJGADerHE7dhsBXttqmzgr/7ZZahZSykGRPhPY1 # crj028kB8KzO0dKC2qQBAwtfgqMLKkkX/6bYq2uT33eD6ByAp2/XKD0LcmZh0kKe # cvSBr6ln9ajX6u1dnx2fA7xEKy1M3qBhfQSUWLtjs2nFt0ELVLptzTlX9ID0cL+i # OPfdboZ3CelT+JXKVKR2Sge0d4YiFAtPZkfSo8z1Z1x7y/Z9mwMIlBAnyuWXs4Ys # NuxdrYIt/QxE31PDOJ9DesS4Bc7H9OTORlEV/AvfiF/VepKZpira1MzLYuCw+uoL # Zn/pkpvd+CvNTS+mEHjBJNa6WK1j8qXFu+jIq+sG9QILHiyB6p/xpHrkJu8zkw39 # 3+VqF9eKlTY2VjRxdycZLrVemZ4Yp3wi33b+W58CllH3HqjmowlZ7SOrgmx8YwYO # kgrHsXOQHyBp6O4FRb8In0+FzjT7ElGie9V7CfhL3IlVFZ4zjuKsZtH1iU3fGu4z # /JnOGT6sCb0BbTqe/uhvpFCQBdH5xPGIA/LrbQUXjU2tWJgHhTIqnN/HvHyOHi5t # M4zP3nhgh2rJ6Kqq2xsHBeNYs/R18xQ8DeIg+c90Eoaeh0YlN1KU8AyYol3K9M+q # Y5ez8syd/7ZlrRnoVewgH3P1pcswgga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6E # prPqOTANBgkqhkiG9w0BAQwFADCBgDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVu # aXpldG8gVGVjaG5vbG9naWVzIFMuQS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZp # Y2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29y # ayBDQSAyMB4XDTIxMDUxOTA1MzIxOFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UE # BhMCUEwxITAfBgNVBAoTGEFzc2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UE # AxMbQ2VydHVtIENvZGUgU2lnbmluZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAnSPPBDAjO8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4 # mSAfgLKTvggv24/rWiVGzGxT9YEASVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck # 6+hI1shsylP4JyLvmxwLHtSworV9wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xq # bNTIPsnWtw3E7DmDoUmDQiYi/ucJ42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KN # qfcYf7Z4/iZgkBJ+UFNDcc6zokZ2uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSl # bfsq7BiqljjaCun+RJgTgFRCtsuAEw0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYq # VWQ4x6cM7/G0yswg1ElLlJj6NYKLw9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAE # xzd4prHwYjUXTeZIlVXqj+eaYqoMTpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962 # WZayx7ACFf5ydJpoEowSP07YaBiQ8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiY # PIAHlezf4muJqxqIns/kqld6JVX8cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36 # E/ENVv8urPS0yOnpG4tIoBGxVCARPCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPEC # AwEAAaOCAVUwggFRMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23uf # oHTKsW73PMAywHDNMB8GA1UdIwQYMBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4G # A1UdDwEB/wQEAwIBBjATBgNVHSUEDDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWg # I6Ahhh9odHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEB # BGAwXjAoBggrBgEFBQcwAYYcaHR0cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAy # BggrBgEFBQcwAoYmaHR0cDovL3JlcG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5j # ZXIwOQYDVR0gBDIwMDAuBgRVHSAAMCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cu # Y2VydHVtLnBsL0NQUzANBgkqhkiG9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP8 # 9DttyZqGYn2XRDhJkL6P+/T0IPZyxfxiXumYlARMgwRzLRUStJl490L94C9LGF3v # jzzH8Jq3iR74BRlkO18J3zIdmCKQa5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO # 0eqeLNhLVsgw6/zOfImNlARKn1FP7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2 # mbJCKKtl2s42g9KUJHEIiLni9ByoqIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA # 3ahe8WlxVWkxyrQLjH8ItI17RdySaYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhp # n8zJ+6MTyE6YoEBSRVrs0zFFIHUR08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSo # uCReZwSLo8WdrDlPXtR0gicDnytO7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na # 3PR7nxYVlPu1JPoRZCbH86gc96UTvuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1 # eEJfo2qyrBNPvF7wuAyQfiFXLwvWHamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+ # tKyEVAotnyU9vyEVOaIYMk3IeBrmFnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmv # O1nm+/80nLy5r0AZvCQxaQ4wggbiMIIEyqADAgECAhEA507yVbBQT/rbpt/3/Iuj # FTANBgkqhkiG9w0BAQwFADBVMQswCQYDVQQGEwJHQjEYMBYGA1UEChMPU2VjdGln # byBMaW1pdGVkMSwwKgYDVQQDEyNTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n # IENBIFI0MTAeFw0yNjAzMjUwMDAwMDBaFw0zNzA2MjQyMzU5NTlaMHIxCzAJBgNV # BAYTAkdCMRcwFQYDVQQIEw5HcmVhdGVyIExvbmRvbjEYMBYGA1UEChMPU2VjdGln # byBMaW1pdGVkMTAwLgYDVQQDEydTZWN0aWdvIFB1YmxpYyBUaW1lIFN0YW1waW5n # IFNpZ25lciBSMzcwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCy/8Nt # S9xQ2UUtBRF32bj7VK3n4m50Uqjk/zTciSziYV40H1LKah0/oEklYG42E4VCP3Dv # sBUB6DmpCkDZ0jCnZBPIEevaH15ZJOQwFWP2ZXr5YjlJpb68Nlbs+ElNvKx32/1Y # Hde3qqUSLybjulxPLz6T85+HOIqK7M1Bep8LspyhEP/q6nw5kGxTSrGvufmeH+JF # 8CnVBcVMFA40FlIYh0cDJVFhhfTfdWgLy/vWuLMQoKkf3s/FvByf16r0rtbyHm/i # emwxSioJL9zyZDDKUNAbHXl0dhXo2VxUV2NcPXWXuoKsjL+6cfk6Vm2DHnxAlFdF # saBDIF1JOkSnC6PeLlBznZn2buF3vIIYJcq6N/zeFRCk4/HXDz7zgRsRRMdUB+rh # yk5FoZaBjw0nLq3GZ3fClLUx5es5pUAxzNODMBn7JkFYip2BAGBPER5eV0ROhk6t # GTG+fUiMiV+vgjg1YnP5FvnYWyEtWeQD/B2hp3vz0RvtdkM0p3igyadzrfpOBq5p # pVk/YsuhTQkP99ivneHAGfi5e7lmxJ+meoBPrRLuzMmb81rzzbESjJHMsn5RVtc6 # Ucs7rcMqQC13PUIO7BbGBETV2ufCmV6lPTp3P7XJOvmnUCRTPbVvMTpxP/z+SOHg # 4/OCBhiqs4FA9+4oQvlkk9w32NGASli9GWrm5wIDAQABo4IBjjCCAYowHwYDVR0j # BBgwFoAUOnSlDGfGQlDC/bX8x7spNIL0erkwHQYDVR0OBBYEFGEQ6XoSr1HEhdTy # z6R0D1DNIK/4MA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMBYGA1UdJQEB # /wQMMAoGCCsGAQUFBwMIMEoGA1UdIARDMEEwCAYGZ4EMAQQCMDUGDCsGAQQBsjEB # AgEDCDAlMCMGCCsGAQUFBwIBFhdodHRwczovL3NlY3RpZ28uY29tL0NQUzBKBgNV # HR8EQzBBMD+gPaA7hjlodHRwOi8vY3JsLnNlY3RpZ28uY29tL1NlY3RpZ29QdWJs # aWNUaW1lU3RhbXBpbmdDQVI0MS5jcmwwegYIKwYBBQUHAQEEbjBsMEUGCCsGAQUF # BzAChjlodHRwOi8vY3J0LnNlY3RpZ28uY29tL1NlY3RpZ29QdWJsaWNUaW1lU3Rh # bXBpbmdDQVI0MS5jcnQwIwYIKwYBBQUHMAGGF2h0dHA6Ly9vY3NwLnNlY3RpZ28u # Y29tMA0GCSqGSIb3DQEBDAUAA4ICAQAD6j2N0azN+hl6k6bKB5/U6VuSOs93ZBb3 # Pczy9VtBIKu4947Z5GwL0aFngIxl+GSuLFrJgPruBCRvKJEJsm7kv+LQ1COVCEG9 # tZ+IRtr4ocUoa53lgdFaENlS0N4wgkZkbQEPv+x+1lSjYh+T4JeL9mUznT7Erc6S # p5dWLka5sMP/m3GZi6oJPdPcsCKWagH7m2H2xDGIyHJC5PdH9phvi/KmhkktiSVT # NNqVeV5bWdX2zhRE6UTfz0IcMoCL996lFIydXxOCE4MNDHDM0as4lnTiT/KHMccO # 6l8c9TnUVgmpci9ar1IABZ2U1XUkYjGGSn9MC3EHDP9V39VuBVvZ33/BEV/EWSRr # f07T7jFplKX+gQr/UOqPGMlE7ZJ72UaUkNJy7bVl3bcLKzdpjIHzLkf/4MVa1V7w # 8wqCv5W4gOnRGTlud5UMARbRM8BPxR/CXYXoMmIOD8pmTk2axgRL4LG8XtuchISd # CHRmtacAmLGq5XSYSVTHTXADlO48iDKh3HM2r98LSF6f0sG12d8V9Jn7C3wDUieO # xuKj4MdWrW+hiJU2kF87v6eH00HgCFFc2V0+CvfOCMn7juzS41jLaINcBlKWQ/fK # b/uDLfWOW73z1I2lFY7Xj8tQ1XYtK5eREjWItM8jpl1cbQOc88btR+0XS2TmboE/ # 141+va2PWzGCBcMwggW/AgEBMGowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFz # c2VjbyBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2ln # bmluZyAyMDIxIENBAhAIMk+dt9qRb2Pk8qM8Xl1RMA0GCWCGSAFlAwQCAQUAoIGE # MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQB # gjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkE # MSIEILw1znFX03T5k+VhhWH1AYsFLQiR+K7f4k5b8KbxFYm6MA0GCSqGSIb3DQEB # AQUABIIBgCkdLTiNEa9vh0ljLH5L+DkxvpXCplCj1jGgaLMuT7aOULTcxHlUrbdB # iZH2q12TKUHrS50OT97lcVlliiSRoK1nxH7Gtkp50dX/qZuHhLeX2ArjyX3/1gGl # tOEkNSsbSDNTedEAxZPXWOKg1FuS80+SvAQYQ6E4NcPRepzwCSP3pbdb5CCPfKJ4 # eji5SYQ0zPxuCUi1Qy+5bgAmLeOmrsPZzymn3TIUh5+xSlfO+gL1hPnea7m+DxP2 # SGts0vG1g2Ir19ypycZ7phd/G6Bqk7IxUjP1i3bm+DV91Xv2e1FgNTjnibG09dlG # imbe1X9TdfPI1Nob4V1BljAGlnfSAoVw3f9lZ4QWHzfWT0G0CCEXSlcWyzN9CJ2B # SOfYoCb2876W7G9V/4Qbzb41EoUKWTSSU5Ghp+6crTDmAZthAO2wO6AlL+A3xV9d # SHSftVNliGHlRZIgG1rh5EzJgash0baDR0I6nJyJM0O7yJytK2AUsjpXVymksBdH # UNysqS7vnaGCAyMwggMfBgkqhkiG9w0BCQYxggMQMIIDDAIBATBqMFUxCzAJBgNV # BAYTAkdCMRgwFgYDVQQKEw9TZWN0aWdvIExpbWl0ZWQxLDAqBgNVBAMTI1NlY3Rp # Z28gUHVibGljIFRpbWUgU3RhbXBpbmcgQ0EgUjQxAhEA507yVbBQT/rbpt/3/Iuj # FTANBglghkgBZQMEAgIFAKB5MBgGCSqGSIb3DQEJAzELBgkqhkiG9w0BBwEwHAYJ # KoZIhvcNAQkFMQ8XDTI2MDcwODE5MDY1OFowPwYJKoZIhvcNAQkEMTIEMEE1v0kF # BAyjZTfiasHWZxNpj+FpDY2nAM8kD1yibMa4VlSRigMxcZTGboBdXyBxkDANBgkq # hkiG9w0BAQEFAASCAgCVlt0LyeWXrFhVc8kXH0aIN2k2CmSpx+kkHr2LHAbZqyLK # 87IW234s7IHMHLpVM/2nMWFHlUMZ5TK4W344h8zMeaFNARMDKgeWkHmneCei8nnB # 6z66P4f3TkYHjiprOrVbTa4GYRlzth0rrCK3/QmSxCnXTqfKoLWAppFADUvetlci # P41+7HiRPP8ewEC8NGoG7OkxK0oHE6mdOvPBJkh9oi3UuY4j0gNTAR5yHcsXgAfB # FIZmqrPrjWqogHUl1UbCaqXQQ+YJnvumOvVRqaHNfC+sOfbyJXW/yHbfUouxa+cq # I6hV2HU4SLy9/RxSGjSFcxwi0vgOvUhaDXQO5Alpaqqew74qw4SPrdTqXbHFmr+6 # CtonYU5ZKndy2t2/Km28hVM/O0qMOOjRhq/NfB8GMBYvihp5xNk+D/aAygp6x3sS # OEL70ypff+QwZfzGo9TZ4Pyn1/yxJ+oeUGOcZ+M14v4zaXcnlzK5ctjG8JXb2TXj # rnJ/ox2+rfQi2b6JoztaVjNI+v9l95NdYPQnTF3t0J8/GoXid9rfenmYbRNkeM57 # pt+Vj4q3a4X62E+L+o/hPeHIUWMdgmO/Q3BPds3dkp1va4DOSbbVwiIwCrVY2wE0 # ycD+sDzs24DJyiFNfbGuTtEyYVD/HR/nXY0/oQxKICTPvPdcr32sE1XOwMC3Eg== # SIG # End signature block |