Monitor active M365 ServiceHealth services only with PowerShell

  • last updated: Fri, 10 Feb 2023 14:02:11

You have some Microsoft 365 (M365) customers which you like to monitor. Every day you are looking at the customer specific M365 portal looking for Microsoft event.

Logging in at the customers portal isn’t a very efficient way. Microsoft provides a lot of API’s which can be used for monitoring. By combining some of them you are able to do some really nice and smart things.

As you may properly know Microsoft provide a ServiceHealth API for monitoring the Microsoft 365 services. By requesting API you get all messages even from services you don’t use. Well there is a way to filter only the services you use in your tenant.


Getting the messages

In my blogpost about authenticating against the API with PowerShell i’ve wrote in basics how to get an authentication token which you need for requesting an API.
How to use RESTAPI with Powershell

First you need to request the ServiceHealth message API. This API shows every message for the past few weeks.


To get the right token you need to authenticate to Authenticating against the wrong resource will result in a “invalid audience” error

In PowerShell the request looks like this:

$tenantId = xxx
$resource = "https//"
$clientId = "the application id"
$clientSecret = "The secret you have created under secrets"
$body = @{grant_type = "client_credentials"; resource = $resource; client_id = $ClientId; client_secret = $ClientSecret }
$oauth = Invoke-RestMethod -Method Post -Uri "$($tenantID)/oauth2/token?api-version=1.0" -Body $body
$token = @{'Authorization' = "$($oauth.token_type) $($oauth.access_token)" }

$uri = "$tenantId/ServiceComms/Messages"
$allMessages = Invoke-RestMethod -Uri $uri -Headers $token -Method Get

After running these commands you get some output like this:

AffectedWorkloadDisplayNames : {}
AffectedWorkloadNames        : {}
Status                       : Service restored
Workload                     : SharePoint
WorkloadDisplayName          : SharePoint Online
ActionType                   :
AdditionalDetails            : {@{Name=NotifyInApp; Value=True}}
AffectedTenantCount          : 0
AffectedUserCount            :
Classification               : Incident
EndTime                      : 9/24/2020 6:14:10 PM
Feature                      : customsolutionsworkflows
FeatureDisplayName           : Custom Solutions and Workflows
UserFunctionalImpact         : 
Id                           : SP222774
ImpactDescription            : Users did not receive email notifications from SharePoint Online.
LastUpdatedTime              : 9/24/2020 6:21:20 PM
MessageType                  : Incident
Messages                     : {@{MessageText=Title: Some users are not receiving email notifications through SharePoint Online

                               User Impact: Users are not receiving email notifications through SharePoint Online.

                               Current status: We're investigating a potential issue with SharePoint Online where users are not receiving email notifications through SharePoint Online. We'll 
                               provide an update within 30 minutes.; PublishedTime=9/24/2020 6:56:49 AM}, @{MessageText=Title: Some users are unable to receive email notifications in SharePoint      

                               Next update by: Thursday, September 24, 2020, at 1:00 PM UTC; PublishedTime=9/24/2020 10:37:34 AM}}
PostIncidentDocumentUrl      :
Severity                     : Sev2
StartTime                    : 9/24/2020 6:53:54 AM
TenantParams                 : {}
Title                        : Users did not received email notifications from SharePoint Online


Now we have all the Microsoft 365 health messages. If you want to get messages about specific Microsoft services you need to do some filtering at the API URL. See below for an example for Teams. If you have one tenant this will do, if you have more tenants, like MSP’s, it will grab Teams messages over every tenant. This will result in false positives if a tenant don’t use Microsoft Teams.$tenantId/ServiceComms/Messages?$filter=Workload eq 'Teams'

Writing tenant specific filter queries will work if you have a few. If you have many more you should consider an other option. One of the options I will explain below.

Tenant Info

There is a way how to determine which services a tenant is using automatically. A M365 tenant has a security page which tells you about your tenants security status. The status page will be refreshed every 24 hours.

image-1147 Behind the status page there are some API’s available which provide security info and tenant specific details like enabled services.


Not using the $top=1 filter will result in 90 results. Only the most recent value is fine for now. After requesting the API the info you will get look like this:

id                       : 85bxxxca-b5c4-4xxf-a869-d21xxxx992c9_2020-09-28
azureTenantId            : 85bxxxca-b5c4-4xxf-a869-d21xxxx992c9
activeUserCount          : 1288
createdDateTime          : 9/28/2020 12:00:00 AM
currentScore             : 70
enabledServices          : {HasExchange, HasProject, HasSharePoint, HasOD4B}
licensedUserCount        : 502177
maxScore                 : 238

As you can see it has an object call enabledServices. After doing some PowerShell magic manipulating the string you only have the services itself.

The security API ‘lives’ at a Microsoft different part so you will need to get a new token. Authenticate to

After receiving the security token you can run the following PowerShell command:

$uri =`$top=1
$query = Invoke-WebRequest -Method $method -Uri $uri -ContentType "application/json" -Headers @{Authorization = "Bearer $token" } -ErrorAction Stop 
$ConvertedOutput = $query | Select-Object -ExpandProperty content | ConvertFrom-Json

$convertedoutput.value.enabledServices.replace("Has", $null)


The output after removing “has” is exactly the same as the message Workload content shown at the beginning of this post.

Workload                     : SharePoint

Bring the parts together

Having those two parts we can bring these two parts together. First we need to gather all the messages into a variable. After getting the messages we are going to load the tenantinfo in a variable too.
I wrote a function for getting the tenant information.

function get-tenantInfo {
        [Parameter (Mandatory = $true)][object] $tenantId,
        [Parameter (Mandatory = $true)][object] $clientId,
        [Parameter (Mandatory = $true)][object] $clientSecret
    $results = @()
    # Construct URI
    $uri = "$tenantId/oauth2/v2.0/token"
    # Construct Body
    $body = @{
        client_id     = $clientId
        scope         = ""
        client_secret = $clientSecret
        grant_type    = "client_credentials"
    write-host  "Get OAuth 2.0 Token"
    # Get OAuth 2.0 Token
    $tokenRequest = Invoke-WebRequest -Method Post -Uri $uri -ContentType "application/x-www-form-urlencoded" -Body $body -UseBasicParsing
    # Access Token
    $token = ($tokenRequest.Content | ConvertFrom-Json).access_token
    # Graph API call in PowerShell using obtained OAuth token (see other gists for more details)
    # Specify the URI to call and method
    $method = "GET"
    $uri = "`$top=1"
    write-host "Run Graph API Query"
    # Run Graph API query 
    $query = Invoke-WebRequest -Method $method -Uri $uri -ContentType "application/json" -Headers @{Authorization = "Bearer $token" } -ErrorAction Stop 
    write-host  "Parse results"
    $ConvertedOutput = $query | Select-Object -ExpandProperty content | ConvertFrom-Json

    write-host  "Display results`n"
    foreach ($obj in $convertedoutput.value) {
        $mainCustomerObject = [PSCustomObject][Ordered]@{
            objectId         = $
            tenantId         = $obj.azureTenantId
            activeUsers      = $obj.activeUserCount
            date             = $obj.createdDateTime
            enabledServices  = ($obj.enabledServices).replace("Has", $null)
            currentScore     = $obj.currentScore
            maxPossibleScore = $obj.maxScore
        $results += $mainCustomerObject
    return $results

$tenantParameters = @{
tenantId = xxx
clientId = xxx
clientSecret = xxx
$uri = "$tenantId/ServiceComms/Messages"
$allMessages = Invoke-RestMethod -Uri $uri -Headers $token -Method Get

$healthMessages = $allMessages.Value 
$tenantInfo = get-TenantInfo @tenantParameters

$allMessages | ? { $tenantInfo.enabledServices -match $_.Workload }

Now you have messages fitting the services you use. I’m using Azure Functions which is checking every x time for new messages.

Thank you for reading my blog monitor active m365 servicehealth services only with powershell.
I hope you got a bit inspired. Enjoy your day and happy automating 👋

comments powered by Disqus

Related Posts

How to use REST API with Powershell

The past few years REST API became important more and more. REST API is a common way for communicating against and between applications. Azure and Microsoft 365 also using REST API’s.

Read more

AVD Image Management Automated - Part 1 Create an AVD image version based on the existing config with PowerShell

This article is part one of a series of posts about AVD image management automated. In this first part, I will describe how to create and connect a new disk (based on a snapshot) to a new Azure VM based on the existing sessionhost configuration.

Read more

The IntuneCLI for automated Intune management

Managing Intune can be challenging, especially when having multiple Microsoft Intune environments. Because of that, I started project IntuneAssistant. A part of the project is a CLI tool that can run as a daemon in your own environment.

Read more