scriptdeck.sdPlugin/Send-StreamDeck.ps1

function Send-StreamDeck {
    <#
    .Synopsis
        Sends messages to a StreamDeck
    .Description
        Sends messages to a StreamDeck.
        
        This function will be used within StreamDeck plugins.
    .Link
        Recieve-StreamDeck
    .Link
        https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/
    #>

    [CmdletBinding(SupportsShouldProcess,DefaultParameterSetName='EventName')]
    param(
    # The name of the event
    [Parameter(Mandatory,Position=0,ValueFromPipelineByPropertyName,ParameterSetName='EventName')]
    [Alias('Event')]
    [string]
    $EventName,

    # The event payload.
    [Parameter(Position=2,ParameterSetName='EventName',ValueFromPipelineByPropertyName)]
    [PSObject]
    $Payload,

    # If set, will send a showOk event to the Stream Deck application.
    # This will temporarily show an OK checkmark icon on the image displayed by an instance of an action.
    [Parameter(Mandatory,ParameterSetName='showOK',ValueFromPipelineByPropertyName)]
    [switch]
    $ShowOK,

    # If set, will send a showOk event to the Stream Deck application.
    # This will temporarily show an alert icon on the image displayed by an instance of an action.
    [Parameter(Mandatory,ParameterSetName='showAlert',ValueFromPipelineByPropertyName)]
    [switch]
    $ShowAlert,

    # If set, will send an openURL event to the Stream Deck application.
    # This will temporarily show an alert icon on the image displayed by an instance of an action.
    [Parameter(Mandatory,ParameterSetName='openURL',ValueFromPipelineByPropertyName)]
    [uri]
    $OpenURL,

    # If set, will send an openURL event to the Stream Deck application.
    # This will temporarily show an alert icon on the image displayed by an instance of an action.
    [Parameter(Mandatory,ParameterSetName='logMessage',ValueFromPipelineByPropertyName)]
    [string]
    $LogMessage,

    # If provided will send a showImage event to the Stream Deck application using the contents of the file in ImagePath
    [Parameter(Mandatory,ParameterSetName='setImage',ValueFromPipelineByPropertyName)]
    [Alias('Fullname')]
    [string]
    $ImagePath,

    # The state index of an image or title. Defaults to zero.
    [Parameter(ParameterSetName='setTitle',ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName='setImage',ValueFromPipelineByPropertyName)]
    [int]
    $State = 0,

    # The target of a title or image change. Valid values are: both, hardware, and software.
    [Parameter(ParameterSetName='setTitle',ValueFromPipelineByPropertyName)]
    [Parameter(ParameterSetName='setImage',ValueFromPipelineByPropertyName)]
    [ValidateSet('both','hardware', 'software')]
    [string]
    $EventTarget = 'both',

    # The event context.
    # If not provided, the global variable STREAMDECK_CONTEXT will be used
    [Parameter(Position=3,ValueFromPipelineByPropertyName)]
    [string]
    $Context,

    # The web socket.
    # If not provided, the global variable STREAMDECK_WEBSOCKET will be used.
    [Parameter(Position=4,ValueFromPipelineByPropertyName)]
    [Net.WebSockets.ClientWebSocket]
    $Websocket
    )
    process {
        # If no -WebSocket was provided, use the $global:STREAMDECK_WEBSOCKET
        if (-not $Websocket -and $Global:STREAMDECK_WEBSOCKET) {
            $Websocket = $Global:STREAMDECK_WEBSOCKET 
        }

        # A number of different parameter sets are named to reflect the EventName StreamDeck expects.
        # If we find an argument that hints that this is true
        if ($ShowOK -or $ShowAlert -or $OpenURL -or $LogMessage -or $ImagePath) {
            $EventName = $PSCmdlet.ParameterSetName # set -EventName to the $psCmdlet.ParameterSetName.
        }

        if ($OpenURL) { # If we're going to -OpenURL,
            $Payload = @{url="$OpenURL"} # the payload is a single property, URL.
        }

        if ($LogMessage) { # If we're going to -LogMessage
            $Payload = @{message=$LogMessage} # the payload is a single property, message.
        }
        
        if ($ImagePath) { # If we're going to send an image,
            # find the file
            $resolvedPath = $ExecutionContext.SessionState.Path.GetResolvedPSPathFromPSPath($ImagePath) 
            if (-not $resolvedPath) { return } 
            $resolvedItem = Get-Item -LiteralPath $resolvedPath 
            if (-not $resolvedItem) { return } 
            # and check that it actually is an image.
            $imageExtensions = '.svg','.png','.jpg','.gif','.bmp' 
            if ($resolvedItem.Extension -notin $imageExtensions) {
                Write-Error "-ImagePath '$ImagePath' has an invalid extension. Valid extensions are: $imageExtensions"
                return
            }
            $eventTargetParameterAttributes = $MyInvocation.MyCommand.Parameters['EventTarget'].Attributes
            if ($resolvedItem.Extension -eq '.svg') {
                $Payload = [Ordered]@{
                    image = "data:image/svg+xml;charset=utf8,$(
                        [IO.File]::ReadAllText($resolvedItem.FullName, [Text.Encoding]::UTF8)
                    )"

                    target = $eventTargetParameterAttributes.ValidValues.IndexOf($EventTarget.ToLower())
                    state  = $State
                }
            } else {
                $Payload = [Ordered]@{
                    image = "data:image/$($resolvedItem.Extension.TrimStart('.'));base64,$(
                        [Convert]::ToBase64String([IO.File]::ReadAllBytes($resolvedItem.FullName))
                    )"

                    target = $eventTargetParameterAttributes.ValidValues.IndexOf($EventTarget.ToLower())
                    state  = $State
                }
            }
        }

        if ((-not $Context) -and $Global:STREAMDECK_CONTEXT) {
            $Context = $Global:STREAMDECK_CONTEXT
        }
        

        $WebSocketPayload = @{
            event   = $EventName
            context = $Context
            payload = $Payload
        }        

        if (-not $WebSocketPayload.payload -or $WebSocketPayload.payload.count -eq 0) {
            $WebSocketPayload.Remove('payload')
        }
        if (-not $WebSocketPayload.context) {
            $WebSocketPayload.Remove('context')
        }
        if ($WhatIfPreference) {
            return $WebSocketPayload
        }
        
        if (-not $PSCmdlet.ShouldProcess("Send $($WebSocketPayload | ConvertTo-json)")) {
            return
        }
        if (-not $Context -and -not $PluginUUID) {
            Write-Error "Must provide -Context" -ErrorId Context.Missing -Category InvalidArgument
            return
        }

        if (-not $Websocket ){
            Write-Error "Must provide a -WebSocket" -ErrorId WebSocket.Missing -Category ConnectionError
            return
        }
        $PayloadJson  = $WebSocketPayload | ConvertTo-Json -Depth 100        
        $SendSegment  = [ArraySegment[Byte]]::new([Text.Encoding]::UTF8.GetBytes($PayloadJson))        
        $SendTask     = $Websocket.SendAsync($SendSegment, 'Binary', $true, [Threading.CancellationToken]::new($false))
        while (!$SendTask.IsCompleted) {
            Start-Sleep -Milliseconds 11
        }        
    }
}