lib/TaskActions.ps1

function Start-TMTaskAction {
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$TMSession = "Default",

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [Int]$TaskId,

        [Parameter(Mandatory = $false)]
        [Switch]$Force,

        [Parameter(Mandatory = $false)]
        [Bool]$Api = $false
    )

    begin {
        # Get the session configuration
        Write-Verbose "Checking for cached TMSession"
        $TMSessionConfig = $global:TMSessions[$TMSession]
        Write-Debug "TMSessionConfig:"
        Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5)
        if (-not $TMSessionConfig) {
            throw "TMSession '$TMSession' not found. Use New-TMSession command before using features."
        }
    }

    process {
        if ($TMSessionConfig.TMRestSession -and $Api) {
            Write-Verbose "Using REST API endpoint"
            Write-Verbose "Forming web request parameters"
            $RestSplat = @{
                Uri                = "https://$($TMSessionConfig.TMServer)/tdstm/api/task/$($TaskId)/recordRemoteActionStarted?publicKey=$($TMSessionConfig.AuthTokens.PublicKey -replace "`n", '\n')"
                Method             = 'POST'
                WebSession         = $TMSessionConfig.TMRestSession
                SkipHttpErrorCheck = $true
                StatusCodeVariable = 'StatusCode'
            }
            Write-Debug "Web Request Parameters:"
            Write-Debug ($RestSplat | ConvertTo-Json -Depth 10)
            Write-Verbose "Invoking REST method"
            try {
                $Response = Invoke-RestMethod @RestSplat
                if ($StatusCode -notin 200, 204) {
                    if ($Force -and (${Response}?[0] -eq 'The action has already been invoked')) {
                        Write-Verbose "The action has already been invoked. Resetting action state."
                        Reset-TMTaskAction -TMSession $TMSession -TaskId $TaskId
                        $Response = Invoke-RestMethod @RestSplat
                    } elseif ($Response) {
                        throw $Response
                    } else {
                        throw "Status code $StatusCode does not indicate success"
                    }
                }
                Write-Debug "Response:"
                Write-Debug ($Response | ConvertTo-Json -Depth 10)
                $Response
                # [TMActionRequest]::new($Response)
            } catch {
                throw "Error while starting Task Action: $($_.Exception.Message)"
            }
        } else {
            Write-Verbose "Using web service endpoint"
            Write-Verbose "Forming web request parameters"
            $WebRequestSplat = @{
                Uri        = "https://$($TMSessionConfig.TMServer)/tdstm/ws/task/$($TaskId)/recordRemoteActionStarted"
                Method     = 'POST'
                WebSession = $TMSessionConfig.TMWebSession
                Body       = (@{
                        publicKey = $TMSessionConfig.AuthTokens.PublicKey
                    } | ConvertTo-Json -Compress)
            }
            Write-Debug "Web Request Parameters:"
            Write-Debug ($WebRequestSplat | ConvertTo-Json -Depth 10)
            Write-Verbose "Invoking web request"
            try {
                $Response = Invoke-WebRequest @WebRequestSplat
                Write-Debug "Response Content: $($Response.Content)"
                if ($Response.StatusCode -in 200, 204) {

                    ## Response should be JSON, but might not be, with an error
                    try {
                        $ResponseContent = $Response.Content | ConvertFrom-Json
                    } catch {
                        Write-Debug "Output is not JSON: $($Response.Content)"
                    }

                    # Check for errors in the response
                    $ResponseErrors = if ($Response.Headers.Keys -contains 'X-TM-Error-Message') {
                        $Response.Headers.'X-TM-Error-Message'
                    } elseif (${ResponseContent}?.status -eq 'error') {
                        ${ResponseContent}.errors
                    } else {
                        $null
                    }

                    # Deal with error(s) if possible
                    if ($ResponseErrors) {
                        switch ($ResponseErrors) {
                            {
                                $_ -contains 'The task must be in the Ready or Started state in order to invoke action' -or
                                $_ -eq 'The task must be in the Ready or Started state in order to invoke action'
                            } {
                                ## This Status message is handled by the TMBrokerSubject class itself. bypass throwing any specific message
                                ## TODO: Lookup the current task state so this can report who, when and where the task was executed
                                return
                            }

                            {
                                $_ -contains 'The action has already been invoked' -or
                                $_ -eq 'The action has already been invoked'
                            } {
                                if ($Force) {
                                    Write-Verbose "The action has already been invoked. Resetting action state."
                                    Reset-TMTaskAction -TMSession $TMSession -TaskId $TaskId
                                    Start-TMTaskAction -TMSession $TMSession -TaskId $TaskId -Force:$Force -Api $Api
                                } else {
                                    Write-Verbose "The action has already been invoked. -Force switch was not passed."
                                }
                                return
                            }

                            default {
                                Write-Host "Subject Task could not be invoked: $ResponseErrors" -ForegroundColor Magenta
                                return
                            }
                        }
                    } else {
                        ${ResponseContent}?.actionRequest
                    }
                } else {
                    throw "Status code $($Response.StatusCode) does not indicate success"
                }
            } catch {
                throw "Error while starting Task Action: $($_.Exception.Message)"
            }
        }
    }
}


function Stop-TMTaskAction {
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$TMSession = "Default",

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [Int]$TaskId,

        [Parameter(Mandatory = $false)]
        [Nullable[Int]]$ProjectId,

        [Parameter(Mandatory = $false)]
        [String]$Message,

        [Parameter(Mandatory = $false)]
        [String]$StdOut,

        [Parameter(Mandatory = $false)]
        [String]$StdErr,

        [Parameter(Mandatory = $false)]
        [Switch]$ReportError
    )

    begin {
        # Get the session configuration
        Write-Verbose "Checking for cached TMSession"
        $TMSessionConfig = $global:TMSessions[$TMSession]
        Write-Debug "TMSessionConfig:"
        Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5)
        if (-not $TMSessionConfig) {
            throw "TMSession '$TMSession' not found. Use New-TMSession command before using features."
        }

        # Make sure we have a bearer token for the REST endpoint
        Write-Verbose "Checking for TMRestSession with bearer token"
        if (-not $TMSessionConfig.TMRestSession) {
            throw "TMSession '$TMSession' is not logged into the TransitionManager API. Use New-TMSession -Api `$true command to connect to a compatible server."
        }

        # Use the TM session if a project id is not provided
        $ProjectId ??= $TMSessionConfig.UserContext.Project.id
    }

    process {
        Write-Verbose "Using REST API endpoint"
        Write-Verbose "Forming web request parameters"
        $Body = @{
            project = $ProjectId
        }
        if (-not [String]::IsNullOrWhiteSpace($StdOut)) {
            $Body.stdout = $StdOut
        }
        if (-not [String]::IsNullOrWhiteSpace($StdErr)) {
            $Body.stderr = $StdErr
        }
        if (-not [String]::IsNullOrWhiteSpace($Message)) {
            $Body.message = $Message
        }
        $RestSplat = @{
            Uri        = "https://$($TMSessionConfig.TMServer)/tdstm/api/task/$($TaskId)/$($ReportError.IsPresent ? 'actionError' : 'actionDone')"
            Method     = 'POST'
            Body       = ($Body | ConvertTo-Json)
            WebSession = $TMSessionConfig.TMRestSession
        }
        Write-Debug "Web Request Parameters:"
        Write-Debug ($RestSplat | ConvertTo-Json -Depth 10)
        Write-Verbose "Invoking REST method"
        try {
            $Response = Invoke-RestMethod @RestSplat
            Write-Debug "Response:"
            Write-Debug ($Response | ConvertTo-Json -Depth 10)
        } catch {
            throw "Error while stopping Task Action: $($_.Exception.Message)"
        }
    }
}


function Reset-TMTaskAction {
    param (
        [Parameter(Mandatory = $false, Position = 0)]
        [String]$TMSession = "Default",

        [Parameter(Mandatory = $true, ValueFromPipelineByPropertyName = $true)]
        [Alias('Id')]
        [Int]$TaskId
    )

    begin {
        # Get the session configuration
        Write-Verbose "Checking for cached TMSession"
        $TMSessionConfig = $global:TMSessions[$TMSession]
        Write-Debug "TMSessionConfig:"
        Write-Debug ($TMSessionConfig | ConvertTo-Json -Depth 5)
        if (-not $TMSessionConfig) {
            throw "TMSession '$TMSession' not found. Use New-TMSession command before using features."
        }
    }

    process {
        Write-Verbose "Forming web request parameters"
        $WebRequestSplat = @{
            Uri        = "https://$($TMSessionConfig.TMServer)/tdstm/ws/task/$($TaskId)/resetAction"
            Method     = 'POST'
            WebSession = $TMSessionConfig.TMWebSession
        }
        Write-Debug "Web Request Parameters:"
        Write-Debug ($WebRequestSplat | ConvertTo-Json -Depth 10)
        Write-Verbose "Invoking web request"
        try {
            $Response = Invoke-WebRequest @WebRequestSplat
            Write-Debug "Response Content:"
            Write-Debug ($Response.Content | ConvertTo-Json -Depth 10)
            if ($Response.StatusCode -notin 200, 204) {
                throw "Status code $($Response.StatusCode) does not indicate success"
            }
        } catch {
            throw "Error while resetting task action: $($_.Exception.Message)"
        }
    }
}