PowerShell Teams Webhook: Post Messages to Teams Channel

PowerShell Teams Webhook: Post Messages to Teams Channel

PowerShell Tips Editor 4 min read
PowerShell Teams Webhook: Post Messages to Teams Channel

When an automated script detects a critical issue — a failed service, a backup error, a disk filling up — your on-call team needs to know immediately. PowerShell can send Teams messages to any Microsoft Teams channel using Incoming Webhooks, turning your scripts into proactive alerting systems that reach your team where they already work. This post covers basic messages, Adaptive Card formatting, and a reusable notification function.

Quick Answer / TL;DR

Create an Incoming Webhook connector in Teams, then post JSON to it with Invoke-RestMethod -Uri $webhookUrl -Method Post -ContentType 'application/json' -Body $json. The payload format uses either legacy MessageCard or the newer Adaptive Card schema.

Create Teams Incoming Webhook Connector

Connectors are configured per channel in Teams. Navigate to the channel, click the three-dot menu, select Connectors, find “Incoming Webhook,” and configure it with a name and optional icon. Copy the generated URL — this is your endpoint for all POST requests. Note: as of late 2024, Microsoft is migrating away from Office 365 Connectors toward Power Automate workflows for new webhook integrations.

# Store webhook URL securely — never hardcode in scripts
$webhookUrl = $env:TEAMS_WEBHOOK_URL

# Quick connectivity test
$testPayload = @{
    '@type'    = 'MessageCard'
    '@context' = 'http://schema.org/extensions'
    text       = 'PowerShell webhook test message'
} | ConvertTo-Json -Depth 5

$response = Invoke-RestMethod -Uri $webhookUrl -Method Post `
    -ContentType 'application/json' -Body $testPayload
Write-Host "Response: $response"   # Teams returns "1" on success

Send a Basic Message Card

The MessageCard schema is the original Teams webhook format. It supports a title, text, and optional sections. While Microsoft is phasing this out in favor of Adaptive Cards, it remains widely deployed and is simpler for basic notifications.

$payload = @{
    '@type'    = 'MessageCard'
    '@context' = 'http://schema.org/extensions'
    summary    = 'Script Notification'
    title      = 'Backup Job Complete'
    text       = "Server **$env:COMPUTERNAME** completed backup at $(Get-Date -Format 'HH:mm:ss'). Files copied: 15,432."
} | ConvertTo-Json -Depth 5

Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
    -ContentType 'application/json' -Body $payload | Out-Null

Adaptive Card Format

Adaptive Cards are the modern Teams message format with richer layout options including columns, image sets, and action buttons. Post an Adaptive Card inside an attachments wrapper when using webhook endpoints.

$adaptiveCard = @{
    type    = 'message'
    attachments = @(
        @{
            contentType = 'application/vnd.microsoft.card.adaptive'
            contentUrl  = $null
            content     = @{
                '$schema' = 'http://adaptivecards.io/schemas/adaptive-card.json'
                type      = 'AdaptiveCard'
                version   = '1.4'
                body      = @(
                    @{ type = 'TextBlock'; size = 'Large'; weight = 'Bolder'; text = 'Deployment Report' },
                    @{ type = 'TextBlock'; text = "Server: $env:COMPUTERNAME"; wrap = $true },
                    @{ type = 'TextBlock'; text = "Time: $(Get-Date -Format 'yyyy-MM-dd HH:mm')"; wrap = $true }
                )
            }
        }
    )
} | ConvertTo-Json -Depth 15

Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
    -ContentType 'application/json' -Body $adaptiveCard | Out-Null

Color-Coded Status Messages

MessageCard supports a themeColor property that adds a colored left border to the card. Use hex color codes: green for success, red for failure, orange for warnings. This provides instant visual status recognition in a busy Teams channel.

function Send-TeamsStatusMessage {
    param(
        [string]$Title,
        [string]$Message,
        [ValidateSet('Success','Warning','Error')]
        [string]$Status = 'Success'
    )

    $colors = @{ Success = '00B050'; Warning = 'FFC000'; Error = 'FF0000' }

    $payload = @{
        '@type'    = 'MessageCard'
        '@context' = 'http://schema.org/extensions'
        themeColor = $colors[$Status]
        summary    = $Title
        title      = $Title
        text       = $Message
        sections   = @(@{
            activityText    = "Reported by: $env:COMPUTERNAME"
            activitySubtext = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
        })
    } | ConvertTo-Json -Depth 10

    Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
        -ContentType 'application/json' -Body $payload | Out-Null
}

Send-TeamsStatusMessage -Title 'Database Backup Failed' -Message 'SQL Server returned error 1205' -Status 'Error'

Include Action Buttons

Add potentialAction entries to a MessageCard to include clickable buttons in the Teams message. Use OpenUri actions to link to dashboards, runbooks, or ticket systems.

$payload = @{
    '@type'    = 'MessageCard'
    '@context' = 'http://schema.org/extensions'
    themeColor = 'FF0000'
    title      = 'Disk Space Critical'
    text       = "Drive C: on $env:COMPUTERNAME is at 95% capacity."
    potentialAction = @(
        @{
            '@type'  = 'OpenUri'
            name     = 'Open Dashboard'
            targets  = @(@{ os = 'default'; uri = 'https://monitor.contoso.com' })
        },
        @{
            '@type'  = 'OpenUri'
            name     = 'View Runbook'
            targets  = @(@{ os = 'default'; uri = 'https://wiki.contoso.com/disk-cleanup' })
        }
    )
} | ConvertTo-Json -Depth 10

Invoke-RestMethod -Uri $env:TEAMS_WEBHOOK_URL -Method Post `
    -ContentType 'application/json' -Body $payload | Out-Null

Reusable Send-TeamsMessage Function

A production-ready function with error handling, configurable webhook URL, and support for both text and pre-built card payloads. Import this from a shared module in all your automation scripts.

function Send-TeamsMessage {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [string]$WebhookUrl,

        [string]$Title,
        [Parameter(Mandatory)]
        [string]$Message,
        [string]$Color = '0078D4',
        [hashtable[]]$Actions
    )

    $card = @{
        '@type'    = 'MessageCard'
        '@context' = 'http://schema.org/extensions'
        themeColor = $Color
        summary    = $Title
        title      = $Title
        text       = $Message
    }
    if ($Actions) { $card.potentialAction = $Actions }

    try {
        $body = $card | ConvertTo-Json -Depth 10 -Compress
        Invoke-RestMethod -Uri $WebhookUrl -Method Post `
            -ContentType 'application/json' -Body $body -ErrorAction Stop | Out-Null
        Write-Verbose 'Teams message sent successfully'
    } catch {
        Write-Warning "Teams notification failed: $($_.Exception.Message)"
    }
}

Common Errors and Fixes

  • Office 365 Connectors being deprecated — transition to Power Automate flow. Microsoft announced that Office 365 Connectors (the webhook mechanism used here) will be retired. New integrations should use Power Automate HTTP trigger flows, which accept the same JSON payloads via a POST to a flow URL. Existing connectors continue to work through the transition period.
  • AdaptiveCard format differs from MessageCard format. Adaptive Cards require wrapping in an attachments array with contentType: 'application/vnd.microsoft.card.adaptive'. MessageCards go directly as the POST body with @type: 'MessageCard'. Mixing up the two formats results in the message appearing blank or failing silently.

Related Cmdlets / See Also

Wrapping Up

Teams webhook notifications bridge your PowerShell automation with your team’s communication hub. Use MessageCards for quick alerts, Adaptive Cards for rich formatting, and color-coded borders for instant status recognition. Store webhook URLs in environment variables, handle errors gracefully so a Teams outage does not break your script, and monitor the connector deprecation timeline to plan migration to Power Automate.

Send-Item -To