Public/Export-M365ServiceHealthReport.ps1
|
function Export-M365ServiceHealthReport { <# .SYNOPSIS Generates an HTML report of current Microsoft 365 service health issues. .DESCRIPTION Reads active service issues via Graph (admin/serviceAnnouncement/issues) and shows service, title, classification (incident/advisory), status and start time. Read-only. Requires ServiceHealth.Read.All. .PARAMETER Path Target path of the HTML file. Default: .\ServiceHealth-Report.html .EXAMPLE Export-M365ServiceHealthReport -BrandName 'CloudNest365' #> [CmdletBinding()] [OutputType([pscustomobject])] param( [string]$Path = (Join-Path (Get-Location) 'ServiceHealth-Report.html'), [string]$BrandName = 'TenantToolbox', [string]$BrandTagline = 'M365 Tenant Administration', [switch]$Csv, [switch]$Excel, [string]$DataPath, [switch]$NoHtml, [switch]$PassThru, [switch]$NoOpen ) Assert-TTGraph Write-TTLog -Level INFO -Message "Reading service health issues ..." try { $issues = Get-TTGraphCollection "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues?`$top=100" } catch { if ("$_" -match 'Forbidden|403') { Write-Warning "Access denied. Needs the 'ServiceHealth.Read.All' scope. Reconnect: Connect-TenantToolbox -UseDeviceCode" } else { Write-Warning "Could not read service issues: $_" } $issues = @() } $data = foreach ($i in $issues) { [pscustomobject]@{ Service = $i.service; Title = $i.title; Classification = $i.classification Status = $i.status; Started = $i.startDateTime; Feature = $i.feature; Id = $i.id } } $data = @($data) $active = @($data | Where-Object { $_.Status -notin 'serviceRestored', 'resolved', 'falsePositive', 'postIncidentReviewPublished' }) $total = $data.Count $activeN = $active.Count $incidents = @($data | Where-Object { $_.Classification -eq 'incident' }).Count $advisories = @($data | Where-Object { $_.Classification -eq 'advisory' }).Count $genAt = Get-Date -Format 'yyyy-MM-dd HH:mm'; $tenant = (Get-MgContext).TenantId $activeSet = @{}; foreach ($a in $active) { $activeSet[$a.Id] = $true } $rows = foreach ($i in ($data | Sort-Object Started -Descending)) { $isActive = $activeSet.ContainsKey($i.Id) $fInc = if ($i.Classification -eq 'incident') { '1' } else { '0' } $fActive = if ($isActive) { '1' } else { '0' } $cls = if ($i.Classification -eq 'incident') { "<span class='b b-bad'>incident</span>" } else { "<span class='b b-warn'>advisory</span>" } $st = if ($isActive) { "<span class='b b-warn'>$(TTEnc $i.Status)</span>" } else { "<span class='muted'>$(TTEnc $i.Status)</span>" } $started = if ($i.Started) { ([datetime]$i.Started).ToString('yyyy-MM-dd HH:mm') } else { '–' } $searchAttr = TTEnc ("$($i.Service) $($i.Title)".ToLower()); $nameAttr = TTEnc ([string]$i.Title).ToLower() @" <tr class="item" data-f-incident="$fInc" data-f-active="$fActive" data-name="$nameAttr" data-search="$searchAttr"> <td><b>$(TTEnc $i.Service)</b></td><td>$(TTEnc $i.Title)</td><td>$cls</td><td>$st</td><td>$started</td> </tr> "@ } $kpiHtml = New-TTKpis @( @{ n = $total; l = 'Issues'; filter = 'all' } @{ n = $activeN; l = 'Active'; kind = 'warn'; filter = 'active' } @{ n = $incidents; l = 'Incidents'; kind = 'bad'; filter = 'incident' } @{ n = $advisories; l = 'Advisories'; kind = 'info' } ) $toolbar = New-TTToolbar -SearchPlaceholder 'Search service or title ...' -Filters @( @{ label = 'All'; key = 'all' }, @{ label = 'Active'; key = 'active' }, @{ label = 'Incidents'; key = 'incident' } ) $body = @" <div class="panel"><table class="tbl"><thead><tr><th data-sort="name">Service</th><th>Title</th><th>Type</th><th>Status</th><th>Started</th></tr></thead> <tbody> $($rows -join "`n") </tbody></table><div class="empty" id="empty">No issue matches the filters.</div></div> "@ $html = New-TTHtmlPage -Title 'Service Health' -Heading 'Microsoft 365 Service Health' -Sub "Tenant $tenant · generated $genAt · $activeN active issues" ` -BrandName $BrandName -BrandTagline $BrandTagline -KpiHtml $kpiHtml -ToolbarHtml $toolbar -BodyHtml $body Write-TTLog -Level INFO -Message "Service health report created: $Path ($activeN active, $incidents incidents)." $flat = $data | Select-Object Service, Title, Classification, Status, @{N = 'Started'; E = { if ($_.Started) { ([datetime]$_.Started).ToString('yyyy-MM-dd HH:mm') } } } Complete-TTReport -Html $html -Path $Path -Data $flat -Csv:$Csv -Excel:$Excel -DataPath $DataPath -NoHtml:$NoHtml -NoOpen:$NoOpen -Kind 'Service-Health-Report' if ($PassThru) { $data } } |