PowerShell:检测脚本函数中的错误

Dan*_*anW 10 powershell powershell-2.0

检测脚本函数中是否发生错误的最佳方法是什么?我正在寻找一种一致的方式来指示类似于$的错误/成功状态?(仅适用于cmdlet,而不适用于脚本函数).

给定特定函数可能返回调用者要使用的值,我们不能通过返回布尔值来指示成功.函数可以使用[ref]参数并在函数内部设置适当的值并在调用后进行检查,但这比我想要的开销更大.我们可以使用PowerShell内置的东西吗?

我能想到的最好的是:

  1. 在函数中使用Write-Error将ErrorRecord对象放入错误流中;
  2. 使用ErrorVariable参数调用该函数;
  3. 检查调用后ErrorVariable参数是否为空.

例如:

function MyFun {
  [CmdletBinding()]    # must be an advanced function or this 
  param ()             # will not work as ErrorVariable will not exist
  process {
    # code here....
    if ($SomeErrorCondition) {
      Write-Error -Message "Error occurred; details..."
      return
    }
    # more code here....
  }
}

# call the function
$Err = $null
MyFun -ErrorVariable Err
# this check would be similar to checking if $? -eq $false
if ($Err -ne $null) {
  "An error was detected"
  # handle error, log/email contents of $Err, etc.
}
Run Code Online (Sandbox Code Playgroud)

还有更好的东西吗?有没有办法使用$?在我们的脚本函数中?我宁愿不抛出异常或ErrorRecord对象,并且在整个地方都有大量的try/catch块.我还宁愿不使用$ Error,因为在调用函数之前需要检查计数,因为在调用之前可能还有其他错误 - 而且我不想Clear()并丢失它们.

ale*_*cov 16

检测脚本函数中是否发生错误的最佳方法是什么?我正在寻找一种一致的方式来指示类似于$的错误/成功状态?(仅适用于cmdlet,而不适用于脚本函数).

PowerShell中的错误处理完全是一团糟.有错误的记录,脚本例外,.NET异常,$?,$LASTEXITCODE,trapS,$Error阵列(范围之间),等等.并构造相互交互这些元素(如$ErrorActionPreference).当你有这样的泥沼时,很难保持一致; 但是,有一种方法可以实现这一目标.

必须做出以下观察:

  • $?是一个未被证实的谜团.$?从cmdlet的通话值不传播,这是一个"只读变量"(因而无法通过手动设置),它是不是当上清楚究竟它被设置(怎么可能是一个"执行状态",长期从未使用过在除了上的描述的PowerShell $?about_Automatic_Variables,是一个谜).值得庆幸的是,布鲁斯·帕耶特已经阐明了这一点:如果你想设置$?,那$PSCmdlet.WriteError()是唯一已知的方法.

  • 如果您希望将函数设置$?为cmdlet,则必须避免Write-Error使用并$PSCmdlet.WriteError()改为使用.Write-Error$PSCmdlet.WriteError()做同样的事情,但前者没有$?正确设置而后者没有.(不要试图在某处找到这个记录.但事实并非如此.)

  • 如果要正确处理.NET异常(如如果他们非终止错误,停止而使整个执行到客户端代码的决定),你必须catch$PSCmdlet.WriteError()他们.你不能离开他们未加工的,因为他们成为这也非终止错误并不尊重$ErrorActionPreference.(也没有记录.)

换句话说,产生一致的错误处理行为的关键是尽可能使用$PSCmdlet.WriteError().它设置$?,尊重$ErrorActionPreference(并因此-ErrorAction)接受System.Management.Automation.ErrorRecord从其他cmdlet或catch语句(在$_变量中)生成的对象.

以下示例将说明如何使用此方法.

# Function which propagates an error from an internal cmdlet call,
# setting $? in the process.
function F1 {
    [CmdletBinding()]
    param([String[]]$Path)

    # Run some cmdlet that might fail, quieting any error output.
    Convert-Path -Path:$Path -ErrorAction:SilentlyContinue
    if (-not $?) {
        # Re-issue the last error in case of failure. This sets $?.
        # Note that the Global scope must be explicitly selected if the function is inside
        # a module. Selecting it otherwise also does not hurt.
        $PSCmdlet.WriteError($Global:Error[0])
        return
    }

    # Additional processing.
    # ...
}


# Function which converts a .NET exception in a non-terminating error,
# respecting both $? and $ErrorPreference.
function F2 {
    [CmdletBinding()]
    param()

    try {
        [DateTime]"" # Throws a RuntimeException.
    }
    catch {
        # Write out the error record produced from the .NET exception.
        $PSCmdlet.WriteError($_)
        return
    }
}

# Function which issues an arbitrary error.
function F3 {
    [CmdletBinding()]
    param()

    # Creates a new error record and writes it out.
    $PSCmdlet.WriteError((New-Object -TypeName:"Management.Automation.ErrorRecord"
        -ArgumentList:@(
            [Exception]"Some error happened",
            $null,
            [Management.Automation.ErrorCategory]::NotSpecified,
            $null
        )
    ))

    # The cmdlet error propagation technique using Write-Error also works.
    Write-Error -Message:"Some error happened" -Category:NotSpecified -ErrorAction:SilentlyContinue
    $PSCmdlet.WriteError($Global:Error[0])
}
Run Code Online (Sandbox Code Playgroud)

最后要注意的是,如果要从.NET异常中创建终止错误,请执行try/ catch并重新throw捕获异常.


Emp*_*LII 6

听起来您正在寻找一种通用机制来记录从脚本调用的命令中发生的任何错误.如果是这样,trap可能是最合适的机制:

Set-Alias ReportError Write-Host -Scope script  # placeholder for actual logging

trap {
  ReportError @"
Error in script $($_.InvocationInfo.ScriptName) :
$($_.Exception) $($_.InvocationInfo.PositionMessage)
"@
  continue  # or use 'break' to stop script execution
}

function f( [int]$a, [switch]$err ) {
  "begin $a"
  if( $err ) { throw 'err' }
  "  end $a"
}

f 1
f 2 -err
f 3
Run Code Online (Sandbox Code Playgroud)

运行此测试脚本会生成以下输出,而无需对被调用函数进行任何修改:

PS> ./test.ps1
begin 1
  end 1
begin 2
Error in script C:\Users\umami\t.ps1 :
System.Management.Automation.RuntimeException: err
At C:\Users\umami\t.ps1:13 char:21
+   if( $err ) { throw <<<<  'err' }
begin 3
  end 3
Run Code Online (Sandbox Code Playgroud)

如果在报告错误后脚本执行应该停止continue,请break在陷阱处理程序中替换.


Tor*_*edt 4

想到两件事:(比上面的例子Throw更好),并且Write-Errortry..catch

try
{
   #code here
}
catch
{
   if ($error[0].Exception -match "some particular error")
   {
       Write-Error "Oh No! You did it!"
   }
   else
   {
       Throw ("Ooops! " + $error[0].Exception)
   }
}
Run Code Online (Sandbox Code Playgroud)

恕我直言,通常最好让函数本身尽可能地处理其错误。