Является ли исключение из метода .net завершающей или не завершающей ошибкой?

В этой большой статье Кейт объясняет разницу между завершающими и не заканчивающимися ошибками в Powershell. В соответствии с исключениями Кита, вызванными из вызовов участнику .NET-объекта или типа, это недопустимые ошибки.

Действительно, если мы определим этот класс .net для тестирования:

$a = Add-Type 'public class bla { public static void bl() { throw new System.ApplicationException("test"); }}' -PassThru

И затем эта функция:

function tst { 1 | write-host; $a::bl(); 2 | Write-host }

мы увидим, что при вызове функции tst исключение оказывается не завершающим: второй Write-Host работает.

Но рассмотрим следующее:

function tst2 { try { tst } catch { "Catch!" } }

Если мы откроем документацию, мы можем прочитать, что catch отвечает или обрабатывает завершающие ошибки в скриптах. Во всем тексте статей ошибки, которые статья имеет дело с квалифицированным "прекращением" во многих местах.

Итак, когда мы запускаем строку над вторым Write-Host, она не запускается, но блок catch делает это. Похоже, что наша неокончательная ошибка внезапно прекращается.

Как получилось?

Другое наблюдение заключается в том, что с хорошей старой ловушкой все еще не заканчивается ошибка:

function tst3 { tst trap { "Trap!" } }

Теперь, с практической точки зрения, я хочу достичь следующего. В блоке кода я хочу прекратить исключения из кода .NET. Я хочу оставить завершающие ошибки из завершающих и не завершающих ошибок командлетов из командлетов, не заканчивающихся.

Как мне это достичь?

Пример:

Do-Something
Call::Something()
Do-SomethingElse
Call::SomethingElse()
Do-YetMoreSomething
Call::YetMoreSomething()

Я хочу завершить все исключения из вызовов .net выше. Я также хочу прекратить прерывание ошибок командлетов. Я не хочу останавливать на ошибках, отличных от завершающих командлетов.

Ответ 1

Да, это появилось в списке адресов электронной почты PowerShell MVP в начале этого года. PowerShell изменяет поведение обработки ошибок для исключений .NET в зависимости от того, существует или нет внешний try/catch. Это просто предположение, но я предполагаю, что это было для простых сценариев сценариев. То есть, если scripter (admin) запутывает вызов метода .NET и генерирует исключение, команда PowerShell не хотела, чтобы это прекратило выполнение всего script. Как только V2 пришел и представил правильную попытку/уловку, я предполагаю, что им пришлось пересмотреть это решение и придумал нынешний компромисс.

Тем не менее, работа над этим - это боль, которую вы обнаружили. Вы можете установить $ErrorActionPreference на Stop на уровне script, а затем для каждого командлета, который может генерировать ошибки без конца, используйте параметр -ErrorAction Continue. Или вы можете поместить все ваши вызовы .NET в расширенную функцию (ы), а затем вызвать эту функцию с параметром -ErrorAction Stop. Я бы хотел, чтобы был лучший ответ, но после просмотра этого потока MVP я не видел никаких лучших решений.

Ответ 2

После некоторого тестирования кажется, что исключения, выброшенные за пределы блоков try/catch, ведут себя своеобразно, что не является ни завершающей, ни невыпадающей ошибкой.

Сравните вывод следующего:

# Functions which create simple non-terminating errors.
function E1 { [CmdletBinding()] param() Write-Error "Error"; "E1" }
function E2 { [CmdletBinding()] param() Write-Error "Error" -ErrorAction:Stop; "E2" }

# Functions which throw .NET exceptions, inside and outside try/catch blocks.
function F1 { [CmdletBinding()] param() [DateTime]""; "F1" }
function F2 { [CmdletBinding()] param() try { [DateTime]"" } catch { throw } "F2" }

# Test functions.
function TestE1 { [CmdletBinding()] param() E1; "TestE1" }
function TestF1 { [CmdletBinding()] param() F1; "TestF1" }
function TestE2 { [CmdletBinding()] param() E2; "TestE2" }
function TestF2 { [CmdletBinding()] param() F2; "TestF2" }
function StopE1 { [CmdletBinding()] param() E1 -ErrorAction:Stop; "StopE1" }
function StopF1 { [CmdletBinding()] param() F1 -ErrorAction:Stop; "StopF1" }
function StopE2 { [CmdletBinding()] param() E2 -ErrorAction:Stop; "StopE2" }
function StopF2 { [CmdletBinding()] param() F2 -ErrorAction:Stop; "StopF2" }

# All the following call pairs display similar behavior.
TestE1        # Returns "E1", "TestE1".
TestF1        # Returns "F1", "TestF1".

TestE2        # Halts and returns nothing.
TestF2        # Halts and returns nothing.

StopE2        # Halts and returns nothing.
StopF2        # Halts and returns nothing.

# The following display different behavior.
StopE1        # Halts and returns nothing.
StopF1        # F1 halts but StopF1 doesn't; the call returns "StopF1".

Это показывает, что исключения .NET, выходящие за пределы try/catch, не являются ошибками без конца или, по крайней мере, не такими же ошибками, генерируемыми Write-Error.

Чтобы достичь согласованных результатов, единственным способом до сих пор является catch исключение и либо re throw он (создавая завершающую ошибку), либо Write-Error it (создавая ошибку без конца). Например:

function F1 { [CmdletBinding()] param() try { [DateTime]"" } catch { Write-Error -ErrorRecord:$_ } "F1" }

# Now everything fine.
TestE1        # Returns "E1", "TestE1".
TestF1        # Returns "F1", "TestF1".

StopE1        # Halts and returns nothing.
StopF1        # Halts and returns nothing.

Ответ 3

Блок Try превращает исключение .NET в завершающую ошибку, поэтому вы можете заключить свой вызов .NET в такой блок:

Try { Class::RunSomeCode() } Catch { Throw }

Итак, в вашем примере ваша функция tst становится

function tst { 1 | write-host; Try { $a::bl() } Catch { Throw } 2 | Write-host }

И ваше исключение .NET теперь будет завершено.