Hello everyone, my name is Preston K. Parsard, and I’m a Premier Field Engineer. I’ve been focusing on Azure and PowerShell engagements recently and would like to present an illustrated article about how Windows PowerShell parameter binding works.
REQUIREMENTS
Windows PowerShell version 4.0 is used in the screenshots for this article include both 4.0, however, Get-Service, Stop-Service and Set-Service have been available since PowerShell version 2.0.
PURPOSE
One of the more abstract concepts to both learn as a new user and to explain as an instructor is just exactly what goes on when you use the pipeline operator to combine expressions and pass values from one cmdlet to another in sequence. The good news is that once this process is understood, we can start to develop our own advanced functions while improving our scripting knowledge and capabilities. These are functions that can integrate parameters which accept pipeline input, and in fact this is how many of the native PowerShell cmdlets already work. Now a further discussion of building those types of functions are beyond the scope of this post, but can be found here. For now, we’ll review the process of pipeline inputs for existing native PowerShell cmdlets. First though, I’ll offer a quick description of the pipeline and the pipeline operator, for those of us that are not already familiar.
A pipeline in PowerShell is a series of values, expressions, commands or cmdlets that are combined with the pipe operator (|) to send the results of one command or expression to the next. These results are sent through the pipeline as objects or object properties, not just text as from the Windows command console (cmd.exe) or certain other non-PowerShell methods. If the results consist of an array of objects, these objects are sent in one-by-one through the pipeline.
Figure 1: Using the findstr.exe utility to search for “packets”
Here I am looking for the case insensitive string of “packets” in the result.
Now if I would like to only retrieve the number of received packets using the command console, I could try to do so by modifying my original expression to the following:
Figure 2: Using the findstr.exe utility to search for “received”
Look familiar? The command returns the entire line again, so the result is identical to the previous one shown in Figure 1.
Now what if the command ping localhost were an object, and we could just specify exactly which property of that resulting object we are interested in, such as the Received property to get its value of 4? This would provide more flexibility, but unfortunately we can’t do this directly in the command shell.
In PowerShell, objects can be filtered and manipulated at the other end of the pipeline with the receiving cmdlets, so you gain more control over utilizing all the available properties of those objects than would be possible with just text results.
USE CASES
Figure 3: Stop and Set-Service
Ok, so now that we’ve quickly reviewed the pipeline, let’s turn to our scenarios and some specific examples to examine these concepts in more detail.
STOP-SERVICE
Imagine that you wanted to identify a service and stop it? How would you do that? What about if it’s on a remote server on your network?
It will look something like this:
Get-Service -Name BITS -ComputerName 2012r2-ms | Stop-Service -PassThru -Verbose
If we look at the name parameter attribute for the Stop-Service cmdlet, which is on the receiving side of the pipeline, we’ll see below (1) that this parameter has a type of , which indicates it can accept multiple values, or an array of service names with the ServiceController .NET Class Type Name. It also shows that (2) this parameter accepts pipeline input, by value.
Figure 4: Stop-Service InputObject parameter attribute table
If we also look at the expressions with their available properties and parameters in terms of class diagrams, it would look something like this:
Figure 5: Get-Service and Stop-Service viewed as partial class diagrams
When Get-Service -Name BITS -ComputerName 2012r2-ms
is evaluated on the left side of the pipeline, it produces a result which is an object of type
ServiceController and has a value of bits hosted on the computer 2012r2-ms.
We’ve also just seen from Figure 4 that the Stop-Service cmdlet accepts pipeline input, and that it accepts values coming in to the InputObject parameter specifically, so it accepts these parameters by the object
value or values depending on whether a single or multiple objects are received.
By the way, it will be important to keep this in mind for other parameters which may accept pipeline input both by value and parameter name. The rules and processing order of how these parameters are evaluated follows:
- ByValue without coercion: Match incoming object type to receiving parameter type without having to try and convert the object type to the parameter type.
ByPropertyName without coercion: If the incoming property name (not object value) matches, the receiving parameter name, then attempt to the match incoming object property type to the receiving parameter type, again, without type conversion.
This option requires that the property name of the receiving cmdlet has to match exactly the property name of the incoming expression for which the object property value is being sent. More about this later.
- ByValue with coercion: If the incoming object value (not object property) type is convertible, for example, an integer could be converted or coerced into a string value if that’s what the receiving parameter expects based on its parameter type definition then binding will still work.
- ByProperty name with coercion: If the first match was by property name, then check to see if the incoming object type is convertible to what the receiving parameter has defined as it’s type.
Now that we have the breakdown of the processing order, we see that in our Stop-Service example, the match and subsequent binding will occur, based on the pipeline’s object value. This is because there is an instance of the ServiceController type, which is an object and not an object property coming through the pipeline.
More specifically, the resulting object received from the pipeline is a service object instance for the BITS service (Get-Service -Name BITS) that has a type of ServiceController. It is a single value, but can be converted to an array consisting of a single element. The receiving parameter type now matches sent object type, and so it binds or is associated with the object value, which is also be referred to as the argument in the Trace-Command output. This option was taken first because ByValue without coercion is evaluated highest in the processing order, so after parameter binding is successful, the receiving expression Stop-Service -PassThru -Verbose is then evaluated and executed. Of course, we just threw in the -PassThru and -Verbose parameters here to see what’s going on and get a bit more detail for the output.
This is a good start, but how can we trace the detailed activity for what transpires with parameter binding operations? Well, valued readers, I’m glad you asked.
Like this…
Trace-Command -Name ParameterBinding -Expression { Get-Service -Name bits -ComputerName 2012r2-ms | Stop-Service -PassThru } -PSHost -FilePath c:\pshell\labs_9\Stop-Service-PBv1.0.log
Here, the -PSHost switch parameter tells us to display the results on the screen and -FilePath is used to specify a file to log the same details.
Figure 7: The parameter binding process for Stop-Service
Inside the red outline shown in Figure 7, we first see the bind operation beginning for the pipeline object for the receiving Stop-Service cmdlet.
BIND PIPELINE object to parameters: [Stop-Service]
Next, the pipeline object type is evaluated as [System.ServiceProcess.ServiceController], but we can just exclude the class namespace, and call it ServiceController from here onwards.
PIPELINE object Type = [System.ServiceProcess.ServiceController]
The pipeline parameter value is then restored and because it matches the receiving parameter object type, which is ServiceController. The ByValueFromPipeline without coercion rule is applied first.
RESTORING pipeline parameter’s original values
Parameter [InputObject] PIPELINE INPUT ValueFromPipeline NO COERCION
Now we can bind the specific bits service object value instance to the Stop-Service’s InputObject parameter, but because it’s only a single service value, and an array of services is expected, the binding operation creates a new array and adds this single value of the bits service to it.
Bind arg [bits] to parameter [InputObject]
Binding collection parameter InputObject: argument type [ServiceController], parameter type [System.ServiceProcess.ServiceController[]], collection type Array, element type [System.ServiceProcess.ServiceController], no coerceElementType
Creating array with element type [System.ServiceProcess.ServiceController] and 1 elements
Argument type ServiceController is not IList, treating this as scalar
Adding scalar element of type ServiceController to array position 0
Ok, we’re almost finished with binding, but first we’ll validate that the parameter is not null or empty. This is necessary because it has already been defined as an attribute for the Parameter in PowerShell as part of the Stop-Service cmdlet source code.
Executing VALIDATION metadata: [System.Management.Automation.ValidateNotNullOrEmptyAttribute]
Are we there yet? …Yes, so finally, we can successfully bind the argument bits, now re-formatted as a ServiceController array type to the Stop-Service parameter -InputObject.
BIND arg [System.ServiceProcess.ServiceController[]] to param [InputObject] SUCCESSFUL
SET-SERVICE
Alright, now that we’ve looked at a ByValue example, let’s shift gears this time to the other pipeline input option – ByPropertyName. What’s interesting in this next example is that this parameter accepts pipeline input both ByValue AND ByPropertyName, so we’ll even get to do a bit of comparison as an added bonus.
Figure 8: Parameter attribute table for the Set-Service -Name parameter
And what would a discussion about binding be without a quick glance at another partial class diagram?
Figure 9: Class diagrams for Get-Service and Set-Service
First, we’ll execute the expression;
Figure 10: Trace-Command for Set-Service -Name ByParameterName
This command will select the bits service by it’s property name to set it’s StartupType from Auto to Manual. Notice the red outline area in Figure 10. After the first pipe operator, we single out ONLY name property of the bits ServiceController object, not the entire object, as in the first example with the Stop-Service cmdlet.
Here is the relevant part of the result of the Trace-Command, the rest is just ommited for brevity.
Figure 11: Partial Trace-Command output for Set-Service -Name ByParameterName
In Figure 11, if we look above the red outline, we’ll see that an attempt was previously made using the PIPELINE INPUT ValueFromPipeline NO COERCION rule to bind the incomming name property to receiving name parameter, but was skipped.
Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
BIND arg [@{Name=bits}] to parameter [Name]
BIND arg [@{Name=bits}] to param [Name] SKIPPED
The rule is used as the first in the order of evaluation, however the pipeline value is not an object in this case, but a property called name having the value bits. The verdict therefore, is that a property can’t be bound to parameter by value, only objects can, so this rule doesn’t apply and must be skipped to process the next rule in the binding sequence.
As a convenience, here’s quick reminder list of the binding order again:
- PIPELINE INPUT ValueFromPipeline NO COERCION
- PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION
- PIPELINE INPUT ValueFromPipeline WITH COERCION
- PIPELINE INPUT ValueFromPipelineByPropertyName WITH COERCION
The red outlined area in Figure 11 shows that the name parameter of the Set-Service cmdlet will now attempt to accept pipeline input ByPropertyName without coercion, which is accepted and binding is successful. This is because the pipeline item is a property, and only a property can be bound to a parameter with rule 2 – PIPELINE INPUT ValueFromPipelineByPropertyName NO COERCION, objects can’t bind using this rule, only rules 1 and 3 apply to objects. Rules 2 and 4 apply to properties. The other reason it binds is because the property name is “name”, and the parameter name is also “name”. ByPropertyName means that these two have to match exactly in order to bind.
So what if for the name parameter, we send an object through the pipeline instead of a property this time, since the Set-Cmdlet -Name parameter accepts input both ByValue and ByPropertyName? Again, we’re still setting the StartupType of the bits service from Auto to Manual, but now we will select the service object itself, not the name property of the bits service object as we did in the previous section. Here is the command and corresponding output below.
Figure 12: Class diagrams for Get-Service and Set-Service
We can observe that the pipeline object type is string, which also matches the parameter type (see Figure 8), and we also will notice that rule 1 is evaluated:
Parameter [Name] PIPELINE INPUT ValueFromPipeline NO COERCION
However, because we have sent an entire object with the value “bits” and not a property of an object through the pipeline (figure 13), and because rule 1 – ByValueFromPipeline without coercion, only accepts objects that do not have to be coerced to have its type converted, then it meets all the criteria and will bind successfully.
If we were to summarize these concepts visually in terms of a process diagram, it may resemble:
Figure 6: Parameter binding process
See, I told you this would be illustrated, didn’t I? Notice that indexed items 04.02.01, 04.02.02, 08.00 and 04.02.02.02 all have [A], [B], [C], and [D] prefix designations respectively. This is just a simple hint of the binding order we discussed previously.
SUMMARY
The key points I’d like to reinforce are: First, to know what you are sending through the pipeline. Is it an object or a property of an object? If it’s a valid object, it will bind using rules 1 or 3 ([A] or [C]) depending on if it’s object type requires conversion or not. The sending object type must match the receiving parameter type also.
If the pipeline item is a property of an object and is a valid property, it will use either rules 2 or 4 ([B] or [D]). Both the property/parameter type and the property/parameter names on both sides of the pipeline must eventually match to satisfy binding.
Finally, it’s important to point out that when we refer to the sending expression on the left side of the pipeline, we call the pipeline items either objects or properties, but on the receiving side, these are bound to their corresponding parameters. Parameters are always on the right, while properties and objects traverse the pipeline from the left.
We hope this article has been helpful and would love to get your feedback, especially any stories of how you have been best able use this information. Stay tuned for more topics and happy scripting!
REFERENCES
about_Parameters (Get-Help about_Parameters -ShowWindow)
https://technet.microsoft.com/en-us/library/hh847824.aspx
about_Pipelines (Get-Help about_Pipelines -ShowWindow)
https://technet.microsoft.com/en-us/library/hh847902.aspx