繁体   English   中英

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

[英]PowerShell: detecting errors in script functions

检测脚本函数中是否发生错误的最佳方法是什么? 我正在寻找一种一致的方式来指示类似于$的错误/成功状态? (仅适用于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.
}

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

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

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

必须做出以下观察:

  • $? 是一个未被证实的谜团。 $? 从cmdlet的通话值不传播,这是一个“只读变量”(因而无法通过手动设置),它是不是当它到底被设置(怎么可能是一个“执行状态”上明确,长期从未使用过在PowerShell中,除了about_Automatic_Variables$?的描述about_Automatic_Variables ,是一个谜。 值得庆幸的是Bruce Payette对此有所了解:如果你想设置$? $PSCmdlet.WriteError()是唯一已知的方法。

  • 如果你想要函数设置$? 如cmdlet所做,您必须避免Write-Error并使用$PSCmdlet.WriteError()代替。 Write-Error$PSCmdlet.WriteError()做同样的事情,但前者没有设置$? 适当而后者。 (不要试图在某处找到这个记录。但事实并非如此。)

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

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

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

# 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])
}

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

听起来您正在寻找一种通用机制来记录从脚本调用的命令中发生的任何错误。 如果是这样, 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

我想到两件事: 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,通常最好让函数本身尽可能地处理它的错误。

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

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

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

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

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

为什么%$#@%$与cli中的2>不一样...

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

    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,看起来很酷,但我不知道如何让它工作......文档有点斯巴达。 好像人们正在努力工作而没有太多麻烦,所以我觉得我是一个角落里坐着尖帽子的人......

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

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

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

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

foo 

$?

foo -errorAction Stop



$?

function foo() { throw "problem" } 

foo 

$?

希望这可以帮助

我相信你想要一个全局变量$ 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,马特

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM