Windows $LastExitCode=0,但PowerShell中的$?=False。将stderr重定向到stdout会产生NativeCommandError

Windows $LastExitCode=0,但PowerShell中的$?=False。将stderr重定向到stdout会产生NativeCommandError,windows,powershell,command-line,Windows,Powershell,Command Line,为什么PowerShell在下面的第二个示例中表现出令人惊讶的行为 首先,一个理智行为的例子: PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?" Hello from standard error $LastExitCode=0 and $?=True Enable-ExperimentalFeature PSN

为什么PowerShell在下面的第二个示例中表现出令人惊讶的行为

首先,一个理智行为的例子:

PS C:\> & cmd /c "echo Hello from standard error 1>&2"; echo "`$LastExitCode=$LastExitCode and `$?=$?"
Hello from standard error
$LastExitCode=0 and $?=True
Enable-ExperimentalFeature PSNotApplyErrorActionToStderr
没有意外。我将消息打印为标准错误(使用
cmd
echo
)。我检查变量
$?
$LastExitCode
。正如预期的那样,它们分别等于True和0

但是,如果我要求PowerShell通过第一个命令将标准错误重定向到标准输出,我会得到一个NativeCommandError:

PS C:\> & cmd /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
cmd.exe : Hello from standard error
At line:1 char:4
+ cmd <<<<  /c "echo Hello from standard error 1>&2" 2>&1; echo "`$LastExitCode=$LastExitCode and `$?=$?"
    + CategoryInfo          : NotSpecified: (Hello from standard error :String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

$LastExitCode=0 and $?=False
只需将其作为
\job.ps1
运行,即可正常工作,并且不会发送电子邮件。但是,我是从另一个PowerShell脚本调用它的,记录到一个文件
\job.ps1 2>&1>log.txt
。在这种情况下,会发送一封电子邮件!使用错误流在脚本外部执行的操作会影响脚本的内部行为。观察一种现象会改变结果。这感觉像量子物理,而不是脚本

[有趣的是:
\job.ps1 2>&1
可能会爆炸,也可能不会爆炸,这取决于您运行它的位置]

(注意:这主要是猜测;我很少在PowerShell中使用许多本机命令,其他人可能比我更了解PowerShell内部)

我猜您在PowerShell控制台主机中发现了差异

  • 如果PowerShell接收到标准错误流中的内容,它将假定一个错误并抛出一个
    NativeCommandError
  • PowerShell只有在监视标准错误流时才能检测到此错误
  • PowerShell ISE必须监视它,因为它不是控制台应用程序,因此本机控制台应用程序没有可写入的控制台。这就是为什么在PowerShell ISE中,无论是否使用
    2>&1
    重定向操作符,此操作都会失败
  • 如果使用
    2>&1
    重定向操作符,控制台主机将监视标准错误流,因为必须重定向并读取标准错误流上的输出
  • 我猜控制台PowerShell主机是懒惰的,如果不需要对其输出进行任何处理,只需将本机控制台命令交给控制台即可


    我真的认为这是一个错误,因为PowerShell的行为因主机应用程序而异。

    此错误是PowerShell规范性错误处理设计的一个不可预见的后果,因此很可能永远无法修复。如果您的脚本仅与其他PowerShell脚本一起使用,则您是安全的。但是,如果您的脚本与来自世界各地的应用程序交互,那么这个bug可能会咬人

    PS> nslookup microsoft.com 2>&1 ; echo $?
    
    False
    
    明白了!尽管如此,在痛苦的抓挠之后,你永远不会忘记这一课

    使用
    ($LastExitCode-eq 0)
    而不是
    $?
    (我使用的是PowerShell v2。)

    $?
    ”变量记录在关于自动变量的
    中:

    $? Contains the execution status of the last operation
    $LastExitCode
    是1,因为那是java.exe的退出代码
    $?
    为False,因为shell所做的最后一件事失败了

    但如果我所做的只是把它们换过来:

    > java -jar foo; $LastExitCode; $? Unable to access jarfile foo 1 True …这似乎有点违反直觉,但现在是真的,因为脚本块的执行是成功的,即使在其中运行的命令不是成功的


    返回到
    2>&1
    重定向。。。。这会导致错误记录进入输出流,这就是关于
    NativeCommandError
    的冗长blob的原因。shell正在转储整个错误记录

    当您只想通过管道将
    stderr
    stdout
    组合在一起,以便将它们组合到日志文件或其他文件中时,这可能会特别烦人。谁想让PowerShell连接到他们的日志文件???如果我执行了
    ant build 2>&1>build.log
    ,那么任何转到
    stderr
    的错误都会添加PowerShell的nosey$0.02,而不是在我的日志文件中获得干净的错误消息

    但是,输出流不是文本流!重定向只是对象管道的另一种语法。错误记录是对象,因此您只需在重定向之前将该流上的对象转换为字符串:

    发件人:

    >cmd/c“echo Hello from standard error 1>&2”2>&1 cmd.exe:Hello from standard error 第1行字符数:4 +cmd&2“2>&1 +CategoryInfo:NotSpecified:(来自标准错误的Hello:String)[],RemoteException +FullyQualifiedErrorId:NativeCommandError 致:

    >cmd/c“echo Hello from standard error 1>&2”2>&1 |%{“$"”} 来自标准错误的您好 …并通过重定向到文件:

    > cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt Hello from standard error >cmd/c“echo Hello from standard error 1>&2”2>&1 |%{“$|”}| tee out.txt 来自标准错误的您好 …或者只是:

    > cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt >cmd/c“echo Hello from standard error 1>&2”2>&1 |%{“$"}>out.txt
    对我来说,这是一个错误操作偏好的问题。 当从ISE运行时,我在第一行中设置了$ErrorActionPreference=“Stop”,这就是截取所有事件,并将*>&1作为参数添加到调用中

    所以首先我有一句话:

    &$exe$parameters*>&1
    
    正如我所说,这不起作用,因为我之前在文件中有$ErrorActionPreference=“Stop”(或者可以在概要文件中全局设置,以便用户启动脚本)

    因此,我尝试将其包装在调用表达式中以强制执行ErrorAction:

    Invoke Expression-Command“&`“$exe`”$parameters*>&1”-错误操作继续
    
    这也不管用

    因此,我不得不回过头来使用临时覆盖的ErrorActionPreference进行黑客攻击:

    $old\u error\u action\u preference=$ErrorActionPreference
    尝试
    {
    $ErrorActionPreference=“继续”
    &$exe$parameters*>&1
    }
    最后
    {
    $ErrorActionPreference=$old\u error\u action\u preference
    }
    
    这对我有用

    我把它包装成一个函数:

    
    功能启动NativeExecutable
    {
    [CmdletBinding(SupportsShouldProcess=$true)]
    Param
    (
    [参数(强制)=
    > cmd /c "echo Hello from standard error 1>&2" 2>&1
    cmd.exe : Hello from standard error
    At line:1 char:4
    + cmd &2" 2>&1
        + CategoryInfo          : NotSpecified: (Hello from standard error :String) [], RemoteException
        + FullyQualifiedErrorId : NativeCommandError
    > cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" }
    Hello from standard error
    > cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } | tee out.txt
    Hello from standard error
    > cmd /c "echo Hello from standard error 1>&2" 2>&1 | %{ "$_" } >out.txt
    
    Enable-ExperimentalFeature PSNotApplyErrorActionToStderr
    
    & cmd /c "echo Hello from standard error 1>&2" 2>&1
    echo "`$LastExitCode=$LastExitCode and `$?=$?"
    echo "`$Error.Count=$($Error.Count)"