如果之后抛出错误,则阵列的Powershell写入输出将丢失

如果之后抛出错误,则阵列的Powershell写入输出将丢失,powershell,Powershell,因此,在Powershell中,如果您编写了一些输出行,然后抛出一个错误,那么您将期望得到错误之前发生的输出。因此,如果您执行以下操作: Write-Output ("test output 1") Write-Output ("test output 2") Write-Output ("test output 3") Write-Output ("test output 4") Write-Output (&qu

因此,在Powershell中,如果您编写了一些输出行,然后抛出一个错误,那么您将期望得到错误之前发生的输出。因此,如果您执行以下操作:

Write-Output ("test output 1")
Write-Output ("test output 2")
Write-Output ("test output 3")
Write-Output ("test output 4")
Write-Output ("test output 5")
throw "pretend error"
您将获得预期的结果:

test output 1
test output 2
test output 3
test output 4
test output 5
pretend error
At C:\Powershell\Test.ps1:7 char:1
+ throw "pretend error"
+ ~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : OperationStopped: (pretend error:String) [], RuntimeException
    + FullyQualifiedErrorId : pretend error
但是,如果在数组中写入输出一组对象,然后抛出一个错误,那么数组的写入输出(以及之后的任何内容)将永远不会出现。因此,如果您运行以下命令:

Write-Output ("test output 1")
Write-Output ("test output 2")
Write-Output ("test output 3")
Write-Output ("test output 4")
Write-Output ("test output 5")
$testlist = @()
$count = 1
While($count -lt 6)
{
    $testobj = new-object psobject -prop @{Name="array object $count"}
    $testlist += $testobj
    $count +=1
}
Write-Output $testlist 
Write-Output ("test output 6")
Write-Output ("test output 7")
throw "pretend error"
。。。在“测试输出5”之后,您不会得到任何输出。好像输出数组会使所有内容都得到缓冲,然后错误会丢失缓冲区。与Powershell使用阵列管道的方式有关吗

所以我的问题是:我需要做什么来确保写入输出不会因为后续错误而丢失。这使得日志记录非常困难


这是在Powershell 5.1中,非常有趣。确保显示输出的方法与将
写入主机
写入输出
混合时确保输出顺序正确的方法相同-在每行之后使用
输出主机

Write-Output ("test output 1") | Out-Host
Write-Output ("test output 2") | Out-Host
Write-Output ("test output 3") | Out-Host
Write-Output ("test output 4") | Out-Host
Write-Output ("test output 5") | Out-Host
$testlist = @()
$count = 1
While($count -lt 6)
{
    $testobj = new-object psobject -prop @{Name="array object $count"}
    $testlist += $testobj
    $count +=1
}
Write-Output $testlist  | Out-Host
Write-Output ("test output 6") | Out-Host
Write-Output ("test output 7") | Out-Host
throw "pretend error"
您还可以在子表达式
$(..)
中包含多组行,如果适合这种情况,还可以在主机外执行一个
操作

$(Write-Output ("test output 1")
Write-Output ("test output 2")
Write-Output ("test output 3")
Write-Output ("test output 4")
Write-Output ("test output 5")
$testlist = @()
$count = 1
While($count -lt 6)
{
    $testobj = new-object psobject -prop @{Name="array object $count"}
    $testlist += $testobj
    $count +=1
}
Write-Output $testlist
Write-Output ("test output 6")
Write-Output ("test output 7")) | Out-Host
throw "pretend error"

您似乎看到了一个非常不幸的副作用,即在PowerShell v5中隐式使用300毫秒延迟,以更好地计算合适的列宽,这在中进行了详细介绍和讨论;但是,您的症状是如此的有问题,以至于需要一份新的bug报告

由于
$testlist
数组包含属性少于
4
的对象(即在您的情况下只有一个),并且由于对象的类型(
[pscustomobject]
)没有与其关联的格式数据,PowerShell隐式使用
格式表
进行输出格式设置

当遇到
throw
语句时,300毫秒的延迟还没有过去,因为
throw
终止运行空间[1],所以
格式表
输出(以及随后的
写入输出
输出)永远不会显示

-不完美-解决方法是强制输出同步,可通过以下三种方式之一实现:

  • 使用
    Out Host
    ,它同步使用默认输出行为,但也意味着从PowerShell内部无法捕获或重定向输出;但是,当从外部通过PowerShell的CLI调用时,
    Out Host
    输出将转到stdout,也可以捕获

  • 明确使用
    格式表
    。这也适用于显示,但会将数据的输出更改为不相关的对象,这些对象是格式化指令,PowerShell本身会将这些指令转换为通常的丰富显示格式,但如果您想要捕获输出,这些将是无用的—您的原始数据将丢失

  • 如果您希望至少允许从PowerShell内部捕获原始对象的格式化字符串表示形式,则可以使用
    Out string
    ,它将格式化表示形式输出为单个多行字符串(遗憾的是,后面有一个额外的换行符);从PowerShell外部调用时,其效果本质上与调用主机相同

请注意,在您的情况下,解决方法只需应用于
$testlist
语句:

# Or ... | Out-Host or ... | Format-Table - see comments above.
$testlist | Out-String
有一个替代方案,但更模糊的解决方法依赖于使用
开始睡眠
等待至少300毫秒,并在隐式
格式表
调用后输出至少一个对象,如本简化示例所示:

'before'
[pscustomobject] @{ foo = 1 }
Start-Sleep -Milliseconds 300  # wait for implicit Format-Table
'after' # force output of the table by outputting at least one more object
throw "error"
此解决方案的优点是您的原始对象以这种方式保存在输出中,这将允许您从PowerShell内部捕获它们,例如,
$output=try{./someScript.ps1}catch{Write Error$\}


最后,您的代码可以被简化;总而言之:

# Use *implicit* output - no need for Write-Output
# If you do use Write-Output: separate the arguments with *spaces*,
# don't put (...) around the argument list.
"test output 1"
# ...


# Implicitly capture the foreach loop output in an array.
# This is much more efficient than using += to "extend" an array
# in a loop (which requires creating a *new* array every time).
[array] $testlist = 
  foreach ($count in 1..6) {
    # Simpler and more efficient PSv3+ method for constructing
    # custom objects.
    [pscustomobject] @{ Name="array object $count" }
  }

# Apply the workaround.
$testlist | Out-String

# Use implicit output again.
"test output 6"
"test output 7"

throw "pretend error"


[1] 也就是说,
throw
生成一个运行空间终止(脚本终止)错误,该错误会立即中止整个执行-与语句终止错误不同,语句终止错误只中止手头的语句,例如.NET方法调用中发生异常时会发生的错误;通过将
$ErrorActionPreference
首选项变量设置为
'Stop'
,您可以将所有语句终止错误以及所有非终止错误转换为运行空间终止错误-请参阅。

谢谢,这是可行的,但令人费解。因为根据帮助,“Out主机自动附加到执行的每个命令”。看起来如果我只是把主机放在数组输出线上,其余的都可以
Write output$testlist | Out Host
啊,有人有百科全书式的解释,我必须切换接受的答案,非常抱歉。你先到的!谢谢,@iRon,但事实证明,
开始睡眠
也能工作(据我所知,其行为与
等待事件-超时
)相同),但之后至少需要输出一个额外的对象-请查看我的更新。谢谢,这是非常模糊的,我在一百万年内永远也不会明白这一点。如果您对上下文感兴趣:我肯定会称之为bug,因为它非常出乎意料,并且对我产生了现实世界的影响-检查一个重要的通宵作业运行日志,关于由于此bug而丢失错误之前发生的事情的关键信息。此外,缺少的信息使它看起来像是在脚本中的不同点发生的错误。在某种程度上削弱了我对Powershell的信任。此外,如果您想了解我对设计决策的意见,请联系我们