【发布时间】:2020-03-06 14:07:39
【问题描述】:
由于我没有通过搜索论坛找到解决方案并花了一些时间来找出如何正确解决问题,因此我将问题与可行的解决方案一起放在这里。
场景:在 Powershell 中,需要远程执行存储在变量中的脚本块并捕获其输出以进行进一步处理。除非脚本故意生成,否则屏幕上不应出现任何输出。脚本块可以包含写警告命令。
【问题讨论】:
标签: powershell remoting
由于我没有通过搜索论坛找到解决方案并花了一些时间来找出如何正确解决问题,因此我将问题与可行的解决方案一起放在这里。
场景:在 Powershell 中,需要远程执行存储在变量中的脚本块并捕获其输出以进行进一步处理。除非脚本故意生成,否则屏幕上不应出现任何输出。脚本块可以包含写警告命令。
【问题讨论】:
标签: powershell remoting
请注意,感兴趣的行为通常适用于 PowerShell 命令,而不仅仅是在 Invoke-Command 和 - generally to be avoided - Invoke-Expression 的上下文中;在您的情况下,只需要解决一个错误。[1]
您的own answer 展示了如何将单个特定输出流重定向到成功输出流;例如,3>&1 将 (>&) 警告 流 (3) 重定向到 成功(输出)流 (@987654335 @)。
& 表示重定向目标是一个流,而不是一个文件;有关 PowerShell 输出流的更多信息,请参阅about_Redirection。
如果您想将所有输出流重定向到成功输出流,请使用重定向*>&1
通过将所有流重定向到输出流,它们的组合输出可以在变量中捕获、重定向到文件或通过管道发送,而默认情况下仅捕获成功 输出流 (1)。
单独,您可以使用名为-*Variable 的common 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>&1 和5>&1(或*>&1)间接捕获它们,这需要您从combined 流,通过输出对象过滤 type:
重要:
详细 (4) 和调试 (5) 流 是仅有的两个静默源流默认;也就是说,除非这些流通过-Verbose / -Debug 或它们的preference-variable 等价物$VerbosePreference = 'Continue' / $DebugPreference = 'Continue' 显式打开,不会发出任何东西,也不会发出任何东西捕获。
信息流 (5) 默认情况下仅在输出时静音;也就是说,写入信息流(使用Write-Information)总是将对象写入流,但默认情况下它们不显示(它们仅显示为-InformationAction Continue/$InformationPreference = 'Continue')
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 使用夸大其词:这是明智的recommendation by a core member of the PowerShell team 并且有一个ongoing discussion on how to best frame this recommendation in the official documentation。请注意一般这个词:重点是虽然可能有合法用途(当然,您针对流捕获错误的解决方法是一种),但通常有更好和更安全的解决方案
脚本块包含在$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>&1 和5>&1 参数添加到Invoke-Expression,代码块中写入其他输出流(详细、调试)的命令似乎也不会被捕获。但是,Invoke-Command 以上述方式正确捕获了流 #2(错误)。
【讨论】: