1

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?

1
  • Not really, you can only have one input stream that sort of "drives" the pipeline and binds to a single parameter. If you're exclusively writing this for PowerShell 5.0 and up, you could take advantage of custom class definitions and use that as a pipeline-enabled input type Commented Aug 11, 2020 at 17:41

2 Answers 2

4

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
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you... You answer led me to the solution I wanted (which I'll post momentarily). I thought the [PSCustomObject] as you listed it was too verbose, so I reworked the code to use the array as I envisioned it for brevity.
Just curious, what would you use for a verb?
Glad to hear it, @Tony. As for reducing the verbosity - please see my update. As for what verb to use: if you're converting to a different data format, use ConvertTo. If you're creating for-display-only representations, use Format, but then you should use Write-Host or Out-Host to emit the for-display representations rather than outputting to the pipeline.
1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.