PowerShell管道迭代

时间:2017-04-05 11:52:35

标签: powershell

我经常编写需要接受管道的函数,如:

function Invoke-ExchangeHubChecks
{
    [cmdletbinding()]
    param
    (
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        [object[]]$oServersToTest,
        [int]$QueueWarnThreshold = 50,
        [int]$QueueFailThreshold = 100

    )
    begin
    {
        $oHubTests = @()
        $c=0
    }
    Process
    {
        $c++
        $Server = $oServersToTest[0] 

        $oHubTests += [pscustomobject]@{
            Identity = $server.Identity
            Queue = "blah" 
            Count = $c
        }
    }
    end
    {
        $oHubTests
    }
}
    end
    {
        $oHubTests
    }
}

我经常在进程块中看到带有foreach的代码片段。我的理解是,过程块替换了对foreach块的需求(在大多数情况下)。

我需要在输入对象“$ oServersToTest”中添加一个“过滤器”。我使用“where-object”来创建过滤器:

$Server = $oServersToTest[0] | Where-Object {$_.IsHub}

但是,当迭代集合时,它会将与where子句不匹配的对象添加到自定义对象,由$ c证明:

输出:

Identity    Queue Count
--------    ----- -----
Server01    blah      1
Server02    blah      2
Server03    blah      3
Server04    blah      4
Server05    blah      5
            blah      6
            blah      7
Server06    blah      8
Server07    blah      9
Server08    blah     10
Server09    blah     11
Server10    blah     12
            blah     13
            blah     14

这是一个我必须在进程块中使用foreach的示例:

Process
{
    $c++
    #$Server = $oServersToTest[0] | Where-Object{$_.IsHubServer}
    $TestResult = @()
    $oServersToTest | Where-Object {$_.IsHubServer} | Foreach-object{
        $TestResult += [pscustomobject]@{
            Identity = $_.Identity
            Queue = "blah" 
            Count = $c
        }
    }

    $oHubTests += $TestResult
}
End
{
    $oHubTests
}

哪个有效,但为什么这样做而不是其他方式?对我来说,它正在做两倍的工作。

(Powershell 5.0)

TIA

2 个答案:

答案 0 :(得分:1)

我会在foreach()块中使用process循环。

作为Jeff Zeitlin points out,这也允许您支持使用命名参数和流水线操作来处理集合。

使用foreach()超过ForEach-Object的原因有三个:

  1. 性能:
    当输入集合已经在内存中时(在两种情况下都是如此),隐式参数绑定的开销会使ForEach-Object慢于foreach()
  2. 可读性:
    IMO,拥有一个命名变量(如$Server),使脚本更具可读性,避免与循环中嵌套的ForEach-ObjectWhere-Object子句混淆
  3. 功能:
    用一个普通的ol' foreach(),您可以免费获得breakcontinue等快速流量控制选项。
  4. process {
        $oHubTests += foreach($server in $oServersToTest |Where-Object {$_.IsHubServer}){
            $c++
            [pscustomobject]@{
                Identity = $server.Identity
                Queue = "blah" 
                Count = $c
            }
        }
    }
    

    如果你的cmdlet唯一做的就是接受输入并构造相应的[pscustomobject],那么我会完全删除$oHubTests部分,并在它们流动时输出对象通过:

    function Invoke-ExchangeHubChecks
    {
        [CmdletBinding()]
        param
        (
            [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
            [object[]]$oServersToTest,
            [int]$QueueWarnThreshold = 50,
            [int]$QueueFailThreshold = 100
    
        )
        begin {
            $c=0
        }
        process {
            foreach($server in $oServersToTest |Where-Object {$_.IsHubServer}){
                $c++
                [pscustomobject]@{
                    Identity = $server.Identity
                    Queue = "blah" 
                    Count = $c
                }
            }
        }
    }
    

答案 1 :(得分:1)

您正在为我解决PowerShell最复杂的设计结构之一。创建一个cmdlet,该cmdlet可以处理作为参数提供的对象数组或每个通过管道传输到cmdlet中的项目。

例如do-something -input $items$items | do-something

进程块主要用于cmdlet接受管道值时。它并不像你想象的那样取代for循环。管道已经对集合进行了隐式循环,尽管这不是100%准确,但这是一个不同的故事。为简单起见,我将继续这种不准确的陈述。

在这种情况下,beginprocessend允许您控制管道或虚构循环的每个部分发生的情况。 Beforeend仅执行一次,可用于初始化和完成。在上面的示例中,第一个begin已执行,然后process为从集合中传输的每个项目,然后end。但是,如果该集合被用作普通参数,那么它的beginprocess内部有一个循环,然后是end

我理解这很复杂但是尝试使用do-something实现进行可视化,该实现仅向主机写入正在进行的操作。

在开发MarkdownPS时我必须理解这个顺序。检查此example以及根目录上的使用示例。 Get-ChildItem | Select-Object Name | New-MDList。仅供参考,最后一个例子是我在这个答案中使用的不准确陈述的演示。