【问题标题】:Powershell: capturing remote output streams in Invoke-Command + Invoke-Expression combinationPowershell:在 Invoke-Command + Invoke-Expression 组合中捕获远程输出流
【发布时间】:2020-03-06 14:07:39
【问题描述】:

由于我没有通过搜索论坛找到解决方案并花了一些时间来找出如何正确解决问题,因此我将问题与可行的解决方案一起放在这里。

场景:在 Powershell 中,需要远程执行存储在变量中的脚本块并捕获其输出以进行进一步处理。除非脚本故意生成,否则屏幕上不应出现任何输出。脚本块可以包含写警告命令。

【问题讨论】:

    标签: powershell remoting


    【解决方案1】:

    请注意,感兴趣的行为通常适用于 PowerShell 命令,而不仅仅是在 Invoke-Command 和 - generally to be avoided - Invoke-Expression 的上下文中;在您的情况下,只需要解决一个错误[1]


    您的own answer 展示了如何将单个特定输出流重定向到成功输出流;例如,3>&1 将 (>&) 警告 流 (3) 重定向到 成功(输出)流 (@987654335 @)。

    & 表示重定向目标是一个,而不是一个文件;有关 PowerShell 输出流的更多信息,请参阅about_Redirection

    如果您想所有输出流重定向到成功输出流,请使用重定向*>&1

    通过将所有流重定向到输出流,它们的组合输出可以在变量中捕获、重定向到文件或通过管道发送,而默认情况下仅捕获成功 输出流 (1)。

    单独,您可以使用名为-*Variablecommon parameters 参数来捕获单个流输出在变量中 用于一些 流,即:

    • 1(成功):-OutVariable
    • 2(错误):-ErrorVariable
    • 3(警告):-WarningVariable
    • 6(信息):-InformationVariable

    确保仅通过名称指定目标变量,不带$前缀;例如,要捕获变量 $warnings 中的警告,请使用
    -WarningVariable warnings,例如以下示例:
    Write-Warning hi -WarningVariable warnings; "warnings: $warnings"

    请注意,使用-*Variable,流输出被收集在变量中无论您是否静音或什至忽略该流值得注意-ErrorAction Ignore 除外,在这种情况下,-ErrorVariable 变量填充(并且错误也记录在 automatic $Error variable 中,否则记录会话中发生的所有错误)。

    一般来说,-{StreamName}Action SilentlyIgnore 似乎等同于{StreamNumber}>$null


    注意上面没有详细 (4) 和调试 (5) 流;您只能通过4>&15>&1(或*>&1间接捕获它们,这需要您从combined 流,通过输出对象过滤 type:

    重要

    • 详细 (4) 和调试 (5) 流 是仅有的两个静默流默认;也就是说,除非这些流通过-Verbose / -Debug 或它们的preference-variable 等价物$VerbosePreference = 'Continue' / $DebugPreference = 'Continue' 显式打开不会发出任何东西,也不会发出任何东西捕获

    • 信息流 (5) 默认情况下仅在输出时静音;也就是说,写入信息流(使用Write-Information总是将对象写入流,但默认情况下它们不显示(它们仅显示为-InformationAction Continue/$InformationPreference = 'Continue')

      • 从 v5 开始,Write-Host 现在也写入信息流,虽然 输出 默认打印,但可以使用 6>$null 或 @987654376 抑制@(但不是-InformationAction SilentlyContinue)。
    # Sample function that produces success and verbose output.
    # Note that -Verbose is required for the message to actually be emitted.
    function foo { Write-Output 1; Write-Verbose -Verbose 4 }
    
    # Get combined output, via 4>&1
    $combinedOut = foo 4>&1
    
    # Extract the verbose-stream output records (objects).
    # For the debug output stream (5), the object type is
    # [System.Management.Automation.DebugRecord]
    $verboseOut = $combinedOut.Where({ $_ -is [System.Management.Automation.VerboseRecord] })
    

    [1] 流捕获错误,从 PowerShell v7.0 开始:

    简而言之:remoting(例如这里的 Invoke-Command -Session)、后台作业和所谓的 minishells(将脚本块传递给 PowerShell CLI 以在子进程中执行命令),只能按预期捕获成功(1)和错误(2)流;所有其他都意外地传递给主机(显示) - 请参阅this GitHub issue

    您的命令应该 - 但目前不 - 按如下方式工作,这将消除对Invoke-Expression 的需要:

    # !! 3>&1 redirection is BROKEN as of PowerShell 7.0, if *remoting* is involved
    # !! (parameters -Session or -ComputerName).
    $RemoteOutput = 
      Invoke-Command -Session $Session $Commands 3>&1 -ErrorVariable RemoteError 2>$null
    

    也就是说,原则上您应该能够将包含脚本块的$Commands 变量直接作为(隐含的)-ScriptBlock 参数传递给Invoke-Command

    【讨论】:

    • 感谢您的详细解释,但正如您自己提到的,我的帖子仅涉及解决无法按预期捕获 Invoke-Expression 的远程输出的这种特定情况。关于您的评论“通常要避免 - 调用表达式”。我知道这一点,但这似乎是夸大其词。我知道该建议背后的原因,但我不认为它太重要。最后,代码的正确性无论如何都是一个测试问题。
    • 如果您对全局任务感兴趣,可以从外部文件批量执行代码块。该文件的内容是事先不知道的。通过使用 Invoke-Command 的参数直接执行命令不允许正确捕获远程输出,有时根本无法正常抛出远程执行错误。
    • 谢谢,@Evgeny。是的,从远程调用直接捕获流是有问题的,正如脚注希望说明的那样;我不知道其他错误,所以我不能谈论你关于引发远程执行错误的声明。
    • 至于警告Invoke-Expression 使用夸大其词:这是明智的recommendation by a core member of the PowerShell team 并且有一个ongoing discussion on how to best frame this recommendation in the official documentation。请注意一般这个词:重点是虽然可能有合法用途(当然,您针对流捕获错误的解决方法是一种),但通常有更好和更安全的解决方案
    【解决方案2】:

    脚本块包含在$Commands 变量中。 $Session 是一个已经建立的 Powershell 远程会话。

    任务由以下命令解决:

    $RemoteOutput = 
      Invoke-Command -Session $Session {
        Invoke-Expression $Using:Commands 3>&1
      } -ErrorVariable RemoteError 2>$null
    

    执行命令后,脚本块的所有输出都包含在$RemoteOutput 中。远程代码执行过程中产生的错误放在$RemoteError

    补充说明。 Invoke-Expression 代码块中的 Write-Warning 生成其自己的输出流,Invoke-Command 未捕获该流。在变量中捕获它的唯一方法是使用3>&1 将该流重定向到Invoke-Expression 的标准流。即使将4>&15>&1 参数添加到Invoke-Expression,代码块中写入其他输出流(详细、调试)的命令似乎也不会被捕获。但是,Invoke-Command 以上述方式正确捕获了流 #2(错误)。

    【讨论】:

      猜你喜欢
      • 2018-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多