Public/Send-WingetWebhook.ps1

function Send-WingetWebhook {
    <#
    .SYNOPSIS
        Send package event notifications to Discord, Slack, or Teams webhooks.
 
    .DESCRIPTION
        Push rich notifications to chat platforms when package events occur:
        updates available, maintenance completed, drift detected, installs finished,
        or compliance violations found.
 
        Supports Discord embeds, Slack Block Kit, and Teams Adaptive Cards.
        Configure once with Set-WingetBatchConfig, then use anywhere.
 
    .PARAMETER Platform
        Target platform: Discord, Slack, or Teams.
 
    .PARAMETER WebhookUrl
        The webhook URL. If not specified, reads from saved config.
 
    .PARAMETER Event
        Event type: UpdatesAvailable, MaintenanceComplete, DriftDetected,
        InstallComplete, ComplianceViolation, Custom.
 
    .PARAMETER Title
        Notification title (for Custom events).
 
    .PARAMETER Message
        Notification body/message content.
 
    .PARAMETER Data
        Hashtable of additional data to include in the notification.
 
    .PARAMETER SaveConfig
        Save the webhook URL to config for future use.
 
    .PARAMETER Test
        Send a test notification to verify the webhook works.
 
    .EXAMPLE
        Send-WingetWebhook -Platform Discord -WebhookUrl "https://discord.com/api/webhooks/..." -Event UpdatesAvailable -Data @{Count=5}
        Notify Discord that 5 updates are available.
 
    .EXAMPLE
        Send-WingetWebhook -Platform Slack -Event MaintenanceComplete -Message "Weekly maintenance: 12 updated, 0 failed"
        Notify Slack channel about maintenance results.
 
    .EXAMPLE
        Send-WingetWebhook -Platform Teams -Test
        Send a test notification to verify Teams webhook.
 
    .EXAMPLE
        Send-WingetWebhook -Platform Discord -SaveConfig -WebhookUrl "https://..."
        Save Discord webhook URL to config for automatic use.
 
    .NOTES
        Author: Matthew Bubb
        Webhook URLs are stored in ~/.wingetbatch/config.json (not encrypted).
    #>

    [CmdletBinding(DefaultParameterSetName = 'Send')]
    param(
        [Parameter(Mandatory)]
        [ValidateSet('Discord', 'Slack', 'Teams')]
        [string]$Platform,

        [Parameter()]
        [string]$WebhookUrl,

        [Parameter(ParameterSetName = 'Send', Mandatory)]
        [ValidateSet('UpdatesAvailable', 'MaintenanceComplete', 'DriftDetected', 'InstallComplete', 'ComplianceViolation', 'Custom')]
        [string]$Event,

        [Parameter(ParameterSetName = 'Send')]
        [string]$Title,

        [Parameter(ParameterSetName = 'Send')]
        [string]$Message,

        [Parameter(ParameterSetName = 'Send')]
        [hashtable]$Data,

        [switch]$SaveConfig,

        [Parameter(ParameterSetName = 'Test', Mandatory)]
        [switch]$Test
    )

    # --- Resolve webhook URL ---
    $configDir = Get-WingetBatchConfigDir
    $configPath = Join-Path $configDir "config.json"
    $config = @{}
    if (Test-Path $configPath) {
        $config = Get-Content $configPath -Raw | ConvertFrom-Json -AsHashtable
    }

    if (-not $WebhookUrl) {
        $key = "webhook_$($Platform.ToLower())"
        $WebhookUrl = $config[$key]
    }

    if (-not $WebhookUrl) {
        Write-Error "No webhook URL for $Platform. Provide -WebhookUrl or save one with -SaveConfig."
        return
    }

    # --- Save config ---
    if ($SaveConfig) {
        $key = "webhook_$($Platform.ToLower())"
        $config[$key] = $WebhookUrl
        $config | ConvertTo-Json -Depth 5 | Set-Content -Path $configPath -Encoding UTF8
        Write-Host " ✓ $Platform webhook URL saved to config." -ForegroundColor Green
        if ($Test) { } else { return }
    }

    # --- Build notification content ---
    $hostname = $env:COMPUTERNAME
    $timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"

    if ($Test) {
        $Event = 'Custom'
        $Title = "WingetBatch Test Notification"
        $Message = "Webhook integration is working! Sent from $hostname at $timestamp."
        $Data = @{ Platform = $Platform; Hostname = $hostname }
    }

    # Default titles/messages per event
    $eventDefaults = @{
        'UpdatesAvailable' = @{ Title = "📦 Updates Available"; Color = 0xFFA500; Emoji = "📦" }
        'MaintenanceComplete' = @{ Title = "🔧 Maintenance Complete"; Color = 0x00CC00; Emoji = "🔧" }
        'DriftDetected' = @{ Title = "⚠️ Configuration Drift Detected"; Color = 0xFF4444; Emoji = "⚠️" }
        'InstallComplete' = @{ Title = "✅ Installation Complete"; Color = 0x00AAFF; Emoji = "✅" }
        'ComplianceViolation' = @{ Title = "🚨 Compliance Violation"; Color = 0xFF0000; Emoji = "🚨" }
        'Custom' = @{ Title = "WingetBatch Notification"; Color = 0x7289DA; Emoji = "📋" }
    }

    $defaults = $eventDefaults[$Event]
    if (-not $Title) { $Title = $defaults.Title }
    if (-not $Message) { $Message = "Event: $Event on $hostname at $timestamp" }

    # --- Format per platform ---
    $body = $null
    $contentType = 'application/json'

    switch ($Platform) {
        'Discord' {
            # Discord embed format
            $fields = @()
            if ($Data) {
                foreach ($entry in $Data.GetEnumerator()) {
                    $fields += @{ name = $entry.Key; value = "$($entry.Value)"; inline = $true }
                }
            }
            $fields += @{ name = "Host"; value = $hostname; inline = $true }
            $fields += @{ name = "Time"; value = $timestamp; inline = $true }

            $body = @{
                username = "WingetBatch"
                embeds = @(@{
                    title = "$($defaults.Emoji) $Title"
                    description = $Message
                    color = $defaults.Color
                    fields = $fields
                    footer = @{ text = "WingetBatch v2.8.0 | $hostname" }
                    timestamp = (Get-Date -ToString 'o')
                })
            } | ConvertTo-Json -Depth 10 -Compress
        }
        'Slack' {
            # Slack Block Kit format
            $blocks = @(
                @{ type = "header"; text = @{ type = "plain_text"; text = "$($defaults.Emoji) $Title" } }
                @{ type = "section"; text = @{ type = "mrkdwn"; text = $Message } }
            )

            if ($Data -and $Data.Count -gt 0) {
                $dataText = ($Data.GetEnumerator() | ForEach-Object { "*$($_.Key):* $($_.Value)" }) -join "`n"
                $blocks += @{ type = "section"; text = @{ type = "mrkdwn"; text = $dataText } }
            }

            $blocks += @{ type = "context"; elements = @(@{ type = "mrkdwn"; text = "WingetBatch | $hostname | $timestamp" }) }

            $body = @{ blocks = $blocks } | ConvertTo-Json -Depth 10 -Compress
        }
        'Teams' {
            # Teams Adaptive Card (MessageCard format for simplicity)
            $sections = @(@{
                activityTitle = $Title
                activitySubtitle = "$hostname - $timestamp"
                text = $Message
                facts = @()
            })

            if ($Data) {
                foreach ($entry in $Data.GetEnumerator()) {
                    $sections[0].facts += @{ name = $entry.Key; value = "$($entry.Value)" }
                }
            }
            $sections[0].facts += @{ name = "Host"; value = $hostname }

            $body = @{
                '@type' = "MessageCard"
                '@context' = "http://schema.org/extensions"
                themeColor = $defaults.Color.ToString("X6")
                summary = $Title
                sections = $sections
            } | ConvertTo-Json -Depth 10 -Compress
        }
    }

    # --- Send ---
    try {
        $response = Invoke-RestMethod -Uri $WebhookUrl -Method Post -Body $body -ContentType $contentType -ErrorAction Stop
        Write-Host " ✓ Notification sent to $Platform ($Event)" -ForegroundColor Green
        return [PSCustomObject]@{
            Success = $true
            Platform = $Platform
            Event = $Event
            Timestamp = $timestamp
        }
    }
    catch {
        Write-Error "Failed to send $Platform webhook: $($_.Exception.Message)"
        return [PSCustomObject]@{
            Success = $false
            Platform = $Platform
            Event = $Event
            Error = $_.Exception.Message
        }
    }
}