Counting objects fed to an AdvancedParameter in the pipeline

This topic contains 0 replies, has 1 voice, and was last updated by Profile photo of Forums Archives Forums Archives 5 years, 5 months ago.

  • Author
    Posts
  • #5750

    by cmcknz77 at 2013-04-02 16:33:08

    This is either a bit of a 'quirk' or I'm totally not understanding the way the powershell pipeline works and missing something totally obvious.

    I've got a test function that does 'something' and I want to perform a slightly different action inside the function dependent on the number of entries that have been passed to a specific parameter (only when there's more than One).

    function Test
    {
    [cmdletbinding()]
    Param(
    [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
    [String[]]$TestEntries
    )
    begin {
    Write-Host "BeginTestEntryCount = $($TestEntries.count)"
    }
    process{
    Write-Host "InProcessTestEntryCount = $($TestEntries.count)"
    foreach($TestEntry in $TestEntries)
    {
    Write-Output $TestEntry
    if($TestEntries.count -gt 1){Write-Host "Do something special with $TestEntry"}
    }
    }
    }

    If I name the parameter and pass in a number of entries (in this case 3) I get the expected result when I explicitly try to count the number of entries passed
    to the '-Testentries' parameter:-

    [code2=powershell]PS C:\> test -testentries 'one','two','three'
    BeginTestEntryCount = 3
    InProcessTestEntryCount = 3
    one
    Do something special with one
    two
    Do something special with two
    three
    Do something special with three[/code2]

    However, when I try to pass a bunch of entries in through the pipeline all my 'counts' fail and the 'special thing' doesn't happen.
    [code2=powershell]PS C:\> 'one','two','three'|Test
    BeginTestEntryCount = 0
    InProcessTestEntryCount = 1
    one
    InProcessTestEntryCount = 1
    two
    InProcessTestEntryCount = 1
    three[/code2]

    Any ideas why? Or how I can get the correct number of items that have been input to the parameter when I'm passing those items in through the pipeline?

    by mjolinor at 2013-04-02 17:27:45

    This is happening because the pipeline automatically "unrolls" arrays and collections and passes them to the pipeline one at a time ( but only one level deep). You can pass an array to the pipeline by making it a 2 D array so that it "unrolls" into a 1 D array. Another possibility is to accumulate all the objects from the pipeline into an array, and then work with that in the End block after the pipeline has finished reading everything, or use the automatic variable $input.

    by MasterOfTheHat at 2013-04-03 06:48:00

    Or just make it a 2-step process or do the first function inline in the other function, something like:
    test -testentries (get-arrayofstuff)

    by cmcknz77 at 2013-04-03 09:52:33

    Are you saying that you cannot get that count if you're using an Advanced Function with begin, process and end blocks and the pipeline?
    Why does this work?

    Function Sum1 {
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "BeginNoOfElements = $NoOfElements"
    }

    PS C:\> 'one','two','three'| sum1
    BeginNoOfElements = 3

    And this Not work?
    Function Sum {
    begin{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "BeginNoOfElements = $NoOfElements"
    }
    process{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "ProcessNoOfElements = $NoOfElements"
    }
    end{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "EndoOfElements = $NoOfElements"
    }
    }

    PS C:\> 'one','two','three'| sum
    BeginNoOfElements = 0
    ProcessNoOfElements = 1
    ProcessNoOfElements = 1
    ProcessNoOfElements = 1
    EndoOfElements = 0
    PS C]
    Is the former (sum1) being processed before the 'begin' block?

    by MasterOfTheHat at 2013-04-03 12:56:42

    The pipeline is automagically exploding the array and feeding it to the "Sum" function one element at a time. What is going on looks basically like this:
    $NoofElements=@($input).Count
    Write-Host "BeginNoOfElements = $NoOfElements"

    $arrNumbers = "one","two","three"
    foreach($nbr in $arrNumbers)
    {
    $NoofElements=$nbr.Count
    Write-Host "ProcessNoOfElements = $NoOfElements"
    }

    $NoofElements=@($input).Count
    Write-Host "EndoOfElements = $NoOfElements"

    Everything in the BEGIN block happens before the first element is passed to the function and everything in the END block happens after all elements have been passed to the function and execution is finished, but everything in the PROCESS block happens for each element passed to the array.

    So to answer your question, no I don't think you can get the count of all elements in an object coming down the pipeline. At least not any way that I've seen. The other option is to hold all of the elements in a collection in the PROCESS block and then check the count in your END block, but that doesn't help you if you need to do different processing based on the number of elements.

    by cmcknz77 at 2013-04-03 13:59:43

    Ok – I kinda get it a bit more now for why the 'Advanced' function with the begin,process,end blocks works the way it does (thanks) but that still doesn't explain why I get to be able to do what I need with a (for want of a better term) 'dumb' function (like 'sum1' in the example above) but cannot do it with an 'Advanced' one...

    You've given me an idea though (one that fills me with dread but) putting my 'Advanced' function in a 'dumb' wrapper function lets me do what I need:
    I just don't understand why... And if anyone can point me towards an explanation I'd appreciate it... Because I feel that I'm going to be facing a 'scoping nightmare of epic propertions' if it's really the only way to do it...

    Function Sum1 {
    $Sum1NoofElements=@($input).Count
    $input.Reset()
    Write-Host "Sum1NoOfElements = $Sum1NoOfElements"
    Function Sum {
    begin{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "BeginNoOfElements = $NoOfElements"
    if($Sum1NoOfElements -gt 1){Write-Host "Begin by doing Something Special With `$input $input"}
    $input.reset()
    }
    process{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "ProcessNoOfElements = $NoOfElements"
    if($Sum1NoOfElements -gt 1){Write-Host "Process Does Something Special With each $input"}
    $input.reset()
    }
    end{
    $NoofElements=@($input).Count
    $input.Reset()
    Write-Host "EndoOfElements = $NoOfElements"
    if($Sum1NoofElements -gt 1){Write-Host "End by doing Something Special With `$input $input"}
    $input.reset()
    }
    }
    $input | Sum
    }

    PS C:\> 1 | Sum1
    Sum1NoOfElements = 1
    BeginNoOfElements = 0
    ProcessNoOfElements = 1
    EndoOfElements = 0

    PS C:\> 'two' | Sum1
    Sum1NoOfElements = 1
    BeginNoOfElements = 0
    ProcessNoOfElements = 1
    EndoOfElements = 0

    PS C:\> 'one','2','three' | sum1
    Sum1NoOfElements = 3
    BeginNoOfElements = 0
    Begin by doing Something Special With $input
    ProcessNoOfElements = 1
    Process Does Something Special With each one
    ProcessNoOfElements = 1
    Process Does Something Special With each 2
    ProcessNoOfElements = 1
    Process Does Something Special With each three
    EndoOfElements = 0
    End by doing Something Special With $input

    by mjolinor at 2013-04-03 14:17:04

    [quote]
    You've given me an idea though (one that fills me with dread but) putting my 'Advanced' function in a 'dumb' wrapper function lets me do what I need:
    I just don't understand why... And if anyone can point me towards an explanation I'd appreciate it... Because I feel that I'm going to be facing a 'scoping nightmare of epic propertions' if it's really the only way to do it...
    [/quote]

    You've got part of that answer already. I suggested early on, and others have followed, that you can move the processing into the End block. This runs after the pipeline has been emptied and there is no more input. At this point you have all the records (and therefore a count).

    The reason it works in a "dumb" function is that without the Begin, Process, or End keywords, it will assume and implement End block behaviour. (As an aside, if it is a Filter rather than a Function the Process block is assumed), so you'd just be doing implicitly what's been suggested explicitly.

    I don't see any reason this should be a 'scoping nightmare'.

You must be logged in to reply to this topic.