Run Octopus Deploy Runbook

Octopus.Script exported 2024-07-23 by millerjn21 belongs to ‘Octopus’ category.

This step will kick off a runbook. The runbook can exist in the same space and project, or it can exist on a different instance altogether.

Please Note: Prompted variable values have to be text or sensitive variables. Variable types such as AWS or Azure accounts will not work.

This step should be called from a worker machine. If it is called from a target and the runbook runs on the same target you run the risk of a deadlock.

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

Runbook Name

Run.Runbook.Name =

Required

The name of the runbook to run.

Base Url

Run.Runbook.Base.Url = #{Octopus.Web.ServerUri}

Required

The base URL of your instance, IE https://samples.octopus.app. Defaults to the system variable Octopus.Web.ServerUri.

Api Key

Run.Runbook.Api.Key =

Required

The API key of a user who has permissions to run the runbook specified

Runbook Space

Run.Runbook.Space.Name = #{Octopus.Space.Name}

Required

The name of the space the child project is located in. Defaults to the current space name using the variable #{Octopus.Space.Name}.

Project name

Run.Runbook.Project.Name =

Optional

The name of the project containing the runbook. If the project name is not specified then it will use the first runbook with the matching runbook name it can find.

Environment Name

Run.Runbook.Environment.Name = #{Octopus.Environment.Name}

Required

Name of the environment to run the runbook on. The default is the current environment name using the system variable #{Octopus.Environment.Name}.

Tenant Name

Run.Runbook.Tenant.Name =

Optional

Name of Tenant to run the runbook for. If you want to run a runbook using the same tenant as the deployment or runbook run then use the system variable #{Octopus.Deployment.Tenant.Name}.

Use Published Snapshot

Run.Runbook.UsePublishedSnapShot = True

Indicates if the run should use the most recent published snapshot. When not set it will use the most recent snapshot, regardless if it was published or not.

Default is to use only published snapshots.

If you select to use unpublished snapshots then the runbook will:

  • If no published snapshots exist, then it will create a new unpublished snapshot on each run.
  • Create a new snapshot if anything has changed since the last published snapshot.
  • Use the existing published snapshot if nothing has changed since it was last published.

Please note: A runbook is considered “changed” when any of the following are true:

  • Runbook process has changed.
  • Project variables have changed.
  • Library Variable Sets referenced by the project have changed.
  • A newer version of any referenced packages is found.

Wait for finish

Run.Runbook.Waitforfinish = True

Indicates if the process should be paused and wait for the runbook to finish

Use Guided Failure

Run.Runbook.UseGuidedFailure =

Should the runbook run use guided failure (not a good idea if you are waiting for this to finish)

Cancel Seconds

Run.Runbook.CancelInSeconds = 1800

Optional

The number of seconds to wait before canceling the runbook run. Default is 1800 seconds (30 minutes).

Prompted Variable Values

Run.Runbook.PromptedVariables =

Optional

Values for any prompted variables for the runbook. Each new line represents a new variable. This will only work with string variable types, text and sensitive values.

Use the format Name::Value IE:

PromptedVariableName::My Super Awesome Value

OtherPromptedVariable::Other Super Awesome Value

Scheduling

Run.Runbook.DateTime = N/A

Optional

Schedule the runbook to run in the future. Please note, if this is set, the Wait for Finish option is ignored.

Uses DateTime.TryParse and specific keywords to determine the value sent in. Supported formats:

  • 7:00 PM will deploy at 7:00 PM today
  • 21:00 will deploy at 21:00 hours or 9 PM today
  • YYYY-MM-DD HH:mm:ss or 2021-01-14 21:00:00 will deploy at 9 PM on the 14th of January, 2021
  • YYYY/MM/DD HH:mm:ss or 2021/03/20 22:00:00 will deploy at 10 PM on the 20th of March, 2021
  • MM/DD/YYYY HH:mm:ss or 06/25/2021 19:00:00 will deploy at 7 PM on the 25th of June, 2021
  • DD MMM YYYY HH:mm:ss or 01 Jan 2021 18:00:00 will deploy at 6 PM on the 1st of January, 2021
  • Tomorrow HH:mm:ss or Tomorrow 18:00:00 will deploy at 6 PM tomorrow

Uses the Octopus Server’s Timezone. The queue expiry time will be set to 1 hour from the supplied date.

Default is N/A or not applicable.

Machine List

Run.Runbook.Machines = N/A

Optional

A comma-separated list of Machine Ids or Machine Names to target with this runbook.

Please Note: The step template will remove any machines that cannot be found or are not applicable to the runbook.

The default is N/A. Set to #{Octopus.Deployment.Machines} if you want to target the same machines as the current runbook run or deployment.

Auto approve runbook manual interventions

Run.Runbook.AutoApproveManualInterventions = No

Optional

If the child project has manual interventions the step will look for manual interventions in the parent project or parent runbook.

When a manual intervention in the parent project or parent runbook is found it will check that user’s assigned teams. If that user’s assigned teams can approve the child project it will do so.

Please note, the user associated with the API key must be able to approve the child project manual interventions as well.

The default is No, allow this to happen. Set it to Yes to enable this functionality.

Custom Manual Intervention Notes Toggle

Run.Runbook.CustomNotes.Toggle = False

Check this box if you would like custom notes to be submitted with the automatic manual intervention approval.

Custom Manual Intervention Approval Notes

Run.Runbook.CustomNotes =

Use this field to supply custom manual intervention notes if the above toggle is checked.

Environment name to pull approvals from

Run.Runbook.ManualIntervention.EnvironmentToUse = #{Octopus.Environment.Name}

Optional

The name of the environment you wish to pull the approvals from for the parent project or parent runbook. It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment. If this step is being called from a runbook, it will look at the latest runbook run for the environment specified

Used when you are deploying to Production but want to pull the approvals from Staging or a Prod Approval environment.

Defaults to the current environment name.

Script body

Steps based on this template will execute the following PowerShell script.

[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12

# Octopus Variables
$octopusSpaceId = $OctopusParameters["Octopus.Space.Id"]
$parentTaskId = $OctopusParameters["Octopus.Task.Id"]
$parentReleaseId = $OctopusParameters["Octopus.Release.Id"]
$parentChannelId = $OctopusParameters["Octopus.Release.Channel.Id"]
$parentEnvironmentId = $OctopusParameters["Octopus.Environment.Id"]
$parentRunbookId = $OctopusParameters["Octopus.Runbook.Id"]
$parentEnvironmentName = $OctopusParameters["Octopus.Environment.Name"]
$parentReleaseNumber = $OctopusParameters["Octopus.Release.Number"]

# Step Template Parameters
$runbookRunName = $OctopusParameters["Run.Runbook.Name"]
$runbookBaseUrl = $OctopusParameters["Run.Runbook.Base.Url"]
$runbookApiKey = $OctopusParameters["Run.Runbook.Api.Key"]
$runbookEnvironmentName = $OctopusParameters["Run.Runbook.Environment.Name"]
$runbookTenantName = $OctopusParameters["Run.Runbook.Tenant.Name"]
$runbookWaitForFinish = $OctopusParameters["Run.Runbook.Waitforfinish"]
$runbookUseGuidedFailure = $OctopusParameters["Run.Runbook.UseGuidedFailure"]
$runbookUsePublishedSnapshot = $OctopusParameters["Run.Runbook.UsePublishedSnapShot"]
$runbookPromptedVariables = $OctopusParameters["Run.Runbook.PromptedVariables"]
$runbookCancelInSeconds = $OctopusParameters["Run.Runbook.CancelInSeconds"]
$runbookProjectName = $OctopusParameters["Run.Runbook.Project.Name"]
$runbookCustomNotesToggle = $OctopusParameters["Run.Runbook.CustomNotes.Toggle"]
$runbookCustomNotes = $OctopusParameters["Run.Runbook.CustomNotes"]

$runbookSpaceName = $OctopusParameters["Run.Runbook.Space.Name"]
$runbookFutureDeploymentDate = $OctopusParameters["Run.Runbook.DateTime"]
$runbookMachines = $OctopusParameters["Run.Runbook.Machines"]
$autoApproveRunbookRunManualInterventions = $OctopusParameters["Run.Runbook.AutoApproveManualInterventions"]
$approvalEnvironmentName = $OctopusParameters["Run.Runbook.ManualIntervention.EnvironmentToUse"]

function Write-OctopusVerbose
{
    param($message)
    
    Write-Verbose $message  
}

function Write-OctopusInformation
{
    param($message)
    
    Write-Host $message  
}

function Write-OctopusSuccess
{
    param($message)

    Write-Highlight $message 
}

function Write-OctopusWarning
{
    param($message)

    Write-Warning "$message" 
}

function Write-OctopusCritical
{
    param ($message)

    Write-Error "$message" 
}

function Invoke-OctopusApi
{
    param
    (
        $octopusUrl,
        $endPoint,
        $spaceId,
        $apiKey,
        $method,
        $item     
    )

    if ([string]::IsNullOrWhiteSpace($SpaceId))
    {
        $url = "$OctopusUrl/api/$EndPoint"
    }
    else
    {
        $url = "$OctopusUrl/api/$spaceId/$EndPoint"    
    }  

    try
    {
        if ($null -eq $item)
        {
            Write-Verbose "No data to post or put, calling bog standard invoke-restmethod for $url"
            return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -ContentType 'application/json; charset=utf-8'
        }

        $body = $item | ConvertTo-Json -Depth 10
        Write-Verbose $body

        Write-Host "Invoking $method $url"
        return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -Body $body -ContentType 'application/json; charset=utf-8'
    }
    catch
    {
        if ($null -ne $_.Exception.Response)
        {
            if ($_.Exception.Response.StatusCode -eq 401)
            {
                Write-Error "Unauthorized error returned from $url, please verify API key and try again"
            }
            elseif ($_.Exception.Response.statusCode -eq 403)
            {
                Write-Error "Forbidden error returned from $url, please verify API key and try again"
            }
            else
            {                
                Write-Error -Message "Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )"
            }            
        }
        else
        {
            Write-Verbose $_.Exception
        }
    }

    Throw "There was an error calling the Octopus API please check the log for more details"
}

function Test-RequiredValues
{
	param (
    	$variableToCheck,
        $variableName
    )
    
    if ([string]::IsNullOrWhiteSpace($variableToCheck) -eq $true)
    {
    	Write-OctopusCritical "$variableName is required."
        return $false
    }
    
    return $true
}

function GetCheckBoxBoolean
{
	param (
    	[string]$Value
    )
    
    if ([string]::IsNullOrWhiteSpace($value) -eq $true)
    {
    	return $false
    }
    
    return $value -eq "True"
}

function Get-FilteredOctopusItem
{
    param(
        $itemList,
        $itemName
    )

    if ($itemList.Items.Count -eq 0)
    {
        Write-OctopusCritical "Unable to find $itemName.  Exiting with an exit code of 1."
        Exit 1
    }  

    $item = $itemList.Items | Where-Object { $_.Name -eq $itemName}      

    if ($null -eq $item)
    {
        Write-OctopusCritical "Unable to find $itemName.  Exiting with an exit code of 1."
        exit 1
    }
    
    if ($item -is [array])
    {
    	Write-OctopusCritical "More than one item exists with the name $itemName.  Exiting with an exit code of 1."
        exit 1
    }

    return $item
}

function Get-OctopusItemFromListEndpoint
{
    param(
        $endpoint,
        $itemNameToFind,
        $itemType,
        $defaultUrl,
        $octopusApiKey,
        $spaceId,
        $defaultValue
    )
    
    if ([string]::IsNullOrWhiteSpace($itemNameToFind))
    {
    	return $defaultValue
    }
    
    Write-OctopusInformation "Attempting to find $itemType with the name of $itemNameToFind"
    
    $itemList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "$($endpoint)?partialName=$([uri]::EscapeDataString($itemNameToFind))&skip=0&take=100" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"    
    $item = Get-FilteredOctopusItem -itemList $itemList -itemName $itemNameToFind

    Write-OctopusInformation "Successfully found $itemNameToFind with id of $($item.Id)"

    return $item
}

function Get-MachineIdsFromMachineNames
{
    param (
        $targetMachines,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    $targetMachineList = $targetMachines -split ","
    $translatedList = @()

    foreach ($machineName in $targetMachineList)
    {
        Write-OctopusVerbose "Translating $machineName to an Id.  First checking to see if it is already an Id."
    	if ($machineName.Trim() -like "Machines*")
        {
            Write-OctopusVerbose "$machineName is already an Id, no need to look that up."
        	$translatedList += $machineName
            continue
        }
        
        $machineObject = Get-OctopusItemFromListEndpoint -itemNameToFind $machineName.Trim() -itemType "Deployment Target" -endpoint "machines" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey

        $translatedList += $machineObject.Id
    }

    return $translatedList
}

function Get-RunbookSnapshotIdToRun
{
    param (
        $runbookToRun,
        $runbookUsePublishedSnapshot,
        $defaultUrl,
        $octopusApiKey,
        $spaceId
    )

    $runbookSnapShotIdToUse = $runbookToRun.PublishedRunbookSnapshotId
    Write-OctopusInformation "The last published snapshot for $runbookRunName is $runbookSnapShotIdToUse"

    if ($null -eq $runbookSnapShotIdToUse -and $runbookUsePublishedSnapshot -eq $true)
    {
        Write-OctopusCritical "Use Published Snapshot was set; yet the runbook doesn't have a published snapshot.  Exiting."
        Exit 1
    }

    if ($runbookUsePublishedSnapshot -eq $true)
    {
        Write-OctopusInformation "Use published snapshot set to true, using the published runbook snapshot."
        return $runbookSnapShotIdToUse
    }

    if ($null -eq $runbookToRun.PublishedRunbookSnapshotId)
    {
        Write-OctopusInformation "There have been no published runbook snapshots, going to create a new snapshot."
        return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId
    }

    $runbookSnapShotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "runbookSnapshots/$($runbookToRun.PublishedRunbookSnapshotId)/runbookRuns/template" -method "Get" -item $null

    if ($runbookSnapShotTemplate.IsRunbookProcessModified -eq $false -and $runbookSnapShotTemplate.IsVariableSetModified -eq $false -and $runbookSnapShotTemplate.IsLibraryVariableSetModified -eq $false)
    {        
        Write-OctopusInformation "The runbook has not been modified since the published snapshot was created.  Checking to see if any of the packages have a new version."    
        $runbookSnapShot = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "runbookSnapshots/$($runbookToRun.PublishedRunbookSnapshotId)" -method "Get" -item $null
        $snapshotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "runbooks/$($runbookToRun.Id)/runbookSnapShotTemplate" -method "Get" -item $null

        foreach ($package in $runbookSnapShot.SelectedPackages)
        {
            foreach ($templatePackage in $snapshotTemplate.Packages)
            {
                if ($package.StepName -eq $templatePackage.StepName -and $package.ActionName -eq $templatePackage.ActionName -and $package.PackageReferenceName -eq $templatePackage.PackageReferenceName)
                {
                    $packageVersion = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "feeds/$($templatePackage.FeedId)/packages/versions?packageId=$($templatePackage.PackageId)&take=1" -method "Get" -item $null

                    if ($packageVersion -ne $package.Version)
                    {
                        Write-OctopusInformation "A newer version of a package was found, going to use that and create a new snapshot."
                        return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId                    
                    }
                }
            }
        }

        Write-OctopusInformation "No new package versions have been found, using the published snapshot."
        return $runbookToRun.PublishedRunbookSnapshotId
    }
    
    Write-OctopusInformation "The runbook has been modified since the snapshot was created, creating a new one."
    return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId
}

function New-RunbookUnpublishedSnapshot
{
    param (
        $runbookToRun,
        $defaultUrl,
        $octopusApiKey,
        $spaceId
    )

    $octopusProject = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "projects/$($runbookToRun.ProjectId)" -method "Get" -item $null
    $snapshotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "runbooks/$($runbookToRun.Id)/runbookSnapShotTemplate" -method "Get" -item $null

    $runbookPackages = @()
    foreach ($package in $snapshotTemplate.Packages)
    {
        $packageVersion = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "feeds/$($package.FeedId)/packages/versions?packageId=$($package.PackageId)&take=1" -method "Get" -item $null

        if ($packageVersion.TotalResults -le 0)
        {
            Write-Error "Unable to find a package version for $($package.PackageId).  This is required to create a new unpublished snapshot.  Exiting."
            exit 1
        }

        $runbookPackages += @{
            StepName = $package.StepName
            ActionName = $package.ActionName
            Version = $packageVersion.Items[0].Version
            PackageReferenceName = $package.PackageReferenceName
        }
    }

    $runbookSnapShotRequest = @{
        FrozenProjectVariableSetId = "variableset-$($runbookToRun.ProjectId)"
        FrozenRunbookProcessId = $($runbookToRun.RunbookProcessId)
        LibraryVariableSetSnapshotIds = @($octopusProject.IncludedLibraryVariableSetIds)
        Name = $($snapshotTemplate.NextNameIncrement)
        ProjectId = $($runbookToRun.ProjectId)
        ProjectVariableSetSnapshotId = "variableset-$($runbookToRun.ProjectId)"
        RunbookId = $($runbookToRun.Id)
        SelectedPackages = $runbookPackages
    }

    $newSnapShot = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint "runbookSnapshots" -method "POST" -item $runbookSnapShotRequest

    return $($newSnapShot.Id)
}

function Get-ProjectSlug
{
    param
    (
        $runbookToRun,
        $projectToUse,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    if ($null -ne $projectToUse)
    {
        return $projectToUse.Slug
    }

    $project = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint "projects/$($runbookToRun.ProjectId)" -method "GET" -item $null

    return $project.Slug
}

function Get-RunbookFormValues
{
    param (
        $runbookPreview,
        $runbookPromptedVariables        
    )

    $runbookFormValues = @{}

    if ([string]::IsNullOrWhiteSpace($runbookPromptedVariables) -eq $true)
    {
        return $runbookFormValues
    }    
    
    $promptedValueList = @(($runbookPromptedVariables -Split "`n").Trim())
    Write-OctopusInformation $promptedValueList.Length
    
    foreach($element in $runbookPreview.Form.Elements)
    {
    	$nameToSearchFor = $element.Control.Name
        $uniqueName = $element.Name
        $isRequired = $element.Control.Required
        
        $promptedVariablefound = $false
        
        Write-OctopusInformation "Looking for the prompted variable value for $nameToSearchFor"
    	foreach ($promptedValue in $promptedValueList)
        {
        	$splitValue = $promptedValue -Split "::"
            Write-OctopusInformation "Comparing $nameToSearchFor with provided prompted variable $($promptedValue[0])"
            if ($splitValue.Length -gt 1)
            {
            	if ($nameToSearchFor -eq $splitValue[0])
                {
                	Write-OctopusInformation "Found the prompted variable value $nameToSearchFor"
                	$runbookFormValues[$uniqueName] = $splitValue[1]
                    $promptedVariableFound = $true
                    break
                }
            }
        }
        
        if ($promptedVariableFound -eq $false -and $isRequired -eq $true)
        {
        	Write-OctopusCritical "Unable to find a value for the required prompted variable $nameToSearchFor, exiting"
            Exit 1
        }
    }

    return $runbookFormValues
}

function Invoke-OctopusDeployRunbook
{
    param (
        $runbookBody,
        $runbookWaitForFinish,
        $runbookCancelInSeconds,
        $projectNameForUrl,        
        $defaultUrl,
        $octopusApiKey,
        $spaceId,
        $parentTaskApprovers,
        $autoApproveRunbookRunManualInterventions,
        $parentProjectName,
        $parentReleaseNumber,
        $approvalEnvironmentName,
        $parentRunbookId,
        $parentTaskId
    )

    $runbookResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -item $runbookBody -method "POST" -endPoint "runbookRuns"

    $runbookServerTaskId = $runBookResponse.TaskId
    Write-OctopusInformation "The task id of the new task is $runbookServerTaskId"

    $runbookRunId = $runbookResponse.Id
    Write-OctopusInformation "The runbook run id is $runbookRunId"

    Write-OctopusSuccess "Runbook was successfully invoked, you can access the launched runbook [here]($defaultUrl/app#/$spaceId/projects/$projectNameForUrl/operations/runbooks/$($runbookBody.RunbookId)/snapshots/$($runbookBody.RunbookSnapShotId)/runs/$runbookRunId)"

    if ($runbookWaitForFinish -eq $false)
    {
        Write-OctopusInformation "The wait for finish setting is set to no, exiting step"
        return
    }
    
    if ($null -ne $runbookBody.QueueTime)
    {
    	Write-OctopusInformation "The runbook queue time is set.  Exiting step"
        return
    }

    Write-OctopusSuccess "The setting to wait for completion was set, waiting until task has finished"
    $startTime = Get-Date
    $currentTime = Get-Date
    $dateDifference = $currentTime - $startTime
	
    $taskStatusUrl = "tasks/$runbookServerTaskId"
    $numberOfWaits = 0    
    
    While ($dateDifference.TotalSeconds -lt $runbookCancelInSeconds)
    {
        Write-OctopusInformation "Waiting 5 seconds to check status"
        Start-Sleep -Seconds 5
        $taskStatusResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint $taskStatusUrl -method "GET" -item $null
        $taskStatusResponseState = $taskStatusResponse.State

        if ($taskStatusResponseState -eq "Success")
        {
            Write-OctopusSuccess "The task has finished with a status of Success"
            exit 0            
        }
        elseif($taskStatusResponseState -eq "Failed" -or $taskStatusResponseState -eq "Canceled")
        {
            Write-OctopusSuccess "The task has finished with a status of $taskStatusResponseState status, stopping the run/deployment"
            exit 1            
        }
        elseif($taskStatusResponse.HasPendingInterruptions -eq $true)
        {
            if ($autoApproveRunbookRunManualInterventions -eq "Yes")
            {
                Submit-RunbookRunForAutoApproval -createdRunbookRun $createdRunbookRun -parentTaskApprovers $parentTaskApprovers -defaultUrl $DefaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $approvalEnvironmentName -parentRunbookId $parentRunbookId -parentTaskId $parentTaskId
            }
            else
            {
                if ($numberOfWaits -ge 10)
                {
                    Write-OctopusSuccess "The child project has pending manual intervention(s).  Unless you approve it, this task will time out."
                }
                else
                {
                    Write-OctopusInformation "The child project has pending manual intervention(s).  Unless you approve it, this task will time out."                        
                }
            }
        }
        
        $numberOfWaits += 1
        if ($numberOfWaits -ge 10)
        {
        	Write-OctopusSuccess "The task state is currently $taskStatusResponseState"
        	$numberOfWaits = 0
        }
        else
        {
        	Write-OctopusInformation "The task state is currently $taskStatusResponseState"
        }  
        
        $startTime = $taskStatusResponse.StartTime
        if ($startTime -eq $null -or [string]::IsNullOrWhiteSpace($startTime) -eq $true)
        {        
        	Write-OctopusInformation "The task is still queued, let's wait a bit longer"
        	$startTime = Get-Date
        }
        $startTime = [DateTime]$startTime
        
        $currentTime = Get-Date
        $dateDifference = $currentTime - $startTime        
    }
    
    Write-OctopusSuccess "The cancel timeout has been reached, cancelling the runbook run"
    $cancelResponse = Invoke-RestMethod "$runbookBaseUrl/api/tasks/$runbookServerTaskId/cancel" -Headers $header -Method Post
    Write-OctopusSuccess "Exiting with an error code of 1 because we reached the timeout"
    exit 1
}

function Get-QueueDate
{
	param ( 
    	$futureDeploymentDate
    )
    
    if ([string]::IsNullOrWhiteSpace($futureDeploymentDate) -or $futureDeploymentDate -eq "N/A")
    {
    	return $null
    }
    
    $addOneDay = $false
    $textToParse = $futureDeploymentDate.ToLower()
    if ($textToParse -like "tomorrow*")
    {
    	Write-Host "The future date $futureDeploymentDate supplied contains tomorrow, will add one day to whatever the parsed result is."
    	$addOneDay = $true
        $textToParse = $textToParse -replace "tomorrow", ""
    }
    
    [datetime]$outputDate = New-Object DateTime
    $currentDate = Get-Date
    $currentDate = $currentDate.AddMinutes(2)

    if ([datetime]::TryParse($textToParse, [ref]$outputDate) -eq $false)
    {
        Write-OctopusCritical "The suppplied date $textToParse cannot be parsed by DateTime.TryParse.  Please verify format and try again.  Please [refer to Microsoft's Documentation](https://docs.microsoft.com/en-us/dotnet/api/system.datetime.tryparse) on supported formats."
        exit 1
    }
    
    Write-Host "The proposed date is $outputDate.  Checking to see if this will occur in the past."
    
    if ($addOneDay -eq $true)
    {
    	$outputDate = $outputDate.AddDays(1)
    	Write-host "The text supplied included tomorrow, adding one day.  The new proposed date is $outputDate."
    }
    
    if ($currentDate -gt $outputDate)
    {
    	Write-OctopusCritical "The supplied date $futureDeploymentDate is set for the past.  All queued deployments must be in the future."
        exit 1
    }
    
    return $outputDate
}

function Get-QueueExpiryDate
{
	param (
    	$queueDate
    )
    
    if ($null -eq $queueDate)
    {
    	return $null
    }
    
    return $queueDate.AddHours(1)
}

function Get-RunbookSpecificMachines
{
    param (
        $defaultUrl,
        $octopusApiKey,    
        $runbookPreview,
        $runbookMachines,        
        $runbookRunName        
    )

    if ($runbookMachines -eq "N/A")
    {
        return @()
    }

    if ([string]::IsNullOrWhiteSpace($runbookMachines) -eq $true)
    {
        return @()
    }

    $translatedList = Get-MachineIdsFromMachineNames -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -targetMachines $runbookMachines

    $filteredList = @()    
    foreach ($runbookMachine in $translatedList)
    {    	
    	$runbookMachineId = $runbookMachine.Trim().ToLower()
    	Write-OctopusVerbose "Checking if $runbookMachineId is set to run on any of the runbook steps"
        
        foreach ($step in $runbookPreview.StepsToExecute)
        {
            foreach ($machine in $step.Machines)
            {
            	Write-OctopusVerbose "Checking if $runbookMachineId matches $($machine.Id) and it isn't already in the $($filteredList -join ",")"
                if ($runbookMachineId -eq $machine.Id.Trim().ToLower() -and $filteredList -notcontains $machine.Id)
                {
                	Write-OctopusInformation "Adding $($machine.Id) to the list"
                    $filteredList += $machine.Id
                }
            }
        }
    }

    if ($filteredList.Length -le 0)
    {
        Write-OctopusSuccess "The current task is targeting specific machines, but the runbook $runBookRunName does not run against any of these machines $runbookMachines. Skipping this run."
        exit 0
    }

    return $filteredList
}

function Get-ParentTaskApprovers
{
    param (
        $parentTaskId,
        $spaceId,
        $defaultUrl,
        $octopusApiKey
    )
    
    $approverList = @()
    if ($null -eq $parentTaskId)
    {
    	Write-OctopusInformation "The deployment task id to pull the approvers from is null, return an empty approver list"
    	return $approverList
    }

    Write-OctopusInformation "Getting all the events from the parent project"
    $parentEvents = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "events?regardingAny=$parentTaskId&spaces=$spaceId&includeSystem=true" -apiKey $octopusApiKey -method "GET"
    
    foreach ($parentEvent in $parentEvents.Items)
    {
        Write-OctopusVerbose "Checking $($parentEvent.Message) for manual intervention"
        if ($parentEvent.Message -like "Submitted interruption*")
        {
            Write-OctopusVerbose "The event $($parentEvent.Id) is a manual intervention approval event which was approved by $($parentEvent.Username)."

            $approverExists = $approverList | Where-Object {$_.Id -eq $parentEvent.UserId}        

            if ($null -eq $approverExists)
            {
                $approverInformation = @{
                    Id = $parentEvent.UserId;
                    Username = $parentEvent.Username;
                    Teams = @()
                }

                $approverInformation.Teams = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "teammembership?userId=$($approverInformation.Id)&spaces=$spaceId&includeSystem=true" -apiKey $octopusApiKey -method "GET"            

                Write-OctopusVerbose "Adding $($approverInformation.Id) to the approval list"
                $approverList += $approverInformation
            }        
        }
    }

    return $approverList
}

function Get-ApprovalTaskIdFromDeployment
{
    param (
        $parentReleaseId,
        $approvalEnvironment,
        $parentChannelId,    
        $parentEnvironmentId,
        $defaultUrl,
        $spaceId,
        $octopusApiKey 
    )

    $releaseDeploymentList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "releases/$parentReleaseId/deployments" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId
    
    $lastDeploymentTime = $(Get-Date).AddYears(-50)
    $approvalTaskId = $null
    foreach ($deployment in $releaseDeploymentList.Items)
    {
        if ($deployment.EnvironmentId -ne $approvalEnvironment.Id)
        {
            Write-OctopusInformation "The deployment $($deployment.Id) deployed to $($deployment.EnvironmentId) which doesn't match $($approvalEnvironment.Id)."
            continue
        }
        
        Write-OctopusInformation "The deployment $($deployment.Id) was deployed to the approval environment $($approvalEnvironment.Id)."

        $deploymentTask = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $null -endPoint "tasks/$($deployment.TaskId)" -apiKey $octopusApiKey -Method "Get"
        if ($deploymentTask.IsCompleted -eq $true -and $deploymentTask.FinishedSuccessfully -eq $false)
        {
            Write-Information "The deployment $($deployment.Id) was deployed to the approval environment, but it encountered a failure, moving onto the next deployment."
            continue
        }

        if ($deploymentTask.StartTime -gt $lastDeploymentTime)
        {
            $approvalTaskId = $deploymentTask.Id
            $lastDeploymentTime = $deploymentTask.StartTime
        }
    }        

    if ($null -eq $approvalTaskId)
    {
    	Write-OctopusVerbose "Unable to find a deployment to the environment, determining if it should've happened already."
        $channelInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "channels/$parentChannelId" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId
        $lifecycle = Get-OctopusLifeCycle -channel $channelInformation -defaultUrl $defaultUrl -spaceId $spaceId -OctopusApiKey $octopusApiKey
        $lifecyclePhases = Get-LifecyclePhases -lifecycle $lifecycle -defaultUrl $defaultUrl -spaceId $spaceid -OctopusApiKey $octopusApiKey
        
        $foundDestinationFirst = $false
        $foundApprovalFirst = $false
        
        foreach ($phase in $lifecyclePhases.Phases)
        {
        	if ($phase.AutomaticDeploymentTargets -contains $parentEnvironmentId -or $phase.OptionalDeploymentTargets -contains $parentEnvironmentId)
            {
            	if ($foundApprovalFirst -eq $false)
                {
                	$foundDestinationFirst = $true
                }
            }
            
            if ($phase.AutomaticDeploymentTargets -contains $approvalEnvironment.Id -or $phase.OptionalDeploymentTargets -contains $approvalEnvironment.Id)
            {
            	if ($foundDestinationFirst -eq $false)
                {
                	$foundApprovalFirst = $true
                }
            }
        }
        
        $messageToLog = "Unable to find a deployment for the environment $approvalEnvironmentName.  Auto approvals are disabled."
        if ($foundApprovalFirst -eq $true)
        {
        	Write-OctopusWarning $messageToLog
        }
        else
        {
        	Write-OctopusInformation $messageToLog
        }
        
        return $null
    }

    return $approvalTaskId
}

function Get-ApprovalTaskIdFromRunbook
{
    param (
        $parentRunbookId,
        $approvalEnvironment,
        $defaultUrl,
        $spaceId,
        $octopusApiKey 
    )
}

function Get-ApprovalTaskId
{
	param (
    	$autoApproveRunbookRunManualInterventions,
        $parentTaskId,
        $parentReleaseId,
        $parentRunbookId,
        $parentEnvironmentName,
        $approvalEnvironmentName,
        $parentChannelId,    
        $parentEnvironmentId,
        $defaultUrl,
        $spaceId,
        $octopusApiKey        
    )
    
    if ($autoApproveRunbookRunManualInterventions -eq $false)
    {
    	Write-OctopusInformation "Auto approvals are disabled, skipping pulling the approval deployment task id"
        return $null
    }
    
    if ([string]::IsNullOrWhiteSpace($approvalEnvironmentName) -eq $true)
    {
    	Write-OctopusInformation "Approval environment not supplied, using the current environment id for approvals."
        return $parentTaskId
    }
    
    if ($approvalEnvironmentName.ToLower().Trim() -eq $parentEnvironmentName.ToLower().Trim())
    {
        Write-OctopusInformation "The approval environment is the same as the current environment, using the current task id $parentTaskId"
        return $parentTaskId
    }
    
    $approvalEnvironment = Get-OctopusItemFromListEndpoint -itemNameToFind $approvalEnvironmentName -itemType "Environment" -defaultUrl $DefaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -defaultValue $null -endpoint "environments"
    
    if ([string]::IsNullOrWhiteSpace($parentReleaseId) -eq $false)
    {
        return Get-ApprovalTaskIdFromDeployment -parentReleaseId $parentReleaseId -approvalEnvironment $approvalEnvironment -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId
    }

    return Get-ApprovalTaskIdFromRunbook -parentRunbookId $parentRunbookId -approvalEnvironment $approvalEnvironment -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
}

function Get-OctopusLifecycle
{
    param (
        $channel,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    Write-OctopusInformation "Attempting to find the lifecycle information $($channel.Name)"
    if ($null -eq $channel.LifecycleId)
    {
        $lifecycleName = "Default Lifecycle"
        $lifecycleList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "lifecycles?partialName=$([uri]::EscapeDataString($lifecycleName))&skip=0&take=1" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
        $lifecycle = $lifecycleList.Items[0]
    }
    else
    {
        $lifecycle = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "lifecycles/$($channel.LifecycleId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
    }

    Write-Host "Successfully found the lifecycle $($lifecycle.Name) to use for this channel."

    return $lifecycle
}

function Get-LifecyclePhases
{
    param (
        $lifecycle,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    Write-OctopusInformation "Attempting to find the phase in the lifecycle $($lifecycle.Name) with the environment $environmentName to find the previous phase."
    if ($lifecycle.Phases.Count -eq 0)
    {
        Write-OctopusInformation "The lifecycle $($lifecycle.Name) has no set phases, calling the preview endpoint."
        $lifecyclePreview = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "lifecycles/$($lifecycle.Id)/preview" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
        $phases = $lifecyclePreview.Phases
    }
    else
    {
        Write-OctopusInformation "The lifecycle $($lifecycle.Name) has set phases, using those."
        $phases = $lifecycle.Phases    
    }

    Write-OctopusInformation "Found $($phases.Length) phases in this lifecycle."
    return $phases
}

function Submit-RunbookRunForAutoApproval
{
    param (
        $createdRunbookRun,
        $parentTaskApprovers,
        $defaultUrl,
        $octopusApiKey,
        $spaceId,
        $parentProjectName,
        $parentReleaseNumber,
        $parentRunbookId,
        $parentEnvironmentName,
        $parentTaskId        
    )

    Write-OctopusSuccess "The task has a pending manual intervention.  Checking parent approvals."    
    $manualInterventionInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "interruptions?regarding=$($createdRunbookRun.TaskId)" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId
    foreach ($manualIntervention in $manualInterventionInformation.Items)
    {
        if ($manualIntervention.IsPending -eq $false)
        {
            Write-OctopusInformation "This manual intervention has already been approved.  Proceeding onto the next one."
            continue
        }

        if ($manualIntervention.CanTakeResponsibility -eq $false)
        {
            Write-OctopusSuccess "The user associated with the API key doesn't have permissions to take responsibility for the manual intervention."
            Write-OctopusSuccess "If you wish to leverage the auto-approval functionality give the user permissions."
            continue
        }        

        $automaticApprover = $null
        Write-OctopusVerbose "Checking to see if one of the parent project approvers is assigned to one of the manual intervention teams $($manualIntervention.ResponsibleTeamIds)"
        foreach ($approver in $parentTaskApprovers)
        {
            foreach ($approverTeam in $approver.Teams)
            {
                Write-OctopusVerbose "Checking to see if $($manualIntervention.ResponsibleTeamIds) contains $($approverTeam.TeamId)"
                if ($manualIntervention.ResponsibleTeamIds -contains $approverTeam.TeamId)
                {
                    $automaticApprover = $approver
                    break
                }
            }

            if ($null -ne $automaticApprover)
            {
                break
            }
        }

        if ($null -ne $automaticApprover)
        {
        	Write-OctopusSuccess "Matching approver found auto-approving."
            if ($manualIntervention.HasResponsibility -eq $false)
            {
                Write-OctopusInformation "Taking over responsibility for this manual intervention."
                $takeResponsiblilityResponse = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "interruptions/$($manualIntervention.Id)/responsible" -method "PUT" -apiKey $octopusApiKey -spaceId $spaceId
                Write-OctopusVerbose "Response from taking responsibility $($takeResponsiblilityResponse.Id)"
            }
            
            if ([string]::IsNullOrWhiteSpace($parentReleaseNumber) -eq $false)
            {
                $notes = "Auto-approving this runbook run.  Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName with the task id $parentTaskId was approved by $($automaticApprover.UserName).  That user is a member of one of the teams this manual intervention requires.  You can view that deployment $defaultUrl/app#/$spaceId/tasks/$parentTaskId"
            }
            else 
            {
                $notes = "Auto-approving this runbook run.  Parent project $parentProjectName runbook run $parentRunbookId to $parentEnvironmentName with the task id $parentTaskId was approved by $($automaticApprover.UserName).  That user is a member of one of the teams this manual intervention requires.  You can view that runbook run $defaultUrl/app#/$spaceId/tasks/$parentTaskId"
            }
            if ($runbookCustomNotesToggle -eq $true){
              $notes = $runbookCustomNotes
            }
            $submitApprovalBody = @{
                Instructions = $null;
                Notes = $notes
                Result = "Proceed"
            }
            $submitResult = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "interruptions/$($manualIntervention.Id)/submit" -method "POST" -apiKey $octopusApiKey -item $submitApprovalBody -spaceId $spaceId
            Write-OctopusSuccess "Successfully auto approved the manual intervention $($submitResult.Id)"
        }
        else
        {
            Write-OctopusSuccess "Couldn't find an approver to auto-approve the child project.  Waiting until timeout or child project is approved."    
        }
    }
}


$runbookWaitForFinish = GetCheckboxBoolean -Value $runbookWaitForFinish
$runbookUseGuidedFailure = GetCheckboxBoolean -Value $runbookUseGuidedFailure
$runbookUsePublishedSnapshot = GetCheckboxBoolean -Value $runbookUsePublishedSnapshot
$runbookCancelInSeconds = [int]$runbookCancelInSeconds

Write-OctopusInformation "Wait for Finish Before Check: $runbookWaitForFinish"
Write-OctopusInformation "Use Guided Failure Before Check: $runbookUseGuidedFailure"
Write-OctopusInformation "Use Published Snapshot Before Check: $runbookUsePublishedSnapshot"
Write-OctopusInformation "Runbook Name $runbookRunName"
Write-OctopusInformation "Runbook Base Url: $runbookBaseUrl"
Write-OctopusInformation "Runbook Space Name: $runbookSpaceName"
Write-OctopusInformation "Runbook Environment Name: $runbookEnvironmentName"
Write-OctopusInformation "Runbook Tenant Name: $runbookTenantName"
Write-OctopusInformation "Wait for Finish: $runbookWaitForFinish"
Write-OctopusInformation "Use Guided Failure: $runbookUseGuidedFailure"
Write-OctopusInformation "Cancel run in seconds: $runbookCancelInSeconds"
Write-OctopusInformation "Use Published Snapshot: $runbookUsePublishedSnapshot"
Write-OctopusInformation "Auto Approve Runbook Run Manual Interventions: $autoApproveRunbookRunManualInterventions"
Write-OctopusInformation "Auto Approve environment name to pull approvals from: $approvalEnvironmentName"

Write-OctopusInformation "Octopus runbook run machines: $runbookMachines"
Write-OctopusInformation "Parent Task Id: $parentTaskId"
Write-OctopusInformation "Parent Release Id: $parentReleaseId"
Write-OctopusInformation "Parent Channel Id: $parentChannelId"
Write-OctopusInformation "Parent Environment Id: $parentEnvironmentId"
Write-OctopusInformation "Parent Runbook Id: $parentRunbookId"
Write-OctopusInformation "Parent Environment Name: $parentEnvironmentName"
Write-OctopusInformation "Parent Release Number: $parentReleaseNumber"

$verificationPassed = @()
$verificationPassed += Test-RequiredValues -variableToCheck $runbookRunName -variableName "Runbook Name"
$verificationPassed += Test-RequiredValues -variableToCheck $runbookBaseUrl -variableName "Base Url"
$verificationPassed += Test-RequiredValues -variableToCheck $runbookApiKey -variableName "Api Key"
$verificationPassed += Test-RequiredValues -variableToCheck $runbookEnvironmentName -variableName "Environment Name"

if ($verificationPassed -contains $false)
{
	Write-OctopusInformation "Required values missing"
	Exit 1
}

$runbookSpace = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookSpaceName -endpoint "spaces" -spaceId $null -octopusApiKey $runbookApiKey -defaultUrl $runbookBaseUrl -itemType "Space" -defaultValue $octopusSpaceId
$runbookSpaceId = $runbookSpace.Id

$projectToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookProjectName -endpoint "projects" -spaceId $runbookSpaceId -defaultValue $null -itemType "Project" -octopusApiKey $runbookApiKey -defaultUrl $runbookBaseUrl
if ($null -ne $projectToUse)
{	    
    $runbookEndPoint = "projects/$($projectToUse.Id)/runbooks"
}
else
{
	$runbookEndPoint = "runbooks"
}

$environmentToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookEnvironmentName -itemType "Environment" -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey -defaultValue $null -endpoint "environments"

$runbookToRun = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookRunName -itemType "Runbook" -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -endpoint $runbookEndPoint -octopusApiKey $runbookApiKey -defaultValue $null

$runbookSnapShotIdToUse = Get-RunbookSnapshotIdToRun -runbookToRun $runbookToRun -runbookUsePublishedSnapshot $runbookUsePublishedSnapshot -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $octopusSpaceId
$projectNameForUrl = Get-ProjectSlug -projectToUse $projectToUse -runbookToRun $runbookToRun -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $runbookSpaceId

$tenantToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookTenantName -itemType "Tenant" -defaultValue $null -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey -endpoint "tenants" -defaultUrl $runbookBaseUrl
if ($null -ne $tenantToUse)
{	
    $tenantIdToUse = $tenantToUse.Id  
    $runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint "runbooks/$($runbookToRun.Id)/runbookRuns/preview/$($environmentToUse.Id)/$($tenantIdToUse)" -method "GET" -item $null
}
else
{
	try
    {
    	Write-Host "Trying the new preview step"
    	$runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint "runbookSnapshots/$($runbookSnapShotIdToUse)/runbookRuns/preview/$($environmentToUse.Id)?includeDisabledSteps=true" -method "GET" -item $null
    }
    catch
    {
    	Write-Host "The current version of Octopus Deploy doesn't support Runbook Snapshot Preview"
    	$runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint "runbooks/$($runbookToRun.Id)/runbookRuns/preview/$($environmentToUse.Id)" -method "GET" -item $null
   	}
}

$childRunbookRunSpecificMachines = Get-RunbookSpecificMachines -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -runbookPreview $runBookPreview -runbookMachines $runbookMachines -runbookRunName $runbookRunName
$runbookFormValues = Get-RunbookFormValues -runbookPreview $runBookPreview -runbookPromptedVariables $runbookPromptedVariables

$queueDate = Get-QueueDate -futureDeploymentDate $runbookFutureDeploymentDate
$queueExpiryDate = Get-QueueExpiryDate -queueDate $queueDate

$runbookBody = @{
    RunbookId = $($runbookToRun.Id);
    RunbookSnapShotId = $runbookSnapShotIdToUse;
    FrozenRunbookProcessId = $null;
    EnvironmentId = $($environmentToUse.Id);
    TenantId = $tenantIdToUse;
    SkipActions = @();
    QueueTime = $queueDate;
    QueueTimeExpiry = $queueExpiryDate;
    FormValues = $runbookFormValues;
    ForcePackageDownload = $false;
    ForcePackageRedeployment = $true;
    UseGuidedFailure = $runbookUseGuidedFailure;
    SpecificMachineIds = @($childRunbookRunSpecificMachines);
    ExcludedMachineIds = @()
}

$approvalTaskId = Get-ApprovalTaskId -autoApproveRunbookRunManualInterventions $autoApproveRunbookRunManualInterventions -parentTaskId $parentTaskId -parentReleaseId $parentReleaseId -parentRunbookId $parentRunbookId -parentEnvironmentName $parentEnvironmentName -approvalEnvironmentName $approvalEnvironmentName -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey
$parentTaskApprovers = Get-ParentTaskApprovers -parentTaskId $approvalTaskId -spaceId $runbookSpaceId -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey

Invoke-OctopusDeployRunbook -runbookBody $runbookBody -runbookWaitForFinish $runbookWaitForFinish -runbookCancelInSeconds $runbookCancelInSeconds -projectNameForUrl $projectNameForUrl -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $runbookSpaceId -parentTaskApprovers $parentTaskApprovers -autoApproveRunbookRunManualInterventions $autoApproveRunbookRunManualInterventions -parentProjectName $projectNameForUrl -parentReleaseNumber $parentReleaseNumber -approvalEnvironmentName $approvalEnvironmentName -parentRunbookId $parentRunbookId -parentTaskId $approvalTaskId

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "0444b0b3-088e-4689-b755-112d1360ffe3",
  "Name": "Run Octopus Deploy Runbook",
  "Description": "This step will kick off a runbook.  The runbook can exist in the same space and project, or it can exist on a different instance altogether.  \n\n**Please Note**: Prompted variable values have to be text or sensitive variables.  Variable types such as AWS or Azure accounts will not work.\n\nThis step should be called from a worker machine.  If it is called from a target and the runbook runs on the same target you run the risk of a deadlock.\n\n",
  "Version": 16,
  "ExportedAt": "2024-07-23T14:07:03.309Z",
  "ActionType": "Octopus.Script",
  "Author": "millerjn21",
  "Packages": [],
  "Parameters": [
    {
      "Id": "e9e93cff-973a-4107-afa2-8efa30947979",
      "Name": "Run.Runbook.Name",
      "Label": "Runbook Name",
      "HelpText": "**Required**\n\nThe name of the runbook to run.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "d998db57-3574-4598-81f9-7dd145cab81a",
      "Name": "Run.Runbook.Base.Url",
      "Label": "Base Url",
      "HelpText": "**Required**\n\nThe base URL of your instance, IE https://samples.octopus.app.  Defaults to the system variable [Octopus.Web.ServerUri](https://octopus.com/docs/projects/variables/system-variables#Systemvariables-Server).",
      "DefaultValue": "#{Octopus.Web.ServerUri}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "24884bf3-ca1d-4c17-8ee0-017339d6d87e",
      "Name": "Run.Runbook.Api.Key",
      "Label": "Api Key",
      "HelpText": "**Required**\n\nThe API key of a user who has permissions to run the runbook specified",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "bc2d33fe-d05b-49bd-b02b-eb6de4737eff",
      "Name": "Run.Runbook.Space.Name",
      "Label": "Runbook Space",
      "HelpText": "**Required**\n\nThe name of the space the child project is located in.  Defaults to the current space name using the variable `#{Octopus.Space.Name}`.",
      "DefaultValue": "#{Octopus.Space.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "a1f44858-809a-48ce-9127-e59f02be40a1",
      "Name": "Run.Runbook.Project.Name",
      "Label": "Project name",
      "HelpText": "**Optional**\n\nThe name of the project containing the runbook.  If the project name is not specified then it will use the first runbook with the matching runbook name it can find.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "07bd5b03-4151-4f32-8893-417bf22c4df2",
      "Name": "Run.Runbook.Environment.Name",
      "Label": "Environment Name",
      "HelpText": "**Required**\n\nName of the environment to run the runbook on.  The default is the current environment name using the system variable `#{Octopus.Environment.Name}`.",
      "DefaultValue": "#{Octopus.Environment.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "bf4ae98a-4901-474a-8984-08b0258304ca",
      "Name": "Run.Runbook.Tenant.Name",
      "Label": "Tenant Name",
      "HelpText": "**Optional**\n\nName of Tenant to run the runbook for.  If you want to run a runbook using the same tenant as the deployment or runbook run then use the system variable `#{Octopus.Deployment.Tenant.Name}`.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "9c49ba5c-337b-454a-8837-282353276aea",
      "Name": "Run.Runbook.UsePublishedSnapShot",
      "Label": "Use Published Snapshot",
      "HelpText": "Indicates if the run should use the most recent published snapshot.  When not set it will use the most recent snapshot, regardless if it was published or not.\n\nDefault is to use only published snapshots.\n\nIf you select to use unpublished snapshots then the runbook will:\n- If no published snapshots exist, then it will create a new unpublished snapshot on each run.\n- Create a new snapshot if anything has changed since the last published snapshot.\n- Use the existing published snapshot if nothing has changed since it was last published.\n\n**Please note:** A runbook is considered \"changed\" when any of the following are true:\n\n- Runbook process has changed.\n- Project variables have changed.\n- Library Variable Sets referenced by the project have changed.\n- A newer version of any referenced packages is found.",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "1a3e3ff6-456a-49e0-a0ce-83bfb30bfcaa",
      "Name": "Run.Runbook.Waitforfinish",
      "Label": "Wait for finish",
      "HelpText": "Indicates if the process should be paused and wait for the runbook to finish",
      "DefaultValue": "True",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "c36715c5-b583-43c4-b3e3-a74f44f2b2c4",
      "Name": "Run.Runbook.UseGuidedFailure",
      "Label": "Use Guided Failure",
      "HelpText": "Should the runbook run use guided failure (not a good idea if you are waiting for this to finish)",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "6b951d28-b027-4f16-aaa6-39e91bd906d4",
      "Name": "Run.Runbook.CancelInSeconds",
      "Label": "Cancel Seconds",
      "HelpText": "**Optional**\n\nThe number of seconds to wait before canceling the runbook run.  Default is 1800 seconds (30 minutes).",
      "DefaultValue": "1800",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "c847668d-b4fa-4405-a15b-f03691147597",
      "Name": "Run.Runbook.PromptedVariables",
      "Label": "Prompted Variable Values",
      "HelpText": "**Optional**\n\nValues for any prompted variables for the runbook.  Each new line represents a new variable.  This will only work with string variable types, text and sensitive values.    \n\nUse the format **Name::Value**  IE:\n\n\nPromptedVariableName::My Super Awesome Value\n\nOtherPromptedVariable::Other Super Awesome Value",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "MultiLineText"
      }
    },
    {
      "Id": "dd65f24d-7546-4271-b78b-28101170410c",
      "Name": "Run.Runbook.DateTime",
      "Label": "Scheduling",
      "HelpText": "**Optional**\n\nSchedule the runbook to run in the future.  Please note, if this is set, the `Wait for Finish` option is ignored.\n\nUses `DateTime.TryParse` and specific keywords to determine the value sent in.  Supported formats:\n\n- `7:00 PM` will deploy at 7:00 PM today\n- `21:00` will deploy at 21:00 hours or 9 PM today\n- `YYYY-MM-DD HH:mm:ss` or `2021-01-14 21:00:00` will deploy at 9 PM on the 14th of January, 2021\n- `YYYY/MM/DD HH:mm:ss` or `2021/03/20 22:00:00` will deploy at 10 PM on the 20th of March, 2021\n- `MM/DD/YYYY HH:mm:ss` or `06/25/2021 19:00:00` will deploy at 7 PM on the 25th of June, 2021\n- `DD MMM YYYY HH:mm:ss` or `01 Jan 2021 18:00:00` will deploy at 6 PM on the 1st of January, 2021\n- `Tomorrow HH:mm:ss` or `Tomorrow 18:00:00` will deploy at 6 PM tomorrow\n\nUses the Octopus Server's Timezone.  The queue expiry time will be set to 1 hour from the supplied date.\n\nDefault is `N/A` or not applicable.  ",
      "DefaultValue": "N/A",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "22158e31-8061-4ada-b61b-a7bdacb5dd37",
      "Name": "Run.Runbook.Machines",
      "Label": "Machine List",
      "HelpText": "**Optional**\n\nA comma-separated list of Machine Ids or Machine Names to target with this runbook.  \n\n**Please Note:** The step template will remove any machines that cannot be found or are not applicable to the runbook.\n\nThe default is `N/A`.  Set to `#{Octopus.Deployment.Machines}` if you want to target the same machines as the current runbook run or deployment.",
      "DefaultValue": "N/A",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "95788fe2-f770-460c-b012-e6a586fe04d7",
      "Name": "Run.Runbook.AutoApproveManualInterventions",
      "Label": "Auto approve runbook manual interventions",
      "HelpText": "**Optional**\n\nIf the child project has manual interventions the step will look for manual interventions in the parent project or parent runbook.\n\nWhen a manual intervention in the parent project or parent runbook is found it will check that user's assigned teams.  If that user's assigned teams can approve the child project it will do so.\n\nPlease note, the user associated with the API key must be able to approve the child project manual interventions as well.  \n\nThe default is `No`, allow this to happen.  Set it to `Yes` to enable this functionality.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    },
    {
      "Id": "ea7c213e-380b-46ba-85b7-5c2c0f7b01d7",
      "Name": "Run.Runbook.CustomNotes.Toggle",
      "Label": "Custom Manual Intervention Notes Toggle",
      "HelpText": "Check this box if you would like custom notes to be submitted with the automatic manual intervention approval.",
      "DefaultValue": "False",
      "DisplaySettings": {
        "Octopus.ControlType": "Checkbox"
      }
    },
    {
      "Id": "70549ad5-b451-4587-b8ed-b4afad1752f9",
      "Name": "Run.Runbook.CustomNotes",
      "Label": "Custom Manual Intervention Approval Notes",
      "HelpText": "Use this field to supply custom manual intervention notes if the above toggle is checked.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "b1cd0181-c5a8-4d4d-9746-f7cfe41f6794",
      "Name": "Run.Runbook.ManualIntervention.EnvironmentToUse",
      "Label": "Environment name to pull approvals from",
      "HelpText": "**Optional**\n\nThe name of the environment you wish to pull the approvals from for the parent project or parent runbook.   It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment.  If this step is being called from a runbook, it will look at the latest runbook run for the environment specified\n\nUsed when you are deploying to `Production` but want to pull the approvals from `Staging` or a `Prod Approval` environment.\n\nDefaults to the current environment name.",
      "DefaultValue": "#{Octopus.Environment.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.ScriptBody": "[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12\n\n# Octopus Variables\n$octopusSpaceId = $OctopusParameters[\"Octopus.Space.Id\"]\n$parentTaskId = $OctopusParameters[\"Octopus.Task.Id\"]\n$parentReleaseId = $OctopusParameters[\"Octopus.Release.Id\"]\n$parentChannelId = $OctopusParameters[\"Octopus.Release.Channel.Id\"]\n$parentEnvironmentId = $OctopusParameters[\"Octopus.Environment.Id\"]\n$parentRunbookId = $OctopusParameters[\"Octopus.Runbook.Id\"]\n$parentEnvironmentName = $OctopusParameters[\"Octopus.Environment.Name\"]\n$parentReleaseNumber = $OctopusParameters[\"Octopus.Release.Number\"]\n\n# Step Template Parameters\n$runbookRunName = $OctopusParameters[\"Run.Runbook.Name\"]\n$runbookBaseUrl = $OctopusParameters[\"Run.Runbook.Base.Url\"]\n$runbookApiKey = $OctopusParameters[\"Run.Runbook.Api.Key\"]\n$runbookEnvironmentName = $OctopusParameters[\"Run.Runbook.Environment.Name\"]\n$runbookTenantName = $OctopusParameters[\"Run.Runbook.Tenant.Name\"]\n$runbookWaitForFinish = $OctopusParameters[\"Run.Runbook.Waitforfinish\"]\n$runbookUseGuidedFailure = $OctopusParameters[\"Run.Runbook.UseGuidedFailure\"]\n$runbookUsePublishedSnapshot = $OctopusParameters[\"Run.Runbook.UsePublishedSnapShot\"]\n$runbookPromptedVariables = $OctopusParameters[\"Run.Runbook.PromptedVariables\"]\n$runbookCancelInSeconds = $OctopusParameters[\"Run.Runbook.CancelInSeconds\"]\n$runbookProjectName = $OctopusParameters[\"Run.Runbook.Project.Name\"]\n$runbookCustomNotesToggle = $OctopusParameters[\"Run.Runbook.CustomNotes.Toggle\"]\n$runbookCustomNotes = $OctopusParameters[\"Run.Runbook.CustomNotes\"]\n\n$runbookSpaceName = $OctopusParameters[\"Run.Runbook.Space.Name\"]\n$runbookFutureDeploymentDate = $OctopusParameters[\"Run.Runbook.DateTime\"]\n$runbookMachines = $OctopusParameters[\"Run.Runbook.Machines\"]\n$autoApproveRunbookRunManualInterventions = $OctopusParameters[\"Run.Runbook.AutoApproveManualInterventions\"]\n$approvalEnvironmentName = $OctopusParameters[\"Run.Runbook.ManualIntervention.EnvironmentToUse\"]\n\nfunction Write-OctopusVerbose\n{\n    param($message)\n    \n    Write-Verbose $message  \n}\n\nfunction Write-OctopusInformation\n{\n    param($message)\n    \n    Write-Host $message  \n}\n\nfunction Write-OctopusSuccess\n{\n    param($message)\n\n    Write-Highlight $message \n}\n\nfunction Write-OctopusWarning\n{\n    param($message)\n\n    Write-Warning \"$message\" \n}\n\nfunction Write-OctopusCritical\n{\n    param ($message)\n\n    Write-Error \"$message\" \n}\n\nfunction Invoke-OctopusApi\n{\n    param\n    (\n        $octopusUrl,\n        $endPoint,\n        $spaceId,\n        $apiKey,\n        $method,\n        $item     \n    )\n\n    if ([string]::IsNullOrWhiteSpace($SpaceId))\n    {\n        $url = \"$OctopusUrl/api/$EndPoint\"\n    }\n    else\n    {\n        $url = \"$OctopusUrl/api/$spaceId/$EndPoint\"    \n    }  \n\n    try\n    {\n        if ($null -eq $item)\n        {\n            Write-Verbose \"No data to post or put, calling bog standard invoke-restmethod for $url\"\n            return Invoke-RestMethod -Method $method -Uri $url -Headers @{\"X-Octopus-ApiKey\" = \"$ApiKey\" } -ContentType 'application/json; charset=utf-8'\n        }\n\n        $body = $item | ConvertTo-Json -Depth 10\n        Write-Verbose $body\n\n        Write-Host \"Invoking $method $url\"\n        return Invoke-RestMethod -Method $method -Uri $url -Headers @{\"X-Octopus-ApiKey\" = \"$ApiKey\" } -Body $body -ContentType 'application/json; charset=utf-8'\n    }\n    catch\n    {\n        if ($null -ne $_.Exception.Response)\n        {\n            if ($_.Exception.Response.StatusCode -eq 401)\n            {\n                Write-Error \"Unauthorized error returned from $url, please verify API key and try again\"\n            }\n            elseif ($_.Exception.Response.statusCode -eq 403)\n            {\n                Write-Error \"Forbidden error returned from $url, please verify API key and try again\"\n            }\n            else\n            {                \n                Write-Error -Message \"Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )\"\n            }            \n        }\n        else\n        {\n            Write-Verbose $_.Exception\n        }\n    }\n\n    Throw \"There was an error calling the Octopus API please check the log for more details\"\n}\n\nfunction Test-RequiredValues\n{\n\tparam (\n    \t$variableToCheck,\n        $variableName\n    )\n    \n    if ([string]::IsNullOrWhiteSpace($variableToCheck) -eq $true)\n    {\n    \tWrite-OctopusCritical \"$variableName is required.\"\n        return $false\n    }\n    \n    return $true\n}\n\nfunction GetCheckBoxBoolean\n{\n\tparam (\n    \t[string]$Value\n    )\n    \n    if ([string]::IsNullOrWhiteSpace($value) -eq $true)\n    {\n    \treturn $false\n    }\n    \n    return $value -eq \"True\"\n}\n\nfunction Get-FilteredOctopusItem\n{\n    param(\n        $itemList,\n        $itemName\n    )\n\n    if ($itemList.Items.Count -eq 0)\n    {\n        Write-OctopusCritical \"Unable to find $itemName.  Exiting with an exit code of 1.\"\n        Exit 1\n    }  \n\n    $item = $itemList.Items | Where-Object { $_.Name -eq $itemName}      \n\n    if ($null -eq $item)\n    {\n        Write-OctopusCritical \"Unable to find $itemName.  Exiting with an exit code of 1.\"\n        exit 1\n    }\n    \n    if ($item -is [array])\n    {\n    \tWrite-OctopusCritical \"More than one item exists with the name $itemName.  Exiting with an exit code of 1.\"\n        exit 1\n    }\n\n    return $item\n}\n\nfunction Get-OctopusItemFromListEndpoint\n{\n    param(\n        $endpoint,\n        $itemNameToFind,\n        $itemType,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId,\n        $defaultValue\n    )\n    \n    if ([string]::IsNullOrWhiteSpace($itemNameToFind))\n    {\n    \treturn $defaultValue\n    }\n    \n    Write-OctopusInformation \"Attempting to find $itemType with the name of $itemNameToFind\"\n    \n    $itemList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"$($endpoint)?partialName=$([uri]::EscapeDataString($itemNameToFind))&skip=0&take=100\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"    \n    $item = Get-FilteredOctopusItem -itemList $itemList -itemName $itemNameToFind\n\n    Write-OctopusInformation \"Successfully found $itemNameToFind with id of $($item.Id)\"\n\n    return $item\n}\n\nfunction Get-MachineIdsFromMachineNames\n{\n    param (\n        $targetMachines,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    $targetMachineList = $targetMachines -split \",\"\n    $translatedList = @()\n\n    foreach ($machineName in $targetMachineList)\n    {\n        Write-OctopusVerbose \"Translating $machineName to an Id.  First checking to see if it is already an Id.\"\n    \tif ($machineName.Trim() -like \"Machines*\")\n        {\n            Write-OctopusVerbose \"$machineName is already an Id, no need to look that up.\"\n        \t$translatedList += $machineName\n            continue\n        }\n        \n        $machineObject = Get-OctopusItemFromListEndpoint -itemNameToFind $machineName.Trim() -itemType \"Deployment Target\" -endpoint \"machines\" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n\n        $translatedList += $machineObject.Id\n    }\n\n    return $translatedList\n}\n\nfunction Get-RunbookSnapshotIdToRun\n{\n    param (\n        $runbookToRun,\n        $runbookUsePublishedSnapshot,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId\n    )\n\n    $runbookSnapShotIdToUse = $runbookToRun.PublishedRunbookSnapshotId\n    Write-OctopusInformation \"The last published snapshot for $runbookRunName is $runbookSnapShotIdToUse\"\n\n    if ($null -eq $runbookSnapShotIdToUse -and $runbookUsePublishedSnapshot -eq $true)\n    {\n        Write-OctopusCritical \"Use Published Snapshot was set; yet the runbook doesn't have a published snapshot.  Exiting.\"\n        Exit 1\n    }\n\n    if ($runbookUsePublishedSnapshot -eq $true)\n    {\n        Write-OctopusInformation \"Use published snapshot set to true, using the published runbook snapshot.\"\n        return $runbookSnapShotIdToUse\n    }\n\n    if ($null -eq $runbookToRun.PublishedRunbookSnapshotId)\n    {\n        Write-OctopusInformation \"There have been no published runbook snapshots, going to create a new snapshot.\"\n        return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId\n    }\n\n    $runbookSnapShotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"runbookSnapshots/$($runbookToRun.PublishedRunbookSnapshotId)/runbookRuns/template\" -method \"Get\" -item $null\n\n    if ($runbookSnapShotTemplate.IsRunbookProcessModified -eq $false -and $runbookSnapShotTemplate.IsVariableSetModified -eq $false -and $runbookSnapShotTemplate.IsLibraryVariableSetModified -eq $false)\n    {        \n        Write-OctopusInformation \"The runbook has not been modified since the published snapshot was created.  Checking to see if any of the packages have a new version.\"    \n        $runbookSnapShot = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"runbookSnapshots/$($runbookToRun.PublishedRunbookSnapshotId)\" -method \"Get\" -item $null\n        $snapshotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"runbooks/$($runbookToRun.Id)/runbookSnapShotTemplate\" -method \"Get\" -item $null\n\n        foreach ($package in $runbookSnapShot.SelectedPackages)\n        {\n            foreach ($templatePackage in $snapshotTemplate.Packages)\n            {\n                if ($package.StepName -eq $templatePackage.StepName -and $package.ActionName -eq $templatePackage.ActionName -and $package.PackageReferenceName -eq $templatePackage.PackageReferenceName)\n                {\n                    $packageVersion = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"feeds/$($templatePackage.FeedId)/packages/versions?packageId=$($templatePackage.PackageId)&take=1\" -method \"Get\" -item $null\n\n                    if ($packageVersion -ne $package.Version)\n                    {\n                        Write-OctopusInformation \"A newer version of a package was found, going to use that and create a new snapshot.\"\n                        return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId                    \n                    }\n                }\n            }\n        }\n\n        Write-OctopusInformation \"No new package versions have been found, using the published snapshot.\"\n        return $runbookToRun.PublishedRunbookSnapshotId\n    }\n    \n    Write-OctopusInformation \"The runbook has been modified since the snapshot was created, creating a new one.\"\n    return New-RunbookUnpublishedSnapshot -runbookToRun $runbookToRun -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId\n}\n\nfunction New-RunbookUnpublishedSnapshot\n{\n    param (\n        $runbookToRun,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId\n    )\n\n    $octopusProject = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"projects/$($runbookToRun.ProjectId)\" -method \"Get\" -item $null\n    $snapshotTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"runbooks/$($runbookToRun.Id)/runbookSnapShotTemplate\" -method \"Get\" -item $null\n\n    $runbookPackages = @()\n    foreach ($package in $snapshotTemplate.Packages)\n    {\n        $packageVersion = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"feeds/$($package.FeedId)/packages/versions?packageId=$($package.PackageId)&take=1\" -method \"Get\" -item $null\n\n        if ($packageVersion.TotalResults -le 0)\n        {\n            Write-Error \"Unable to find a package version for $($package.PackageId).  This is required to create a new unpublished snapshot.  Exiting.\"\n            exit 1\n        }\n\n        $runbookPackages += @{\n            StepName = $package.StepName\n            ActionName = $package.ActionName\n            Version = $packageVersion.Items[0].Version\n            PackageReferenceName = $package.PackageReferenceName\n        }\n    }\n\n    $runbookSnapShotRequest = @{\n        FrozenProjectVariableSetId = \"variableset-$($runbookToRun.ProjectId)\"\n        FrozenRunbookProcessId = $($runbookToRun.RunbookProcessId)\n        LibraryVariableSetSnapshotIds = @($octopusProject.IncludedLibraryVariableSetIds)\n        Name = $($snapshotTemplate.NextNameIncrement)\n        ProjectId = $($runbookToRun.ProjectId)\n        ProjectVariableSetSnapshotId = \"variableset-$($runbookToRun.ProjectId)\"\n        RunbookId = $($runbookToRun.Id)\n        SelectedPackages = $runbookPackages\n    }\n\n    $newSnapShot = Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -endPoint \"runbookSnapshots\" -method \"POST\" -item $runbookSnapShotRequest\n\n    return $($newSnapShot.Id)\n}\n\nfunction Get-ProjectSlug\n{\n    param\n    (\n        $runbookToRun,\n        $projectToUse,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    if ($null -ne $projectToUse)\n    {\n        return $projectToUse.Slug\n    }\n\n    $project = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint \"projects/$($runbookToRun.ProjectId)\" -method \"GET\" -item $null\n\n    return $project.Slug\n}\n\nfunction Get-RunbookFormValues\n{\n    param (\n        $runbookPreview,\n        $runbookPromptedVariables        \n    )\n\n    $runbookFormValues = @{}\n\n    if ([string]::IsNullOrWhiteSpace($runbookPromptedVariables) -eq $true)\n    {\n        return $runbookFormValues\n    }    \n    \n    $promptedValueList = @(($runbookPromptedVariables -Split \"`n\").Trim())\n    Write-OctopusInformation $promptedValueList.Length\n    \n    foreach($element in $runbookPreview.Form.Elements)\n    {\n    \t$nameToSearchFor = $element.Control.Name\n        $uniqueName = $element.Name\n        $isRequired = $element.Control.Required\n        \n        $promptedVariablefound = $false\n        \n        Write-OctopusInformation \"Looking for the prompted variable value for $nameToSearchFor\"\n    \tforeach ($promptedValue in $promptedValueList)\n        {\n        \t$splitValue = $promptedValue -Split \"::\"\n            Write-OctopusInformation \"Comparing $nameToSearchFor with provided prompted variable $($promptedValue[0])\"\n            if ($splitValue.Length -gt 1)\n            {\n            \tif ($nameToSearchFor -eq $splitValue[0])\n                {\n                \tWrite-OctopusInformation \"Found the prompted variable value $nameToSearchFor\"\n                \t$runbookFormValues[$uniqueName] = $splitValue[1]\n                    $promptedVariableFound = $true\n                    break\n                }\n            }\n        }\n        \n        if ($promptedVariableFound -eq $false -and $isRequired -eq $true)\n        {\n        \tWrite-OctopusCritical \"Unable to find a value for the required prompted variable $nameToSearchFor, exiting\"\n            Exit 1\n        }\n    }\n\n    return $runbookFormValues\n}\n\nfunction Invoke-OctopusDeployRunbook\n{\n    param (\n        $runbookBody,\n        $runbookWaitForFinish,\n        $runbookCancelInSeconds,\n        $projectNameForUrl,        \n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId,\n        $parentTaskApprovers,\n        $autoApproveRunbookRunManualInterventions,\n        $parentProjectName,\n        $parentReleaseNumber,\n        $approvalEnvironmentName,\n        $parentRunbookId,\n        $parentTaskId\n    )\n\n    $runbookResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -item $runbookBody -method \"POST\" -endPoint \"runbookRuns\"\n\n    $runbookServerTaskId = $runBookResponse.TaskId\n    Write-OctopusInformation \"The task id of the new task is $runbookServerTaskId\"\n\n    $runbookRunId = $runbookResponse.Id\n    Write-OctopusInformation \"The runbook run id is $runbookRunId\"\n\n    Write-OctopusSuccess \"Runbook was successfully invoked, you can access the launched runbook [here]($defaultUrl/app#/$spaceId/projects/$projectNameForUrl/operations/runbooks/$($runbookBody.RunbookId)/snapshots/$($runbookBody.RunbookSnapShotId)/runs/$runbookRunId)\"\n\n    if ($runbookWaitForFinish -eq $false)\n    {\n        Write-OctopusInformation \"The wait for finish setting is set to no, exiting step\"\n        return\n    }\n    \n    if ($null -ne $runbookBody.QueueTime)\n    {\n    \tWrite-OctopusInformation \"The runbook queue time is set.  Exiting step\"\n        return\n    }\n\n    Write-OctopusSuccess \"The setting to wait for completion was set, waiting until task has finished\"\n    $startTime = Get-Date\n    $currentTime = Get-Date\n    $dateDifference = $currentTime - $startTime\n\t\n    $taskStatusUrl = \"tasks/$runbookServerTaskId\"\n    $numberOfWaits = 0    \n    \n    While ($dateDifference.TotalSeconds -lt $runbookCancelInSeconds)\n    {\n        Write-OctopusInformation \"Waiting 5 seconds to check status\"\n        Start-Sleep -Seconds 5\n        $taskStatusResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint $taskStatusUrl -method \"GET\" -item $null\n        $taskStatusResponseState = $taskStatusResponse.State\n\n        if ($taskStatusResponseState -eq \"Success\")\n        {\n            Write-OctopusSuccess \"The task has finished with a status of Success\"\n            exit 0            \n        }\n        elseif($taskStatusResponseState -eq \"Failed\" -or $taskStatusResponseState -eq \"Canceled\")\n        {\n            Write-OctopusSuccess \"The task has finished with a status of $taskStatusResponseState status, stopping the run/deployment\"\n            exit 1            \n        }\n        elseif($taskStatusResponse.HasPendingInterruptions -eq $true)\n        {\n            if ($autoApproveRunbookRunManualInterventions -eq \"Yes\")\n            {\n                Submit-RunbookRunForAutoApproval -createdRunbookRun $createdRunbookRun -parentTaskApprovers $parentTaskApprovers -defaultUrl $DefaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $approvalEnvironmentName -parentRunbookId $parentRunbookId -parentTaskId $parentTaskId\n            }\n            else\n            {\n                if ($numberOfWaits -ge 10)\n                {\n                    Write-OctopusSuccess \"The child project has pending manual intervention(s).  Unless you approve it, this task will time out.\"\n                }\n                else\n                {\n                    Write-OctopusInformation \"The child project has pending manual intervention(s).  Unless you approve it, this task will time out.\"                        \n                }\n            }\n        }\n        \n        $numberOfWaits += 1\n        if ($numberOfWaits -ge 10)\n        {\n        \tWrite-OctopusSuccess \"The task state is currently $taskStatusResponseState\"\n        \t$numberOfWaits = 0\n        }\n        else\n        {\n        \tWrite-OctopusInformation \"The task state is currently $taskStatusResponseState\"\n        }  \n        \n        $startTime = $taskStatusResponse.StartTime\n        if ($startTime -eq $null -or [string]::IsNullOrWhiteSpace($startTime) -eq $true)\n        {        \n        \tWrite-OctopusInformation \"The task is still queued, let's wait a bit longer\"\n        \t$startTime = Get-Date\n        }\n        $startTime = [DateTime]$startTime\n        \n        $currentTime = Get-Date\n        $dateDifference = $currentTime - $startTime        \n    }\n    \n    Write-OctopusSuccess \"The cancel timeout has been reached, cancelling the runbook run\"\n    $cancelResponse = Invoke-RestMethod \"$runbookBaseUrl/api/tasks/$runbookServerTaskId/cancel\" -Headers $header -Method Post\n    Write-OctopusSuccess \"Exiting with an error code of 1 because we reached the timeout\"\n    exit 1\n}\n\nfunction Get-QueueDate\n{\n\tparam ( \n    \t$futureDeploymentDate\n    )\n    \n    if ([string]::IsNullOrWhiteSpace($futureDeploymentDate) -or $futureDeploymentDate -eq \"N/A\")\n    {\n    \treturn $null\n    }\n    \n    $addOneDay = $false\n    $textToParse = $futureDeploymentDate.ToLower()\n    if ($textToParse -like \"tomorrow*\")\n    {\n    \tWrite-Host \"The future date $futureDeploymentDate supplied contains tomorrow, will add one day to whatever the parsed result is.\"\n    \t$addOneDay = $true\n        $textToParse = $textToParse -replace \"tomorrow\", \"\"\n    }\n    \n    [datetime]$outputDate = New-Object DateTime\n    $currentDate = Get-Date\n    $currentDate = $currentDate.AddMinutes(2)\n\n    if ([datetime]::TryParse($textToParse, [ref]$outputDate) -eq $false)\n    {\n        Write-OctopusCritical \"The suppplied date $textToParse cannot be parsed by DateTime.TryParse.  Please verify format and try again.  Please [refer to Microsoft's Documentation](https://docs.microsoft.com/en-us/dotnet/api/system.datetime.tryparse) on supported formats.\"\n        exit 1\n    }\n    \n    Write-Host \"The proposed date is $outputDate.  Checking to see if this will occur in the past.\"\n    \n    if ($addOneDay -eq $true)\n    {\n    \t$outputDate = $outputDate.AddDays(1)\n    \tWrite-host \"The text supplied included tomorrow, adding one day.  The new proposed date is $outputDate.\"\n    }\n    \n    if ($currentDate -gt $outputDate)\n    {\n    \tWrite-OctopusCritical \"The supplied date $futureDeploymentDate is set for the past.  All queued deployments must be in the future.\"\n        exit 1\n    }\n    \n    return $outputDate\n}\n\nfunction Get-QueueExpiryDate\n{\n\tparam (\n    \t$queueDate\n    )\n    \n    if ($null -eq $queueDate)\n    {\n    \treturn $null\n    }\n    \n    return $queueDate.AddHours(1)\n}\n\nfunction Get-RunbookSpecificMachines\n{\n    param (\n        $defaultUrl,\n        $octopusApiKey,    \n        $runbookPreview,\n        $runbookMachines,        \n        $runbookRunName        \n    )\n\n    if ($runbookMachines -eq \"N/A\")\n    {\n        return @()\n    }\n\n    if ([string]::IsNullOrWhiteSpace($runbookMachines) -eq $true)\n    {\n        return @()\n    }\n\n    $translatedList = Get-MachineIdsFromMachineNames -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -targetMachines $runbookMachines\n\n    $filteredList = @()    \n    foreach ($runbookMachine in $translatedList)\n    {    \t\n    \t$runbookMachineId = $runbookMachine.Trim().ToLower()\n    \tWrite-OctopusVerbose \"Checking if $runbookMachineId is set to run on any of the runbook steps\"\n        \n        foreach ($step in $runbookPreview.StepsToExecute)\n        {\n            foreach ($machine in $step.Machines)\n            {\n            \tWrite-OctopusVerbose \"Checking if $runbookMachineId matches $($machine.Id) and it isn't already in the $($filteredList -join \",\")\"\n                if ($runbookMachineId -eq $machine.Id.Trim().ToLower() -and $filteredList -notcontains $machine.Id)\n                {\n                \tWrite-OctopusInformation \"Adding $($machine.Id) to the list\"\n                    $filteredList += $machine.Id\n                }\n            }\n        }\n    }\n\n    if ($filteredList.Length -le 0)\n    {\n        Write-OctopusSuccess \"The current task is targeting specific machines, but the runbook $runBookRunName does not run against any of these machines $runbookMachines. Skipping this run.\"\n        exit 0\n    }\n\n    return $filteredList\n}\n\nfunction Get-ParentTaskApprovers\n{\n    param (\n        $parentTaskId,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey\n    )\n    \n    $approverList = @()\n    if ($null -eq $parentTaskId)\n    {\n    \tWrite-OctopusInformation \"The deployment task id to pull the approvers from is null, return an empty approver list\"\n    \treturn $approverList\n    }\n\n    Write-OctopusInformation \"Getting all the events from the parent project\"\n    $parentEvents = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"events?regardingAny=$parentTaskId&spaces=$spaceId&includeSystem=true\" -apiKey $octopusApiKey -method \"GET\"\n    \n    foreach ($parentEvent in $parentEvents.Items)\n    {\n        Write-OctopusVerbose \"Checking $($parentEvent.Message) for manual intervention\"\n        if ($parentEvent.Message -like \"Submitted interruption*\")\n        {\n            Write-OctopusVerbose \"The event $($parentEvent.Id) is a manual intervention approval event which was approved by $($parentEvent.Username).\"\n\n            $approverExists = $approverList | Where-Object {$_.Id -eq $parentEvent.UserId}        \n\n            if ($null -eq $approverExists)\n            {\n                $approverInformation = @{\n                    Id = $parentEvent.UserId;\n                    Username = $parentEvent.Username;\n                    Teams = @()\n                }\n\n                $approverInformation.Teams = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"teammembership?userId=$($approverInformation.Id)&spaces=$spaceId&includeSystem=true\" -apiKey $octopusApiKey -method \"GET\"            \n\n                Write-OctopusVerbose \"Adding $($approverInformation.Id) to the approval list\"\n                $approverList += $approverInformation\n            }        \n        }\n    }\n\n    return $approverList\n}\n\nfunction Get-ApprovalTaskIdFromDeployment\n{\n    param (\n        $parentReleaseId,\n        $approvalEnvironment,\n        $parentChannelId,    \n        $parentEnvironmentId,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey \n    )\n\n    $releaseDeploymentList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"releases/$parentReleaseId/deployments\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId\n    \n    $lastDeploymentTime = $(Get-Date).AddYears(-50)\n    $approvalTaskId = $null\n    foreach ($deployment in $releaseDeploymentList.Items)\n    {\n        if ($deployment.EnvironmentId -ne $approvalEnvironment.Id)\n        {\n            Write-OctopusInformation \"The deployment $($deployment.Id) deployed to $($deployment.EnvironmentId) which doesn't match $($approvalEnvironment.Id).\"\n            continue\n        }\n        \n        Write-OctopusInformation \"The deployment $($deployment.Id) was deployed to the approval environment $($approvalEnvironment.Id).\"\n\n        $deploymentTask = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $null -endPoint \"tasks/$($deployment.TaskId)\" -apiKey $octopusApiKey -Method \"Get\"\n        if ($deploymentTask.IsCompleted -eq $true -and $deploymentTask.FinishedSuccessfully -eq $false)\n        {\n            Write-Information \"The deployment $($deployment.Id) was deployed to the approval environment, but it encountered a failure, moving onto the next deployment.\"\n            continue\n        }\n\n        if ($deploymentTask.StartTime -gt $lastDeploymentTime)\n        {\n            $approvalTaskId = $deploymentTask.Id\n            $lastDeploymentTime = $deploymentTask.StartTime\n        }\n    }        \n\n    if ($null -eq $approvalTaskId)\n    {\n    \tWrite-OctopusVerbose \"Unable to find a deployment to the environment, determining if it should've happened already.\"\n        $channelInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"channels/$parentChannelId\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId\n        $lifecycle = Get-OctopusLifeCycle -channel $channelInformation -defaultUrl $defaultUrl -spaceId $spaceId -OctopusApiKey $octopusApiKey\n        $lifecyclePhases = Get-LifecyclePhases -lifecycle $lifecycle -defaultUrl $defaultUrl -spaceId $spaceid -OctopusApiKey $octopusApiKey\n        \n        $foundDestinationFirst = $false\n        $foundApprovalFirst = $false\n        \n        foreach ($phase in $lifecyclePhases.Phases)\n        {\n        \tif ($phase.AutomaticDeploymentTargets -contains $parentEnvironmentId -or $phase.OptionalDeploymentTargets -contains $parentEnvironmentId)\n            {\n            \tif ($foundApprovalFirst -eq $false)\n                {\n                \t$foundDestinationFirst = $true\n                }\n            }\n            \n            if ($phase.AutomaticDeploymentTargets -contains $approvalEnvironment.Id -or $phase.OptionalDeploymentTargets -contains $approvalEnvironment.Id)\n            {\n            \tif ($foundDestinationFirst -eq $false)\n                {\n                \t$foundApprovalFirst = $true\n                }\n            }\n        }\n        \n        $messageToLog = \"Unable to find a deployment for the environment $approvalEnvironmentName.  Auto approvals are disabled.\"\n        if ($foundApprovalFirst -eq $true)\n        {\n        \tWrite-OctopusWarning $messageToLog\n        }\n        else\n        {\n        \tWrite-OctopusInformation $messageToLog\n        }\n        \n        return $null\n    }\n\n    return $approvalTaskId\n}\n\nfunction Get-ApprovalTaskIdFromRunbook\n{\n    param (\n        $parentRunbookId,\n        $approvalEnvironment,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey \n    )\n}\n\nfunction Get-ApprovalTaskId\n{\n\tparam (\n    \t$autoApproveRunbookRunManualInterventions,\n        $parentTaskId,\n        $parentReleaseId,\n        $parentRunbookId,\n        $parentEnvironmentName,\n        $approvalEnvironmentName,\n        $parentChannelId,    \n        $parentEnvironmentId,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey        \n    )\n    \n    if ($autoApproveRunbookRunManualInterventions -eq $false)\n    {\n    \tWrite-OctopusInformation \"Auto approvals are disabled, skipping pulling the approval deployment task id\"\n        return $null\n    }\n    \n    if ([string]::IsNullOrWhiteSpace($approvalEnvironmentName) -eq $true)\n    {\n    \tWrite-OctopusInformation \"Approval environment not supplied, using the current environment id for approvals.\"\n        return $parentTaskId\n    }\n    \n    if ($approvalEnvironmentName.ToLower().Trim() -eq $parentEnvironmentName.ToLower().Trim())\n    {\n        Write-OctopusInformation \"The approval environment is the same as the current environment, using the current task id $parentTaskId\"\n        return $parentTaskId\n    }\n    \n    $approvalEnvironment = Get-OctopusItemFromListEndpoint -itemNameToFind $approvalEnvironmentName -itemType \"Environment\" -defaultUrl $DefaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -defaultValue $null -endpoint \"environments\"\n    \n    if ([string]::IsNullOrWhiteSpace($parentReleaseId) -eq $false)\n    {\n        return Get-ApprovalTaskIdFromDeployment -parentReleaseId $parentReleaseId -approvalEnvironment $approvalEnvironment -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId\n    }\n\n    return Get-ApprovalTaskIdFromRunbook -parentRunbookId $parentRunbookId -approvalEnvironment $approvalEnvironment -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n}\n\nfunction Get-OctopusLifecycle\n{\n    param (\n        $channel,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    Write-OctopusInformation \"Attempting to find the lifecycle information $($channel.Name)\"\n    if ($null -eq $channel.LifecycleId)\n    {\n        $lifecycleName = \"Default Lifecycle\"\n        $lifecycleList = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"lifecycles?partialName=$([uri]::EscapeDataString($lifecycleName))&skip=0&take=1\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n        $lifecycle = $lifecycleList.Items[0]\n    }\n    else\n    {\n        $lifecycle = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"lifecycles/$($channel.LifecycleId)\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n    }\n\n    Write-Host \"Successfully found the lifecycle $($lifecycle.Name) to use for this channel.\"\n\n    return $lifecycle\n}\n\nfunction Get-LifecyclePhases\n{\n    param (\n        $lifecycle,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    Write-OctopusInformation \"Attempting to find the phase in the lifecycle $($lifecycle.Name) with the environment $environmentName to find the previous phase.\"\n    if ($lifecycle.Phases.Count -eq 0)\n    {\n        Write-OctopusInformation \"The lifecycle $($lifecycle.Name) has no set phases, calling the preview endpoint.\"\n        $lifecyclePreview = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"lifecycles/$($lifecycle.Id)/preview\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n        $phases = $lifecyclePreview.Phases\n    }\n    else\n    {\n        Write-OctopusInformation \"The lifecycle $($lifecycle.Name) has set phases, using those.\"\n        $phases = $lifecycle.Phases    \n    }\n\n    Write-OctopusInformation \"Found $($phases.Length) phases in this lifecycle.\"\n    return $phases\n}\n\nfunction Submit-RunbookRunForAutoApproval\n{\n    param (\n        $createdRunbookRun,\n        $parentTaskApprovers,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId,\n        $parentProjectName,\n        $parentReleaseNumber,\n        $parentRunbookId,\n        $parentEnvironmentName,\n        $parentTaskId        \n    )\n\n    Write-OctopusSuccess \"The task has a pending manual intervention.  Checking parent approvals.\"    \n    $manualInterventionInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"interruptions?regarding=$($createdRunbookRun.TaskId)\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId\n    foreach ($manualIntervention in $manualInterventionInformation.Items)\n    {\n        if ($manualIntervention.IsPending -eq $false)\n        {\n            Write-OctopusInformation \"This manual intervention has already been approved.  Proceeding onto the next one.\"\n            continue\n        }\n\n        if ($manualIntervention.CanTakeResponsibility -eq $false)\n        {\n            Write-OctopusSuccess \"The user associated with the API key doesn't have permissions to take responsibility for the manual intervention.\"\n            Write-OctopusSuccess \"If you wish to leverage the auto-approval functionality give the user permissions.\"\n            continue\n        }        \n\n        $automaticApprover = $null\n        Write-OctopusVerbose \"Checking to see if one of the parent project approvers is assigned to one of the manual intervention teams $($manualIntervention.ResponsibleTeamIds)\"\n        foreach ($approver in $parentTaskApprovers)\n        {\n            foreach ($approverTeam in $approver.Teams)\n            {\n                Write-OctopusVerbose \"Checking to see if $($manualIntervention.ResponsibleTeamIds) contains $($approverTeam.TeamId)\"\n                if ($manualIntervention.ResponsibleTeamIds -contains $approverTeam.TeamId)\n                {\n                    $automaticApprover = $approver\n                    break\n                }\n            }\n\n            if ($null -ne $automaticApprover)\n            {\n                break\n            }\n        }\n\n        if ($null -ne $automaticApprover)\n        {\n        \tWrite-OctopusSuccess \"Matching approver found auto-approving.\"\n            if ($manualIntervention.HasResponsibility -eq $false)\n            {\n                Write-OctopusInformation \"Taking over responsibility for this manual intervention.\"\n                $takeResponsiblilityResponse = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"interruptions/$($manualIntervention.Id)/responsible\" -method \"PUT\" -apiKey $octopusApiKey -spaceId $spaceId\n                Write-OctopusVerbose \"Response from taking responsibility $($takeResponsiblilityResponse.Id)\"\n            }\n            \n            if ([string]::IsNullOrWhiteSpace($parentReleaseNumber) -eq $false)\n            {\n                $notes = \"Auto-approving this runbook run.  Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName with the task id $parentTaskId was approved by $($automaticApprover.UserName).  That user is a member of one of the teams this manual intervention requires.  You can view that deployment $defaultUrl/app#/$spaceId/tasks/$parentTaskId\"\n            }\n            else \n            {\n                $notes = \"Auto-approving this runbook run.  Parent project $parentProjectName runbook run $parentRunbookId to $parentEnvironmentName with the task id $parentTaskId was approved by $($automaticApprover.UserName).  That user is a member of one of the teams this manual intervention requires.  You can view that runbook run $defaultUrl/app#/$spaceId/tasks/$parentTaskId\"\n            }\n            if ($runbookCustomNotesToggle -eq $true){\n              $notes = $runbookCustomNotes\n            }\n            $submitApprovalBody = @{\n                Instructions = $null;\n                Notes = $notes\n                Result = \"Proceed\"\n            }\n            $submitResult = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"interruptions/$($manualIntervention.Id)/submit\" -method \"POST\" -apiKey $octopusApiKey -item $submitApprovalBody -spaceId $spaceId\n            Write-OctopusSuccess \"Successfully auto approved the manual intervention $($submitResult.Id)\"\n        }\n        else\n        {\n            Write-OctopusSuccess \"Couldn't find an approver to auto-approve the child project.  Waiting until timeout or child project is approved.\"    \n        }\n    }\n}\n\n\n$runbookWaitForFinish = GetCheckboxBoolean -Value $runbookWaitForFinish\n$runbookUseGuidedFailure = GetCheckboxBoolean -Value $runbookUseGuidedFailure\n$runbookUsePublishedSnapshot = GetCheckboxBoolean -Value $runbookUsePublishedSnapshot\n$runbookCancelInSeconds = [int]$runbookCancelInSeconds\n\nWrite-OctopusInformation \"Wait for Finish Before Check: $runbookWaitForFinish\"\nWrite-OctopusInformation \"Use Guided Failure Before Check: $runbookUseGuidedFailure\"\nWrite-OctopusInformation \"Use Published Snapshot Before Check: $runbookUsePublishedSnapshot\"\nWrite-OctopusInformation \"Runbook Name $runbookRunName\"\nWrite-OctopusInformation \"Runbook Base Url: $runbookBaseUrl\"\nWrite-OctopusInformation \"Runbook Space Name: $runbookSpaceName\"\nWrite-OctopusInformation \"Runbook Environment Name: $runbookEnvironmentName\"\nWrite-OctopusInformation \"Runbook Tenant Name: $runbookTenantName\"\nWrite-OctopusInformation \"Wait for Finish: $runbookWaitForFinish\"\nWrite-OctopusInformation \"Use Guided Failure: $runbookUseGuidedFailure\"\nWrite-OctopusInformation \"Cancel run in seconds: $runbookCancelInSeconds\"\nWrite-OctopusInformation \"Use Published Snapshot: $runbookUsePublishedSnapshot\"\nWrite-OctopusInformation \"Auto Approve Runbook Run Manual Interventions: $autoApproveRunbookRunManualInterventions\"\nWrite-OctopusInformation \"Auto Approve environment name to pull approvals from: $approvalEnvironmentName\"\n\nWrite-OctopusInformation \"Octopus runbook run machines: $runbookMachines\"\nWrite-OctopusInformation \"Parent Task Id: $parentTaskId\"\nWrite-OctopusInformation \"Parent Release Id: $parentReleaseId\"\nWrite-OctopusInformation \"Parent Channel Id: $parentChannelId\"\nWrite-OctopusInformation \"Parent Environment Id: $parentEnvironmentId\"\nWrite-OctopusInformation \"Parent Runbook Id: $parentRunbookId\"\nWrite-OctopusInformation \"Parent Environment Name: $parentEnvironmentName\"\nWrite-OctopusInformation \"Parent Release Number: $parentReleaseNumber\"\n\n$verificationPassed = @()\n$verificationPassed += Test-RequiredValues -variableToCheck $runbookRunName -variableName \"Runbook Name\"\n$verificationPassed += Test-RequiredValues -variableToCheck $runbookBaseUrl -variableName \"Base Url\"\n$verificationPassed += Test-RequiredValues -variableToCheck $runbookApiKey -variableName \"Api Key\"\n$verificationPassed += Test-RequiredValues -variableToCheck $runbookEnvironmentName -variableName \"Environment Name\"\n\nif ($verificationPassed -contains $false)\n{\n\tWrite-OctopusInformation \"Required values missing\"\n\tExit 1\n}\n\n$runbookSpace = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookSpaceName -endpoint \"spaces\" -spaceId $null -octopusApiKey $runbookApiKey -defaultUrl $runbookBaseUrl -itemType \"Space\" -defaultValue $octopusSpaceId\n$runbookSpaceId = $runbookSpace.Id\n\n$projectToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookProjectName -endpoint \"projects\" -spaceId $runbookSpaceId -defaultValue $null -itemType \"Project\" -octopusApiKey $runbookApiKey -defaultUrl $runbookBaseUrl\nif ($null -ne $projectToUse)\n{\t    \n    $runbookEndPoint = \"projects/$($projectToUse.Id)/runbooks\"\n}\nelse\n{\n\t$runbookEndPoint = \"runbooks\"\n}\n\n$environmentToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookEnvironmentName -itemType \"Environment\" -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey -defaultValue $null -endpoint \"environments\"\n\n$runbookToRun = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookRunName -itemType \"Runbook\" -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -endpoint $runbookEndPoint -octopusApiKey $runbookApiKey -defaultValue $null\n\n$runbookSnapShotIdToUse = Get-RunbookSnapshotIdToRun -runbookToRun $runbookToRun -runbookUsePublishedSnapshot $runbookUsePublishedSnapshot -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $octopusSpaceId\n$projectNameForUrl = Get-ProjectSlug -projectToUse $projectToUse -runbookToRun $runbookToRun -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $runbookSpaceId\n\n$tenantToUse = Get-OctopusItemFromListEndpoint -itemNameToFind $runbookTenantName -itemType \"Tenant\" -defaultValue $null -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey -endpoint \"tenants\" -defaultUrl $runbookBaseUrl\nif ($null -ne $tenantToUse)\n{\t\n    $tenantIdToUse = $tenantToUse.Id  \n    $runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint \"runbooks/$($runbookToRun.Id)/runbookRuns/preview/$($environmentToUse.Id)/$($tenantIdToUse)\" -method \"GET\" -item $null\n}\nelse\n{\n\ttry\n    {\n    \tWrite-Host \"Trying the new preview step\"\n    \t$runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint \"runbookSnapshots/$($runbookSnapShotIdToUse)/runbookRuns/preview/$($environmentToUse.Id)?includeDisabledSteps=true\" -method \"GET\" -item $null\n    }\n    catch\n    {\n    \tWrite-Host \"The current version of Octopus Deploy doesn't support Runbook Snapshot Preview\"\n    \t$runBookPreview = Invoke-OctopusApi -octopusUrl $runbookBaseUrl -spaceId $runbookSpaceId -apiKey $runbookApiKey -endPoint \"runbooks/$($runbookToRun.Id)/runbookRuns/preview/$($environmentToUse.Id)\" -method \"GET\" -item $null\n   \t}\n}\n\n$childRunbookRunSpecificMachines = Get-RunbookSpecificMachines -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -runbookPreview $runBookPreview -runbookMachines $runbookMachines -runbookRunName $runbookRunName\n$runbookFormValues = Get-RunbookFormValues -runbookPreview $runBookPreview -runbookPromptedVariables $runbookPromptedVariables\n\n$queueDate = Get-QueueDate -futureDeploymentDate $runbookFutureDeploymentDate\n$queueExpiryDate = Get-QueueExpiryDate -queueDate $queueDate\n\n$runbookBody = @{\n    RunbookId = $($runbookToRun.Id);\n    RunbookSnapShotId = $runbookSnapShotIdToUse;\n    FrozenRunbookProcessId = $null;\n    EnvironmentId = $($environmentToUse.Id);\n    TenantId = $tenantIdToUse;\n    SkipActions = @();\n    QueueTime = $queueDate;\n    QueueTimeExpiry = $queueExpiryDate;\n    FormValues = $runbookFormValues;\n    ForcePackageDownload = $false;\n    ForcePackageRedeployment = $true;\n    UseGuidedFailure = $runbookUseGuidedFailure;\n    SpecificMachineIds = @($childRunbookRunSpecificMachines);\n    ExcludedMachineIds = @()\n}\n\n$approvalTaskId = Get-ApprovalTaskId -autoApproveRunbookRunManualInterventions $autoApproveRunbookRunManualInterventions -parentTaskId $parentTaskId -parentReleaseId $parentReleaseId -parentRunbookId $parentRunbookId -parentEnvironmentName $parentEnvironmentName -approvalEnvironmentName $approvalEnvironmentName -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -defaultUrl $runbookBaseUrl -spaceId $runbookSpaceId -octopusApiKey $runbookApiKey\n$parentTaskApprovers = Get-ParentTaskApprovers -parentTaskId $approvalTaskId -spaceId $runbookSpaceId -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey\n\nInvoke-OctopusDeployRunbook -runbookBody $runbookBody -runbookWaitForFinish $runbookWaitForFinish -runbookCancelInSeconds $runbookCancelInSeconds -projectNameForUrl $projectNameForUrl -defaultUrl $runbookBaseUrl -octopusApiKey $runbookApiKey -spaceId $runbookSpaceId -parentTaskApprovers $parentTaskApprovers -autoApproveRunbookRunManualInterventions $autoApproveRunbookRunManualInterventions -parentProjectName $projectNameForUrl -parentReleaseNumber $parentReleaseNumber -approvalEnvironmentName $approvalEnvironmentName -parentRunbookId $parentRunbookId -parentTaskId $approvalTaskId",
    "Octopus.Action.Script.Syntax": "PowerShell"
  },
  "Category": "Octopus",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/run-octopus-runbook.json",
  "Website": "/step-templates/0444b0b3-088e-4689-b755-112d1360ffe3",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAC1QTFRFT6Tl////L5Pg8vj9Y67omsvwPJrisdfzfbzs5fL7y+T32Ov5isLucLXqvt31CJPHWwAABMJJREFUeNrs3deW4jAMAFDF3U75/89dlp0ZhiU4blJEjvQ8hYubLJsA00UCBCIQgQhEIAIRiEAEIhCBCEQgAhGIQAQiEIEIhD8kJm+t+QprfdKfB9HbYpx6CWfspj8HMi+gMgHL/AmQA8W3JTKH+ALFvzCeL0RbpyoCPE9IJeNOSQwh5Z3qd6yRGWQ2qi2cZQWxqj1WzQYSjeoJmJlAklOd4VlArOqPhQEkqBERToeMcfRJBkC0Uep8CfBpjz4JsHJ0zF3dkEWNje0kiB/sUC6eApndaIiCMyAa1PiwJ0AWhRGJHJJQHG2dC7h1rNbO1QOxSA7lNCkkKrQIpJCAB1GREILYIC1NAiwbpKFJgGWDNExcwGstfExcZBCHC6nOglshHtmhViLIig1RNBCN7qjtW8C0Z1UvJcC1Z9XmwMBzzvobmgAyEzgq91dtEEsBsQSQQAFZCSBAATEEEApHZbrVBIkkEIUPSVeB+KtALA0kXQUSrwKZBCIQBnk8Y4i5CsReBeKvkqLM+BCSDWJlrZFvGk9SRTHshkgjZCGAaArIxm3H3grhVzFlW2msfl1ca79UJ1bofYvsDHHlNdTZnlh5MghuPd5NdBDUNZHyCkfktIh03XzALGRPlBDPac7qgWjHZzWcmF5zmmkhidMQ6boKiDXcDTUEaylZqCGJ0Vjvu/fLJtHqhSANEvqb2OYqkOUqEHuVMbJcZdZCGiPhKhC4yjqiIjEE7XThMp8fAWII3mY3kUIQD+AMKQTzPiBhgQ63HlT/KSvgtoi0dq5mCPah1UIE0eh3sT0NhOByvKeAkFzi8PgQomumFhsyOxpIzZN4gLOj5plVwNpR0b2AuePWKBEHQu24pSsJA+LVCeHHQxZ1SiyDIdqok8IOhSSnTottHEQTdyt4ettAj4KkzA4dMikk2Dht2S5ptm1vswnPDxn0YyDZ5oDM3iToo2T5voWaYe+Q+vdjH80QyAzZhCgcDtLMI1Tmtz9w++XHgziHQHJJu/OZ3bs9Xn8gQ72NcP3dKqEfkp10F51xhoIi2I91R+LurXV/5q7pH+wx061CzO16oSQleMyr8fXvwMA0Pro8432DPD/ySx8XrHfSuDAM8n6UhnjQabaiXf5Bq/lREHvEeNtn1rJ08+C/uXkQZHeguxAPC3UvtcJYUogLzZX5hhZZvS6onG5lxXtzWGaygwb79vT/IXhdlNibwlKYOR6T8xjI7W8n+xV7T+GH4tMzWwR+lZhRkJYSsC0thpmCYqyngOz3rN2FLBZ2wZflBCggUHF0Vnp88JKienzIXLSEZCZqU7IKr/gQW9yx3pzV7Y9kvWZWTRRIqDmTtRUnU7b2lLcTYmoqHqnmiO1poER0SPkAeZMAZxaJx0Y3TCdAclsIqDz03ALcyxfTCZBsthoGXWmigGyVhWPLFJJfuuKQWycoEFdXbH4dJJoJxNR1eD/kshz6yn48cF8yW8sFoitflB1w6Q8n+/15Za7oA17/pYNmYgP5fmWm8L1NOHPWgK8kuFew1/JXtOA0yJCv7ah7X8ObUuT5kObU30+fDZm8+zqP+HTIpK0xQ796b5Kv2hSIQAQiEIEIRCACEYhABCIQgQhEIAIRiEAEIpBf8UeAAQAEjtYmlDTcCgAAAABJRU5ErkJggg==",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Tuesday, July 23, 2024