PSRetryCommand.psm1

Function Wait-Until {
<#
    .SYNOPSIS
        Wait polling for a scriptblock to complete successfully
 
    .DESCRIPTION
        Wait polling for a scriptblock to complete successfully
 
    .PARAMETER ScriptBlock
        Specifies the commands to run. Enclose the commands in braces ({ }) to create a script block.
 
    .PARAMETER Timeout
        Specifies the maximum wait time for each job, in seconds. The default value, -1, indicates that the cmdlet will wait indefinitely.
 
    .PARAMETER DelayMillis
        Amount of milliseconds to sleep between polling attempts
 
    .PARAMETER MaximumRetryCount
        Max amount of polling attempts
 
    .PARAMETER AbortOnExceptions
        List of exception types to abort execution with failure immediately
 
    .PARAMETER SkipResultTest
        By default only results evaluating to true with simple if check are considered as valid.
        Skip this check to e.g. accept empty string as a valid response
 
    .EXAMPLE
        PS C:\> Wait-Until -ScriptBlock {Invoke-RestMethod http://localhost:8080}
 
        Waits until we get a valid response from localhost port 8080.
#>

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)]
        [ScriptBlock]$ScriptBlock,

        [Parameter(Mandatory = $false)]
        [Int32]$Timeout = -1,

        [ValidateScript({ $_ -gt 0 })]
        [Alias("RetryEvery")]
        [Parameter(Mandatory = $false)]
        [Int]$DelayMillis = 100,

        [Parameter(Mandatory = $false)]
        [Int]$MaximumRetryCount = [Int]::MaxValue,

        [Parameter(Mandatory = $false)]
        [String[]]$AbortOnExceptions = @(),

        [switch]$SkipResultTest
    )

    process {
        $Attempt = 0
        $StopWatch = [system.diagnostics.stopwatch]::StartNew()
        $EndLoop = $false

        do {
            $Attempt++
            try {
                "Attempt ${Attempt}, invoking script block ..." | Write-Verbose
                $ret = Invoke-Command -Command $ScriptBlock

                if ($SkipResultTest -or $ret) {
                    return $ret
                }
                else {
                    throw $ret
                }
            }
            catch {
                if ($_.Exception.GetType().FullName -in $AbortOnExceptions) {
                    "Terminating Exception '$($_.Exception.GetType().FullName)' caught, bailing out ..." | Write-Verbose
                    throw $_.Exception
                }
                if ($MaximumRetryCount -lt $Attempt) {
                    "Maximum Attempts (${Attempt}) reached, bailing out ..." | Write-Verbose
                    throw $_.Exception
                }
                $RemainingMillis = [Int]::MaxValue
                if ($Timeout -ge 0) {
                    $RemainingMillis = $Timeout * 1000 - [int] $stopwatch.Elapsed.TotalMilliseconds
                }
                $SleepMillis = [Math]::Min($RemainingMillis, $DelayMillis)
                if ($SleepMillis -le 0) {
                    throw $_.Exception
                }
                Write-Error $_.Exception -ErrorAction Continue

                "Sleeping ${SleepMillis} milliseconds ..." | Write-Verbose
                Start-Sleep -Milliseconds $SleepMillis
            }
        } until ($EndLoop)
    }
}