Tuesday, November 16, 2010
PowerShell: Making it all happen
Sunday, November 07, 2010
PowerShell: Advanced Functions (Binding Behavior)
PowerShell binds in a variety of ways. The pipeline attempts to connect by inspecting the Attributes. of the Function Parameters statement. Explicit binding by name takes precedence whenever the type is compatible.
Here are two variations in behavior:
function Test-MyCmdLetA( [parameter( ValueFromPipeline=$true )]$Name ){ process{ $Name } } # test pipeline for 'function A' dir c:\|Test-MyCmdLetA function Test-MyCmdLetB( [parameter( ValueFromPipeline=$true )][string]$Name ){ process{ $Name } } # test pipeline for function 'B' dir c:\Test-MyCmdLetBIn the first function above notice that the output is the full object even though we have a property called ‘Name’ on the objects returned from the DIR CmdLet. There is NOT sufficient match to bind by property name.
In the second function I have added a ‘type’ declaration or converter to the parameter statement in the form of [string]$name. When we test this function the output is the value of the ‘Name’ property of the objects sent through the pipeline.
Now try this:
function Test-MyCmdLetB( [parameter( ValueFromPipeline=$true )][string]$junk ){ process{ $junk } } # test pipeline for function 'C' dir c:\Test-MyCmdLetCNotice that we still get the name string output. So how is this binding? What if we don’t want the Name string bound to $junk?
How about if we try and fool the code by putting in a second parameter named 'Name’?
function Test-MyCmdLetB( [parameter( ValueFromPipeline=$true )][string]$junk="Not bound", [parameter( ValueFromPipeline=$true )][string]$Name="Not bound" ){ process{ $Name } } dir c:\Test-MyCmdLetBNow we are getting the strings output on the ‘Name’ parameter just like we want but what is happening on the other parameter ‘$junk’?
function Test-MyCmdLetE( [parameter( ValueFromPipeline=$true )][string]$junk="Not bound", [parameter( ValueFromPipeline=$true )][string]$Name="Not bound" ){ process{ Write-Host "Name=$Name and Junk=$junk" } } dir c:\|Test-MyCmdLetEIt appears that we are getting the same output on both arguments…hmmm???
Now try this:
function Test-MyCmdLetF( [parameter( ValueFromPipelineByPropertyName=$true )][string]$junk="Not bound", [parameter( ValueFromPipeline=$true )][string]$Name="Not bound" ){ process{ Write-Host "Name=$Name and Junk=$junk" } } dir c:\Test-MyCmdLetFNow we are only getting the name. $junk is reported as ‘Not bound’. Now rey the following:
function Test-MyCmdLetG( [parameter( ValueFromPipelineByPropertyName=$true )][string]$junk="Not bound", [parameter( ValueFromPipeline=$true )][string]$junk2="Not bound", [parameter( ValueFromPipeline=$true )][string]$Name="Not bound" ){ process{ Write-Host "Name=$Name and Junk=$junk and Junk2=$junk2" } } dir c:\Test-MyCmdLetGNow only the property called ‘Name’ is being bound. Why is the first argument being bound even though the named argument (parameter) is being bound? What is really happening here?
This is the way the properties are discovered and bound. IN the absence of anexplicit request by name the properties are bound and discovered in order applying any type conversions possible.
Here is another bit that will help in understanding what is going on.
function Test-MyCmdLetB( [parameter( ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][string]$junk="Not bound", [parameter( ValueFromPipeline=$true )][string]$junk2="Not bound", [parameter( ValueFromPipeline=$true )][string]$Name="Not bound" ){ process{ Write-Host "Name=$Name and Junk=$junk and Junk2=$junk2" } } dir c:\Test-MyCmdLetBI have added the ValueFromPipeline statement in along with the ValueFromPipelineByPropertyName statement and now the output, once again, binds to ALL parameters. Adding both of these statements to the same parameter causes the behavior to ignore the value by name version. Use only one of these in any parameter.
Here are some references to articles that will help to clarify what is happening and why PowerShell s designed this way:
PowerShell Team Blog: ValuFromPipeLineByPropertyName
Microsoft Connect:ValueFromPipelineByPropertyName overridden by ValueFromPipeline
Previous:
Script CmdLets
The Pipeline
Next:
PowerShell: Advanced Functions (The Pipeline)
How do we handle items passed to our Advanced Function from the PowerShell pipeline?
When we run a CmdLet or a piece of core that outputs objects we can pass this output to another CmdLet as long as the CmdLet is designed to receive the kind of objects we are passing to it.
Let’s set up our Function to receive any object passed in the pipeline and look at the objects that are received. First we will inspect teh default object in the pipeline using the $_ variable.
function Test-MyCmdLet{ Param( [cmdletBinding()] [parameter(ValueFromPipeline=$true)]$stuff ) process{ ($_.GetType()).Name } }
What do we get as output when running the following?
1. dir c:\ | Test-MyCmdLet
2. dir c:\ | where-object {!$_.psiscontainer} | Test-MyCmdLet
3. Get-Process | Test-MyCmdLet
In case #1 above we get a mixture of object types. In case #2 we have limited the output to one type by filtering out objects that are containers (folders). In case #3 we get only ‘Process’ objects.
You can see that our CmdLet can receive any object and perform an inspection or nay other code task we may need to perform on the object. In this case we are extracting the object’s type-name and passing that into the pipeline.
What happens if we inspect our parameter $stuff using this new version of the CmdLet?
function Test-MyCmdLet{ Param( [cmdletBinding()] [parameter(ValueFromPipeline=$true)] $stuff ) process{ $stuff } }
Run the same three test examples from above to see how this acts. Notice that everything being passed into the CmdLet is just being passed back out.
What does this look like if we use the following version to inspect the objects type?
function Test-MyCmdLet{ Param( [cmdletBinding()] [parameter(ValueFromPipeline=$true)] $stuff ) process{ $stuff.GetType().Name } }
This looks the same as using the default object. What good is that? Well it can be useful however, we really would like to use the parameters for something more useful since we always have access to the full object. Why don’t we ‘bind’ the parameter to our input object?
function Test-MyCmdLet( [parameter( ValueFromPipelineByPropertyName=$true )]$Name ){ process{ $Name } }
Now we have changed the attribute to ValueFromPipelineByPropertyName. This will cause the name parameter to be connected to any object property with the 'name ‘Name’. (not case sensitive). Run the three examples again and see what happens.
We can use this to bind to one or more properties by name. This is one of the main and most powerful features of the pipeline.
Previous: Script CmdLetsNext: Binding Behavior
Thursday, November 04, 2010
PowerShell: Advanced Functions (Script CmdLets)
PowerShell calls ‘Script CmdLets’ Advanced Functions.
At a PowerShell prompt type: help about_advanced_functions
Here is a minimal advanced function that can be used as a CmdLet. The minimum requirement is to have either [CmdletBinding()] or at least one [Parameter()] statement.
1 function Test-MyCmdLet( 2 [cmdletBinding()] 3 [parameter( 4 ValueFromPipeline=$true 5 ) 6 ]$whatever 7 ){ 8 9 process{ 10 $whatever 11 } 12 }
Now we are sure that the function will be recognized as a CmdLet and not just a regular function.
What does this buy us in PowerShell? Not much. We can call the function and be prompted for input if we mark this as a ‘Mandatory’ parameter.
[Parameter(Mandatory=$true)]
Now we will be prompted if we don’t provide an argument.
We can also add ‘help’ to the prompt like this:
[Parameter(
Mandatory=$true,
HelpMessage=”Please enter a value for this parameter
)]
Now we will be prompted with this more specific message.
All of this is very nice but it really doesn’t buy much in the way of processing. What we would really like to do is have the function accept values directly from the pipeline. To accomplish this we can just add an attribute:
[Parameter(Mandatory=$true,
HelpMessage=”Please enter a value for this parameter,
ValueFromPipeline=$true
)]
Now we can pump items in the pipeline directly through the function.
Get-Process |Test-MyCmdLet
Get-Process | Select-Object processname, handles | Test-MyCmdLet | ft -auto
This will allow us to build pipeline smart functions that can easily add custom processing to our PowerShell scripts.
Next: The Pipeline