Public/Slack/Send-SlackNotification.ps1

function Send-SlackNotification
{
    [CmdletBinding(
        DefaultParameterSetName = "Default"
    )]
    Param(
        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 1
        )]
        [string]
        $Message,

        [Parameter(
            Mandatory = $true,
            ValueFromPipelineByPropertyName = $true,
            Position = 2
        )] 
        [string]
        $Webhook,

        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            Position = 3
        )]
        [string]
        $Channel,
    
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Attachments"
        )]
        [string]
        [Alias('color')]
        $Colour, 
    
        [Parameter(
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Attachments"
        )]
        [Alias('Push')]
        [string]
        $Title,

        [Parameter(
            Mandatory = $false, 
            ValueFromPipelineByPropertyName = $true
            )]
        [array]
        $UpperBlocks,

        [Parameter(Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Attachments"
        )]
        [array]
        $SubBlocks,

        [Parameter(Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = "Attachments"
        )]
        [array]
        $Fields
    )

    if ($Title.Length -gt 75)
    {
        throw "Title must be 75 characters or less"
    }

    if ($Fields -and $SubBlocks)
    {
        throw "You cannot specify both -Fields and SubBlocks"
    }

    # Let's initialize an empty hash table that we'll use to build up the JSON payload
    # By default we set the "text" field to the $message variable so we always have something to send
    $SlackBody = @{
        text        = $Message
        blocks      = @()
        attachments = @(
            @{
                blocks = @(
                )
            }
        )
    }

    if ($Channel)
    {
        $SlackBody.Add('channel', $Channel)
    }

    if ($Colour)
    {
        if ($Colour -notmatch '#[0-9A-Fa-f]{6}')
        {
            throw "Colour must match the hexadecimal colour format. (e.g #FF1234)"
        }
        $SlackBody.attachments[0].Add('color', $Colour)
    }

    # If we've got any "attachments" then we need to make sure our message is set in the "attachments" section
    If ($Title -or $Colour)
    {
        # Make sure the "text" param is blanked out otherwise it really messes things up :(
        $SlackBody.text = ""

        # Add a fallback message to the attachment - this affects things like pop-up's/toasts
        $SlackBody.attachments[0].Add('fallback', $Title)

        # If the message is longer than the max length we'll need to send it as raw text to the attachment instead.
        try
        {
            $MessageLength = ($Message | ConvertTo-Json).Length
        }
        catch
        {
            # Ignore errors
        }
        if (!$MessageLength)
        {
            Write-Verbose "Failed to determine JSON length of message, falling back to legacy method"
            $MessageLength = $Message.Length
        }
        if ($MessageLength -lt 3000 -and (!$Fields))
        {
            # Build up a message object, but add it later
            $MessageObject = @{
                type = 'section'
                text = @{
                    type = 'mrkdwn'
                    text = $Message
                }
            }
        }
        else
        {
            Write-Verbose "Message over 3000 characters or -Fields specified, falling back to legacy method"
            $SlackBody.attachments[0].Add('text', $Message)
            # We need to tell Slack that the text is a "mrkdwn" type
            $SlackBody.attachments[0].Add('mrkdwn_in', @('text'))
        }
    }

    if ($Title)
    {
        # If we're using the "attachments" for our main text then we need to add the title to the attachment instead of the blocks
        if ($SlackBody.attachments[0].text)
        {
            $SlackBody.attachments[0].Add('title', $Title)
        }
        else
        {
            # Build up the "title" object and add it to the body
            $TitleObject = @{
                type = 'header'
                text = @{
                    type  = 'plain_text'
                    text  = $Title
                    emoji = $true
                }
            }
            $SlackBody.attachments[0].blocks += $TitleObject
        }
    }

    # We need to add the message object _after_ the title otherwise things look wrong 😂
    if ($MessageObject)
    {
        $SlackBody.attachments[0].blocks += $MessageObject
    }

    # If we've got any sub-blocks then add them at the end of the message
    if ($SubBlocks)
    {
        if ($SlackBody.attachments[0].text)
        {
            Write-Warning "Cannot use SubBlocks due to length of main message, they will be ignored.`nTry using -UpperBlocks or -Fields instead"
            $SubBlocks = $null
        }
        if ($SubBlocks.count -gt 100)
        {
            Write-Warning "Cannot use SubBlocks due to too many blocks, they will be ignored. (Maximum 100 blocks)"
            $SubBlocks = $null
        }
        if ($SubBlocks.fields)
        {
            if ($SubBlocks.fields.count -gt 10)
            {
                Write-Warning "Cannot use SubBlocks with fields that contain more than 10 items, they will be ignored."
                $SubBlocks = $null
            }
        }
        if ($SubBlocks)
        {
            $SubBlocks | ForEach-Object {
                $SlackBody.attachments[0].blocks += $_
            }
        }
    }

        # If we've got any upper-blocks then add them in
        if ($UpperBlocks)
        {
            if ($UpperBlocks.count -gt 100)
            {
                Write-Warning "Cannot use SubBlocks due to too many blocks, they will be ignored. (Maximum 100 blocks)"
                $UpperBlocks = $null
            }
            if ($UpperBlocks.fields)
            {
                if ($UpperBlocks.fields.count -gt 10)
                {
                    Write-Warning "Cannot use SubBlocks with fields that contain more than 10 items, they will be ignored."
                    $UpperBlocks = $null
                }
            }
            if ($UpperBlocks)
            {
                $UpperBlocks | ForEach-Object {
                    $SlackBody.blocks += $_
                }
            }
        }

        # If we've got any fields then add them in
        if ($Fields)
        {
            # 'fields' and 'blocks' cannot be used together, so remove blocks if we've got them
            $SlackBody.attachments[0].remove('blocks')
            $SlackBody.attachments[0].Add('fields', @())
            if ($Fields.Count -gt 3)
            {
                Write-Warning "Cannot use Fields with more than 3 items, they will be ignored."
                $Fields = $null
            }
            if ($Fields)
            {
                $Fields | ForEach-Object {
                    $SlackBody.attachments[0].fields += $_
                }
            }
        }

    # Convert with a reasonable depth, we have a lot of nested objects!
    $ConvertedBody = $SlackBody | ConvertTo-Json -Depth 10

    Write-Debug $ConvertedBody

    try
    {
        Invoke-RestMethod -Uri $Webhook -Method Post -Body $ConvertedBody -ErrorAction Stop
    }
    catch
    {
        # We can't control what a user enters in the SubBlocks parameter, so try to warn them if they've got something wrong
        $AdditionalError = ""
        if ($SubBlocks)
        {
            $AdditionalError += "`nYou are using SubBlocks, it's possible that your SubBlocks are malformed, try removing them and running the command again."
        }
        if ($UpperBlocks)
        {
            $AdditionalError += "`nYou are using UpperBlocks, it's possible that your UpperBlocks are malformed, try removing them and running the command again."
        }
        if ($Fields)
        {
            $AdditionalError += "`nYou are using Fields, it's possible that your Fields are malformed, try removing them and running the command again."
        }
        Write-Error "Failed to send Slack notification.$AdditionalError.`n$($_.Exception.Message)"
    }
}