如何从PowerShell写入标准错误,或捕获错误,以便:
错误- 信息显示为错误(真正写入标准错误,以便TeamCity和八达通将其视为错误)
- 没有堆栈跟踪垃圾混淆了我美丽,简洁的错误消息
这些年来,我一直通过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:> ▏
前言 关于什么不起作用:
-
将
$ErrorView
首选项变量$ErrorView
变量设置为'CategoryView'
会导致 PowerShell 改为输出简洁的单行错误表示形式,但此表示形式可能并不总是包含足够的信息,因为通常不包括错误消息;从好的方面来说,传递给Throw "..."
的文本会得到反映,但相比之下,Write-Error
输出在'CategoryView'
生效时不包含任何特定信息。 -
虽然PowerShell(核心)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内部(跨主机)工作,也可以在PowerShell外部工作:
[Console]::Error.WriteLine()
虽然可以正确写入外部世界的stderr,但无法在 PowerShell中捕获或抑制其输出,并且仅适用于 PowerShell控制台主机。同样,
$host.ui.WriteErrorLine()
,即使适用于所有主机,它也是一种在PowerShell的流系统之外工作的UI方法,因此它的输出也无法在PowerShell.
中捕获或抑制,更重要的是,它不会写入外部世界的stderr(它在这方面的行为类似于Write-Error
,见下文)。在PowerShell内部,只有
Write-Error
写入PowerShell的错误流,因此可以捕获/抑制其输出.
然而,不幸的是,Write-Error
(除了嘈杂)不会写入外部世界的stderr,除非奇怪的是,stderr被明确重定向- 有关详细信息,请参阅我的答案。
[1] Peter(OP本人)为此提供了一个解决方法:
[Console]::ForegroundColor = 'red'
[Console]::Error.WriteLine("An error occurred ... Have you tried rebooting?")
[Console]::ResetColor()
Suneg的有用答案为它提供了一个函数包装器。
幸运的是,PowerShell 在检测到输出正在重定向(到文件)时会自动省略颜色代码。
基于上一个答案中的想法,可以使用自定义函数临时替代内置的写入错误 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 Functions优先于cmdlet的事实。
https://technet.microsoft.com/en-us/library/hh848304.aspx
这是我能够想到的最优雅的方法,既可以显示漂亮简洁的错误消息,又可以让 TeamCity 轻松检测问题。
我最近需要自己解决这个问题,所以我把一个写错误消息函数放在一起,详见这里: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 块来处理代码中的多个错误。您还可以包含一个"一揽子"捕获块来捕获您尚未处理的所有错误。例:
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!"
}
根据 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"
Powershell 7提供了新的错误视图类别"简洁视图",该类别应抑制"噪音"。
Powershell:
$ErrorView = 'ConciseView'
Get-ChildItem -path 'C:NoRealDirectory'
输出:
Get-ChildItem: Cannot find path 'C:NoRealDirectory' because it does not exist.
PowerShell-7.2#错误视图