Block Retry using Powershell
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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
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 }
1
2
3
4
5
6
7
8
9
10
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.
This post is licensed under CC BY 4.0 by the author.
Comments powered by Disqus.