Public/Invoke-ADTCommandWithRetries.ps1
#----------------------------------------------------------------------------- # # MARK: Invoke-ADTCommandWithRetries # #----------------------------------------------------------------------------- function Invoke-ADTCommandWithRetries { <# .SYNOPSIS Drop-in replacement for any cmdlet/function where a retry is desirable due to transient issues. .DESCRIPTION This function invokes the specified cmdlet/function, accepting all of its parameters but retries an operation for the configured value before throwing. .PARAMETER Command The name of the command to invoke. .PARAMETER Retries How many retries to perform before throwing. .PARAMETER SleepSeconds How many seconds to sleep between retries. .PARAMETER Parameters A 'ValueFromRemainingArguments' parameter to collect the parameters as would be passed to the provided Command. While values can be directly provided to this parameter, it's not designed to be explicitly called. .INPUTS None You cannot pipe objects to this function. .OUTPUTS System.Object Invoke-ADTCommandWithRetries returns the output of the invoked command. .EXAMPLE Invoke-ADTCommandWithRetries -Command Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile "$($adtSession.DirSupportFiles)\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle" Downloads the latest WinGet installer to the SupportFiles directory. .NOTES An active ADT session is NOT required to use this function. Tags: psadt Website: https://psappdeploytoolkit.com Copyright: (C) 2024 PSAppDeployToolkit Team (Sean Lillis, Dan Cunningham, Muhammad Mashwani, Mitch Richters, Dan Gough). License: https://opensource.org/license/lgpl-3-0 .LINK https://psappdeploytoolkit.com #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = "This function is appropriately named and we don't need PSScriptAnalyzer telling us otherwise.")] [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [System.Object]$Command, [Parameter(Mandatory = $false)] [ValidateNotNullOrEmpty()] [System.UInt32]$Retries = 3, [Parameter(Mandatory = $false)] [ValidateRange(1, 60)] [System.UInt32]$SleepSeconds = 5, [Parameter(Mandatory = $false, ValueFromRemainingArguments = $true, DontShow = $true)] [ValidateNotNullOrEmpty()] [System.Collections.Generic.List[System.Object]]$Parameters ) begin { # Initialize function. Initialize-ADTFunction -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState } process { try { try { # Attempt to get command from our lookup table. $commandObj = if ($Command -is [System.Management.Automation.CommandInfo]) { $Command } elseif ($Script:CommandTable.Contains($Command)) { $Script:CommandTable.$Command } else { Get-Command -Name $Command } # Convert the passed parameters into a dictionary for splatting onto the command. $boundParams = Convert-ADTValuesFromRemainingArguments -RemainingArguments $Parameters $callerName = (Get-PSCallStack)[1].Command # Perform the request, and retry it as per the configured values. for ($i = 0; $i -lt $Retries; $i++) { try { return (& $commandObj @boundParams) } catch { Write-ADTLogEntry -Message "The invocation to '$($commandObj.Name)' failed with message: $($_.Exception.Message.TrimEnd('.')). Trying again in $SleepSeconds second$(if (!$SleepSeconds.Equals(1)) {'s'})." -Severity 2 -Source $callerName [System.Threading.Thread]::Sleep($SleepSeconds * 1000) $errorRecord = $_ } } # If we're here, we failed too many times. Throw the captured ErrorRecord. throw $errorRecord } catch { # Re-writing the ErrorRecord with Write-Error ensures the correct PositionMessage is used. Write-Error -ErrorRecord $_ } } catch { # Process the caught error, log it and throw depending on the specified ErrorAction. Invoke-ADTFunctionErrorHandler -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -ErrorRecord $_ } } end { # Finalize function. Complete-ADTFunction -Cmdlet $PSCmdlet } } |