Entra / Microsoft 365 · Exchange Online
Get service health information (Graph)
Example of using the Graph Service Communication API with Microsoft Graph PowerShell SDK cmdlets.
Connect & set up
Run these once per session. All scopes are read-only unless the script makes changes.
Connect-MgGraph -NoWelcome -Scopes $RequiredScopes
Run it
The main script. Copy it, or download the .ps1 and run it from your console.
param([string] $TenantId = "",[string] $AppId = "")Function Add-MessageRecipients {# Function to build an addressee list to send email[cmdletbinding()]Param([array]$ListOfAddresses )ForEach ($SMTPAddress in $ListOfAddresses) {@{ emailAddress = @{address = $SMTPAddress}}}}[array]$RequiredScopes = "ServiceHealth.Read.All", "ServiceMessage.Read.All", "Organization.Read.All"Read-Host "Run script with App or Delegated permissions? (Enter 'App' or 'Delegated')" -OutVariable PermissionTypeIf ($PermissionType -eq "App") {Write-Host "Running with App permissions" -ForegroundColor Yellow$Interactive = $false} Else {Write-Host "Running with Delegated permissions" -ForegroundColor Yellow$Interactive = $true}# Connect to Microsoft Graph with the selected permissionsIf ($Interactive) {# We're running interactively...Clear-HostWrite-Host "Script running interactively... connecting to the Graph" -ForegroundColor YellowConnect-MgGraph -NoWelcome -Scopes $RequiredScopes$Interactive = $true# Email will be sent from the account running the script, so we capture that for later use.} Else {# Running in app-only mode# Email will be sent from this account - adjust for your tenant$MsgFrom = "Azure.Management.Account@office365itpros.com"# Authentication values for the app registration - adjust for your tenant$Thumbprint = "0CF6CE3F3548FD73E7AC8CF20226ED447E125C71"Connect-MgGraph -AppId $AppId -CertificateThumbprint $Thumbprint -TenantId $TenantId -NoWelcome# To connect from Azure Automation runbook, Use Connect-MgGraph -Identity and ensure that the automation account has the right permissions assigned in Azure AD.}# Check that we have the right permissions - in Azure Automation, we assume that the automation account has the right permissionsIf ($Interactive) {[int]$RequiredScopesCount = $RequiredScopes.Count[string[]]$CurrentScopes = (Get-MgContext).Scopes[string[]]$RequiredScopes = $RequiredScopes$CheckScopes =[object[]][Linq.Enumerable]::Intersect($RequiredScopes,$CurrentScopes)If ($CheckScopes.Count -ne $RequiredScopesCount ) {Write-Host ("To run this script, you need to connect to Microsoft Graph with the following scopes: {0}" -f $RequiredScopes) -ForegroundColor RedBreak}}# Recipient for the email sent at the end of the script - define the addresses you want to use here. They can be single recipients,# distribution lists, or Microsoft 365 groups. Each recipient address is defined as an element in an array[array]$EmailRecipient = "Email.Admins@office365itpros.com", "Kim.Akers@office365itpros.com"Write-Host "Looking for open service health advisories..."[string]$RunDate = Get-Date -format 'dd-MMM-yyyy HH:mm'# Find the set of service health items that have advisory status (the ones shown in the M365 admin center)[array]$ServiceHealthItems = Get-MgServiceAnnouncementIssue -All `-Filter "classification eq 'Advisory' and status eq 'serviceDegradation'" | `Sort-Object {$_.LastModifiedDateTime -as [datetime]} -DescendingIf ($ServiceHealthItems) {Write-Host ("{0} Service health items found..." -f $ServiceHealthItems.count)} Else {Write-Host "No service health items found - exiting"Break}# What's happening in the tenant$ServiceHealthItems | Format-Table LastModifiedDateTime, Status, ImpactDescription# Get overall service health[array]$ImportantServices = "Exchange", "Teams", "SharePoint", "OrgLiveID", "Planner", "microsoftteams", "O365Client"[array]$ImportantServiceStatus = Get-MgServiceAnnouncementHealthOverview | Where-Object {$_.Id -in $ImportantServices}$ImportantServiceStatus | Sort-Object Service | Format-Table Service, Status -AutoSize$Report = [System.Collections.Generic.List[Object]]::new()ForEach ($Issue in $ServiceHealthItems) {$IssueId = $Issue.Id# Get the posts from the issue$Posts = $Issue | Select-Object -ExpandProperty Posts | Sort-Object {$_.CreatedDateTime -as [datetime]} -Descending$DaysIssueOld = (New-TimeSpan -Start $Issue.StartDateTime).Days$HoursSinceUpdate = (New-TimeSpan -Start $Issue.LastModifiedDateTime).Hours$DataLine = [PSCustomObject] @{Id = $IssueIdFeatureGroup = $Issue.FeatureGroupTitle = $Issue.TitleDescription = $Issue.ImpactDescriptionStatus = $Issue.Status'Last Note' = $Posts[0].description.content'Start' = $Issue.StartDateTime'Last Update' = $Issue.LastModifiedDateTime'Days old' = $DaysIssueOld'Hours since update'= $HoursSinceUpdate}$Report.Add($DataLine)}# Email the reportWrite-Host ("All done - emailing details to {0}" -f ($EmailRecipient -join ", "))$ToRecipientList = @( $EmailRecipient )[array]$MsgToRecipients = Add-MessageRecipients -ListOfAddresses $ToRecipientList$MsgSubject = ("Current tenant Service Health advisories as at {0}" -f $RunDate)$OrgName = (Get-MgOrganization).DisplayName$HTMLHead="<html><style>BODY{font-family: Arial; font-size: 8pt;}H1{font-size: 22px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H2{font-size: 18px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}H3{font-size: 16px; font-family: 'Segoe UI Light','Segoe UI','Lucida Grande',Verdana,Arial,Helvetica,sans-serif;}TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt;}TH{border: 1px solid #969595; background: #dddddd; padding: 5px; color: #000000;}TD{border: 1px solid #969595; padding: 5px; }td.StateOrange{background: #FFA500;}td.StateRed{background: #FF0000;}td.StateYellow{background: #FFFF00;}</style><body><div align=center><p><h1>Open Service Health Advisories</h1></p><p><h2><b>For the " + $Orgname + " tenant</b></h2></p><p><h3>Generated: " + $RunDate + "</h3></p><p>Details of the known service health advisories.</p></div>"$Line = ("<p>There are currently <b>{0}</b> service advistories open.</p>" -f $ServiceHealthItems.count)$HTMLHead = $HTMLHead + $Line$HtmlBody = $Report | Select-Object Id, FeaturGroup, Title, Status, 'Last Note', 'Start', 'Last Update', 'Hours since update', 'Days old' | ConvertTo-Html -Fragment# This section highlights whether a conditional access policy is enabled or disabled in the summary.# Idea from https://stackoverflow.com/questions/37662940/convertto-html-highlight-the-cells-with-special-values# First, convert the CA Policies report to HTML and then import it into an XML structure$HTMLTable = $Report | ConvertTo-Html -Fragment[xml]$XML = $HTMLTable# Create an attribute class to use, name it, and append to the XML table attributes$TableClass = $XML.CreateAttribute("class")$TableClass.Value = "State"$XML.table.Attributes.Append($TableClass) | Out-Null# Conditional formatting for the table rows. The number of available units is in table row 6, so we update td[5]ForEach ($TableRow in $XML.table.SelectNodes("tr")) {# each TR becomes a member of class "tablerow"$TableRow.SetAttribute("class","tablerow")[int]$HoursSinceUpdate = 0; [int]$DaysSinceStart = 0If ($TableRow.td.count -eq 10) {# If a valid table row, extract the hours since the last update and the days since the incident start[int]$HoursSinceUpdate = $TableRow.td[9][int]$DaysSinceStart = $TableRow.td[8]# Orange state for incident that's between 12 and 23 hours oldIf (($TableRow.td) -and ($HoursSinceUpdate -ge 12 -and $HoursSinceUpdate -lt 24)) {$TableRow.SelectNodes("td")[9].SetAttribute("class","StateOrange")}# Red state for incidents open longer than 24 hoursIf (($TableRow.td) -and ($HoursSinceUpdate -ge 24)) {$TableRow.SelectNodes("td")[9].SetAttribute("class","StateRed")}# Days old is greater than 7If (($TableRow.td) -and ($DaysSinceStart -gt 7)) {$TableRow.SelectNodes("td")[8].SetAttribute("class","StateYellow")}}}# Wrap the output table with a div tag$HTMLBody = [string]::Format('<div class="tablediv">{0}</div>',$XML.OuterXml)# Put the message content together$HTMLMsg = "</body></html><p>" + $HTMLHead + $HTMLBody + "<p>"# Construct the message body$MsgBody = @{Content = "$($HTMLMsg)"ContentType = 'html'}$Message = @{subject = $MsgSubject}$Message += @{toRecipients = $MsgToRecipients}$Message += @{body = $MsgBody}$Params = @{'message' = $Message}$Params += @{'saveToSentItems' = $True}$Params += @{'isDeliveryReceiptRequested' = $True}# And send the message using the parameters that we've filled inSend-MgUserMail -UserId $MsgFrom -BodyParameter $ParamsWrite-Output ("Message containing information about current open service advisories sent to {0}!" -f ($EmailRecipient -join ", "))
Parameters
ParameterDefaultNotes
-TenantId""Microsoft Entra tenant ID for app-only Graph authentication.-AppId""Application (client) ID for the app registration used to connect.Attribution
Author
Office365itpros