lowteq
lowteq

Reputation: 121

Handling pipeline and parameter input in a Powershell function

I'm confused about something I saw in the book Learn PowerShell in a Month of lunches. In chapter 21 when the author discusses functions that accept input via parameter binding or the pipeline he gives two patterns.

The first as follows

function someworkerfunction {
# do some work
}
function Get-SomeWork {
   param ([string[]]$computername)
   BEGIN {
      $usedParameter = $False
      if($PSBoundParameters.ContainsKey('computername')) {
         $usedParameter = $True
      }   
   }
   PROCESS {
      if($usedParameter) {
         foreach($computer in $computername) {
            someworkerfunction -computername $comptuer
         }
      } else {
         someworkerfunction -comptuername $_
      }
   }

   END {}
}

The second like this

function someworkerfunction {
# do stuff
}
function Get-Work {
   [CmdletBinding()]
   param(
      [Parameter(Mandatory=$True,
      ValueFromPipelineByPropertyName=$True)]
      [Alias('host')]
      [string[]]$computername
   )
   BEGIN {}
   PROCESS {
      foreach($computer in $computername) {
         someworkerfunction -comptuername $computer
      }
   }
   END {}
}

I know the second sample is a standard Powershell 2.0 Advanced function. My question is with Powershell 2.0 support for the cmdletbinding directive would you ever want to use the first pattern. Is that just a legacy from Powershell 1.0? Basically is there ever a time when using Powershell 2.0 that I would want to mess around with the first pattern, when the second pattern is so much cleaner.

Any insight would be appreciated.

Thank you.

Upvotes: 8

Views: 18758

Answers (4)

Tyler
Tyler

Reputation: 417

No, the first example is not just legacy. In order to create a PowerShell function that uses an array parameter and takes pipeline input you have to do some work.

I will even go as far as to say that the second example does not work. At least I could not get it to work.

Take this example...

function PipelineMadness()
{
   [cmdletbinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [int[]] $InputArray
    )

    Write-Host ('$InputArray.Count {0}' -f $InputArray.Count)
    Write-Host $InputArray

    Write-Host ('$input.Count {0}' -f $input.Count)
    Write-Host $input

    if($input) { Write-Host "input is true" }
    else { Write-Host "input is false" }
}

results ...

PS C:\Windows\system32> 1..5 | PipelineMadness
$InputArray.Count 1
5
$input.Count 5
1 2 3 4 5
input is true

PS C:\Windows\system32> PipelineMadness (1..5)
$InputArray.Count 5
1 2 3 4 5
$input.Count 1

input is false

Notice that when the pipeline is used the $InputArray variable is a single value of 5...

Now with BEGIN and PROCESS blocks

function PipelineMadnessProcess()
{
    [cmdletbinding()]
    param (
        [Parameter(Mandatory = $true, ValueFromPipeline=$true)]
        [int[]] $InputArray
    )

    BEGIN
    {
        Write-Host 'BEGIN'
        Write-Host ('$InputArray.Count {0}' -f $InputArray.Count)
        Write-Host $InputArray

        Write-Host ('$input.Count {0}' -f $input.Count)
        Write-Host $input

        if($input) { Write-Host "input is true" }
        else { Write-Host "input is false" }
    }

    PROCESS
    {
        Write-Host 'PROCESS'
        Write-Host ('$InputArray.Count {0}' -f $InputArray.Count)
        Write-Host $InputArray

        Write-Host ('$input.Count {0}' -f $input.Count)
        Write-Host $input

        if($input) { Write-Host "input is true" }
        else { Write-Host "input is false" }
    }
}

Now this is where it gets weird

PS C:\Windows\system32> 1..5 | PipelineMadnessProcess
BEGIN
$InputArray.Count 0

$input.Count 0

input is false
PROCESS
$InputArray.Count 1
1
$input.Count 1
1
input is true
PROCESS
$InputArray.Count 1
2
$input.Count 1
2
input is true

...

PROCESS
$InputArray.Count 1
5
$input.Count 1
5
input is true

The BEGIN block does not have any data in there at all. And the process block works well however if you had a foreach like the example it would actually work but it would be running the foreach with 1 entry X times. Or if you passed in the array it would run the foreach once with the full set.

So I guess technically the example would work but it may not work the way you expect it to.

Also note that even though the BEGIN block had no data the function passed syntax validation.

Upvotes: 5

JPBlanc
JPBlanc

Reputation: 72610

To answer your question, I would say that the first pattern is just a legacy from PowerShell 1.0, you also can use $input in classical functions without Process script block. As far as you are just writting code for PowerShell 2.0 you can forget it.

Regarding pipeline functions, in powerShell V1.0 they can be handled with filters.

you just have to know that it have been done like that when you take samples from the Net or when you have to debug old Powerhell code.

Personally I still use old functions and filters inside my modules I reserve cmdletbinding for export functions or profile functions.

Powershell is a bit like lego blocks, you can do many things in many different ways.

Upvotes: 1

mjolinor
mjolinor

Reputation: 68243

The first form is expecting one or more computer names as string arguments either from an argumentlist or from the pipeline.

The second form is expecting either an array of string arguments from an argument list, or input objects from the pipeline that have the computer names as a property.

Upvotes: 0

Andy Arismendi
Andy Arismendi

Reputation: 52567

If you want to process pipeline input in your function but don't want to add all the parameter attributes or want backwards compatibility go with the cmdletbindingless way.

If you want to use the additional features of PowerShell script cmdlets like the parameter attributes, parameter sets etc... then go with the second one.

Upvotes: 3

Related Questions