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

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

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

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

try {
  throw "error" #sample code for Stackru. 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:\> ▏

6 ответов

Решение

Настройка автоматического $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() вывод не печатается красным в консоли [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 автоматически пропускает цветовые коды, когда обнаруживает, что вывод перенаправляется (в файл).

Основываясь на идее предыдущего ответа, вы можете временно переопределить встроенный командлет 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 легко обнаруживать проблемы.

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

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

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

На мой взгляд, лучший способ отловить ошибки в 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!"
    }

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

Powershell 7 предоставляет новую категорию просмотра ошибок «ConciseView», которая должна подавлять «шумы».

PowerShell:

      $ErrorView = 'ConciseView'
Get-ChildItem -path 'C:\NoRealDirectory'

Выход:

      Get-ChildItem: Cannot find path 'C:\NoRealDirectory' because it does not exist.

powershell-7.2#просмотр ошибок

Основываясь на ответе suneg, я написал следующие функции, которые позволят вам легко заменить ошибку записи на пользовательскую функцию и обратно. Я также добавил проверку, вызывает ли пользователь ошибку записи из PowerShell ISE.

# Override the built-in cmdlet with a custom version
function New-ErrorFunc {
        function Dyn($message){
            param($message,$ErrorAction)
            if($psISE){
                $Host.UI.WriteErrorLine($message)
            }
            else{
            [Console]::ForegroundColor = 'red'
            [Console]::Error.WriteLine($message)
            [Console]::ResetColor()
            }
           if($ErrorAction -eq 'Stop'){
           Break
           }
        }

    return ${function:Dyn}
}
function Set-ErrorFunc(){
    param([bool]$custom=$true)
    if($custom){
    $dynfex= New-ErrorFunc
    Invoke-Expression -Command "function script:Write-Error{ $dynfex }"
    }
    else {
        $custom= Get-Command Write-Error | Where-Object {$_.CommandType -eq 'Function'} 
        if($custom){ Remove-Item function:Write-Error }
   }
}

#User our Custom Error Function
Set-ErrorFunc 
# Pretty-print "Something is wrong" on stderr (in red).
Write-Error "Something is wrong"

# Setting things back to normal 
Set-ErrorFunc -custom $false
# Print the standard bloated Powershell errors
Write-Error "Back to normal errors"
Другие вопросы по тегам