PowerShell:ForEach-Object的神秘-RemainingScripts参数

Fan*_*hou 10 powershell powershell-3.0

简短的问题:任何人都有关于ForEach-Object的-RemainingScripts参数的详细信息?

长问题:

我刚从上周开始学习PowerShell,我将通过每个Cmdlet来了解更多细节.基于公共文档,我们知道ForEach-Object可以有Begin-Process-End块,如下所示:

Get-ChildItem | foreach -Begin { "block1";
    $fileCount = $directoryCount = 0} -Process { "block2";
    if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}} -End {
    "block3"; "$directoryCount directories and $fileCount files"}
Run Code Online (Sandbox Code Playgroud)

预期结果:"block1"和"block3"为1次,对于传入的每个项目重复"block2",并且dir计数/文件计数都是正确的.到现在为止还挺好.

现在,有趣的是,以下命令也起作用并给出完全相同的结果:

Get-ChildItem | foreach { "block1"
    $fileCount = $directoryCount = 0}{ "block2";
    if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}}{
    "block3"; "$directoryCount directories and $fileCount files"}
Run Code Online (Sandbox Code Playgroud)

只有3个ScriptBlocks传递给foreach.根据手册,第一个进入-Process(位置1).但剩下的2怎么样?根据手册,没有"位置2"的参数.所以我转向Trace-Command,发现后两个脚本块实际上是RemainingScripts,因为"IList with 2 elements".

BIND arg [$fileCount = $directoryCount = 0] to parameter [Process]
BIND arg [System.Management.Automation.ScriptBlock[]] to param [Process] SUCCESSFUL
BIND arg [System.Collections.ArrayList] to parameter [RemainingScripts]
BIND arg [System.Management.Automation.ScriptBlock[]] to param [RemainingScripts] SUCCESSFUL
Run Code Online (Sandbox Code Playgroud)

所以,如果我将命令更改为:

# No difference with/without the comma "," between the last 2 blocks
Get-ChildItem | foreach -Process { "block1"
    $fileCount = $directoryCount = 0} -RemainingScripts { "block2";
    if ($_.PsIsContainer) {$directoryCount++} else {$fileCount++}},{
    "block3"; "$directoryCount directories and $fileCount files"}
Run Code Online (Sandbox Code Playgroud)

仍然,完全相同的结果.

正如您所注意到的,所有3个命令都给出了相同的结果.这提出了一个有趣的问题:后两个命令(隐式地)都指定了-Process,但ForEach-Object令人惊讶地最终使用-Process的参数作为"-Begin"!(脚本块在开头执行一次).

这个实验表明:

  1. -RemainingScripts参数将采用所有未绑定的ScriptBlock
  2. 当传入3个块时,虽然第一个块进入-Process,但后来它实际上用作"Begin",而剩下的2个变为"Process"和"End"

不过,以上所有只是我的猜测.我没有找到支持我猜测的文档

所以,最后我们回到我的简短问题:)任何人都有关于ForEach-Object的-RemainingScripts参数的详细信息?

谢谢.

Fan*_*hou 6

我做了更多的研究,现在有信心在传入多个 ScriptBlock 时回答 -RemainingScripts 参数的行为。

如果您运行以下命令并仔细检查结果,您将找到模式。这不是很简单,但仍然不难弄清楚。

1..5 | foreach { "process block" } { "remain block" }
1..5 | foreach { "remain block" }  -Process { "process block" }
1..5 | foreach { "remain block" } -End { "end block" } -Process { "process block" } -Begin { "begin block" }
1..5 | foreach { "remain block 1" } -End { "end block" } -Process { "process block" } { "remain block 2" }
1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } -Begin { "begin block" }
1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } { "remain block 3" }
1..5 | foreach { "process block" } { "remain block 1" } { "remain block 2" } -Begin { "begin block" }
1..5 | foreach { "process block" } { "remain block 1" } { "remain block 2" } { "remain block 3" }
Run Code Online (Sandbox Code Playgroud)

那么这里的模式是什么?

  • 当传入单个 ScriptBlock 时:简单,它只是转到 -Process(最常见的用法)

  • 当正好传入 2 个 ScriptBlocks 时,有 3 种可能的组合

    1. -Process & -Begin -> 按指定执行
    2. -Process & -End -> 按指定执行
    3. -Process & -RemainingScripts -> Process 变为 Begin,而 RemainingScripts 变为 Process

如果我们运行这两个语句:

1..5 | foreach { "process block" } { "remain block" }
1..5 | foreach { "remain block" }  -Process { "process block" }

# Both of them will return:
process block
remain block
remain block
remain block
remain block
remain block
Run Code Online (Sandbox Code Playgroud)

您会发现,这只是以下测试用例的一个特例:

  • 当传入超过 2 个 ScriptBlocks 时,请遵循以下工作流程:

    1. 按指定绑定所有脚本块(开始、处理、结束);剩余的 ScriptBlocks 转到 RemainingScripts
    2. 将所有脚本排序为:开始 > 处理 > 剩余 > 结束
    3. 排序的结果是 ScriptBlocks 的集合。我们称这个集合为OrderedScriptBlocks

      • 如果未绑定开始/结束,则忽略
    4. (内部)基于OrderedScriptBlocks重新绑定参数

      • OrderedScriptBlocks[0] 变为 Begin
      • OrderedScriptBlocks[1..-2] 变成 Process
      • OrderedScriptBlocks[-1](最后一个)变成 End

让我们以这个例子

1..5 | foreach { "remain block 1" } { "remain block 2" } -Process { "process block" } { "remain block 3" }
Run Code Online (Sandbox Code Playgroud)

订单结果为:

{ "process block" }    # new Begin
{ "remain block 1" }   # new Process
{ "remain block 2" }   # new Process
{ "remain block 3" }   # new End
Run Code Online (Sandbox Code Playgroud)

现在执行结果完全可以预测:

process block
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 1
remain block 2
remain block 3
Run Code Online (Sandbox Code Playgroud)

这就是 -RemainingScripts 背后的秘密,现在我们了解了 ForEach-Object 的更多内部行为!

我仍然不得不承认没有文档支持我的猜测(不是我的错!),但是这些测试用例应该足以解释我描述的行为。