使用Invoke-Expression命令在PowerShell中标准错误和标准输出没有任何输出或错误信息。

4
我试图获取运行Java进程的stdoutstderr,看到了一种看起来很简单、很酷的方法,但它没有起作用:
$command = "java -jar secretserver-jconsole.jar -s 123456 Password"
Invoke-Expression $command -OutVariable output -ErrorVariable errors
Write-Host "stdout: $output"
Write-Host "stderr: $errors"

$output$errors没有显示任何内容。

我现在正在使用:

$output = cmd /c $command

这段代码确实可以输出 stdout,但是我想要的是错误信息 $error

我尝试了一个复杂的方法,但好像并没有执行该进程,因为返回的时间几乎是立即的,而正常进程需要几秒钟的时间。它也没有显示任何输出或错误:

$psi = New-object System.Diagnostics.ProcessStartInfo 
$psi.CreateNoWindow = $true 
$psi.UseShellExecute = $false 
$psi.RedirectStandardOutput = $true 
$psi.RedirectStandardError = $true 
$psi.FileName = 'java' 
$psi.Arguments = @("-jar","secretserver-jconsole.jar","-
s","123456","Password") 
$process = New-Object System.Diagnostics.Process 
$process.StartInfo = $psi 
[void]$process.Start()
$output = $process.StandardOutput.ReadToEnd() 
$process.WaitForExit()
Write-Host "stdout: $output"
(tried: adding:
do

    $process.StandardOutput.ReadLine()
}
while (!$process.HasExited)

在 PowerShell ISE 中尝试一下。它应该按照你想要的方式工作。 - user4003407
@PetSerAl: 运行在 ISE 中的两种方法中哪一种更好?为什么会有差异呢? - mklement0
@mklement0 第一个。因为ISE没有控制台。 - user4003407
1个回答

8
注意:下面的解决方案假定外部工具表现良好:将数据发送到标准输出,将错误消息发送到标准错误输出。如果某个工具意外地将错误消息发送到标准输出而不是标准错误输出,就像 OP 的情况一样,您必须自己解析标准输出以提取错误消息。
$stdout = java -jar secretserver-jconsole.jar -s 123456 Password 2>($tmpFile=New-TemporaryFile)
$stderr = Get-Content $tmpFile; Remove-Item $tmpFile
New-TemporaryFile需要PSv5,但在早期版本中可以使用[io.path]::GetTempFileName()。请注意,$stdout$stderr都将包含一个字符串数组,每个项表示一个输出行。外部实用程序的退出代码反映在PowerShell的自动$LASTEXITCODE变量中。请注意,捕获stdout和stderr 组合实际上更容易:
$stdoutAndStdErr = java -jar secretserver-jconsole.jar -s 123456 Password 2>&1

注意:结果中的每个 stderr 行都表示为一个 [System.Management.Automation.ErrorRecord] 实例,但在字符串上下文中评估时,它们会转换为该行的内容。

关于您尝试的内容:

通常不需要将命令存储在字符串中(因此很少需要使用Invoke-Expression,其使用通常应避免且可能存在风险)。

  • 要么:直接调用外部命令行(要注意PowerShell可能会对命令行进行不必要的解释;在PSv3中,--%停止解析标记可以关闭PowerShell的解释,但请注意其限制)。
  • 或者:如果您需要先将完整命令存储在变量中,请使用脚本块$cmd = { ... }),然后稍后使用&(调用运算符)或.(点源运算符)调用它——前者为PS变量创建子范围,后者则不会。
  • 或者:如果您需要以编程方式并有条件地构造要传递给给定外部程序的参数列表,请使用数组,如此答案所述。

-OutVariable-ErrorVariable这两个常用参数只适用于PowerShell的输出流,而不是外部世界的stdout和stderr。

  • PowerShell将stdout输出捕获,就好像它已经被发送到PowerShell的成功输出流一样,并将输出行视为一个字符串数组

  • stderr输出会通过(并且不会反映在$Error中),但您可以使用2>$file将其重定向到文件,或者使用2>&1将其合并到成功流中(在这种情况下,每个stderr行都表示为一个[System.Management.Automation.ErrorRecord]实例)。


我真的希望它能够工作。我尝试运行它,没有错误,并且它在$stdout中显示了一些东西,但是如果我故意让它失败,它会在$stdout上显示stderr。这可能是jar文件的问题,所以我尝试了一些不同的东西,其中命令是一个简单的dir c:\ temp,它在$stdout上显示了一些东西,然后我执行了command dir c:\ temp2(不存在),它确实在stderr上正确地显示了。不幸的是,我将不得不解析stdout以查找指示它是错误的内容。谢谢你对“。”、“&”、“-OutVariable/-ErrorVariable”等各种部分的澄清。 - archcutbank
@user372429:所以这个jar包的行为不良是将其错误消息发送到标准输出而不是标准错误输出?如果是这种情况,并且您同意此答案中提出的方法适用于行为良好的实用程序,我仍建议您接受此答案以为未来的读者提供指导。 - mklement0
绝对的,这个答案在大多数情况下是正确的,但似乎jar文件没有将错误发送到stderr。我想我可以尝试找到它使用的记录器或联系供应商并要求更新,但我不认为他们会特别迅速地回复。再次感谢您提供的信息。我已经标记它为一个好答案。 - archcutbank
@user372429:我很感激,绝对值得向供应商提供反馈。 - mklement0

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