-
-
Save gabriel-vanca/51fdf312a70d57551fba0508729ebb86 to your computer and use it in GitHub Desktop.
Invoke-CommandWithRetries.psm1 - Retries a Powershell command n-times upon failure.
This file contains 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
<# | |
.SYNOPSIS | |
Retries a Powershell command n-times upon failure. | |
.DESCRIPTION | |
The cmdlet is capable of retrying a PowerShell command passed as a [ScriptBlock] according to the user defined number of retries and time between retries. | |
.PARAMETER ScriptBlock | |
PoweShell command(s) as a ScriptBlock that will be executed and retried in case of Errors. | |
Make sure the script block throws an error when it fails, otherwise the cmdlet won't run the retry logic. | |
.PARAMETER RetryCount | |
Number of times to retry the command. Default value is '5' | |
.PARAMETER MillisecondsBetweenRetries | |
The time in milliseconds to pause and wait between each retry. Default is 5000. | |
.EXAMPLE | |
Invoke-CommandWithRetries -ScriptBlock { Stop-Service -Name "SomeService" } -RetryCount 2 -MillisecondsBetweenRetries 2000 -Verbose | |
VERBOSE: [Invoke-CommandWithRetries] Function started | |
VERBOSE: [1/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Cannot find any service with service name 'SomeService'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [2/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Cannot find any service with service name 'SomeService'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [3/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Cannot find any service with service name 'SomeService'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: All retry attempts depleted. | |
Stop-Service: Cannot find any service with service name 'SomeService'. | |
Note: You can explicitly define the 'Verbose' switch to see the retry logic in action. | |
.EXAMPLE | |
Invoke-CommandWithRetries -ScriptBlock { Get-Service bits | Stop-Service } -RetryCount 2 -MillisecondsBetweenRetries 2000 -Verbose | |
VERBOSE: [Invoke-CommandWithRetries] Function started | |
VERBOSE: [1/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Service 'Background Intelligent Transfer Service (bits)' cannot be stopped due to the following error: Cannot open 'bits' service on computer '.'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [2/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Service 'Background Intelligent Transfer Service (bits)' cannot be stopped due to the following error: Cannot open 'bits' service on computer '.'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [3/3] Failure to complete the task. | |
VERBOSE: Exception: Microsoft.PowerShell.Commands.ServiceCommandException: Service 'Background Intelligent Transfer Service (bits)' cannot be stopped due to the following error: Cannot open 'bits' service on computer '.'. | |
VERBOSE: ErrorDetails: | |
VERBOSE: All retry attempts depleted. | |
.EXAMPLE | |
{1/0} | Invoke-CommandWithRetries -RetryCount 2 -MillisecondsBetweenRetries 2000 -Verbose | |
VERBOSE: [Invoke-CommandWithRetries] Function started | |
VERBOSE: [1/3] Failure to complete the task. | |
VERBOSE: Exception: System.Management.Automation.RuntimeException: Attempted to divide by zero. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [2/3] Failure to complete the task. | |
VERBOSE: Exception: System.Management.Automation.RuntimeException: Attempted to divide by zero. | |
VERBOSE: ErrorDetails: | |
VERBOSE: Waiting 2000 milliseconds before trying again... | |
VERBOSE: [3/3] Failure to complete the task. | |
VERBOSE: Exception: System.Management.Automation.RuntimeException: Attempted to divide by zero. | |
VERBOSE: ErrorDetails: | |
VERBOSE: All retry attempts depleted. | |
Note: The function is capable of handling scriptblock's as input through the pipeline. | |
#> | |
function Invoke-CommandWithRetries { | |
[CmdletBinding()] | |
param | |
( | |
[Parameter(Position = 0, Mandatory = $true, ValueFromPipeline = $true, HelpMessage = 'The script block to execute.')] | |
[ValidateNotNullOrEmpty()] | |
[scriptblock] $ScriptBlock, | |
[Parameter(Position = 1, Mandatory = $false, HelpMessage = 'Number of times to retry the command. Default value is 5.')] | |
[int] $RetryCount = 5, | |
[Parameter(Position = 2, Mandatory = $false, HelpMessage = 'The time in milliseconds to pause and wait between each retry. Default is 5000.')] | |
[int] $MillisecondsBetweenRetries = 5000, | |
[Parameter(Position = 3, Mandatory = $false, HelpMessage = 'A list of error messages that should not be retried. If any part of the exception or error details match one of these messages, the script block will not be retried.')] | |
[string[]] $ErrorMessagesToNotRetry = @() | |
) | |
begin { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Function started" } | |
process { | |
[int] $currentAttemptIndex = 0 | |
do { | |
try { | |
$PreviousPreference = $ErrorActionPreference | |
$ErrorActionPreference = 'Stop' | |
Invoke-Command -ScriptBlock $ScriptBlock -OutVariable Result | |
$ErrorActionPreference = $PreviousPreference | |
# flow control will execute the next lines only if the command in the scriptblock executed without any errors | |
# if an error is thrown, flow control will go to the 'catch' block | |
Write-Verbose "Command completed successfully `n" | |
# Break out of the while loop since the command succeeded. | |
break | |
} | |
catch { | |
[string] $errorMessage = $_.Exception.ToString() | |
[string] $errorDetails = $_.ErrorDetails | |
Write-Verbose "[$($currentAttemptIndex + 1)/$($RetryCount + 1)] Failure to complete the task." | |
Write-Verbose "Exception: $errorMessage" | |
Write-Verbose "ErrorDetails: $errorDetails" | |
if ($currentAttemptIndex -lt $RetryCount) { | |
foreach ($noRetryMessage in $ErrorMessagesToNotRetry) { | |
if ($errorMessage -like "*$noRetryMessage*" -or $errorDetails -like "*$noRetryMessage*") { | |
Write-Verbose "Found an error message that should not be retried. Aborting process..." | |
throw | |
} | |
} | |
Write-Verbose "Waiting $MillisecondsBetweenRetries milliseconds before trying again...`n" | |
Start-Sleep -Milliseconds $MillisecondsBetweenRetries | |
} | |
else { | |
Write-Verbose "All retry attempts depleted." | |
throw | |
} | |
$currentAttemptIndex++ | |
} | |
} while ($true) | |
} | |
end { Write-Verbose "[$($MyInvocation.MyCommand.Name)] Complete" } | |
} | |
Export-ModuleMember -Function Invoke-CommandWithRetries |
This file contains 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
# ========== Examples of how to use module from another ps1 file ========== | |
using module .\Invoke-ScriptBlockWithRetries.psm1 | |
# === Example 1 - Action that does not return data === | |
[scriptblock] $exampleThatDoesNotReturnData = { | |
Stop-Service -Name "SomeService" | |
} | |
Invoke-CommandWithRetries -ScriptBlock $exampleThatDoesNotReturnData | |
# === Example 2 - Capturing data returned === | |
[scriptblock] $exampleThatReturnsData = { | |
Invoke-WebRequest -Uri 'https://google.com' | |
} | |
$result = Invoke-CommandWithRetries -ScriptBlock $exampleThatReturnsData -MaxNumberOfRetries 3 | |
if ($result.StatusCode -eq 200) { | |
Write-Output "Success" | |
} | |
# === Example 3 - Dealing with failures that still occur even with retries === | |
[scriptblock] $exampleThatWillAlwaysFail = { | |
Invoke-WebRequest -Uri 'https://SomeAddressThatDoesNotExist.com' | |
} | |
try { | |
$result = Invoke-CommandWithRetries -ScriptBlock $exampleThatWillAlwaysFail | |
} | |
catch { | |
$exceptionMessage = $_.Exception.Message | |
Write-Error "The following error occurred: $exceptionMessage" | |
} | |
# === Example 4 - Specify messages that should not be retried if the exception contains them === | |
[scriptblock] $exampleThatReturnsData = { | |
Invoke-RestMethod -Uri 'https://google.com' | |
} | |
[string[]] $noRetryMessages = @( | |
'400 (Bad Request)' | |
'401 (Unauthorized)' | |
) | |
Invoke-CommandWithRetries -ScriptBlock $exampleThatReturnsData -ErrorMessagesToNotRetry $noRetryMessages |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment