PowerShell

Sending Messages to Microsoft Teams from PowerShell just got easier and better

Christmas time is upon us, and I've decided that my PSTeams module needs some love. I wrote it in late 2018 and updated it a few times at the beginning of 2019. This release hopefully is worth of having 1.0 version number. I don't do that often and usually go for build numbers changes only, but Microsoft Teams message cards have their limits on functionality. Therefore, there are not many things that can be added unless Microsoft opens up and gives us all the cool features of Adaptive Cards. PSTeams module uses Webconnector to send messages to Teams. That method only supports Message Cards, which even Microsoft calls Legacy. But legacy doesn't mean it isn't fully functional. It also has some cool features of their own. If you're new to PSTeams you may want to read those 2 posts below to get information how to set it up.

PSTeams - Old way of sending messages to Microsoft Teams

In old PSTeams sending notifications to Teams was pretty straightforward and not very complicated.

$TeamsID = ''

Send-TeamsMessage -URI $TeamsID -MessageText "This text will show up"

With proper TeamsID url you can just use one parameter and send any text to Microsoft Teams Channel. This text also accepts limited markdown so you can use bold, italics, underline just like you do in Markdown.

You can extend this code by adding MessageTitle or Color.

$TeamsID = ''
Send-TeamsMessage -URI $TeamsID -MessageTitle 'PSTeams - Pester Test' -MessageText "This text will show up" -Color DodgerBlue

While the above code is straightforward, you can send complicated messages to teams and fulfill your goal.

$TeamsID = 'YourCodeGoesHere'
$Button1 = New-TeamsButton -Name 'Visit English Evotec Website' -Link "https://evotec.xyz"
$Fact1 = New-TeamsFact -Name 'PS Version' -Value "**$($PSVersionTable.PSVersion)**"
$Fact2 = New-TeamsFact -Name 'PS Edition' -Value "**$($PSVersionTable.PSEdition)**"
$Fact3 = New-TeamsFact -Name 'OS' -Value "**$($PSVersionTable.OS)**"
$CurrentDate = Get-Date
$Section = New-TeamsSection `
    -ActivityTitle "**PSTeams**" `
    -ActivitySubtitle "@PSTeams - $CurrentDate" `
    -ActivityImage Add `
    -ActivityText "This message proves PSTeams Pester test passed properly." `
    -Buttons $Button1 `
    -ActivityDetails $Fact1, $Fact2, $Fact3
Send-TeamsMessage `
    -URI $TeamsID `
    -MessageTitle 'PSTeams - Pester Test' `
    -MessageText "This text will show up" `
    -Color DodgerBlue `
    -Sections $Section

What happens above is that to send a more advanced message, we needed to build a Section. The section can have many Facts, multiple Buttons and it's own ActivityTitle, ActivitySubtitle, and so on. Nothing complicated, but at the same time you would first need to define variables that are then reused within Section, which further is used by Send-TeamsMessage.

PSTeams - New way / Christmas Edition

This weekend I've decided to add some new syntax and some more functionality. For some time, I've been working on PSWriteHTML module which allowed me to learn some new things and at the same time fell in love with the syntax it uses. Therefore today PSTeams gets it's very own DSL syntax.

$TeamsID = ''
Send-TeamsMessage -Verbose {
    New-TeamsSection -ActivityTitle "**Elon Musk**" -ActivitySubtitle "@elonmusk - 9/12/2016 at 5:33pm" -ActivityImageLink "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg" -ActivityText "Climate change explained in comic book form by xkcd xkcd.com/1732"
    New-TeamsSection -ActivityTitle "**Mark Knopfler**" -ActivitySubtitle "@MarkKnopfler - 9/12/2016 at 1:12pm" -ActivityImageLink "https://pbs.twimg.com/profile_images/1042367841117384704/YvrqQiBK_400x400.jpg" -ActivityText "Mark Knopfler features on B.B King's all-star album of Blues greats, released on this day in 2005..."
    New-TeamsSection -ActivityTitle "**Elon Musk**" -ActivitySubtitle "@elonmusk - 9/12/2016 at 5:33pm" -ActivityImageLink "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg" -ActivityText "Climate change explained in comic book form by xkcd xkcd.com/1732"
} -Uri $TeamsID -Color DarkSeaGreen -MessageSummary 'Tweet'

I know the code isn't pretty and hard to read, but it delivers a full message in just four lines of code. Code above generates the following JSON, which is then sent to Microsoft Teams.

Body {
    "sections":  [
                     {
                         "activityTitle":  "**Elon Musk**",
                         "activitySubtitle":  "@elonmusk - 9/12/2016 at 5:33pm",
                         "activityImage":  "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg",
                         "activityText":  "Climate change explained in comic book form by xkcd xkcd.com/1732"
                     },
                     {
                         "activityTitle":  "**Mark Knopfler**",
                         "activitySubtitle":  "@MarkKnopfler - 9/12/2016 at 1:12pm",
                         "activityImage":  "https://pbs.twimg.com/profile_images/1042367841117384704/YvrqQiBK_400x400.jpg",
                         "activityText":  "Mark Knopfler features on B.B King\u0027s all-star album of Blues greats, released on this day in 2005..."
                     },
                     {
                         "activityTitle":  "**Elon Musk**",
                         "activitySubtitle":  "@elonmusk - 9/12/2016 at 5:33pm",
                         "activityImage":  "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg",
                         "activityText":  "Climate change explained in comic book form by xkcd xkcd.com/1732"
                     }
                 ],
    "themeColor":  "#8fbc8f",
    "summary":  "Tweet"
}

The code above allows you to define one or more sections within Send-TeamsMessage. You can set for each section its Title, Subtitle, Image Link, or Image Text. All that is fully translated to JSON and displayed nicely in Microsoft Teams. You can also use Splatting if you like, for readability purposes.

Send-TeamsMessage -Verbose {
    $Splat1 = @{
        ActivityTitle     = "**Elon Musk**"
        ActivitySubtitle  = "@elonmusk - 9/12/2016 at 5:33pm"
        ActivityImageLink = "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg"
        ActivityText      = "Climate change explained in comic book form by xkcd xkcd.com/1732"

    }
    New-TeamsSection @Splat1
    $Splat2 = @{
        ActivityTitle     = "**Mark Knopfler**"
        ActivitySubtitle  = "@MarkKnopfler - 9/12/2016 at 1:12pm"
        ActivityImageLink = "https://pbs.twimg.com/profile_images/1042367841117384704/YvrqQiBK_400x400.jpg"
        ActivityText      = "Mark Knopfler features on B.B King's all-star album of Blues greats, released on this day in 2005..."
    }
    New-TeamsSection @Splat2
    New-TeamsSection @Splat1
} -Uri $TeamsID -Color DarkSeaGreen -MessageSummary 'Tweet'

Or, my favourite way

Send-TeamsMessage -Verbose {
    New-TeamsSection {
        ActivityTitle -Title "**Elon Musk**"
        ActivitySubtitle -Subtitle "@elonmusk - 9/12/2016 at 5:33pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg"
        ActivityText -Text "Climate change explained in comic book form by xkcd xkcd.com/1732"
    }
    New-TeamsSection {
        ActivityTitle -Title "**Mark Knopfler**"
        ActivitySubtitle -Subtitle "@MarkKnopfler - 9/12/2016 at 1:12pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/1042367841117384704/YvrqQiBK_400x400.jpg"
        ActivityText -Text "Mark Knopfler features on B.B King's all-star album of Blues greats, released on this day in 2005..."
    }
    New-TeamsSection {
        ActivityTitle -Title "**Elon Musk**"
        ActivitySubtitle -Subtitle "@elonmusk - 9/12/2016 at 5:33pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg"
        ActivityText -Text "Climate change explained in comic book form by xkcd xkcd.com/1732"
    }
} -Uri $TeamsID -Color DarkSeaGreen -MessageSummary 'Tweet'

Whether you prefer to use oneliner, splatting, or DSL style all the way – it's all there.

PSTeams - More features

Let's see something more advanced, shall we?

$TeamsID = 'https://outlook.office.com/webhook/a5c7c'

Send-TeamsMessage -Verbose {
    New-TeamsSection -ActivityTitle "**Elon Musk**" -ActivitySubtitle "@elonmusk - 9/12/2016 at 5:33pm" -ActivityImageLink "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg" -ActivityText "Climate change explained in comic book form by xkcd xkcd.com/1732" {
        New-TeamsButton -Name 'View Link' -Link 'https://evotec.xyz' -Type 'ViewAction'
        #New-TeamsButton -Name 'Input Text' -Type 'TextInput' -Link 'https://evotec.xyz'
        New-TeamsButton -Type OpenURI -Name 'Open Link' -Link 'https://evotec.xyz'
        New-TeamsButton -Type TextInput -Name 'Leave a Comment' -Link 'https://evotec.xyz'
        New-TeamsButton -Type DateInput -Name 'Choose a Date' -Link 'https://evotec.xyz'
        New-TeamsButton -Type HttpPost -Name 'Post Link' -Link 'https://evotec.xyz'
        New-TeamsButton -Type OpenURI -Name 'Open Link' -Link 'https://evotec.xyz'
        New-TeamsImage -Link "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg"
        New-TeamsImage -Link "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg"
        New-TeamsImage -Link "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg"
        New-TeamsBigImage -Link "https://evotec.pl/wp-content/uploads//2015/05/Logo-evotec-012.png"
        New-TeamsBigImage -Link "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg"
    } -Text 'This is long text that will be added below Big Images. If there would be no big image... it would be just this text.'
    New-TeamsSection {
        ActivityTitle -Title "**Elon Musk**"
        ActivitySubtitle -Subtitle "@elonmusk - 9/12/2016 at 5:33pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg"
        ActivityText -Text "Climate change explained in comic book form by xkcd xkcd.com/1732"
    }
    New-TeamsSection {
        ActivityTitle -Title "**Mark Knopfler**"
        ActivitySubtitle -Subtitle "@MarkKnopfler - 9/12/2016 at 1:12pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/1042367841117384704/YvrqQiBK_400x400.jpg"
        ActivityText -Text "Mark Knopfler features on B.B King's all-star album of Blues greats, released on this day in 2005..."
    }
    New-TeamsSection {
        ActivityTitle -Title "**Elon Musk**"
        ActivitySubtitle -Subtitle "@elonmusk - 9/12/2016 at 5:33pm"
        ActivityImageLink -Link "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0.jpg"
        ActivityText -Text "Climate change explained in comic book form by xkcd xkcd.com/1732"
    }
} -Uri $TeamsID -Color DarkSeaGreen -MessageSummary 'Tweet'

What you see above is a mix of sections we used before, but also a new section with multiple different button types, image gallery, and some additional text. Most of that (except images and few button types) was already available in the old module. Still, with new syntax, everything is more readable (my personal opinion, of course) and more comfortable to understand what goes where.

One thing to know here is that while there are different types of buttons, the usage of the link that example shows are wrong. You can't use links as I did for HttpPost, DateInput, or TextInput types because there's nowhere to send that information. You need to use proper links for it to work (or at least I think so – it should be possible to connect it with Microsoft Flow from what I've read). Another issue is that it seems when there are multiple TextInput buttons, it doesn't work correctly on iOS (not sure about Android). The message is merely empty. Not sure why that happens, seems like a bug to me – hopefully it gets fixed at some point.

PSTeams - Another advanced example

I also thought that using lists was a bit too cumbersome in the old version. You had to use markdown to achieve lists. I decided to simplify it a bit. Let's see how that works, shall we?

Send-TeamsMessage -Verbose -Color DimGray {
    New-TeamsSection -Title 'This is 1st section within 1 message' {
        New-TeamsFact -Name 'Bold' -Value '**Special GPO**'
        New-TeamsFact -Name 'Italic and Bold' -Value '***Other values***'
        New-TeamsFact -Name 'Italic' -Value '*2010-10-10*'
    }
    New-TeamsSection -Title 'This is 2nd section within 1 message' {
        New-TeamsFact -Name 'Bold' -Value '**Special GPO**'
        New-TeamsFact -Name 'Italic and Bold' -Value '***Other values***'
        New-TeamsFact -Name 'Italic' -Value '*2010-10-10*'
        New-TeamsFact -Name 'Link example' -Value "[Microsoft](https://www.microsoft.com)"
        New-TeamsFact -Name 'Other link example' -Value "[Evotec](https://evotec.xyz) and some **bold** text"
        New-TeamsList -Name 'Testing List' {
            New-TeamsListItem -Text 'Test 1' -Level 0 -Numbered
            New-TeamsListItem -Text 'Test 2' -Level 1 -Numbered
            New-TeamsListItem -Text 'Test 3' -Level 1 -Numbered
            New-TeamsListItem -Text 'Test 4' -Level 2 -Numbered
            New-TeamsListItem -Text 'Test 5' -Level 2 -Numbered
            New-TeamsListItem -Text 'Test 6' -Level 2 -Numbered
            New-TeamsListItem -Text 'Test 7' -Level 0 -Numbered
            New-TeamsListItem -Text 'Test 8' -Level 0 -Numbered
        }
        New-TeamsList -Name 'Testing List' {
            New-TeamsListItem -Text 'Test 1' -Level 0
            New-TeamsListItem -Text 'Test 2' -Level 1
            New-TeamsListItem -Text 'Test 3' -Level 1
            New-TeamsListItem -Text 'Test 4' -Level 2
            New-TeamsListItem -Text 'Test 5' -Level 2
            New-TeamsListItem -Text 'Test 6' -Level 2
            New-TeamsListItem -Text 'Test 7' -Level 0
            New-TeamsListItem -Text 'Test 8' -Level 0
        }
        New-TeamsList -Name 'Testing List' {
            New-TeamsListItem -Text 'First ordered list item' -Level 0
            New-TeamsListItem -Text 'Another item' -Level 1  -Numbered
            New-TeamsListItem -Text 'First ordered list item' -Level 2
            New-TeamsListItem -Text 'First ordered list item' -Level 3
            New-TeamsListItem -Text 'Unordered sub-list' -Level 0
            New-TeamsListItem -Text "Actual numbers don't matter, just that it's a number" -Level 0 -Numbered
            New-TeamsListItem -Text 'Ordered sub-list' -Level 1 -Numbered
            New-TeamsListItem -Text 'Another entry' -Level 1 -Numbered
            New-TeamsListItem -Text 'Another entry' -Level 0 -Numbered
            New-TeamsListItem -Text 'Very very long line that I want to show, hellow helow. Very very long line that I want to show, hellow helow. ' -Level 0 -Numbered
        }
    }
} -Uri $TeamsID -MessageSummary 'Test1' #-MessageText 'Test2' -MessageTitle 'Test1' #

PSTeams uses similar approach to what you've seen in PSWriteHTML. You can mix and match different types of lists however what I noticed that sometimes things don't start properly as they should. If you try to use Level 0 with and without Numbered within same list things will not work correctly.

Send-TeamsMessage -Verbose -Color DimGray {
    New-TeamsSection -Title 'This is 2nd section within 1 message' {
        New-TeamsList -Name 'Testing List' {
            New-TeamsListItem -Text 'First ordered list item' -Level 0
            New-TeamsListItem -Text 'Another item' -Level 0  -Numbered
        }
    }
} -Uri $TeamsID -MessageSummary 'Test1' #-MessageText 'Test2' -MessageTitle 'Test1' #

Body {
    "sections":  [
                     {
                         "title":  "This is 2nd section within 1 message",
                         "facts":  [
                                       {
                                           "name":  "Testing List",
                                           "value":  "- First ordered list item\r1. Another item"
                                       }
                                   ]
                     }
                 ],
    "themeColor":  "#696969",
    "summary":  "Test1"
}

As you can see, JSON is correctly built (at least I think so), but for some reason, nesting kicks in. Oh well, don't mix and match lists, and you should be safe, and maybe, sometime in the future, Microsoft will fix this issue.

PSTeams - Old code vs new code

Let's come back to the code we started with. In the old code you would do something like this

$TeamsID = 'YourCodeGoesHere'
$Button1 = New-TeamsButton -Name 'Visit English Evotec Website' -Link "https://evotec.xyz"
$Fact1 = New-TeamsFact -Name 'PS Version' -Value "**$($PSVersionTable.PSVersion)**"
$Fact2 = New-TeamsFact -Name 'PS Edition' -Value "**$($PSVersionTable.PSEdition)**"
$Fact3 = New-TeamsFact -Name 'OS' -Value "**$($PSVersionTable.OS)**"
$CurrentDate = Get-Date
$Section = New-TeamsSection `
    -ActivityTitle "**PSTeams**" `
    -ActivitySubtitle "@PSTeams - $CurrentDate" `
    -ActivityImage Add `
    -ActivityText "This message proves PSTeams Pester test passed properly." `
    -Buttons $Button1 `
    -ActivityDetails $Fact1, $Fact2, $Fact3
Send-TeamsMessage `
    -URI $TeamsID `
    -MessageTitle 'PSTeams - Pester Test' `
    -MessageText "This text will show up" `
    -Color DodgerBlue `
    -Sections $Section

In new approach your code can look like this

$TeamsID = ''
Send-TeamsMessage -URI $TeamsID -MessageTitle 'PSTeams - Pester Test' -MessageText "This text will show up" -Color DodgerBlue {
    New-TeamsSection {
        New-TeamsActivityTitle -Title "**PSTeams**"
        New-TeamsActivitySubtitle -Subtitle "@PSTeams - $CurrentDate"
        New-TeamsActivityImage -Image Add
        New-TeamsActivityText -Text "This message proves PSTeams Pester test passed properly."
        New-TeamsFact -Name 'PS Version' -Value "**$($PSVersionTable.PSVersion)**"
        New-TeamsFact -Name 'PS Edition' -Value "**$($PSVersionTable.PSEdition)**"
        New-TeamsFact -Name 'OS' -Value "**$($PSVersionTable.OS)**"
        New-TeamsButton -Name 'Visit English Evotec Website' -Link "https://evotec.xyz"
    }
}

Both deliver same output. Both should work in new version. It's up to you which one do you want to use.

PSTeams - Where to get it?

How do you install it? The easiest and most optimal way is to use PowerShellGallery.

Install-Module PSTeams
# Update-Module PSTeams

However, if you're into code – want to see how everything is done, you can use GitHub sources. Please keep in mind that the PowerShellGallery version is optimized and better for production use. If you see any issues, bugs, or features that are missing, please make sure to submit them on GitHub.

This post was last modified on January 5, 2020 13:47

Przemyslaw Klys

System Architect with over 14 years of experience in the IT field. Skilled, among others, in Active Directory, Microsoft Exchange and Office 365. Profoundly interested in PowerShell. Software geek.

Share
Published by
Przemyslaw Klys

Recent Posts

Active Directory Replication Summary to your Email or Microsoft Teams

Active Directory replication is a critical process that ensures the consistent and up-to-date state of…

2 weeks ago

Syncing Global Address List (GAL) to personal contacts and between Office 365 tenants with PowerShell

Hey there! Today, I wanted to introduce you to one of the small but excellent…

5 months ago

Active Directory Health Check using Microsoft Entra Connect Health Service

Active Directory (AD) is crucial in managing identities and resources within an organization. Ensuring its…

7 months ago

Seamless HTML Report Creation: Harness the Power of Markdown with PSWriteHTML PowerShell Module

In today's digital age, the ability to create compelling and informative HTML reports and documents…

8 months ago

How to Efficiently Remove Comments from Your PowerShell Script

As part of my daily development, I create lots of code that I subsequently comment…

9 months ago

Unlocking PowerShell Magic: Different Approach to Creating ‘Empty’ PSCustomObjects

Today I saw an article from Christian Ritter, "PowerShell: Creating an "empty" PSCustomObject" on X…

9 months ago