PowerShell: обнаружение ошибок в функциях скрипта - PullRequest
10 голосов
/ 21 июня 2011

Как лучше всего обнаружить, если в функции скрипта возникает ошибка? Я ищу последовательный способ указать состояние ошибки / успеха, подобное $? (который работает только с командлетами, а не с функциями сценариев).

Учитывая, что конкретная функция может возвращать значение, которое будет использовать вызывающая сторона, мы не можем указать успех, возвращая логическое значение. Функция может использовать параметр [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, так как это потребовало бы проверки счетчика перед вызовом функции, поскольку там могут быть другие ошибки перед вызовом - и я не хочу очищать () и терять их.

Ответы [ 6 ]

16 голосов
/ 22 сентября 2014

Как лучше всего обнаружить, если в функции скрипта возникает ошибка? Я ищу последовательный способ указать состояние ошибки / успеха, подобное $? (которая работает только с командлетами, а не с функциями сценариев).

Обработка ошибок в PowerShell - полный беспорядок. Есть записи об ошибках, исключения сценариев, исключения .NET, $?, $LASTEXITCODE, trap s, $Error массив (между областями) и так далее. И конструкции для взаимодействия этих элементов друг с другом (например, $ErrorActionPreference). Очень трудно быть последовательным, когда у тебя есть такие проблемы. Тем не менее, есть способ достичь этой цели.

Необходимо сделать следующие замечания:

  • $? - недокументированная тайна. $? значения из вызовов командлета не распространяются, это «переменная только для чтения» (поэтому ее нельзя установить вручную), и неясно, когда точно будет установлен (что может быть «статус выполнения», термин, который никогда не использовался в PowerShell, за исключением описания $? в about_Automatic_Variables, является загадкой). К счастью, Брюс Пайетт пролил свет на это: если вы хотите установить $?, $PSCmdlet.WriteError() - единственный известный способ.

  • Если вы хотите, чтобы функции устанавливали $?, как это делают командлеты, вы должны воздержаться от Write-Error и использовать вместо него $PSCmdlet.WriteError(). Write-Error и $PSCmdlet.WriteError() делают то же самое, но первый не устанавливает $? правильно, а второй делает. (Не пытайтесь найти это где-нибудь документально. Это не так.)

  • Если вы хотите правильно обрабатывать исключения .NET (как если бы они были не прерывающими ошибками, оставляя решение о прекращении всего выполнения вплоть до кода клиента), вы должны catch и $PSCmdlet.WriteError() их , Вы не можете оставить их необработанными, так как они становятся неразрывными ошибками, которые не уважают $ErrorActionPreference. (Также не документировано.)

Другими словами, ключом для обеспечения согласованного поведения при обработке ошибок является использование $PSCmdlet.WriteError(), когда это возможно. Он устанавливает $?, уважает $ErrorActionPreference (и, следовательно, -ErrorAction) и принимает System.Management.Automation.ErrorRecord объекты, созданные из других командлетов или оператор 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])
}

В качестве последнего замечания, если вы хотите создать завершающие ошибки из исключений .NET, выполните try / catch и повторно throw пойманное исключение.

6 голосов
/ 17 декабря 2011

Похоже, вы ищете общий механизм для регистрации любой ошибки, возникающей в команде, вызываемой из скрипта. Если это так, 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 в обработчике прерываний.

3 голосов
/ 21 июня 2011

На ум приходят две вещи: 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)
   }
}

Имхо, как правило, лучше иметь саму функцию для обработкиего ошибок как можно больше.

0 голосов
/ 14 ноября 2017

Большая часть этого издала прекрасный свистящий звук, когда он ударил меня прямо в голову ... 100_ಠ

Я с Дэном. PS Ведение журнала - это полный беспорядок, и кажется, что он более чем удвоит размер кода, который я пишу ...

Честно говоря, я был бы рад, если бы я мог записывать вывод консоли прямо в журналы, бородавки и все ...

Блок Try / Catch такой ... такой ... дерьмовый, я чувствую его запах, и мои глаза стали коричневыми.

$? это очень интересно, но вы, ребята, на самом деле знаете, что вы делаете, потому что, где я нахожусь в точке, где я понял, что ничего не знаю (на прошлой неделе я думал, что я знаю хоть что-то, но нееееет).

Почему в% $ # @% $ нет ничего похожего на 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, который, кажется, был бы довольно крутым, но я не уверен, как заставить его работать ... Документация немного спартанская. Кажется, что люди работают без особых проблем, так что я чувствую, что я заостренная шляпа такого типа ...

0 голосов
/ 23 июня 2011

Полагаю, вам нужна глобальная переменная $ GLOBAL: variable_name. Эта переменная будет находиться в области действия сценария, а не только в функции.

Глядя на код, вы также можете использовать trap (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, Matt

0 голосов
/ 22 июня 2011

$?зависит от того, выдает ли функция завершающую ошибку или нет.Если используется Write-Error, а не Throw, $?не установлен.Многие командлеты не устанавливают $?когда они имеют ошибку, потому что эта ошибка не является завершающей ошибкой.

Самый простой способ заставить вашу функцию установить $?это использовать -ErrorAction Stop.Это остановит скрипт, когда ваша функция выдаст ошибку, а $?будет установлен.

Обратите внимание на этот блок образцов, чтобы увидеть, как $?работает:

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

foo 

$?

foo -errorAction Stop



$?

function foo() { throw "problem" } 

foo 

$?

Надеюсь, это поможет

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...