Microsoft 365 Message Center and Service Health Power App

Update: please reference the new Graph API endpoint located at: https://docs.microsoft.com/en-us/graph/api/resources/service-communications-api-overview?view=graph-rest-1.0

When working with customers a common topic I’m asked is about service reliability and availability and where to stay current with service health status. This month’s post we’ll look at how to utilize the Office 365 Service Communications API to build a Power App to display service health and communication messages accessible right from your phone.

Within the Office 365 admin portal is a section called the “Message center” where messages are posted about service updates, information, health and so on. To access messages we need to navigate to: Microsoft 365 admin center – Service health where admins see a list of service related messages with the option to enable email notifications. I don’t know about you, however I receive enough email and prefer not to add to the pile, so I figured why not create an app to pull the messages and service health status to be viewed on any device.

Recently the Intune support team published a blog about “Staying up to date on Intune new features, service changes, and service health”, more details may be found here:

https://techcommunity.microsoft.com/t5/intune-customer-success/staying-up-to-date-on-intune-new-features-service-changes-and/ba-p/2038203

Let’s get started

Video: https://www.screencast.com/t/S8tWKpcjyGdF

Using the O365 Service Communications API, we’re creating a Power App to consolidate the data from the two screens below located in the following two M365 admin consoles:


To a single Power Apps app designed for a mobile experience to be quickly viewed on any device. Below is the same app showing two different messages about Intune:


Requirements
Microsoft Azure
Microsoft 365 management API
Power Apps
Power Automate

Power Automate
We’ll need two Power Automate flows to pull data from the M365 service message center API, one for the message center and one service health.

Pro Tip
Create one Power Automate flow and once it’s all working, use the “Save As” option and rename to be utilized for the other flow.

Message Center Power Automate flow

HTTP action
For HTTP action utilize the following URL:

https://manage.office.com/api/v1.0/cbenterprisemobility.onmicrosoft.com/ServiceComms/Message

Note: this is a different endpoint from my previous posts where I solely rely on Microsoft Graph. You may need to update your Azure AD registered app and add API perms to call the service.

If you haven’t configured the authentication for the HTTP action, utilize Azure AD auth under advanced options and add the proper information under the required fields.


Parse JSON action
Use the “Body” from the HTTP action in the “Content” field and add the schema. You can copy mine or simply copy the Json output from the HTTP action, select “Generate from sample” and paste in the data. This will automatically generate the schema for you.

{
    "type": "object",
    "properties": {
        "statusCode": {
            "type": "integer"
        },
        "headers": {
            "type": "object",
            "properties": {
                "Pragma": {
                    "type": "string"
                },
                "OData-Version": {
                    "type": "string"
                },
                "X-Instance-Name": {
                    "type": "string"
                },
                "X-Activity-Id": {
                    "type": "string"
                },
                "X-Time-Taken": {
                    "type": "string"
                },
                "Cache-Control": {
                    "type": "string"
                },
                "Server": {
                    "type": "string"
                },
                "X-AspNet-Version": {
                    "type": "string"
                },
                "X-Powered-By": {
                    "type": "string"
                },
                "Date": {
                    "type": "string"
                },
                "Content-Length": {
                    "type": "string"
                },
                "Content-Type": {
                    "type": "string"
                },
                "Expires": {
                    "type": "string"
                }
            }
        },
        "body": {
            "type": "object",
            "properties": {
                "@@odata.context": {
                    "type": "string"
                },
                "value": {
                    "type": "array",
                    "items": {
                        "type": "object",
                        "properties": {
                            "AffectedWorkloadDisplayNames": {
                                "type": "array"
                            },
                            "AffectedWorkloadNames": {
                                "type": "array"
                            },
                            "Status": {
                                "type": "string"
                            },
                            "Workload": {
                                "type": "string"
                            },
                            "WorkloadDisplayName": {
                                "type": "string"
                            },
                            "ActionType": {},
                            "AdditionalDetails": {
                                "type": "array"
                            },
                            "AffectedTenantCount": {
                                "type": "integer"
                            },
                            "AffectedUserCount": {},
                            "Classification": {
                                "type": "string"
                            },
                            "EndTime": {
                                "type": "string"
                            },
                            "Feature": {
                                "type": "string"
                            },
                            "FeatureDisplayName": {
                                "type": "string"
                            },
                            "UserFunctionalImpact": {
                                "type": "string"
                            },
                            "Id": {
                                "type": "string"
                            },
                            "ImpactDescription": {
                                "type": "string"
                            },
                            "LastUpdatedTime": {
                                "type": "string"
                            },
                            "MessageType": {
                                "type": "string"
                            },
                            "Messages": {
                                "type": "array",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "MessageText": {
                                            "type": "string"
                                        },
                                        "PublishedTime": {
                                            "type": "string"
                                        }
                                    },
                                    "required": [
                                        "MessageText",
                                        "PublishedTime"
                                    ]
                                }
                            },
                            "PostIncidentDocumentUrl": {},
                            "Severity": {
                                "type": "string"
                            },
                            "StartTime": {
                                "type": "string"
                            },
                            "TenantParams": {
                                "type": "array"
                            },
                            "Title": {
                                "type": "string"
                            },
                            "@@odata.type": {
                                "type": "string"
                            },
                            "ActionRequiredByDate": {},
                            "AnnouncementId": {
                                "type": "integer"
                            },
                            "Category": {
                                "type": "string"
                            },
                            "MessageTagNames": {
                                "type": "array"
                            },
                            "ExternalLink": {
                                "type": "string"
                            },
                            "IsDismissed": {
                                "type": "boolean"
                            },
                            "IsRead": {
                                "type": "boolean"
                            },
                            "IsMajorChange": {
                                "type": "boolean"
                            },
                            "PreviewDuration": {},
                            "AppliesTo": {},
                            "MilestoneDate": {
                                "type": "string"
                            },
                            "Milestone": {
                                "type": "string"
                            },
                            "BlogLink": {
                                "type": "string"
                            },
                            "HelpLink": {
                                "type": "string"
                            },
                            "FlightName": {},
                            "FeatureName": {}
                        },
                        "required": [
                            "AffectedWorkloadDisplayNames",
                            "AffectedWorkloadNames",
                            "Status",
                            "Workload",
                            "WorkloadDisplayName",
                            "ActionType",
                            "AdditionalDetails",
                            "AffectedTenantCount",
                            "AffectedUserCount",
                            "Classification",
                            "EndTime",
                            "Feature",
                            "FeatureDisplayName",
                            "UserFunctionalImpact",
                            "Id",
                            "ImpactDescription",
                            "LastUpdatedTime",
                            "MessageType",
                            "Messages",
                            "PostIncidentDocumentUrl",
                            "Severity",
                            "StartTime",
                            "TenantParams",
                            "Title"
                        ]
                    }
                }
            }
        }
    }
}

Pro Tip
Sometimes it’s nice to see a count of the records when building a Power Automate flow, so the next two actions are optional.

Select action – optional step
Add a select action in the “From” field add “value” from the Parse Json from the dynamic content, then map ID to Id or whatever field you want to count from.

Initialize Variable action – optional step
To count the records returned add an “Initialize variable” action, name it, select Integer as the type, and use the following formula to count the data returned in the array:

length(intersection(body('Select'),body('Select')))

Select 2 action
For this select action we’ll use the parse Json value from the dynamic content and map the files as shown in the flow below.

Here’s a peek at the code it generates:

{
    "inputs": {
        "from": "@body('Parse_JSON')?['value']",
        "select": {
            "ID": "@item()?['Id']",
            "Feature": "@item()?['Feature']",
            "Workload": "@item()?['Workload']",
            "Status": "@item()?['Status']",
            "ImpactDescription": "@item()?['ImpactDescription']",
            "Severity": "@item()?['Severity']",
            "StartTime": "@item()?['StartTime']",
            "Classification": "@item()?['Classification']",
            "LastUpdatedTime": "@item()?['LastUpdatedTime']",
            "Title": "@item()?['Title']"
        }
    }
}

Response action
As with all Power Automate flow that push data into Power Apps I utilize the Response action to parse the array. For the Body, select the output from the dynamic content for “Select 2”. The response schema is just the array the Parse JSON output updated to reflect the mapping in “Select 2”:

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "ID": {
                "type": "string"
            },
            "Feature": {
                "type": "string"
            },
            "Workload": {
                "type": "string"
            },
            "Status": {
                "type": "string"
            },
            "ImpactDescription": {
                "type": "string"
            },
            "Severity": {
                "type": "string"
            },
            "StartTime": {
                "type": "string"
            },
            "Classification": {
                "type": "string"
            },
            "LastUpdatedTime": {
                "type": "string"
            },
            "Title": {
                "type": "string"
            }
        }
    }
}

Service Health Power Automate flow
Use the copied flow from above and change the action parameters where needed.

HTTP action
For HTTP action utilize the following URL:

https://manage.office.com/api/v1.0/cbenterprisemobility.onmicrosoft.com/ServiceComms/CurrentStatus

Parse JSON action
Use the “Body” from the HTTP action in the “Content” field and add the schema. You can copy mine or simply copy the Json output from the HTTP action, select “Generate from sample” and paste in the data. This will automatically generate the schema for you.

{
    "type": "object",
    "properties": {
        "@@odata.context": {
            "type": "string"
        },
        "value": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "FeatureStatus": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "FeatureDisplayName": {
                                    "type": "string"
                                },
                                "FeatureName": {
                                    "type": "string"
                                },
                                "FeatureServiceStatus": {
                                    "type": "string"
                                },
                                "FeatureServiceStatusDisplayName": {
                                    "type": "string"
                                }
                            },
                            "required": [
                                "FeatureDisplayName",
                                "FeatureName",
                                "FeatureServiceStatus",
                                "FeatureServiceStatusDisplayName"
                            ]
                        }
                    },
                    "Id": {
                        "type": "string"
                    },
                    "IncidentIds": {
                        "type": "array"
                    },
                    "Status": {
                        "type": "string"
                    },
                    "StatusDisplayName": {
                        "type": "string"
                    },
                    "StatusTime": {
                        "type": "string"
                    },
                    "Workload": {
                        "type": "string"
                    },
                    "WorkloadDisplayName": {
                        "type": "string"
                    }
                },
                "required": [
                    "FeatureStatus",
                    "Id",
                    "IncidentIds",
                    "Status",
                    "StatusDisplayName",
                    "StatusTime",
                    "Workload",
                    "WorkloadDisplayName"
                ]
            }
        }
    }
}

Pro Tip
Sometimes it’s nice to see a count of the records when building a Power Automate flow, so the next two actions are optional.

Select action – optional step
Add a select action in the “From” field add “value” from the Parse Json from the dynamic content, then map ID to Id or whatever field you want to count from.

Initialize Variable action – optional step
To count the records returned add an “Initialize variable” action, name it, select Integer as the type, and use the following formula to count the data returned in the array:

length(intersection(body('Select'),body('Select')))

Select 2 action
For this select action we’ll use the parse Json value from the dynamic content and map the files as shown in the flow below.

Here’s a peek at the code it generates:

{
    "inputs": {
        "from": "@body('Parse_JSON')?['value']",
        "select": {
            "ID": "@item()?['Id']",
            "Workload": "@item()?['Workload']",
            "Specific workload": "@item()?['WorkloadDisplayName']",
            "Status": "@item()?['Status']",
            "Status name": "@item()?['StatusDisplayName']",
            "Status time": "@item()?['StatusTime']",
            "Feature status": "@item()?['FeatureStatus']",
            "Incident ID": "@item()?['IncidentIds']"
        }
    }
}

Response action
As with all Power Automate flow that push data into Power Apps I utilize the Response action to parse the array. For the Body, select the output from the dynamic content for “Select 2”. The response schema is just the array the Parse JSON output updated to reflect the mapping in “Select 2”:

{
    "type": "array",
    "items": {
        "type": "object",
        "properties": {
            "ID": {
                "type": "string"
            },
            "Status": {
                "type": "string"
            },
            "Workload": {
                "type": "string"
            },
            "Specific workload": {
                "type": "string"
            },
            "Status name": {
                "type": "string"
            },
            "Status time": {
                "type": "string"
            },
            "Feature status": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "FeatureDisplayName": {
                            "type": "string"
                        },
                        "FeatureName": {
                            "type": "string"
                        },
                        "FeatureServiceStatus": {
                            "type": "string"
                        },
                        "IncidentIds": {
                            "type": "string"
                        },
                        "FeatureServiceStatusDisplayName": {
                            "type": "string"
                        }
                                        
                }
            }
        }
    }
}

Power Apps
For this app I decided to use the phone layout instead of tablet as I think this type of app would be accessed more on a phone vs tablet, however feel free to choose what layout works well for your organization’s use case.

Screen
Add the he following to the screen OnVisible property:

ClearCollect(IntuneMsgCollection,MEMHealthDashboard.Run());ClearCollect(serviceHealth,M365ServiceHealthApp.Run())

When the apps loads, it will call both Power Automate flows and populate the collections in Power Apps and display the results in both gallery’s.

For the first gallery utilize the following code to call the first Power Automate flow created:

ClearCollect(IntuneMsgCollection,MEMHealthDashboard.Run()) 

In the second gallery utilize the following code to call the second Power Automate flow:

ClearCollect(serviceHealth,M365ServiceHealthApp.Run())


Gallery’s
Add two blank gallery’s and stack them as shown in the image. One gallery will be for service messages and the other for service health status.

Add a Textinput box to use for searching the gallery messages and add “Search messages” to the hint text property.


Messages gallery
Add the following in the Items property of the gallery:

Sort(Search(IntuneMsgCollection,TextInput1.Text,"Workload","Title","ID","Classification","Severity"),LastUpdatedTime,Descending) 

What I’m doing in the code above is sorting the list alphabetically then searching on the five different attributes in quotations. Feel free to add or remove attributes.

With the gallery selected, add an htmltext item and utilized the following html block to point to the M365 message center:

"<font color=blue><a href=""https://admin.microsoft.com/Adminportal/Home?source=applauncher#/MessageCenter"">Visit Message center</a></font>" 

Service status gallery
Add the following to the Items property of the Gallery:

Sort(serviceHealth,'Specific workload') 

Service status icons
Select the gallery and add an information icon. For the color coding of the icons and service status text I use conditions based on the text value that is returned.

For the icons under DESIGN > Icon property we’ll utilize “Switch” to switch the icon based on the status i.e. health of the service:

Switch(ThisItem.Status,"ServiceOperational",Icon.Information,"ServiceDegradation",Icon.Warning)

For the color of the icon, under DESIGN > Color, we use “Switch” again to flip between colors based on the status of the service:

Switch(ThisItem.Status,"ServiceOperational",Color.Green,"ServiceDegradation",Color.Orange,"ServiceRestored",Color.Green) 

I also make the icon selectable because we may want to see more details about any service issues. For this under ACTION > OnSelect add:

Launch("https://admin.microsoft.com/Adminportal/Home?source=applauncher#/servicehealth") 

Refresh button
Add the following to the OnSelect property for the button:

ClearCollect(IntuneMsgCollection,MEMHealthDashboard.Run());ClearCollect(serviceHealth,M365ServiceHealthApp.Run())

Conclusion
That’s it! To summarize, we created two Power Automate flows and a Power App to pull all the service messages and health status for M365 services using the Office 365 Service Communications API. I hope you found this useful as I have.

Author: Courtenay Bernier

Courtenay is a technology professional with expertise in aligning traditional software and cloud services to strategic business initiatives. He has over 20 years of experience in the technology field as well as industry experience working with distribution centers, call centers, manufacturing, retail, restaurant, software development, engineering, and consulting. I am a Principal PM on the Microsoft Endpoint Management Engineering Team, all posts, opinions, statements are my own.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.