AUTOMATE OFFICE 365 HEALTH STATUS MONITORING USING OFFICE 365 SERVICE COMMUNICATIONS GRAPH API

I wrote an article on how to Automate Office 365 Health Status Monitoring using Office 365 Service Communications API a couple of years ago, which was found very useful for many Office 365 Admins. It was based on the API “https://manage.office.com/api/v1.0/$TenantId/ServiceComms/Messages”, which has now been discontinued by Microsoft. I have put an update on that article about this, so that you can save your time by NOT attempting to try n make that work anymore.

Instead Microsoft released Office 365 Service Communications API in Microsoft Graph, which not only provides additional endpoints to work with Service Communications with ever expanding Microsoft Graph ecosystem.

I will re-use some content from my previous article, as most of the steps remain similar and we just need to update some of the API calls to make this work.

Problem Statement

So, what exactly we are trying to solve here? Well very basic actually. If you are facing any issues with your office 365 environment (i.e. SharePoint pages not loading correctly, unable to sign-in to MS Teams etc.), make sure to check the Office 365 Admin Portal and look into the Service Health, where MS Publishes all Office 365 Health related details, including the updates about troubleshooting.

Manual Health Monitoring

So, now that we know where to look, lets just quickly take a tour about how and what to look for. Even though this is very basic, but could be useful for users who are getting started with Office 365 Administration.

Just login to the Office 365 Admin portal, and go to the Service Health under Health Category. Then click on Incidents tab on the page. It will list down all the ongoing incidents in office 365 environment across various applications.

If you click on the Title of any of the messages, it will show detailed history of all the status, troubleshooting and current status.

And there you have it. In the above example, if your users are facing issues in accessing the mailbox archive, you now know why and chose to send a local/global communication to your end users based on the updates in the portal.

But essentially, it’s quite a reactive approach. In this scenario, if the Office 365 Admins are not proactive enough (hard to be in such cases!), and checking the Health status very frequently, chances are they will know only after users start complaining!

So, what can we do about it?

Configure Notifications

Now, we are back to the topic of this article. In such scenario, obviously, Office 365 Admins will prefer to get some kind of notification (like over email), which tell them about the issue as soon as Microsoft posts something in the Health Center.

Good thing, it’s really easy to setup now. Now, you can just setup to receive mail notifications from with the Health Center configurations. What more, you can pick and choose which type of events you want to monitor (Incidents or Advisory) and also, events related to which Application(s). Just click on “Preferences” and select the check box “Send me service health notifications in email”. You can the type in upto 2 email addresses, select type of events and applications and click Save.

And it’s Done. Next time, MS publishes any new update, you will receive emails in the mailbox.

At the time of writing this article, you can’t customize the mail format and add/remove any details from it. But it’s quick and easy and provides basic necessary details.

So, that’s done, then why do we even need to use the Office 365 Communication Graph APIs, the main topic of this article?

Office 365 Communication Graph API

I know I am being lengthy, but the idea is that you should know that there are other alternatives available which can serve good enough depending upon your scenario.

As we saw in previous sections, even the OOB notifications seem good enough to work with then why even bother about dealing with this Communication Graph APIs.

  • If you want to set up notifications for various users based on different applications
  • If you want to get more details about the incident in the notification mail itself
  • If you want to change the mail format to include/remove details about the event
  • If you want to feed the information to some other system
  • XXX other reasons, you are a techie, you will find reasons 🙂

So, now we have a few reasons to invest our efforts in it. Let’s get started.

Register App in Azure AD

To use the Office 365 Communication Graph APIs, we first need to register an app in Azure AD and grant it permissions to be able to read reports. Just go to Azure AD – App Registrations and Click on New registration.

Fill in the Application Name and account type. Redirect URI is optional, but you can fill in any url like https://localhost.

I am using name as Office 365 Usage Reports, but If you are using your application only for Health Status monitoring, just use a relevant name.

Once registered, go to the Overview and copy the Application (client) ID and Tenant ID to a notepad. We’ll need those later.

Now, we need to assign appropriate permission to this app to be able to read Office 365 Usage Reports. Click on API Permissions in the left navigation and Click Add Permission. In the new window, click on Microsoft Graph and search for “ServiceHealth.Read.All” and “ServiceMessage.Read.All” under Application Permissions. Click on Add Permission after selecting the above mentioned permissions.

You need to Grant Admin Consent to add this permission. If you have tenant admin credentials, you can just click on the button “Grant Admin Consent for xxx”, otherwise send the link of the app to your tenant admin to do so.

Last step is to generate the Client Secret. Click on Certificates and secrets in the left navigation and then click on New client secret. Type in a Description, select Expires as Never and Click Add. Copy the generated Client Secret to a notepad.

Now, our app is ready to read the Office 365 Health Status using the Communication Graph API. We can call the API either through PowerShell or through Power Automate Flow.

PowerShell

So, now that we have an app registered and configured as required, lets jump into the nice stuffs.

I will keep it simple. What are we looking to achieve here is that we should be able to query the Health status incrementally. So essentially, we want to say that send me a notification if something happened in Office 365 Health status after my script ran last time.

So, you got the idea. We need to keep the last run time of the script somewhere. For this script, assuming it will be run on a VM, I decided to keep that last run time in a Registry key. You can always keep it in a separate text file, if you prefer.

Let’s write a separate function which will return the last run time and update the current time of the script at the same time.

#Keep Last Run Time of the Script in Registry
Function GetLastRunTimeFromReg($LastRunTime)
{
$ExistingDateTime = $LastRunTime
if((Get-ItemProperty HKCU:\Software\O365ServiceMonitor -Name LastRunTime -ea 0).LastRunTime)
{
    $ExistingDateTime = (Get-ItemProperty HKCU:\Software\O365ServiceMonitor -Name LastRunTime).LastRunTime
    Set-ItemProperty -Path HKCU:\Software\O365ServiceMonitor -Name LastRunTime -Value $LastRunTime
}
else
{
    New-Item -Path HKCU:\Software\O365ServiceMonitor -Force
    Set-ItemProperty -Path HKCU:\Software\O365ServiceMonitor -Name LastRunTime -Value $LastRunTime
}
return $ExistingDateTime
}

When this script gets executed (details later), it will make an entry in the windows registry like this.

Next, we know to call the Office 365 APIs, we need to get the Access Token using the Client Id and Secret from the Application we registered earlier.

Let’s add another function which will accept the required parameters and return is the Access token.

Function GetBearerToken($TenantId, $ClientId, $ClientSecret)
{
    #No need to change these
    $Scope = "https://graph.microsoft.com/.default"
    $TokenURI = "https://login.microsoft.com/$TenantId/oauth2/token?api-version=1.0"
    $Resource = "https://graph.microsoft.com"
    $TokenHeader = @{
    'Content-Type' = 'application/x-www-form-urlencoded'
    }
    #Notice, body should NOT be converted to JSON format
    $TokenBody = @{
    'client_id'=$ClientId
    'scope'=$Scope
    'resource' = $Resource
    'client_secret'= $ClientSecret
    'grant_type'='client_credentials'
    }

    #Call the API to get the beaerer token in response
    $Response = Invoke-RestMethod -Uri $TokenURI -Headers $TokenHeader -Body $TokenBody -Method Post

    #Extract the bearer token from response
    $AccessToken = $Response.access_token
  
  return $AccessToken
}

Now, we need the another function which will do the trick. This will call the Office 365 communication API, get the results and send the mails.



Function GetOffice365ServiceCommunicationMessages($TenantId, $AccessToken, [string[]]$Workloads, [string[]]$SendMailTo, $SendMailFromUserName, $SendMailFromUserPassword)
{
    #Prepare header for Graph API call
    $Headers = @{
    'Content-Type' = 'application/json'
    'Authorization' = 'Bearer ' + $AccessToken
    }
    #Endpoint URI to get Service Communication Messages
    $Uri = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/issues"

    #Get the last run time of this script from the registry and query for incidents happened or updates posted after that time only
    $CurrentDateTime = (Get-Date).ToUniversalTime()
    $CurrentDateTime = $CurrentDateTime.ToString("MM/dd/yyyy HH:mm:ss")
    $LastRunTimeOfThisScript = GetLastRunTimeFromReg $CurrentDateTime
    $Culture = Get-Culture

    #Call the API to get the , look for incidents only
 $Responses = (Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get).Value | Where {$_.classification -eq "Incident" -and $_.service -in ($Workloads) -and ((($_.lastModifiedDateTime).ToDateTime($Culture)).ToUniversalTime() -gt ($LastRunTimeOfThisScript.ToDateTime($Culture)).ToUniversalTime()) } 
       
    #Name of the SMTP Server to send mails, this is default for Office 365, no need to change
    $Smtp = "smtp.office365.com"
   
    #Get the credentials to send mails
    $SendMailFromUserPassword = ConvertTo-SecureString  $SendMailFromUserPassword -AsPlainText -Force
    $PsCred = New-Object System.Management.Automation.PSCredential -ArgumentList ($SendMailFromUserName, $SendMailFromUserPassword)

    #Loop through each response and send a mail for each message
    foreach($Response in $Responses)
    {
        $MessageID = $Response.Id
        $WorkloadDisplayName = $Response.service
        $Status = $Response.Status
        $MessageTitle = $Response.Title

        #One response may contain multiple messages, only send the latest message over email
        $MessageDetails = $Response.posts[$response.posts.Count-1].description.content
      
        #Subject of the mail to contain the workload name, incident title, current status and ID of the event
        $Subject = "$WorkloadDisplayName : $MessageTitle - $Status ($MessageID)"
        send-MailMessage -SmtpServer $Smtp -From $MailSendFromUserName -To $SendMailTo  -Subject $Subject -Body $MessageDetails -Priority high -UseSsl -Credential $PsCred -Port 587
    }
}

Let’s put all these things together and execute



#Declate the varibales, update these for your tenant and app
$TenantId = "xxxx-62f4-xxxx-ad9c-xxxx"
$ClientId = "xxxxx-8671-xxxx-8347-xxxxx"
$ClientSecret = "xxx-84GkNB.xxxxe5qwp"

#Put the workloads you want to receive mails for (values are like Exchange Online, Microsoft 365 suite, SharePoint Online, etc) For full list, you can call this API in Graph Explorer - https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews and look at Service field
[string[]]$Workloads = "Exchange Online" "SharePoint Online", "Microsoft 365 suite"

#List of users’ valid mail IDs to send mails to
[string[]]$SendMailTo = "[email protected]"

#Any valid account with Exchange online license assigned to appear under from. This is because we are using smtp.office365.com as SMTP.
$SendMailFromUserName = "[email protected]"
$SendMailFromUserPassword = "xxxx" 

#Get the access token
$AccessToken = GetBearerToken $TenantId $ClientId $ClientSecret

#Call the API to look into message center for Incidences and send mails

GetOffice365ServiceCommunicationMessages $TenantId $AccessToken $Workloads $SendMailTo $SendMailFromUserName $SendMailFromUserPassword

And we are done!

Our script is now ready to be added to the task scheduler. Put all these in a single .ps1 file and Add that to the task scheduler and schedule it to run every 30 mins!

Microsoft normally publishes incident updates every 30 mins, so that should cover most updates

It will run twice every hour and if there are any Incidents posted in Office 365 portal since it ran last, it will send the details over an email. Obviously, you can also trigger this manually.

If everything went as expected, you should receive nice mails with all the required details like this.

Looks better than default notifications with all the required details in one go, isn’t it. I will leave it upto you to give it try.

Enjoy,
Anupam

You may also like

6 comments

  1. Hi Anupams,

    I am getting error message

    GetOffice365ServiceCommunicationMessages $TenantId $AccessToken $Workloads $SendMailTo $SendMailFromUserName $SendMailFromUserPassword
    Invoke-RestMethod : The remote server returned an error: (403) Forbidden.
    At line:18 char:16
    + … esponses = (Invoke-RestMethod -Uri $Uri -Headers $Headers -Method Get …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:Htt
    pWebRequest) [Invoke-RestMethod], WebException
    + FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShe
    ll.Commands.InvokeRestMethodCommand

    ConvertTo-SecureString : Cannot bind argument to parameter ‘String’ because it
    is an empty string.
    At line:24 char:57
    + … rPassword = ConvertTo-SecureString $SendMailFromUserPassword -AsPlai …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidData: (:) [ConvertTo-SecureString], Param
    eterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAl
    lowed,Microsoft.PowerShell.Commands.ConvertToSecureStringCommand

    New-Object : Cannot find an overload for “PSCredential” and the argument
    count: “2”.
    At line:25 char:15
    + … $PsCred = New-Object System.Management.Automation.PSCredential -Arg …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : InvalidOperation: (:) [New-Object], MethodExcept
    ion
    + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.Power
    Shell.Commands.NewObjectCommand

    Please help me

    1. Just debug the script in PowerShell IDE and look at the output of each step… Are you passing valid values for parameters $SendMailFromUserName and $SendMailFromUserPassword?
      These are supposed to be valid accounts with mailbox associated.

  2. Hi Anupams,

    Thank you for your script,

    Actually, I’ve the following error message:
    Exception calling “ToDateTime” with “1” argument(s): “String was not recognized as a valid DateTime.”
    At line:62 char:93
    + … ue | Where {$_.classification -eq “Incident” -and $_.service -in ($Wo …
    + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : FormatException

    The registry is created correctly, and $Culture return value
    LCID Name DisplayName
    —- —- ———–
    4108 fr-CH French (Switzerland)

    I don’t know if this is related to regional settings?

    Best regards,

    1. Date and Time format related issues are mostly related to regional settings. Just check something like (“01/31/2023”).ToDateTime($Culture).ToUniversalTime() in any PowerShell window to confirm that the saved date format is correct.

Leave a Reply

Your email address will not be published. Required fields are marked *