Private/Stack/Wait-PSCFNStack.ps1

function Wait-PSCFNStack
{
    <#
    .SYNOPSIS
        Wait for a stack to do something, printing events along the way.

    .PARAMETER StackArn
        Stack(s) to wait on.

    .PARAMETER CredentialArguments
        Hash of common credetial and region arguments.

    .PARAMETER StartTime
        Time the stack update command was issued

    .OUTPUTS
        [bool] true if operation succeeded; else false
#>

    param
    (
        [string[]]$StackArn,

        [hashtable]$CredentialArguments,

        [DateTime]$StartTime
    )

    $checkTime = $StartTime

    # Copy input array so as not to trash it.
    $arns = $StackArn |
    Foreach-Object {
        $_
    }

    # States indicating any stack operation complete
    $completionStates = @(
        'CREATE_COMPLETE'
        'CREATE_FAILED'
        'DELETE_COMPLETE'
        'DELETE_FAILED'
        'ROLLBACK_COMPLETE'
        'ROLLBACK_FAILED'
        'UPDATE_COMPLETE'
        'UPDATE_ROLLBACK_COMPLETE'
        'UPDATE_ROLLBACK_FAILED'
        'IMPORT_COMPLETE'
        'IMPORT_ROLLBACK_COMPLETE'
        'IMPORT_ROLLBACK_FAILED'
    )

    # Best guess how wide to make stack name column
    $stackColumnWidth = $arns | Foreach-Object {
        Get-CFNStack -StackName $_ @CredentialArguments
    } |
    Foreach-Object {
        $stack = $_

        # This stack
        $stack.StackName.Length

        # Nested stacks
        Get-CFNStackResourceList -StackName $_.StackId @CredentialArguments |
        Where-Object {
            $_.ResourceType -ieq 'AWS::CloudFormation::Stack'
        } |
        Foreach-Object {
            ($stack.StackName + "-" + $_.LogicalResourceId + "-" + ("X" * 12)).Length
        }
    } |
    Measure-Object -Maximum |
    Select-Object -ExpandProperty Maximum

    $anyFailed = $false
    $writeHeaders = $true

    # If localstack testing, poll faster
    $sleepTime = $(
        if ($CredentialArguments.Keys -icontains 'EndpointUrl')
        {
            1
        }
        else
        {
            10
        }
    )

    while (($arns | Measure-Object).Count -gt 0)
    {
        Start-Sleep -Seconds $sleepTime

        $stacks = $arns |
            Foreach-Object {

            Get-CFNStack -StackName $_ @CredentialArguments
        }

        $completedStackArns = $stacks |
            Where-Object {

            if ($_.StackStatus -ilike '*ROLLBACK*' -or $_.StackStatus -ilike '*FAILED*')
            {
                $anyFailed = $true
            }

            $completionStates -icontains $_.StackStatus
        } |
            Select-Object -ExpandProperty StackId

        if ($completedStackArns)
        {
            $arns = Compare-Object -ReferenceObject $arns -DifferenceObject $completedStackArns -PassThru
        }

        $ts = Write-StackEvents -StackArn $arns -EventsAfter $checkTime $CredentialArguments -WriteHeaders $writeHeaders -StackColumnWidth $stackColumnWidth
        $writeHeaders = $false

        if ($ts)
        {
            $checkTime = $ts
        }
    }

    # Capture return value so it doesn't leak into the pipeline
    $ts = Write-StackEvents -StackArn $StackArn -EventsAfter $checkTime $CredentialArguments -WriteHeaders $false -StackColumnWidth $stackColumnWidth

    # Output boolean - any final state containing ROLLBACK or FAILED indicates operation unsuccessful
    return (-not $anyFailed)
}