簡體   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