PowerShell尝试/捕获并重试

时间:2017-08-02 21:06:34

标签: powershell error-handling try-catch

我有一个相当大的PowerShell脚本,有许多(20+)个函数可以执行各种操作。

现在所有代码都没有任何错误处理或重试功能。如果某个特定的任务/功能失败,它就会失败并继续。

我想改进错误处理并实现重试以使其更加健壮。

我在想类似的事情:

$tries = 0
while ($tries -lt 5) {
    try{    

       # Do Something

       # No retries necessary
       $tries = 5;
    } catch {
       # Report the error
       # Other error handling
    }
 }

问题在于我需要执行许多步骤。

我认为实施上述代码20次并不合理。这似乎真的是多余的。

我在考虑编写一个带有单个参数的“TryCatch”函数,该参数包含我想要调用的实际函数?

我不确定这是正确的做法。我最终会得到一个类似于:

的脚本
TryCatch "Function1 Parameter1 Parameter2"
TryCatch "Function2 Parameter1 Parameter2"
TryCatch "Function3 Parameter1 Parameter2"

有更好的方法吗?

4 个答案:

答案 0 :(得分:8)

如果您经常需要多次重试动作的代码,您可以将循环try..catch包装在函数中并在脚本块中传递命令:

function Retry-Command {
    [CmdletBinding()]
    Param(
        [Parameter(Position=0, Mandatory=$true)]
        [scriptblock]$ScriptBlock,

        [Parameter(Position=1, Mandatory=$false)]
        [int]$Maximum = 5
    )

    Begin {
        $cnt = 0
    }

    Process {
        do {
            $cnt++
            try {
                $ScriptBlock.Invoke()
                return
            } catch {
                Write-Error $_.Exception.InnerException.Message -ErrorAction Continue
            }
        } 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.'
    }
}

调用这样的函数(默认为5次重试):

Retry-Command -ScriptBlock {
    # do something
}

或者像这样(如果在某些情况下需要不同的重试次数):

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

该功能可以进一步改进,例如通过在$Maximum尝试失败后尝试使用其他参数配置脚本终止,以便您可以拥有导致脚本失败时停止的操作,以及可以忽略失败的操作。

答案 1 :(得分:2)

我修改了@Victor的答案并添加:

  • 重试参数
  • ErrorAction设置和恢复(否则不会捕获异常)
  • 指数退避延迟(我知道OP并没有要求,但是我使用了它)
  • 摆脱了VSCode警告(即将sleep替换为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

答案 2 :(得分:0)

错误处理总是会为您的脚本添加更多内容,因为它通常需要处理许多不同的事情。如果您希望每个函数都有多次尝试,Try Catch函数可能最适合您上面描述的内容。自定义函数允许您在尝试之间通过每次传入一个值来设置像睡眠定时器之类的东西,或者改变函数尝试尝试的次数。

答案 3 :(得分:0)

将委托传递给函数而不是脚本块的解决方案:

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!"})