From f89f442cbcbd8fc71b03f2c63c52c3bc810bae44 Mon Sep 17 00:00:00 2001 From: Matthew Casperson Date: Fri, 30 Oct 2026 06:02:59 +1000 Subject: [PATCH 1/3] Save the task ID as an output variable --- step-templates/deploy-child-project.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/step-templates/deploy-child-project.json b/step-templates/deploy-child-project.json index 9189e3753..7e3215013 100644 --- a/step-templates/deploy-child-project.json +++ b/step-templates/deploy-child-project.json @@ -3,13 +3,13 @@ "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.", "ActionType": "Octopus.Script", - "Version": 29, + "Version": 30, "CommunityActionTemplateId": null, "Packages": [], "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" + "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 param (\n $variableToCheck,\n $variableName\n )\n\n if ([string]::IsNullOrWhiteSpace($variableToCheck) -eq $true)\n {\n Write-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 param(\n $spaceName,\n $spaceId,\n $defaultUrl,\n $octopusApiKey\n )\n\n if ([string]::IsNullOrWhiteSpace($spaceName))\n {\n return $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 DeploymentPreviews = @(\n @{\n TenantId = $deploymentTenant.Id;\n EnvironmentId = $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 param (\n $releaseToDeploy,\n $errorHandleForNoRelease,\n $releaseNumber,\n $sourceDestinationEnvironmentInfo,\n $environmentList\n )\n\n if ($null -ne $releaseToDeploy)\n {\n return\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 $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 Write-OctopusCritical $errorMessage\n exit 1\n }\n\n Insert-EmptyOutputVariables -releaseToDeploy $null\n\n if ($errorHandleForNoRelease -eq \"Skip\")\n {\n Write-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 param (\n $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 # Output the task ID as a variable. This means subsequent steps can track the status, get the logs etc.\n Set-OctopusVariable -name \"TaskId\" -value $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 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.\"\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 $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 if ($autoapproveChildManualInterventions -eq $true)\n {\n Submit-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 if ($numberOfWaits -ge 10)\n {\n Write-OctopusSuccess \"The child project has pending manual intervention(s). Unless you approve it, this task will time out.\"\n }\n else\n {\n Write-OctopusInformation \"The child project has pending manual intervention(s). Unless you approve it, this task will time out.\"\n }\n }\n }\n\n 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 param (\n $releaseToDeploy,\n $specificMachineDeployment,\n $environmentName,\n $childDeploymentSpecificMachines,\n $project,\n $ignoreSpecificMachineMismatch,\n $deploymentMachines,\n $releaseHasAlreadyBeenDeployed,\n $isPromotionMode\n )\n\n if ($releaseHasAlreadyBeenDeployed -eq $true -and $isPromotionMode -eq $true)\n {\n 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.\"\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 Write-OctopusInformation \"The deployment task id to pull the approvers from is null, return an empty approver list\"\n return $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 param (\n $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 $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 $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 Write-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 param (\n $futureDeploymentDate\n )\n\n if ([string]::IsNullOrWhiteSpace($futureDeploymentDate) -or $futureDeploymentDate -eq \"N/A\")\n {\n return $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 Write-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 param (\n $queueDate\n )\n\n if ($null -eq $queueDate)\n {\n return $null\n }\n\n return $queueDate.AddHours(1)\n}\n\nfunction Insert-EmptyOutputVariables\n{\n param (\n $releaseToDeploy\n )\n\n if ($null -ne $releaseToDeploy)\n {\n Set-OctopusVariable -Name \"ReleaseToPromote\" -Value $($releaseToDeploy.Version)\n Set-OctopusVariable -Name \"ReleaseNotes\" -value \"Release already deployed to destination environment.\"\n }\n else\n {\n Set-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 param (\n $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 Write-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 Write-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 Write-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 if (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $parentEnvironmentId)\n {\n if ($foundApprovalFirst -eq $false)\n {\n $foundDestinationFirst = $true\n }\n }\n\n if (Test-PhaseContainsEnvironmentId -phase $phase -environmentId $approvalEnvironment.Id)\n {\n if ($foundDestinationFirst -eq $false)\n {\n $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 Write-OctopusWarning $messageToLog\n }\n else\n {\n Write-OctopusInformation $messageToLog\n }\n\n return $null\n }\n\n return $approvalTaskId\n}\n\nfunction Invoke-RefreshVariableSnapshot\n{\n param (\n $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 Write-OctopusVerbose \"Refreshing variables is set to no, skipping\"\n return\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 Write-OctopusVerbose \"Variables have not been updated since release creation, skipping\"\n return\n }\n\n if ($whatIf -eq $true)\n {\n Write-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 $trimmedMachineName = $machineName.Trim()\n Write-OctopusVerbose \"Translating $trimmedMachineName into an Octopus Id\"\n if ($trimmedMachineName -like \"Machines-*\")\n {\n Write-OctopusVerbose \"$trimmedMachineName is already an Octopus Id, adding it to the list\"\n $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 param (\n $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 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.\"\n return $true\n }\n\n if ($projectToDeploy.DefaultGuidedFailureMode -eq \"On\")\n {\n Write-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 Write-OctopusInformation \"Required values missing\"\n Exit 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 Write-OctopusSuccess \"This deployment is targeting the specific machines $specificMachines.\"\n $specificMachineDeployment = $true\n}\n\nif ([string]::IsNullOrWhiteSpace($excludeMachines) -eq $false)\n{\n Write-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{\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 Set-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 $releaseNotes | Out-File \"ReleaseNotes.txt\"\n $currentDate = Get-Date\n $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 New-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" }, "Parameters": [ { From d1d6ac1d0b8e3af86e507ce84ae55d5a74dc5c3a Mon Sep 17 00:00:00 2001 From: Matthew Casperson Date: Fri, 30 Oct 2026 06:06:05 +1000 Subject: [PATCH 2/3] feat: Update deployment metadata with new export date and Octopus version --- step-templates/deploy-child-project.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/step-templates/deploy-child-project.json b/step-templates/deploy-child-project.json index 7e3215013..5dafc8969 100644 --- a/step-templates/deploy-child-project.json +++ b/step-templates/deploy-child-project.json @@ -264,8 +264,8 @@ ], "StepPackageId": "Octopus.Script", "$Meta": { - "ExportedAt": "2023-11-17T03:06:39.613Z", - "OctopusVersion": "2023.2.13113", + "ExportedAt": "2026-10-30T06:00:00.000Z", + "OctopusVersion": "2026.4.8680", "Type": "ActionTemplate" }, "LastModifiedBy": "REOScotte", From ea185a2514d749f30e634436bff021f00fabefca Mon Sep 17 00:00:00 2001 From: Matthew Casperson Date: Fri, 30 Oct 2026 06:06:40 +1000 Subject: [PATCH 3/3] chore: Update LastModifiedBy field in deploy-child-project.json --- step-templates/deploy-child-project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/step-templates/deploy-child-project.json b/step-templates/deploy-child-project.json index 5dafc8969..521f99d03 100644 --- a/step-templates/deploy-child-project.json +++ b/step-templates/deploy-child-project.json @@ -268,6 +268,6 @@ "OctopusVersion": "2026.4.8680", "Type": "ActionTemplate" }, - "LastModifiedBy": "REOScotte", + "LastModifiedBy": "mcasperson", "Category": "octopus" }