Check VMSS Provision Status (Deployment Targets)

Octopus.AzurePowerShell exported 2021-07-26 by BobJWalker belongs to ‘Azure’ category.

Use this step when leveraging Azure Virtual Machines Scale Sets (VMSS) with Octopus Deploy Deployment Targets.

Please run this on a worker or the Octopus Server.

This step specifically targets Deployment Targets. It will:

  • Pause a runbook run or deployment until the VMSS has been provisioned
  • Pause a runbook run or deployment until all the VMs in a VMss have been provisioned
  • Reconcile the list of VMs in the VMSS with the list of VMs in Octopus Deploy. Any VMs in Octopus Deploy (based on role) not in the VMSS will be removed.
  • Set output variables of all the deployment targets found in the VMSS.

This step will set the following output variables:

  • VMSSHasServersToDeployTo: Indicates the VMSS has servers to deploy to.
  • VMSSDeploymentTargetIds: A comma-separated list of deployment target Ids you can use in later steps.
  • VMSSDeploymentTargetNames: A comma-separated list of deployment target names you can use in later steps.

Please Note: Setting the parameter Exclude Pre-Existing Servers from Output to Yes will remove any servers prior to a scale event from being returned in the output variables.

This step makes the following assumptions:

  • The name of the machine registration in Octopus matches the computer name. Like matching is supported, for example, a match will be made in the case of Octopus has p-app-server-01 and the computer name is p-app-server-01.mydomain.com.
  • You have the Azure Az PowerShell modules pre-installed on a worker or your Octopus Server.
  • This step is running in the same space/environment/tenant (optional) as the deployment targets.

Parameters

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

VMSS Name

VMSS.ScaleSet.Name =

Required

The name of the Azure Virtual Machine Scale Set

VMSS Resource Group Name

VMSS.ResourceGroup.Name =

Required

The name of the resource group where the VMSS is located.

Azure Account

VMSS.Azure.Account =

Required

The Azure Account to use when querying the VMSS.

Deployment Target Roles

VMSS.DeploymentTarget.Roles =

Required

A comma-separated list of deployment target roles to filter your deployment targets by. These roles are how this step will determine which machines to reconcile.

If you supply multiple roles, for example todo-web-server,todo-virtual-machine-scale-set the deployment targets have to be assigned todo-web-server AND todo-virtual-machine-scaleset roles for the target to be considered.

Octopus URL

VMSS.Octopus.Url = #{Octopus.Web.ServerUri}

Required

The URL of the Octopus Server to query against. Example: https://samples.octopus.app.

Octopus API Key

VMSS.Octopus.ApiKey =

Required

API Key of a service account that has permissions to:

  • Query deployment targets, environments, and tenants.
  • Query events (audit history)
  • Delete deployment targets

Assigning the service account to a team with Project Viewer and Environment Manager roles will work.

Timeout (In Minutes)

VMSS.Timeout.Value = 30

Required

How long this step will wait (in minutes) for the VMSS and VMs in the VMSS to finish being created.

Timeout Error

VMSS.Timeout.ErrorHandle = Proceed

Required

What will happen when a timeout occurs.

  • Proceed: The script will reconcile the VMs it can and then finish. No error thrown.
  • Error: Will throw an error and it will stop the deployment from proceeding.

The default is Proceed

Duplicate Run Time Allowance (in Minutes)

VMSS.Duplicate.TimeInMinutes = 3

Required

This step is designed to wait for VMSS to finish scaling out. However, Octopus isn’t aware of VMSS and may attempt to run the same deployment multiple times as new targets are “discovered.”

Typically, when this happens, a deployment is queued within a few minutes of the previous one finishing. When that happens this step will treat that as a duplicate run.

This setting indicates the number of minutes that must pass before a duplicate run is found. The default is 3 minutes.

Duplicate Run Adjustment

VMSS.Duplicate.Handle = Proceed

Required

What the step will do when a duplicate run is found. The two options are:

  • Cancel: cancel the current runbook run or deployment.
  • Proceed: continue on with the current runbook run or deployment.

The default is Proceed.

Exclude Pre-Existing Servers from Output

VMSS.OldServers.ExcludeFromOutput = No

Required

Old servers are any servers that existed prior to scaling out the VMSS.

If this step is run in a deployment target trigger it will pull back all the machines in a scale set and return them in an output variable. Depending on how you use that list, this could result in redeployment.

You can exclude pre-existing servers by setting this value to Yes. The default is No.

Script body

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

$vmssScaleSetName = $OctopusParameters["VMSS.ScaleSet.Name"]
$vmssScaleSetResourceGroup = $OctopusParameters["VMSS.ResourceGroup.Name"]
$roleToSearchFor = $OctopusParameters["VMSS.DeploymentTarget.Roles"]
$apiKey = $OctopusParameters["VMSS.Octopus.ApiKey"]
$octopusUrl = $OctopusParameters["VMSS.Octopus.Url"]
$timeoutInMinutes = $OctopusParameters["VMSS.Timeout.Value"]
$timeoutErrorHandle = $OctopusParameters["VMSS.Timeout.ErrorHandle"]
$duplicateRunDetectionInMinutes = $OctopusParameters["VMSS.Duplicate.TimeInMinutes"]
$duplicateRunHandle = $OctopusParameters["VMSS.Duplicate.Handle"]
$excludeOldServers = $OctopusParameters["VMSS.OldServers.ExcludeFromOutput"]

$octopusSpaceId = $OctopusParameters["Octopus.Space.Id"]
$octopusEnvironmentId = $OctopusParameters["Octopus.Environment.Id"]
$octopusTenantId = $OctopusParameters["Octopus.Deployment.Tenant.Id"]
$octopusDeploymentId = $OctopusParameters["Octopus.Deployment.Id"]
$octopusTriggerId = $OctopusParameters["Octopus.Deployment.Trigger.Id"]
$octopusTaskId = $OctopusParameters["Octopus.Task.Id"]
$octopusRunbookRunId = $OctopusParameters["Octopus.RunbookRun.Id"]

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

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

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

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

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

        return $result

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

    Throw "There was an error calling the Octopus API."
}

function Get-QueuedEventInfo
{
    param (
        $octopusRunbookRunId,
        $octopusDeploymentId,
        $octopusSpaceId,
        $octopusUrl,
        $apiKey
    )

    if ([string]::IsNullOrWhiteSpace($octopusRunbookRunId))
    {
        $queuedListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=Deployments&eventCategories=DeploymentQueued" -spaceId $null -apiKey $apiKey -method "GET"
    }
    else
    {
        $queuedListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "events?regardingAny=$($octopusRunbookRunId)&spaces=$($octopusSpaceId)&documentTypes=RunbookRuns&eventCategories=RunbookRunQueued" -spaceId $null -apiKey $apiKey -method "GET"
    }

    $queuedArray = @($queuedListRaw.Items)

    return @{
        CurrentDeploymentQueued = [DateTime]$queuedArray[0].Occurred
        NumberOfQueuedEvents = $queuedArray.Length
    }
}

function Get-CompletedEventInfo
{
    param (
        $octopusRunbookRunId,
        $octopusDeploymentId,
        $octopusSpaceId,
        $octopusUrl,
        $apiKey
    )

    if ([string]::IsNullOrWhiteSpace($octopusRunbookRunId))
    {      
        $finishedEventListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=Deployments&eventCategories=DeploymentSucceeded,DeploymentFailed&skip=0&take=1" -spaceId $null -apiKey $apiKey -method "GET"
    }
    else
    {
        $finishedEventListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=RunbookRuns&eventCategories=RunbookRunSucceeded,RunbookRunFailed&skip=0&take=1" -spaceId $null -apiKey $apiKey -method "GET"    
    }

    $finishedEventArray = @($finishedEventListRaw.Items)
        
    return [DateTime]$finishedEventArray[0].Occurred
}

function Test-ForDuplicateRun 
{
    param (
        $octopusRunbookRunId,
        $octopusDeploymentId,
        $octopusSpaceId,
        $queuedEventInfo,
        $duplicateRunDetectionInMinutes,
        $duplicateRunHandle,
        $octopusTaskId,
        $octopusUrl,
        $apiKey        
    )

    Write-Host "Checking to see if this current run is a duplicate because of deployment target triggers"
    $duplicateRun = $false

    if ([string]::IsNullOrWhiteSpace($octopusTriggerId) -eq $false)
    {
        Write-Highlight "This run was triggered by a trigger."
        
        Write-Host "The number of items in the queued array is: $($queuedEventInfo.NumberOfQueuedEvents)"
        if ($queuedEventInfo.NumberOfQueuedEvents -gt 1)
        {
            Write-Host "This task has been run before"  

            $previousDeploymentFinished = Get-CompletedEventInfo -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId -octopusUrl $octopusUrl -apiKey $apiKey            
            Write-Host "The current deployment was queued $($queuedEventInfo.CurrentDeploymentQueued) while the previous deployment was finished $previousDeploymentFinished"
            
            $queuedCompletedDifference = $queuedEventInfo.CurrentDeploymentQueued - $previousDeploymentFinished            
            Write-Host "The difference in minutes is $($queuedCompletedDifference.TotalMinutes)"

            if ($queuedCompletedDifference.TotalMinutes -le $duplicateRunDetectionInMinutes)
            {
                Write-Highlight "The previous deployment finished in the last $($queuedCompletedDifference.TotalMinutes) minutes before this was trigger, that is extremely fast.  This is a duplicate run."
                $duplicateRun = $true
                
                if ($duplicateRunHandle.ToLower().Trim() -eq "cancel")
                {
                    Write-Highlight "The duplicate run handle is set to cancel, cancelling current deployment."
                    Invoke-OctopusApi -octopusUrl $octopusUrl -apiKey $apiKey -spaceId $OctopusSpaceId -method "POST" -endPoint "tasks/$($octopusTaskId)/cancel"
                    exit 0    
                }
                else
                {
                    Write-Highlight "The duplicate run handle is set to proceed."
                }
            }
            else
            {
                Write-Highlight "The last deployment finished and this one was queued after $($queuedCompletedDifference.TotalMinutes) minutes has passed which is outside the window of $duplicateRunDetectionInMinutes minutes.  Not a duplicate."
            }
        }	    
        else
        {
            Write-Highlight "This is the first time this release has been deployed to this environment.  This is not a duplicate run."
        }
    }

    return $duplicateRun
}

function Start-WaitForVMSSToFinishProvisioning
{
    param 
    (
        $vmssScaleSetResourceGroup,
        $vmssScaleSetName,
        $timeoutInMinutes
    )

    $vmssState = "Provisioning"
    $startTime = Get-Date

    Write-Host "Will now wait until the VMSS has finished provisioning."

    do
    {
        try
        {
            $vmssInfo = Get-AzVmss -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName
        }
        catch
        {
            Write-Highlight "Unable to access the scale set $vmssScaleSetName.  Exiting step."
            Write-Host $_.Exception
            exit 0
        }

        Write-Verbose "VMSSInfo: "
        Write-Verbose ($vmssInfo | ConvertTo-JSON -Depth 10)

        $vmssInstanceCount = $vmssInfo.Sku.Capacity
        $vmssState = $vmssInfo.ProvisioningState

        if($vmssState.ToLower().Trim() -ne "provisioning")
        {    
            Write-Highlight "The VMSS $vmssScaleSetName capacity is current set to $vmssInstanceCount with a provisioning state of $vmssState"
        }
        else
        {
            Write-Host "The VMSS is still provisioning, sleeping for 10 seconds then checking again."
            Start-Sleep -Seconds 10
        }
        
        $currentTime = Get-Date
        $dateDifference = $currentTime - $startTime
        
        if ($dateDifference.TotalMinutes -ge $timeoutInMinutes)
        {
            Write-Highlight "We have been waiting $($dateDifference.TotalMinutes) for the VMSS to finish provisioning.  Timeout reached, exiting."
            exit 1
        }   
        
    } While ($vmssState.ToLower().Trim() -eq "provisioning")
}

function Start-WaitForVMsInVMSSToFinishProvisioning
{
    param 
    (
        $vmssScaleSetResourceGroup,
        $vmssScaleSetName,
        $timeoutInMinutes,
        $timeoutErrorHandle
    )

    $vmssVmsAreProvisioning = $false
    $startTime = Get-Date
    $numberOfWaits = 0
    $printVmssVmList = $true

    Write-Highlight "Checking the state of all VMs in the scale set."

    do
    {
        $numberOfWaits += 1
        $vmssVmList = Get-AzVmssVM -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName
        
        if ($printVmssVmList -eq $true)
        {
            Write-Host ($vmssVmList | ConvertTo-Json -Depth 10)
            $printVmssVmList = $false
        }
        
        $vmssVmsAreProvisioning = $false
        foreach ($vmInfo in $vmssVmList)
        {
            if ($vmInfo.ProvisioningState.ToLower().Trim() -eq "creating")
            {
                $vmssVmsAreProvisioning = $true
                break
            }
        }
        
        if ($vmssVmsAreProvisioning -eq $true)
        {        
            $currentTime = Get-Date
            $dateDifference = $currentTime - $startTime

            if ($dateDifference.TotalMinutes -ge $timeoutInMinutes)
            {
                $vmssVmsAreProvisioning = $false
                if ($timeoutErrorHandle.ToLower().Trim() -eq "error")
                {
                    Write-Highlight "The VMs in the scale have been provisioning for over $timeoutInMinutes.  Error handle is set to error out, exiting with an exit code of 1."
                    exit 1
                }
                
                Write-Highlight "The VMs in the scale have been provisioning for over $timeoutInMinutes.  Going to move on and continue with the deployment for any VMs that have finished provisioning."
            } 
            else
            {
                if ($numberofWaits -ge 10)
                {
                    Write-Highlight "The VMs are still currently provisioning, waiting..."
                    $numberOfWaits = 0
                }
                else
                {            
                    Write-Host "The VMs are still currently provisioning, sleeping for 10 seconds then checking again."
                }
                Start-Sleep -Seconds 10
            }
        }
        else
        {
            Write-Highlight "All the VMs in the VM Scale Set have been provisioned, reconciling them with the list in Octopus."
        }         
    } while ($vmssVmsAreProvisioning -eq $true)
}


Write-Host "ScaleSet Name: $vmssScaleSetName"
Write-Host "Resource Group Name: $vmssScaleSetResourceGroup"
Write-Host "Deployment Target Role to Search For: $roleToSearchFor"
Write-Host "Octopus Url: $octopusUrl"
Write-Host "Timeout In Minutes: $timeoutInMinutes"
Write-Host "Timeout Error Handle: $timeoutErrorHandle"
Write-Host "Duplicate Run Detection in Minutes: $duplicateRunDetectionInMinutes"
Write-Host "Duplicate Run Handle: $duplicateRunHandle"
Write-host "Exclude Old Servers: $excludeOldServers"

Write-Host "Space Id: $octopusSpaceId"
Write-Host "Environment Id: $octopusEnvironmentId"
Write-Host "Tenant Id: $octopusTenantId"
Write-Host "Deployment Id: $octopusDeploymentId"
Write-Host "Trigger Id: $octopusTriggerId"
Write-Host "Task Id: $octopusTaskId"
Write-Host "Runbook Run Id: $octopusRunbookRunId"

if ([string]::IsNullOrWhiteSpace($vmssScaleSetName)) { Write-Error "Scale Set Name is required." }
if ([string]::IsNullOrWhiteSpace($vmssScaleSetResourceGroup)) { Write-Error "Resource Group Name is required." }
if ([string]::IsNullOrWhiteSpace($roleToSearchFor)) { Write-Error "Scale Set Name is required." }
if ([string]::IsNullOrWhiteSpace($octopusUrl)) { Write-Error "Octopus Url is required." }
if ([string]::IsNullOrWhiteSpace($apiKey)) { Write-Error "Octopus Api Key is required." }
if ([string]::IsNullOrWhiteSpace($timeoutInMinutes)) { Write-Error "Timeout in minutes is required." }
if ([string]::IsNullOrWhiteSpace($timeoutErrorHandle)) { Write-Error "Timeout error handle is required." }
if ([string]::IsNullOrWhiteSpace($duplicateRunDetectionInMinutes)) { Write-Error "Duplicate run detection in minutes is required." }
if ([string]::IsNullOrWhiteSpace($duplicateRunHandle)) { Write-Error "Duplicate run handle is required." }
if ([string]::IsNullOrWhiteSpace($excludeOldServers)) { Write-Error "Exclude old servers is required." }

$queuedEventInfo = Get-QueuedEventInfo -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId -octopusUrl $octopusUrl -apiKey $apiKey

Write-Host "The current deployment was queued at: $($queuedEventInfo.CurrentDeploymentQueued)"

$duplicateRun = Test-ForDuplicateRun -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId  -duplicateRunDetectionInMinutes $duplicateRunDetectionInMinutes -duplicateRunHandle $duplicateRunHandle -queuedEventInfo $queuedEventInfo -octopusTaskId $octopusTaskId -octopusUrl $octopusUrl -apiKey $apiKey

Start-WaitForVMSSToFinishProvisioning -vmssScaleSetResourceGroup $vmssScaleSetResourceGroup -vmssScaleSetName $vmssScaleSetName -timeoutInMinutes $timeoutInMinutes
Start-WaitForVMsInVMSSToFinishProvisioning -vmssScaleSetResourceGroup $vmssScaleSetResourceGroup -vmssScaleSetName $vmssScaleSetName -timeoutInMinutes $timeoutInMinutes -timeoutErrorHandle $timeoutErrorHandle

$vmssVmList = Get-AzVmssVM -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName
$vmListToReconcile = @()

foreach ($vmInfo in $vmssVmList)
{
	if ($vmInfo.ProvisioningState.ToLower().Trim() -ne "failed")
    {
    	$vmListToReconcile += $vmInfo.OsProfile.ComputerName
    }
}

$octopusDeployTargets = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "machines?environmentIds=$($octopusEnvironmentId)&roles=$($roleToSearchFor)&skip=0&take=1000" -spaceId $octopusSpaceId -apiKey $apiKey -method "GET"
$octopusDeployTargetIds = @()
$octopusDeployTargetNames = @()
$roleList = $roleToSearchFor.Split(",")
foreach ($deploymentTarget in $octopusDeployTargets.Items)
{
	$matchingRole = $true
    foreach ($role in $roleList)
    {
    	if ($deploymentTarget.Roles -notContains ($role.Trim()))
        {
        	Write-Host "The target $($deploymentTarget.Name) does not contain the role $role.  To be considered part of the scale set it has to be assigned to all the roles $roleToSearchFor.  Excluding from reconcilation logic."
            $matchingRole = $false
            break
        }
    }
    
    if ($matchingRole -eq $false)
    {
    	continue
    }
    
    if ([string]::IsNullOrWhiteSpace($octopusTenantId) -eq $false -and $deploymentTarget.TenantIds -notcontains $octopusTenantId)
    {
    	Write-Host "The target $($deploymentTarget.Name) is not assigned to $octopusTenantId.  But the current run is running under the context of that tenant.  Excluding from reconcilation logic."
        continue
    }
    
    $hasMatchingName = $false
    $deploymentTargetNameLowerTrim = $deploymentTarget.Name.ToLower().Trim()

    Write-Host "Attempting to do a match on name"
	foreach ($vmssVM in $vmListToReconcile)
    {
    	$vmssVMLowerTrim = $vmssVM.ToLower().Trim()

        Write-Host "Checking to see if $($deploymentTarget.Name) is like $vmssVM"
        if ($deploymentTargetNameLowerTrim -eq $vmssVMLowerTrim)
        {
            Write-Host "The vmss vm name $vmssVM is equal to to the deployment target name $($deploymentTarget.Name), set matching to true"         
            $hasMatchingName = $true
            break
        }
        if ($deploymentTargetNameLowerTrim -like "*$vmssVMLowerTrim*")
        {   
            Write-Host "The deployment target name $($deploymentTarget.Name) contains the vmss vm name $vmssVM, set matching to true"         
            $hasMatchingName = $true
            break
        }
        elseif ($vmssVMLowerTrim -like "*$deploymentTargetNameLowerTrim*")
        {
            Write-Host "The vmss vm name $vmssVM contains the deployment target name $($deploymentTarget.Name), set matching to true"
            $hasMatchingName = $true
            break
        }
    }
    
    if ($hasMatchingName -eq $false)
    {
    	Write-Highlight "The deployment target $($deploymentTarget.Name) is not in the list of VMs assigned to the scale set, deleting it."
        Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "machines/$($deploymentTarget.Id)" -spaceId $octopusSpaceId -apiKey $apiKey -method "DELETE"
    }
    else
    {
    	Write-Highlight "The deployment target $($deploymentTarget.Name) is in the list of VMS assigned to the scale set, leaving it alone."
        
        $addToOutputArray = $true
        if ($excludeOldServers.ToLower().Trim() -eq "yes")
        {
        	Write-Host "Pulling back the creation event for $($deploymentTarget.Name)"
        	$creationTasks = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint "events?regarding=$($deploymentTarget.Id)&spaces=$($octopusSpaceId)&includeSystem=true&eventCategories=Created" -spaceId $null -apiKey $apiKey -method "GET"
            
            $creationDate = @($creationTasks.Items)[0].Occurred
            Write-Host "The deployment target $($deploymentTarget.Name) was created on $creationDate"
            $differenceInCreationTime = [DateTime]$queuedEventInfo.CurrentDeploymentQueued - [DateTime]$creationDate
            
            Write-Host "The difference in minutes between creation date and current task queued time is $($differenceInCreationTime.TotalMinutes) minutes" 
            if ($differenceInCreationTime.TotalMinutes -gt 3)
            {
            	Write-Host "The deployment target $($deploymentTarget.Name) existed for more than 3 minutes before this was ran and the excludeOldServers was set to yes, removing this from the output"
            	$addToOutputArray = $false
            }
        }
        
        if ($addToOutputArray -eq $true)
        {
	        $octopusDeployTargetIds += $deploymentTarget.Id
    	    $octopusDeployTargetNames += $deploymentTarget.Name
        }
    }
}

Write-Highlight "The Azure VM Scale Set $vmssScaleSetName and Octopus Deploy target list have been successfully reconciled."

$vmssHasServersToDeployTo = $octopusDeployTargetIds.Count -gt 0
if ($duplicateRun -eq $true)
{
	Write-Highlight "Duplicate run detected, therefore there are no new servers to deploy to."
    $vmssHasServersToDeployTo = $false
}
elseif ($vmssHasServersToDeployTo -eq $false)
{
	Write-Highlight "There are no servers to deploy to.  Exclude old servers was set to '$excludeOldServers'.  This likely means this was a scale in event or all the servers existed prior to this run."
}

Write-Highlight "Setting the output variable 'VMSSHasServersToDeployTo' to $vmssHasServersToDeployTo."
Set-OctopusVariable -Name "VMSSHasServersToDeployTo" -Value $vmssHasServersToDeployTo

Write-Highlight "Setting the output variable 'VMSSDeploymentTargetIds' to $($octopusDeployTargetIds -join ",")."
Set-OctopusVariable -Name "VMSSDeploymentTargetIds" -Value ($octopusDeployTargetIds -join ",")

Write-Highlight "Setting the output variable 'VMSSDeploymentTargetNames' to $($octopusDeployTargetNames -join ",")."
Set-OctopusVariable -Name "VMSSDeploymentTargetNames" -Value ($octopusDeployTargetNames -join ",")

Provided under the Apache License version 2.0.

Report an issue

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

{
  "Id": "e04c5cd8-0982-44b8-9cae-0a4b43676adc",
  "Name": "Check VMSS Provision Status (Deployment Targets)",
  "Description": "Use this step when leveraging Azure Virtual Machines Scale Sets (VMSS) with Octopus Deploy Deployment Targets.\n\n**Please run this on a worker or the Octopus Server.**\n\nThis step specifically targets Deployment Targets.  It will:\n- Pause a runbook run or deployment until the VMSS has been provisioned\n- Pause a runbook run or deployment until all the VMs in a VMss have been provisioned\n- Reconcile the list of VMs in the VMSS with the list of VMs in Octopus Deploy.  Any VMs in Octopus Deploy (based on role) not in the VMSS will be removed.\n- Set output variables of all the deployment targets found in the VMSS.\n\nThis step will set the following output variables:\n- `VMSSHasServersToDeployTo`: Indicates the VMSS has servers to deploy to.  \n- `VMSSDeploymentTargetIds`: A comma-separated list of deployment target Ids you can use in later steps.\n- `VMSSDeploymentTargetNames`: A comma-separated list of deployment target names you can use in later steps.\n\n**Please Note**: Setting the parameter `Exclude Pre-Existing Servers from Output` to `Yes` will remove any servers prior to a scale event from being returned in the output variables. \n\nThis step makes the following assumptions:\n- The name of the machine registration in Octopus matches the computer name.  Like matching is supported, for example, a match will be made in the case of Octopus has `p-app-server-01` and the computer name is `p-app-server-01.mydomain.com.`  \n- You have the Azure Az PowerShell modules pre-installed on a worker or your Octopus Server.\n- This step is running in the same space/environment/tenant (optional) as the deployment targets.",
  "Version": 3,
  "ExportedAt": "2021-07-26T19:15:02.718Z",
  "ActionType": "Octopus.AzurePowerShell",
  "Author": "BobJWalker",
  "Packages": [],
  "Parameters": [
    {
      "Id": "9f9bc9fb-fa96-41ab-aa06-ad0dd68ff038",
      "Name": "VMSS.ScaleSet.Name",
      "Label": "VMSS Name",
      "HelpText": "**Required**\n\nThe name of the Azure Virtual Machine Scale Set",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "32939873-a965-46ae-915f-f94d93f94c0b",
      "Name": "VMSS.ResourceGroup.Name",
      "Label": "VMSS Resource Group Name",
      "HelpText": "**Required**\n\nThe name of the resource group where the VMSS is located.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "571df393-6353-4f12-96cd-c92f3a5e1452",
      "Name": "VMSS.Azure.Account",
      "Label": "Azure Account",
      "HelpText": "**Required**\n\nThe Azure Account to use when querying the VMSS.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "AzureAccount"
      }
    },
    {
      "Id": "b03d1bf3-76f8-4a3c-917a-60d5e19a15f4",
      "Name": "VMSS.DeploymentTarget.Roles",
      "Label": "Deployment Target Roles",
      "HelpText": "**Required**\n\nA comma-separated list of deployment target roles to filter your deployment targets by.  These roles are how this step will determine which machines to reconcile.\n\nIf you supply multiple roles, for example `todo-web-server,todo-virtual-machine-scale-set` the deployment targets have to be assigned `todo-web-server` AND `todo-virtual-machine-scaleset` roles for the target to be considered.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "757ca61b-d15b-4dbb-911a-b3f4dc0258b5",
      "Name": "VMSS.Octopus.Url",
      "Label": "Octopus URL",
      "HelpText": "**Required**\n\nThe URL of the Octopus Server to query against.  Example: `https://samples.octopus.app`.",
      "DefaultValue": "#{Octopus.Web.ServerUri}",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "7b070182-e79b-48a0-966a-39d332802b60",
      "Name": "VMSS.Octopus.ApiKey",
      "Label": "Octopus API Key",
      "HelpText": "**Required**\n\nAPI Key of a service account that has permissions to:\n- Query deployment targets, environments, and tenants. \n- Query events (audit history) \n- Delete deployment targets\n\nAssigning the service account to a team with `Project Viewer` and `Environment Manager` roles will work.",
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "Sensitive"
      }
    },
    {
      "Id": "6e0f8635-70ba-4b8a-b5b4-909dba3f9fd8",
      "Name": "VMSS.Timeout.Value",
      "Label": "Timeout (In Minutes)",
      "HelpText": "**Required**\n\nHow long this step will wait (in minutes) for the VMSS and VMs in the VMSS to finish being created.",
      "DefaultValue": "30",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "0392f153-73b0-44d4-9181-4f366c9012f7",
      "Name": "VMSS.Timeout.ErrorHandle",
      "Label": "Timeout Error",
      "HelpText": "**Required**\n\nWhat will happen when a timeout occurs.\n\n- `Proceed`: The script will reconcile the VMs it can and then finish.  No error thrown.  \n- `Error`: Will throw an error and it will stop the deployment from proceeding.\n\nThe default is `Proceed`",
      "DefaultValue": "Proceed",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Proceed|Proceed\nError|Error"
      }
    },
    {
      "Id": "80ef4fb9-c773-4d0a-82af-efdfb302e58f",
      "Name": "VMSS.Duplicate.TimeInMinutes",
      "Label": "Duplicate Run Time Allowance (in Minutes)",
      "HelpText": "**Required**\n\nThis step is designed to wait for VMSS to finish scaling out.  However, Octopus isn't aware of VMSS and may attempt to run the same deployment multiple times as new targets are \"discovered.\"\n\nTypically, when this happens, a deployment is queued within a few minutes of the previous one finishing.  When that happens this step will treat that as a duplicate run.  \n\nThis setting indicates the number of minutes that must pass before a duplicate run is found.  The default is `3` minutes.",
      "DefaultValue": "3",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      }
    },
    {
      "Id": "0eb3bed8-9259-440e-bff0-cb86af7da52e",
      "Name": "VMSS.Duplicate.Handle",
      "Label": "Duplicate Run Adjustment",
      "HelpText": "**Required**\n\nWhat the step will do when a duplicate run is found.  The two options are:\n\n- `Cancel`: cancel the current runbook run or deployment.\n- `Proceed`: continue on with the current runbook run or deployment.\n\nThe default is `Proceed`.",
      "DefaultValue": "Proceed",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "Cancel|Cancel\nProceed|Proceed"
      }
    },
    {
      "Id": "adaf6a8c-453f-4b69-9ba6-f6a54b4f5bb9",
      "Name": "VMSS.OldServers.ExcludeFromOutput",
      "Label": "Exclude Pre-Existing Servers from Output",
      "HelpText": "**Required**\n\nOld servers are any servers that existed prior to scaling out the VMSS.  \n\nIf this step is run in a deployment target trigger it will pull back all the machines in a scale set and return them in an output variable.  Depending on how you use that list, this could result in redeployment.\n\nYou can exclude pre-existing servers by setting this value to `Yes`.  The default is `No`.",
      "DefaultValue": "No",
      "DisplaySettings": {
        "Octopus.ControlType": "Select",
        "Octopus.SelectOptions": "No|No\nYes|Yes"
      }
    }
  ],
  "Properties": {
    "OctopusUseBundledTooling": "False",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Azure.AccountId": "#{VMSS.Azure.Account}",
    "Octopus.Action.Script.ScriptBody": "$vmssScaleSetName = $OctopusParameters[\"VMSS.ScaleSet.Name\"]\n$vmssScaleSetResourceGroup = $OctopusParameters[\"VMSS.ResourceGroup.Name\"]\n$roleToSearchFor = $OctopusParameters[\"VMSS.DeploymentTarget.Roles\"]\n$apiKey = $OctopusParameters[\"VMSS.Octopus.ApiKey\"]\n$octopusUrl = $OctopusParameters[\"VMSS.Octopus.Url\"]\n$timeoutInMinutes = $OctopusParameters[\"VMSS.Timeout.Value\"]\n$timeoutErrorHandle = $OctopusParameters[\"VMSS.Timeout.ErrorHandle\"]\n$duplicateRunDetectionInMinutes = $OctopusParameters[\"VMSS.Duplicate.TimeInMinutes\"]\n$duplicateRunHandle = $OctopusParameters[\"VMSS.Duplicate.Handle\"]\n$excludeOldServers = $OctopusParameters[\"VMSS.OldServers.ExcludeFromOutput\"]\n\n$octopusSpaceId = $OctopusParameters[\"Octopus.Space.Id\"]\n$octopusEnvironmentId = $OctopusParameters[\"Octopus.Environment.Id\"]\n$octopusTenantId = $OctopusParameters[\"Octopus.Deployment.Tenant.Id\"]\n$octopusDeploymentId = $OctopusParameters[\"Octopus.Deployment.Id\"]\n$octopusTriggerId = $OctopusParameters[\"Octopus.Deployment.Trigger.Id\"]\n$octopusTaskId = $OctopusParameters[\"Octopus.Task.Id\"]\n$octopusRunbookRunId = $OctopusParameters[\"Octopus.RunbookRun.Id\"]\n\nfunction Invoke-OctopusApi\n{\n    param\n    (\n        $octopusUrl,\n        $endPoint,\n        $spaceId,\n        $apiKey,\n        $method,\n        $item  \n    )\n\n    if ([string]::IsNullOrWhiteSpace($SpaceId))\n    {\n        $url = \"$OctopusUrl/api/$EndPoint\"\n    }\n    else\n    {\n        $url = \"$OctopusUrl/api/$spaceId/$EndPoint\"    \n    }  \n\n    try\n    {        \n        if ($null -ne $item)\n        {\n            $body = $item | ConvertTo-Json -Depth 10\n            Write-Verbose $body\n\n            Write-Host \"Invoking $method $url\"\n            return Invoke-RestMethod -Method $method -Uri $url -Headers @{\"X-Octopus-ApiKey\" = \"$ApiKey\" } -Body $body -ContentType 'application/json; charset=utf-8' \n        }\n\n\t\tWrite-Verbose \"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        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-Error \"Unauthorized error returned from $url, please verify API key and try again\"\n            }\n            elseif ($_.Exception.Response.statusCode -eq 403)\n            {\n                Write-Error \"Forbidden error returned from $url, please verify API key and try again\"\n            }\n            else\n            {                \n                Write-Verbose -Message \"Error calling $url $($_.Exception.Message) StatusCode: $($_.Exception.Response.StatusCode )\"\n            }            \n        }\n        else\n        {\n            Write-Verbose $_.Exception\n        }\n    }\n\n    Throw \"There was an error calling the Octopus API.\"\n}\n\nfunction Get-QueuedEventInfo\n{\n    param (\n        $octopusRunbookRunId,\n        $octopusDeploymentId,\n        $octopusSpaceId,\n        $octopusUrl,\n        $apiKey\n    )\n\n    if ([string]::IsNullOrWhiteSpace($octopusRunbookRunId))\n    {\n        $queuedListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=Deployments&eventCategories=DeploymentQueued\" -spaceId $null -apiKey $apiKey -method \"GET\"\n    }\n    else\n    {\n        $queuedListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"events?regardingAny=$($octopusRunbookRunId)&spaces=$($octopusSpaceId)&documentTypes=RunbookRuns&eventCategories=RunbookRunQueued\" -spaceId $null -apiKey $apiKey -method \"GET\"\n    }\n\n    $queuedArray = @($queuedListRaw.Items)\n\n    return @{\n        CurrentDeploymentQueued = [DateTime]$queuedArray[0].Occurred\n        NumberOfQueuedEvents = $queuedArray.Length\n    }\n}\n\nfunction Get-CompletedEventInfo\n{\n    param (\n        $octopusRunbookRunId,\n        $octopusDeploymentId,\n        $octopusSpaceId,\n        $octopusUrl,\n        $apiKey\n    )\n\n    if ([string]::IsNullOrWhiteSpace($octopusRunbookRunId))\n    {      \n        $finishedEventListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=Deployments&eventCategories=DeploymentSucceeded,DeploymentFailed&skip=0&take=1\" -spaceId $null -apiKey $apiKey -method \"GET\"\n    }\n    else\n    {\n        $finishedEventListRaw = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"events?regardingAny=$($OctopusDeploymentId)&spaces=$($octopusSpaceId)&documentTypes=RunbookRuns&eventCategories=RunbookRunSucceeded,RunbookRunFailed&skip=0&take=1\" -spaceId $null -apiKey $apiKey -method \"GET\"    \n    }\n\n    $finishedEventArray = @($finishedEventListRaw.Items)\n        \n    return [DateTime]$finishedEventArray[0].Occurred\n}\n\nfunction Test-ForDuplicateRun \n{\n    param (\n        $octopusRunbookRunId,\n        $octopusDeploymentId,\n        $octopusSpaceId,\n        $queuedEventInfo,\n        $duplicateRunDetectionInMinutes,\n        $duplicateRunHandle,\n        $octopusTaskId,\n        $octopusUrl,\n        $apiKey        \n    )\n\n    Write-Host \"Checking to see if this current run is a duplicate because of deployment target triggers\"\n    $duplicateRun = $false\n\n    if ([string]::IsNullOrWhiteSpace($octopusTriggerId) -eq $false)\n    {\n        Write-Highlight \"This run was triggered by a trigger.\"\n        \n        Write-Host \"The number of items in the queued array is: $($queuedEventInfo.NumberOfQueuedEvents)\"\n        if ($queuedEventInfo.NumberOfQueuedEvents -gt 1)\n        {\n            Write-Host \"This task has been run before\"  \n\n            $previousDeploymentFinished = Get-CompletedEventInfo -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId -octopusUrl $octopusUrl -apiKey $apiKey            \n            Write-Host \"The current deployment was queued $($queuedEventInfo.CurrentDeploymentQueued) while the previous deployment was finished $previousDeploymentFinished\"\n            \n            $queuedCompletedDifference = $queuedEventInfo.CurrentDeploymentQueued - $previousDeploymentFinished            \n            Write-Host \"The difference in minutes is $($queuedCompletedDifference.TotalMinutes)\"\n\n            if ($queuedCompletedDifference.TotalMinutes -le $duplicateRunDetectionInMinutes)\n            {\n                Write-Highlight \"The previous deployment finished in the last $($queuedCompletedDifference.TotalMinutes) minutes before this was trigger, that is extremely fast.  This is a duplicate run.\"\n                $duplicateRun = $true\n                \n                if ($duplicateRunHandle.ToLower().Trim() -eq \"cancel\")\n                {\n                    Write-Highlight \"The duplicate run handle is set to cancel, cancelling current deployment.\"\n                    Invoke-OctopusApi -octopusUrl $octopusUrl -apiKey $apiKey -spaceId $OctopusSpaceId -method \"POST\" -endPoint \"tasks/$($octopusTaskId)/cancel\"\n                    exit 0    \n                }\n                else\n                {\n                    Write-Highlight \"The duplicate run handle is set to proceed.\"\n                }\n            }\n            else\n            {\n                Write-Highlight \"The last deployment finished and this one was queued after $($queuedCompletedDifference.TotalMinutes) minutes has passed which is outside the window of $duplicateRunDetectionInMinutes minutes.  Not a duplicate.\"\n            }\n        }\t    \n        else\n        {\n            Write-Highlight \"This is the first time this release has been deployed to this environment.  This is not a duplicate run.\"\n        }\n    }\n\n    return $duplicateRun\n}\n\nfunction Start-WaitForVMSSToFinishProvisioning\n{\n    param \n    (\n        $vmssScaleSetResourceGroup,\n        $vmssScaleSetName,\n        $timeoutInMinutes\n    )\n\n    $vmssState = \"Provisioning\"\n    $startTime = Get-Date\n\n    Write-Host \"Will now wait until the VMSS has finished provisioning.\"\n\n    do\n    {\n        try\n        {\n            $vmssInfo = Get-AzVmss -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName\n        }\n        catch\n        {\n            Write-Highlight \"Unable to access the scale set $vmssScaleSetName.  Exiting step.\"\n            Write-Host $_.Exception\n            exit 0\n        }\n\n        Write-Verbose \"VMSSInfo: \"\n        Write-Verbose ($vmssInfo | ConvertTo-JSON -Depth 10)\n\n        $vmssInstanceCount = $vmssInfo.Sku.Capacity\n        $vmssState = $vmssInfo.ProvisioningState\n\n        if($vmssState.ToLower().Trim() -ne \"provisioning\")\n        {    \n            Write-Highlight \"The VMSS $vmssScaleSetName capacity is current set to $vmssInstanceCount with a provisioning state of $vmssState\"\n        }\n        else\n        {\n            Write-Host \"The VMSS is still provisioning, sleeping for 10 seconds then checking again.\"\n            Start-Sleep -Seconds 10\n        }\n        \n        $currentTime = Get-Date\n        $dateDifference = $currentTime - $startTime\n        \n        if ($dateDifference.TotalMinutes -ge $timeoutInMinutes)\n        {\n            Write-Highlight \"We have been waiting $($dateDifference.TotalMinutes) for the VMSS to finish provisioning.  Timeout reached, exiting.\"\n            exit 1\n        }   \n        \n    } While ($vmssState.ToLower().Trim() -eq \"provisioning\")\n}\n\nfunction Start-WaitForVMsInVMSSToFinishProvisioning\n{\n    param \n    (\n        $vmssScaleSetResourceGroup,\n        $vmssScaleSetName,\n        $timeoutInMinutes,\n        $timeoutErrorHandle\n    )\n\n    $vmssVmsAreProvisioning = $false\n    $startTime = Get-Date\n    $numberOfWaits = 0\n    $printVmssVmList = $true\n\n    Write-Highlight \"Checking the state of all VMs in the scale set.\"\n\n    do\n    {\n        $numberOfWaits += 1\n        $vmssVmList = Get-AzVmssVM -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName\n        \n        if ($printVmssVmList -eq $true)\n        {\n            Write-Host ($vmssVmList | ConvertTo-Json -Depth 10)\n            $printVmssVmList = $false\n        }\n        \n        $vmssVmsAreProvisioning = $false\n        foreach ($vmInfo in $vmssVmList)\n        {\n            if ($vmInfo.ProvisioningState.ToLower().Trim() -eq \"creating\")\n            {\n                $vmssVmsAreProvisioning = $true\n                break\n            }\n        }\n        \n        if ($vmssVmsAreProvisioning -eq $true)\n        {        \n            $currentTime = Get-Date\n            $dateDifference = $currentTime - $startTime\n\n            if ($dateDifference.TotalMinutes -ge $timeoutInMinutes)\n            {\n                $vmssVmsAreProvisioning = $false\n                if ($timeoutErrorHandle.ToLower().Trim() -eq \"error\")\n                {\n                    Write-Highlight \"The VMs in the scale have been provisioning for over $timeoutInMinutes.  Error handle is set to error out, exiting with an exit code of 1.\"\n                    exit 1\n                }\n                \n                Write-Highlight \"The VMs in the scale have been provisioning for over $timeoutInMinutes.  Going to move on and continue with the deployment for any VMs that have finished provisioning.\"\n            } \n            else\n            {\n                if ($numberofWaits -ge 10)\n                {\n                    Write-Highlight \"The VMs are still currently provisioning, waiting...\"\n                    $numberOfWaits = 0\n                }\n                else\n                {            \n                    Write-Host \"The VMs are still currently provisioning, sleeping for 10 seconds then checking again.\"\n                }\n                Start-Sleep -Seconds 10\n            }\n        }\n        else\n        {\n            Write-Highlight \"All the VMs in the VM Scale Set have been provisioned, reconciling them with the list in Octopus.\"\n        }         \n    } while ($vmssVmsAreProvisioning -eq $true)\n}\n\n\nWrite-Host \"ScaleSet Name: $vmssScaleSetName\"\nWrite-Host \"Resource Group Name: $vmssScaleSetResourceGroup\"\nWrite-Host \"Deployment Target Role to Search For: $roleToSearchFor\"\nWrite-Host \"Octopus Url: $octopusUrl\"\nWrite-Host \"Timeout In Minutes: $timeoutInMinutes\"\nWrite-Host \"Timeout Error Handle: $timeoutErrorHandle\"\nWrite-Host \"Duplicate Run Detection in Minutes: $duplicateRunDetectionInMinutes\"\nWrite-Host \"Duplicate Run Handle: $duplicateRunHandle\"\nWrite-host \"Exclude Old Servers: $excludeOldServers\"\n\nWrite-Host \"Space Id: $octopusSpaceId\"\nWrite-Host \"Environment Id: $octopusEnvironmentId\"\nWrite-Host \"Tenant Id: $octopusTenantId\"\nWrite-Host \"Deployment Id: $octopusDeploymentId\"\nWrite-Host \"Trigger Id: $octopusTriggerId\"\nWrite-Host \"Task Id: $octopusTaskId\"\nWrite-Host \"Runbook Run Id: $octopusRunbookRunId\"\n\nif ([string]::IsNullOrWhiteSpace($vmssScaleSetName)) { Write-Error \"Scale Set Name is required.\" }\nif ([string]::IsNullOrWhiteSpace($vmssScaleSetResourceGroup)) { Write-Error \"Resource Group Name is required.\" }\nif ([string]::IsNullOrWhiteSpace($roleToSearchFor)) { Write-Error \"Scale Set Name is required.\" }\nif ([string]::IsNullOrWhiteSpace($octopusUrl)) { Write-Error \"Octopus Url is required.\" }\nif ([string]::IsNullOrWhiteSpace($apiKey)) { Write-Error \"Octopus Api Key is required.\" }\nif ([string]::IsNullOrWhiteSpace($timeoutInMinutes)) { Write-Error \"Timeout in minutes is required.\" }\nif ([string]::IsNullOrWhiteSpace($timeoutErrorHandle)) { Write-Error \"Timeout error handle is required.\" }\nif ([string]::IsNullOrWhiteSpace($duplicateRunDetectionInMinutes)) { Write-Error \"Duplicate run detection in minutes is required.\" }\nif ([string]::IsNullOrWhiteSpace($duplicateRunHandle)) { Write-Error \"Duplicate run handle is required.\" }\nif ([string]::IsNullOrWhiteSpace($excludeOldServers)) { Write-Error \"Exclude old servers is required.\" }\n\n$queuedEventInfo = Get-QueuedEventInfo -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId -octopusUrl $octopusUrl -apiKey $apiKey\n\nWrite-Host \"The current deployment was queued at: $($queuedEventInfo.CurrentDeploymentQueued)\"\n\n$duplicateRun = Test-ForDuplicateRun -octopusRunbookRunId $octopusRunbookRunId -octopusDeploymentId $octopusDeploymentId -octopusSpaceId $octopusSpaceId  -duplicateRunDetectionInMinutes $duplicateRunDetectionInMinutes -duplicateRunHandle $duplicateRunHandle -queuedEventInfo $queuedEventInfo -octopusTaskId $octopusTaskId -octopusUrl $octopusUrl -apiKey $apiKey\n\nStart-WaitForVMSSToFinishProvisioning -vmssScaleSetResourceGroup $vmssScaleSetResourceGroup -vmssScaleSetName $vmssScaleSetName -timeoutInMinutes $timeoutInMinutes\nStart-WaitForVMsInVMSSToFinishProvisioning -vmssScaleSetResourceGroup $vmssScaleSetResourceGroup -vmssScaleSetName $vmssScaleSetName -timeoutInMinutes $timeoutInMinutes -timeoutErrorHandle $timeoutErrorHandle\n\n$vmssVmList = Get-AzVmssVM -ResourceGroupName $vmssScaleSetResourceGroup -VMScaleSetName $vmssScaleSetName\n$vmListToReconcile = @()\n\nforeach ($vmInfo in $vmssVmList)\n{\n\tif ($vmInfo.ProvisioningState.ToLower().Trim() -ne \"failed\")\n    {\n    \t$vmListToReconcile += $vmInfo.OsProfile.ComputerName\n    }\n}\n\n$octopusDeployTargets = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"machines?environmentIds=$($octopusEnvironmentId)&roles=$($roleToSearchFor)&skip=0&take=1000\" -spaceId $octopusSpaceId -apiKey $apiKey -method \"GET\"\n$octopusDeployTargetIds = @()\n$octopusDeployTargetNames = @()\n$roleList = $roleToSearchFor.Split(\",\")\nforeach ($deploymentTarget in $octopusDeployTargets.Items)\n{\n\t$matchingRole = $true\n    foreach ($role in $roleList)\n    {\n    \tif ($deploymentTarget.Roles -notContains ($role.Trim()))\n        {\n        \tWrite-Host \"The target $($deploymentTarget.Name) does not contain the role $role.  To be considered part of the scale set it has to be assigned to all the roles $roleToSearchFor.  Excluding from reconcilation logic.\"\n            $matchingRole = $false\n            break\n        }\n    }\n    \n    if ($matchingRole -eq $false)\n    {\n    \tcontinue\n    }\n    \n    if ([string]::IsNullOrWhiteSpace($octopusTenantId) -eq $false -and $deploymentTarget.TenantIds -notcontains $octopusTenantId)\n    {\n    \tWrite-Host \"The target $($deploymentTarget.Name) is not assigned to $octopusTenantId.  But the current run is running under the context of that tenant.  Excluding from reconcilation logic.\"\n        continue\n    }\n    \n    $hasMatchingName = $false\n    $deploymentTargetNameLowerTrim = $deploymentTarget.Name.ToLower().Trim()\n\n    Write-Host \"Attempting to do a match on name\"\n\tforeach ($vmssVM in $vmListToReconcile)\n    {\n    \t$vmssVMLowerTrim = $vmssVM.ToLower().Trim()\n\n        Write-Host \"Checking to see if $($deploymentTarget.Name) is like $vmssVM\"\n        if ($deploymentTargetNameLowerTrim -eq $vmssVMLowerTrim)\n        {\n            Write-Host \"The vmss vm name $vmssVM is equal to to the deployment target name $($deploymentTarget.Name), set matching to true\"         \n            $hasMatchingName = $true\n            break\n        }\n        if ($deploymentTargetNameLowerTrim -like \"*$vmssVMLowerTrim*\")\n        {   \n            Write-Host \"The deployment target name $($deploymentTarget.Name) contains the vmss vm name $vmssVM, set matching to true\"         \n            $hasMatchingName = $true\n            break\n        }\n        elseif ($vmssVMLowerTrim -like \"*$deploymentTargetNameLowerTrim*\")\n        {\n            Write-Host \"The vmss vm name $vmssVM contains the deployment target name $($deploymentTarget.Name), set matching to true\"\n            $hasMatchingName = $true\n            break\n        }\n    }\n    \n    if ($hasMatchingName -eq $false)\n    {\n    \tWrite-Highlight \"The deployment target $($deploymentTarget.Name) is not in the list of VMs assigned to the scale set, deleting it.\"\n        Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"machines/$($deploymentTarget.Id)\" -spaceId $octopusSpaceId -apiKey $apiKey -method \"DELETE\"\n    }\n    else\n    {\n    \tWrite-Highlight \"The deployment target $($deploymentTarget.Name) is in the list of VMS assigned to the scale set, leaving it alone.\"\n        \n        $addToOutputArray = $true\n        if ($excludeOldServers.ToLower().Trim() -eq \"yes\")\n        {\n        \tWrite-Host \"Pulling back the creation event for $($deploymentTarget.Name)\"\n        \t$creationTasks = Invoke-OctopusApi -octopusUrl $octopusUrl -endPoint \"events?regarding=$($deploymentTarget.Id)&spaces=$($octopusSpaceId)&includeSystem=true&eventCategories=Created\" -spaceId $null -apiKey $apiKey -method \"GET\"\n            \n            $creationDate = @($creationTasks.Items)[0].Occurred\n            Write-Host \"The deployment target $($deploymentTarget.Name) was created on $creationDate\"\n            $differenceInCreationTime = [DateTime]$queuedEventInfo.CurrentDeploymentQueued - [DateTime]$creationDate\n            \n            Write-Host \"The difference in minutes between creation date and current task queued time is $($differenceInCreationTime.TotalMinutes) minutes\" \n            if ($differenceInCreationTime.TotalMinutes -gt 3)\n            {\n            \tWrite-Host \"The deployment target $($deploymentTarget.Name) existed for more than 3 minutes before this was ran and the excludeOldServers was set to yes, removing this from the output\"\n            \t$addToOutputArray = $false\n            }\n        }\n        \n        if ($addToOutputArray -eq $true)\n        {\n\t        $octopusDeployTargetIds += $deploymentTarget.Id\n    \t    $octopusDeployTargetNames += $deploymentTarget.Name\n        }\n    }\n}\n\nWrite-Highlight \"The Azure VM Scale Set $vmssScaleSetName and Octopus Deploy target list have been successfully reconciled.\"\n\n$vmssHasServersToDeployTo = $octopusDeployTargetIds.Count -gt 0\nif ($duplicateRun -eq $true)\n{\n\tWrite-Highlight \"Duplicate run detected, therefore there are no new servers to deploy to.\"\n    $vmssHasServersToDeployTo = $false\n}\nelseif ($vmssHasServersToDeployTo -eq $false)\n{\n\tWrite-Highlight \"There are no servers to deploy to.  Exclude old servers was set to '$excludeOldServers'.  This likely means this was a scale in event or all the servers existed prior to this run.\"\n}\n\nWrite-Highlight \"Setting the output variable 'VMSSHasServersToDeployTo' to $vmssHasServersToDeployTo.\"\nSet-OctopusVariable -Name \"VMSSHasServersToDeployTo\" -Value $vmssHasServersToDeployTo\n\nWrite-Highlight \"Setting the output variable 'VMSSDeploymentTargetIds' to $($octopusDeployTargetIds -join \",\").\"\nSet-OctopusVariable -Name \"VMSSDeploymentTargetIds\" -Value ($octopusDeployTargetIds -join \",\")\n\nWrite-Highlight \"Setting the output variable 'VMSSDeploymentTargetNames' to $($octopusDeployTargetNames -join \",\").\"\nSet-OctopusVariable -Name \"VMSSDeploymentTargetNames\" -Value ($octopusDeployTargetNames -join \",\")"
  },
  "Category": "Azure",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/azure-check-vmss-provision-status.json",
  "Website": "/step-templates/e04c5cd8-0982-44b8-9cae-0a4b43676adc",
  "Logo": "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF////AHjXf7vrv931QJrh7/f8EIDaIIncMJHfYKvmz+b3n8zw3+76j8Ttr9XycLPpUKLkkKvYFAAABGZJREFUeNrsnNmCqjoQRc1MEiD8/9cer7Yt2KBJZQC8ez07sKlKTQlcLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzoUSnt8YxXlFuGHSbIaxvj+fip4btkLn1blkWLaF5v03yLhLOYlVuGYfMOMZzNGxCOzhjTJqFkXnjq3Dr1yyvPI3hGl3Ih3zzHHNKudRstRhX5O58vIcShY67Gq6EPIESlzUWvazaGAOGbvU7ArDu/g8M4o8opDZWvbvPzlL/MMBE8jT9T9W7PbAJlHPTBFRf9yVTEcs63msXz2UHLSgf650G/d5t+wjbxxB2UCMqGrk8/LFSD7uJMeNt5bcJCyQZyAe5Fo9KYfWS2flQrr4b4tpuzaeWjYs49rt9LHf9uZD7+VbyVi9EBNrjYjuq2sxQOrl+p+HuBVu45qvqfq691ttYFQ5KyKbyJgaIY/NGxrlWZwlwGvmvu1oY3PuAv0niTq6tZ78jk//9uc1r1r4lQki7y7sp2Tu4V1y2iLoqFTqi1lIGcpFiebrZNZ1dOkF0cCIlO8jQ47nCkam9Lilz9GhDF1I6XGLzfnhwDIIZVfI7+8SSgfHsijqXENOGJF5QorG4EcW0OrScqX/dDrXpr70Ut/BII+1OfECPuYz/NWxYmgrCsUskxPvyhgmrw+WGZ6lGTuOlIyCYWTFyWjpM5KIZRUIOwjRNYRQ6tZF9BXtk8hWAHPtLNJ727Fq0JSkC1FDRRF0Jalj0d5qVh2KEpM2TuSsCYTCT6ZkdmFYI9LrYp5QayWbo6NXlZwcRD/61pth5Fq5EX423QQxNjhqWvvklkljOLkYjrmphXPZOJOk6Pg7HKMsrtQKcowzZoK3rx1ZUelGMdQA/HaKkjAt2RgqpZeYqbNbH7Hp2ct4nqfSPOfe0ftiSTZJydOV6rG5bQbyLK+nRuCC0343PzDgiOXyQA5c14BTZi98uR/5KJ1SnatLdoO50WWBQZPTq0VgsklU3h932actuo17ayrHrb/3ykiegd3KbqF2wbV6RrlsJ07yLcpsWFTul9RyK6ZScr+tk7oNrFj0o7HQUlj4EiEvJ6rPLKSmlMZCrksl1OnLaRkxc+/HB1naMhNtT/6yM2bDs6azCRHrM3aVPN7aW8irD/10B8njpAMcsl8okXcdKrl4sPsLmQVy/Sj90ucPRc/d/Bxxj+dXSpCayen32D+hLi16MsIV8gfCXrYp6ySsiJKRUF0XXiLpVbFU+fNv4r7mOwhFsX4ZdwpSi1DYs2jb6ebZ9788cblTzMrYhu7sf/17IFdtuviJ2ioHA6pMHkoH4CLUeMBU7iGkxuM/YgcdderF9ibRdc7O982F1HpYhjfWUe+x5a6pjop9iNLfoePvlsdZdTSMwfxSmTY20Q0eHnUNzga1edeNmmqbg18aMVR1L9vwSXHF9TfIWBxpKLs2hj3eQeBC0USvp2HHF3eIkRdhFOd6ER8AAAAAAAAAAAAAAAAAAAAAAAAAAAAA/I/4J8AAo/80BciBec4AAAAASUVORK5CYII=",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Monday, July 26, 2021