Является ли исключение из метода .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 теперь будет завершено.