如何在PowerShell中显示一个“裸”的错误信息而不带有相应的堆栈跟踪?

45
我该如何在PowerShell中写入标准错误,或者陷阱错误,使得:
  • 错误消息以错误的形式显示(真正地将其写入标准错误以便TeamCity和Octopus将其视为错误)
  • 没有堆栈跟踪垃圾混淆我的简洁错误消息
这些年来,我靠throw错误或通过Write-Error编写信息来生存,但我已经累了,老了,在我的脚本中,我只想看到一个简洁的错误消息。我尝试了所有的组合,包括trapthrowWrite-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:\> ▏

2
顺便提一下,$error[0].Exception.Message 包含了错误信息,但我不确定这对你有用。 - sodawillow
@sodawillow,这对我很有帮助,谢谢你。 - undefined
6个回答

27
前言关于什么不起作用:
$ErrorView preference variable设置为'CategoryView'会导致PowerShell输出简洁的单行错误表示,但这种表示可能不总是包含足够的信息,因为错误消息通常不包括在内;另一方面,传递给Throw "..."的文本会被反映出来,但与此相反,在'CategoryView'生效时,Write-Error输出不包含任何特定信息。
虽然PowerShell (Core) 7+现在默认使用更简洁的错误格式(在最简单的情况下仅打印错误消息)通过其新的$ErrorView默认值ConciseView,但此输出仅在特定情况下为单行。 值得注意的是,如果错误发生在脚本(.*ps1文件)或从脚本中点源的function中,则仍然为多行输出,此时错误消息之前会有额外的行报告脚本的文件路径、源代码行和行号。
只要你的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的流系统之外也可以工作的UI方法,因此其输出也无法被捕获或抑制在PowerShell中。
    更重要的是,它不会写入到外部世界的stderr(在这方面和Write-Error的行为类似,请参阅下面)。

  • 在PowerShell的内部,只有Write-Error可以写入到PowerShell的错误流,因此其输出可以被捕获/抑制。
    然而,不幸的是,Write-Error(除了喧闹之外)不会写入到外部世界的stderr,除非奇怪地显式地进行了重定向 - 有关详细信息,请参阅我的这个答案


[1] 彼得(本人)提供了一个解决方法:

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

suneg 的有用回答 为此提供了一个函数包装器。

幸运的是,PowerShell 在检测到输出被重定向(到文件)时会自动省略颜色代码。


3
好的回答。除了使用 [Console]::Error.WriteLine 之外,我想推荐另一种方法。该方法不仅不能在 ISE 中使用,而且不会添加到 $Error 变量中。如果我要使用 Write-Error,那是因为我希望稍后使用 $Error 变量处理错误,然后我会完全隐藏输出:Write-Error "hmm" -ErrorAction SilentlyContinue,并且可以选择在此时以红色显示错误文本,我将使用Write-Host "hmm" -ForegroundColor Red,不过需要注意的是,Write-Host 文本无法管道化,并且也不能将其重定向到文件中。 - papo
谢谢,@papo。在PSv5+中,您可以使用6>重定向Write-Host输出。 - mklement0

15

借鉴之前一个回答的思路,您可以使用自定义函数暂时覆盖内置的Write-Error cmdlet。

# 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函数优先于cmdlet的事实。

https://technet.microsoft.com/zh-cn/library/hh848304.aspx

这是我能想到的最优雅的方法,既可以显示简洁美观的错误消息,又可以让TeamCity轻松检测问题。


4

最近我自己遇到这个问题,因此我按照这里的详细说明编写了一个Write-ErrorMessage函数。

具体而言,我利用了以下组合:

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

2
欢迎提供解决方案的链接,但请确保您的答案即使没有链接也是有用的:在链接周围添加上下文,以便其他用户知道它是什么以及为什么存在,然后引用您链接的页面中最相关的部分,以防目标页面不可用。仅仅是一个链接的答案可能会被删除。 - Zoe stands with Ukraine
1
在Sitecore PSE中,仅使用$Host.UI.WriteErrorLine()即可解决问题,而不是使用Write-Error。 - Yuriy

1
在我看来,捕获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!"
}

1
使用 Write-Output 编写错误消息是不明智的,因为它会写入到 PowerShell 的“成功”(输出)流中。除此之外,你的帖子更侧重于异常处理本身,而不是 OP 的问题所在(他在问题中清楚地展示了如何捕获异常)。 - mklement0
这种情况下,使用Write-Host会更好吗? - Tyler Helder
说实话,我希望有人能告诉我是否可以在$host上设置一个选项来禁止堆栈跟踪。我真的需要真正的错误(写入stderr),以便错误被传递到寻找错误输出的地方(例如TeamCity构建)。在TeamCity的20000行构建日志中查找错误消息很困难;这就是为什么我们需要将其写入stderr的原因。 - Peter Seale
@TylerHelder: Write-Host稍微好一些,因为它不会污染输出流,但最终你需要的是写入专用的_error_流的东西 - 从PowerShell自身的角度和外部世界的角度来看都是如此。可悲的是,即使是PowerShell自己冗长的Write-Error也不能满足这些标准。 - mklement0
1
@PeterSeale:将$ErrorView偏好变量设置为'CategoryView'会输出更简洁的错误表示,但是,遗憾的是,通常这还不够,因为实际的错误_消息_丢失了。 - mklement0

0

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#errorview


1
值得注意的是,在PowerShell 7+中,“ConciseView”是默认视图。换句话说:只有在您想要恢复旧的、嘈杂的显示时,才需要设置$ErrorView$ErrorView = 'NormalView')。然而,请注意,即使使用“ConciseView”,您仍然可以获得多行错误消息,特别是如果错误发生在脚本(.*ps1文件)或从脚本中点源的function中,(包括配置文件脚本),在这种情况下,错误消息之前会有额外的行报告脚本的文件路径、源代码行和行号。 - mklement0

0

基于suneg的回答,我编写了以下函数,使您可以轻松地将Write-Error与自定义函数交换并返回。我还添加了一个检查,以确定用户是否从PowerShell ISE调用write-error。

# 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"

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接