Block Retry using Powershell Comments

Last Updated: Feb 06, 2015

I’ve been doing a lot of Powershell scripting lately, and one of the features I’ve really been pining for is the ability to apply some form of retry logic to either a function or a block.

Take the following sample:

1 2 3 4 5 6 7 8 function RandomlyFail { $rnd = Get-Random -minimum 1 -maximum 3 if ( $rnd -eq 2 ) { throw "OH NOES!!!" } $Host . UI . WriteLine ( "W00t!!!" ) }

Depending on what the random number we get is, we’ll get one of two scenarios:

# Success RandomlyFail W00t!!! # Failure RandomlyFail OH NOES!!! At line:62 char:9 + throw "OH NOES!!!" + ~~~~~~~~~~~~~~~~~~ + CategoryInfo : OperationStopped: (OH NOES!!!:String) [], RuntimeException + FullyQualifiedErrorId : OH NOES!!!

Now, if this happened to be part of a larger script and we didn’t have everything wrapped in a try..catch block, execution could potentially stop there.

Since Powershell supports closures, we can write a function that accepts a script block as a parameter.

<# This function can be used to pass a ScriptBlock (closure) to be executed and returned.

The operation retried a few times on failure, and if the maximum threshold is surpassed, the operation fails completely.

Params: Command - The ScriptBlock to be executed RetryDelay - Number (in seconds) to wait between retries (default: 5) MaxRetries - Number of times to retry before accepting failure (default: 5) VerboseOutput - More info about internal processing (default: false) Examples:

Execute-With-Retry { $connection.Open() } $result = Execute-With-Retry -RetryDelay 1 -MaxRetries 2 { $command.ExecuteReader() }

>

function Execute-With-Retry { [CmdletBinding()] param(

[Parameter(ValueFromPipeline,Mandatory)] $Command,

$RetryDelay = 5, $MaxRetries = 5, $VerboseOutput = $false )

$currentRetry = 0 $success = $false $cmd = $Command.ToString() do { try { $result = & $Command $success = $true if ($VerboseOutput -eq $true) { $Host.UI.WriteDebugLine("Successfully executed [$cmd]") } return $result } catch [System.Exception] { $currentRetry = $currentRetry + 1 if ($VerboseOutput -eq $true) { $Host.UI.WriteErrorLine("Failed to execute [$cmd]: " + $_.Exception.Message) } if ($currentRetry -gt $MaxRetries) { throw "Could not execute [$cmd]. The error: " + $_.Exception.ToString() } else { if ($VerboseOutput -eq $true) { $Host.UI.WriteDebugLine("Waiting $RetryDelay second(s) before attempt #$currentRetry of [$cmd]") } Start-Sleep -s $RetryDelay } } } while (!$success);

}

Now, if we retrofit our sample above:

1 Execute-With-Retry -RetryDelay 1 -VerboseOutput $true { RandomlyFail }

Failed to execute [ RandomlyFail ]: OH NOES!!! DEBUG: Waiting 1 second(s) before attempt #1 of [ RandomlyFail ] Failed to execute [ RandomlyFail ]: OH NOES!!! DEBUG: Waiting 1 second(s) before attempt #2 of [ RandomlyFail ] Failed to execute [ RandomlyFail ]: OH NOES!!! DEBUG: Waiting 1 second(s) before attempt #3 of [ RandomlyFail ] Failed to execute [ RandomlyFail ]: OH NOES!!! DEBUG: Waiting 1 second(s) before attempt #4 of [ RandomlyFail ] W00t!!! DEBUG: Successfully executed [ RandomlyFail ]

The inspiration for this comes from an excellent article by Pawel Pabich.