Public/Schedules/Start-JIMSchedule.ps1

# Copyright (c) Tetron Limited. All rights reserved.
# Licensed under the Tetron Commercial License. See LICENSE file in the project root.

function Start-JIMSchedule {
    <#
    .SYNOPSIS
        Manually triggers a Schedule execution in JIM.
 
    .DESCRIPTION
        Manually starts a Schedule execution, regardless of its trigger type
        or enabled state. The schedule runs through all its configured steps.
 
    .PARAMETER Id
        The unique identifier (GUID) of the Schedule to start.
 
    .PARAMETER Wait
        Waits for the Schedule Execution to finish (Complete,
        CompleteWithError, Failed or Cancelled), checking every 5 seconds and
        showing progress, then returns it.
 
    .PARAMETER Timeout
        How long -Wait waits before it gives up with a warning. The Schedule
        Execution carries on in JIM.
 
    .PARAMETER PassThru
        Returns the new Schedule Execution.
 
    .OUTPUTS
        With -PassThru, the new Schedule Execution as it stands; with -Wait, the execution once it has finished, or
        as it stands when the timeout is reached; otherwise nothing.
 
        JIM.ScheduleExecution
        StepDisplay (string): The step group the execution has reached, as one sentence naming it, for example Step
            2 of 5: 2 in parallel, matching what the portal shows in Admin > Operations > Activity.
 
    .EXAMPLE
        Start-JIMSchedule -Id "12345678-1234-1234-1234-123456789012"
 
        Triggers the specified Schedule to run.
 
    .EXAMPLE
        Get-JIMSchedule -Name "Delta Sync" | Start-JIMSchedule -PassThru
 
        Triggers the "Delta Sync" schedule and returns the execution object.
 
    .EXAMPLE
        Start-JIMSchedule -Id "12345678-..." -Wait -Timeout ([TimeSpan]::FromMinutes(60))
 
        Triggers the schedule and waits up to 60 minutes for it to complete.
 
    .LINK
        Get-JIMSchedule
        Get-JIMScheduleExecution
        Stop-JIMScheduleExecution
    #>

    [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')]
    [OutputType('JIM.ScheduleExecution')]
    param(
        [Parameter(Mandatory, ValueFromPipelineByPropertyName)]
        [Alias('ScheduleId')]
        [guid]$Id,

        [switch]$Wait,

        [TimeSpan]$Timeout = [TimeSpan]::FromMinutes(30),

        [switch]$PassThru
    )

    process {
        # Check connection first
        if (-not $script:JIMConnection) {
            Write-Error "You are not connected to JIM. Run Connect-JIM -Url <your JIM URL> to authenticate, then try again."
            return
        }

        if ($PSCmdlet.ShouldProcess($Id, "Start Schedule")) {
            Write-Verbose "Starting Schedule: $Id"

            try {
                $response = Invoke-JIMApi -Endpoint "/api/v1/schedules/$Id/run" -Method 'POST'
                $executionId = $response.executionId

                Write-Verbose "Started Schedule Execution: $executionId"

                # Fetch the full execution object to return consistent data
                $execution = Invoke-JIMApi -Endpoint "/api/v1/schedule-executions/$executionId"

                if ($Wait) {
                    $startTime = [DateTime]::UtcNow
                    $pollInterval = 5  # seconds
                    $terminalStatuses = @('Complete', 'CompleteWithError', 'Failed', 'Cancelled')

                    Write-Verbose "Waiting for execution to complete (timeout: $Timeout)..."

                    while ($true) {
                        $currentExecution = Invoke-JIMApi -Endpoint "/api/v1/schedule-executions/$executionId"
                        # Keep the latest state, so a wait that times out returns the run as it now stands
                        $execution = $currentExecution

                        # The API serialises ScheduleExecutionStatus by name, never as an ordinal
                        # (JsonStringEnumConverter with allowIntegerValues:false; see ApiJsonConfiguration),
                        # so match on the terminal status names. Comparing the string numerically is
                        # what broke this before: PowerShell types a comparison by its left operand, so
                        # "$status -ge 2" was a string comparison against '2' that every status name
                        # satisfied, and the loop exited on its first poll.
                        if ($currentExecution.status -in $terminalStatuses) {
                            Write-Verbose "Execution finished with status: $($currentExecution.status)"
                            break
                        }

                        # Check timeout
                        $elapsed = [DateTime]::UtcNow - $startTime
                        if ($elapsed -ge $Timeout) {
                            Write-Warning "Timeout waiting for schedule execution to complete. Execution ID: $executionId"
                            break
                        }

                        # Display progress
                        $progressPercent = if ($currentExecution.totalSteps -gt 0) {
                            [int](($currentExecution.currentStepIndex / $currentExecution.totalSteps) * 100)
                        } else { 0 }

                        Write-Progress -Activity "Waiting for Schedule Execution" `
                            -Status "Step $($currentExecution.currentStepIndex + 1) of $($currentExecution.totalSteps)" `
                            -PercentComplete $progressPercent

                        Start-Sleep -Seconds $pollInterval
                    }

                    Write-Progress -Activity "Waiting for Schedule Execution" -Completed
                    Add-JIMScheduleExecutionStepDisplay -Execution $execution
                }
                elseif ($PassThru) {
                    Add-JIMScheduleExecutionStepDisplay -Execution $execution
                }
            }
            catch {
                Write-Error "Failed to start Schedule: $_"
            }
        }
    }
}