How to process pipelined parameters that are collections?

fourpastmidnight 0 Reputation points
2023-06-28T21:30:31.8266667+00:00

I've been writing advanced functions for many years now and have even written quite a few modules at this point. But, there's one question for which I have never really been able to find an answer.

Let's look at a Cmdlet that Microsoft provides in the MSMQ module, and "re-implement" it as an advanced PowerShell function: Send-MsmqQueue. But this function will be a bit different than the one that's provided with the MSMQ module in that not only will it accept multiple MSMQ queues for the $InputObject parameter, but also multiple MSMQ queue names for the $Name parameter (the Cmdlet version only accepts a single string for the $Name parameter). I won't be showing the entire implementation, just enough to illustrate my question.

function Send-MsmqQueue {
    [CmdletBinding(DefaultParameterSetName = 'Name')]
    [OutputType([Microsoft.Msmq.PowerShell.Commands.Message])]
    Param (
        # I'm going to use native C# System.Messaging objects here, instead of the
        # PowerShell ones, in order to better demonstrate my question. So, somewhere,
        # Add-Type -AssemblyName System.Messaging  will need to be executed....
        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
        [Messaging.MessageQueue[]] $InputObject,

        [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Name')]
        [string[]] $Name,

        [Messaging.Message] $MessageObject

        # All other parameters are elided, as this is the bare minimum set
        # necessary to demonstrate my question
    )

    Process {
        # I do this to "homogenize" the data. If I'm sent a string, I get the queue;
        # Otherwise, I just return the InputObject. And, this handles when the parameter
        # is passed as an array and not via the pipeline.
        #
        # I do it this way so that I don't have to basically implement the core logic
        # of the function TWICE based on the parameter set used. I always operate over
        # (in this case) a queue.
        #
        # The biggest potential problem I see with this is the foreach loop for processing
        # potentially `x` names, but I'm not sure how "big" an issue that really is. At most,
        # it makes this function O(2n) instead of O(1n), which is still O(n) since constants
        # are ignored in Big-O notation.
        #
        # NOTE: Normally, I don't do the (,<stuff>) wrapper as shown here, but C#
        # Messaging.MessageQueue objects implement IEnumerable, and I want to enumerate over
        # the queues themselves and not the messages in the queues! So, just ignore the (,...).
        $Queues = (,@(
            if ($PSCmdlet.ParameterSetName -ieq 'Name') {
                foreach ($n in $Name) { [Messaging.MessageQueue]::new($n) }
            } else {
                $InputObject
            }
        ))

        # I like using foreach (...) { ... } instead of ForEach-Object, because oftentimes,
        # I may need to break or continue within, and using ForEach-Object causes issues
        # due to the way break and continue operate inside a ForEach-Object scriptblock.
        foreach ($q in $Queues) {
            $q.Send($MessageObject)
            $MessageObject
        }
    }
}

I have written many functions which take varying types of pipelined input collections like the one shown above. Is what I'm doing above advisable? Is there a different way that this should be done?

Windows for business | Windows Server | User experience | PowerShell
0 comments No comments
{count} votes

4 answers

Sort by: Most helpful
  1. Deleted

    This answer has been deleted due to a violation of our Code of Conduct. The answer was manually reported or identified through automated detection before action was taken. Please refer to our Code of Conduct for more information.

    1 deleted comment

    Comments have been turned off. Learn more

  2. fourpastmidnight 0 Reputation points
    2023-06-29T15:09:34.61+00:00

    I asked this question over on StackOverflow and received an answer there.

    Basically, yes, the pattern that I use above is a good pattern.

    0 comments No comments

  3. fourpastmidnight 0 Reputation points
    2023-06-29T15:10:17.6766667+00:00

    Sorry, this originally was added as a "comment", then saw the "Post Your Answer"--but then, both seem to do the same thing?? Ugh, MSFT still can't create a basic, intuitive Q&A site... Don't know why I even bothered.

    0 comments No comments

  4. Rich Matheisen 48,036 Reputation points
    2023-06-29T18:29:46.3066667+00:00

    Another way to do it (without the -NoEnumerate switch) is to rearrange the code a bit:

    function Send-MsmqQueue {
        [CmdletBinding(DefaultParameterSetName = 'Name')]
    #    [OutputType([Microsoft.Msmq.PowerShell.Commands.Message])]
        Param (
            [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'InputObject')]
            [array[]] $InputObject,
    
            [Parameter(Mandatory, ValueFromPipeline, ParameterSetName = 'Name')]
            [string[]] $Name,
    
            [array] $MessageObject
        )
    
        Process {
            [array]$Queues= @()
            if ($PSCmdlet.ParameterSetName -ieq 'InputObject'){
                $Queues = $InputObject
            }
            else{
                foreach ($n in $Name){ 
                    $Queues += [PSCustomObject]@{Stuff = $n}
                }
            }
            foreach ($q in $Queues) {
                $q
            }
        }
    }
    
    $x = Send-MsmqQueue -Name 1,2,3
    $y = Send-MsmqQueue -InputObject ([PSCustomObject]@{Stuff='A'},[PSCustomObject]@{Stuff='B'},[PSCustomObject]@{Stuff='C'})
    

    I didn't use any of the actual message queuing objects, just strings and PSCustomObjects. The concept is there, though.


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.