0

I have been naïvely assuming that an object passed to a commandlet using PowerShell's pipeline parameter binding is treated the same as passing it using named parameters. However, the following script seems to demonstrate there is a difference:

Function Process-xMsmqQueue{
    [CmdletBinding()]
    Param(
    [Parameter()]$obj,
    [Parameter(
        ValueFromPipeline=$True,
        ValueFromPipelineByPropertyName=$True)]
    [System.Messaging.MessageQueue]$queue
    )
    Write-Host $obj
}

New-MsmqQueue 'xTest1' -ErrorAction Ignore
[System.Reflection.Assembly]::LoadWithPartialName('System.Messaging') | Out-Null
New-Object System.Messaging.MessageQueue('.\private$\xTest1') -OutVariable q
$first = 'first'
$second = 'second'
$q | Process-xMsmqQueue -obj $first
Process-xMsmqQueue -queue $q -obj $second 

I expected this script to print first and second. Instead, it throws ParameterArgumentTransformationError on the last line:

enter image description here

The difference in treatment of the $queue parameter only seems to manifest for some object types. For example, replacing [System.Messaging.MessageQueue] with [System.String] and passing a string to $queue yields no errors.

My Questions:

  1. Why is the System.Messaging.MessageQueue object treated differently when passed using the pipeline versus named parameters?
  2. Why isn't System.String affected by the difference?
  3. How can I change this script to make passing by named parameter work for -queue?
alx9r
  • 3,675
  • 4
  • 26
  • 55
  • Try writing the value of `$queue` when using a String type. My guess is that PowerShell does an implicit conversion that isn't really what you want, but it doesn't produce an error either. – Mike Zboray Dec 14 '14 at 22:46
  • Actually it looks like `-OutVariable` has something to do with it. It produces a variable with an ArrayList containing a single instance of `MessageQueue`. While `$q = New-Object System.Messaging.MessageQueue('.\private$\xTest1')` produces just an object of type `MessageQueue`. – Mike Zboray Dec 14 '14 at 23:02
  • It seems like you're right. And I bet the pipeline automatically iterates through ArrayList. – alx9r Dec 14 '14 at 23:22
  • `$q | gm` indicates that `$q` is `System.Messaging.MessageQueue` type. However, `$q.GetType()` returns `ArrayList`. It looks like [Get-Member](http://technet.microsoft.com/en-us/library/hh849928.aspx) reports on items in a collection piped to it, not the collection itself. – alx9r Dec 15 '14 at 00:34

1 Answers1

1

As @mikez pointed out the answers to all three questions are obvious once you realize that -OutVariable produces a ArrayList of objects. -Outvariable is suitable for further consumption by the pipeline and seems to exist to facilitate inspection of objects passed through the pipeline.

Question 1

Why is the System.Messaging.MessageQueue object treated differently when passed using the pipeline versus named parameters?

Consider the following line:

New-Object System.Messaging.MessageQueue('.\private$\xTest1') -OutVariable q

actually produces an object of type System.Messaging.MessageQueue[] (not System.Messaging.MessageQueue) which is an Arraylist of MessageQueues.

Pipeline

Consider this line:

$q | Process-xMsmqQueue -obj $first

As I understand it, Powershell interprets the pipeline operator so that the subsequent commandlet acts on each element in the Arraylist. Given the context, that line is equivalent to the following:

foreach($item in $q){
    Process-xMsmqQueue -queue $item -obj $first
}

And, because $q contains only a single object, the body of the foreach is executed only once.

Named Parameter

Now that we know that $q is actually an Arraylist of System.Messaging.MessageQueue objects, it is no surprise that the following line yields an error:

Process-xMsmqQueue -queue $q -obj $second

-queue accepts System.Messaging.MessageQueue but $q is of type Arraylist.


Side Note: The thing that led me astray is that $q | gm outputs TypeName: System.Messaging.MessageQueue. However, $q.GetType() returns ArrayList. A look at The Get-Member documentation explains the discrepancy:

When you pipe a collection of objects to Get-Member, Get-Member gets the members of the individual objects in the collection, such as the properties of each string in an array of strings.


Question 2

Why isn't System.String affected by the difference?

It is, if you obtain a string in the same way:

New-Object System.String('MyString') -OutVariable $str

$str is an ArrayList of System.String objects. If I pass it by name to a parameter expecting System.String, I get the same ParameterArgumentTransformationError.

Question 3

How can I change this script to make passing by named parameter work for -queue?

There are a few options:

  1. Use = to assign the output of New-Object: $q=New-Object ... This way $q contains a single object not the ArrayList you get with -OutVariable

  2. Change Process-xMsmqQueue -queue to accept System.Messaging.MessageQueue[]. The commandlet would need to iterate over the ArrayList.

  3. Index $q like this: Process-xMsmqQueue -queue $q[0] This gets the first element of the ArrayList that was produced by New-Object

Community
  • 1
  • 1
alx9r
  • 3,675
  • 4
  • 26
  • 55
  • A pipeline is not equivalent to `foreach($i in $many)`. The pipeline is asynchronous, and each item is passed separately. When you use `foreach($i in $many)` it collects all the results before continuing with the pipeline. – Eris Dec 16 '14 at 08:53
  • @Eris can you recommend a good reference for precisely how the pipeline handles execution? Everything I've found gets into detail about parameter bindings but glosses over its execution strategy. – alx9r Dec 16 '14 at 15:24
  • Nothing canonical, but the Scripting Guy blog has a some good articles. Here is the one explaining `Foreach-Object` vs `foreach()`: http://blogs.technet.com/b/heyscriptingguy/archive/2014/07/08/getting-to-know-foreach-and-foreach-object.aspx – Eris Dec 16 '14 at 18:44