Office 365

Introducing PSTeams 2.0 – Support for Adaptive Cards, Hero Cards, List Cards and Thumbnail Cards

PSTeams PowerShell module has been on the market for a while now. It supports sending notifications to Microsoft Teams channels via Incoming WebHooks. You could send a pretty message to the team's channel with just a few lines of code. With PSTeams 2.0, support for Adaptive Cards, Hero Cards, List Cards, and Thumbnail Cards was added. In PSTeams 1.0, you could send a message to Teams Channel using the following code:

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 $Env:TEAMSPESTERID -Color DarkSeaGreen -MessageSummary 'Tweet'

This would give you, what is supposed to imitate “Twitter” look.

In PSTeams 1.0, there are multiple ways to build how you want your incoming messages to look, but it had their limits. I've shown other examples and use cases in my earlier blog posts, so please make sure to review them:

PSTeams 2.0 - What's new

Microsoft Teams supports multiple different types of cards. Those are:

  • Adaptive Card – Highly customizable card that can contain any combination of text, speech, images, buttons, and input fields.
  • Hero Card – Typically contains a single large image, one or more buttons, and a small amount of text.
  • List Card – A scrolling list of items.
  • Office 365 Connector Card – Flexible layout with multiple sections, fields, images, and actions.
  • Receipt Card – Provides a receipt to the user.
  • Sign in Card – Enables a bot to request that a user sign in.
  • Thumbnail Card – Typically contains a single thumbnail image, some short text, and one or more buttons.
  • Card Collections – Used to return multiple items in a single response

PSTeams 1.0 and earlier provided many options to play with; it mostly focused on Office 365 Connector Card, which is officially the only one supported type of card for Webhook Connector. All other cards are mostly for use within Bots in Teams, Messaging Extensions, or Bot Framework. For full cards reference, you can check out this Cards Reference – Teams | Microsoft Docs article. While not officially supported with a workaround, PSTeams 2.0 adds Hero Cards, Card Lists, and Thumbnail Cards. Those are similar but different variations of Office 365 Connector Card. Finally, we're adding support for Adaptive Cards, which is the most flexible and advanced card there is. Of course, I didn't want to break any existing functionality, so whatever worked in PSTeams 1.0 works in PSTeams 2.0. The only change is – now there are a bit more cmdlets to chose from. What's important is that officially all those mentioned types are not supported by Microsoft, and they state that in their documentation. They do work, even though it's clear from docs that they are not supposed to. I guess it's because some features are not available, as there is no backend. This means some features won't do anything when users try to interact with them.

Sending Hero Cards to Microsoft Teams via Incoming Webhook

Sending a Hero Card is pretty simple. You define a new Hero Card with New-HeroCard cmdlet, and within it, you define 1 image and buttons you would like to use.

New-HeroCard -Title 'Seattle Center Monorail' -SubTitle 'Seattle Center Monorail' -Text "The Seattle Center Monorail is an elevated train line between Seattle Center (near the Space Needle) and downtown Seattle. It was built for the 1962 World's Fair. Its original two trains, completed in 1961, are still in service." {
    New-HeroImage -Url 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Seattle_monorail01_2008-02-25.jpg/1024px-Seattle_monorail01_2008-02-25.jpg'
    New-HeroButton -Type openUrl -Title 'Official website' -Value 'https://www.seattlemonorail.com'
    New-HeroButton -Type openUrl -Title 'Wikipeda page' -Value 'https://www.seattlemonorail.com'
    New-HeroButton -Type imBack -Title 'Evotec page' -Value 'https://www.evotec.xyz'
} -Uri $IncomingWebHookLink

That's all there is to send the Hero Card, and I believe that's all there is to offer. As I didn't want to create separate New-HeroImage and New-HeroButton cmdlets, I've aliased them to other commands, so they do have additional options that may not work within Hero Cards. I think I'll change this in 2.1 to make sure only options that are supported will be available when using cmdlets for each card type.

Sending List Cards to Microsoft Teams via Incoming Webhook

Card Lists is another type available for use in Microsoft Teams, and just like Hero Card it's not officially supported by Microsoft. Sending Card Lists is as easy as using New-CardList with content such as New-CardListItem and New-CardListButton.

New-CardList {
    New-CardListItem -Type file -Title 'Report' -SubTitle 'teams > new > design' -TapType openUrl -TapValue "https://contoso.sharepoint.com/teams/new/Shared%20Documents/Report.xlsx" -TapAction editOnline
    New-CardListItem -Type resultItem -Title 'Report' -SubTitle 'teams > new > design' -TapType openUrl -TapValue "https://contoso.sharepoint.com/teams/new/Shared%20Documents/Report.xlsx"
    New-CardListItem -Type resultItem -Title 'Trello title' -SubTitle 'A Trello subtitle' -TapType openUrl -TapValue "http://trello.com" -Icon "https://cdn2.iconfinder.com/data/icons/social-icons-33/128/Trello-128.png"
    New-CardListItem -Type section -Title 'Manager'
    New-CardListItem -Type person -Title "John Doe" -SubTitle 'Manager' -TapType imBack -TapValue "JohnDoe@contoso.com" -TapAction whois
    New-CardListButton -Type openUrl -Title 'Show' -Value 'https://evotec.xyz'
} -Uri $Env:TEAMSPESTERID -Title 'Card Title'

As you can see above, multiple different types of items are used, and while they do display correctly in Microsoft Teams, actions under them are not really providing much value. They either display messages, as seen below, or they do not do any action.

I'm trying to say – you will need to test what works for you, what doesn't, and how to use those cards. You will see similar issues across all cards added in PSTeams 2.0. For now, I've decided to leave options that don't do anything now, but maybe if there will be enough feedback that it doesn't make sense, I'll remove them later on.

Sending Thumbnail Cards to Microsoft Teams via Incoming Webhook

Thumbnail Cards can be built with 3 elements. New-ThumbnailCard starts generating a new card, and within it, you can use New-ThumbnailImage or New-ThumbnailButton. Buttons have different types of actions, but those types are probably useless, and the most useful will be openUrl.

# Please notice that
# - Images are not supported in buttons, you can send them but it's not displayed
# - imBack action is not supported in buttons, you can send them but once you click it an notification message appears

New-ThumbnailCard -Title 'Bender' -SubTitle "tale of a robot who dared to love" -Text "Bender Bending Rodríguez is a main character in the animated television series Futurama. He was created by series creators Matt Groening and David X. Cohen, and is voiced by John DiMaggio" {
    New-ThumbnailImage -Url 'https://upload.wikimedia.org/wikipedia/en/a/a6/Bender_Rodriguez.png' -AlternateText "Bender Rodríguez"
    New-ThumbnailButton -Type imBack -Title 'Thumbs Up' -Value 'I like it' #-Image "http://moopz.com/assets_c/2012/06/emoji-thumbs-up-150-thumb-autox125-140616.jpg"
    New-ThumbnailButton -Type openUrl -Title 'Thumbs Down' -Value 'https://evotec.xyz'
    New-ThumbnailButton -Type openUrl -Title 'I feel luck' -Value 'https://www.bing.com/images/search?q=bender&qpvt=bender&qpvt=bender&qpvt=bender&FORM=IGRE'
} -Uri $Env:TEAMSPESTERID

Sending Adaptive Cards to Microsoft Teams via Incoming Webhook

Finally, we've arrived for what everyone has been waiting for. How to use Adaptive Cards in PSTeams 2.0. Those cards are the most advanced cards you can get in Microsoft Teams. They follow the very same process as other mentioned cards. You define them by starting up with New-AdaptiveCardAnd then you define any of the possible options. Those are:

  • New-AdaptiveActionSet – allows you to define one or more actions
  • New-AdaptiveAction – definition for an action
  • New-AdaptiveColumnSet – allows you to define containers for one or more columns. You need to use it to be able to add columns
  • New-AdaptiveColumn – adds a column
  • New-AdaptiveContainer – adds a container that can contain other Adaptive Types
  • New-AdaptiveFactSet – allows you to define one or more facts. You need to use it to be able to add Facts.
  • New-AdaptiveFact – adds a fact
  • New-AdaptiveImageSet – allows you to create a gallery of pictures. Contrary to other “Set” commands you don't have to use it for adding more images.
  • New-AdaptiveImage – allows you to add an image
  • New-AdaptiveMedia – this command acts the same way as other Set commands. Unfortunately playing media is not supported yet, so it's a bit useless.
  • New-AdaptiveMediaSource – this command should be used within New-AdaptiveMedia. Unfortunately playing media is not supported yet, so it's a bit useless.
  • New-AdaptiveRichTextBlock – adds the ability to add multiple texts that can be additionally formatted
  • New-AdaptiveTextBlock – adds ability to add a text

Ok, it doesn't sound like a lot, but contrary to other types, you can do all kinds of mixing of those cmdlets, providing you vast customization options. And if you consider that you can nest Adaptive Cards within Adaptive Cards, it opens a whole new area of options. For example, displaying some basic data, and when the user presses a button, more data is displayed.

New-AdaptiveCard -Uri $IncomingWebhook -VerticalContentAlignment center {
    New-AdaptiveTextBlock -Size ExtraLarge -Weight Bolder -Text 'Test' -Color Attention -HorizontalAlignment Center
    New-AdaptiveColumnSet {
        New-AdaptiveColumn {
            New-AdaptiveImage -BackgroundColor AlbescentWhite -Url 'https://devblogs.microsoft.com/powershell/wp-content/uploads/sites/30/2018/09/Powershell_256.png'
        }
        New-AdaptiveColumn {
            New-AdaptiveImage -Url "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg" -Size Small -Style person
        }
    }
} -Action {
    New-AdaptiveAction -Title 'Set due date' -Type Action.Submit
    New-AdaptiveAction -Title 'Comment' -Type Action.OpenUrl -ActionUrl 'https://evotec.xyz'
}

Not very impressive, but you get the idea, right?

New-AdaptiveCard -Uri $IncomingWebhook -VerticalContentAlignment center {
    New-AdaptiveTextBlock -Size ExtraLarge -Weight Bolder -Text 'Test' -Color Attention -HorizontalAlignment Center
    New-AdaptiveColumnSet {
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Size 'Medium' -Text 'Test Card Title 1' -Color Dark
            New-AdaptiveTextBlock -Size 'Medium' -Text 'Test Card Title 1' -Color Light
        }
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Size 'Medium' -Text 'Test Card Title 1' -Color Warning
            New-AdaptiveTextBlock -Size 'Medium' -Text 'Test Card Title 1' -Color Good
        }
    }
} -SelectAction Action.OpenUrl -SelectActionUrl 'https://evotec.xyz' -Verbose

Each command has IntelliSense available for parameters. This is because some of the choices Microsoft made are a bit weird. Such as defining Colors by what they represent Good, Warning, Dark, Light. Unfortunately, even that is not consistent; as with other commands, some parameters, even though they have the same name, provide different choices. I've tried to make sure it's exactly as Microsoft set it up, so I don't have to introduce breaking changes. Let's try something more advanced, shall we?

New-AdaptiveCard -Uri $IncomingWebhook -BackgroundUrl 'https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Background.jpg' {
    New-AdaptiveColumnSet {
        New-AdaptiveColumn -WidthInWeight 35 {
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png" -Size Stretch -AlternateText "Mostly cloudy weather"
        }
        New-AdaptiveColumn -WidthInWeight 65 {
            New-AdaptiveTextBlock -Text 'Tue, Nov 5, 2019' -Weight Bolder -Size Large
            New-AdaptiveTextBlock -Text '32 / 50' -Size Medium -Spacing None
            New-AdaptiveTextBlock -Text '31% chance of rain' -Spacing None
            New-AdaptiveTextBlock -Text 'Winds 4.4 mph SSE' -Spacing None
        }
    }
    New-AdaptiveColumnSet {
        New-AdaptiveColumn -WidthInWeight 20 {
            New-AdaptiveTextBlock -Text 'Wednesday' -HorizontalAlignment Center
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Drizzle-Square.png" -Size Auto -AlternateText "Drizzly weather"
            New-AdaptiveFactSet {
                New-AdaptiveFact -Title 'High' -Value '50'
                New-AdaptiveFact -Title 'Low' -Value '32'
            }
        } -SelectActionUrl 'https://www.evotec.xyz' -SelectActionTitle 'View Wednesday'
        New-AdaptiveColumn -WidthInWeight 20 {
            New-AdaptiveTextBlock -Text 'Thursday' -HorizontalAlignment Center
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png" -Size Auto -AlternateText "Mostly cloudy weather"
            New-AdaptiveFactSet {
                New-AdaptiveFact -Title 'High' -Value '50'
                New-AdaptiveFact -Title 'Low' -Value '32'
            }
        } -SelectActionUrl 'https://www.evotec.xyz' -SelectActionTitle 'View Thursday'
        New-AdaptiveColumn -WidthInWeight 20 {
            New-AdaptiveTextBlock -Text 'Friday' -HorizontalAlignment Center
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png" -Size Auto -AlternateText "Mostly cloudy weather"
            New-AdaptiveFactSet {
                New-AdaptiveFact -Title 'High' -Value '59'
                New-AdaptiveFact -Title 'Low' -Value '32'
            }
        } -SelectActionUrl 'https://www.evotec.xyz' -SelectActionTitle 'View Friday'
        New-AdaptiveColumn -WidthInWeight 20 {
            New-AdaptiveTextBlock -Text 'Saturday' -HorizontalAlignment Center
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png" -Size Auto -AlternateText "Mostly cloudy weather"
            New-AdaptiveFactSet {
                New-AdaptiveFact -Title 'High' -Value '50'
                New-AdaptiveFact -Title 'Low' -Value '32'
            }
        } -SelectActionUrl 'https://www.evotec.xyz' -SelectActionTitle 'View Saturday'
    }
} -Speak "Weather forecast for Monday is high of 62 and low of 42 degrees with a 20% chance of rainWinds will be 5 mph from the northeast" -Verbose

Cool right? I would never expect you could do this kind of setup within Teams. How about a bit less advanced weather widget?

New-AdaptiveCard -Uri $IncomingWebhook -VerticalContentAlignment center {
    New-AdaptiveTextBlock -Text 'Redmond, WA' -Size Large -Subtle
    New-AdaptiveTextBlock -Text 'Mon, Nov 4, 2019 6:21 PM' -Spacing None

    New-AdaptiveColumnSet {
        New-AdaptiveColumn {
            New-AdaptiveImage -Url "https://messagecardplayground.azurewebsites.net/assets/Mostly%20Cloudy-Square.png" -Size Small -AlternateText "Mostly cloudy weather"
        } -Width Auto
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Text '46' -Size ExtraLarge -Spacing None
        } -Width Auto
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Text "°F" -Weight Bolder -Spacing Small
        } -Width Stretch
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Text 'Hi 50' -HorizontalAlignment Left
            New-AdaptiveTextBlock -Text 'Lo 46' -HorizontalAlignment Left -Spacing None
        } -Width Stretch
    }
} -Speak "The forecast for Seattle January 20 is mostly clear with a High of 51 degrees and Low of 40 degrees" # -Verbose

Nice, right? Of course, I don't expect you're going to use incoming webhooks for weather information, but having that much level of customization to display data is great. Be it Active Directory, Office 365, or other types you have access with PowerShell – you can really make a nice view for whoever is watching Teams Channel. You can do similar thing for stock updates with few lines of code:

New-AdaptiveCard -Uri $IncomingLink -VerticalContentAlignment center {
    New-AdaptiveContainer {
        New-AdaptiveTextBlock -Text "Microsoft Corporation" -Size Medium -Wrap
        New-AdaptiveTextBlock -Text "Nasdaq Global Select: MSFT" -Subtle -Spacing None -Wrap
        New-AdaptiveTextBlock -Text "Fri, May 3, 2019 1:00 PM"
    }
    New-AdaptiveContainer {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text "128.90" -Size ExtraLarge
                New-AdaptiveTextBlock -Text "▲ 2.69 USD (2.13%)" -Color Good -Spacing None
            } -Width Stretch
            New-AdaptiveColumn {
                New-AdaptiveFactSet {
                    New-AdaptiveFact -Title 'Open' -Value '127.42'
                    New-AdaptiveFact -Title 'High' -Value '129.43'
                    New-AdaptiveFact -Title 'Low' -Value '127.25'
                }
            } -Width Auto
        }
    } -Spacing None
} -Speak 'Microsoft stock is trading at $62.30 a share, which is down .32%' -Verbose

Or maybe having a live score for your favorite team?

New-AdaptiveCard -Uri $IncomingWebhook -VerticalContentAlignment center {
    New-AdaptiveContainer {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn {
                New-AdaptiveImage -Url "https://adaptivecards.io/content/cats/3.png" -Size Medium -AlternateText "Shades cat team emblem" -HorizontalAlignment Center
                New-AdaptiveTextBlock -Weight Bolder -Text 'SHADES' -HorizontalAlignment Center
            } -Width Auto
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text "Sat, Aug 31, 2019" -HorizontalAlignment Center -Wrap
                New-AdaptiveTextBlock -Text "Final" -Spacing None -HorizontalAlignment Center
                New-AdaptiveTextBlock -Text "45 - 7" -HorizontalAlignment Center -Size ExtraLarge
            } -Width Stretch -Separator -Spacing Medium
            New-AdaptiveColumn {
                New-AdaptiveImage -Url "https://adaptivecards.io/content/cats/2.png" -Size Medium -HorizontalAlignment Center -AlternateText "Skins cat team emblem"
                New-AdaptiveTextBlock -Weight Bolder -Text 'SKINS' -HorizontalAlignment Center
            } -Width Auto -Separator -Spacing Medium
        }
    }
} -Speak 'The Seattle Seahawks beat the Carolina Panthers 40-7'

Want more? Let's try to get flight summary?

New-AdaptiveCard -Uri $IncomingWebhook {
    New-AdaptiveTextBlock -Text 'Passengers' -Weight Bolder -Subtle
    New-AdaptiveTextBlock -Text 'Sarah Hum' -Separator
    New-AdaptiveTextBlock -Text 'Jeremy Goldberg' -Spacing None
    New-AdaptiveTextBlock -Text 'Evan Litvak' -Spacing None
    New-AdaptiveTextBlock -Text '2 Stops' -Spacing Medium -Weight Bolder
    New-AdaptiveTextBlock -Text 'Tue, May 30, 2017 12:25 PM' -Spacing None -Weight Bolder

    New-AdaptiveColumnSet {
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'San Francisco' -Subtle
            New-AdaptiveTextBlock -Text 'SFO' -Size ExtraLarge -Color Accent -Spacing None
        }
        New-AdaptiveColumn -Width Auto {
            New-AdaptiveTextBlock -Text ' '
            New-AdaptiveImage -Url 'https://adaptivecards.io/content/airplane.png' -Size Small -Spacing Large -AlternateText 'Flight to'
        }
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'Amsterdam' -Subtle -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text 'AMS' -Size ExtraLarge -Color Accent -Spacing None -HorizontalAlignment Right
        }
    }

    New-AdaptiveTextBlock -Text 'Non-Stop' -Weight Bolder -Spacing Medium
    New-AdaptiveTextBlock -Text 'Fri, Jun 2, 2017 1:55 PM' -Weight Bolder -Spacing None

    New-AdaptiveColumnSet -Separator {
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'Amsterdam' -Subtle
            New-AdaptiveTextBlock -Text 'AMS' -Size ExtraLarge -Color Accent -Spacing None
        }
        New-AdaptiveColumn -Width Auto {
            New-AdaptiveTextBlock -Text ' '
            New-AdaptiveImage -Url 'https://adaptivecards.io/content/airplane.png' -Size Small -Spacing Large -AlternateText 'Flight to'
        }
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'San Francisco' -Subtle -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text 'SFO' -Size ExtraLarge -Color Accent -Spacing None -HorizontalAlignment Right
        }
    }

    New-AdaptiveColumnSet -Spacing Medium {
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'Total' -Subtle -Size Medium
        }
        New-AdaptiveColumn -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text '$4,032.54' -Size Medium -Weight Bolder -HorizontalAlignment Right
        }
    }

} -Speak "Your flight is confirmed for you and 3 other passengers from San Francisco to Amsterdam on Friday, October 10 8:30 AM" -Verbose

As another example, I wanted to show an Expense report. Why? Because it contains some tricks that make it unique. For example, it allows you to have some parts of your report hidden and only expanded on button press.

New-AdaptiveCard -Uri $Env:TEAMSPESTERID {
    New-AdaptiveContainer -Style Emphasis -Bleed {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn -Width Stretch {
                New-AdaptiveTextBlock -Text '**EXPENSE APPROVAL**' -Weight Bolder -Size Large
            }
            New-AdaptiveColumn -Width Auto {
                New-AdaptiveImage -Url "https://adaptivecards.io/content/pending.png" -AlternateText 'Pending' -HeightInPixels 30 #-HorizontalAlignment Right
            }
        }
    }

    New-AdaptiveContainer {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn -Width Stretch {
                New-AdaptiveTextBlock -Text 'Trip to UAE' -Wrap -Size ExtraLarge
            }
            New-AdaptiveColumn -Width Auto {
                New-AdaptiveActionSet {
                    New-AdaptiveAction -Title 'LINK TO CLICK' -ActionUrl 'https://adaptivecards.io'
                }
            }
        }
    }
    New-AdaptiveTextBlock -Text "[ER-13052](https://adaptivecards.io)" -Spacing Small -Size Small -Weight Bolder -Color Accent

    New-AdaptiveFactSet -Spacing Large {
        New-AdaptiveFact -Title "Submitted By" -Value "**Matt Hidinger**  matt@contoso.com"
        New-AdaptiveFact -Title "Duration" -Value "2019-06-19 - 2019-06-21"
        New-AdaptiveFact -Title "Submitted On" -Value "2019-04-14"
        New-AdaptiveFact -Title "Reimbursable Amount" -Value '$ 400.00'
        New-AdaptiveFact -Title "Awaiting approval from" -Value "**Thomas**  thomas@contoso.com"
        New-AdaptiveFact -Title "Submitted to" -Value "**David**  david@contoso.com"
    }

    New-AdaptiveContainer -Style Emphasis -Spacing Large {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text 'DATE' -Weight Bolder
            } -Width Auto
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text 'CATEGORY' -Weight Bolder
            } -Width Stretch
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text 'AMOUNT' -Weight Bolder
            } -Width Auto
        }
    } -Bleed

    New-AdaptiveColumnSet {
        New-AdaptiveColumn -Width Auto -Spacing Medium {
            New-AdaptiveTextBlock -Text '06-19' -Wrap
        }
        New-AdaptiveColumn -Width Stretch {
            New-AdaptiveTextBlock -Text 'Air Travel Expense' -Wrap
        }
        New-AdaptiveColumn -Width Auto {
            New-AdaptiveTextBlock -Text '$300.00' -Wrap
        }
        # Special column with Action and hidden items
        # Notice ActionTargetElement which triggers automatically ToggleVisibility for those mentioned
        New-AdaptiveColumn -Spacing Small -VerticalContentAlignment Center -Width Auto {
            New-AdaptiveImage -Id 'chevronDown1' -Url "https://adaptivecards.io/content/down.png" -WidthInPixels 20 -AlternateText "Details collapsed"
            New-AdaptiveImage -Id 'chevronUp1' -Url "https://adaptivecards.io/content/up.png" -WidthInPixels 20 -AlternateText "Details collapsed" -Hidden
        } -SelectActionTargetElement 'cardContent1', 'chevronDown1', 'chevronUp1'
    }

    # Notice this will be hidden initially and shown with the action from above
    New-AdaptiveContainer -Hidden -Id 'cardContent1' {
        New-AdaptiveTextBlock -Text '* Leg 1 on Tue, Jun 19th, 2019 at 6:00 AM.' -Subtle -Wrap
        New-AdaptiveTextBlock -Text '* Leg 2 on Tue, Jun 19th, 2019 at 7:15 PM.' -Subtle -Wrap
        New-AdaptiveContainer -Style Good {
            # This should be an input type, but not yet added - not sure if it makes sense, as inputs are not working for webhooks
            New-AdaptiveTextBlock -Text 'Some more data in good color' -Subtle -Wrap
        }
    }

    New-AdaptiveColumnSet {
        New-AdaptiveColumn -Width Auto -Spacing Medium {
            New-AdaptiveTextBlock -Text '06-19' -Wrap
        }
        New-AdaptiveColumn -Width Stretch {
            New-AdaptiveTextBlock -Text 'Auto Mobile Expense' -Wrap
        }
        New-AdaptiveColumn -Width Auto {
            New-AdaptiveTextBlock -Text '$100.00' -Wrap
        }
        # Special column with Action and hidden items
        # Notice ActionTargetElement which triggers automatically ToggleVisibility for those mentioned
        New-AdaptiveColumn -Spacing Small -VerticalContentAlignment Center -Width Auto {
            New-AdaptiveImage -Id 'chevronDown2' -Url "https://adaptivecards.io/content/down.png" -WidthInPixels 20 -AlternateText "Details collapsed"
            New-AdaptiveImage -Id 'chevronUp2' -Url "https://adaptivecards.io/content/up.png" -WidthInPixels 20 -AlternateText "Details collapsed" -Hidden
        } -SelectActionTargetElement 'cardContent2', 'chevronDown2', 'chevronUp2'
    }

    # Notice this will be hidden initially and shown with the action from above
    New-AdaptiveContainer -Hidden -Id 'cardContent2' {
        New-AdaptiveTextBlock -Text '* Contoso Car Rentrals, Tues 6/19 at 7:00 AM' -Subtle -Wrap
        New-AdaptiveContainer -Style Warning {
            # This should be an input type, but not yet added - not sure if it makes sense, as inputs are not working for webhooks
            New-AdaptiveTextBlock -Text 'Some more data in warning color' -Subtle -Wrap
        }
    }

    New-AdaptiveColumnSet {
        New-AdaptiveColumn -Width Auto -Spacing Medium {
            New-AdaptiveTextBlock -Text '06-21' -Wrap
        }
        New-AdaptiveColumn -Width Stretch {
            New-AdaptiveTextBlock -Text 'Excess Baggage Cost' -Wrap
        }
        New-AdaptiveColumn -Width Auto {
            New-AdaptiveTextBlock -Text '$50.38' -Wrap
        }
        # Special column with Action and hidden items
        # Notice ActionTargetElement which triggers automatically ToggleVisibility for those mentioned
        New-AdaptiveColumn -Spacing Small -VerticalContentAlignment Center -Width Auto {
            New-AdaptiveImage -Id 'chevronDown3' -Url "https://adaptivecards.io/content/down.png" -WidthInPixels 20 -AlternateText "Details collapsed"
            New-AdaptiveImage -Id 'chevronUp3' -Url "https://adaptivecards.io/content/up.png" -WidthInPixels 20 -AlternateText "Details collapsed" -Hidden
        } -SelectActionTargetElement 'cardContent3', 'chevronDown3', 'chevronUp3'
    }

    # Notice this will be hidden initially and shown with the action from above
    New-AdaptiveContainer -Hidden -Id 'cardContent3' {
        New-AdaptiveTextBlock -Text 'More data' -Subtle -Wrap
        New-AdaptiveContainer -Style Attention {
            # This should be an input type, but not yet added - not sure if it makes sense, as inputs are not working for webhooks
            New-AdaptiveTextBlock -Text 'Some more data in warning color' -Subtle -Wrap
        }
    }

    New-AdaptiveColumnSet -Spacing Large -Separator {
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Text "Total Expense Amount" -Wrap -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text 'Non-reimbursable Amount' -Wrap -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text 'Advance Amount' -Wrap -HorizontalAlignment Right
        } -Width Stretch
        New-AdaptiveColumn {
            New-AdaptiveTextBlock -Text '$450.38' -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text '(-) 50.38' -HorizontalAlignment Right
            New-AdaptiveTextBlock -Text '(-) 0.00' -HorizontalAlignment Right
        } -Width Auto
    }

    New-AdaptiveContainer -Style Emphasis {
        New-AdaptiveColumnSet {
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text 'Amount to be Reimbursed' -Wrap -HorizontalAlignment Right
            } -Width Stretch
            New-AdaptiveColumn {
                New-AdaptiveTextBlock -Text '$ 400.00' -Weight Bolder
            } -Width Auto
        }
    } -Bleed

    New-AdaptiveColumnSet {
        New-AdaptiveColumn -VerticalContentAlignment Center -WidthInWeight 1 {
            New-AdaptiveTextBlock -Text 'Show history' -Wrap -HorizontalAlignment Right -Id 'showHistory' -Color Accent
            New-AdaptiveTextBlock -Text 'Hide history' -Wrap -HorizontalAlignment Right -Id 'hideHistory' -Color Accent -Hidden
        } -SelectActionTargetElement 'cardContent4', 'showHistory', 'hideHistory'
    }

    New-AdaptiveContainer -id 'cardContent4' -Hidden {
        New-AdaptiveTextBlock -Text '* Expense submitted by **Matt Hidinger** on Mon, Jul 15, 2019' -Subtle -Wrap
        New-AdaptiveTextBlock -Text '* Expense approved by **Thomas** on Mon, Jul 15, 2019' -Subtle -Wrap
    }
} -Action {
    # This won't really work as submit doesn't work in
    New-AdaptiveAction -Type Action.Submit -Title 'Approve'
    New-AdaptiveAction -Type Action.Submit -Title 'Reject'
} -Verbose

Long code, but what you get is Adaptive Card at it's finest! Of course, as mentioned before, some stuff is not supported. So while you can display all sorts of information that can show or hide on a button press, you need to be aware of its limits. You won't be able to take action. You can press a button that will open the URL, you can press a section that will open the URL, but having it do some magic when you have a bot framework in place is not possible.

Another thing you should be aware of is that when you're not providing the URI parameter New-AdaptiveCard will return the JSON code. This is useful for actions that nest another Adaptive card on button press. The following code demonstrates how this works.

New-AdaptiveCard -Uri $IncomingWebHook {
    New-AdaptiveContainer {
        New-AdaptiveTextBlock -Text 'Publish Adaptive Card schema' -Weight Bolder -Size Medium
        New-AdaptiveColumnSet {
            New-AdaptiveColumn -Width auto {
                New-AdaptiveImage -Url "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg" -Size Small -Style person
            }
            New-AdaptiveColumn -Width stretch {
                New-AdaptiveTextBlock -Text "Matt Hidinger" -Weight Bolder -Wrap
                New-AdaptiveTextBlock -Text "Created {{DATE(2017-02-14T06:08:39Z, SHORT)}}" -Subtle -Spacing None -Wrap
            }
        }
    }
    New-AdaptiveContainer {
        New-AdaptiveTextBlock -Text "Now that we have defined the main rules and features of the format, we need to produce a schema and publish it to GitHub. The schema will be the starting point of our reference documentation." -Wrap
        New-AdaptiveFactSet {
            New-AdaptiveFact -Title 'Board:' -Value 'Adaptive Card'
            New-AdaptiveFact -Title 'List:' -Value 'Backlog'
            New-AdaptiveFact -Title 'Assigned to:' -Value 'Matt Hidinger'
            New-AdaptiveFact -Title 'Due date:' -Value 'Not set'
        }
    }
} -Action {
    New-AdaptiveAction -Title 'Set due date' -Type Action.Submit
    New-AdaptiveAction -Title 'Comment' -Type Action.OpenUrl -ActionUrl 'https://evotec.xyz'
    New-AdaptiveAction -Title 'Show Nested, but limited Adaptive Card' -Body {
        New-AdaptiveTextBlock -Text 'Publish Adaptive Card schema' -Weight Bolder -Size Medium
        New-AdaptiveColumnSet {
            New-AdaptiveColumn -Width auto {
                New-AdaptiveImage -Url "https://pbs.twimg.com/profile_images/3647943215/d7f12830b3c17a5a9e4afcc370e3a37e_400x400.jpeg" -Size Small -Style person
            }
            New-AdaptiveColumn -Width stretch {
                New-AdaptiveTextBlock -Text "Matt Hidinger" -Weight Bolder -Wrap
                New-AdaptiveTextBlock -Text "Created {{DATE(2017-02-14T06:08:39Z, SHORT)}}" -Subtle -Spacing None -Wrap
            }
        }
        New-AdaptiveFactSet {
            New-AdaptiveFact -Title 'Board:' -Value 'Adaptive Card'
            New-AdaptiveFact -Title 'List:' -Value 'Backlog'
            New-AdaptiveFact -Title 'Assigned to:' -Value 'Matt Hidinger'
            New-AdaptiveFact -Title 'Due date:' -Value 'Not set'
        }
    }
}

What I've shown you is just the tip of the iceberg of what Adaptive Cards can do. You can find more examples on the Samples and Templates | Adaptive Cards Microsoft website. Of course, those are JSON based cards, and you would need to translate them just like I did to my PSTeams approach. I'm mostly linking it to give you ideas on how you can deliver your own data to Microsoft Teams.

PSTeams - How to get it up and running?

How do you install it? The easiest and most optimal way is to use PowerShellGallery. This will get you up and running in no time. Whenever there is an update, just run Update-Module, and you're done.

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.

Will there be more? Hopefully yes. I want to get mentioning working – but so far, I'm failing. I also want to allow inline images, just like you can see in command New-TeamsActivityImage. If you want to help with those, I'm open for PRs or open an issue and let me know how things could work 🙂

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

Upgrade Azure Active Directory Connect fails with unexpected error

Today, I made the decision to upgrade my test environment and update the version of…

2 months ago

Mastering Active Directory Hygiene: Automating Stale Computer Cleanup with CleanupMonster

Have you ever looked at your Active Directory and wondered, "Why do I still have…

3 months ago

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…

7 months 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…

12 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…

1 year 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…

1 year ago