Microsoft Message Center on Telegram via PowerShell and Azure Automation

And the plus and minus of any SaaS system is that it is not controlled by us, and we (in most cases) cannot influence the cycle of updates of the main functionality and the addition of new features. However, these updates can be both informative and not carry any major changes in functionality, or they can be critical for the infrastructure, which in turn carries additional risks for business, and therefore, for our peace of mind, as for IT engineers support the whole thing. This article will tell you how to get all the necessary messages about updates in Microsoft 365 without installing any additional applications. Of all we need is a registered application for API access in Azure Active Directory, Azure Automation, PowerShell, and a bot in Telegram.





A task:





, API M365, Teams Telegram.





:





  1. Microsoft 365 Roadmap





  2. Microsoft 365 Message Center





, , .. RSS feed, RSS Telegram . .





, Roadmap , , , Microsoft Message Center . , , .





, PowerShell, . . Azure Automation . , Azure Active Directory .





1.      Azure Active Directory





AAD to Azure
AAD Azure

2.      Manage App registrations





3.      New Registration Azure Active Directory





4.      Name , .





5.      Register . , , :





  • Application ID





  • Directory ID





.





6.      , Client  Secret, . Certificates & Secrets New Client Secret. , Client Secret.





7.      Description .





8.      Value , , .





9.      Message Center. API Permissions





10.  Graph API User.Read, .





.





M365 , Delegated Permissions, , Application Permissions . , - , MFA , Application Permissions Global . User.Read , . Remove permission.





Message Center. Add Permission > Office 365 Management API





11.  Application Permissions > ServiceHealth.Read Add Permissions





12.  Global Admin, Grant admin consent, ,





13.  , Granted for <tenant name>





.





, M365 API, Get-APIToken. :





  • Application ID





  • Tenant ID (directory ID)





  • App Secret (Client Secret)





5





Rest URL :





β€œhttps://login.microsoftonline.com/” + $TenantID + β€œ/oauth2/v2.0/token”











Function Get-ApiToken {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$True)]
        [String]
        $AppId, $AppSecret, $TenantID
    )

    $AuthUrl = "https://login.microsoftonline.com/$TenantID/oauth2/v2.0/token"
    $Scope = "https://manage.office.com/.default"

    $Body = @{
        client_id = $AppId
        client_secret = $AppSecret
        scope = $Scope
        grant_type = 'client_credentials'
    }

    $PostSplat = @{
        ContentType = 'application/x-www-form-urlencoded'
        Method = 'POST'
        Body = $Body
        Uri = $AuthUrl
    }

    try {
        Invoke-RestMethod @PostSplat -ErrorAction Stop
    }
    catch {
        Write-Warning "$(Get-Date): Exception was caught: $($_.Exception.Message)" 
    }
}
      
      



Token .





try {
    $Token = Get-ApiToken -AppId $ClientId -AppSecret $ClientSecret -TenantID $TenantId -ErrorAction Stop
    Write-Output "$(Get-Date): Token successfully issued"
}
catch {
    Write-Error "$(Get-Date): Can't get the token!"
    break
}
      
      



 





:





  , Message Center , Get-MCMessages Get-ApiRequestResult





Get-ApiRequestResult.





URL , .





header Splat.





Function Get-ApiRequestResult {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$True)]
        [String]
        $Url, $Method, $Token
    )
 
    $Header = @{
        Authorization = "$($Token.token_type) $($Token.access_token)"
    }

    $PostSplat = @{
        ContentType = 'application/json'
        Method = $Method
        Header = $Header
        Uri = $Url
    }

    try {
        Invoke-RestMethod @PostSplat -ErrorAction Stop
    }
    catch {
        $Ex = $_.Exception
        $ErrorResponse = $ex.Response.GetResponseStream()
        $Reader = New-Object System.IO.StreamReader($errorResponse)
        $Reader.BaseStream.Position = 0
        $Reader.DiscardBufferedData()
        $ResponseBody = $Reader.ReadToEnd();
        Write-Output "$(Get-Date): Response content:`n$responseBody" -f Red
        throw Write-Error "$(Get-Date): Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
    }
}
      
      



Message Center.





Get https://manage.office.com/api/ServiceComms/Messages





Function Get-MCMessages {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$True)]
        $APIUrl, $TenantId
    )

    $ApiVersion = "v1.0"
    $MS_resource = "ServiceComms/Messages?&`$filter=MessageType%20eq%20'MessageCenter'"
    $Uri = "$APIUrl/$ApiVersion/$($TenantId)/$MS_resource"
    
    $Method = "GET"

    try {
        Get-ApiRequestResult -Url $Uri -Token $Token -Method $Method -ErrorAction Stop
        Write-Output "$(Get-Date): New messages successfully collected"
    }
    catch {
        $Ex = $_.Exception
        $ErrorResponse = $ex.Response.GetResponseStream()
        $Reader = New-Object System.IO.StreamReader($errorResponse)
        $Reader.BaseStream.Position = 0
        $Reader.DiscardBufferedData()
        $ResponseBody = $Reader.ReadToEnd();
        Write-Output "$(Get-Date): Response content:`n$responseBody" -f Red
        throw Write-Error "$(Get-Date): Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)"
    }
}
      
      



? , API M365 , , , . , $MS_Resource $URL Get-MCMessages. Intune, Get-IntuneManagedDevices, MS_Resource "deviceManagement/managedDevices", URL https://graph.microsoft.com, .





:





, Azure Automation, . , - «» , . LastUpdatedTime, .





Runbook  Azure Automation 1 , . , : , . :





$CurrentTime = Get-Date
$ontrolTime = ($CurrentTime).AddMinutes(-60)
      
      



Get-MCMessages.





$Messages = Get-MCmessages -APIUrl $APIUrl -TenantId $TenantId
      
      



,





$NewMessages = $Messages.value | Where-Object {$(Get-date $($_.LastUpdatedTime)) -ge $controlTime}
      
      







$NewMessagesCount = $NewMessages.id.count

if ($NewMessagesCount -gt 0) {
    Write-Output "$(Get-Date): There are $NewMessagesCount new messages"
}
else {
    Write-Output "$(Get-Date): There is no new messages"
    break
}
      
      



, , . .





if ($NewMessagesCount -gt 0) {

    foreach ($NewMessage in $NewMessages){

    }

}
      
      



.





$MessagePreview = $NewMessage.Messages.MessageText
$MessageID = $NewMessage.id
$MessageTitle = $NewMessage.Title
$MessageType = $NewMessage.actiontype
$PublishedTime = Get-date $($NewMessage.Messages.publishedTime)
$UpdatedTime = Get-Date $($NewMessage.LastUpdatedTime)
      
      



MessageText html, , Telegram . , , , Telegram . Remove-HtmlTags, html , .





. :





  • - .





  • - , .





  • - - .





, , . :





Function Remove-HtmlTags {

    param (
        $Text
    )

    $SimpleTags = @(
        'p',
        'i',
        'span',
        'div',
        'ul',
        'ol',
        'h1',
        'h2',
        'h3',
        'div'
    )

    $TagsToRemove = (
        "\<\/?font[^>]*\>",
        '\<br\s?\/?\>',
        '\&rarr',
        'style=""',
        ' target\=\"_blank\"'
    )

    $TagsToReplace = @(
        @('\[','<b>'),
        @('\]','</b>'),
        @('\<A','<a'),
        @('\<\/A\>','</a>'),
        @('\<img[^>]*\>','[There was an image]'),
        @('&nbsp;',' '),
        @('\<li\>',' -'),
        @('\<\/li\>',"`n")
    )

    foreach($Tag in $SimpleTags){
        $Pattern = "\<\/?$tag\>"
        $Text = $Text -replace $Pattern
    }

    foreach($Tag in $TagsToRemove){
        $Text = $Text -replace $Tag
    }

    foreach($Tag in $TagsToReplace){
        $Text = $Text -replace $Tag

    }
    
    foreach($Tag in $SimpleTags){
        $Pattern = "\<\/?$Tag\>"
        $Text = $Text -replace $Pattern
    }

    $Text
    
}
      
      



, , , , , - . - , , , .





, . , , Microsoft , . </p>, , , html . .





$MessageTextWithHtmlString = $MessagePreview -split ('\<\/p\>')
$FormattedMesssageText = $(Remove-HtmlTags $MessageTextWithHtmlString-creplace '(?m)^\s*\r?\n',''
      
      



. Title .





$PublishingInfo = "Published: $PublishedTime `nUpdated: $UpdatedTime"
$TgmMessage = "$BoldMessageTitle `n$MessageDescription `n$PublishingInfo `n$FormattedMesssageText"
      
      



Microsoft , , . .





$MessageActionRequiredByDate = $NewMessage.ActionRequiredByDate
$MessageAdditionalInformation = $NewMessage.ExternalLink
$MessageBlogLink = $NewMessage.BlogLink

if($MessageActionRequiredByDate){

		$TgmMessage += "`nAction required by date:  $MessageActionRequiredByDate"

}
elseif ($MessageAdditionalInformation) {

		$TgmMessage += "`n$MessageAdditionalInformation'>Additional info"

}
elseif ($MessageBlogLink) {

		$TgmMessage += "`n$MessageBlogLink'>Blog"

}
      
      



. , .





ChatID, Token, ParsingType, - , .





function Send-TelegramMessage {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory=$true)]
        [string]
        $MessageText,$TokenTelegram,$ChatID

        [Parameter(Mandatory=$true)]
        [ValidateSet("html","markdown")]
        [string]$ParsingType
    )

    $URL_set = "https://api.telegram.org/bot$TokenTelegram/sendMessage"

    $Body = @{
        text = $MessageText
        parse_mode = $ParsingType
        chat_id = $chatID
    }

    $MessageJson = $body | ConvertTo-Json

    try {
        Invoke-RestMethod $URL_set -Method Post -ContentType 'application/json; charset=utf-8' -Body $MessageJson -ErrorAction Stop
        Write-Output "$(Get-Date): Message has been sent"
    }
    catch {
        Write-Error "$(Get-Date): Can't sent message"
        Write-Output "$(Get-Date): StatusCode:" $_.Exception.Response.StatusCode.value__ 
        Write-Output "$(Get-Date): StatusDescription:" $_.Exception.Response.StatusDescription
        throw
    }
    
}
      
      



, , :





Send-TelegramMessage -MessageText $TgmMessage -TokenTelegram $TokenTelegram -ChatID $chatID -ParsingType 'html'
      
      



:





, ID, , . Azure Automation Secure Assets. .





Link to repository with code





Link to the channel with the running bot





PS I will be glad to contributors and suggestions for improving the bot.








All Articles