Pimp up your Teams for a daily newspaper

UPDATE

30.10.2020 -New names for the existing update channels by Microsoft and so the URLs have been changed as well:

$Monthly = ‘https://docs.microsoft.com/en-us/officeupdates/monthly-enterprise-channel’
$SAC = ‘https://docs.microsoft.com/en-us/officeupdates/semi-annual-enterprise-channel’
$SACT = ‘https://docs.microsoft.com/en-us/officeupdates/semi-annual-enterprise-channel-preview’

 

Nowadays as a consultant, staying up to date with all of technologies which I am implementing for my customers, but even other interesting products, is a daunting task. With changes seemingly happening daily, how do you keep up?

This blog post describes how to use PowerShell to send various items of information about Microsoft 365 to a Teams channel using the incoming webhook connector. The idea is to scan for recent updates and post new items as message cards to inform tenant admins about new features.

Goals #1

For instance, my main focus is all around Microsoft 365.

There are multiple sources for information on what happening with Microsoft 365

  • Microsoft 365 Roadmap: tells you about what is planned and rolling out for each service.
  • Message Center: tells you about new features and breaking changes to your specific tenant.
  • Service Health: tells you about the current status of each service in your tenant.
  • Office Pro Plus What’s New: tells you about new features and capabilities of Word, Excel, PowerPoint, etc.

I want to have only one platform where I can have all of these updates like my own information center. Some of these information requires admin privileges in your tenant. The introduction of the Global Reader role helps in some capacity. Still, there are a lot of different places where you can find this information.

Goals #2

I would merge my RSS clients into this one implemented platform too, in my Teams channel. So I could use only Teams for this purpose which is working on my iPadOS, on my iOS even on my Windows machines too. 😉

Goals #3

Reduce my tweet list and RSS feeds.

Solution

Use Graph and PowerShell to get this information, run it in Azure Automation and publish it to a dedicated team with channels per feature and source. From there your team can leverage the per channel notification in Teams where you can get notified of all new posts that arrives in a specific channel. In this way, you can easily get information about news that interest you and your focus area.

Again one platform to have these important updates around Microsoft 365.

Prerequisites

  • Microsoft 365 license for using Teams (what a surprise): for sure, my corporate tenant has this license; Office 365 Business Premium.
  • Azure subscription for Automation: for my goals, it’s easy to have a Pay-As-You-Go plan.
  • Microsoft Power Automate (formerly: Flow): As part of Office 365 with Microsoft Flow for Office 365 – this is a free license, allowing for a limited number of Flows to be run per month.

How to get there

  1. Connect to your Office 365 Services (like Exchange, Teams, MSOnline)
    I hope you are using Multi-factor Authentication (MFA) to protect at least your global admin, so for this purpose you need to use modern authentication via PowerShell. Please follow this nice article how to do that.
    Connect-Office365 -Service Exchange, MSOnline, Teams -MFA
  2. Create a Team with Channels
    $Name = ""
    $Domain= ""   # Mail domain
    $UserName=""  # Teams enabled owner
    
    # Microsoft Teams powershell
    Install-Module MicrosoftTeams
    Connect-MicrosoftTeams
    
    # Create the Office 365 Group
    New-UnifiedGroup –DisplayName $Name –Alias $Name –EmailAddresses "$Name@$Domain" -owner $UserName -RequireSenderAuthenticationEnabled $False -Verbose
    
    # This is optional, but may be a good practice initially since Office 365 Groups may clutter your Global Addressbook
    Set-UnifiedGroup –Identity $Name  –HiddenFromAddressListsEnabled $true
    
    # Create the Team
    $group = New-Team -Group (Get-UnifiedGroup $Name ).ExternalDirectoryObjectId -Verbose
    
    Get-Team -DisplayName $Name
    
    $Group = Get-Team -DisplayName $Name
    # Add Channels to the Team for Message Center
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Core Services Message Center" -Description "All Message Center posts related to Microsoft 365 Office Subscription, Office 365 Portal, Office for the web and other undocumented categories"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Exchange Message Center" -Description "All Message Center posts related to Exchange"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Teams and Skype Message Center" -Description "All Message Center posts related to Microsoft Teams and Skype for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "SharePoint and OneDrive Message Center" -Description "All Message Center posts related to SharePoint and OneDrive for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Yammer Message Center" -Description "All Message Center posts related to Yammer"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Intune Message Center" -Description "All Message Center posts related to Microsoft Intune"
    
    # Add channels to the Teams for Service Health
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Core Services Service Health" -Description "All Health Center posts related to Microsoft 365 Office Subscription, Office 365 Portal, Office for the web and other undocumented categories"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Exchange Service Health" -Description "All Health Center posts related to Exchange"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Teams and Skype Service Health" -Description "All Health Center posts related to Microsoft Teams and Skype for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "SharePoint and OneDrive Service Health" -Description "All Health Center posts related to SharePoint and OneDrive for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Yammer Service Health" -Description "All Health Center posts related to Yammer"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Intune Service Health" -Description "All Health Center posts related to Microsoft Intune"
    
    # Add channels to the Teams for Microsoft 365 Roadmap
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Core Services Roadmap" -Description "All Microsoft 365 Roadmap posts related to Microsoft 365 Office Subscription, Office 365 Portal, Office for the web and other undocumented categories"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Exchange Roadmap" -Description "All Microsoft 365 Roadmap posts related to Exchange"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Teams and Skype Roadmap" -Description "All Microsoft 365 Roadmap posts related to Microsoft Teams and Skype for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "SharePoint and OneDrive Roadmap" -Description "All Microsoft 365 Roadmap posts related to SharePoint and OneDrive for Business"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Yammer Roadmap" -Description "All Microsoft 365 Roadmap posts related to Yammer"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Intune and Identity Roadmap" -Description "All Microsoft 365 Roadmap posts related to Microsoft Intune"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Security Roadmap" -Description "All Microsoft 365 Roadmap posts related to ATP and Cloud App Security"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "NextGenApps Roadmap" -Description "All Microsoft 365 Roadmap posts related to Planner, Stream, Forms, Bookings and Whiteboard"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Office Client Roadmap" -Description "All Microsoft 365 Roadmap posts related to Word, Excel, PowerPoint, Outlook, To-Do, Visio, OneNote, Project, Access"
    New-TeamChannel -GroupId $group.GroupId -DisplayName "Compliance Roadmap" -Description "All Microsoft 365 Roadmap posts related to Information Protection"
    
    # Add channels to the Teams for Office client updated
    New-TeamChannel -GroupId $grogp.GroupId -DisplayName "Office ProPlus Monthly Channel" -Description "All Office clients updates to the monthly channel"
    New-TeamChannel -GroupId $grogp.GroupId -DisplayName "Office ProPlus Semi-Annual Channel" -Description "All Office clients updates to the semi-annual channel"
    New-TeamChannel -GroupId $grogp.GroupId -DisplayName "Office ProPlus Semi-Annual Channel Targeted" -Description "All Office clients updates to the semi-annual channel targeted"
    
    # Disconnect the PowerShell Session
    Disconnect-MicrosoftTeams
  3. Create a Connector / Webhook per Channel
    Each channel needs their own incoming webhook connector. The connector cannot be setup up via scripting, but creating a new connector is quickly done through the Connectors link in the channel’s […] menu and it has to be done once. The important thing is to copy and store the URI created for the connector as you need that to post to the channel.

  4. Update PowerShell Script for Microsoft 365 Roadmap Part
    # User defined variables
    $Roadmap = 'https://www.microsoft.com/en-us/microsoft-365/RoadmapFeatureRSS'
    $IPandURLS = 'https://support.office.com/en-us/o365ip/rss'
    $Hours = '24'
    $Now = Get-Date
    
    # Teams webhooks for Microsoft 365 Roadmap add to top of script
    $M365RInfra = "Your Webhook URI"
    $M365RExchange = "Your Webhook URI"
    $M365RTeamsSkype = "Your Webhook URI"
    $M365RSPOOF = "Your Webhook URI"
    $M365RYammer = "Your Webhook URI"
    $M365RIntune = "Your Webhook URI"
    $M365RSecurity = "Your Webhook URI"
    $M365RNextGenApps = "Your Webhook URI"
    $M365ROffice = "Your Webhook URI"
    $M365RCompliance = "Your Webhook URI"
    
    # Request data
    $messages = (Invoke-RestMethod -Uri $Roadmap -Headers $headerParams -Method Get)
    
    # Function for creating object with all categories
    Function Add-Category {
        New-Object -TypeName PSCustomObject -Property @{
            Category = $Null
        }
    }
    
    # Declaration of variable that collects all categories
    $AllCategories = @()
    
    # Parse data
    ForEach ($msg in $messages) {
    
        # Add updates posted last 24 hours                
        If (($Now - [datetime]$msg.pubDate).TotalHours -le $Hours) {
                    
            # Count, join and prepare category for use in the card
            $categoryno = $msg.category.Count
            $category = $msg.category[1..$categoryno] -join ", "
                    
            # Convert MessageText to JSON beforehand, if not the payload will fail.
            $Message = ConvertTo-Json $msg.description
    
            #Set the color line of the card according to the Status of the environment
            if ($msg.category.Contains("In development")) {
                $color = "ff0000"
            }
          
            elseif ($msg.category.Contains("Rolling out")) {
                $color = "ffff00"
            }
            else {
                $color = "00cc00"
            }
        
        # Generate payload(s)          
        $Payload = @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "More info",
                "targets": [
                    {
                        "os": "default",
                        "uri": "$($msg.Link)"
                    }
                ]
            },
         ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Status:",
                        "value": "$($msg.category[0])"
                    },
                    {
                        "name": "Category:",
                        "value": "$($category)"
                    }
                ],
                "text": $($message)
            }
        ],
        "summary": "$($msg.Title)",
        "themeColor": "$($color)",
        "title": "Feature ID: $($msg.guid.'#text') - $($msg.Title)"
    }
    "@
        
       # Determine which channel the post belongs to
       if ($category -match "exchange"){$uri=$M365RExchange}
       elseif ($category -match "Lync" -or $category -match "Skype" -or $category -match "teams"){$uri=$M365RTeamsSkype}
       elseif ($category -match "sharepoint" -or $category -match "onedrive"){$uri=$M365RSPOOF}
       elseif ($category -match "Intune"){$uri=$M365RIntune}
       elseif ($category -match "Yammer"){$uri=$M365RYammer}
       elseif ($category -match "ATP" -or $category -match "Cloud App Secruity"){$uri=$M365RSecurity}
       elseif ($category -match "Planner" -or $category -match "Stream" -or $category -match "Forms" -or $category -match "Bookings" -or $category -match "Whiteboard"){$uri=$M365RNextGenApps}
       elseif ($category -match "Office" -or $category -match "Outlook" -or $category -match "PowerPoint" -or $category -match "To-Do" -or $category -match "Visio" -or $category -match "OneNote" -or $category -match "Excel" -or $category -match "Project" -or $category -match "Access" -or $category -match "Word"){$uri=$M365ROffice}
       elseif ($category -match "Compliance" -or $category -match "Information Protection" ){$uri=$M365RCompliance}
       else {$uri=$M365RInfra}
            
       # If any new posts, add to Teams
       Invoke-RestMethod -uri $uri -Method Post -body $Payload -ContentType 'application/json; charset=utf-8'
        }
    }
  5. Create AppID in Azure AD
    • Register an Application in your organizational directory
    • Request a view permission for Office 365 Management APIs: ServiceHealth.Read
    • Create new client secret: copy the secret to a secure place, as this is the last time you are able to. If you forget, you need to generate a new secret.

  6. Update PowerShell Script for Microsoft 365 Message Center Part
    # User defined variables
    $ApplicationID = 'Application ID'
    $ApplicationKey = 'Application Secret Key'
    $TenantDomain = 'Your FQDN' # Alternatively use DirectoryID if tenant domain fails
    $Now = Get-Date
    $Hours = '1'    
    $color = '0377fc'
    
    # Teams webhooks for Message Center add to top of script
    $MCInfra = "Your Webhook URI"
    $MCExchange = "Your Webhook URI"
    $MCTeamsSkype = "Your Webhook URI"
    $MCSPOOFB = "Your Webhook URI"
    $MCYammer = "Your Webhook URI"
    $MCIntune = "Your Webhook URI"
    
    # Request data
        $body = @{
            grant_type="client_credentials";
            resource="https://manage.office.com";
            client_id=$ApplicationID;
            client_secret=$ApplicationKey;
            earliest_time="-$($Hours)h@s"}
    
        $oauth = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$($tenantdomain)/oauth2/token?api-version=1.0" -Body $body
        $headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
        $messages = (Invoke-RestMethod -Uri "https://manage.office.com/api/v1.0/$($tenantdomain)/ServiceComms/Messages" -Headers $headerParams -Method Get)
        $incidents = $messages.Value | Where-Object {$_.MessageType -eq 'MessageCenter'}
    
    # Parse data
    ForEach ($inc in $incidents){
                                 
    	# Add updates posted last 24 hours
    	If (($Now - [datetime]$inc.LastUpdatedTime).TotalHours -le $Hours) {
    
    	# Convert MessageText to JSON beforehand, if not the payload will fail.
    	$Message = ConvertTo-Json $inc.Messages.MessageText
    
    # Generate payload(s)
    $Payload =  @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "More info",
                "targets": [
                    {
                        "os": "default",
                        "uri": "$($inc.ExternalLink)"
                    }
                ]
            },
            {
                "@type": "OpenUri",
                "name": "Blog link",
                "targets": [
                    {
                        "os": "default",
                        "uri": "$($inc.BlogLink)"
                    }
                ]
            },
            {
                "@type": "OpenUri",
                "name": "Help link",
                "targets": [
                    {
                        "os": "default",
                        "uri": "$($inc.HelpLink)"
                    }
                ]
            }        
        ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Service:",
                        "value": "$($inc.AffectedWorkloadDisplayNames)"
                    },
                    {
                        "name": "Action Type:",
                        "value": "$($inc.ActionType)"
                    },
                    {
                        "name": "Classification:",
                        "value": "$($inc.Classification)"
                    }
                ],
                "text": $($Message)
            }
        ],
        "summary": "$($Inc.Title)",
        "themeColor": "$($color)",
        "title": "$($Inc.Id) - $($Inc.Title)"
    }
    "@
    
    # Determine which channel the post belongs to
    if ($inc.AffectedWorkloadDisplayNames -match "exchange"){$uri=$MCExchange}
    elseif ($inc.AffectedWorkloadDisplayNames -match "Skype" -or $inc.AffectedWorkloadDisplayNames -match "teams"){$uri=$MCTeamsSkype}
    elseif ($inc.AffectedWorkloadDisplayNames -match "sharepoint" -or $inc.AffectedWorkloadDisplayNames -match "onedrive"){$uri=$MCSPOOFB}
    elseif ($inc.AffectedWorkloadDisplayNames -match "Intune"){$uri=$MCIntune}
    elseif ($inc.AffectedWorkloadDisplayNames -match "Yammer"){$uri=$MCYammer}
    else {$uri=$MCInfra}
    
    $inc.AffectedWorkloadDisplayNames
    
    # If any new posts, add to Teams
    Invoke-RestMethod -uri $uri -Method Post -body $Payload -ContentType 'application/json; charset=utf-8'
      }
    }
  7. Update PowerShell Script for Microsoft 365 Health Status Part
    # User defined variables
    $ApplicationID = '13b315f7-334e-4d83-ae3d-a635d05a9064'
    $ApplicationKey = 'iH6bf5YQakBDNDe94qCsDNy=/A[.4XQM'
    $TenantDomain = '48b60871-5f61-4b52-ab7b-cfbcf6137f61' # Alternatively use DirectoryID if tenant domain fails
    $Now = Get-Date
    $Minutes = '60'
    
    # Teams webhooks for Health Status add to top of script
    $HCInfra = "Your Webhook URI"
    $HCExchange = "Your Webhook URI"
    $HCTeamsSkype = "Your Webhook URI"
    $HCSPOOF = "Your Webhook URI"
    $HCYammer = "Your Webhook URI"
    $HCIntune = "Your Webhook URI"
    
    # Request data
    $body = @{
        grant_type="client_credentials";
        resource="https://manage.office.com";
        client_id=$ApplicationID;
        client_secret=$ApplicationKey;
        earliest_time="-$($Minutes)m@s"}
    
    $oauth = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$($tenantdomain)/oauth2/token?api-version=1.0" -Body $body
    $headerParams = @{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
    $messages = (Invoke-RestMethod -Uri "https://manage.office.com/api/v1.0/$($tenantdomain)/ServiceComms/Messages" -Headers $headerParams -Method Get)
    $incidents = $messages.Value | Where-Object {$_.MessageType -eq 'Incident'}
    
    # Parse data
    ForEach ($inc in $incidents){
                    
                    # Add updates posted last $Minutes
                    If (($Now - [datetime]$inc.LastUpdatedTime).TotalMinutes -le $Minutes) {
                    
                    # Set the color line of the card according to the Classification of the event, or if it has ended
                    if ($inc.Classification -eq "Incident" -and $inc.EndTime -eq $null)
                    {
                    $color = "ff0000" # Red
                    }
                    else
                        {
                        if ($inc.EndTime -ne $null)
                            {
                            $color = "00cc00" # Green
                            }
                            else
                                {
                                $color = "ffff00" # Yellow
                                }
                            }
    
    # Pick latest message in the message index and convert the text to JSON before generating payload (if not it will fail).
    $Message = $inc.Messages.MessageText[$inc.Messages.Count-1] | ConvertTo-Json
      
    # Generate payload(s)
    $Payload =  @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "Post INC document",
                "targets": [
                    {
                        "os": "default",
                        "uri": "$($inc.PostIncidentDocumentUrl)"
                    }
                ]
            },           
        ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Service:",
                        "value": "$($inc.WorkloadDisplayName)"
                    },
                    {
                        "name": "Status:",
                        "value": "$($inc.Status)"
                    },
                    {
                        "name": "Severity:",
                        "value": "$($inc.Severity)"
                    },
                    {
                        "name": "Classification:",
                        "value": "$($inc.Classification)"
                    }
                ],
                "text": $($Message)
            }
        ],
        "summary": "$($Inc.Title)",
        "themeColor": "$($color)",
        "title": "$($Inc.Id) - $($Inc.Title)"
    }
    "@
    
    #Determine which channel the post belongs to
    
    if ($inc.Workload -match "exchange"){$uri=$HCExchange}
    elseif ($inc.Workload -match "Lync" -or $inc.Workload -match "Skype" -or $inc.Workload -match "teams"){$uri=$HCTeamsSkype}
    elseif ($inc.Workload -match "sharepoint" -or $inc.Workload -match "onedrive"){$uri=$HCSPOOF}
    elseif ($inc.Workload -match "Intune"){$uri=$HCIntune}
    elseif ($inc.Workload -match "Yammer"){$uri=$HCYammer}
    else {$uri=$HCInfra}
    
    $inc.Workload
    
    # If any new posts, add to Teams
    Invoke-RestMethod -uri $uri -Method Post -body $Payload -ContentType 'application/json; charset=utf-8'
        }
      }
  8. Update PowerShell Script for Office ProPlus Channel Update Part
    # User defined variables
    # ----------------------
    # If you want to check Monthly Channel, Semi-Annual Channel Targeted (SACT) and/or Semi-Annual Channel, add your Teams URI in the variables fields. 
    # Leave the ones you don't want to check blank.
    
    # Get Microsoft Office ProPlus channel updates and post to Teams using webhooks
    $MonthlyURI = 'Your Webhook URI'
    $SACTURI = 'Your Webhook URI'
    $SACURI = 'Your Webhook URI'
    $Hours = '24'
    $Color = '00ff00' # Green
    
    # Setting other script variables
    $Now = Get-Date -Format 'yyyy-MM-dd HH:mm'
    $Year = Get-Date -Format yyyy
    $Monthly = 'https://docs.microsoft.com/en-us/officeupdates/monthly-enterprise-channel'
    $SAC = 'https://docs.microsoft.com/en-us/officeupdates/semi-annual-enterprise-channel'
    $SACT = 'https://docs.microsoft.com/en-us/officeupdates/semi-annual-enterprise-channel-preview'
    
    # Monthly channel
    # ---------------
    
    If ($MonthlyURI) {
    # Get data
    $Monthlyweb = Invoke-RestMethod -Uri $Monthly
    
    # Find article's last updated time
    $monthlydatepattern = '\d{4}-\d{2}-\d{2} \d{2}:\d{2} [AP]M'
    $monthlyLastUpdated = $monthlyweb | select-string  -Pattern $monthlydatepattern -AllMatches | % { $_.Matches } | % { $_.Value }
    
    # Convert match into Date/Time
    $monthlyDate = Get-Date $monthlyLastUpdated
    
    # Check if updates are newer than time variable, if so, do stuff
                                 
    	# Calculate time difference
    	If (([datetime]$Now - [datetime]$monthlyDate).TotalHours -le $Hours) {
    
        # Picking out title
        $monthlytitlepattern = '(?<=\<h2 id="v.*?\>)(.*?)(?=<\/h2\>)'
        $monthlytitle = $Monthlyweb | select-string  -Pattern $monthlytitlepattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
    
        # Tailor the "More info" button by adding suffix to link to right section of the webpage
        $monthlylinkpattern = '(?<=\<h2.*?\")(.*?)(?=\"\>)'
        $monthlylink = $Monthlyweb | Select-String -Pattern $monthlylinkpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -Index 1
    
        # Select latest updates
        $monthlycontentpattern = '(\<h2 id="v.+?\>)(.|\n)*?(?=(\<h2 id="v.+?\>|<div class.+?\>))'
        $monthlyupdate = $Monthlyweb | select-string  -Pattern $monthlycontentpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
        $monthlycontent = $monthlyupdate | ConvertTo-Json
    
    # Generate payload
              
    $MonthlyPayload =  @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "More info",
                "targets": [
                    {
                        "os": "default",
                        "uri": "https://docs.microsoft.com/en-us/officeupdates/monthly-channel-$($Year)#$($monthlylink)"
                    }
                ]
            },
         ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Version updated:",
                        "value": "$($monthlyDate)"
                    }
                    
                ],
                "text": $monthlycontent
            }
        ],
        "summary": "O365 ProPlus Monthly",
        "themeColor": "$($color)",
        "title": "Monthly Channel release: $($monthlytitle)"
    }
    "@
    
    # If any new posts, add to Teams
    Invoke-RestMethod -uri $MonthlyURI -Method Post -body $MonthlyPayload -ContentType 'application/json; charset=utf-8'
    }
    Else {
         }
    }
    
    # Semi-annual channel targeted (SACT)
    # -----------------------------------
    
    If ($SACTURI) {
    # Get data
    $SACTweb = Invoke-RestMethod -Uri $SACT
    
    # Find article's last updated time
    $sactdatepattern = '\d{4}-\d{2}-\d{2} \d{2}:\d{2} [AP]M'
    $sactLastUpdated = $SACTweb | select-string  -Pattern $sactdatepattern -AllMatches | % { $_.Matches } | % { $_.Value }
    
    # Convert match into Date/Time
    $SACTDate = Get-Date $sactLastUpdated
    
    # Check if updates are newer than time variable, if so, do stuff
                                 
    	# Calculate time difference
    	If (([datetime]$Now - [datetime]$SACTDate).TotalHours -le $Hours) {
    
        # Picking out title
        $sacttitlepattern = '(?<=\<h2 id="v.*?\>)(.*?)(?=<\/h2\>)'
        $sacttitle = $sactweb | select-string  -Pattern $sacttitlepattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
    
        # Tailor the "More info" button by adding suffix to link to right section of the webpage
        $sactlinkpattern = '(?<=\<h2.*?\")(.*?)(?=\"\>)'
        $sactlink = $sactweb | Select-String -Pattern $sactlinkpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -Index 1
    
        # Select latest updates
        $sactcontentpattern = '(\<h2 id="v.+?\>)(.|\n)*?(?=(\<h2 id="v.+?\>|<div class.+?\>))'
        $sactupdate = $sactweb | select-string  -Pattern $sactcontentpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
        $sactcontent = $sactupdate | ConvertTo-Json
    
    # Generate payload
              
    $SACTPayload =  @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "More info",
                "targets": [
                    {
                        "os": "default",
                        "uri": "https://docs.microsoft.com/en-us/officeupdates/semi-annual-channel-targeted-$($Year)#$($sactlink)"
                    }
                ]
            },
         ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Version updated:",
                        "value": "$($SACTDate)"
                    }
                    
                ],
                "text": $sactcontent
            }
        ],
        "summary": "O365 ProPlus SACT",
        "themeColor": "$($color)",
        "title": "Semi-Annual Channel (targeted) release: $($sacttitle)"
    }
    "@
    
    # If any new posts, add to Teams
    Invoke-RestMethod -uri $SACTURI -Method Post -body $SACTPayload -ContentType 'application/json; charset=utf-8'
    }
    Else {
         }
    }
    
    # Semi-annual channel (SAC)
    # -------------------------
    
    If ($SACURI) {
    #Get data
    $SACweb = Invoke-RestMethod -Uri $SAC
    
    # Find article's last updated time
    $sacdatepattern = '\d{4}-\d{2}-\d{2} \d{2}:\d{2} [AP]M'
    $sacLastUpdated = $SACweb | select-string  -Pattern $sacdatepattern -AllMatches | % { $_.Matches } | % { $_.Value }
    
    # Convert match into Date/Time
    $SACDate = Get-Date $sacLastUpdated
    
    # Check if updates are newer than time variable, if so, do stuff
                                 
    	# Calculate time difference
    	If (([datetime]$Now - [datetime]$SACDate).TotalHours -le $Hours) {
    
        # Picking out title
        $sactitlepattern = '(?<=\<h2 id="v.*?\>)(.*?)(?=<\/h2\>)'
        $sactitle = $SACweb | select-string  -Pattern $sactitlepattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
    
        # Tailor the "More info" button by adding suffix to link to right section of the webpage
        $saclinkpattern = '(?<=\<h2.*?\")(.*?)(?=\"\>)'
        $saclink = $SACweb | Select-String -Pattern $saclinkpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -Index 1
    
        # Select latest updates
        $saccontentpattern = '(\<h2 id="v.+?\>)(.|\n)*?(?=(\<h2 id="v.+?\>|<div class.+?\>))'
        $sacupdate = $SACweb | select-string  -Pattern $saccontentpattern -AllMatches | % { $_.Matches } | % { $_.Value } | Select-Object -First 1
        $saccontent = $sacupdate | ConvertTo-Json
    
    #Generate payload
              
    $SACPayload =  @"
    {
        "@context": "https://schema.org/extensions",
        "@type": "MessageCard",
        "potentialAction": [
                {
                "@type": "OpenUri",
                "name": "More info",
                "targets": [
                    {
                        "os": "default",
                        "uri": "https://docs.microsoft.com/en-us/officeupdates/semi-annual-channel-$($Year)#$($saclink)"
                    }
                ]
            },
         ],
        "sections": [
            {
                "facts": [
                    {
                        "name": "Version updated:",
                        "value": "$($SACDate)"
                    }
                    
                ],
                "text": $saccontent
            }
        ],
        "summary": "O365 ProPlus SAC",
        "themeColor": "$($color)",
        "title": "Semi-Annual Channel release: $($sactitle)"
    }
    "@
    
    # If any new posts, add to Teams
    Invoke-RestMethod -uri $SACURI -Method Post -body $SACPayload -ContentType 'application/json; charset=utf-8'
    }
    Else {
         }
    }
  9. Test
    Do a dry run to see of the script works as intended with your own variables. If you see no incidents, message center items posted, it could be because of the time interval. Try to expand the number of minutes to see if you get any hits.

    Once your happy, revert the interval and you’re ready to go.

  10. Add Azure Automation Variables
    For ApplicationID and Application Secret Key, but even for the Domain Name, use encrypted Azure Automation variables.

  11. Create Runbooks
    To schedule the script, there are several ways. I prefer an Azure Automation Runbook
    Use here for each PowerShell scripts the before created variables:
    # Read the values from Azure Automation variables and add to script variables
    $Tenantdomain = Get-AutomationVariable -Name 'AzVariableTenantDomainName'
    $ApplicationID = Get-AutomationVariable -Name 'AzVariableApplicationIDName'
    $ApplicationKey = Get-AutomationVariable -Name 'AzVariableApplicationKeyName'
  12. Schedule it
    As the runbooks can only be run once per hour, you can either create 4 schedules, or set up another Azure Logic Apps.

Manage RSS feeds via Microsoft Power Automate

If you are still not faced with Power Automate (formerly Flow), let’s have a look here and try it for free. It’s too easy do not using.

I used for my purpose the following flow template, for posting a message on Teams when a RSS feed is published. Below you can see an example on of this flow:

For these feeds, a new channel has been created separately. One disadvantage for this solution, the polling interval of the RSS trigger is only 1800 seconds.

Other Solution(s)

You can configure directly a RSS connector to your Teams channel. But then you can only one RSS feed option to post a new message here. Even if the digest frequency could be 15 or 30 Minutes, it’s not my best option. I want all of my RSS feeds post in the same Teams channel.

To have tweets into the Teams channels, we could configure a Twitter connector or a Power Automate flow too. I still keep my favorite tweeters in my Twitter app. 🙂

Summary

Now I am getting the pre-defined Microsoft 365 information and updates every morning via Azure Automation to my morning coffee. Plus the new RSS feeds too. I only have to open my Teams app on my devices! I am happy for this kind of consolidation.

Leave a Reply

Your email address will not be published.