Deploy Child Octopus Deploy Project

Octopus.Script exported 2023-11-17 by REOScotte belongs to ‘Octopus’ category.

This step will find the latest release in a source environment matching your criteria and deploy it.

Use cases:

  • As a user, I want to create a single parent release 2020.2.1. When I promote the parent release I want the latest child releases matching 2020.2.1.* to be promoted to the next environment.
  • As a user, I want the latest release in the dev environment to be promoted to the test environment. Not the most recently created release, the most recent release deployed that environment.
  • As a user, when we are finished with our QA process, we want to automatically push the latest releases from QA to Staging without having to manually promote each release.
  • As a user, I’d like to set up a nightly build to promote the latest releases from Dev to QA
  • As a user, I’d like to be able to deploy a suite of applications to a tenant. If the tenant isn’t assigned to the project then skip over.
  • As a user, I’d like to see what releases would go to production and approve those releases without having to manually verify and approve each one.
  • As a user, I’d like to be able to target specific machines in my parent project and only have child projects deploy associated with those machines.
  • As a user, I’d like to be able to exclude specific machines in my parent project and only have child projects deploy to the remaining machines.
  • As a user, I’d like to have a single deployment target trigger on my parent project and when I scale up my servers deploy the appropriate child projects.
  • As a user, I’d like to be able to approve the deployments and then schedule them to be deployed at 7 PM
  • As a user, I’d like to be able to have one space for orchestration projects and another space for developers to work in.

This step template also allows you to skip deployments to the destination environment if it has already been deployed.

Parameters

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

Octopus Base Url

ChildProject.Web.ServerUrl = #{Octopus.Web.ServerUri}

Required

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

Octopus API Key

ChildProject.Api.Key =

Required

The API key of the user authorized to query and promote releases.

Child Project Space

ChildProject.Space.Name = #{Octopus.Space.Name}

Required

The name of the space the child project is located in. Defaults to the current space name

Child Project Name

ChildProject.Project.Name =

Required

The name of the child project you wish to deploy.

Child Project Release Number

ChildProject.Release.Number =

Optional

The release number to filter on. This field accepts:

  • No value (default) - the most recent release for the channel in the calculated previous environment or specified previous environment
  • Wild card - Example: 2020.2.* will find the most recent release with a major version of 2020 and a minor version of 2. Please note the period is important, if you enter 2020.1* you could end up with 2020.10 releases.
  • Specific version - Example: 2020.2.1.2 will deploy that specific version.

Child Project Release Not Found Error Handle

ChildProject.Release.NotFoundError = Warning

What this step should do when a matching release cannot be found.

  • Error - Stops the deployment when no matching release is found. For example, you specified 2021.1.2.*, but there is no release in the child project matching that pattern. Or, matching releases have been found but they cannot be promoted to the destination environment.
  • Warning - Stops this step with a warning. The rest of the deployment continues.
  • Skip - Stops this with no error or warning. Logs an information message. The rest of the deployment continues.

The default is Warning.

Destination Environment Name

ChildProject.Destination.EnvironmentName = #{Octopus.Environment.Name}

Required

The name of the destination environment.

Examples: Development, Test, or Production

The parent project and child project do NOT have to have the same lifecycle. The only requirement is all the previous phases’ requirements in the child project must be met. For example, if the child project’s life cycle is Dev->Test->Staging->Prod and the parent project lifecycle is Staging->Prod and you wish to deploy to staging, then the child project’s release must go through Dev and Test first.

Source Environment Name

ChildProject.SourceEnvironment.Name =

Optional

The name of the source environment. When blank the source environment will be calculated using the channel’s lifecycle.

Examples: Development, Test, or Production

Please Note: The most recently created release will be selected when the destination environment is the first phase of the child project’s lifecycle.

Child Project Channel

ChildProject.Channel.Name =

Optional

The name of the channel in the child project tied to the release you wish to deploy. If left blank it will look at the project’s default channel.

Tenant Name

ChildProject.Tenant.Name = #{Octopus.Deployment.Tenant.Name}

Optional

The name of the tenant you wish to deploy.

Child Project Prompted Variables

ChildProject.Prompted.Variables =

Optional

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

Use the format Name::Value

For example:

PromptedVariableName::My Super Awesome Value
OtherPromptedVariable::Other Super Awesome Value

Deployment Mode

ChildProject.DeploymentMode.Value = Promote

Required

Indicates if the step template will promote a release from one environment to another, or it will redeploy an existing release.

Options:

  • Promote: the step template will promote the release from one environment to another
  • Redeploy: the step template will take whatever the last release deployed to the target environment and redeploy it. Useful when you are rebuilding a server.
  • AlwaysLatest: the step template will find the latest release available to the target environment and deploy it, regardless if it has already been deployed or not.

Default is Promote

Refresh Variables Snapshots

ChildProject.RefreshVariableSnapShots.Option = No

Allows you to choose if/when variable snapshots will be refreshed.

  • Yes: Variable snapshot is refreshed when a change is detected
  • No: Never refresh the variable snapshot

Default is No. Recommend configuring a prompted variable to control this option at deployment time. When What If is set to Yes this will report a change has been made, but the snapshot refresh will not run.

Specific Deployment Target

ChildProject.Target.MachineNames = N/A

Optional

A comma-separated list of deployment targets names or ids you want to deploy to. If using an id, it must start with Machines-.

Please note: this step template will filter out any targets your child project cannot deploy to. If you provide a list of 5 machines and the child project can only deploy to 3 of them, then the child project will get 3 machines sent to it.

This will overwrite the machines sent in via a deployment target trigger.

Defaults N/A. If you do want to use this parameter the recommendation is to use prompted variables where you pass in specific machine names.

Ignore specific machine mismatch

ChildProject.Deployment.IgnoreSpecificMachineMismatch = No

When the parent project is deploying to specific machines and the child project isn’t associated with those machines the step will ignore it.

Examples:

  • A deployment target trigger fires for a newly created machine. Only 1 out of 5 child projects deploy to that newly created machine’s roles.
  • A redeployment needs to occur, but only for a specific machine. Only 2 out of the 4 child projects deploy to that specific machine’s roles.

In both those examples, the default behavior of this step template is to skip those child projects not tied to the machine’s roles. When the child project is invoked, the specific matching machines will be sent to the child project.

Set to Yes to ignore the difference. Default is No. Warning, setting to Yes could result in a failed deployment.

Save release notes as artifact

ChildProject.ReleaseNotes.SaveAsArtifact = No

This step will pull the release notes (or build information) from the child project and will save it to the output variable ReleaseNotes.

This option allows you to save those release notes as an artifact. The default is No.

What If

ChildProject.WhatIf.Value = No

By default, this step will trigger a deployment.

Setting this value to Yes will perform all the work up to triggering the deployment. This is useful for approval steps, you can run this step (or set of steps) to get the list of child releases to deploy, and then verify them via a manual intervention.

When this is set to Yes it will set an output variable ReleaseToPromote.

Wait for finish

ChildProject.WaitForFinish.Value = Yes

Set to Yes to avoid waiting for the deployment to finish. Will only be used when What If is set to No.

Enable Enhanced Logging

ChildProject.EnableEnhancedLogging.Value = No

Set to Yes to retrieve output of the child release while waiting for the deployment to finish. Will only be used when What If is set to No and Wait for finish is set to Yes.

Wait for Deployment

ChildProject.CancelDeployment.Seconds = 1800

Amount of time, in seconds, to wait for the deployment to finish. Default is 1800 seconds, or 30 minutes.

Scheduling

ChildProject.Deployment.FutureTime = N/A

Optional

Schedule the deployment for the future. Please note, if this is set, the Wait for Deployment option is ignored.

Uses DateTime.TryParse 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

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.

Auto-Approve Child Project Manual Interventions

ChildProject.ManualInterventions.UseApprovalsFromParent = Yes

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

When a manual intervention in the parent project 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 Yes, allow this to happen. Set it to No to skip this functionality.

Approval Environment

ChildProject.ManualIntervention.EnvironmentToUse = #{Octopus.Environment.Name}

Optional

The name of the environment you wish to pull the approvals from for the parent project. It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment.

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.

Approval Tenant

ChildProject.ManualIntervention.Tenant.Name = #{Octopus.Deployment.Tenant.Name}

Optional

The name of the tenant you wish to pull the approvals from for the parent project. It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment.

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

Defaults to the current tenant name. Will be skipped when doing a non-tenanted deployment.

Script body

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

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

# Supplied Octopus Parameters
$parentReleaseId = $OctopusParameters["Octopus.Release.Id"]
$parentChannelId = $OctopusParameters["Octopus.Release.Channel.Id"]
$destinationSpaceId = $OctopusParameters["Octopus.Space.Id"]
$specificMachines = $OctopusParameters["Octopus.Deployment.SpecificMachines"]
$excludeMachines = $OctopusParameters["Octopus.Deployment.ExcludedMachines"]
$deploymentMachines = $OctopusParameters["Octopus.Deployment.Machines"]
$parentDeploymentTaskId = $OctopusParameters["Octopus.Task.Id"]
$parentProjectName = $OctopusParameters["Octopus.Project.Name"]
$parentReleaseNumber = $OctopusParameters["Octopus.Release.Number"]
$parentEnvironmentName = $OctopusParameters["Octopus.Environment.Name"]
$parentEnvironmentId = $OctopusParameters["Octopus.Environment.Id"]
$parentSpaceId = $OctopusParameters["Octopus.Space.Id"]

# User Parameters
$octopusApiKey = $OctopusParameters["ChildProject.Api.Key"]
$projectName = $OctopusParameters["ChildProject.Project.Name"]
$channelName = $OctopusParameters["ChildProject.Channel.Name"]
$releaseNumber = $OctopusParameters["ChildProject.Release.Number"]
$environmentName = $OctopusParameters["ChildProject.Destination.EnvironmentName"]
$sourceEnvironmentName = $OctopusParameters["ChildProject.SourceEnvironment.Name"]
$formValues = $OctopusParameters["ChildProject.Prompted.Variables"]
$destinationSpaceName = $OctopusParameters["ChildProject.Space.Name"]
$whatIfValue = $OctopusParameters["ChildProject.WhatIf.Value"]
$waitForFinishValue = $OctopusParameters["ChildProject.WaitForFinish.Value"]
$enableEnhancedLoggingValue = $OctopusParameters['ChildProject.EnableEnhancedLogging.Value']
$deploymentCancelInSeconds = $OctopusParameters["ChildProject.CancelDeployment.Seconds"]
$ignoreSpecificMachineMismatchValue = $OctopusParameters["ChildProject.Deployment.IgnoreSpecificMachineMismatch"]
$autoapproveChildManualInterventionsValue = $OctopusParameters["ChildProject.ManualInterventions.UseApprovalsFromParent"]
$saveReleaseNotesAsArtifactValue = $OctopusParameters["ChildProject.ReleaseNotes.SaveAsArtifact"]
$futureDeploymentDate = $OctopusParameters["ChildProject.Deployment.FutureTime"]
$errorHandleForNoRelease = $OctopusParameters["ChildProject.Release.NotFoundError"]
$approvalEnvironmentName = $OctopusParameters["ChildProject.ManualIntervention.EnvironmentToUse"]
$approvalTenantName = $OctopusParameters["ChildProject.ManualIntervention.Tenant.Name"]
$refreshVariableSnapShot = $OctopusParameters["ChildProject.RefreshVariableSnapShots.Option"]
$deploymentMode = $OctopusParameters["ChildProject.DeploymentMode.Value"]
$targetMachines = $OctopusParameters["ChildProject.Target.MachineNames"]
$deploymentTenantName = $OctopusParameters["ChildProject.Tenant.Name"]
$defaultUrl = $OctopusParameters["ChildProject.Web.ServerUrl"]

$cachedResults = @{}

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 Test-RequiredValues
{
	param (
    	$variableToCheck,
        $variableName
    )
    
    if ([string]::IsNullOrWhiteSpace($variableToCheck) -eq $true)
    {
    	Write-OctopusCritical "$variableName is required."
        return $false
    }
    
    return $true
}

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

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

    try
    {        
        if ($null -ne $item)
        {
            $body = $item | ConvertTo-Json -Depth 10
            Write-OctopusVerbose $body

            Write-OctopusInformation "Invoking $method $url"
            return Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -Body $body -ContentType 'application/json; charset=utf-8' 
        }

        if (($null -eq $ignoreCache -or $ignoreCache -eq $false) -and $method.ToUpper().Trim() -eq "GET")
        {
            Write-OctopusVerbose "Checking to see if $url is already in the cache"
            if ($cachedResults.ContainsKey($url) -eq $true)
            {
                Write-OctopusVerbose "$url is already in the cache, returning the result"
                return $cachedResults[$url]
            }
        }
        else
        {
            Write-OctopusVerbose "Ignoring cache."    
        }

        Write-OctopusVerbose "No data to post or put, calling bog standard invoke-restmethod for $url"
        $result = Invoke-RestMethod -Method $method -Uri $url -Headers @{"X-Octopus-ApiKey" = "$ApiKey" } -ContentType 'application/json; charset=utf-8'

        if ($cachedResults.ContainsKey($url) -eq $true)
        {
            $cachedResults.Remove($url)
        }
        Write-OctopusVerbose "Adding $url to the cache"
        $cachedResults.add($url, $result)

        return $result

               
    }
    catch
    {
        if ($null -ne $_.Exception.Response)
        {
            if ($_.Exception.Response.StatusCode -eq 401)
            {
                Write-OctopusCritical "Unauthorized error returned from $url, please verify API key and try again"
            }
            elseif ($_.Exception.Response.statusCode -eq 403)
            {
                Write-OctopusCritical "Forbidden error returned from $url, please verify API key and try again"
            }
            else
            {                
                Write-OctopusVerbose -Message "Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )"
            }            
        }
        else
        {
            Write-OctopusVerbose $_.Exception
        }
    }

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

function Get-ListFromOctopusApi
{
    param (
        $octopusUrl,
        $endPoint,
        $spaceId,
        $apiKey,
        $propertyName
    )

    $rawItemList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint $endPoint -spaceId $spaceId -apiKey $octopusApiKey -method "GET"

    $returnList = @($rawItemList.$propertyName)

    Write-OctopusVerbose "The endpoint $endPoint returned a list with $($returnList.Count) items"

    return ,$returnList
}

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

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

    $item = $itemList | Where-Object { $_.Name.ToLower().Trim() -eq $itemName.ToLower().Trim() }      

    if ($null -eq $item)
    {
        Write-OctopusCritical "Unable to find $itemName.  Exiting with an exit code of 1."
        exit 1
    }

    return $item
}

function Test-PhaseContainsEnvironmentId
{
    param (
        $phase,
        $environmentId
    )

    Write-OctopusVerbose "Checking to see if $($phase.Name) automatic deployment environments $($phase.AutomaticDeploymentTargets) contains $environmentId"
    if ($phase.AutomaticDeploymentTargets -contains $environmentId)
    {
        Write-OctopusVerbose "It does, returning true"
        return $true
    } 
    
    Write-OctopusVerbose "Checking to see if $($phase.Name) optional deployment environments $($phase.OptionalDeploymentTargets) contains $environmentId"
    if ($phase.OptionalDeploymentTargets -contains $environmentId)
    {
        Write-OctopusVerbose "It does, returning true"
        return $true
    }

    Write-OctopusVerbose "The phase does not contain the environment returning false"
    return $false
}

function Get-OctopusItemByName
{
    param(
        $itemName,
        $itemType,
        $endpoint,
        $defaultValue,
        $spaceId,
        $defaultUrl,
        $octopusApiKey
    )

    if ([string]::IsNullOrWhiteSpace($itemName) -or $itemName -like "#{Octopus*")
    {
        Write-OctopusVerbose "The item name passed in was $itemName, returning the default value for $itemType"
        return $defaultValue
    }

    Write-OctopusInformation "Attempting to find $itemType with the name of $itemName"
    
    $itemList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "$($endPoint)?partialName=$([uri]::EscapeDataString($itemName))&skip=0&take=100" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" -propertyName "Items"   
    $item = Get-FilteredOctopusItem -itemList $itemList -itemName $itemName

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

    return $item
}

function Get-OctopusItemById
{
    param(
        $itemId,
        $itemType,
        $endpoint,
        $defaultValue,
        $spaceId,
        $defaultUrl,
        $octopusApiKey
    )

    if ([string]::IsNullOrWhiteSpace($itemId))
    {
        return $defaultValue
    }

    Write-OctopusInformation "Attempting to find $itemType with the id of $itemId"
    
    $item = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "$endPoint/$itemId" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"        

    if ($null -eq $item)
    {
        Write-OctopusCritical "Unable to find $itemType with the id of $itemId"
        exit 1
    }
    else 
    {
        Write-OctopusInformation "Successfully found $itemId with name of $($item.Name)"    
    }
    
    return $item
}

function Get-OctopusSpaceIdByName
{
	param(
    	$spaceName,
        $spaceId,
        $defaultUrl,
        $octopusApiKey    
    )
    
    if ([string]::IsNullOrWhiteSpace($spaceName))
    {
    	return $spaceId
    }

    $space = Get-OctopusItemByName -itemName $spaceName -itemType "Space" -endpoint "spaces" -defaultValue $spaceId -spaceId $null -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey
    
    return $space.Id
}

function Get-OctopusProjectByName
{
    param (
        $projectName,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    return Get-OctopusItemByName -itemName $projectName -itemType "Project" -endpoint "projects" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    
}

function Get-OctopusEnvironmentByName
{
    param (
        $environmentName,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    return Get-OctopusItemByName -itemName $environmentName -itemType "Environment" -endpoint "environments" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    
}

function Get-OctopusTenantByName
{
    param (
        $tenantName,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    return Get-OctopusItemByName -itemName $tenantName -itemType "Tenant" -endpoint "tenants" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    
}

function Get-OctopusApprovalTenant
{
    param (
        $tenantToDeploy,
        $approvalTenantName,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    Write-OctopusInformation "Checking to see if there is an approval tenant to consider"

    if ($null -eq $tenantToDeploy)
    {
        Write-OctopusInformation "Not doing tenant deployments, skipping this check"    
        return $null
    }

    if ([string]::IsNullOrWhiteSpace($approvalTenantName) -eq $true -or $approvalTenantName -eq "#{Octopus.Deployment.Tenant.Name}")
    {
        Write-OctopusInformation "No approval tenant was provided, returning $($tenantToDeploy.Id)"
        return $tenantToDeploy
    }

    if ($approvalTenantName.ToLower().Trim() -eq $tenantToDeploy.Name.ToLower().Trim())
    {
        Write-OctopusInformation "The approval tenant name matches the deployment tenant name, using the current tenant"
        return $tenantToDeploy
    }

    return Get-OctopusTenantByName -tenantName $approvalTenantName -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey
}

function Get-OctopusChannel
{
    param (
        $channelName,
        $project,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    Write-OctopusInformation "Attempting to find the channel information for project $projectName matching the channel name $channelName"
    $channelList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "projects/$($project.Id)/channels?skip=0&take=1000" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" -propertyName "Items"
    $channelToUse = $null
    foreach ($channel in $channelList)
    {
        if ([string]::IsNullOrWhiteSpace($channelName) -eq $true -and $channel.IsDefault -eq $true)
        {
            Write-OctopusVerbose "The channel name specified is null or empty and the current channel $($channel.Name) is the default, using that"
            $channelToUse = $channel
            break
        }

        if ([string]::IsNullOrWhiteSpace($channelName) -eq $false -and $channel.Name.Trim().ToLowerInvariant() -eq $channelName.Trim().ToLowerInvariant())
        {
            Write-OctopusVerbose "The channel name specified $channelName matches the the current channel $($channel.Name) using that"
            $channelToUse = $channel
            break
        }
    }

    if ($null -eq $channelToUse)
    {
        Write-OctopusCritical "Unable to find a channel to use.  Exiting with an exit code of 1."
        exit 1
    }

    return $channelToUse
}

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

    Write-OctopusInformation "Attempting to find the lifecycle information $($channel.Name)"
    if ($null -eq $channel.LifecycleId)
    {
        return Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "lifecycles/$($project.LifecycleId)/preview" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" -propertyName "Phases"
    }
    else
    {
        return Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "lifecycles/$($channel.LifecycleId)/preview" -spaceId $spaceId -apiKey $octopusApiKey -method "GET" -propertyName "Phases"
    }
}

function Get-SourceDestinationEnvironmentInformation
{
    param (
        $phaseList,
        $targetEnvironment,
        $sourceEnvironment,
        $isPromotionMode,
        $isAlwaysLatestMode
    )

    Write-OctopusVerbose "Attempting to pull the environment ids from the source and destination phases"

    $destTargetEnvironmentInfo = @{        
        TargetEnvironment = $targetEnvironment
        SourceEnvironmentList = @()
        FirstLifecyclePhase = $false
        HasRequiredPhase = $false
    }

    if ($isPromotionMode -eq $false)
    {
        if ($isAlwaysLatestMode -eq $true)
        {
            Write-OctopusInformation "Currently running in AlwaysLatest mode, setting the source environment to the target environment."
        }
        else
        {
            Write-OctopusInformation "Currently running in redeploy mode, setting the source environment to the target environment."

        }
        $destTargetEnvironmentInfo.SourceEnvironmentList = $targetEnvironment.Id

        return $destTargetEnvironmentInfo
    }

    $indexOfTargetEnvironment = $null
    for ($i = 0; $i -lt $phaseList.Length; $i++)
    {
        Write-OctopusInformation "Checking to see if lifecycle phase $($phaseList[$i].Name) contains the target environment id $($targetEnvironment.Id)"

        if (Test-PhaseContainsEnvironmentId -phase $phaseList[$i] -environmentId $targetEnvironment.Id)    
        {            
            Write-OctopusVerbose "The phase $($phaseList[$i].Name) has the environment $($targetEnvironment.Name)."
            $indexOfTargetEnvironment = $i
            break
        }
    }

    if ($null -eq $indexOfTargetEnvironment)
    {
        Write-OctopusCritical "Unable to find the target phase in this lifecycle attached to this channel.  Exiting with exit code of 1"
        Exit 1
    }

    if ($indexOfTargetEnvironment -eq 0)
    {
        Write-OctopusInformation "This is the first phase in the lifecycle.  The current mode is promotion.  Going to get the latest release created that matches the release number rules for the channel."
        $destTargetEnvironmentInfo.FirstLifecyclePhase = $true        
        $destTargetEnvironmentInfo.SourceEnvironmentList += $targetEnvironment.Id

        return $destTargetEnvironmentInfo
    }
    
    if ($null -ne $sourceEnvironment)
    {
        Write-OctopusInformation "The source environment $($sourceEnvironment.Name) was provided, using that as the source environment"
        $destTargetEnvironmentInfo.SourceEnvironmentList += $sourceEnvironment.Id

        return $destTargetEnvironmentInfo
    }

    Write-OctopusVerbose "Looping through all the previous phases until a required phase is found."
    $startingIndex = ($indexOfTargetEnvironment - 1)
    for($i = $startingIndex; $i -ge 0; $i--)
    {
        $previousPhase = $phaseList[$i]
        Write-OctopusInformation "Adding environments from the phase $($previousPhase.Name)"
        foreach ($environmentId in $previousPhase.AutomaticDeploymentTargets)
        {
            $destTargetEnvironmentInfo.SourceEnvironmentList += $environmentId
        }

        foreach ($environmentId in $previousPhase.OptionalDeploymentTargets)
        {
            $destTargetEnvironmentInfo.SourceEnvironmentList += $environmentId
        }

        if ($previousPhase.IsOptionalPhase -eq $false)
        {
            Write-OctopusVerbose "The phase $($previousPhase.Name) is a required phase, exiting previous phase loop"
            $destTargetEnvironmentInfo.HasRequiredPhase = $true
            break
        }
        elseif ($i -gt 0)
        {
            Write-OctopusVerbose "The phase $($previousPhase.Name) is an optional phase, continuing going to check the next phase"    
        }
        else
        {
            Write-OctopusVerbose "The phase $($previousPhase.Name) is an optional phase.  This is the last phase so I'm stopping now."    
        }
    }

    return $destTargetEnvironmentInfo             
}

function Get-ReleaseCanBeDeployedToTargetEnvironment
{
    param (
        $release,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $sourceDestinationEnvironmentInfo,
        $tenantToDeploy,
        $isPromotionMode,
        $isAlwaysLatestMode
    )

    if ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)
    {
        Write-OctopusInformation "The current mode is redeploy.  Of course the release can be deployed to the target environment, no need to recheck it."
        return $true
    }

    Write-OctopusInformation "Pulling the deployment template information for release $($release.Version)"
    $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($release.Id)/deployments/template" -spaceId $spaceId -method GET -apiKey $octopusApiKey

    $releaseCanBeDeployedToDestination = $false    
    Write-OctopusInformation "Looping through deployment template list for $($release.Version) to see if it can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)."
    foreach ($promoteToEnvironment in $releaseDeploymentTemplate.PromoteTo)
    {
        if ($promoteToEnvironment.Id -eq $sourceDestinationEnvironmentInfo.TargetEnvironment.Id)
        {
            Write-OctopusInformation "The environment $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) was found in the list of environments to promote to"
            $releaseCanBeDeployedToDestination = $true
            break
        }
    }    

    if ($null -eq $tenantToDeploy -or $releaseDeploymentTemplate.TenantPromotions.Length -le 0)
    {
        return $releaseCanBeDeployedToDestination
    }

    $releaseCanBeDeployedToDestination = $false
    Write-OctopusInformation "The tenant id was supplied, looping through the tenant templates to see if it can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)."
    foreach ($tenantPromotion in $releaseDeploymentTemplate.TenantPromotions)
    {
        if ($tenantPromotion.Id -ne $tenantToDeploy.Id)
        {
            Write-OctopusVerbose "The tenant ids $($tenantPromotion.Id) and $($tenantToDeploy.Id) don't match, moving onto the next one"
            continue
        }

        Write-OctopusVerbose "The tenant Id matches checking to see if the environment can be promoted to."
        foreach ($promoteToEnvironment in $tenantPromotion.PromoteTo)
        {
            if ($promoteToEnvironment.Id -ne $sourceDestinationEnvironmentInfo.TargetEnvironment.Id)
            {
                Write-OctopusVerbose "The environmentIds $($promoteToEnvironment.Id) and $($sourceDestinationEnvironmentInfo.TargetEnvironment.Id) don't match, moving onto the next one."
                continue
            }

            Write-OctopusInformation "The environment $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) was found in the list of environments tenant $($tenantToDeploy.Id) can be promoted to"
            $releaseCanBeDeployedToDestination = $true
        }
    }

    return $releaseCanBeDeployedToDestination
}

function Get-DeploymentPreview
{
    param (
        $releaseToDeploy,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $targetEnvironment,
        $deploymentTenant
    )

    if ($null -eq $deploymentTenant)
    {
        Write-OctopusInformation "The deployment tenant id was not sent in, generating a preview by hitting releases/$($releaseToDeploy.Id)/deployments/preview/$($targetEnvironment.Id)?includeDisabledSteps=true"    
        return Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($releaseToDeploy.Id)/deployments/preview/$($targetEnvironment.Id)?includeDisabledSteps=true" -apiKey $octopusApiKey -method "GET" -spaceId $spaceId
    }

    Write-OctopusInformation "The deployment tenant id was sent in, generating a preview by hitting releases/$($releaseToDeploy.Id)/deployments/previews" 
    $requestBody = @{
    		DeploymentPreviews = @(
    			@{
                	TenantId = $deploymentTenant.Id;
            		EnvironmentId = $targetEnvironment.Id
                 }
            )
    }
    return Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($releaseToDeploy.Id)/deployments/previews" -apiKey $octopusApiKey -method "POST" -spaceId $spaceId -item $requestBody -itemIsArray $true
}

function Get-ValuesForPromptedVariables
{
    param (
        $deploymentPreview,
        $formValues
    )

    $deploymentFormValues = @{}
    if ([string]::IsNullOrWhiteSpace($formValues) -eq $true)
    {
        return $deploymentFormValues
    }   
    
    $promptedValueList = @(($formValues -Split "`n").Trim())
    Write-OctopusVerbose $promptedValueList.Length
    
    foreach($element in $deploymentPreview.Form.Elements)
    {
        $nameToSearchFor = $element.Control.Name
        $uniqueName = $element.Name
        $isRequired = $element.Control.Required
        
        $promptedVariablefound = $false
        
        Write-OctopusVerbose "Looking for the prompted variable value for $nameToSearchFor"
        foreach ($promptedValue in $promptedValueList)
        {
            $splitValue = $promptedValue -Split "::"
            Write-OctopusVerbose "Comparing $nameToSearchFor with provided prompted variable $($promptedValue[0])"
            if ($splitValue.Length -gt 1)
            {
                if ($nameToSearchFor.ToLower().Trim() -eq $splitValue[0].ToLower().Trim())
                {
                    Write-OctopusVerbose "Found the prompted variable value $nameToSearchFor"
                    $deploymentFormValues[$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 $deploymentFormValues
}

function Test-ProjectTenantSettings
{
    param (
        $tenantToDeploy,
        $project,
        $targetEnvironment
    )

    Write-OctopusVerbose "About to check if $tenantToDeploy is not null and tenant deploy mode on the project $($project.TenantedDeploymentMode) <> Untenanted"
    if ($null -eq $tenantToDeploy)
    {
        Write-OctopusInformation "Not doing a tenanted deployment, no need to check if the project supports tenanted deployments."
        return $null
    }

    if ($project.TenantedDeploymentMode -eq "Untenanted")
    {
        Write-OctopusInformation "The project is not tenanted, but we are doing a tenanted deployment, removing the tenant from the equation"
        return $null
    }

    Write-OctopusInformation "Found the tenant $($tenantToDeploy.Name) checking to see if $($project.Name) is assigned to it."
        
    Write-OctopusVerbose "Checking to see if $($tenantToDeploy.ProjectEnvironments) has $($project.Id) as a property."
    if ($null -eq (Get-Member -InputObject $tenantToDeploy.ProjectEnvironments -Name $project.Id -MemberType Properties))
    {
        Write-OctopusSuccess "The tenant $($tenantToDeploy.Name) is not assigned to $($project.Name).  Exiting."
        Insert-EmptyOutputVariables -releaseToDeploy $null
        
        Exit 0
    }

    Write-OctopusInformation "The tenant $($tenantToDeploy.Name) is assigned to $($project.Name).  Now checking to see if it can be deployed to the target environment."
    $tenantProjectId = $project.Id
    
    Write-OctopusVerbose "Checking to see if $($tenantToDeploy.ProjectEnvironments.$tenantProjectId) has $($targetEnvironment.Id)"    
    if ($tenantToDeploy.ProjectEnvironments.$tenantProjectId -notcontains $targetEnvironment.Id)
    {
        Write-OctopusSuccess "The tenant $($tenantToDeploy.Name) is assigned to $($project.Name), but not to the environment $($targetEnvironment.Name).  Exiting."
        Insert-EmptyOutputVariables -releaseToDeploy $null
        
        Exit 0
    } 
    
    return $tenantToDeploy
}

function Test-ReleaseToDeploy
{
	param (
    	$releaseToDeploy,
        $errorHandleForNoRelease,
        $releaseNumber,        
        $sourceDestinationEnvironmentInfo, 
        $environmentList
    )
    
    if ($null -ne $releaseToDeploy)
    {
    	return
    }
        
    $errorMessage = "No releases were found in environment(s)" 

    $environmentMessage = @()
    foreach ($environmentId in $sourceDestinationEnvironmentInfo.SourceEnvironmentList)
    {
        $environment = $environmentList | Where-Object {$_.Id -eq $environmentId }

        if ($null -ne $environment)
        {
            $environmentMessage += $environment.Name
        }
    }

    $errorMessage += " $($environmentMessage -join ",")"
    
    if ([string]::IsNullOrWhitespace($releaseNumber) -eq $false)
    {
    	$errorMessage = "$errorMessage matching $releaseNumber"
    }
    
    $errorMessage = "$errorMessage that can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)"
    
    if ($errorHandleForNoRelease -eq "Error")
    {
    	Write-OctopusCritical $errorMessage
        exit 1
    }
    
    Insert-EmptyOutputVariables -releaseToDeploy $null
    
    if ($errorHandleForNoRelease -eq "Skip")
    {
    	Write-OctopusInformation $errorMessage
        exit 0
    }
    
    Write-OctopusSuccess $errorMessage
    exit 0
}

function Get-TenantIsAssignedToPreviousEnvironments
{
    param (
        $tenantToDeploy,
        $sourceDestinationEnvironmentInfo,
        $projectId,
        $isPromotionMode
    )

    if ($null -eq $tenantToDeploy)
    {
        Write-OctopusVerbose "The tenant is null, skipping the check to see if it is assigned to the previous environment list."
        return $false
    }

    if ($isPromotionMode -eq $false)
    {
        Write-OctopusVerbose "The current mode is redeploy, the source and destination environment are the same, no need to check."
        return $true
    }

    Write-OctopusVerbose "Checking to see if $($tenantToDeploy.Name) is assigned to the previous environments."     
    Write-OctopusVerbose "Checking to see if $($tenantToDeploy.ProjectEnvironments.$projectId) is assigned to the source environments(s) $($sourceDestinationEnvironmentInfo.SourceEnvironmentList)"

    foreach ($environmentId in $tenantToDeploy.ProjectEnvironments.$projectId)
    {
        Write-OctopusVerbose "Checking to see if $environmentId appears in $($sourceDestinationEnvironmentInfo.SourceEnvironmentList)"
        if ($sourceDestinationEnvironmentInfo.SourceEnvironmentList -contains $environmentId)
        {
            Write-OctopusVerbose "Found the environment $environmentId assigned to $($tenantToDeploy.Name), attempting to find the latest release for this tenant"
            return $true
        }
    }

    Write-OctopusVerbose "The tenant is not assigned to any environment in the source environments $($sourceDestinationEnvironmentInfo.SourceEnvironmentList), pulling the latest release to the environment regardless of tenant."
    return $false
}

function Create-NewOctopusDeployment
{
	param (
    	$releaseToDeploy,
        $targetEnvironment,
        $createdDeployment,
        $project,
        $waitForFinish,
        $enableEnhancedLogging,
        $deploymentCancelInSeconds,
        $defaultUrl,
        $octopusApiKey,
        $spaceId,
        $parentDeploymentApprovers,
        $parentProjectName,
        $parentReleaseNumber, 
        $parentEnvironmentName, 
        $parentDeploymentTaskId,
        $autoapproveChildManualInterventions,
        $approvalTenant
    )
    
    Write-OctopusSuccess "Deploying $($releaseToDeploy.Version) to $($targetEnvironment.Name)"

    $createdDeploymentResponse = Invoke-OctopusApi -method "POST" -endPoint "deployments" -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -item $createdDeployment
    Write-OctopusInformation "The task id for the new deployment is $($createdDeploymentResponse.TaskId)"

    Write-OctopusSuccess "Deployment was successfully invoked, you can access the deployment [here]($defaultUrl/app#/$spaceId/projects/$($project.Slug)/deployments/releases/$($releaseToDeploy.Version)/deployments/$($createdDeploymentResponse.Id)?activeTab=taskSummary)"
    
    if ($null -ne $createdDeployment.QueueTime -and $waitForFinish -eq $true)
    {
    	Write-OctopusWarning "The option to wait for the deployment to finish was set to yes AND a future deployment date was set to a future value.  Ignoring the wait for finish option and exiting."
        return
    }
    
    if ($waitForFinish -eq $true)
    {
        Write-OctopusSuccess "Waiting until deployment has finished"
        $startTime = Get-Date
        $currentTime = Get-Date
        $dateDifference = $currentTime - $startTime
        $lastEnhancedLoggingWriteTime = [datetime]::MinValue

        $numberOfWaits = 0    

        While ($dateDifference.TotalSeconds -lt $deploymentCancelInSeconds)
        {
	        $numberOfWaits += 1
        
            Write-Host "Waiting 5 seconds to check status"
            Start-Sleep -Seconds 5
            $taskStatusResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint "tasks/$($createdDeploymentResponse.TaskId)" -method "GET" -ignoreCache $true   
            $taskStatusResponseState = $taskStatusResponse.State

            if ($taskStatusResponseState -eq "Success")
            {
                if ($enableEnhancedLogging -eq $true)
                {
                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime
                }
                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 deployment"
                exit 1            
            }
            elseif($taskStatusResponse.HasPendingInterruptions -eq $true)
            {
            	if ($autoapproveChildManualInterventions -eq $true)
                {
                	Submit-ChildProjectDeploymentForAutoApproval -createdDeployment $createdDeploymentResponse -parentDeploymentApprovers $parentDeploymentApprovers -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $parentEnvironmentName -parentDeploymentTaskId $parentDeploymentTaskId -approvalTenant $approvalTenant
                }
                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."                        
                    }
                }
            }
            
            if ($numberOfWaits -ge 10)
            {
                if ($enableEnhancedLogging -eq $true)
                {
                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime
                }
                else
                {
                    Write-OctopusSuccess "The task state is currently $taskStatusResponseState"
                    $numberOfWaits = 0
                }
            }
            else
            {
                if ($enableEnhancedLogging -eq $true)
                {
                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime
                }
                else
                {
                    Write-OctopusInformation "The task state is currently $taskStatusResponseState"
                }
            }  

            $startTime = $taskStatusResponse.StartTime
            if ($null -eq $startTime -or [string]::IsNullOrWhiteSpace($startTime) -eq $true)
            {        
                Write-Host "The task is still queued, let's wait a bit longer"
                $startTime = Get-Date
            }
            $startTime = [DateTime]$startTime

            $currentTime = Get-Date
            $dateDifference = $currentTime - $startTime        
        }

        Write-OctopusCritical "The cancel timeout has been reached, cancelling the deployment"
        Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -method "POST" -endPoint "tasks/$($createdDeploymentResponse.TaskId)/cancel"    
        Write-OctopusInformation "Exiting with an error code of 1 because we reached the timeout"
        exit 1
    }
}

function Get-ChildDeploymentSpecificMachines
{
    param (
        $deploymentPreview,
        $deploymentMachines,
        $specificMachineDeployment
    )

    if ($specificMachineDeployment -eq $false)
    {
        Write-OctopusVerbose "Not doing specific machine deployments, returning any empty list of specific machines to deploy to"
        return @()
    }

    $filteredList = @()
    $deploymentMachineList = $deploymentMachines -split ","

    Write-OctopusInformation "Doing a specific machine deployment, comparing the machines being targeted with the machines the child project can deploy to.  The number of machines being targeted is $($deploymentMachineList.Count)"

    foreach ($deploymentMachine in $deploymentMachineList)
    {
        $deploymentMachineLowerTrim = $deploymentMachine.Trim().ToLower()           

        foreach ($step in $deploymentPreview.StepsToExecute)
        {
            foreach ($machine in $step.Machines)
            {   
                $machineLowerTrim = $machine.Id.Trim().ToLower()
                
                Write-OctopusVerbose "Comparing $deploymentMachineLowerTrim with $machineLowerTrim"
                if ($deploymentMachineLowerTrim -ne $machineLowerTrim)
                {
                    Write-OctopusVerbose "The two machine ids do not match, moving on to the next machine"
                    continue
                }

                Write-OctopusVerbose "Checking to see if $machineLowerTrim is already in the filtered list."
                if ($filteredList -notcontains $machine.Id)
                {
                    Write-OctopusVerbose "The machine is not in the list, adding it to the list."
                    $filteredList += $machine.Id
                }
            }
        }
    }

    if ($filteredList.Count -gt 0)
    {
        Write-OctopusSuccess "The machines applicable to this project are $filteredList."
    }    

    return $filteredList
}

function Test-ChildProjectDeploymentCanProceed
{
	param (
    	$releaseToDeploy,
        $specificMachineDeployment,                        
        $environmentName,
        $childDeploymentSpecificMachines,
        $project,
        $ignoreSpecificMachineMismatch,
        $deploymentMachines,
        $releaseHasAlreadyBeenDeployed,
        $isPromotionMode       
    )
    
	if ($releaseHasAlreadyBeenDeployed -eq $true -and $isPromotionMode -eq $true)
    {	     	 
    	Write-OctopusSuccess "Release $($releaseToDeploy.Version) is the most recent version deployed to $environmentName.  The deployment mode is Promote.  If you wish to redeploy this release then set the deployment mode to Redeploy.  Skipping this project."
        
        if ($specificMachineDeployment -eq $true -and $childDeploymentSpecificMachines.Length -gt 0)
        {
            Write-OctopusSuccess "$($project.Name) can deploy to $childDeploymentSpecificMachines but redeployments are not allowed."
        }
        
        Insert-EmptyOutputVariables -releaseToDeploy $releaseToDeploy

        exit 0
    }
    
    if ($childDeploymentSpecificMachines.Length -le 0 -and $specificMachineDeployment -eq $true -and $ignoreSpecificMachineMismatch -eq $false)
    {
        Write-OctopusSuccess "$($project.Name) does not deploy to $($deploymentMachines -replace ",", " OR ").  The value for ""Ignore specific machine mismatch"" is set to ""No"".  Skipping this project."
        
        Insert-EmptyOutputVariables -releaseToDeploy $releaseToDeploy
        
        Exit 0
    }

    if ($childDeploymentSpecificMachines.Length -le 0 -and $specificMachineDeployment -eq $true -and $ignoreSpecificMachineMismatch -eq $true)
    {
        Write-OctopusSuccess "You are doing a deployment for specific machines but $($project.Name) does not deploy to $($deploymentMachines -replace ",", " OR ").  You have set the value for ""Ignore specific machine mismatch"" to ""Yes"".  The child project will be deployed to, but it will do this for all machines, not any specific machines."
    }
}

function Get-ParentDeploymentApprovers
{
    param (
        $parentDeploymentTaskId,
        $spaceId,
        $defaultUrl,
        $octopusApiKey
    )
    
    $approverList = @()
    if ($null -eq $parentDeploymentTaskId)
    {
    	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"
    $parentDeploymentEvents = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "events?regardingAny=$parentDeploymentTaskId&spaces=$spaceId&includeSystem=true" -apiKey $octopusApiKey -method "GET"
    
    foreach ($parentDeploymentEvent in $parentDeploymentEvents.Items)
    {
        Write-OctopusVerbose "Checking $($parentDeploymentEvent.Message) for manual intervention"
        if ($parentDeploymentEvent.Message -like "Submitted interruption*")
        {
            Write-OctopusVerbose "The event $($parentDeploymentEvent.Id) is a manual intervention approval event which was approved by $($parentDeploymentEvent.Username)."

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

            if ($null -eq $approverExists)
            {
                $approverInformation = @{
                    Id = $parentDeploymentEvent.UserId;
                    Username = $parentDeploymentEvent.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 Submit-ChildProjectDeploymentForAutoApproval
{
    param (
        $createdDeployment,
        $parentDeploymentApprovers,
        $defaultUrl,
        $octopusApiKey,
        $spaceId,
        $parentProjectName,
        $parentReleaseNumber,
        $parentEnvironmentName,
        $parentDeploymentTaskId,
        $approvalTenant
    )

    Write-OctopusSuccess "The task has a pending manual intervention.  Checking parent approvals."    
    $manualInterventionInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "interruptions?regarding=$($createdDeployment.TaskId)" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId -ignoreCache $true
    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 $parentDeploymentApprovers)
        {
            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-OctopusVerbose "Found matching approvers, attempting to auto approve."
            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 -ignoreCache $true
                Write-OctopusVerbose "Response from taking responsibility $($takeResponsiblilityResponse.Id)"
            }
            
            if ($null -ne $approvalTenant)
            {
                $approvalMessage = "Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName for the tenant $($approvalTenant.Name) with the task id $parentDeploymentTaskId was approved by $($automaticApprover.UserName)."
            }
            else
            {
                $approvalMessage = "Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName with the task id $parentDeploymentTaskId was approved by $($automaticApprover.UserName)."
            }

            $submitApprovalBody = @{
                Instructions = $null;
                Notes = "Auto-approving this deployment. $approvalMessage That user is a member of one of the teams this manual intervention requires.  You can view that deployment $defaultUrl/app#/$spaceId/tasks/$parentDeploymentTaskId";
                Result = "Proceed"
            }
            $submitResult = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "interruptions/$($manualIntervention.Id)/submit" -method "POST" -apiKey $octopusApiKey -item $submitApprovalBody -spaceId $spaceId -ignoreCache $true
            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."    
        }
    }
}

function Get-ReleaseNotes
{
	param (
    	$releaseToDeploy,
        $deploymentPreview,
        $channel,
        $spaceId,
        $defaultUrl,
        $octopusApiKey
    )
            
    $releaseNotes = @("")
    $releaseNotes += "**Release Information**"
    $releaseNotes += ""

    $packageVersionAdded = @()
    $workItemsAdded = @()
    $commitsAdded = @()

    if ($null -ne $releaseToDeploy.BuildInformation -and $releaseToDeploy.BuildInformation.Count -gt 0)
    {
        $releaseNotes += "- Package Versions"
        foreach ($change in $deploymentPreview.Changes)
        {        
            foreach ($package in $change.BuildInformation)
            {
                $packageInformation = "$($package.PackageId).$($package.Version)"
                if ($packageVersionAdded -notcontains $packageInformation)
                {
                    $releaseNotes += "  - $packageInformation"
                    $packageVersionAdded += $packageInformation
                }
            }
        }

		$releaseNotes += ""
        $releaseNotes += "- Work Items"
        foreach ($change in $deploymentPreview.Changes)
        {        
            foreach ($workItem in $change.WorkItems)
            {            
                if ($workItemsAdded -notcontains $workItem.Id)
                {
                    $workItemInformation = "[$($workItem.Id)]($($workItem.LinkUrl)) - $($workItem.Description)"
                    $releaseNotes += "  - $workItemInformation"
                    $workItemsAdded += $workItem.Id
                }
            }
        }

		$releaseNotes += ""
        $releaseNotes += "- Commits"
        foreach ($change in $deploymentPreview.Changes)
        {        
            foreach ($commit in $change.Commits)
            {            
                if ($commitsAdded -notcontains $commit.Id)
                {
                    $commitInformation = "[$($commit.Id)]($($commit.LinkUrl)) - $($commit.Comment)"
                    $releaseNotes += "  - $commitInformation"
                    $commitsAdded += $commit.Id
                }
            }
        }            
    }
    else
    {
        $releaseNotes += $releaseToDeploy.ReleaseNotes
        $releaseNotes += ""
        $releaseNotes += "Package Versions"  
        
        $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "deploymentprocesses/$($releaseToDeploy.ProjectDeploymentProcessSnapshotId)/template?channel=$($channel.Id)&releaseId=$($releaseToDeploy.Id)" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId
        
        foreach ($package in $releaseToDeploy.SelectedPackages)
        {
        	Write-OctopusVerbose "Attempting to find $($package.StepName) and $($package.ActionName)"
            
            $deploymentProcessPackageInformation = $releaseDeploymentTemplate.Packages | Where-Object {$_.StepName -eq $package.StepName -and $_.actionName -eq $package.ActionName}
            if ($null -ne $deploymentProcessPackageInformation)
            {
                $packageInformation = "$($deploymentProcessPackageInformation.PackageId).$($package.Version)"
                if ($packageVersionAdded -notcontains $packageInformation)
                {
                    $releaseNotes += "  - $packageInformation"
                    $packageVersionAdded += $packageInformation
                }
            }
        }
    }

    return $releaseNotes -join "`n"
}

function Get-QueueDate
{
	param ( 
    	$futureDeploymentDate
    )
    
    if ([string]::IsNullOrWhiteSpace($futureDeploymentDate) -or $futureDeploymentDate -eq "N/A")
    {
    	return $null
    }
    
    [datetime]$outputDate = New-Object DateTime
    $currentDate = Get-Date

    if ([datetime]::TryParse($futureDeploymentDate, [ref]$outputDate) -eq $false)
    {
        Write-OctopusCritical "The suppplied date $futureDeploymentDate 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
    }
    
    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 Insert-EmptyOutputVariables
{
	param (
    	$releaseToDeploy
    )
    
	if ($null -ne $releaseToDeploy)
    {
		Set-OctopusVariable -Name "ReleaseToPromote" -Value $($releaseToDeploy.Version)
        Set-OctopusVariable -Name "ReleaseNotes" -value "Release already deployed to destination environment."
    }
    else
    {
    	Set-OctopusVariable -Name "ReleaseToPromote" -Value "N/A"
        Set-OctopusVariable -Name "ReleaseNotes" -value "No release found"
    }        
    
    Write-OctopusInformation "Setting the output variable ChildReleaseToDeploy to $false"
    Set-OctopusVariable -Name "ChildReleaseToDeploy" -Value $false
}

function Get-ApprovalDeploymentTaskId
{
	param (
    	$autoapproveChildManualInterventions,
        $parentDeploymentTaskId,
        $parentReleaseId,
        $parentEnvironmentName,
        $approvalEnvironmentName,
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $parentChannelId,    
        $parentEnvironmentId,
        $approvalTenant,
        $parentProject
    )
    
    if ($autoapproveChildManualInterventions -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 $parentDeploymentTaskId
    }
    
    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 $parentDeploymentTaskId"
        return $parentDeploymentTaskId
    }
    
    $approvalEnvironment = Get-OctopusEnvironmentByName -environmentName $approvalEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
    $releaseDeploymentList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "releases/$parentReleaseId/deployments?skip=0&take=1000" -method "GET" -apiKey $octopusApiKey -spaceId $spaceId -propertyName "Items"
    
    $lastDeploymentTime = $(Get-Date).AddYears(-50)
    $approvalTaskId = $null
    foreach ($deployment in $releaseDeploymentList)
    {
        if ($deployment.EnvironmentId -ne $approvalEnvironment.Id)
        {
            Write-OctopusInformation "The deployment $($deployment.Id) deployed to $($deployment.EnvironmentId) which doesn't match $($approvalEnvironment.Id).  Moving onto the next deployment."
            continue
        }

        if ($null -ne $approvalTenant -and $null -ne $deployment.TenantId -and $deployment.TenantId -ne $approvalTenant.Id)
        {
            Write-OctopusInformation "The deployment $($deployment.Id) was deployed to the correct environment, $($approvalEnvironment.Id), but the deployment tenant $($deployment.TenantId) doesn't match the approval tenant $($approvalTenant.Id).  Moving onto the next deployment."
            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 $false)
        {
            Write-OctopusInformation "The deployment $($deployment.Id) is being deployed to the approval environment, but it hasn't completed, moving onto the next deployment."
            continue
        }

        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
        $lifecyclePhases = Get-OctopusLifeCyclePhases -channel $channelInformation -defaultUrl $defaultUrl -spaceId $spaceId -OctopusApiKey $octopusApiKey -project $parentProject        
        
        $foundDestinationFirst = $false
        $foundApprovalFirst = $false
        
        foreach ($phase in $lifecyclePhases)
        {
        	if (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $parentEnvironmentId)
            {
            	if ($foundApprovalFirst -eq $false)
                {
                	$foundDestinationFirst = $true
                }
            }
            
            if (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $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 Invoke-RefreshVariableSnapshot
{
	param (
    	$refreshVariableSnapShot,
        $whatIf,
        $releaseToDeploy,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )
    
    Write-OctopusVerbose "Checking to see if variable snapshot will be updated."
    
    if ($refreshVariableSnapShot -eq "No")
    {
    	Write-OctopusVerbose "Refreshing variables is set to no, skipping"
    	return
    }
    
    $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($releaseToDeploy.Id)/deployments/template" -spaceId $spaceId -method GET -apiKey $octopusApiKey
    
    if ($releaseDeploymentTemplate.IsVariableSetModified -eq $false -and $releaseDeploymentTemplate.IsLibraryVariableSetModified -eq $false)
    {
    	Write-OctopusVerbose "Variables have not been updated since release creation, skipping"
        return
    }
    
    if ($whatIf -eq $true)
    {
    	Write-OctopusSuccess "Variables have been updated since release creation, whatif set to true, no update will occur."
        return
    }
    
    $snapshotVariables = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($releaseToDeploy.Id)/snapshot-variables" -spaceId $spaceId -method "POST" -apiKey $octopusApiKey
    Write-OctopusSuccess "Variables have been modified since release creation.  Variable snapshot was updated on $($snapshotVariables.LastModifiedOn)"
}

function Get-MatchingOctopusDeploymentTasks
{
    param (
        $spaceId,
        $project,
        $tenantToDeploy,
        $tenantIsAssignedToPreviousEnvironments,
        $sourceDestinationEnvironmentInfo,
        $defaultUrl,
        $octopusApiKey
    )

    $taskEndPoint = "tasks?skip=0&take=100&spaces=$spaceId&includeSystem=false&project=$($project.Id)&name=Deploy&states=Success"

    if ($null -ne $tenantToDeploy -and $tenantIsAssignedToPreviousEnvironments -eq $true)
    {
        $taskEndPoint += "&tenant=$($tenantToDeploy.Id)"
    }

    $taskList = @()

    foreach ($sourceEnvironmentId in $sourceDestinationEnvironmentInfo.SourceEnvironmentList)
    {
        $octopusTaskList = Get-ListFromOctopusApi -octopusUrl $DefaultUrl -endPoint "$($taskEndPoint)&environment=$sourceEnvironmentId" -spaceId $null -apiKey $octopusApiKey -method "GET" -propertyName "Items"
        $taskList += $octopusTaskList
    }

    $orderedTaskList = @($taskList | Sort-Object -Property StartTime -Descending)
    Write-OctopusVerbose "We have $($orderedTaskList.Count) number of tasks to loop through"

    return $orderedTaskList
}

function Get-TaskDetails
{
    param (
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $taskId,
        $lastEnhancedLoggingWriteTime
    )

    $taskDetails = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint "tasks/$taskId/details" -method 'GET' -ignoreCache $true
    $activityLogs = $taskDetails.ActivityLogs
    $writeStepName = $writeTargetName = $true
    $returnTime = [datetime]::MinValue

    foreach ($step in $activityLogs.Children)
    {
        foreach ($target in $step.Children)
        {
            foreach ($logElement in $target.LogElements)
            {
                $occurredAt = [datetime]($logElement.OccurredAt)
                if ($occurredAt -gt $lastEnhancedLoggingWriteTime)
                {
                    if ($writeStepName -eq $true)
                    {
                        $trailingCount = 66 - $step.Name.Length
                        if ($trailingCount -lt 0) { $trailingCount = 0 }
                        Write-OctopusInformation "╔═ $($step.Name) $("═" * $trailingCount)"
                        $writeStepName = $false
                    }
                    if ($writeTargetName -eq $true)
                    {
                        $trailingCount = 64 - $target.Name.Length
                        if ($trailingCount -lt 0) { $trailingCount = 0 }
                        Write-OctopusInformation "║ ┌─ $($target.Name) $('─' * $trailingCount)"
                        $writeTargetName = $false
                    }
                    Write-OctopusInformation "║ │ $($logElement.MessageText)"
                    if ($occurredAt -gt $returnTime) { $returnTime = $occurredAt}
                }
            }
            if ($writeTargetName -eq $false)
            {
                Write-OctopusInformation "║ └$('─' * 67)"
                $writeTargetName = $true
            }
        }
        if ($writeStepName -eq $false)
        {
            Write-OctopusInformation "╚$('═' * 69)"
            $writeStepName = $true
        }
    }

    return $returnTime
}

function Get-ReleaseToDeployFromTaskList
{
    param (
        $taskList,
        $channel,
        $releaseNumber,
        $tenantToDeploy,
        $sourceDestinationEnvironmentInfo,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $isPromotionMode
    )
    
    foreach ($task in $taskList)
    {
        Write-OctopusVerbose "Pulling the deployment information for $($task.Id)"
        $deploymentInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "deployments/$($task.Arguments.DeploymentId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"

        if ($deploymentInformation.ChannelId -ne $channel.Id)
        {
            Write-OctopusInformation "The deployment was not for the channel we want to deploy to, moving onto next task."
            continue
        }

        Write-OctopusVerbose "Pulling the release information for $($deploymentInformation.Id)"
        $releaseInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "releases/$($deploymentInformation.ReleaseId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
        
        if ($isPromotionMode -eq $false)
        {
            Write-OctopusInformation "Current mode is set to redeploy, the release is for the correct channel and was successful, using it."            
            return $releaseInformation
        }

        if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false -and $releaseInformation.Version -notlike $releaseNumber)
        {
            Write-OctopusInformation "The release version $($releaseInformation.Version) does not match $releaseNumber.  Moving onto the next task."
            continue
        }

        $releaseCanBeDeployed = Get-ReleaseCanBeDeployedToTargetEnvironment -defaultUrl $defaultUrl -release $releaseInformation -spaceId $spaceId -octopusApiKey $octopusApiKey -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -tenantToDeploy $tenantToDeploy -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode

        if ($releaseCanBeDeployed -eq $true)
        {
            Write-OctopusInformation "The release $($releaseInformation.Version) can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)."
            return $releaseInformation                                    
        }

        Write-OctopusInformation "The release $($releaseInformation.Version) cannot be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).  Moving onto next task"
    } 
    
    return $null
}

function Get-ReleaseToDeployFromChannel
{
    param (
        $channel,
        $releaseNumber,
        $tenantToDeploy,
        $sourceDestinationEnvironmentInfo,        
        $defaultUrl,
        $spaceId,
        $octopusApiKey,
        $isPromotionMode,
        $isAlwaysLatestMode
    )

    if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false)
    {        
        $urlReleaseNumber = $releaseNumber.Replace("*", "")
        Write-OctopusInformation "The release number was sent in, sending $urlReleaseNumber to the channel endpoint to have the server filter on that number first."
        $releaseChannelList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "channels/$($channel.Id)/releases?skip=0&take=100&searchByVersion=$urlReleaseNumber" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"    
    }
    else
    {
        Write-OctopusInformation "The release number was not sent in, attempting to find the latest release from the channel to deploy."
        $releaseChannelList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint "channels/$($channel.Id)/releases?skip=0&take=100" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"    
    }
    
    Write-OctopusInformation "There are $($releaseChannelList.Items.Count) potential releases to go through."

    foreach ($releaseInformation in $releaseChannelList.Items)
    {
        if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false -and $releaseInformation.Version -notlike $releaseNumber)
        {
            Write-OctopusInformation "The release version $($releaseInformation.Version) does not match $releaseNumber.  Moving onto the next release in the channel."
            continue
        }

        $releaseCanBeDeployed = Get-ReleaseCanBeDeployedToTargetEnvironment -defaultUrl $defaultUrl -release $releaseInformation -spaceId $spaceId -octopusApiKey $octopusApiKey -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -tenantToDeploy $tenantToDeploy -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode

        if ($releaseCanBeDeployed -eq $true)
        {
            Write-OctopusInformation "The release $($releaseInformation.Version) can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)."
            return $releaseInformation                                    
        }

        Write-OctopusInformation "The release $($releaseInformation.Version) cannot be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).  Moving onto next release in the channel."
    }

    return $null
}

function Get-ReleaseHasAlreadyBeenPromotedToTargetEnvironment
{
    param (
        $releaseToDeploy,
        $tenantToDeploy,
        $sourceDestinationEnvironmentInfo,
        $isPromotionMode,
        $isAlwaysLatestMode,
        $defaultUrl,
        $spaceId,
        $octopusApiKey
    )

    if ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)
    {
        Write-OctopusInformation "Currently in redeploy mode, of course the release has already been deployed to the target environment.  Exiting the Release Has Already Been Promoted To Target Environment check."
        return $true
    }

    Write-OctopusVerbose "Pulling the last release for the target environment to see if the release to deploy is the latest one in that environment."
    $taskEndPoint = "tasks?skip=0&take=1&spaces=$spaceId&includeSystem=false&project=$($releaseToDeploy.ProjectId)&name=Deploy&states=Success&environment=$($sourceDestinationEnvironmentInfo.TargetEnvironment.Id)"

    if ($null -ne $tenantToDeploy)
    {
        $taskEndPoint += "&tenant=$($tenantToDeploy.Id)"
    }

    $octopusTaskList = Get-ListFromOctopusApi -octopusUrl $DefaultUrl -endPoint "$taskEndPoint" -spaceId $null -apiKey $octopusApiKey -method "GET" -propertyName "Items"

    if ($octopusTaskList.Count -eq 0)
    {
        Write-OctopusInformation "There have been no releases to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) for this project."
        return $false
    }

    $task = $octopusTaskList[0]
    $deploymentInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "deployments/$($task.Arguments.DeploymentId)" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"

    if ($releaseToDeploy.Id -eq $deploymentInformation.ReleaseId)
    {
        Write-OctopusInformation "The release to deploy $($release.ReleaseNumber) is the last successful release to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)"
        return $true
    }

    Write-OctopusInformation "The release to deploy $($release.ReleaseNumber) is different than the last successful release to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)"
    return $false
}

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

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

    foreach ($machineName in $targetMachineList)
    {
    	$trimmedMachineName = $machineName.Trim()
        Write-OctopusVerbose "Translating $trimmedMachineName into an Octopus Id"
    	if ($trimmedMachineName -like "Machines-*")
        {
        	Write-OctopusVerbose "$trimmedMachineName is already an Octopus Id, adding it to the list"
        	$translatedList += $machineName
            continue
        }
        
        $machineObject = Get-OctopusItemByName -itemName $trimmedMachineName -itemType "Deployment Target" -endpoint "machines" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey

        $translatedList += $machineObject.Id
    }

    return $translatedList -join ","
}

function Write-ReleaseInformation
{
    param (
        $releaseToDeploy,
        $environmentList
    )

    $releaseDeployments = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint "releases/$($releaseToDeploy.Id)/deployments" -spaceId $spaceId -apiKey $octopusApiKey -method "GET"
    $releaseEnvironmentList = @()

    foreach ($deployment in $releaseDeployments.Items)
    {        
        $releaseEnvironment = $environmentList | Where-Object {$_.Id -eq $deployment.EnvironmentId }
        
        if ($null -ne $releaseEnvironment -and $releaseEnvironmentList -notcontains $releaseEnvironment.Name)
        {
            Write-OctopusVerbose "Adding $($releaseEnvironment.Name) to the list of environments this release has been deployed to"
            $releaseEnvironmentList += $releaseEnvironment.Name
        }                
    }
    
    if ($releaseEnvironmentList.Count -gt 0)
    {
        Write-OctopusSuccess "The release to deploy is $($releaseToDeploy.Version) which has been deployed to $($releaseEnvironmentList -join ",")"
    }
    else
    {
        Write-OctopusSuccess "The release to deploy is $($releaseToDeploy.Version) which currently has no deployments."    
    }
}

function Get-GuidedFailureMode
{
	param (
    	$projectToDeploy,
        $environmentToDeployTo
    )
    
    Write-OctopusInformation "Checking $($projectToDeploy.DefaultGuidedFailureMode) and $($environmentToDeployTo.UseGuidedFailure) to determine guided failure mode."
    
    if ($projectToDeploy.DefaultGuidedFailureMode -eq "EnvironmentDefault" -and $environmentToDeployTo.UseGuidedFailure -eq $true)
    {
    	Write-OctopusInformation "Guided failure for the project is set to environment default, and destination environment says to use guided failure.  Setting guided failure to true."
        return $true
    }
    
    if ($projectToDeploy.DefaultGuidedFailureMode -eq "On")
    {
    	Write-OctopusInformation "Guided failure for the project is set to always use guided falure.  Setting guided failure to true."
        return $true
    }
    
    Write-OctopusInformation "Guided failure is not turned on for the project nor the environment.  Setting to false."
    return $false
}

Write-OctopusInformation "Octopus SpaceId: $destinationSpaceId"
Write-OctopusInformation "Octopus Deployment Task Id: $parentDeploymentTaskId"
Write-OctopusInformation "Octopus Project Name: $parentProjectName"
Write-OctopusInformation "Octopus Release Number: $parentReleaseNumber"
Write-OctopusInformation "Octopus Release Id: $parentReleaseId"
Write-OctopusInformation "Octopus Environment Name: $parentEnvironmentName"
Write-OctopusInformation "Octopus Release Channel Id: $parentChannelId"
Write-OctopusInformation "Octopus Specific deployment machines: $specificMachines"
Write-OctopusInformation "Octopus Exclude deployment machines: $excludeMachines"
Write-OctopusInformation "Octopus deployment machines: $deploymentMachines"

Write-OctopusInformation "Child Project Name: $projectName"
Write-OctopusInformation "Child Project Space Name: $destinationSpaceName"
Write-OctopusInformation "Child Project Channel Name: $channelName"
Write-OctopusInformation "Child Project Release Number: $releaseNumber"
Write-OctopusInformation "Child Project Error Handle No Release Found: $errorHandleForNoRelease"
Write-OctopusInformation "Destination Environment Name: $environmentName"
Write-OctopusInformation "Source Environment Name: $sourceEnvironmentName"
Write-OctopusInformation "Ignore specific machine mismatch: $ignoreSpecificMachineMismatchValue"
Write-OctopusInformation "Save release notes as artifact: $saveReleaseNotesAsArtifactValue"
Write-OctopusInformation "What If: $whatIfValue"
Write-OctopusInformation "Wait for finish: $waitForFinishValue"
Write-OctopusInformation "Cancel deployment in seconds: $deploymentCancelInSeconds"
Write-OctopusInformation "Scheduling: $futureDeploymentDate"
Write-OctopusInformation "Auto-Approve Child Project Manual Interventions: $autoapproveChildManualInterventionsValue"
Write-OctopusInformation "Approval Environment: $approvalEnvironmentName"
Write-OctopusInformation "Approval Tenant: $approvalTenantName"
Write-OctopusInformation "Refresh Variable Snapshot: $refreshVariableSnapShot"
Write-OctopusInformation "Deployment Mode: $deploymentMode"
Write-OctopusInformation "Target Machine Names: $targetMachines"
Write-OctopusInformation "Deployment Tenant Name: $deploymentTenantName"

$whatIf = $whatIfValue -eq "Yes"
$waitForFinish = $waitForFinishValue -eq "Yes"
$enableEnhancedLogging = $enableEnhancedLoggingValue -eq "Yes"
$ignoreSpecificMachineMismatch = $ignoreSpecificMachineMismatchValue -eq "Yes"
$autoapproveChildManualInterventions = $autoapproveChildManualInterventionsValue -eq "Yes"
$saveReleaseNotesAsArtifact = $saveReleaseNotesAsArtifactValue -eq "Yes"

$verificationPassed = @()
$verificationPassed += Test-RequiredValues -variableToCheck $octopusApiKey -variableName "Octopus API Key"
$verificationPassed += Test-RequiredValues -variableToCheck $destinationSpaceName -variableName "Child Project Space"
$verificationPassed += Test-RequiredValues -variableToCheck $projectName -variableName "Child Project Name"
$verificationPassed += Test-RequiredValues -variableToCheck $environmentName -variableName "Destination Environment Name"

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

$isPromotionMode = $deploymentMode -eq "Promote"
$isAlwaysLatestMode = $deploymentMode -eq 'AlwaysLatest'
$spaceId = Get-OctopusSpaceIdByName -spaceName $destinationSpaceName -spaceId $destinationSpaceId -defaultUrl $defaultUrl -OctopusApiKey $octopusApiKey    

Write-OctopusSuccess "The current mode of the step template is $deploymentMode"

if ($isAlwaysLatestMode -eq $true)
{
    Write-OctopusSuccess "Currently in AlwaysLatest mode, release number filter will be ignored, source environment will be set to the target environment, all redeployment checks will be ignored."
}

if ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)
{
    Write-OctopusSuccess "Currently in redeploy mode, release number filter will be ignored, source environment will be set to the target environment, all redeployment checks will be ignored."
}

if ($isPromotionMode -eq $true -and [string]::IsNullOrWhiteSpace($sourceEnvironmentName) -eq $false -and $sourceEnvironmentName.ToLower().Trim() -eq $environmentName.ToLower().Trim())
{
    Write-OctopusSuccess "The current mode is promotion.  Both the source environment and destination environment are the same.  You cannot promote from the same environment as the source environment.  Exiting.  Change the deployment mode value to redeploy if you want to redeploy."
    Exit 0
}

$specificMachineDeployment = $false
if ([string]::IsNullOrWhiteSpace($specificMachines) -eq $false)
{
	Write-OctopusSuccess "This deployment is targeting the specific machines $specificMachines."
	$specificMachineDeployment = $true
}

if ([string]::IsNullOrWhiteSpace($excludeMachines) -eq $false)
{
	Write-OctopusSuccess "This deployment is excluding the specific machines $excludeMachines.  The machines being deployed to are: $deploymentMachines."
    $specificMachineDeployment = $true
}

if ([string]::IsNullOrWhiteSpace($targetMachines) -eq $false -and $targetMachines -ne "N/A")
{
    Write-OctopusSuccess "You have specified specific machines to target in this deployment.  Ignoring the machines that triggered this deployment."
    $specificMachineDeployment = $true
    $deploymentMachines = Get-MachineIdsFromMachineNames -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -targetMachines $targetMachines
}

$project = Get-OctopusProjectByName -projectName $projectName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
$parentProject = Get-OctopusProjectByName -projectName $parentProjectName -defaultUrl $defaultUrl -spaceId $parentSpaceId -octopusApiKey $octopusApiKey
$tenantToDeploy = Get-OctopusTenantByName -tenantName $deploymentTenantName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
$targetEnvironment = Get-OctopusEnvironmentByName -environmentName $environmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
$tenantToDeploy = Test-ProjectTenantSettings -tenantToDeploy $tenantToDeploy -project $project -targetEnvironment $targetEnvironment

$sourceEnvironment = Get-OctopusEnvironmentByName -environmentName $sourceEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey
$channel = Get-OctopusChannel -channelName $channelName -defaultUrl $defaultUrl -project $project -spaceId $spaceId -octopusApiKey $octopusApiKey
$phaseList = Get-OctopusLifecyclePhases -channel $channel -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -project $project
$sourceDestinationEnvironmentInfo = Get-SourceDestinationEnvironmentInformation -phaseList $phaseList -targetEnvironment $targetEnvironment -sourceEnvironment $sourceEnvironment -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode

if ($deploymentMode -eq 'AlwaysLatest')
{
    Write-OctopusInformation "Finding the latest release that can be deployed."
    $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode
}
elseif ($sourceDestinationEnvironmentInfo.FirstLifecyclePhase -eq $false)
{
    $tenantIsAssignedToPreviousEnvironments = Get-TenantIsAssignedToPreviousEnvironments -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -projectId $project.Id -isPromotionMode $isPromotionMode
    $taskList = Get-MatchingOctopusDeploymentTasks -spaceId $spaceId -project $project -tenantToDeploy $tenantToDeploy -tenantIsAssignedToPreviousEnvironments $tenantIsAssignedToPreviousEnvironments -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey
    $releaseToDeploy = Get-ReleaseToDeployFromTaskList -taskList $taskList -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $DefaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode    

    if ($null -eq $releaseToDeploy -and $sourceDestinationEnvironmentInfo.HasRequiredPhase -eq $false)
    {
        Write-OctopusInformation "No release was found that has been deployed.  However, all the phases prior to the destination phase is optional.  Checking to see if any releases exist at the channel level that haven't been deployed."
        $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode
    }
}
else
{
    $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode
}

$environmentList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint "environments?skip=0&take=1000" -spaceId $spaceId -propertyName "Items" -apiKey $octopusApiKey

Test-ReleaseToDeploy -releaseToDeploy $releaseToDeploy -errorHandleForNoRelease $errorHandleForNoRelease -releaseNumber $releaseNumber -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -environmentList $environmentList

if ($null -ne $releaseToDeploy)
{
    Write-ReleaseInformation -releaseToDeploy $releaseToDeploy -environmentList $environmentList
}

$releaseHasAlreadyBeenDeployed = Get-ReleaseHasAlreadyBeenPromotedToTargetEnvironment -releaseToDeploy $releaseToDeploy -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode

$deploymentPreview = Get-DeploymentPreview -releaseToDeploy $releaseToDeploy -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -targetEnvironment $targetEnvironment -deploymentTenant $tenantToDeploy
$childDeploymentSpecificMachines = Get-ChildDeploymentSpecificMachines -deploymentPreview $deploymentPreview -deploymentMachines $deploymentMachines -specificMachineDeployment $specificMachineDeployment
$deploymentFormValues = Get-ValuesForPromptedVariables -formValues $formValues -deploymentPreview $deploymentPreview

$queueDate = Get-QueueDate -futureDeploymentDate $futureDeploymentDate
$queueExpiryDate = Get-QueueExpiryDate -queueDate $queueDate
$useGuidedFailure = Get-GuidedFailureMode -projectToDeploy $project -environmentToDeployTo $targetEnvironment

$createdDeployment = @{
    EnvironmentId = $targetEnvironment.Id;
    ExcludeMachineIds = @();
    ForcePackageDownload = $false;
    ForcePackageRedeployment = $false;
    FormValues = $deploymentFormValues;
    QueueTime = $queueDate;
    QueueTimeExpiry = $queueExpiryDate;
    ReleaseId = $releaseToDeploy.Id;
    SkipActions = @();
    SpecificMachineIds = @($childDeploymentSpecificMachines);
    TenantId = $null;
    UseGuidedFailure = $useGuidedFailure
}

if ($null -ne $tenantToDeploy -and $project.TenantedDeploymentMode -ne "Untenanted")
{
    $createdDeployment.TenantId = $tenantToDeploy.Id
}

if ($whatIf -eq $true)
{    	
    Write-OctopusVerbose "Would have done a POST to /api/$spaceId/deployments with the body:"
    Write-OctopusVerbose $($createdDeployment | ConvertTo-JSON)        
    
    Write-OctopusSuccess "What If set to true."
    Write-OctopusSuccess "Setting the output variable ReleaseToPromote to $($releaseToDeploy.Version)."            
	Set-OctopusVariable -Name "ReleaseToPromote" -Value ($releaseToDeploy.Version)       
}

Write-OctopusVerbose "Getting the release notes"
$releaseNotes = Get-ReleaseNotes -releaseToDeploy $releaseToDeploy -deploymentPreview $deploymentPreview -channel $channel -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey
Write-OctopusSuccess "Setting the output variable ReleaseNotes which contains the release notes from the child project"
Set-OctopusVariable -Name "ReleaseNotes" -value $releaseNotes

Test-ChildProjectDeploymentCanProceed -releaseToDeploy $releaseToDeploy -specificMachineDeployment $specificMachineDeployment -environmentName $environmentName -childDeploymentSpecificMachines $childDeploymentSpecificMachines -project $project -ignoreSpecificMachineMismatch $ignoreSpecificMachineMismatch -deploymentMachines $deploymentMachines -releaseHasAlreadyBeenDeployed $releaseHasAlreadyBeenDeployed -isPromotionMode $isPromotionMode

if ($saveReleaseNotesAsArtifact -eq $true)
{
	$releaseNotes | Out-File "ReleaseNotes.txt"
    $currentDate = Get-Date
	$currentDateFormatted = $currentDate.ToString("yyyy_MM_dd_HH_mm")
    $artifactName = "$($project.Name) $($releaseToDeploy.Version) $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).ReleaseNotes_$($currentDateFormatted).txt"
    Write-OctopusInformation "Creating the artifact $artifactName"
    
	New-OctopusArtifact -Path "ReleaseNotes.txt" -Name $artifactName
}

Invoke-RefreshVariableSnapshot -refreshVariableSnapShot $refreshVariableSnapShot -whatIf $whatIf -releaseToDeploy $releaseToDeploy -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey

if ($whatif -eq $true)
{
    Write-OctopusSuccess "Exiting because What If set to true."
    Write-OctopusInformation "Setting the output variable ChildReleaseToDeploy to $true"
    Set-OctopusVariable -Name "ChildReleaseToDeploy" -Value $true
    Exit 0
}

$approvalTenant = Get-OctopusApprovalTenant -tenantToDeploy $tenantToDeploy -approvalTenantName $approvalTenantName -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey
$approvalDeploymentTaskId = Get-ApprovalDeploymentTaskId -autoapproveChildManualInterventions $autoapproveChildManualInterventions  -parentDeploymentTaskId $parentDeploymentTaskId -parentReleaseId $parentReleaseId -parentEnvironmentName $parentEnvironmentName -approvalEnvironmentName $approvalEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -approvalTenant $approvalTenant -parentProject $parentProject
$parentDeploymentApprovers = Get-ParentDeploymentApprovers -parentDeploymentTaskId $approvalDeploymentTaskId -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey

Create-NewOctopusDeployment -releaseToDeploy $releaseToDeploy -targetEnvironment $targetEnvironment -createdDeployment $createdDeployment -project $project -waitForFinish $waitForFinish -enableEnhancedLogging $enableEnhancedLogging -deploymentCancelInSeconds $deploymentCancelInSeconds -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentDeploymentApprovers $parentDeploymentApprovers -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $approvalEnvironmentName -parentDeploymentTaskId $approvalDeploymentTaskId -autoapproveChildManualInterventions $autoapproveChildManualInterventions -approvalTenant $approvalTenant

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": "0dac2fe6-91d5-4c05-bdfb-1b97adf1e12e",
  "Name": "Deploy Child Octopus Deploy Project",
  "Description": "This step will find the latest release in a source environment matching your criteria and deploy it.  \n\nUse cases:\n- As a user, I want to create a single parent release `2020.2.1`.  When I promote the parent release I want the latest child releases matching `2020.2.1.*` to be promoted to the next environment.\n- As a user, I want the latest release in the dev environment to be promoted to the test environment.  Not the most recently created release, the most recent release deployed that environment.\n- As a user, when we are finished with our QA process, we want to automatically push the latest releases from QA to Staging without having to manually promote each release.\n- As a user, I'd like to set up a nightly build to promote the latest releases from Dev to QA\n- As a user, I'd like to be able to deploy a suite of applications to a tenant.  If the tenant isn't assigned to the project then skip over.\n- As a user, I'd like to see what releases would go to production and approve those releases without having to manually verify and approve each one.\n- As a user, I'd like to be able to target specific machines in my parent project and only have child projects deploy associated with those machines.\n- As a user, I'd like to be able to exclude specific machines in my parent project and only have child projects deploy to the remaining machines.\n- As a user, I'd like to have a single deployment target trigger on my parent project and when I scale up my servers deploy the appropriate child projects.\n- As a user, I'd like to be able to approve the deployments and then schedule them to be deployed at 7 PM\n- As a user, I'd like to be able to have one space for orchestration projects and another space for developers to work in.\n\nThis step template also allows you to skip deployments to the destination environment if it has already been deployed.",
  "Version": 29,
  "ExportedAt": "2023-11-17T03:06:39.613Z",
  "ActionType": "Octopus.Script",
  "Author": "REOScotte",
  "Packages": [],
  "Parameters": [
    {
      "Id": "311597af-b1d5-42c1-bb8b-88c20d322989",
      "Name": "ChildProject.Web.ServerUrl",
      "Label": "Octopus Base Url",
      "HelpText": "**Required**\n\nThe base URL of your instance. For example `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": "f7357d18-33c3-4f1e-883d-613e13e098cd",
      "Name": "ChildProject.Api.Key",
      "Label": "Octopus API Key",
      "HelpText": "*Required* \n\n\nThe API key of the user authorized to query and promote releases.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "e3337365-c83f-4f73-a7d0-c3f36469a70d",
      "Name": "ChildProject.Space.Name",
      "Label": "Child Project Space",
      "HelpText": "*Required*\n\nThe name of the space the child project is located in.  Defaults to the current space name",
      "DefaultValue": "#{Octopus.Space.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "d9afe2db-720a-40d0-928e-6d1763286fc9",
      "Name": "ChildProject.Project.Name",
      "Label": "Child Project Name",
      "HelpText": "*Required* \n\n\nThe name of the child project you wish to deploy.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "dfe2c259-11c4-491f-900a-db7c37d82836",
      "Name": "ChildProject.Release.Number",
      "Label": "Child Project Release Number",
      "HelpText": "*Optional* \n\n\nThe release number to filter on.  This field accepts:\n- *No value (default)* - the most recent release for the channel in the calculated previous environment or specified previous environment\n- *Wild card* - Example: `2020.2.*` will find the most recent release with a major version of 2020 and a minor version of 2.  Please note the period is important, if you enter `2020.1*` you could end up with 2020.10 releases.\n- *Specific version* - Example: `2020.2.1.2` will deploy that specific version.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "c64e9163-ce84-4f56-8509-af849262a9be",
      "Name": "ChildProject.Release.NotFoundError",
      "Label": "Child Project Release Not Found Error Handle",
      "HelpText": "What this step should do when a matching release cannot be found.\n\n- `Error` - Stops the deployment when no matching release is found.  For example, you specified `2021.1.2.*`, but there is no release in the child project matching that pattern.  Or, matching releases have been found but they cannot be promoted to the destination environment.\n- `Warning` - Stops this step with a warning.  The rest of the deployment continues.\n- `Skip` - Stops this with no error or warning. Logs an information message.  The rest of the deployment continues.\n\nThe default is `Warning`.",
      "DefaultValue": "Warning",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Error|Error\nWarning|Warning\nSkip|Skip"
      }
    },
    {
      "Id": "04b2e549-888f-4a10-811c-f325690b3a80",
      "Name": "ChildProject.Destination.EnvironmentName",
      "Label": "Destination Environment Name",
      "HelpText": "*Required*\n\nThe name of the destination environment.  \n\nExamples: `Development`, `Test`, or `Production`\n\n\nThe parent project and child project do *NOT* have to have the same lifecycle.  The only requirement is all the previous phases' requirements in the child project must be met.  For example, if the child project's life cycle is Dev->Test->Staging->Prod and the parent project lifecycle is Staging->Prod and you wish to deploy to staging, then the child project's release must go through Dev and Test first.",
      "DefaultValue": "#{Octopus.Environment.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "e439fdb3-0697-4ea1-8b2c-8e343bfaa5af",
      "Name": "ChildProject.SourceEnvironment.Name",
      "Label": "Source Environment Name",
      "HelpText": "*Optional* \n\n\nThe name of the source environment.  When blank the source environment will be calculated using the channel's lifecycle.  \n\n\nExamples: `Development`, `Test`, or `Production`\n\n\n**Please Note:** The most recently created release will be selected when the destination environment is the first phase of the child project's lifecycle.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "789a658e-ee7f-4bf2-aaa3-cf1a998adf57",
      "Name": "ChildProject.Channel.Name",
      "Label": "Child Project Channel",
      "HelpText": "*Optional* \n\n\nThe name of the channel in the child project tied to the release you wish to deploy.  If left blank it will look at the project's default channel.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "d462feb8-1911-401c-8329-b7c9775ac4fd",
      "Name": "ChildProject.Tenant.Name",
      "Label": "Tenant Name",
      "HelpText": "*Optional*\n\nThe name of the tenant you wish to deploy.",
      "DefaultValue": "#{Octopus.Deployment.Tenant.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "4f9b232a-43ce-484c-9b30-0d3b195ddb15",
      "Name": "ChildProject.Prompted.Variables",
      "Label": "Child Project Prompted Variables",
      "HelpText": "*Optional*\n\n\nValues for any prompted variables for the release. Each new line represents a new variable. This will only work with string variable types, text, and sensitive values.\n\n\nUse the format Name::Value \n\n\nFor example:\n\n\n```\nPromptedVariableName::My Super Awesome Value\nOtherPromptedVariable::Other Super Awesome Value\n```",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "MultiLineText"
      }
    },
    {
      "Id": "3149fde0-05dc-4582-ac85-8539220fc09b",
      "Name": "ChildProject.DeploymentMode.Value",
      "Label": "Deployment Mode",
      "HelpText": "**Required**\n\nIndicates if the step template will promote a release from one environment to another, or it will redeploy an existing release.  \n\nOptions:\n- `Promote`: the step template will promote the release from one environment to another\n- `Redeploy`: the step template will take whatever the last release deployed to the target environment and redeploy it.  Useful when you are rebuilding a server.\n- `AlwaysLatest`: the step template will find the latest release available to the target environment and deploy it, regardless if it has already been deployed or not.\n\nDefault is `Promote`\n",
      "DefaultValue": "Promote",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Promote|Promote\nRedeploy|Redeploy\nAlwaysLatest|AlwaysLatest"
      }
    },
    {
      "Id": "f231c791-d431-493f-8802-64adcb6a6de1",
      "Name": "ChildProject.RefreshVariableSnapShots.Option",
      "Label": "Refresh Variables Snapshots",
      "HelpText": "Allows you to choose if/when variable snapshots will be refreshed.\n\n- `Yes`: Variable snapshot is refreshed when a change is detected\n- `No`: Never refresh the variable snapshot\n\nDefault is `No`.  Recommend configuring a prompted variable to control this option at deployment time.  When What If is set to `Yes` this will report a change has been made, but the snapshot refresh will not run.\n",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    },
    {
      "Id": "6b587acb-7278-45ea-bda5-3bd8f4ecc211",
      "Name": "ChildProject.Target.MachineNames",
      "Label": "Specific Deployment Target",
      "HelpText": "**Optional**\n\nA comma-separated list of deployment targets names or ids you want to deploy to.  If using an id, it must start with `Machines-`.\n\nPlease note: this step template will filter out any targets your child project cannot deploy to.  If you provide a list of 5 machines and the child project can only deploy to 3 of them, then the child project will get 3 machines sent to it.\n\nThis will overwrite the machines sent in via a deployment target trigger.\n\nDefaults `N/A`.  If you do want to use this parameter the recommendation is to use prompted variables where you pass in specific machine names.",
      "DefaultValue": "N/A",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "02805228-fde7-4c1d-853f-acad7e819ba3",
      "Name": "ChildProject.Deployment.IgnoreSpecificMachineMismatch",
      "Label": "Ignore specific machine mismatch",
      "HelpText": "When the parent project is deploying to specific machines and the child project isn't associated with those machines the step will ignore it.  \n\nExamples:\n- A deployment target trigger fires for a newly created machine.  Only 1 out of 5 child projects deploy to that newly created machine's roles.\n- A redeployment needs to occur, but only for a specific machine.  Only 2 out of the 4 child projects deploy to that specific machine's roles.    \n\nIn both those examples, the default behavior of this step template is to skip those child projects not tied to the machine's roles.  When the child project is invoked, the specific matching machines will be sent to the child project.\n\nSet to `Yes` to ignore the difference.  Default is `No`.  Warning, setting to `Yes` could result in a failed deployment.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    },
    {
      "Id": "aefc5d01-907c-47f6-a1d6-efaf97eefb6c",
      "Name": "ChildProject.ReleaseNotes.SaveAsArtifact",
      "Label": "Save release notes as artifact",
      "HelpText": "This step will pull the release notes (or build information) from the child project and will save it to the output variable `ReleaseNotes`.\n\nThis option allows you to save those release notes as an artifact.  The default is `No`.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    },
    {
      "Id": "563136dd-b848-49f8-b9ef-57acc879dbc2",
      "Name": "ChildProject.WhatIf.Value",
      "Label": "What If",
      "HelpText": "By default, this step will trigger a deployment.\n\n\nSetting this value to Yes will perform all the work up to triggering the deployment.  This is useful for approval steps, you can run this step (or set of steps) to get the list of child releases to deploy, and then verify them via a manual intervention.\n\n\nWhen this is set to `Yes` it will set an output variable `ReleaseToPromote`.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Yes|Yes\nNo|No"
      }
    },
    {
      "Id": "d1d42293-e6fc-425e-a81c-973cc81eaa1d",
      "Name": "ChildProject.WaitForFinish.Value",
      "Label": "Wait for finish",
      "HelpText": "Set to `Yes` to avoid waiting for the deployment to finish.  Will only be used when *What If* is set to `No`.",
      "DefaultValue": "Yes",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Yes|Yes\nNo|No"
      }
    },
    {
      "Id": "52a65ba4-4ded-4f55-82e0-dc5a8c71a2f8",
      "Name": "ChildProject.EnableEnhancedLogging.Value",
      "Label": "Enable Enhanced Logging",
      "HelpText": "Set to `Yes` to retrieve output of the child release while waiting for the deployment to finish. Will only be used when *What If* is set to `No` and *Wait for finish* is set to `Yes`.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Yes|Yes\nNo|No"
      }
    },
    {
      "Id": "fd04d6fc-cc78-4a4f-9373-e48c349c9b07",
      "Name": "ChildProject.CancelDeployment.Seconds",
      "Label": "Wait for Deployment",
      "HelpText": "Amount of time, in seconds, to wait for the deployment to finish.  Default is 1800 seconds, or 30 minutes.",
      "DefaultValue": "1800",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "7c3e75d3-a91c-47a1-acaa-1c6ea9f0699d",
      "Name": "ChildProject.Deployment.FutureTime",
      "Label": "Scheduling",
      "HelpText": "**Optional**\n\nSchedule the deployment for the future.  Please note, if this is set, the `Wait for Deployment` option is ignored.\n\nUses `DateTime.TryParse` 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\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": "5789f25c-6efb-4bbc-9236-2939faf7c553",
      "Name": "ChildProject.ManualInterventions.UseApprovalsFromParent",
      "Label": "Auto-Approve Child Project Manual Interventions",
      "HelpText": "If the child project has manual interventions the step will look for manual interventions in the parent project.\n\nWhen a manual intervention in the parent project 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 `Yes`, allow this to happen.  Set it to `No` to skip this functionality.",
      "DefaultValue": "Yes",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    },
    {
      "Id": "2a93a2e9-562f-4985-8933-acbf1b0e6df6",
      "Name": "ChildProject.ManualIntervention.EnvironmentToUse",
      "Label": "Approval Environment",
      "HelpText": "*Optional*\n\nThe name of the environment you wish to pull the approvals from for the parent project.   It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment.\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"
      }
    },
    {
      "Id": "11101484-c68a-4a8b-aa4d-3f0a31e36ae2",
      "Name": "ChildProject.ManualIntervention.Tenant.Name",
      "Label": "Approval Tenant",
      "HelpText": "*Optional*\n\nThe name of the tenant you wish to pull the approvals from for the parent project.   It will look at all the deployments for the current release of the parent project and select the latest deployment to the specified environment.\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 tenant name.  Will be skipped when doing a non-tenanted deployment.",
      "DefaultValue": "#{Octopus.Deployment.Tenant.Name}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    }
  ],
  "Properties": {
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptBody": "[Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12\n\n# Supplied Octopus Parameters\n$parentReleaseId = $OctopusParameters[\"Octopus.Release.Id\"]\n$parentChannelId = $OctopusParameters[\"Octopus.Release.Channel.Id\"]\n$destinationSpaceId = $OctopusParameters[\"Octopus.Space.Id\"]\n$specificMachines = $OctopusParameters[\"Octopus.Deployment.SpecificMachines\"]\n$excludeMachines = $OctopusParameters[\"Octopus.Deployment.ExcludedMachines\"]\n$deploymentMachines = $OctopusParameters[\"Octopus.Deployment.Machines\"]\n$parentDeploymentTaskId = $OctopusParameters[\"Octopus.Task.Id\"]\n$parentProjectName = $OctopusParameters[\"Octopus.Project.Name\"]\n$parentReleaseNumber = $OctopusParameters[\"Octopus.Release.Number\"]\n$parentEnvironmentName = $OctopusParameters[\"Octopus.Environment.Name\"]\n$parentEnvironmentId = $OctopusParameters[\"Octopus.Environment.Id\"]\n$parentSpaceId = $OctopusParameters[\"Octopus.Space.Id\"]\n\n# User Parameters\n$octopusApiKey = $OctopusParameters[\"ChildProject.Api.Key\"]\n$projectName = $OctopusParameters[\"ChildProject.Project.Name\"]\n$channelName = $OctopusParameters[\"ChildProject.Channel.Name\"]\n$releaseNumber = $OctopusParameters[\"ChildProject.Release.Number\"]\n$environmentName = $OctopusParameters[\"ChildProject.Destination.EnvironmentName\"]\n$sourceEnvironmentName = $OctopusParameters[\"ChildProject.SourceEnvironment.Name\"]\n$formValues = $OctopusParameters[\"ChildProject.Prompted.Variables\"]\n$destinationSpaceName = $OctopusParameters[\"ChildProject.Space.Name\"]\n$whatIfValue = $OctopusParameters[\"ChildProject.WhatIf.Value\"]\n$waitForFinishValue = $OctopusParameters[\"ChildProject.WaitForFinish.Value\"]\n$enableEnhancedLoggingValue = $OctopusParameters['ChildProject.EnableEnhancedLogging.Value']\n$deploymentCancelInSeconds = $OctopusParameters[\"ChildProject.CancelDeployment.Seconds\"]\n$ignoreSpecificMachineMismatchValue = $OctopusParameters[\"ChildProject.Deployment.IgnoreSpecificMachineMismatch\"]\n$autoapproveChildManualInterventionsValue = $OctopusParameters[\"ChildProject.ManualInterventions.UseApprovalsFromParent\"]\n$saveReleaseNotesAsArtifactValue = $OctopusParameters[\"ChildProject.ReleaseNotes.SaveAsArtifact\"]\n$futureDeploymentDate = $OctopusParameters[\"ChildProject.Deployment.FutureTime\"]\n$errorHandleForNoRelease = $OctopusParameters[\"ChildProject.Release.NotFoundError\"]\n$approvalEnvironmentName = $OctopusParameters[\"ChildProject.ManualIntervention.EnvironmentToUse\"]\n$approvalTenantName = $OctopusParameters[\"ChildProject.ManualIntervention.Tenant.Name\"]\n$refreshVariableSnapShot = $OctopusParameters[\"ChildProject.RefreshVariableSnapShots.Option\"]\n$deploymentMode = $OctopusParameters[\"ChildProject.DeploymentMode.Value\"]\n$targetMachines = $OctopusParameters[\"ChildProject.Target.MachineNames\"]\n$deploymentTenantName = $OctopusParameters[\"ChildProject.Tenant.Name\"]\n$defaultUrl = $OctopusParameters[\"ChildProject.Web.ServerUrl\"]\n\n$cachedResults = @{}\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 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 Invoke-OctopusApi\n{\n    param\n    (\n        $octopusUrl,\n        $endPoint,\n        $spaceId,\n        $apiKey,\n        $method,\n        $item,\n        $ignoreCache     \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 -ne $item)\n        {\n            $body = $item | ConvertTo-Json -Depth 10\n            Write-OctopusVerbose $body\n\n            Write-OctopusInformation \"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\n        if (($null -eq $ignoreCache -or $ignoreCache -eq $false) -and $method.ToUpper().Trim() -eq \"GET\")\n        {\n            Write-OctopusVerbose \"Checking to see if $url is already in the cache\"\n            if ($cachedResults.ContainsKey($url) -eq $true)\n            {\n                Write-OctopusVerbose \"$url is already in the cache, returning the result\"\n                return $cachedResults[$url]\n            }\n        }\n        else\n        {\n            Write-OctopusVerbose \"Ignoring cache.\"    \n        }\n\n        Write-OctopusVerbose \"No data to post or put, calling bog standard invoke-restmethod for $url\"\n        $result = Invoke-RestMethod -Method $method -Uri $url -Headers @{\"X-Octopus-ApiKey\" = \"$ApiKey\" } -ContentType 'application/json; charset=utf-8'\n\n        if ($cachedResults.ContainsKey($url) -eq $true)\n        {\n            $cachedResults.Remove($url)\n        }\n        Write-OctopusVerbose \"Adding $url to the cache\"\n        $cachedResults.add($url, $result)\n\n        return $result\n\n               \n    }\n    catch\n    {\n        if ($null -ne $_.Exception.Response)\n        {\n            if ($_.Exception.Response.StatusCode -eq 401)\n            {\n                Write-OctopusCritical \"Unauthorized error returned from $url, please verify API key and try again\"\n            }\n            elseif ($_.Exception.Response.statusCode -eq 403)\n            {\n                Write-OctopusCritical \"Forbidden error returned from $url, please verify API key and try again\"\n            }\n            else\n            {                \n                Write-OctopusVerbose -Message \"Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )\"\n            }            \n        }\n        else\n        {\n            Write-OctopusVerbose $_.Exception\n        }\n    }\n\n    Throw \"There was an error calling the Octopus API please check the log for more details\"\n}\n\nfunction Get-ListFromOctopusApi\n{\n    param (\n        $octopusUrl,\n        $endPoint,\n        $spaceId,\n        $apiKey,\n        $propertyName\n    )\n\n    $rawItemList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint $endPoint -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n\n    $returnList = @($rawItemList.$propertyName)\n\n    Write-OctopusVerbose \"The endpoint $endPoint returned a list with $($returnList.Count) items\"\n\n    return ,$returnList\n}\n\nfunction Get-FilteredOctopusItem\n{\n    param(\n        $itemList,\n        $itemName\n    )\n\n    if ($itemList.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 | Where-Object { $_.Name.ToLower().Trim() -eq $itemName.ToLower().Trim() }      \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    return $item\n}\n\nfunction Test-PhaseContainsEnvironmentId\n{\n    param (\n        $phase,\n        $environmentId\n    )\n\n    Write-OctopusVerbose \"Checking to see if $($phase.Name) automatic deployment environments $($phase.AutomaticDeploymentTargets) contains $environmentId\"\n    if ($phase.AutomaticDeploymentTargets -contains $environmentId)\n    {\n        Write-OctopusVerbose \"It does, returning true\"\n        return $true\n    } \n    \n    Write-OctopusVerbose \"Checking to see if $($phase.Name) optional deployment environments $($phase.OptionalDeploymentTargets) contains $environmentId\"\n    if ($phase.OptionalDeploymentTargets -contains $environmentId)\n    {\n        Write-OctopusVerbose \"It does, returning true\"\n        return $true\n    }\n\n    Write-OctopusVerbose \"The phase does not contain the environment returning false\"\n    return $false\n}\n\nfunction Get-OctopusItemByName\n{\n    param(\n        $itemName,\n        $itemType,\n        $endpoint,\n        $defaultValue,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey\n    )\n\n    if ([string]::IsNullOrWhiteSpace($itemName) -or $itemName -like \"#{Octopus*\")\n    {\n        Write-OctopusVerbose \"The item name passed in was $itemName, returning the default value for $itemType\"\n        return $defaultValue\n    }\n\n    Write-OctopusInformation \"Attempting to find $itemType with the name of $itemName\"\n    \n    $itemList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"$($endPoint)?partialName=$([uri]::EscapeDataString($itemName))&skip=0&take=100\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\" -propertyName \"Items\"   \n    $item = Get-FilteredOctopusItem -itemList $itemList -itemName $itemName\n\n    Write-OctopusInformation \"Successfully found $itemName with id of $($item.Id)\"\n\n    return $item\n}\n\nfunction Get-OctopusItemById\n{\n    param(\n        $itemId,\n        $itemType,\n        $endpoint,\n        $defaultValue,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey\n    )\n\n    if ([string]::IsNullOrWhiteSpace($itemId))\n    {\n        return $defaultValue\n    }\n\n    Write-OctopusInformation \"Attempting to find $itemType with the id of $itemId\"\n    \n    $item = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"$endPoint/$itemId\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"        \n\n    if ($null -eq $item)\n    {\n        Write-OctopusCritical \"Unable to find $itemType with the id of $itemId\"\n        exit 1\n    }\n    else \n    {\n        Write-OctopusInformation \"Successfully found $itemId with name of $($item.Name)\"    \n    }\n    \n    return $item\n}\n\nfunction Get-OctopusSpaceIdByName\n{\n\tparam(\n    \t$spaceName,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey    \n    )\n    \n    if ([string]::IsNullOrWhiteSpace($spaceName))\n    {\n    \treturn $spaceId\n    }\n\n    $space = Get-OctopusItemByName -itemName $spaceName -itemType \"Space\" -endpoint \"spaces\" -defaultValue $spaceId -spaceId $null -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n    \n    return $space.Id\n}\n\nfunction Get-OctopusProjectByName\n{\n    param (\n        $projectName,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    return Get-OctopusItemByName -itemName $projectName -itemType \"Project\" -endpoint \"projects\" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    \n}\n\nfunction Get-OctopusEnvironmentByName\n{\n    param (\n        $environmentName,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    return Get-OctopusItemByName -itemName $environmentName -itemType \"Environment\" -endpoint \"environments\" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    \n}\n\nfunction Get-OctopusTenantByName\n{\n    param (\n        $tenantName,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    return Get-OctopusItemByName -itemName $tenantName -itemType \"Tenant\" -endpoint \"tenants\" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey    \n}\n\nfunction Get-OctopusApprovalTenant\n{\n    param (\n        $tenantToDeploy,\n        $approvalTenantName,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    Write-OctopusInformation \"Checking to see if there is an approval tenant to consider\"\n\n    if ($null -eq $tenantToDeploy)\n    {\n        Write-OctopusInformation \"Not doing tenant deployments, skipping this check\"    \n        return $null\n    }\n\n    if ([string]::IsNullOrWhiteSpace($approvalTenantName) -eq $true -or $approvalTenantName -eq \"#{Octopus.Deployment.Tenant.Name}\")\n    {\n        Write-OctopusInformation \"No approval tenant was provided, returning $($tenantToDeploy.Id)\"\n        return $tenantToDeploy\n    }\n\n    if ($approvalTenantName.ToLower().Trim() -eq $tenantToDeploy.Name.ToLower().Trim())\n    {\n        Write-OctopusInformation \"The approval tenant name matches the deployment tenant name, using the current tenant\"\n        return $tenantToDeploy\n    }\n\n    return Get-OctopusTenantByName -tenantName $approvalTenantName -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n}\n\nfunction Get-OctopusChannel\n{\n    param (\n        $channelName,\n        $project,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    Write-OctopusInformation \"Attempting to find the channel information for project $projectName matching the channel name $channelName\"\n    $channelList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"projects/$($project.Id)/channels?skip=0&take=1000\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\" -propertyName \"Items\"\n    $channelToUse = $null\n    foreach ($channel in $channelList)\n    {\n        if ([string]::IsNullOrWhiteSpace($channelName) -eq $true -and $channel.IsDefault -eq $true)\n        {\n            Write-OctopusVerbose \"The channel name specified is null or empty and the current channel $($channel.Name) is the default, using that\"\n            $channelToUse = $channel\n            break\n        }\n\n        if ([string]::IsNullOrWhiteSpace($channelName) -eq $false -and $channel.Name.Trim().ToLowerInvariant() -eq $channelName.Trim().ToLowerInvariant())\n        {\n            Write-OctopusVerbose \"The channel name specified $channelName matches the the current channel $($channel.Name) using that\"\n            $channelToUse = $channel\n            break\n        }\n    }\n\n    if ($null -eq $channelToUse)\n    {\n        Write-OctopusCritical \"Unable to find a channel to use.  Exiting with an exit code of 1.\"\n        exit 1\n    }\n\n    return $channelToUse\n}\n\nfunction Get-OctopusLifecyclePhases\n{\n    param (\n        $channel,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $project\n    )\n\n    Write-OctopusInformation \"Attempting to find the lifecycle information $($channel.Name)\"\n    if ($null -eq $channel.LifecycleId)\n    {\n        return Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"lifecycles/$($project.LifecycleId)/preview\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\" -propertyName \"Phases\"\n    }\n    else\n    {\n        return Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"lifecycles/$($channel.LifecycleId)/preview\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\" -propertyName \"Phases\"\n    }\n}\n\nfunction Get-SourceDestinationEnvironmentInformation\n{\n    param (\n        $phaseList,\n        $targetEnvironment,\n        $sourceEnvironment,\n        $isPromotionMode,\n        $isAlwaysLatestMode\n    )\n\n    Write-OctopusVerbose \"Attempting to pull the environment ids from the source and destination phases\"\n\n    $destTargetEnvironmentInfo = @{        \n        TargetEnvironment = $targetEnvironment\n        SourceEnvironmentList = @()\n        FirstLifecyclePhase = $false\n        HasRequiredPhase = $false\n    }\n\n    if ($isPromotionMode -eq $false)\n    {\n        if ($isAlwaysLatestMode -eq $true)\n        {\n            Write-OctopusInformation \"Currently running in AlwaysLatest mode, setting the source environment to the target environment.\"\n        }\n        else\n        {\n            Write-OctopusInformation \"Currently running in redeploy mode, setting the source environment to the target environment.\"\n\n        }\n        $destTargetEnvironmentInfo.SourceEnvironmentList = $targetEnvironment.Id\n\n        return $destTargetEnvironmentInfo\n    }\n\n    $indexOfTargetEnvironment = $null\n    for ($i = 0; $i -lt $phaseList.Length; $i++)\n    {\n        Write-OctopusInformation \"Checking to see if lifecycle phase $($phaseList[$i].Name) contains the target environment id $($targetEnvironment.Id)\"\n\n        if (Test-PhaseContainsEnvironmentId -phase $phaseList[$i] -environmentId $targetEnvironment.Id)    \n        {            \n            Write-OctopusVerbose \"The phase $($phaseList[$i].Name) has the environment $($targetEnvironment.Name).\"\n            $indexOfTargetEnvironment = $i\n            break\n        }\n    }\n\n    if ($null -eq $indexOfTargetEnvironment)\n    {\n        Write-OctopusCritical \"Unable to find the target phase in this lifecycle attached to this channel.  Exiting with exit code of 1\"\n        Exit 1\n    }\n\n    if ($indexOfTargetEnvironment -eq 0)\n    {\n        Write-OctopusInformation \"This is the first phase in the lifecycle.  The current mode is promotion.  Going to get the latest release created that matches the release number rules for the channel.\"\n        $destTargetEnvironmentInfo.FirstLifecyclePhase = $true        \n        $destTargetEnvironmentInfo.SourceEnvironmentList += $targetEnvironment.Id\n\n        return $destTargetEnvironmentInfo\n    }\n    \n    if ($null -ne $sourceEnvironment)\n    {\n        Write-OctopusInformation \"The source environment $($sourceEnvironment.Name) was provided, using that as the source environment\"\n        $destTargetEnvironmentInfo.SourceEnvironmentList += $sourceEnvironment.Id\n\n        return $destTargetEnvironmentInfo\n    }\n\n    Write-OctopusVerbose \"Looping through all the previous phases until a required phase is found.\"\n    $startingIndex = ($indexOfTargetEnvironment - 1)\n    for($i = $startingIndex; $i -ge 0; $i--)\n    {\n        $previousPhase = $phaseList[$i]\n        Write-OctopusInformation \"Adding environments from the phase $($previousPhase.Name)\"\n        foreach ($environmentId in $previousPhase.AutomaticDeploymentTargets)\n        {\n            $destTargetEnvironmentInfo.SourceEnvironmentList += $environmentId\n        }\n\n        foreach ($environmentId in $previousPhase.OptionalDeploymentTargets)\n        {\n            $destTargetEnvironmentInfo.SourceEnvironmentList += $environmentId\n        }\n\n        if ($previousPhase.IsOptionalPhase -eq $false)\n        {\n            Write-OctopusVerbose \"The phase $($previousPhase.Name) is a required phase, exiting previous phase loop\"\n            $destTargetEnvironmentInfo.HasRequiredPhase = $true\n            break\n        }\n        elseif ($i -gt 0)\n        {\n            Write-OctopusVerbose \"The phase $($previousPhase.Name) is an optional phase, continuing going to check the next phase\"    \n        }\n        else\n        {\n            Write-OctopusVerbose \"The phase $($previousPhase.Name) is an optional phase.  This is the last phase so I'm stopping now.\"    \n        }\n    }\n\n    return $destTargetEnvironmentInfo             \n}\n\nfunction Get-ReleaseCanBeDeployedToTargetEnvironment\n{\n    param (\n        $release,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $sourceDestinationEnvironmentInfo,\n        $tenantToDeploy,\n        $isPromotionMode,\n        $isAlwaysLatestMode\n    )\n\n    if ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)\n    {\n        Write-OctopusInformation \"The current mode is redeploy.  Of course the release can be deployed to the target environment, no need to recheck it.\"\n        return $true\n    }\n\n    Write-OctopusInformation \"Pulling the deployment template information for release $($release.Version)\"\n    $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($release.Id)/deployments/template\" -spaceId $spaceId -method GET -apiKey $octopusApiKey\n\n    $releaseCanBeDeployedToDestination = $false    \n    Write-OctopusInformation \"Looping through deployment template list for $($release.Version) to see if it can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).\"\n    foreach ($promoteToEnvironment in $releaseDeploymentTemplate.PromoteTo)\n    {\n        if ($promoteToEnvironment.Id -eq $sourceDestinationEnvironmentInfo.TargetEnvironment.Id)\n        {\n            Write-OctopusInformation \"The environment $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) was found in the list of environments to promote to\"\n            $releaseCanBeDeployedToDestination = $true\n            break\n        }\n    }    \n\n    if ($null -eq $tenantToDeploy -or $releaseDeploymentTemplate.TenantPromotions.Length -le 0)\n    {\n        return $releaseCanBeDeployedToDestination\n    }\n\n    $releaseCanBeDeployedToDestination = $false\n    Write-OctopusInformation \"The tenant id was supplied, looping through the tenant templates to see if it can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).\"\n    foreach ($tenantPromotion in $releaseDeploymentTemplate.TenantPromotions)\n    {\n        if ($tenantPromotion.Id -ne $tenantToDeploy.Id)\n        {\n            Write-OctopusVerbose \"The tenant ids $($tenantPromotion.Id) and $($tenantToDeploy.Id) don't match, moving onto the next one\"\n            continue\n        }\n\n        Write-OctopusVerbose \"The tenant Id matches checking to see if the environment can be promoted to.\"\n        foreach ($promoteToEnvironment in $tenantPromotion.PromoteTo)\n        {\n            if ($promoteToEnvironment.Id -ne $sourceDestinationEnvironmentInfo.TargetEnvironment.Id)\n            {\n                Write-OctopusVerbose \"The environmentIds $($promoteToEnvironment.Id) and $($sourceDestinationEnvironmentInfo.TargetEnvironment.Id) don't match, moving onto the next one.\"\n                continue\n            }\n\n            Write-OctopusInformation \"The environment $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) was found in the list of environments tenant $($tenantToDeploy.Id) can be promoted to\"\n            $releaseCanBeDeployedToDestination = $true\n        }\n    }\n\n    return $releaseCanBeDeployedToDestination\n}\n\nfunction Get-DeploymentPreview\n{\n    param (\n        $releaseToDeploy,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $targetEnvironment,\n        $deploymentTenant\n    )\n\n    if ($null -eq $deploymentTenant)\n    {\n        Write-OctopusInformation \"The deployment tenant id was not sent in, generating a preview by hitting releases/$($releaseToDeploy.Id)/deployments/preview/$($targetEnvironment.Id)?includeDisabledSteps=true\"    \n        return Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($releaseToDeploy.Id)/deployments/preview/$($targetEnvironment.Id)?includeDisabledSteps=true\" -apiKey $octopusApiKey -method \"GET\" -spaceId $spaceId\n    }\n\n    Write-OctopusInformation \"The deployment tenant id was sent in, generating a preview by hitting releases/$($releaseToDeploy.Id)/deployments/previews\" \n    $requestBody = @{\n    \t\tDeploymentPreviews = @(\n    \t\t\t@{\n                \tTenantId = $deploymentTenant.Id;\n            \t\tEnvironmentId = $targetEnvironment.Id\n                 }\n            )\n    }\n    return Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($releaseToDeploy.Id)/deployments/previews\" -apiKey $octopusApiKey -method \"POST\" -spaceId $spaceId -item $requestBody -itemIsArray $true\n}\n\nfunction Get-ValuesForPromptedVariables\n{\n    param (\n        $deploymentPreview,\n        $formValues\n    )\n\n    $deploymentFormValues = @{}\n    if ([string]::IsNullOrWhiteSpace($formValues) -eq $true)\n    {\n        return $deploymentFormValues\n    }   \n    \n    $promptedValueList = @(($formValues -Split \"`n\").Trim())\n    Write-OctopusVerbose $promptedValueList.Length\n    \n    foreach($element in $deploymentPreview.Form.Elements)\n    {\n        $nameToSearchFor = $element.Control.Name\n        $uniqueName = $element.Name\n        $isRequired = $element.Control.Required\n        \n        $promptedVariablefound = $false\n        \n        Write-OctopusVerbose \"Looking for the prompted variable value for $nameToSearchFor\"\n        foreach ($promptedValue in $promptedValueList)\n        {\n            $splitValue = $promptedValue -Split \"::\"\n            Write-OctopusVerbose \"Comparing $nameToSearchFor with provided prompted variable $($promptedValue[0])\"\n            if ($splitValue.Length -gt 1)\n            {\n                if ($nameToSearchFor.ToLower().Trim() -eq $splitValue[0].ToLower().Trim())\n                {\n                    Write-OctopusVerbose \"Found the prompted variable value $nameToSearchFor\"\n                    $deploymentFormValues[$uniqueName] = $splitValue[1]\n                    $promptedVariableFound = $true\n                    break\n                }\n            }\n        }\n        \n        if ($promptedVariableFound -eq $false -and $isRequired -eq $true)\n        {\n            Write-OctopusCritical \"Unable to find a value for the required prompted variable $nameToSearchFor, exiting\"\n            Exit 1\n        }\n    }\n    \n    return $deploymentFormValues\n}\n\nfunction Test-ProjectTenantSettings\n{\n    param (\n        $tenantToDeploy,\n        $project,\n        $targetEnvironment\n    )\n\n    Write-OctopusVerbose \"About to check if $tenantToDeploy is not null and tenant deploy mode on the project $($project.TenantedDeploymentMode) <> Untenanted\"\n    if ($null -eq $tenantToDeploy)\n    {\n        Write-OctopusInformation \"Not doing a tenanted deployment, no need to check if the project supports tenanted deployments.\"\n        return $null\n    }\n\n    if ($project.TenantedDeploymentMode -eq \"Untenanted\")\n    {\n        Write-OctopusInformation \"The project is not tenanted, but we are doing a tenanted deployment, removing the tenant from the equation\"\n        return $null\n    }\n\n    Write-OctopusInformation \"Found the tenant $($tenantToDeploy.Name) checking to see if $($project.Name) is assigned to it.\"\n        \n    Write-OctopusVerbose \"Checking to see if $($tenantToDeploy.ProjectEnvironments) has $($project.Id) as a property.\"\n    if ($null -eq (Get-Member -InputObject $tenantToDeploy.ProjectEnvironments -Name $project.Id -MemberType Properties))\n    {\n        Write-OctopusSuccess \"The tenant $($tenantToDeploy.Name) is not assigned to $($project.Name).  Exiting.\"\n        Insert-EmptyOutputVariables -releaseToDeploy $null\n        \n        Exit 0\n    }\n\n    Write-OctopusInformation \"The tenant $($tenantToDeploy.Name) is assigned to $($project.Name).  Now checking to see if it can be deployed to the target environment.\"\n    $tenantProjectId = $project.Id\n    \n    Write-OctopusVerbose \"Checking to see if $($tenantToDeploy.ProjectEnvironments.$tenantProjectId) has $($targetEnvironment.Id)\"    \n    if ($tenantToDeploy.ProjectEnvironments.$tenantProjectId -notcontains $targetEnvironment.Id)\n    {\n        Write-OctopusSuccess \"The tenant $($tenantToDeploy.Name) is assigned to $($project.Name), but not to the environment $($targetEnvironment.Name).  Exiting.\"\n        Insert-EmptyOutputVariables -releaseToDeploy $null\n        \n        Exit 0\n    } \n    \n    return $tenantToDeploy\n}\n\nfunction Test-ReleaseToDeploy\n{\n\tparam (\n    \t$releaseToDeploy,\n        $errorHandleForNoRelease,\n        $releaseNumber,        \n        $sourceDestinationEnvironmentInfo, \n        $environmentList\n    )\n    \n    if ($null -ne $releaseToDeploy)\n    {\n    \treturn\n    }\n        \n    $errorMessage = \"No releases were found in environment(s)\" \n\n    $environmentMessage = @()\n    foreach ($environmentId in $sourceDestinationEnvironmentInfo.SourceEnvironmentList)\n    {\n        $environment = $environmentList | Where-Object {$_.Id -eq $environmentId }\n\n        if ($null -ne $environment)\n        {\n            $environmentMessage += $environment.Name\n        }\n    }\n\n    $errorMessage += \" $($environmentMessage -join \",\")\"\n    \n    if ([string]::IsNullOrWhitespace($releaseNumber) -eq $false)\n    {\n    \t$errorMessage = \"$errorMessage matching $releaseNumber\"\n    }\n    \n    $errorMessage = \"$errorMessage that can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)\"\n    \n    if ($errorHandleForNoRelease -eq \"Error\")\n    {\n    \tWrite-OctopusCritical $errorMessage\n        exit 1\n    }\n    \n    Insert-EmptyOutputVariables -releaseToDeploy $null\n    \n    if ($errorHandleForNoRelease -eq \"Skip\")\n    {\n    \tWrite-OctopusInformation $errorMessage\n        exit 0\n    }\n    \n    Write-OctopusSuccess $errorMessage\n    exit 0\n}\n\nfunction Get-TenantIsAssignedToPreviousEnvironments\n{\n    param (\n        $tenantToDeploy,\n        $sourceDestinationEnvironmentInfo,\n        $projectId,\n        $isPromotionMode\n    )\n\n    if ($null -eq $tenantToDeploy)\n    {\n        Write-OctopusVerbose \"The tenant is null, skipping the check to see if it is assigned to the previous environment list.\"\n        return $false\n    }\n\n    if ($isPromotionMode -eq $false)\n    {\n        Write-OctopusVerbose \"The current mode is redeploy, the source and destination environment are the same, no need to check.\"\n        return $true\n    }\n\n    Write-OctopusVerbose \"Checking to see if $($tenantToDeploy.Name) is assigned to the previous environments.\"     \n    Write-OctopusVerbose \"Checking to see if $($tenantToDeploy.ProjectEnvironments.$projectId) is assigned to the source environments(s) $($sourceDestinationEnvironmentInfo.SourceEnvironmentList)\"\n\n    foreach ($environmentId in $tenantToDeploy.ProjectEnvironments.$projectId)\n    {\n        Write-OctopusVerbose \"Checking to see if $environmentId appears in $($sourceDestinationEnvironmentInfo.SourceEnvironmentList)\"\n        if ($sourceDestinationEnvironmentInfo.SourceEnvironmentList -contains $environmentId)\n        {\n            Write-OctopusVerbose \"Found the environment $environmentId assigned to $($tenantToDeploy.Name), attempting to find the latest release for this tenant\"\n            return $true\n        }\n    }\n\n    Write-OctopusVerbose \"The tenant is not assigned to any environment in the source environments $($sourceDestinationEnvironmentInfo.SourceEnvironmentList), pulling the latest release to the environment regardless of tenant.\"\n    return $false\n}\n\nfunction Create-NewOctopusDeployment\n{\n\tparam (\n    \t$releaseToDeploy,\n        $targetEnvironment,\n        $createdDeployment,\n        $project,\n        $waitForFinish,\n        $enableEnhancedLogging,\n        $deploymentCancelInSeconds,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId,\n        $parentDeploymentApprovers,\n        $parentProjectName,\n        $parentReleaseNumber, \n        $parentEnvironmentName, \n        $parentDeploymentTaskId,\n        $autoapproveChildManualInterventions,\n        $approvalTenant\n    )\n    \n    Write-OctopusSuccess \"Deploying $($releaseToDeploy.Version) to $($targetEnvironment.Name)\"\n\n    $createdDeploymentResponse = Invoke-OctopusApi -method \"POST\" -endPoint \"deployments\" -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -item $createdDeployment\n    Write-OctopusInformation \"The task id for the new deployment is $($createdDeploymentResponse.TaskId)\"\n\n    Write-OctopusSuccess \"Deployment was successfully invoked, you can access the deployment [here]($defaultUrl/app#/$spaceId/projects/$($project.Slug)/deployments/releases/$($releaseToDeploy.Version)/deployments/$($createdDeploymentResponse.Id)?activeTab=taskSummary)\"\n    \n    if ($null -ne $createdDeployment.QueueTime -and $waitForFinish -eq $true)\n    {\n    \tWrite-OctopusWarning \"The option to wait for the deployment to finish was set to yes AND a future deployment date was set to a future value.  Ignoring the wait for finish option and exiting.\"\n        return\n    }\n    \n    if ($waitForFinish -eq $true)\n    {\n        Write-OctopusSuccess \"Waiting until deployment has finished\"\n        $startTime = Get-Date\n        $currentTime = Get-Date\n        $dateDifference = $currentTime - $startTime\n        $lastEnhancedLoggingWriteTime = [datetime]::MinValue\n\n        $numberOfWaits = 0    \n\n        While ($dateDifference.TotalSeconds -lt $deploymentCancelInSeconds)\n        {\n\t        $numberOfWaits += 1\n        \n            Write-Host \"Waiting 5 seconds to check status\"\n            Start-Sleep -Seconds 5\n            $taskStatusResponse = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint \"tasks/$($createdDeploymentResponse.TaskId)\" -method \"GET\" -ignoreCache $true   \n            $taskStatusResponseState = $taskStatusResponse.State\n\n            if ($taskStatusResponseState -eq \"Success\")\n            {\n                if ($enableEnhancedLogging -eq $true)\n                {\n                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime\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 deployment\"\n                exit 1            \n            }\n            elseif($taskStatusResponse.HasPendingInterruptions -eq $true)\n            {\n            \tif ($autoapproveChildManualInterventions -eq $true)\n                {\n                \tSubmit-ChildProjectDeploymentForAutoApproval -createdDeployment $createdDeploymentResponse -parentDeploymentApprovers $parentDeploymentApprovers -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $parentEnvironmentName -parentDeploymentTaskId $parentDeploymentTaskId -approvalTenant $approvalTenant\n                }\n                else\n                {\n                \tif ($numberOfWaits -ge 10)\n                    {\n                \t\tWrite-OctopusSuccess \"The child project has pending manual intervention(s).  Unless you approve it, this task will time out.\"\n                    }\n                    else\n                    {\n                    \tWrite-OctopusInformation \"The child project has pending manual intervention(s).  Unless you approve it, this task will time out.\"                        \n                    }\n                }\n            }\n            \n            if ($numberOfWaits -ge 10)\n            {\n                if ($enableEnhancedLogging -eq $true)\n                {\n                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime\n                }\n                else\n                {\n                    Write-OctopusSuccess \"The task state is currently $taskStatusResponseState\"\n                    $numberOfWaits = 0\n                }\n            }\n            else\n            {\n                if ($enableEnhancedLogging -eq $true)\n                {\n                    $lastEnhancedLoggingWriteTime = Get-TaskDetails -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -taskId $createdDeploymentResponse.TaskId -lastEnhancedLoggingWriteTime $lastEnhancedLoggingWriteTime\n                }\n                else\n                {\n                    Write-OctopusInformation \"The task state is currently $taskStatusResponseState\"\n                }\n            }  \n\n            $startTime = $taskStatusResponse.StartTime\n            if ($null -eq $startTime -or [string]::IsNullOrWhiteSpace($startTime) -eq $true)\n            {        \n                Write-Host \"The task is still queued, let's wait a bit longer\"\n                $startTime = Get-Date\n            }\n            $startTime = [DateTime]$startTime\n\n            $currentTime = Get-Date\n            $dateDifference = $currentTime - $startTime        \n        }\n\n        Write-OctopusCritical \"The cancel timeout has been reached, cancelling the deployment\"\n        Invoke-OctopusApi -octopusUrl $defaultUrl -apiKey $octopusApiKey -spaceId $spaceId -method \"POST\" -endPoint \"tasks/$($createdDeploymentResponse.TaskId)/cancel\"    \n        Write-OctopusInformation \"Exiting with an error code of 1 because we reached the timeout\"\n        exit 1\n    }\n}\n\nfunction Get-ChildDeploymentSpecificMachines\n{\n    param (\n        $deploymentPreview,\n        $deploymentMachines,\n        $specificMachineDeployment\n    )\n\n    if ($specificMachineDeployment -eq $false)\n    {\n        Write-OctopusVerbose \"Not doing specific machine deployments, returning any empty list of specific machines to deploy to\"\n        return @()\n    }\n\n    $filteredList = @()\n    $deploymentMachineList = $deploymentMachines -split \",\"\n\n    Write-OctopusInformation \"Doing a specific machine deployment, comparing the machines being targeted with the machines the child project can deploy to.  The number of machines being targeted is $($deploymentMachineList.Count)\"\n\n    foreach ($deploymentMachine in $deploymentMachineList)\n    {\n        $deploymentMachineLowerTrim = $deploymentMachine.Trim().ToLower()           \n\n        foreach ($step in $deploymentPreview.StepsToExecute)\n        {\n            foreach ($machine in $step.Machines)\n            {   \n                $machineLowerTrim = $machine.Id.Trim().ToLower()\n                \n                Write-OctopusVerbose \"Comparing $deploymentMachineLowerTrim with $machineLowerTrim\"\n                if ($deploymentMachineLowerTrim -ne $machineLowerTrim)\n                {\n                    Write-OctopusVerbose \"The two machine ids do not match, moving on to the next machine\"\n                    continue\n                }\n\n                Write-OctopusVerbose \"Checking to see if $machineLowerTrim is already in the filtered list.\"\n                if ($filteredList -notcontains $machine.Id)\n                {\n                    Write-OctopusVerbose \"The machine is not in the list, adding it to the list.\"\n                    $filteredList += $machine.Id\n                }\n            }\n        }\n    }\n\n    if ($filteredList.Count -gt 0)\n    {\n        Write-OctopusSuccess \"The machines applicable to this project are $filteredList.\"\n    }    \n\n    return $filteredList\n}\n\nfunction Test-ChildProjectDeploymentCanProceed\n{\n\tparam (\n    \t$releaseToDeploy,\n        $specificMachineDeployment,                        \n        $environmentName,\n        $childDeploymentSpecificMachines,\n        $project,\n        $ignoreSpecificMachineMismatch,\n        $deploymentMachines,\n        $releaseHasAlreadyBeenDeployed,\n        $isPromotionMode       \n    )\n    \n\tif ($releaseHasAlreadyBeenDeployed -eq $true -and $isPromotionMode -eq $true)\n    {\t     \t \n    \tWrite-OctopusSuccess \"Release $($releaseToDeploy.Version) is the most recent version deployed to $environmentName.  The deployment mode is Promote.  If you wish to redeploy this release then set the deployment mode to Redeploy.  Skipping this project.\"\n        \n        if ($specificMachineDeployment -eq $true -and $childDeploymentSpecificMachines.Length -gt 0)\n        {\n            Write-OctopusSuccess \"$($project.Name) can deploy to $childDeploymentSpecificMachines but redeployments are not allowed.\"\n        }\n        \n        Insert-EmptyOutputVariables -releaseToDeploy $releaseToDeploy\n\n        exit 0\n    }\n    \n    if ($childDeploymentSpecificMachines.Length -le 0 -and $specificMachineDeployment -eq $true -and $ignoreSpecificMachineMismatch -eq $false)\n    {\n        Write-OctopusSuccess \"$($project.Name) does not deploy to $($deploymentMachines -replace \",\", \" OR \").  The value for \"\"Ignore specific machine mismatch\"\" is set to \"\"No\"\".  Skipping this project.\"\n        \n        Insert-EmptyOutputVariables -releaseToDeploy $releaseToDeploy\n        \n        Exit 0\n    }\n\n    if ($childDeploymentSpecificMachines.Length -le 0 -and $specificMachineDeployment -eq $true -and $ignoreSpecificMachineMismatch -eq $true)\n    {\n        Write-OctopusSuccess \"You are doing a deployment for specific machines but $($project.Name) does not deploy to $($deploymentMachines -replace \",\", \" OR \").  You have set the value for \"\"Ignore specific machine mismatch\"\" to \"\"Yes\"\".  The child project will be deployed to, but it will do this for all machines, not any specific machines.\"\n    }\n}\n\nfunction Get-ParentDeploymentApprovers\n{\n    param (\n        $parentDeploymentTaskId,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey\n    )\n    \n    $approverList = @()\n    if ($null -eq $parentDeploymentTaskId)\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    $parentDeploymentEvents = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"events?regardingAny=$parentDeploymentTaskId&spaces=$spaceId&includeSystem=true\" -apiKey $octopusApiKey -method \"GET\"\n    \n    foreach ($parentDeploymentEvent in $parentDeploymentEvents.Items)\n    {\n        Write-OctopusVerbose \"Checking $($parentDeploymentEvent.Message) for manual intervention\"\n        if ($parentDeploymentEvent.Message -like \"Submitted interruption*\")\n        {\n            Write-OctopusVerbose \"The event $($parentDeploymentEvent.Id) is a manual intervention approval event which was approved by $($parentDeploymentEvent.Username).\"\n\n            $approverExists = $approverList | Where-Object {$_.Id -eq $parentDeploymentEvent.UserId}        \n\n            if ($null -eq $approverExists)\n            {\n                $approverInformation = @{\n                    Id = $parentDeploymentEvent.UserId;\n                    Username = $parentDeploymentEvent.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 Submit-ChildProjectDeploymentForAutoApproval\n{\n    param (\n        $createdDeployment,\n        $parentDeploymentApprovers,\n        $defaultUrl,\n        $octopusApiKey,\n        $spaceId,\n        $parentProjectName,\n        $parentReleaseNumber,\n        $parentEnvironmentName,\n        $parentDeploymentTaskId,\n        $approvalTenant\n    )\n\n    Write-OctopusSuccess \"The task has a pending manual intervention.  Checking parent approvals.\"    \n    $manualInterventionInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"interruptions?regarding=$($createdDeployment.TaskId)\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId -ignoreCache $true\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 $parentDeploymentApprovers)\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            Write-OctopusVerbose \"Found matching approvers, attempting to auto approve.\"\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 -ignoreCache $true\n                Write-OctopusVerbose \"Response from taking responsibility $($takeResponsiblilityResponse.Id)\"\n            }\n            \n            if ($null -ne $approvalTenant)\n            {\n                $approvalMessage = \"Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName for the tenant $($approvalTenant.Name) with the task id $parentDeploymentTaskId was approved by $($automaticApprover.UserName).\"\n            }\n            else\n            {\n                $approvalMessage = \"Parent project $parentProjectName release $parentReleaseNumber to $parentEnvironmentName with the task id $parentDeploymentTaskId was approved by $($automaticApprover.UserName).\"\n            }\n\n            $submitApprovalBody = @{\n                Instructions = $null;\n                Notes = \"Auto-approving this deployment. $approvalMessage That user is a member of one of the teams this manual intervention requires.  You can view that deployment $defaultUrl/app#/$spaceId/tasks/$parentDeploymentTaskId\";\n                Result = \"Proceed\"\n            }\n            $submitResult = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"interruptions/$($manualIntervention.Id)/submit\" -method \"POST\" -apiKey $octopusApiKey -item $submitApprovalBody -spaceId $spaceId -ignoreCache $true\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\nfunction Get-ReleaseNotes\n{\n\tparam (\n    \t$releaseToDeploy,\n        $deploymentPreview,\n        $channel,\n        $spaceId,\n        $defaultUrl,\n        $octopusApiKey\n    )\n            \n    $releaseNotes = @(\"\")\n    $releaseNotes += \"**Release Information**\"\n    $releaseNotes += \"\"\n\n    $packageVersionAdded = @()\n    $workItemsAdded = @()\n    $commitsAdded = @()\n\n    if ($null -ne $releaseToDeploy.BuildInformation -and $releaseToDeploy.BuildInformation.Count -gt 0)\n    {\n        $releaseNotes += \"- Package Versions\"\n        foreach ($change in $deploymentPreview.Changes)\n        {        \n            foreach ($package in $change.BuildInformation)\n            {\n                $packageInformation = \"$($package.PackageId).$($package.Version)\"\n                if ($packageVersionAdded -notcontains $packageInformation)\n                {\n                    $releaseNotes += \"  - $packageInformation\"\n                    $packageVersionAdded += $packageInformation\n                }\n            }\n        }\n\n\t\t$releaseNotes += \"\"\n        $releaseNotes += \"- Work Items\"\n        foreach ($change in $deploymentPreview.Changes)\n        {        \n            foreach ($workItem in $change.WorkItems)\n            {            \n                if ($workItemsAdded -notcontains $workItem.Id)\n                {\n                    $workItemInformation = \"[$($workItem.Id)]($($workItem.LinkUrl)) - $($workItem.Description)\"\n                    $releaseNotes += \"  - $workItemInformation\"\n                    $workItemsAdded += $workItem.Id\n                }\n            }\n        }\n\n\t\t$releaseNotes += \"\"\n        $releaseNotes += \"- Commits\"\n        foreach ($change in $deploymentPreview.Changes)\n        {        \n            foreach ($commit in $change.Commits)\n            {            \n                if ($commitsAdded -notcontains $commit.Id)\n                {\n                    $commitInformation = \"[$($commit.Id)]($($commit.LinkUrl)) - $($commit.Comment)\"\n                    $releaseNotes += \"  - $commitInformation\"\n                    $commitsAdded += $commit.Id\n                }\n            }\n        }            \n    }\n    else\n    {\n        $releaseNotes += $releaseToDeploy.ReleaseNotes\n        $releaseNotes += \"\"\n        $releaseNotes += \"Package Versions\"  \n        \n        $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"deploymentprocesses/$($releaseToDeploy.ProjectDeploymentProcessSnapshotId)/template?channel=$($channel.Id)&releaseId=$($releaseToDeploy.Id)\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId\n        \n        foreach ($package in $releaseToDeploy.SelectedPackages)\n        {\n        \tWrite-OctopusVerbose \"Attempting to find $($package.StepName) and $($package.ActionName)\"\n            \n            $deploymentProcessPackageInformation = $releaseDeploymentTemplate.Packages | Where-Object {$_.StepName -eq $package.StepName -and $_.actionName -eq $package.ActionName}\n            if ($null -ne $deploymentProcessPackageInformation)\n            {\n                $packageInformation = \"$($deploymentProcessPackageInformation.PackageId).$($package.Version)\"\n                if ($packageVersionAdded -notcontains $packageInformation)\n                {\n                    $releaseNotes += \"  - $packageInformation\"\n                    $packageVersionAdded += $packageInformation\n                }\n            }\n        }\n    }\n\n    return $releaseNotes -join \"`n\"\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    [datetime]$outputDate = New-Object DateTime\n    $currentDate = Get-Date\n\n    if ([datetime]::TryParse($futureDeploymentDate, [ref]$outputDate) -eq $false)\n    {\n        Write-OctopusCritical \"The suppplied date $futureDeploymentDate 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    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 Insert-EmptyOutputVariables\n{\n\tparam (\n    \t$releaseToDeploy\n    )\n    \n\tif ($null -ne $releaseToDeploy)\n    {\n\t\tSet-OctopusVariable -Name \"ReleaseToPromote\" -Value $($releaseToDeploy.Version)\n        Set-OctopusVariable -Name \"ReleaseNotes\" -value \"Release already deployed to destination environment.\"\n    }\n    else\n    {\n    \tSet-OctopusVariable -Name \"ReleaseToPromote\" -Value \"N/A\"\n        Set-OctopusVariable -Name \"ReleaseNotes\" -value \"No release found\"\n    }        \n    \n    Write-OctopusInformation \"Setting the output variable ChildReleaseToDeploy to $false\"\n    Set-OctopusVariable -Name \"ChildReleaseToDeploy\" -Value $false\n}\n\nfunction Get-ApprovalDeploymentTaskId\n{\n\tparam (\n    \t$autoapproveChildManualInterventions,\n        $parentDeploymentTaskId,\n        $parentReleaseId,\n        $parentEnvironmentName,\n        $approvalEnvironmentName,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $parentChannelId,    \n        $parentEnvironmentId,\n        $approvalTenant,\n        $parentProject\n    )\n    \n    if ($autoapproveChildManualInterventions -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 $parentDeploymentTaskId\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 $parentDeploymentTaskId\"\n        return $parentDeploymentTaskId\n    }\n    \n    $approvalEnvironment = Get-OctopusEnvironmentByName -environmentName $approvalEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n    $releaseDeploymentList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$parentReleaseId/deployments?skip=0&take=1000\" -method \"GET\" -apiKey $octopusApiKey -spaceId $spaceId -propertyName \"Items\"\n    \n    $lastDeploymentTime = $(Get-Date).AddYears(-50)\n    $approvalTaskId = $null\n    foreach ($deployment in $releaseDeploymentList)\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).  Moving onto the next deployment.\"\n            continue\n        }\n\n        if ($null -ne $approvalTenant -and $null -ne $deployment.TenantId -and $deployment.TenantId -ne $approvalTenant.Id)\n        {\n            Write-OctopusInformation \"The deployment $($deployment.Id) was deployed to the correct environment, $($approvalEnvironment.Id), but the deployment tenant $($deployment.TenantId) doesn't match the approval tenant $($approvalTenant.Id).  Moving onto the next deployment.\"\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 $false)\n        {\n            Write-OctopusInformation \"The deployment $($deployment.Id) is being deployed to the approval environment, but it hasn't completed, moving onto the next deployment.\"\n            continue\n        }\n\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        $lifecyclePhases = Get-OctopusLifeCyclePhases -channel $channelInformation -defaultUrl $defaultUrl -spaceId $spaceId -OctopusApiKey $octopusApiKey -project $parentProject        \n        \n        $foundDestinationFirst = $false\n        $foundApprovalFirst = $false\n        \n        foreach ($phase in $lifecyclePhases)\n        {\n        \tif (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $parentEnvironmentId)\n            {\n            \tif ($foundApprovalFirst -eq $false)\n                {\n                \t$foundDestinationFirst = $true\n                }\n            }\n            \n            if (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $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 Invoke-RefreshVariableSnapshot\n{\n\tparam (\n    \t$refreshVariableSnapShot,\n        $whatIf,\n        $releaseToDeploy,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n    \n    Write-OctopusVerbose \"Checking to see if variable snapshot will be updated.\"\n    \n    if ($refreshVariableSnapShot -eq \"No\")\n    {\n    \tWrite-OctopusVerbose \"Refreshing variables is set to no, skipping\"\n    \treturn\n    }\n    \n    $releaseDeploymentTemplate = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($releaseToDeploy.Id)/deployments/template\" -spaceId $spaceId -method GET -apiKey $octopusApiKey\n    \n    if ($releaseDeploymentTemplate.IsVariableSetModified -eq $false -and $releaseDeploymentTemplate.IsLibraryVariableSetModified -eq $false)\n    {\n    \tWrite-OctopusVerbose \"Variables have not been updated since release creation, skipping\"\n        return\n    }\n    \n    if ($whatIf -eq $true)\n    {\n    \tWrite-OctopusSuccess \"Variables have been updated since release creation, whatif set to true, no update will occur.\"\n        return\n    }\n    \n    $snapshotVariables = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($releaseToDeploy.Id)/snapshot-variables\" -spaceId $spaceId -method \"POST\" -apiKey $octopusApiKey\n    Write-OctopusSuccess \"Variables have been modified since release creation.  Variable snapshot was updated on $($snapshotVariables.LastModifiedOn)\"\n}\n\nfunction Get-MatchingOctopusDeploymentTasks\n{\n    param (\n        $spaceId,\n        $project,\n        $tenantToDeploy,\n        $tenantIsAssignedToPreviousEnvironments,\n        $sourceDestinationEnvironmentInfo,\n        $defaultUrl,\n        $octopusApiKey\n    )\n\n    $taskEndPoint = \"tasks?skip=0&take=100&spaces=$spaceId&includeSystem=false&project=$($project.Id)&name=Deploy&states=Success\"\n\n    if ($null -ne $tenantToDeploy -and $tenantIsAssignedToPreviousEnvironments -eq $true)\n    {\n        $taskEndPoint += \"&tenant=$($tenantToDeploy.Id)\"\n    }\n\n    $taskList = @()\n\n    foreach ($sourceEnvironmentId in $sourceDestinationEnvironmentInfo.SourceEnvironmentList)\n    {\n        $octopusTaskList = Get-ListFromOctopusApi -octopusUrl $DefaultUrl -endPoint \"$($taskEndPoint)&environment=$sourceEnvironmentId\" -spaceId $null -apiKey $octopusApiKey -method \"GET\" -propertyName \"Items\"\n        $taskList += $octopusTaskList\n    }\n\n    $orderedTaskList = @($taskList | Sort-Object -Property StartTime -Descending)\n    Write-OctopusVerbose \"We have $($orderedTaskList.Count) number of tasks to loop through\"\n\n    return $orderedTaskList\n}\n\nfunction Get-TaskDetails\n{\n    param (\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $taskId,\n        $lastEnhancedLoggingWriteTime\n    )\n\n    $taskDetails = Invoke-OctopusApi -octopusUrl $defaultUrl -spaceId $spaceId -apiKey $octopusApiKey -endPoint \"tasks/$taskId/details\" -method 'GET' -ignoreCache $true\n    $activityLogs = $taskDetails.ActivityLogs\n    $writeStepName = $writeTargetName = $true\n    $returnTime = [datetime]::MinValue\n\n    foreach ($step in $activityLogs.Children)\n    {\n        foreach ($target in $step.Children)\n        {\n            foreach ($logElement in $target.LogElements)\n            {\n                $occurredAt = [datetime]($logElement.OccurredAt)\n                if ($occurredAt -gt $lastEnhancedLoggingWriteTime)\n                {\n                    if ($writeStepName -eq $true)\n                    {\n                        $trailingCount = 66 - $step.Name.Length\n                        if ($trailingCount -lt 0) { $trailingCount = 0 }\n                        Write-OctopusInformation \"╔═ $($step.Name) $(\"\" * $trailingCount)\"\n                        $writeStepName = $false\n                    }\n                    if ($writeTargetName -eq $true)\n                    {\n                        $trailingCount = 64 - $target.Name.Length\n                        if ($trailingCount -lt 0) { $trailingCount = 0 }\n                        Write-OctopusInformation \"║ ┌─ $($target.Name) $('─' * $trailingCount)\"\n                        $writeTargetName = $false\n                    }\n                    Write-OctopusInformation \"║ │ $($logElement.MessageText)\"\n                    if ($occurredAt -gt $returnTime) { $returnTime = $occurredAt}\n                }\n            }\n            if ($writeTargetName -eq $false)\n            {\n                Write-OctopusInformation \"║ └$('─' * 67)\"\n                $writeTargetName = $true\n            }\n        }\n        if ($writeStepName -eq $false)\n        {\n            Write-OctopusInformation \"╚$('═' * 69)\"\n            $writeStepName = $true\n        }\n    }\n\n    return $returnTime\n}\n\nfunction Get-ReleaseToDeployFromTaskList\n{\n    param (\n        $taskList,\n        $channel,\n        $releaseNumber,\n        $tenantToDeploy,\n        $sourceDestinationEnvironmentInfo,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $isPromotionMode\n    )\n    \n    foreach ($task in $taskList)\n    {\n        Write-OctopusVerbose \"Pulling the deployment information for $($task.Id)\"\n        $deploymentInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"deployments/$($task.Arguments.DeploymentId)\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n\n        if ($deploymentInformation.ChannelId -ne $channel.Id)\n        {\n            Write-OctopusInformation \"The deployment was not for the channel we want to deploy to, moving onto next task.\"\n            continue\n        }\n\n        Write-OctopusVerbose \"Pulling the release information for $($deploymentInformation.Id)\"\n        $releaseInformation = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"releases/$($deploymentInformation.ReleaseId)\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n        \n        if ($isPromotionMode -eq $false)\n        {\n            Write-OctopusInformation \"Current mode is set to redeploy, the release is for the correct channel and was successful, using it.\"            \n            return $releaseInformation\n        }\n\n        if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false -and $releaseInformation.Version -notlike $releaseNumber)\n        {\n            Write-OctopusInformation \"The release version $($releaseInformation.Version) does not match $releaseNumber.  Moving onto the next task.\"\n            continue\n        }\n\n        $releaseCanBeDeployed = Get-ReleaseCanBeDeployedToTargetEnvironment -defaultUrl $defaultUrl -release $releaseInformation -spaceId $spaceId -octopusApiKey $octopusApiKey -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -tenantToDeploy $tenantToDeploy -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n\n        if ($releaseCanBeDeployed -eq $true)\n        {\n            Write-OctopusInformation \"The release $($releaseInformation.Version) can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).\"\n            return $releaseInformation                                    \n        }\n\n        Write-OctopusInformation \"The release $($releaseInformation.Version) cannot be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).  Moving onto next task\"\n    } \n    \n    return $null\n}\n\nfunction Get-ReleaseToDeployFromChannel\n{\n    param (\n        $channel,\n        $releaseNumber,\n        $tenantToDeploy,\n        $sourceDestinationEnvironmentInfo,        \n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey,\n        $isPromotionMode,\n        $isAlwaysLatestMode\n    )\n\n    if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false)\n    {        \n        $urlReleaseNumber = $releaseNumber.Replace(\"*\", \"\")\n        Write-OctopusInformation \"The release number was sent in, sending $urlReleaseNumber to the channel endpoint to have the server filter on that number first.\"\n        $releaseChannelList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"channels/$($channel.Id)/releases?skip=0&take=100&searchByVersion=$urlReleaseNumber\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"    \n    }\n    else\n    {\n        Write-OctopusInformation \"The release number was not sent in, attempting to find the latest release from the channel to deploy.\"\n        $releaseChannelList = Invoke-OctopusApi -octopusUrl $defaultUrl -endPoint \"channels/$($channel.Id)/releases?skip=0&take=100\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"    \n    }\n    \n    Write-OctopusInformation \"There are $($releaseChannelList.Items.Count) potential releases to go through.\"\n\n    foreach ($releaseInformation in $releaseChannelList.Items)\n    {\n        if ([string]::IsNullOrWhiteSpace($releaseNumber) -eq $false -and $releaseInformation.Version -notlike $releaseNumber)\n        {\n            Write-OctopusInformation \"The release version $($releaseInformation.Version) does not match $releaseNumber.  Moving onto the next release in the channel.\"\n            continue\n        }\n\n        $releaseCanBeDeployed = Get-ReleaseCanBeDeployedToTargetEnvironment -defaultUrl $defaultUrl -release $releaseInformation -spaceId $spaceId -octopusApiKey $octopusApiKey -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -tenantToDeploy $tenantToDeploy -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n\n        if ($releaseCanBeDeployed -eq $true)\n        {\n            Write-OctopusInformation \"The release $($releaseInformation.Version) can be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).\"\n            return $releaseInformation                                    \n        }\n\n        Write-OctopusInformation \"The release $($releaseInformation.Version) cannot be deployed to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).  Moving onto next release in the channel.\"\n    }\n\n    return $null\n}\n\nfunction Get-ReleaseHasAlreadyBeenPromotedToTargetEnvironment\n{\n    param (\n        $releaseToDeploy,\n        $tenantToDeploy,\n        $sourceDestinationEnvironmentInfo,\n        $isPromotionMode,\n        $isAlwaysLatestMode,\n        $defaultUrl,\n        $spaceId,\n        $octopusApiKey\n    )\n\n    if ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)\n    {\n        Write-OctopusInformation \"Currently in redeploy mode, of course the release has already been deployed to the target environment.  Exiting the Release Has Already Been Promoted To Target Environment check.\"\n        return $true\n    }\n\n    Write-OctopusVerbose \"Pulling the last release for the target environment to see if the release to deploy is the latest one in that environment.\"\n    $taskEndPoint = \"tasks?skip=0&take=1&spaces=$spaceId&includeSystem=false&project=$($releaseToDeploy.ProjectId)&name=Deploy&states=Success&environment=$($sourceDestinationEnvironmentInfo.TargetEnvironment.Id)\"\n\n    if ($null -ne $tenantToDeploy)\n    {\n        $taskEndPoint += \"&tenant=$($tenantToDeploy.Id)\"\n    }\n\n    $octopusTaskList = Get-ListFromOctopusApi -octopusUrl $DefaultUrl -endPoint \"$taskEndPoint\" -spaceId $null -apiKey $octopusApiKey -method \"GET\" -propertyName \"Items\"\n\n    if ($octopusTaskList.Count -eq 0)\n    {\n        Write-OctopusInformation \"There have been no releases to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name) for this project.\"\n        return $false\n    }\n\n    $task = $octopusTaskList[0]\n    $deploymentInformation = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"deployments/$($task.Arguments.DeploymentId)\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n\n    if ($releaseToDeploy.Id -eq $deploymentInformation.ReleaseId)\n    {\n        Write-OctopusInformation \"The release to deploy $($release.ReleaseNumber) is the last successful release to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)\"\n        return $true\n    }\n\n    Write-OctopusInformation \"The release to deploy $($release.ReleaseNumber) is different than the last successful release to $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name)\"\n    return $false\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    \t$trimmedMachineName = $machineName.Trim()\n        Write-OctopusVerbose \"Translating $trimmedMachineName into an Octopus Id\"\n    \tif ($trimmedMachineName -like \"Machines-*\")\n        {\n        \tWrite-OctopusVerbose \"$trimmedMachineName is already an Octopus Id, adding it to the list\"\n        \t$translatedList += $machineName\n            continue\n        }\n        \n        $machineObject = Get-OctopusItemByName -itemName $trimmedMachineName -itemType \"Deployment Target\" -endpoint \"machines\" -defaultValue $null -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n\n        $translatedList += $machineObject.Id\n    }\n\n    return $translatedList -join \",\"\n}\n\nfunction Write-ReleaseInformation\n{\n    param (\n        $releaseToDeploy,\n        $environmentList\n    )\n\n    $releaseDeployments = Invoke-OctopusApi -octopusUrl $DefaultUrl -endPoint \"releases/$($releaseToDeploy.Id)/deployments\" -spaceId $spaceId -apiKey $octopusApiKey -method \"GET\"\n    $releaseEnvironmentList = @()\n\n    foreach ($deployment in $releaseDeployments.Items)\n    {        \n        $releaseEnvironment = $environmentList | Where-Object {$_.Id -eq $deployment.EnvironmentId }\n        \n        if ($null -ne $releaseEnvironment -and $releaseEnvironmentList -notcontains $releaseEnvironment.Name)\n        {\n            Write-OctopusVerbose \"Adding $($releaseEnvironment.Name) to the list of environments this release has been deployed to\"\n            $releaseEnvironmentList += $releaseEnvironment.Name\n        }                \n    }\n    \n    if ($releaseEnvironmentList.Count -gt 0)\n    {\n        Write-OctopusSuccess \"The release to deploy is $($releaseToDeploy.Version) which has been deployed to $($releaseEnvironmentList -join \",\")\"\n    }\n    else\n    {\n        Write-OctopusSuccess \"The release to deploy is $($releaseToDeploy.Version) which currently has no deployments.\"    \n    }\n}\n\nfunction Get-GuidedFailureMode\n{\n\tparam (\n    \t$projectToDeploy,\n        $environmentToDeployTo\n    )\n    \n    Write-OctopusInformation \"Checking $($projectToDeploy.DefaultGuidedFailureMode) and $($environmentToDeployTo.UseGuidedFailure) to determine guided failure mode.\"\n    \n    if ($projectToDeploy.DefaultGuidedFailureMode -eq \"EnvironmentDefault\" -and $environmentToDeployTo.UseGuidedFailure -eq $true)\n    {\n    \tWrite-OctopusInformation \"Guided failure for the project is set to environment default, and destination environment says to use guided failure.  Setting guided failure to true.\"\n        return $true\n    }\n    \n    if ($projectToDeploy.DefaultGuidedFailureMode -eq \"On\")\n    {\n    \tWrite-OctopusInformation \"Guided failure for the project is set to always use guided falure.  Setting guided failure to true.\"\n        return $true\n    }\n    \n    Write-OctopusInformation \"Guided failure is not turned on for the project nor the environment.  Setting to false.\"\n    return $false\n}\n\nWrite-OctopusInformation \"Octopus SpaceId: $destinationSpaceId\"\nWrite-OctopusInformation \"Octopus Deployment Task Id: $parentDeploymentTaskId\"\nWrite-OctopusInformation \"Octopus Project Name: $parentProjectName\"\nWrite-OctopusInformation \"Octopus Release Number: $parentReleaseNumber\"\nWrite-OctopusInformation \"Octopus Release Id: $parentReleaseId\"\nWrite-OctopusInformation \"Octopus Environment Name: $parentEnvironmentName\"\nWrite-OctopusInformation \"Octopus Release Channel Id: $parentChannelId\"\nWrite-OctopusInformation \"Octopus Specific deployment machines: $specificMachines\"\nWrite-OctopusInformation \"Octopus Exclude deployment machines: $excludeMachines\"\nWrite-OctopusInformation \"Octopus deployment machines: $deploymentMachines\"\n\nWrite-OctopusInformation \"Child Project Name: $projectName\"\nWrite-OctopusInformation \"Child Project Space Name: $destinationSpaceName\"\nWrite-OctopusInformation \"Child Project Channel Name: $channelName\"\nWrite-OctopusInformation \"Child Project Release Number: $releaseNumber\"\nWrite-OctopusInformation \"Child Project Error Handle No Release Found: $errorHandleForNoRelease\"\nWrite-OctopusInformation \"Destination Environment Name: $environmentName\"\nWrite-OctopusInformation \"Source Environment Name: $sourceEnvironmentName\"\nWrite-OctopusInformation \"Ignore specific machine mismatch: $ignoreSpecificMachineMismatchValue\"\nWrite-OctopusInformation \"Save release notes as artifact: $saveReleaseNotesAsArtifactValue\"\nWrite-OctopusInformation \"What If: $whatIfValue\"\nWrite-OctopusInformation \"Wait for finish: $waitForFinishValue\"\nWrite-OctopusInformation \"Cancel deployment in seconds: $deploymentCancelInSeconds\"\nWrite-OctopusInformation \"Scheduling: $futureDeploymentDate\"\nWrite-OctopusInformation \"Auto-Approve Child Project Manual Interventions: $autoapproveChildManualInterventionsValue\"\nWrite-OctopusInformation \"Approval Environment: $approvalEnvironmentName\"\nWrite-OctopusInformation \"Approval Tenant: $approvalTenantName\"\nWrite-OctopusInformation \"Refresh Variable Snapshot: $refreshVariableSnapShot\"\nWrite-OctopusInformation \"Deployment Mode: $deploymentMode\"\nWrite-OctopusInformation \"Target Machine Names: $targetMachines\"\nWrite-OctopusInformation \"Deployment Tenant Name: $deploymentTenantName\"\n\n$whatIf = $whatIfValue -eq \"Yes\"\n$waitForFinish = $waitForFinishValue -eq \"Yes\"\n$enableEnhancedLogging = $enableEnhancedLoggingValue -eq \"Yes\"\n$ignoreSpecificMachineMismatch = $ignoreSpecificMachineMismatchValue -eq \"Yes\"\n$autoapproveChildManualInterventions = $autoapproveChildManualInterventionsValue -eq \"Yes\"\n$saveReleaseNotesAsArtifact = $saveReleaseNotesAsArtifactValue -eq \"Yes\"\n\n$verificationPassed = @()\n$verificationPassed += Test-RequiredValues -variableToCheck $octopusApiKey -variableName \"Octopus API Key\"\n$verificationPassed += Test-RequiredValues -variableToCheck $destinationSpaceName -variableName \"Child Project Space\"\n$verificationPassed += Test-RequiredValues -variableToCheck $projectName -variableName \"Child Project Name\"\n$verificationPassed += Test-RequiredValues -variableToCheck $environmentName -variableName \"Destination Environment Name\"\n\nif ($verificationPassed -contains $false)\n{\n\tWrite-OctopusInformation \"Required values missing\"\n\tExit 1\n}\n\n$isPromotionMode = $deploymentMode -eq \"Promote\"\n$isAlwaysLatestMode = $deploymentMode -eq 'AlwaysLatest'\n$spaceId = Get-OctopusSpaceIdByName -spaceName $destinationSpaceName -spaceId $destinationSpaceId -defaultUrl $defaultUrl -OctopusApiKey $octopusApiKey    \n\nWrite-OctopusSuccess \"The current mode of the step template is $deploymentMode\"\n\nif ($isAlwaysLatestMode -eq $true)\n{\n    Write-OctopusSuccess \"Currently in AlwaysLatest mode, release number filter will be ignored, source environment will be set to the target environment, all redeployment checks will be ignored.\"\n}\n\nif ($isPromotionMode -eq $false -and $isAlwaysLatestMode -eq $false)\n{\n    Write-OctopusSuccess \"Currently in redeploy mode, release number filter will be ignored, source environment will be set to the target environment, all redeployment checks will be ignored.\"\n}\n\nif ($isPromotionMode -eq $true -and [string]::IsNullOrWhiteSpace($sourceEnvironmentName) -eq $false -and $sourceEnvironmentName.ToLower().Trim() -eq $environmentName.ToLower().Trim())\n{\n    Write-OctopusSuccess \"The current mode is promotion.  Both the source environment and destination environment are the same.  You cannot promote from the same environment as the source environment.  Exiting.  Change the deployment mode value to redeploy if you want to redeploy.\"\n    Exit 0\n}\n\n$specificMachineDeployment = $false\nif ([string]::IsNullOrWhiteSpace($specificMachines) -eq $false)\n{\n\tWrite-OctopusSuccess \"This deployment is targeting the specific machines $specificMachines.\"\n\t$specificMachineDeployment = $true\n}\n\nif ([string]::IsNullOrWhiteSpace($excludeMachines) -eq $false)\n{\n\tWrite-OctopusSuccess \"This deployment is excluding the specific machines $excludeMachines.  The machines being deployed to are: $deploymentMachines.\"\n    $specificMachineDeployment = $true\n}\n\nif ([string]::IsNullOrWhiteSpace($targetMachines) -eq $false -and $targetMachines -ne \"N/A\")\n{\n    Write-OctopusSuccess \"You have specified specific machines to target in this deployment.  Ignoring the machines that triggered this deployment.\"\n    $specificMachineDeployment = $true\n    $deploymentMachines = Get-MachineIdsFromMachineNames -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -targetMachines $targetMachines\n}\n\n$project = Get-OctopusProjectByName -projectName $projectName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n$parentProject = Get-OctopusProjectByName -projectName $parentProjectName -defaultUrl $defaultUrl -spaceId $parentSpaceId -octopusApiKey $octopusApiKey\n$tenantToDeploy = Get-OctopusTenantByName -tenantName $deploymentTenantName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n$targetEnvironment = Get-OctopusEnvironmentByName -environmentName $environmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n$tenantToDeploy = Test-ProjectTenantSettings -tenantToDeploy $tenantToDeploy -project $project -targetEnvironment $targetEnvironment\n\n$sourceEnvironment = Get-OctopusEnvironmentByName -environmentName $sourceEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n$channel = Get-OctopusChannel -channelName $channelName -defaultUrl $defaultUrl -project $project -spaceId $spaceId -octopusApiKey $octopusApiKey\n$phaseList = Get-OctopusLifecyclePhases -channel $channel -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -project $project\n$sourceDestinationEnvironmentInfo = Get-SourceDestinationEnvironmentInformation -phaseList $phaseList -targetEnvironment $targetEnvironment -sourceEnvironment $sourceEnvironment -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n\nif ($deploymentMode -eq 'AlwaysLatest')\n{\n    Write-OctopusInformation \"Finding the latest release that can be deployed.\"\n    $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n}\nelseif ($sourceDestinationEnvironmentInfo.FirstLifecyclePhase -eq $false)\n{\n    $tenantIsAssignedToPreviousEnvironments = Get-TenantIsAssignedToPreviousEnvironments -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -projectId $project.Id -isPromotionMode $isPromotionMode\n    $taskList = Get-MatchingOctopusDeploymentTasks -spaceId $spaceId -project $project -tenantToDeploy $tenantToDeploy -tenantIsAssignedToPreviousEnvironments $tenantIsAssignedToPreviousEnvironments -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n    $releaseToDeploy = Get-ReleaseToDeployFromTaskList -taskList $taskList -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $DefaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode    \n\n    if ($null -eq $releaseToDeploy -and $sourceDestinationEnvironmentInfo.HasRequiredPhase -eq $false)\n    {\n        Write-OctopusInformation \"No release was found that has been deployed.  However, all the phases prior to the destination phase is optional.  Checking to see if any releases exist at the channel level that haven't been deployed.\"\n        $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n    }\n}\nelse\n{\n    $releaseToDeploy = Get-ReleaseToDeployFromChannel -channel $channel -releaseNumber $releaseNumber -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n}\n\n$environmentList = Get-ListFromOctopusApi -octopusUrl $defaultUrl -endPoint \"environments?skip=0&take=1000\" -spaceId $spaceId -propertyName \"Items\" -apiKey $octopusApiKey\n\nTest-ReleaseToDeploy -releaseToDeploy $releaseToDeploy -errorHandleForNoRelease $errorHandleForNoRelease -releaseNumber $releaseNumber -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -environmentList $environmentList\n\nif ($null -ne $releaseToDeploy)\n{\n    Write-ReleaseInformation -releaseToDeploy $releaseToDeploy -environmentList $environmentList\n}\n\n$releaseHasAlreadyBeenDeployed = Get-ReleaseHasAlreadyBeenPromotedToTargetEnvironment -releaseToDeploy $releaseToDeploy -tenantToDeploy $tenantToDeploy -sourceDestinationEnvironmentInfo $sourceDestinationEnvironmentInfo -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -isPromotionMode $isPromotionMode -isAlwaysLatestMode $isAlwaysLatestMode\n\n$deploymentPreview = Get-DeploymentPreview -releaseToDeploy $releaseToDeploy -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -targetEnvironment $targetEnvironment -deploymentTenant $tenantToDeploy\n$childDeploymentSpecificMachines = Get-ChildDeploymentSpecificMachines -deploymentPreview $deploymentPreview -deploymentMachines $deploymentMachines -specificMachineDeployment $specificMachineDeployment\n$deploymentFormValues = Get-ValuesForPromptedVariables -formValues $formValues -deploymentPreview $deploymentPreview\n\n$queueDate = Get-QueueDate -futureDeploymentDate $futureDeploymentDate\n$queueExpiryDate = Get-QueueExpiryDate -queueDate $queueDate\n$useGuidedFailure = Get-GuidedFailureMode -projectToDeploy $project -environmentToDeployTo $targetEnvironment\n\n$createdDeployment = @{\n    EnvironmentId = $targetEnvironment.Id;\n    ExcludeMachineIds = @();\n    ForcePackageDownload = $false;\n    ForcePackageRedeployment = $false;\n    FormValues = $deploymentFormValues;\n    QueueTime = $queueDate;\n    QueueTimeExpiry = $queueExpiryDate;\n    ReleaseId = $releaseToDeploy.Id;\n    SkipActions = @();\n    SpecificMachineIds = @($childDeploymentSpecificMachines);\n    TenantId = $null;\n    UseGuidedFailure = $useGuidedFailure\n}\n\nif ($null -ne $tenantToDeploy -and $project.TenantedDeploymentMode -ne \"Untenanted\")\n{\n    $createdDeployment.TenantId = $tenantToDeploy.Id\n}\n\nif ($whatIf -eq $true)\n{    \t\n    Write-OctopusVerbose \"Would have done a POST to /api/$spaceId/deployments with the body:\"\n    Write-OctopusVerbose $($createdDeployment | ConvertTo-JSON)        \n    \n    Write-OctopusSuccess \"What If set to true.\"\n    Write-OctopusSuccess \"Setting the output variable ReleaseToPromote to $($releaseToDeploy.Version).\"            \n\tSet-OctopusVariable -Name \"ReleaseToPromote\" -Value ($releaseToDeploy.Version)       \n}\n\nWrite-OctopusVerbose \"Getting the release notes\"\n$releaseNotes = Get-ReleaseNotes -releaseToDeploy $releaseToDeploy -deploymentPreview $deploymentPreview -channel $channel -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\nWrite-OctopusSuccess \"Setting the output variable ReleaseNotes which contains the release notes from the child project\"\nSet-OctopusVariable -Name \"ReleaseNotes\" -value $releaseNotes\n\nTest-ChildProjectDeploymentCanProceed -releaseToDeploy $releaseToDeploy -specificMachineDeployment $specificMachineDeployment -environmentName $environmentName -childDeploymentSpecificMachines $childDeploymentSpecificMachines -project $project -ignoreSpecificMachineMismatch $ignoreSpecificMachineMismatch -deploymentMachines $deploymentMachines -releaseHasAlreadyBeenDeployed $releaseHasAlreadyBeenDeployed -isPromotionMode $isPromotionMode\n\nif ($saveReleaseNotesAsArtifact -eq $true)\n{\n\t$releaseNotes | Out-File \"ReleaseNotes.txt\"\n    $currentDate = Get-Date\n\t$currentDateFormatted = $currentDate.ToString(\"yyyy_MM_dd_HH_mm\")\n    $artifactName = \"$($project.Name) $($releaseToDeploy.Version) $($sourceDestinationEnvironmentInfo.TargetEnvironment.Name).ReleaseNotes_$($currentDateFormatted).txt\"\n    Write-OctopusInformation \"Creating the artifact $artifactName\"\n    \n\tNew-OctopusArtifact -Path \"ReleaseNotes.txt\" -Name $artifactName\n}\n\nInvoke-RefreshVariableSnapshot -refreshVariableSnapShot $refreshVariableSnapShot -whatIf $whatIf -releaseToDeploy $releaseToDeploy -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey\n\nif ($whatif -eq $true)\n{\n    Write-OctopusSuccess \"Exiting because What If set to true.\"\n    Write-OctopusInformation \"Setting the output variable ChildReleaseToDeploy to $true\"\n    Set-OctopusVariable -Name \"ChildReleaseToDeploy\" -Value $true\n    Exit 0\n}\n\n$approvalTenant = Get-OctopusApprovalTenant -tenantToDeploy $tenantToDeploy -approvalTenantName $approvalTenantName -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n$approvalDeploymentTaskId = Get-ApprovalDeploymentTaskId -autoapproveChildManualInterventions $autoapproveChildManualInterventions  -parentDeploymentTaskId $parentDeploymentTaskId -parentReleaseId $parentReleaseId -parentEnvironmentName $parentEnvironmentName -approvalEnvironmentName $approvalEnvironmentName -defaultUrl $defaultUrl -spaceId $spaceId -octopusApiKey $octopusApiKey -parentChannelId $parentChannelId -parentEnvironmentId $parentEnvironmentId -approvalTenant $approvalTenant -parentProject $parentProject\n$parentDeploymentApprovers = Get-ParentDeploymentApprovers -parentDeploymentTaskId $approvalDeploymentTaskId -spaceId $spaceId -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey\n\nCreate-NewOctopusDeployment -releaseToDeploy $releaseToDeploy -targetEnvironment $targetEnvironment -createdDeployment $createdDeployment -project $project -waitForFinish $waitForFinish -enableEnhancedLogging $enableEnhancedLogging -deploymentCancelInSeconds $deploymentCancelInSeconds -defaultUrl $defaultUrl -octopusApiKey $octopusApiKey -spaceId $spaceId -parentDeploymentApprovers $parentDeploymentApprovers -parentProjectName $parentProjectName -parentReleaseNumber $parentReleaseNumber -parentEnvironmentName $approvalEnvironmentName -parentDeploymentTaskId $approvalDeploymentTaskId -autoapproveChildManualInterventions $autoapproveChildManualInterventions -approvalTenant $approvalTenant"
  },
  "Category": "Octopus",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/deploy-child-project.json",
  "Website": "/step-templates/0dac2fe6-91d5-4c05-bdfb-1b97adf1e12e",
  "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 Friday, November 17, 2023