Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save gabriel-vanca/51fdf312a70d57551fba0508729ebb86 to your computer and use it in GitHub Desktop.
Save gabriel-vanca/51fdf312a70d57551fba0508729ebb86 to your computer and use it in GitHub Desktop.
Invoke-CommandWithRetries.psm1 - Retries a Powershell command n-times upon failure.
<#
.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
# ========== 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