【问题标题】:Powershell Write-Output of array goes missing if error thrown afterwards如果之后抛出错误,则数组的 Powershell 写入输出丢失
【发布时间】:2021-02-17 05:22:09
【问题描述】:

在 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 中

【问题讨论】:

    标签: powershell


    【解决方案1】:

    您似乎看到了在 PowerShell v5 中隐式使用Format-Table 中引入的有问题的 300 毫秒延迟的非常不幸的副作用为了更好地计算合适的列宽,在this answer中有详细说明,在GitHub issue #4594中有讨论;但是,您的症状非常有问题,需要新的错误报告GitHub issue #13985

    因为您的 $testlist 数组包含的对象少于 4 属性(即在您的情况下只有一个),并且因为您的对象类型 ([pscustomobject]) 具有 没有与之关联的格式化数据,PowerShell隐式使用Format-Table进行输出格式化。

    在遇到throw 语句时,300 毫秒的延迟还没有过去,因为throw 终止了运行空间[1]Format-Table 的输出(和随后的Write-Output 输出)永远不会显示。

    不完美 - 解决方法强制输出为同步,这可以用一个来完成三种方式:

    • 使用Out-Host,它使用默认输出行为同步,但这也意味着从PowerShell内部你将无法捕获或重定向输出;但是,当通过 PowerShell 的 CLI 从外部调用时Out-Host 的输出会进入标准输出并且也可以被捕获。

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

    • 如果您希望最少允许从 PowerShell 中捕获原始对象的格式化字符串表示,您可以使用Out-String,它将格式化表示输出为单个多行字符串 (不幸的是,有一个额外的尾随换行符);当从 PowerShell 外部调用时,效果本质上与调用 Out-Host 相同。

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

    # Or ... | Out-Host or ... | Format-Table - see comments above.
    $testlist | Out-String
    

    有一个替代但更模糊的解决方法,它依赖于使用Start-Sleep 来等待至少 300 毫秒。 并在隐式调用 Format-Table 后至少再输出一个对象,如以下简化示例所示:

    '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 会生成一个 runspace 终止(脚本终止)错误,它会立即中止整个执行 - 与 statement 不同-终止错误,仅中止手头的语句,例如在 .NET 方法调用中发生异常时会发生;您可以通过将$ErrorActionPreference 首选项变量设置为'Stop' 将所有语句终止以及所有非终止错误转换为运行空间终止错误 - 请参阅this answer

    【讨论】:

    • 谢谢@iRon,但事实证明Start-Sleep 也可以工作(据我所知,其行为与Wait-Event -Timeout 相同),但您需要在之后至少输出一个附加对象它 - 请查看我的更新。
    • 谢谢你,这太晦涩难懂了,一百万年我都想不通。如果您对上下文感兴趣:我肯定会将此称为错误,因为它非常出乎意料并且对我产生了现实世界的影响 - 检查重要的夜间作业运行的日志,有关在错误丢失之前发生的事情的关键信息,因为这个错误。此外,缺少的信息使错误看起来像是在脚本中的不同点发生的。在某种程度上削弱了我对 Powershell 的信任。
    • 此外,如果您想了解我对有关格式化延迟的设计决策的看法:引入异步延迟似乎是作弊。就像他们已经建立了延迟,然后试图将其隐藏在地毯下。如果他们真的想等待 300 毫秒来采样数据,那么这个延迟应该是同步发生的。非常感谢。
    • 有趣的是,导致我的脚本出错的原因是长时间运行的 System.Net.WebClient.DownloadFile() 调用 - 它运行了几个小时才失败。但由于该命令在隐式格式表的 300 毫秒内开始,因此写入输出数据仍然丢失。
    • @codeulike:是的,如果在 300 毫秒内启动的命令期间发生 runspace 终止错误异常。窗口,无论它在异常发生之前运行了多长时间,都不会将控制权返回给 PowerShell,并且不会产生任何输出。但是,默认情况下,.NET 方法调用只是一个 statement 终止错误,这将 not 产生症状 - 除非$ErrorActionPreference = 'Stop' 有效,否则它会变成进入一个runspace-终止(脚本终止)的。
    【解决方案2】:

    这很有趣。确保看到输出的方法与混合Write-HostWrite-Output 时确保输出顺序正确的方法相同 - 在每行之后使用Out-Host

    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"
    

    您还可以在子表达式 $(..) 中包含一组行,如果适合这种情况,请添加一个 Out-Host

    $(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"
    

    【讨论】:

    • 谢谢,这可行,但令人费解。因为根据帮助“Out-Host 会自动附加到每个执行的命令”。看起来如果我只是将 Out-Host 放在数组输出线上,其余的都可以Write-Output $testlist | Out-Host
    • 啊,有人有百科全书式的解释,我将不得不切换接受的答案,非常抱歉。你先到了!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-23
    • 2019-03-02
    • 2020-12-24
    • 2013-08-18
    • 2021-06-26
    相关资源
    最近更新 更多