Подтвердить что ты не робот

Как я могу отобразить "голые" сообщения об ошибке в PowerShell без сопровождающей stacktrace?

Как я могу написать в стандартную ошибку из PowerShell, или перехватить ошибки, такие как:

  • Сообщение об ошибке отображается как ошибка (истинная запись в стандартную ошибку, так что TeamCity и Octopus видят в ней ошибку)
  • Отсутствие мусора в стеке мешает мое красивое, лаконичное сообщение об ошибке

Все эти годы я выживал, throw ошибки или записывая через Write-Error, но я устал и стар, и в своих сценариях я просто хочу увидеть одно краткое сообщение об ошибке. Я пробовал все комбинации trap, throw, Write-Error и -ErrorAction, но безрезультатно:

try {
  throw "error" # Sample code for a stack overflow. In the theater
  # of your mind, imagine there is code here that does something real and useful
} catch {
  Write-Error "An error occurred attempting to 'do something.' Have you tried rebooting?"
}

Вот пользовательский опыт, который я хочу увидеть:

C:\> & .\Do-Something.ps1
An error occurred attempting to 'do something.' Have you tried rebooting?

C:\> ▏

Вместо этого я получаю:

C:\> & .\Do-Something.ps1
An error occurred attempting to 'do something.' Have you tried rebooting?
At line:1 char:1
+ Do-RealWork
+ ~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Write-Error], WriteErrorException
    + FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Do-RealWork

C:\> ▏
4b9b3361

Ответ 1

Если для автоматической переменной $ErrorView значение 'CategoryView' PowerShell вместо этого выводит краткие однострочные сообщения об ошибках, но это представление не всегда может содержать достаточно информации, поскольку сообщение об ошибке обычно не включается;с положительной стороны, текст, переданный в Throw "...", отражается, но, напротив, вывод Write-Error не содержит никакой конкретной информации, пока действует 'CategoryView'.
Для версии 6 обсуждается добавление нового представления ошибок в PowerShell, которое является однострочным, но всегда содержит всю важную информацию.

При условии, что ваш код PowerShell запускается из консоли (использует хост консоли), используйте [Console]::Error.WriteLine(), который безоговорочно записывает в stderr внешнего мира (стандартный поток ошибок):

[Console]::Error.WriteLine("An error occurred ... Have you tried rebooting?")

Замечания:

  • Это не будет работать с неконсольных хостов, таких как PowerShell ISE.

  • [Console]::Error.WriteLine() Вывод [Console]::Error.WriteLine() не печатается красным цветом в консоли [1].


К сожалению, нет единого решения, которое бы работало как внутри PowerShell (на разных хостах), так и вне его:

  • [Console]::Error.WriteLine(), при правильной записи в stderr для внешнего мира, его вывод не может быть захвачен или подавлен внутри PowerShell и работает только с консольным хостом PowerShell.

  • Аналогично, $host.ui.WriteErrorLine(), хотя и работает со всеми хостами, это метод пользовательского интерфейса, который работает и вне потоковой системы PowerShell, и, следовательно, его вывод также не может быть захвачен или подавлен в PowerShell.
    Что еще более важно, он не пишет в stderr внешнего мира (в этом отношении он ведет себя как Write-Error, см. Ниже).

  • Внутри PowerShell только Write-Error записывается в поток ошибок PowerShell, поэтому его выходные данные могут быть зафиксированы/подавлены.
    Однако, к сожалению, Write-Error (не считая шумного) не пишет во внешний мир stderr, если только странным образом не перенаправляется stderr - подробности смотрите в моем ответе.


[1] Питер (сам ОП) предлагает обходной путь для этого:

[Console]::ForegroundColor = 'red'
[Console]::Error.WriteLine("An error occurred ... Have you tried rebooting?")
[Console]::ResetColor()

Полезный ответ suneg предоставляет функциональную оболочку для него.

К счастью, PowerShell автоматически пропускает цветовые коды, когда обнаруживает, что вывод перенаправляется (в файл).

Ответ 2

Основываясь на идее предыдущего ответа, вы можете временно переопределить встроенный командлет Write-Error с помощью пользовательской функции.

# Override the built-in cmdlet with a custom version
function Write-Error($message) {
    [Console]::ForegroundColor = 'red'
    [Console]::Error.WriteLine($message)
    [Console]::ResetColor()
}

# Pretty-print "Something is wrong" on stderr (in red).
Write-Error "Something is wrong"

# Setting things back to normal 
Remove-Item function:Write-Error

# Print the standard bloated Powershell errors
Write-Error "Back to normal errors"

При этом вы используете тот факт, что функции Powershell имеют приоритет над командлетами.

https://technet.microsoft.com/en-us/library/hh848304.aspx

Это самый элегантный подход, который я смог придумать, чтобы показывать красивые и краткие сообщения об ошибках, а также позволял TeamCity легко обнаруживать проблемы.

Ответ 3

На мой взгляд, лучший способ отловить ошибки в PowerShell - использовать следующее:

$Error[0].Exception.GetType().FullName

Вот пример того, как использовать это правильно. В основном, тестируйте то, что вы пытаетесь сделать в PowerShell, с различными сценариями, в которых ваш сценарий потерпит неудачу.

Вот типичное сообщение об ошибке PowerShell:

PS C:\> Stop-Process -Name 'FakeProcess'
Stop-Process : Cannot find a process with the name "FakeProcess". Verify the process name and call the cmdlet again.
At line:1 char:1
+ Stop-Process -Name 'FakeProcess'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (FakeProcess:String) [Stop-Process], ProcessCommandException
    + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.StopProcessCommand

Далее вы получите исключение из сообщения об ошибке:

PS C:\> $Error[0].Exception.GetType().FullName
Microsoft.PowerShell.Commands.ProcessCommandException

Вы должны настроить свой код на перехват сообщения об ошибке следующим образом:

Try
{
    #-ErrorAction Stop is needed to go to catch statement on error
    Get-Process -Name 'FakeProcess' -ErrorAction Stop
}
Catch [Microsoft.PowerShell.Commands.ProcessCommandException]
{
    Write-Host "ERROR: Process Does Not Exist. Please Check Process Name"
}

Вывод будет выглядеть следующим образом вместо стандартной ошибки Powershell в приведенном выше примере:

ERROR: Process Does Not Exist. Please Check Process Name

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

Try
{
    Get-Process -Name 'FakeProcess' -ErrorAction Stop
}

Catch [Microsoft.PowerShell.Commands.ProcessCommandException]
{
    Write-Host "ERROR: Process Does Not Exist. Please Check Process Name"
}

Catch [System.Exception]
{
    Write-Host "ERROR: Some Error Message Here!"
}

Catch
{
    Write-Host "ERROR: I am a blanket catch to handle all unspecified errors you aren't handling yet!"
}

Ответ 4

Недавно мне нужно было решить эту проблему самостоятельно, поэтому я собрал функцию Write-ErrorMessage, как описано здесь: https://intellitect.com/powershell-write-error-without-writing-stack-trace/

В частности, я использовал комбинацию

Write-Error -Message $err -ErrorAction SilentlyContinue
$Host.UI.WriteErrorLine($errorMessage)