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:
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.
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).
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.
{"id":null,"mode":"button","open_style":"in_modal","currency_code":"EUR","currency_symbol":"\u20ac","currency_type":"decimal","blank_flag_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/blank.gif","flag_sprite_url":"https:\/\/office365itpros.com\/wp-content\/plugins\/tip-jar-wp\/\/assets\/images\/flags\/flags.png","default_amount":100,"top_media_type":"featured_image","featured_image_url":"https:\/\/office365itpros.com\/wp-content\/uploads\/2022\/11\/cover-141x200.jpg","featured_embed":"","header_media":null,"file_download_attachment_data":null,"recurring_options_enabled":true,"recurring_options":{"never":{"selected":true,"after_output":"One time only"},"weekly":{"selected":false,"after_output":"Every week"},"monthly":{"selected":false,"after_output":"Every month"},"yearly":{"selected":false,"after_output":"Every year"}},"strings":{"current_user_email":"","current_user_name":"","link_text":"Virtual Tip Jar","complete_payment_button_error_text":"Check info and try again","payment_verb":"Pay","payment_request_label":"Office 365 for IT Pros","form_has_an_error":"Please check and fix the errors above","general_server_error":"Something isn't working right at the moment. Please try again.","form_title":"Office 365 for IT Pros","form_subtitle":null,"currency_search_text":"Country or Currency here","other_payment_option":"Other payment option","manage_payments_button_text":"Manage your payments","thank_you_message":"Thank you for supporting the work of Office 365 for IT Pros!","payment_confirmation_title":"Office 365 for IT Pros","receipt_title":"Your Receipt","print_receipt":"Print Receipt","email_receipt":"Email Receipt","email_receipt_sending":"Sending receipt...","email_receipt_success":"Email receipt successfully sent","email_receipt_failed":"Email receipt failed to send. Please try again.","receipt_payee":"Paid to","receipt_statement_descriptor":"This will show up on your statement as","receipt_date":"Date","receipt_transaction_id":"Transaction ID","receipt_transaction_amount":"Amount","refund_payer":"Refund from","login":"Log in to manage your payments","manage_payments":"Manage Payments","transactions_title":"Your Transactions","transaction_title":"Transaction Receipt","transaction_period":"Plan Period","arrangements_title":"Your Plans","arrangement_title":"Manage Plan","arrangement_details":"Plan Details","arrangement_id_title":"Plan ID","arrangement_payment_method_title":"Payment Method","arrangement_amount_title":"Plan Amount","arrangement_renewal_title":"Next renewal date","arrangement_action_cancel":"Cancel Plan","arrangement_action_cant_cancel":"Cancelling is currently not available.","arrangement_action_cancel_double":"Are you sure you'd like to cancel?","arrangement_cancelling":"Cancelling Plan...","arrangement_cancelled":"Plan Cancelled","arrangement_failed_to_cancel":"Failed to cancel plan","back_to_plans":"\u2190 Back to Plans","update_payment_method_verb":"Update","sca_auth_description":"Your have a pending renewal payment which requires authorization.","sca_auth_verb":"Authorize renewal payment","sca_authing_verb":"Authorizing payment","sca_authed_verb":"Payment successfully authorized!","sca_auth_failed":"Unable to authorize! Please try again.","login_button_text":"Log in","login_form_has_an_error":"Please check and fix the errors above","uppercase_search":"Search","lowercase_search":"search","uppercase_page":"Page","lowercase_page":"page","uppercase_items":"Items","lowercase_items":"items","uppercase_per":"Per","lowercase_per":"per","uppercase_of":"Of","lowercase_of":"of","back":"Back to plans","zip_code_placeholder":"Zip\/Postal Code","download_file_button_text":"Download File","input_field_instructions":{"tip_amount":{"placeholder_text":"How much would you like to tip?","initial":{"instruction_type":"normal","instruction_message":"How much would you like to tip? Choose any currency."},"empty":{"instruction_type":"error","instruction_message":"How much would you like to tip? Choose any currency."},"invalid_curency":{"instruction_type":"error","instruction_message":"Please choose a valid currency."}},"recurring":{"placeholder_text":"Recurring","initial":{"instruction_type":"normal","instruction_message":"How often would you like to give this?"},"success":{"instruction_type":"success","instruction_message":"How often would you like to give this?"},"empty":{"instruction_type":"error","instruction_message":"How often would you like to give this?"}},"name":{"placeholder_text":"Name on Credit Card","initial":{"instruction_type":"normal","instruction_message":"Enter the name on your card."},"success":{"instruction_type":"success","instruction_message":"Enter the name on your card."},"empty":{"instruction_type":"error","instruction_message":"Please enter the name on your card."}},"privacy_policy":{"terms_title":"Terms and conditions","terms_body":null,"terms_show_text":"View Terms","terms_hide_text":"Hide Terms","initial":{"instruction_type":"normal","instruction_message":"I agree to the terms."},"unchecked":{"instruction_type":"error","instruction_message":"Please agree to the terms."},"checked":{"instruction_type":"success","instruction_message":"I agree to the terms."}},"email":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email address"},"success":{"instruction_type":"success","instruction_message":"Enter your email address"},"blank":{"instruction_type":"error","instruction_message":"Enter your email address"},"not_an_email_address":{"instruction_type":"error","instruction_message":"Make sure you have entered a valid email address"}},"note_with_tip":{"placeholder_text":"Your note here...","initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"empty":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"not_empty_initial":{"instruction_type":"normal","instruction_message":"Attach a note to your tip (optional)"},"saving":{"instruction_type":"normal","instruction_message":"Saving note..."},"success":{"instruction_type":"success","instruction_message":"Note successfully saved!"},"error":{"instruction_type":"error","instruction_message":"Unable to save note note at this time. Please try again."}},"email_for_login_code":{"placeholder_text":"Your email address","initial":{"instruction_type":"normal","instruction_message":"Enter your email to log in."},"success":{"instruction_type":"success","instruction_message":"Enter your email to log in."},"blank":{"instruction_type":"error","instruction_message":"Enter your email to log in."},"empty":{"instruction_type":"error","instruction_message":"Enter your email to log in."}},"login_code":{"initial":{"instruction_type":"normal","instruction_message":"Check your email and enter the login code."},"success":{"instruction_type":"success","instruction_message":"Check your email and enter the login code."},"blank":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."},"empty":{"instruction_type":"error","instruction_message":"Check your email and enter the login code."}},"stripe_all_in_one":{"initial":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"empty":{"instruction_type":"error","instruction_message":"Enter your credit card details here."},"success":{"instruction_type":"normal","instruction_message":"Enter your credit card details here."},"invalid_number":{"instruction_type":"error","instruction_message":"The card number is not a valid credit card number."},"invalid_expiry_month":{"instruction_type":"error","instruction_message":"The card's expiration month is invalid."},"invalid_expiry_year":{"instruction_type":"error","instruction_message":"The card's expiration year is invalid."},"invalid_cvc":{"instruction_type":"error","instruction_message":"The card's security code is invalid."},"incorrect_number":{"instruction_type":"error","instruction_message":"The card number is incorrect."},"incomplete_number":{"instruction_type":"error","instruction_message":"The card number is incomplete."},"incomplete_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incomplete."},"incomplete_expiry":{"instruction_type":"error","instruction_message":"The card's expiration date is incomplete."},"incomplete_zip":{"instruction_type":"error","instruction_message":"The card's zip code is incomplete."},"expired_card":{"instruction_type":"error","instruction_message":"The card has expired."},"incorrect_cvc":{"instruction_type":"error","instruction_message":"The card's security code is incorrect."},"incorrect_zip":{"instruction_type":"error","instruction_message":"The card's zip code failed validation."},"invalid_expiry_year_past":{"instruction_type":"error","instruction_message":"The card's expiration year is in the past"},"card_declined":{"instruction_type":"error","instruction_message":"The card was declined."},"missing":{"instruction_type":"error","instruction_message":"There is no card on a customer that is being charged."},"processing_error":{"instruction_type":"error","instruction_message":"An error occurred while processing the card."},"invalid_request_error":{"instruction_type":"error","instruction_message":"Unable to process this payment, please try again or use alternative method."},"invalid_sofort_country":{"instruction_type":"error","instruction_message":"The billing country is not accepted by SOFORT. Please try another country."}}}},"fetched_oembed_html":false}
3 Replies to “Post to Teams Channels from Azure Automation Scripts”