Post to Teams Channels from Azure Automation Scripts

From File Creation to Post to Teams Channels

About a month ago, I wrote about my experiences of creating files in SharePoint Online using a PowerShell script executing as an Azure Automation runbook. I reported that I used user credentials stored as a resource in the Azure Automation account to authenticate with the SharePoint PnP module. Once authenticated, I could use the Add-PnPFile cmdlet to create the file created by the script as a file in a SharePoint document library.

I noted that I used the stored credentials to make sure that I could create the file using the identity of a member of the Microsoft 365 group which owned the document library and hadn’t been able to find another way of doing this. I also said that I couldn’t find a way to post to Teams channels because of the way Graph permissions work. Clearly, I was exploring the limits of my knowledge.

Two comments made helpful suggestions. The first noted that the PnP PowerShell module includes a Submit-PnpTeamsChannelMessage cmdlet and suggested that this could be an answer, especially if combined with certificate-based authentication (CBA). The second suggested using the incoming webhook connector to post to a target channel.

Using Submit-PnpTeamsChannelMessage to Post to Teams Channels

My script created the output report in CSV and HTML files and already had a connection to PnP. The Submit-PnpTeamsChannelMessage cmdlet accepts HTML content as the message body for a channel. With the connection and body part in place, I could add post the message using this code:

$TargetTeamId = "107fe4dd-809c-4ec9-a3a1-ab88c96e0a5e"
$TargetTeamChannel = "19:6d688803124c48d6bfa796284e641e9d@thread.tacv2"
Submit-PnPTeamsChannelMessage -Team $TargetTeamId -Channel $TargetTeamChannel -Message $Body -ContentType Html -Important

The parameters are the identifiers for the team owning the target channel and the channel. The team identifier is easily found using the Get-Team or Get-MgGroup cmdlets:

Get-Team -DisplayName "Tenant Information" | ft GroupId, DisplayName

GroupId                              DisplayName
-------                              -----------
107fe4dd-809c-4ec9-a3a1-ab88c96e0a5e Tenant Information

Get-MgGroup -Filter "displayName eq 'Tenant Information'" | ft Id, DisplayName

Id                                   DisplayName
--                                   -----------
107fe4dd-809c-4ec9-a3a1-ab88c96e0a5e Tenant Information

Knowing the team identifier, we can fetch the channel identifiers using the Get-TeamChannel cmdlet:

Get-TeamChannel -GroupId 107fe4dd-809c-4ec9-a3a1-ab88c96e0a5e | ft Id, DisplayName

Id                                               DisplayName
--                                               -----------
19:078bef3cfb6c4c519d4f585f099c9c91@thread.tacv2 General
19:6d688803124c48d6bfa796284e641e9d@thread.tacv2 Planning 2021

The other parameters are self-explanatory. The only other point of interest to note is that the Important switch applies this marking to the message. Figure 1 shows the result.

Message posted to a Teams channel using a script running in Azure Automation
Figure 1: Message posted to a Teams channel using a script running in Azure Automation

Great! We can post a message to a Teams channel using content created by a script running in Azure Automation. The only remaining challenge is how to eliminate the use of the stored credentials. I’m still exploring that point.

Using the Incoming Webhook Connector to Post to Teams Channels

The Incoming Webhook Connector is one of the standard connectors supported by all teams channels. The function of the connector is to accept JSON-formatted content submitted to a URI identifying the target channel and post it as a new message to that channel. Here’s an example of using the webhook connector to post information about new Microsoft 365 roadmap items. Instead of posting a normal message to a channel, the connector posts message cards. These are intended to be notifications that new information is available and can include directions (like a hyperlink) to tell users where they can find the complete story. In my case, it was impossible to fit the complete report into a message card as this blew the maximum size limit for a card. I therefore ended up creating a card to tell the reader that a new version of the report was available together with a button for them to download the report (from SharePoint Online). Here’s the code I used:

# Post to Teams channel using an incoming webhook connector
$GroupWebHookData = 'The new report is available in <a href="' + $NewFileUri + '">' + 'Microsoft 365 Groups Expiration Report</a>'
$DateNow = Get-Date -format g
$Notification = @"
    {
        "@type": "MessageCard",
        "@context": "https://schema.org/extensions",
        "summary": "Microsoft 365 Groups",
        "themeColor": "0072C6",
        "title": "Notification: New Microsoft 365 Groups Expiration Report is available",
         "sections": [
            {
                "facts": [
                    {
                        "name": "Tenant:",
                        "value": "TENANT"
                    },
                    {
                        "name": "Date:",
                        "value": "DATETIME"
                    }],
                    "markdown" : "true"                   
                  }],
                "potentialAction": [{
                       "@type": "OpenUri",
                       "name": "Download the report",
                       "targets": [{
                           "os": "default",
                           "uri": "URI"
                 }],
    }  ]
   } 
"@

$NotificationBody = $Notification.Replace("TENANT","$TenantName").Replace("DATETIME","$DateNow").Replace("URI","$NewFileUri")
# Make sure you use the URI for your channel here.
$TargetChannelURI = "https://office365itpros.webhook.office.com/webhookb2/107fe4dd-809c-4ec9-a3a1-ab88c96e0a5e@b662313f-14fc-43a2-9a7a-d2e27f4f3478/IncomingWebhook/0a3dea30f595436ead8138334516911a/eff4cd58-1bb8-4899-94de-795f656b4a18"
$Command = (Invoke-RestMethod -uri $TargetChannelURI -Method Post -body $NotificationBody -ContentType 'application/json')     

The resulting message card posted to the channel is simple, but it gets the job done (Figure 2).

A message card posted to a Teams channel using the incoming webhook connector
Figure 2: A message card posted to a Teams channel using the incoming webhook connector

Getting the Job Done

The conclusion is that it’s possible to post messages to Teams channels using either the Submit-PnpTeamsChannelMessage cmdlet or inbound webhook connector. Both methods have their own limitations, but once you understand what the limitations are, it’s easy to decide which approach to take in different circumstances.

The full script I used to create the output in an Azure Automation runbook is available in GitHub.


Keep up with the changing world of the Microsoft 365 ecosystem by subscribing to the Office 365 for IT Pros eBook. Monthly updates mean that our subscribers learn about new developments as they happen.

3 Replies to “Post to Teams Channels from Azure Automation Scripts”

Leave a Reply

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