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, as an example, and "re-implement" it as an advanced PowerShell function: Send-MsmqQueue
. But this function will be a bit different than the one provided by 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, where these two parameters belong to different parameter sets. (The Cmdlet version of this function normally only accepts a single string value for the $Name
parameter.) I won't be showing a complete re-implementation, just enough to illustrate what I, at times, find myself doing when this situation arises. (NOTE: one other slight difference is that I will be using the classes from System.Messaging
namespace instead of the PowerShell-provided ones in Microsoft.Msmq.PowerShell.Commands
namespace. So assume that implicitly, somewhere, Add-Type -AssemblyName System.Messaging
has been executed.)
function Send-MsmqQueue {
[CmdletBinding(DefaultParameterSetName = 'Name')]
[OutputType([Messaging.Message])]
Param (
[Parameter(
Mandatory,
ValueFromPipeline,
ParameterSetName = 'InputObject')
]
[Messaging.MessageQueue[]] $InputObject,
[Parameter(
Mandatory,
ValueFromPipeline,
ParameterSetName = 'Name')
]
[string[]] $Name,
# Below is the original parameter name, not mine ;)
[Messaging.Message] $MessageObject
# All other normal Send-MsmqQueue parameters elided as they are not
# needed to illustrate the premise of my question.
)
Process {
# When I have parameters defined as above, the first thing I do in my
# Process block is "homogenize" the data so I don't have to implement
# two foreach loops or do the branching on each foreach loop iteration
# which can obscure the main logic that is being executed, i.e., I get
# this done all "up-front".
#
# One aspect of my question is, from purely a PowerShell perspective,
# is this hurting performance in any meaningful way? (I know that when it
# comes to specific implementation details, there are INFINITE ways to
# write non-performant code, so from purely a PowerShell perspective,
# as far as the language design/inner-workings, is this hurting
# performance?
#
# NOTE: I don't normally need the wrapping "force this thing to be an
# array" construct (,<array_items>), BUT, in this case, the C#
# System.Messaging.MessageQueue class implements IEnumerable,
# which PowerShell (unhelpfully) iterates over automatically, and results
# in the messages in the queues being iterated over instead of the queues
# themselves, so this is an implementation detail specific to this
# particular function.
$Queues = (,@(
if ($PSCmdlet.ParameterSetName -ieq 'Name') {
# Handle when the parameter is NOT passed by the pipeline...
foreach ($n in $Name) { [Messaging.MessageQueue]::new($n) }
} else {
$InputObject
}
))
# I like using 'foreach (...) { ... }' instead of ForEach-Object because
# oftentimes, I will need to break or continue based on implementation
# details, and using ForEach-Object in combination with break/continue
# causes the pipeline to prematurely exit.
foreach ($q in $Queues) {
$q.Send($MessageObject)
# Normally, I wouldn't return this, especially since it wasn't
# modified, but this is a re-implementation of MSFT's Send-MsmqQueue,
# and it returns the sent message.
$MessageObject
}
}
}
As I stated in the introduction to this question, I have written many functions which take varying collection-based parameters belonging to different parameter sets which can be piped into the function, and this is the pattern that I use. I'm hoping someone can either confirm that this is OK from a PowerShell language/style perspective and/or help me understand why I should not do this and what I ought to consider instead.
Thank you!