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

时间:2011-06-21 18:44:38

标签: powershell powershell-v2.0

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

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

我能想到的最好的是:

  1. 在函数中使用Write-Error来 将ErrorRecord对象放入错误中 流;
  2. 用一个调用函数 ErrorVariable参数;
  3. 检查ErrorVariable参数 通话结束后不为空。
  4. 例如:

    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.
    }
    

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

6 个答案:

答案 0 :(得分:15)

  

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

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

必须进行以下观察:

  • $?是一个未被充分记录的谜团。来自cmdlet调用的$?值不会传播,它是一个"只读变量" (因此不能手动设置)并且不清楚何时完全它被设置(可能是"执行状态",术语从未在PowerShell中使用,除了在$?about_Automatic_Variables的描述是一个谜。值得庆幸的是,Bruce Payette对此有所了解:如果你想设置$?$PSCmdlet.WriteError()是唯一已知的方式。

  • 如果您希望函数将$?设置为cmdlet,则必须避免使用Write-Error并使用$PSCmdlet.WriteError()Write-Error$PSCmdlet.WriteError()执行相同的操作,但前者未正确设置$?,后者则不然。 (不要试图在某处找到这个记录。但事实并非如此。)

  • 如果您想正确处理.NET异常(就好像它们是非终止错误,而不是将整个执行暂停到客户端代码),您必须catch和{{ 1}}他们。您不能将它们保留为未处理状态,因为它们会成为尊重$PSCmdlet.WriteError()的非终止错误。 (未记录。)

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

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

catch

最后一点,如果您想从.NET异常中创建终止错误,请执行$_ / # 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]) } 并重新try捕获的异常。

答案 1 :(得分: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

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

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

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

答案 2 :(得分:3)

有两点需要考虑:Throw(优于上例中的Write-Error)和try..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)
   }
}
Imho,通常最好让函数本身尽可能地处理它的错误。

答案 3 :(得分:0)

$?取决于函数是否抛出终止错误。如果使用Write-Error,不是Throw,$?没有设定。许多cmdlet没有设置$?当他们有错误时,因为该错误不是终止错误。

使您的函数设置为$的最简单方法?是使用-ErrorAction Stop。这会在你的函数错误时停止脚本,并且$?将被设定。

注意这块样品看看怎么样?工作原理:

function foo([ParameteR()]$p) { Write-Error "problem" } 

foo 

$?

foo -errorAction Stop



$?

function foo() { throw "problem" } 

foo 

$?

希望这有帮助

答案 4 :(得分:0)

我相信你想要一个全局变量$ GLOBAL:variable_name。该变量将在脚本的范围内,而不仅仅是函数。

查看代码,您可能也想使用陷阱(Get-Help about_Trap) - 虽然$ GLOBAL:variable_name可以与您的上面一起使用。这是代码示例的重新分析 - 我没有对此进行过测试,因此它的伪代码更多......:)

function MyFun {
  [CmdletBinding()]    # must be an advanced function or this 
  param ()             # will not work as ErrorVariable will not exist
  begin {
    trap {
      $GLOBAL:my_error_boolean = $true
      $GLOBAL:my_error_message = "Error Message?"

      return
    }
  }
  process {
    # more code here....
  }
}

# call the function
$GLOBAL:my_error_boolean = $false
MyFun 
# this check would be similar to checking if $? -eq $false
if ($GLOBAL:my_error_boolean) {
  "An error was detected"
  # handle error, log/email contents of $Err, etc.
}

HTH,马特

答案 5 :(得分:0)

大部分都发出了可爱的嘶嘶声,因为它正好在我脑海上......ಠ_ಠ

我和丹在一起。 PS记录是一个完整的混乱,似乎它将超过我正在编写的代码的两倍...

坦率地说,如果我能直接将控制台输出捕获到日志,疣和所有......我会很高兴。

Try / Catch块是如此......如此......糟糕,我能闻到它,它让我的眼睛变成棕色。

$?非常有趣,但是你们真的知道你在做什么,就像我在意识到我什么都不知道的地方一样(上周我以为我至少知道一些东西,但是noooooo)。

为什么%$#@%$不存在2&gt;在cli ...

好的,所以这就是我要做的事情(你已经读过这篇文章了,为什么不呢?):

    Function MyFunc($Param1, $Param2){
Do{
  $Var = Get-Something | Select Name, MachineName, Status 
 $NotherVar = Read-Host -Prompt "Do you want to Stop or Start or check the $Var (1 to Start, 2 to stop, 3 to check, 4 to continue)?" 
    If ($SetState -eq 1) 
     {
      Do Stuff
    }
    ElseIf ($Var -eq 2) 
       {
      Do Stuff
    }
    ElseIf ($Var -eq 3)
       {
      Do Stuff
    }
  }
    Until ($Var -eq 4)
Do other stuff
} 

有效吗?是的,很好......记录并继续。没有?然后捕获错误,记录并继续脚本...

我很想要求用户输入,添加内容并继续......

顺便说一句,我确实找到了一个模块PSLogging,看起来很酷,但我不知道如何让它工作......文档有点斯巴达。好像人们正在努力工作而不会有太多麻烦,所以我觉得我是一个坐在尖角帽上的人...... ...