How to use Office 365 Service Communications API V2 with PowerShell

Copper Contributor

Just today, when I ran this code to connect to the Office 365 Service API.

powershell_2018-09-17_20-06-16.png

 

It looks like the API Service EndPoint is down, and I'm not sure if this is tempory or permanent.

Got me thinking about moving to to the v2 API (https://docs.microsoft.com/en-us/office/office-365-management-api/office-365-service-communications-...). I was able to go through registering the app in Azure, but the way it is handled in PowerShell looks way different than that of the v1 API.

 

Has anyone had any success in scripting with the v2 API for retrieving the Office 365 Service Status?

 

 

8 Replies

We are using the O365ServiceCommunications module written by Matt McNabb (https://github.com/mattmcnabb/O365ServiceCommunications) and started receiving "The service is unavailable" messages on 9/15.  We didn't realize the API that this module utilizes was slated to be deprecated.  We would also be interested if anyone has any code to share on how to retrieve health and message center events using v2 of the API.

figured it out, after hours of testing. really lacking documentation.
I find that the v2 API filter doesn't work, as it returns all messages to your rest query. Although this is no big deal since the results can be filtered for consumption anyway.

I'm still working on the script, and would probably be sharing tomorrow.

FYI - It seems like the older API endpoint is back up again.  At least for us it's working.  However, we do plan to work on converting our scripts to using the newer API.  Biggest hurdle for us was just registering the app in Azure and figuring out the best way to manage the authentication tokens.  The documentation is definitely lacking, but the objects returned from the API seem somewhat similar to those of the older API.

could you please share script with API v2. It's not working for us.

Below is the PowerShell script I came up with which uses v2 of the Graph API.  A few notes:

  • You will need to register an Azure app for use with this script.  The app will need to be granted permissions to read tenant health info.  I have not included instructions for registering the Azure app.  A good starting point on how to do this can be found at the following URL: https://lazyadmin.nl/it/using-microsoft-graph-api-with-powershell/. 
  • Review the "script variables" section of the script and adjust as necessary.
  • As this is my first try at using the Graph API, I'm not convinced the portion of the code which retrieves the OAuth token is optimal.  Please share any changes you make to improve that portion of the script.
  • The secret for the registered app is stored in a file (location specified in PassFile variable).  For the version of the script we use, we encrypt PassFile and have the script decrypt it in order to retrieve the secret.  For the version I've attached I assume an unencrypted file is used to store the secret.

Hopefully others can make use of this and please share any improvements you make with the community. 

 

--------------------------

Monitor-Tenant-Health

--------------------------
#Script variables
$HistoryFile = "Enter folder and path to file which will be used to track tenant health history (i.e. C:\Temp\TenantHealth.csv)"
$PassFile = "Location of file containing client secret for Azure app created for use in obtaining tenant health"
$ClientID = "Application ID of Azure app created for use in obtaining tenant health"
$TenantDomain = "yourtenant.onmicrosoft.com"
$TenantGUID = "GUID of your tenant"
$NotificationRecipients = @("someone@somewhere.com")
$NotificationSender = "donotreply@somewhere.com"
$SmtpServer = "smtpserver.somewhere.com"
$SmtpPort = "25"
$RedirectUri = "https://localhost"
$LoginUrl = "https://login.microsoft.com"
$Resource = "https://manage.office.com"

#Retrieve health events from previous run
$HistoryTable = @{}
If (Test-Path $HistoryFile)
{
$HistoryFileContents = Get-Content -Path $HistoryFile
ForEach ($Entry in $HistoryFileContents) {$HistoryTable.Add(($Entry.Split(","))[0],($Entry.Split(","))[1])}
}

#Retrieve OAuth token
$ClientSecret = Get-Content $PassFile
$RequestBody = @{grant_type="client_credentials";redirect_uri=$RedirectUri;resource=$Resource;client_id=$ClientID;client_secret=$ClientSecret}
$OAuth = Invoke-RestMethod -Method Post -Uri $LoginUrl/$TenantDomain/oauth2/token?api-version=1.0 -Body $RequestBody
$HeaderParams = @{'Authorization'="$($OAuth.Token_Type) $($Oauth.Access_Token)"}

#Retrieve current O365 health events posted for tenant
$O365Incidents = (Invoke-RestMethod -Method Get -Uri $Resource/api/v1.0/$TenantGUID/ServiceComms/Messages -Headers $HeaderParams).Value | Where {$_.MessageType -eq "Incident"}

#Get list of new or changed events
$NewChangedEvents = @()
ForEach ($Incident in $O365Incidents)
{
$NewChangedFlag = 0
If ($HistoryTable.ContainsKey($Incident.Id))
{
If ($HistoryTable[$Incident.Id] -ne (Get-Date($Incident.LastUpdatedTime)).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")) {$NewChangedFlag = 1; $HistoryTable[$Incident.Id] = (Get-Date($Incident.LastUpdatedTime)).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")}
}
Else
{
$HistoryTable.Add($Incident.Id,(Get-Date($Incident.LastUpdatedTime)).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"))
$NewChangedFlag = 1
}

If ($NewChangedFlag -eq 1)
{
$LatestMessage = $($Incident.Messages | Sort-Object -Property PublishedTime)[$Incide3nt.Messages.Count - 1]
$Event = New-Object PSObject
$Event | Add-Member -Type NoteProperty -Name ID -Value $Incident.Id
$Event | Add-Member -Type NoteProperty -Name Service -Value $Incident.WorkloadDisplayName
$Event | Add-Member -Type NoteProperty -Name Status -Value $Incident.Status
$Event | Add-Member -Type NoteProperty -Name StartTime -Value (Get-Date($Incident.StartTime)).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")
$Event | Add-Member -Type NoteProperty -Name LastUpdatedTime -Value (Get-Date($Incident.LastUpdatedTime)).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss")
$Event | Add-Member -Type NoteProperty -Name Details -Value $LatestMessage.MessageText
$Event | Add-Member -Type NoteProperty -Name NotificationRecipients $NotificationRecipients
$NewChangedEvents += @($Event)
}
}

ForEach ($Event in $NewChangedEvents)
{
#Build HTML for email
$MessageText = $Event.Details -replace("`n",'<br>') -replace([char]8217,"'") -replace([char]8220,'"') -replace([char]8221,'"') -replace('\[','<b><i>') -replace('\]','</i></b>')
$HTML = "<html>"
$HTML += "<style>"
$HTML += "BODY{font-family: Arial; font-size: 10pt;}"
$HTML += "H1{font-size: 22px;}"
$HTML += "H2{font-size: 18px; padding-top: 10px;}"
$HTML += "H3{font-size: 16px; padding-top: 8px;}"
$HTML += "H4{font-size: 12px; padding-top: 4px;}"
$HTML += "TABLE{border: 1px solid black; border-collapse: collapse; font-size: 8pt; table-layout: fixed; width: 800px;}"
$HTML += "TABLE.summary{text-align: center; width: auto;}"
$HTML += "TH{border: 1px solid black; background: #dddddd; padding: 5px; color: #000000;}"
$HTML += "TD{border: 1px solid black; padding: 5px; vertical-align: top; }"
$HTML += "td.pass{background: #7FFF00;}"
$HTML += "td.warn{background: #FFE600;}"
$HTML += "td.fail{background: #FF0000; color: #ffffff;}"
$HTML += "td.info{background: #85D4FF;}"
$HTML += "ul{list-style: inside; padding-left: 0px;}"
$HTML += ".firstrunnotice { font-size: 14px; color: #4286f4; }"
$HTML += "</style>"
$HTML += "<body>"
$HTML += "<table>"
$HTML += "<tr bgcolor=""#000099"">"
$HTML += " <td align=""center""><font color=""#ffffff"">Event ID</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">Service</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">Status</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">Event Start Time</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">Last Updated</font></td>"
$HTML += "</tr>"
$HTML += "<tr bgcolor=""#0000FF"">"
$HTML += " <td align=""center""><font color=""#ffffff"">$($Event.ID)</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">$($Event.Service)</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">$($Event.Status)</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">$($Event.StartTime)</font></td>"
$HTML += " <td align=""center""><font color=""#ffffff"">$($Event.LastUpdatedTime)</font></td>"
$HTML += "<tr><td colspan=""5"" style=""border:none"">&nbsp;</td></tr>"
$HTML += "<tr>"
$HTML += " <td colspan=""5"">$MessageText<br/>&nbsp;<br/><a href=""http://o365health.ad.csbsju.edu/"">Office 365 Health Dashboard</a></td>"
$HTML += "</tr>"
$HTML += "<tr><td colspan=""5"" style=""border:none"">&nbsp;</td></tr>"
$HTML += "</table>"
$HTML += "</body>"
$HTML += "</html>"

#Send notification
Send-MailMessage -From $NotificationSender -To $NotificationRecipients -Subject "O365 Health Alert - $($Event.Service) [Event ID: $($Event.ID)]" -Body $HTML -BodyAsHtml -SmtpServer $SmtpServer -Port $SmtpPort -UseSsl
}

Hi All,

 

I'm sorry it took long for me to update this.

 

I have published a working, functional script that I am already using in production.

 

You can read through the post here:

https://www.lazyexchangeadmin.com/2018/10/shd365.html

 

I hope this helps.

 

 

 

@Shawn Beckers 

We are using almost same script which works just fine when run manually. However, when running as a scheduled job, it fails with below error. Initial auth token call is successful but fails on next call to retrieve messages. Any idea/suggestions? Appreciate it!

 

System.Net.WebException: The underlying connection was closed: An unexpected error occurred on a send. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.TlsStream.EndWrite(IAsyncResult asyncResult)
at System.Net.ConnectStream.WriteHeadersCallback(IAsyncResult ar)
--- End of inner exception stack trace ---
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.GetResponse(WebRequest request)
at Microsoft.PowerShell.Commands.WebRequestPSCmdlet.ProcessRecord()

@Rahul Babar 

I believe I've run into that error, or a similar one, when connections are being blocked by firewall or some other locally running security app.  Do you have anything like that running on this machine?  If so, I'd suggest temporarily disabling to see if that allows you to run the scheduled script.