ActionCard.ps1

[Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Scope='Function', Target='New*', Justification='New- Commands create definitions but do not change system state')]
Param()
Function Convert-ColorToHex     {
    <#
      .Synopsis
        Turns a [System.Drawing.Color] into a hex string e.g. Red to ff0000
    #>

    [cmdletbinding()]
    [outputType([String])]
    Param (
        [Alias("Colour")]
        [System.Drawing.Color]$Color
    )
    #return 2 hex digits for red, green and blue components
    "{0:x2}{1:x2}{2:x2}" -f $Color.r, $Color.g, $Color.b
}

Function New-CardImage          {
    [cmdletbinding()]
    [Alias('CardImage')]
    [OutPuttype([Hashtable])]
    Param (
        [parameter(Position=0, Mandatory=$true)]
        [alias('URI')]
        [string]$ImageURI,
        [string]$Title = 'Image'
    )
    @{image = 'uri' ; title='title'}
}

Function New-CardSection        {
    [CmdletBinding()]
    [alias('cardsection')]
    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    Param (
        [String]$Title            = '' ,
        [String]$ActivityImage    = '' ,
        [String]$ActivityTitle    = '' ,
        [String]$ActivitySubtitle = '' ,
        [String]$ActivityText     = '' ,
        [bool]$StartGroup         = $false ,
        $Images                  ,# = @( @{image = 'uri1' ; title='title1'}, @{image = 'uri' ; title='title'}) ,
        $Facts                   ,# = @( @{name1='value'}, @{name2='value2'}) ,
        $HeroimageURI             = 'HeroImageuri'  ,
        $HeroimageTitle           = 'HeroImagetitle'
    )
    $section = [ordered]@{
                    title                = $Title
                    startGroup           = $StartGroup
                    activityImage        = $ActivityImage
                    activityTitle        = $ActivityTitle
                    activitySubtitle     = $ActivitySubtitle
                    activityText         = $ActivityText
                    text                 = $Text
    }
    if ($HeroimageURI) {
                   $heroimage            = New-CardImage  -ImageURI $HeroimageURI -title $HeroimageTitle
                   $section['heroImage'] = $heroimage
    }
    if ($Facts)  { $section['facts']     = $Facts }
    if ($Images) { $section['images']    = $Images}
    return   $section
}

Function New-CardInput          {
    <#
      .synopsis
        Creates the UI imput controls for message cards
      .Example
      > New-CardInput -InputType MultiLineText -ID 'Feedback' -Title "Let us know what you think"
      #Creates as a multi-line text box with the the ID "feedback" and a title
 
    #>

    [CmdletBinding()]
    [alias('cardinput')]
    Param (
        [Parameter(ParameterSetName='SingleChoice',Mandatory=$true,Position=0)]
        [ValidateSet('Text','MultiLineText','DateOnly','DateTime')]
        [Alias('Type')]
        [string]$InputType,
        [Parameter(ParameterSetName='SingleChoice')]
        [int]$MaxLength,
        [Parameter(ParameterSetName='MultiChoice',Mandatory=$true)]
        [string[]]$Choices,
        [Parameter(ParameterSetName='MultiChoice')]
        [switch]$MultiSelect,
        [Parameter(ParameterSetName='SingleChoice',Mandatory=$true,Position=1)]
        [Parameter(ParameterSetName='MultiChoice',Mandatory=$true,Position=0)]
        [string]$ID = 'feedback' ,
        [string]$Title,
        [string]$DefaultValue,
        [switch]$IsRequired
    )
    if ($InputType -in ('Text','MultilineText')) {
        $InputControl = [ordered]@{
            '@type'       = 'TextInput'
            'id'          = $ID
            'isRequired'  = [bool]$IsRequired
            'title'       = $Title
        }
        if ($InputType -eq 'MultilineText') { $InputControl['isMultiline'] = $true }
        if ($MaxLength) {                     $InputControl['maxLength']   = $MaxLength }
    }
    elseif ($InputType -in ('DateTime','Date') ) {
        $InputControl = [ordered]@{
            '@type'       = 'DateInput'
            'id'          = $ID
            'isRequired'  = [bool]$IsRequired
            'title'       = $Title
        }
        if ($InputType -eq 'DateTime') {InputControl['includeTime'] = $true}
    }
    elseif ($Choices) {
        $InputControl = [ordered]@{
            '@type'       = 'MultichoiceInput'
            'id'          = $ID
            'isRequired'  = [bool]$IsRequired
            'title'       = $Title
            'choices'     = @()
        }
        if ($MultiSelect) {$InputControl['isMultiSelect'] = $true}
        foreach ($c in $choices) {
                if ($c -is [string]) {
                    $InputControl['choices'] += @{'display' = $c ; 'value' = $c}
                }
                elseif ($c.display -and $c.value) {
                    $InputControl['choices'] += @{'display' = $c.display ; 'value' = $c.value}
                }
                else {throw 'Invalid value for a choice.'; return}
        }
    }
    if ($DefaultValue) { $InputControl['value'] = $DefaultValue }
    return $InputControl
}

Function New-CardActionHttpPost {
    <#
      .Synopsis
        Creates an Http Post action for a message card
      .Description
        See https://docs.microsoft.com/en-gb/outlook/actionable-messages/message-card-reference#httppost-action
     .Example
        New-CardActionHttpPost -Name 'Send Feedback' -Target 'http://feedback.contoso.com' -Primary
        Creates a button for a form which contains which will post to the URL.
          need more params to be useful!
    #>

    [cmdletbinding()]
    [Alias("HttpPostAction")]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        $Name ,
        [Parameter(Mandatory=$true,Position=1)]
        $Target ,
        [string[]]$Headers  ,
        $Body ,
        [ValidateSet('application/json', 'application/x-www-form-urlencoded')]
        $ContentType,
        [switch]$Primary
    )
    $action = [ordered]@{
        '@type'    = 'HttpPOST'
        'name'     = $Name
        'target'   = $target
    }
    if ($Primary)     {$action['isPrimary']       = $true}
    if ($Headers)     {$action['headers']         = $Headers}
    if ($Body)        {$action['body']            = $Body}
    if ($ContentType) {$action['bodyContentType'] = $ContentType}
    return $action
}

Function New-CardActionOpenUri  {
    <#
      .Synopsis
         Creates a button on a message card to open a link
      .Example
          New-CardActionOpenUri -Name 'Learn more' -Targets 'https://docs.microsoft.com/outlook/actionable-messages'
          This creates an action which a appers as button on the card [Learn More] clicking it opens the link
    #>

    [cmdletbinding()]
    [Alias("OpenUriAction")]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        $Name ,
        [Parameter(Mandatory=$true,Position=1)]
        $Targets ,
        [switch]$Primary
    )
    $Action  = [ordered]@{
        '@type'    = 'OpenUri'
        'name'     = $Name
        'targets'  = @()
    }
    if ($Primary) {$Action['isPrimary']= $true}
    foreach ($t in $Targets) {
                if ($t -is [string]) {
                    $Action['targets'] += @{'os' = 'default' ; 'uri' = $t}
                }
                elseif ($t.os -and $t.uri) {
                    $Action['targets'] += @{'os' = $t.os ; 'uri' = $t.uri}
                }
                else {throw 'Invalid value for a target.'; return}

    }
    return $action
}

Function New-CardActionCard     {
    <#
      .Synopsis
        Creates an "Action card" action for a message card
      .Description
        Actions are presented on the card as buttons the user can click
        For an "action card" action this reveals a 'sub-card' with input controls
        and action buttons. The buttons must be Http posts or Open URI types, they
        can't be nest action cards.
      .Example
      >
      >$inputs = @()
      >$inputs += New-CardInput ...
      >$actions = New-CardActionHttpPost -Name 'Send Feedback' -Target 'http://....'
      >$actioncard = New-CardActionCard -Name 'Send Feedback' -Inputs $inputs -Actions $actions
      This example starts by creating the input fields for the Action card
      It the definies a single action - the both these steps have been truncated for brevity
      The final step creates the action card. This will then be passed in the Actions parameter for New-Message card.
      The post action will usually need to send a some of the input in the body of the post
      see under 'Input value substitution' in https://docs.microsoft.com/en-gb/outlook/actionable-messages/message-card-reference
    #>

    [cmdletbinding()]
    [Alias("CardAction")]
    param(
        [Parameter(Mandatory=$true,Position=0)]
        $Name ,
        $Inputs,
        $Actions
      )
      [ordered]@{
        '@type'    = 'ActionCard'
        'name'     = $Name
        'inputs'   = @() + $Inputs
        'actions'  = @() + $Actions
    }
}

Function New-MessageCard        {
    <#
      .Synopsis
        Creates a message card and either posts to a webhook or returns it for examination/tweaking.
      .Example
        >
        >new-messageCard -WebHookURI $hookUri -Title 'From powershell to teams using webhooks' -Text @'
            ![Logo](https://cdn.vsassets.io/content/notifications/teams-build-succeeded.png)**James** did a _crane job_ on the logo!
        '@
        Creates a simple card with a title and text using mark down to display a logo.
        This card is posted to the Webhook in $webhooURI.
      .Description
        See https://docs.microsoft.com/en-gb/outlook/actionable-messages/message-card-reference
    #>

    [cmdletbinding()]
    [Alias("MessageCard")]
    param(
            #The title for the card. The font for this is fixed.
            [string]$Title      = 'Visit the Outlook Dev Portal' ,
            #The body text for the card. This supports markdown, which you can use to include images.
            [String]$Text       = '',
            #Short version of the text.
            [string]$Summary    = '',
            [system.drawing.color]$Themecolor,
            $ThemeColorHex        ,# ='0072C6',
            $Actions  ,
            [switch]$AsHashTable,
            [String]$WebHookURI

    )
    #Build the card as a HashTable. Make it ordered when we connvert to JSON the items don't look in a strange order
    $CardSettings = [ordered]@{
        '@context'       = 'https://schema.org/extensions'
        '@type'          = 'MessageCard'
        'title'          = $Title
        'text'           = $Text
        'summary'        = $Summary
    }
    #Add optional items color is a hex string but we'll take and convert system colors.
    if     ($ThemeColorHex) {$cardSettings['themeColor']      = $ThemeColorHex}
    elseif ($Themecolor)    {$cardSettings['themeColor']      = Convert-ColorToHex -Color $Themecolor}
    if     ($Actions)       {$cardSettings['potentialAction'] = @() + $Actions}

    #And either post to a webhook or return the results.
    if     ($AsHashTable) {return $cardSettings}
    elseif ($WebHookURI)  {
        Invoke-RestMethod -Method Post -Uri $WebHookURI -ContentType "application/json" -Body (ConvertTo-Json $cardSettings -Depth 99)
    }
    else {ConvertTo-Json $cardSettings -Depth 99}
}

<# Mobula's web hook: to avoid uploading it use git update-index --skip-worktree .\ActionCard.ps1
  to commit any changes remove this part and do git update-index --no-skip-worktree .\ActionCard.ps1
  $hookUri = 'https://outlook.office.com/webhook/8c4e1893-63bc-463b-b31e-abe0243c54fa@e6af5578-6d03-49e0-af3b-383cf5ec0b5f/IncomingWebhook/8c241937656d49c0a4d8446cfc3df21c/12ae949b-a88b-4d53-ab12-fd6542ddfb45' #>


############## Example ####################

# Your Web hook here
# $hookUri = 'https://outlook.office.com/webhook/<<teamGuid>>@<<org GUID>>/IncomingWebhook/<<id>>/<<creator guid>>'

<#
First we can use these with the commands being written out in max-verbose. and storing in variables
$inputs = New-CardInput -InputType MultiLineText -ID 'Feedback' -Title "Let us know what you think"
$actions = New-CardActionHttpPost -Name 'Send Feedback' -Target 'http://....' -Primary
$actioncard = New-CardActionCard -Name 'Send Feedback' -Inputs $inputs -Actions $actions
$link = New-CardActionOpenUri -Name 'Learn more' -Targets 'https://docs.microsoft.com/outlook/actionable-messages'
 
$actions = @($link,$actionCard)
$json = New-MessageCard -Actions $actions -Title 'Visit the Outlook Dev Portal' -Text 'Click **Learn More** to learn more about Actionable Messages!'
 
#The same but using aliases and being terse with parameters.
$inputs = Cardinput MultiLineText feedback -Title "Let us know what you think"
$actions = HttpPostAction 'Send Feeback' 'http://....' -Primary
$actioncard = CardAction 'Send Feedback' -Inputs $inputs -Actions $actions
$link = OpenUriAction 'Learn more' 'https://docs.microsoft.com/outlook/actionable-messages'
$json = MessageCard 'Visit the Outlook Dev Portal' 'Click **Learn More** to learn more about Actionable Messages!' -Actions $link,$actioncard
 
#And the same again but with attitude of "We don't need no stinking variables"
MessageCard -Title 'Visit the Outlook Dev Portal' -Text @'
![Logo](https://cdn.vsassets.io/content/notifications/teams-build-succeeded.png)**James** did a _crane job_ on the logo!
'@ -Actions @(
            (CardAction "Feedback" -Inputs (Cardinput MultiLineText feedback -Title "Let us know, what do you think?") `
                                        -Actions (HttpPostAction 'Send us feedback' 'http://....' -Primary) ) ,
            (OpenUriAction 'Learn more' 'https://docs.microsoft.com/outlook/actionable-messages')
        ) | clip # paste into https://messagecardplayground.azurewebsites.net/
#>