PSAdaptiveCard.psm1

<#
.SYNOPSIS
    Module for creating and managing Adaptive Cards for Microsoft Teams.
 
.DESCRIPTION
    This module provides functions to generate JSON payloads for Adaptive Cards, Fact Sets, and Tables.
    These payloads can be used to post messages to Microsoft Teams channels via Workflow webhooks.
 
.AUTHOR
    EW
 
.COPYRIGHT
    No
 
.LICENSE
    MIT
 
.VERSION
    1.0.0
 
.NOTES
    - Requires PowerShell 5.1 or later.
    - For more information, see the Adaptive Cards documentation at https://adaptivecards.io.
 
.EXAMPLE
    $cardContent = @(
        New-TextBlock -Text "Hello, Teams!"
    )
    New-AdaptiveCard -BodyContent $cardContent | ConvertTo-Json -Depth 20
     
#>


function New-AdaptiveCard {
    param (
        [Parameter(Mandatory = $true)]
        [array]$BodyContent
    )

    $adaptiveCard = [pscustomobject]@{
        type = 'AdaptiveCard'
        body = @()
        '$schema' = 'http://adaptivecards.io/schemas/adaptive-card.json'
        version = '1.4'        
    }

    foreach ($item in $BodyContent) {
        $adaptiveCard.body += $item
    }    
    return $adaptiveCard
}

function New-TextBlock {
    param (        
        [bool]$IsSubtle = $false,
        [bool]$separator = $false,        
        [int]$MaxLines,
        [ValidateSet('default', 'small', 'medium', 'large', 'extraLarge', IgnoreCase = $false)]
        [string]$Size = 'default',
        [ValidateSet('default', 'lighter', 'bolder', IgnoreCase = $false)]
        [string]$Weight = 'default',
        [bool]$Wrap = $true,
        [ValidateSet('default', 'dark', 'light', 'accent', 'good', 'warning', 'attention', IgnoreCase = $false)]
        [string]$Color = 'default',
        [ValidateSet('default', 'monospace', IgnoreCase = $false)]
        [string]$Fonttype = 'default',
        [ValidateSet('left', 'center', 'right', IgnoreCase = $false)]
        [string]$HorizontalAlignment = 'left',
        [Parameter(Mandatory = $true)]
        [string]$Text
    )

    $textBlock = [pscustomobject]@{
        type                = 'TextBlock'
        isSubtle            = $IsSubtle
        separator           = $separator
        maxLines            = $MaxLines
        size                = ($size.ToLower())
        weight              = ($Weight.ToLower())
        wrap                = $Wrap
        color               = $Color
        fonttype            = $Fonttype
        horizontalAlignment = $HorizontalAlignment
        text                = $Text
    }
    return $textBlock
}

function New-FactSet {
    param (
        [Parameter(ValueFromPipeline = $true)]
        [psobject]$Object,
        [Parameter(Mandatory = $true)]
        [string]$TitleProperty,
        [Parameter(Mandatory = $true)]
        [string]$ValueProperty
    )
    
    begin {
        $facts = @()
    }
    
    process {
        $value = [string]$Object.$ValueProperty
        $fact = @{
            title = $Object.$TitleProperty
            value = $value
        }
        $facts += $fact
    }
    end {
        $factSet = @{
            type  = "FactSet"
            facts = $facts
        }
        return $factSet
    }    
}

function New-Table {
    [CmdletBinding(DefaultParameterSetName = 'Default')]
    param (
        [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Default')]
        [Parameter(ValueFromPipeline = $true, ParameterSetName = 'Highlight')]
        [psobject]$Object,

        [Parameter(Mandatory = $false, ParameterSetName = 'Highlight')]
        [string]$HighlightValueMatch,

        [Parameter(Mandatory = $false, ParameterSetName = 'Highlight')]
        [ValidateSet('dark', 'light', 'accent', 'good', 'warning', 'attention')]
        [string]$HighlightValueStyle,

        [Parameter(Mandatory = $false)]
        [bool]$firstRowAsHeader = $true,

        [Parameter(Mandatory = $false)]
        [ValidateSet('default', 'dark', 'light', 'accent', 'good', 'warning', 'attention')]
        [string]$headerRowStyle,
        
        [Parameter(Mandatory = $false)]
        [bool]$showGridLines = $true,

        [Parameter(Mandatory = $false)]
        [ValidateSet('default', 'dark', 'light', 'accent', 'good', 'warning', 'attention')]
        [string]$gridStyle = 'default',

        [Parameter(Mandatory = $false)]
        [ValidateSet('left', 'center', 'right')]
        [string]$horizontalCellContentAlignment,

        [Parameter(Mandatory = $false)]
        [ValidateSet('top', 'center', 'bottom')]
        [string]$verticalCellContentAlignment
    )

    begin {        
        $table = [pscustomobject]@{
            type                          = 'Table'
            gridStyle                     = $gridStyle
            firstRowAsHeader              = $firstRowAsHeader
            showGridLines                 = $showGridLines
            columns                       = @()
            rows                          = @()
        }

        # Add optional attributes if provided
        if ($horizontalCellContentAlignment) {
            $table.horizontalCellContentAlignment = $horizontalCellContentAlignment
        }
        if ($verticalCellContentAlignment) {
            $table.verticalCellContentAlignment = $verticalCellContentAlignment
        }

        $columns = @()        
        $isHighlighting = $HighlightValueMatch -and $HighlightValueStyle
    }

    process {
        if ($columns.Count -eq 0) {
            # Get the columns from the first object
            $columns = $Object.PSObject.Properties | Where-Object { $_.MemberType -eq 'NoteProperty' } | Select-Object -ExpandProperty Name

            # Initialize the columns with equal width
            foreach ($column in $columns) {
                $table.columns += @{
                    width = 'auto'
                }
            }

            # Add the header row
            $headerRow = @{
                type  = 'TableRow'
                cells = @()
            }

            # Add optional attributes if provided
            if ($headerRowStyle) {
                $headerRow.style = $headerRowStyle
            }
        

            foreach ($column in $columns) {
                $headerRow.cells += @{
                    type  = 'TableCell'
                    items = @(
                        @{
                            type = 'TextBlock'
                            text = $column
                        }
                    )
                }
            }

            $table.rows += $headerRow
        }

        # Process each object and add a row to the table
        $row = @{
            type  = 'TableRow'
            cells = @()
        }

        foreach ($column in $columns) {
            $textValue = [string]$Object.$column
            $textBlock = @{
                type = 'TextBlock'
                text = $textValue
            }

            if ($isHighlighting -and $textValue -eq $HighlightValueMatch) {
                $textBlock.color = $HighlightValueStyle
            }

            $row.cells += @{
                type  = 'TableCell'
                items = @($textBlock)
            }
        }

        $table.rows += $row
    }

    end {        
        return $table
    }
}

function Send-JsonToTeamsWebhook {
    param (
        [Parameter(Mandatory = $true)]
        [string]$WebhookURI,
        [Parameter(ValueFromPipeline = $true, Mandatory = $true)]
        [pscustomobject]$adaptiveCard,
        [Parameter(Mandatory = $false)]
        [switch]$fullWidth,
        [Parameter(Mandatory = $false)]
        [switch]$onlyConvertToJson
    )
    begin {
        #
    }
    process {
        $attachment = [pscustomobject]@{
            contentType = "application/vnd.microsoft.card.adaptive"
            contentUrl = $null
            content = $adaptiveCard
        }

        $message = [pscustomobject]@{
            type = "message"
            attachments = @()
        }
        $message.attachments += $attachment

        if ($fullWidth) {
            $msteamsProperty = @{
                width = "Full"
            }
            $message.attachments[0].content | Add-Member -MemberType NoteProperty -Name msteams -Value $msteamsProperty
        }

        $Json = ($message | ConvertTo-Json -Depth 20) -replace '\\\\', '\'
        
        if ($OnlyConvertToJson) {
            Write-Output $Json
            Break
        }

        $parameters = @{
            "URI"         = $WebhookURI
            "Method"      = 'POST'
            "Body"        = $Json
            "ContentType" = 'application/json; charset=UTF-8'
            "ErrorAction" = 'Stop'
        }
        try {
            Invoke-RestMethod @parameters            
        }
        catch {
            Write-Error "Failed to send request: $($_.Exception.Message)"
        }
    }
}