PowerShell Try/Catch and Retry

前端 未结 4 1998
故里飘歌
故里飘歌 2020-12-31 07:22

I have a fairly large powershell scripts with many (20+) functions which perform various actions.

Right now all of the code doesn\'t really have any error handling

相关标签:
4条回答
  • 2020-12-31 07:24

    If you frequently need code that retries an action a number of times you could wrap your looped try..catch in a function and pass the command in a scriptblock:

    function Retry-Command {
        [CmdletBinding()]
        Param(
            [Parameter(Position=0, Mandatory=$true)]
            [scriptblock]$ScriptBlock,
    
            [Parameter(Position=1, Mandatory=$false)]
            [int]$Maximum = 5,
    
            [Parameter(Position=2, Mandatory=$false)]
            [int]$Delay = 100
        )
    
        Begin {
            $cnt = 0
        }
    
        Process {
            do {
                $cnt++
                try {
                    $ScriptBlock.Invoke()
                    return
                } catch {
                    Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
                    Start-Sleep -Milliseconds $Delay
                }
            } while ($cnt -lt $Maximum)
    
            # Throw an error after $Maximum unsuccessful invocations. Doesn't need
            # a condition, since the function returns upon successful invocation.
            throw 'Execution failed.'
        }
    }
    

    Invoke the function like this (default is 5 retries):

    Retry-Command -ScriptBlock {
        # do something
    }
    

    or like this (if you need a different amount of retries in some cases):

    Retry-Command -ScriptBlock {
        # do something
    } -Maximum 10
    

    The function could be further improved e.g. by making script termination after $Maximum failed attempts configurable with another parameter, so that you can have have actions that will cause the script to stop when they fail, as well as actions whose failures can be ignored.

    0 讨论(0)
  • 2020-12-31 07:40

    I adapted @Victor's answer and added:

    • parameter for retries
    • ErrorAction set and restore (or else exceptions do not get caught)
    • exponential backoff delay (I know the OP didn't ask for this, but I use it)
    • got rid of VSCode warnings (i.e. replaced sleep with Start-Sleep)
    # [Solution with passing a delegate into a function instead of script block](https://stackoverflow.com/a/47712807/)
    function Retry()
    {
        param(
            [Parameter(Mandatory=$true)][Action]$action,
            [Parameter(Mandatory=$false)][int]$maxAttempts = 3
        )
    
        $attempts=1    
        $ErrorActionPreferenceToRestore = $ErrorActionPreference
        $ErrorActionPreference = "Stop"
    
        do
        {
            try
            {
                $action.Invoke();
                break;
            }
            catch [Exception]
            {
                Write-Host $_.Exception.Message
            }
    
            # exponential backoff delay
            $attempts++
            if ($attempts -le $maxAttempts) {
                $retryDelaySeconds = [math]::Pow(2, $attempts)
                $retryDelaySeconds = $retryDelaySeconds - 1  # Exponential Backoff Max == (2^n)-1
                Write-Host("Action failed. Waiting " + $retryDelaySeconds + " seconds before attempt " + $attempts + " of " + $maxAttempts + ".")
                Start-Sleep $retryDelaySeconds 
            }
            else {
                $ErrorActionPreference = $ErrorActionPreferenceToRestore
                Write-Error $_.Exception.Message
            }
        } while ($attempts -le $maxAttempts)
        $ErrorActionPreference = $ErrorActionPreferenceToRestore
    }
    
    # function MyFunction($inputArg)
    # {
    #     Throw $inputArg
    # }
    
    # #Example of a call:
    # Retry({MyFunction "Oh no! It happened again!"})
    # Retry {MyFunction "Oh no! It happened again!"} -maxAttempts 10
    
    0 讨论(0)
  • 2020-12-31 07:41

    Solution with passing a delegate into a function instead of script block:

    function Retry([Action]$action)
    {
        $attempts=3    
        $sleepInSeconds=5
        do
        {
            try
            {
                $action.Invoke();
                break;
            }
            catch [Exception]
            {
                Write-Host $_.Exception.Message
            }            
            $attempts--
            if ($attempts -gt 0) { sleep $sleepInSeconds }
        } while ($attempts -gt 0)    
    }
    
    function MyFunction($inputArg)
    {
        Throw $inputArg
    }
    
    #Example of a call:
    Retry({MyFunction "Oh no! It happend again!"})
    
    0 讨论(0)
  • 2020-12-31 07:44

    Error handling is always going to add more to your script since it usually has to handle many different things. A Try Catch function would probably work best for what you are describing above if you want to have each function have multiple tries. A custom function would allow you to even set things like a sleep timer between tries by passing in a value each time, or to vary how many tries the function will attempt.

    0 讨论(0)
提交回复
热议问题