Tony
Tony

Reputation: 2774

Powershell function receiving multiple parameters from pipeline

I'm writing a function as follows:

Function Display-ItemLocation {
   Param(
      [ Parameter ( 
         Mandatory   = $True,
         Valuefrompipeline = $True ) ]
         [ String ]$stringItem,
      [ Parameter ( 
         Mandatory   = $False,
         Valuefrompipeline = $True ) ]
         [ String ]$stringLocation = 'unknown' 
   )
     Echo "The location of item $stringItem is $stringLocation."
}

Display-ItemLocation 'Illudium Q-36 Explosive Space Modulator' 'Mars'
Display-ItemLocation 'Plumbus'

It works fine as written.

The location of item Illudium Q-36 Explosive Space Modulator is Mars.
The location of item Plumbus is unknown.

I'd like to be able to pre-load an array with multiple data pairs and send it via pipeline into the function.

$Data = @(
           @('Bucket','Aisle 1'),
           @('Spinach Pie','Freezer 4')
         )
$Data | Display-ItemLocation

I can't find the magic syntax to get this to work. Can the function accept a pair of values at the same time from the pipeline?

Upvotes: 1

Views: 218

Answers (2)

Tony
Tony

Reputation: 2774

Thanks to @mklement0 for leading me to this solution. I came up with two options to resolve my dilemma using an array.

Option 1: Use .ForEach to pass the parameters in the usual way.

$Data = @(
           @('Bucket','Aisle 1'),
           @('Spinach Pie','Freezer 4')
         )

$Data.ForEach({Format-ItemLocation "$($_[0])" "$($_[1])"})

Option 2: Using the pipeline (which is what I was after), I modified the function as mklement0 suggested to enable the ValuefromPipelineByPropertyName and to include a Process { } block.

Function Format-ItemLocation {
   Param (
      [ Parameter ( 
         Mandatory   = $True,
         ValuefromPipelineByPropertyName = $True ) ]
         [ String ]$stringItem,
      [ Parameter ( 
         Mandatory   = $False,
         ValuefromPipelineByPropertyName = $True ) ]
         [ String ]$stringLocation = 'unknown' 
   )
   Process {
       "The location of item $stringItem is $stringLocation."
   }
} 

I pass the array via pipeline to a middle step to assign the parameter names to a [PSCustomObject]. This greatly reduces the amount of text that would bulk up the code, and it's the reason I was searching for a more elegant solution.

$Data = @(
           @('Bucket','Aisle 1'),
           @('Spinach Pie','Freezer 4')
         )

$Data | 
   ForEach-Object { [PSCustomObject]@{stringItem=$_[0];stringLocation=$_[1]} } |
   Format-ItemLocation

I changed the function name to Format-* as recommended.

Upvotes: 1

mklement0
mklement0

Reputation: 440536

Define your pipeline-binding parameters as binding by property name - ValuefromPipelineByPropertyName - and then pipe (custom) objects that have such properties:

Function Display-ItemLocation {
   Param(
      [ Parameter ( 
         Mandatory,
         ValuefromPipelineByPropertyName ) ]
         [ String ]$stringItem,
      [ Parameter ( 
         Mandatory = $False,
         ValuefromPipelineByPropertyName ) ]
         [ String ]$stringLocation = 'unknown' 
   )
   
   process { # !! You need a `process` block to process *all* input objects.
     Echo "The location of item $stringItem is $stringLocation."
   }

}

As an aside: Display is not an approved verb in PowerShell.

Now you can pipe to the function as follows; note that the property names must match the parameter names:

$Data = [pscustomobject] @{ stringItem = 'Bucket'; stringLocation = 'Aisle 1' },
        [pscustomobject] @{ stringItem = 'Spinach Pie'; stringLocation = 'Freezer 4' }

$Data | Display-ItemLocation

The above yields:

The location of item Bucket is Aisle 1.
The location of item Spinach Pie is Freezer 4.

  • The above uses [pscustomobject] instances, which are easy to construct ad hoc.

    • Note that hash tables (e.g., just @{ stringItem = 'Bucket'; stringLocation = 'Aisle 1' }) do not work - although changing that is being discussed in this GitHub issue.
  • In PSv5+ you could alternatively define a custom class:

# Define the class.
class Product {
  [string] $stringItem
  [string] $stringLocation
  Product([object[]] $itemAndLocation) { 
    $this.stringItem = $itemAndLocation[0]
    $this.stringLocation = $itemAndLocation[1]
  }
}

# Same output as above.
[Product[]] (
  ('Bucket', 'Aisle 1'), 
  ('Spinach Pie', 'Freezer 4')
) | Display-ItemLocation

Upvotes: 4

Related Questions