Quantcast
Channel: Hey, Scripting Guy! Blog
Viewing all articles
Browse latest Browse all 3333

Learn About Using PowerShell Value Binding by Property Name

$
0
0

Summary:  Guest blogger, Ingo Karstein, talks about using Windows PowerShell and value binding by property name.

Microsoft Scripting Guy, Ed Wilson, is here. Today our blog is written by Ingo Karstein who has shared his knowledge previously with us. Ingo is an independent consultant for SharePoint and Windows PowerShell based in Leipzig, Germany and a Microsoft Certified Master for SharePoint 2010.

Take it away, Ingo…

Do you know about value binding by property name in Windows PowerShell pipelines and what it is good for? I’d like to explain it to you!

First, what is value binding? In a Windows PowerShell pipeline context, it is possible to transfer (pipe) objects from one cmdlet to another.

Here is a simple example:

You can use Get-ProcessIExplore to retrieve all Internet Explorer processes as .NET objects. With the cmdlet Stop-Process, you can stop processes. A WindowsPowerShell pipeline connects both cmdlets. By piping the Internet Explorer objects from Get-Process to Stop-Process, you can stop the processes.

Get-Process "IExplore" | Stop-Process

Image of command output

When you use this command, you are using a Windows PowerShell pipeline marked by the pipe character ( | ), and you use value binding. The Stop-Process cmdlet has a parameter called InputObject, which accepts process objects to do its work. You could also use a variable to specify the InputObject parameter.

Image of command output

The example in the previous screenshot shows an explicit value binding of the $ie variable to the InputObject parameter. The Windows PowerShell pipeline does exactly the same, but implicitly! It takes one or more objects from Get-Process and tries to bind them to some parameter of the Stop-Process cmdlet. “Some parameter” means that Windows PowerShell analyzes the cmdlet and its parameters, and if a parameter’s data type matches the given object’s data type, it binds the object to the parameter and runs the command.

The following image shows a look into the Windows PowerShell internal Help for Stop-Process.

Image of command output

You can see that the InputObject parameter accepts an array (marked by “[ ]”) of Process objects through the pipeline by using ByValue.

Now let’s get closer to the main topic of this blog post: Value binding by property name.

Let’s have a look at another parameter of Get-Process called Name.

Image of command output

Like the InputObject parameter, Name also accepts pipeline input, but by using ByPropertyName instead of ByValue as in the previous screenshot.

Now let’s try to run the command first with an explicit data-type string value for the Name parameter. This works as expected.

Image of command output

Now we try to bind the string value IExplore to the parameter by using a Windows PowerShell pipeline.

Image of error message

This presents an error message because there is no parameter for the Stop-Process cmdlet that accepts string (or array of strings) data as ByValue pipeline input.

So how can we use the pipeline binding for the Name parameter that is marked with ByPropertyName? Let’s say we have a certain object that has a property called Name like the following object:

Image of command output

The object in the $myObject variable has only one property (Name) with the IExplore value.

Now we can use this object as pipeline input.

Image of command output

No error message here! The pipeline engine of Windows PowerShell does a lot of simple magic here. It tries to match all properties of the input object to the cmdlet. This match is done by name. The input object in the example has a Name property of the type String, and the Stop-Process cmdlet has a Name parameter that accepts String values. This is a perfect match!

The match also works if the input object has more properties.

Image of command output

When you run $myObject | Stop-Process again, it will also match the Name property and the Name parameter. But it does not work for an object that has no Name property.

Image of error message

Here is how I created the object in the $myObject variable:

Image of command output

I created a new empty Windows PowerShell object by using New-Object System.Management.Automation.Object, and I added some properties by using the Add-Member cmdlet. This is a very cool feature in Windows PowerShell that allows you to create custom objects on the fly and work with them.

Now a more complex example…

Let’s say you want to import some plain text log files into Windows PowerShell. For each log file, you want to specify the file name plus the number of lines you want to read from the beginning of the file or the number of lines to read from the end of the file. You could use the following.

Image of command output

Following is the Help information about Get-Content.

Image of command output

As you can see, three parameters can be bound by Property name. If you have a certain objects with corresponding Property names, you can pass these objects to the cmdlet and bind the parameters to the object values.

Let’s create some objects:

$log1 = New-Object System.Management.Automation.PSObject

$log1 | Add-Member -Name "Path" -Value "Log1.txt" -MemberType NoteProperty

$log1 | Add-Member -Name "TotalCount" -Value 3 -MemberType NoteProperty

 

$log2 = New-Object System.Management.Automation.PSObject

$log2 | Add-Member -Name "Path" -Value "Log2.txt" -MemberType NoteProperty

$log2 | Add-Member -Name "TotalCount" -Value 5 -MemberType NoteProperty

 

$log3 = New-Object System.Management.Automation.PSObject

$log3 | Add-Member -Name "Path" -Value "Log3.txt" -MemberType NoteProperty

$log3 | Add-Member -Name "Tail" -Value 2 -MemberType NoteProperty

 

$log4 = New-Object System.Management.Automation.PSObject

$log4 | Add-Member -Name "Path" -Value "Log4.txt" -MemberType NoteProperty

$log4 | Add-Member -Name "Tail" -Value 4 -MemberType NoteProperty

This is how they look:

Image of command output

Now you can use them together as pipeline input. The pipeline engine will always match the corresponding properties and parameters by their names.

Image of command output

The command @($log1, $log2, $log3, $log4) | Foreach-Object { $_ | Get-Content } looks strange, doesn’t it? If you try to run @($log1, $log2, $log3, $log4) | Get-Content, you will see that it fails for the objects in variables $log3 and $log4 because Get-Content does not allow the TotalCount and Tail parameters side-by-side.

You might say $log3 and $log4 do not have a TotalCount property. You are correct! But through the first two objects, $log1 and $log2, the pipeline engine expects that all pipeline input objects will have at least two properties: Path and TotalCount. For $log3 and $log4, it finds one additional input parameter, called Tail, which theoretically can be bound by property name.

It tries to bind the already known TotalCount property with a value of $null, and the new Tail property with a real value at the same time. That fails because Tail and TotalCount cannot coexist in one call. In the previous screenshot, all objects are piped to the Get-Content cmdlet separately.

One step further…

I am writing this blog because I used value binding by property name in a Windows PowerShell project about importing and exporting design packages. For more information, see:

In those scripts, I wanted to call Windows PowerShell Cmdlet Binding-enabled functions by using hash tables. I wanted to bind named hash table items to cmdlet parameter names. The following example shows an array of four hash tables.

@(

    @{ Path="Log1.txt"; TotalCount=3 },

    @{ Path="Log2.txt"; TotalCount=5 },

    @{ Path="Log3.txt"; Tail=4 },

    @{ Path="Log4.txt"; Tail=2 }

)

I want to use them as pipeline input for the Get-Content cmdlet, but as you can see, it does not work. There is no cmdlet parameter for Get-Content that accepts a hash table as input.

Image of error message

Therefore, I created a function that enables this scenario by creating objects out of hash tables. Here I use the Add-Member cmdlet as shown earlier.

function New-ObjectFromHashtable {

            [CmdletBinding()]

            param(

                        [parameter(Mandatory=$true, Position=1, ValueFromPipeline=$true)]

                        [Hashtable]

                        $Hashtable

            )

 

            begin { }

 

            process {

                        $r = new-object System.Management.Automation.PSObject

           

                        $Hashtable.Keys | % {

            $key=$_

                                    $value=$Hashtable[$key]

 

                                    $r | Add-Member -MemberType NoteProperty -Name $key -Value $value -Force

                        }

 

                        $r

            }

 

            end { }

}

This takes hash tables as pipeline input. For each hash table in the pipeline, the function creates a new object. For each key in the hash table, the function adds a property to the object and sets the value of the property to the value of the hash table item referenced by the key. The result is a list of objects. The function itself can be used in a pipeline.

Image of command output

I hope you will find this function useful in your own scripts when you want to utilize pipeline binding by property name to set multiple cmdlet parameter values at once.

Thanks for reading!

~Ingo

Thank you Ingo! This excellent blog should prove to be immediately valuable to many of the readers. Join me tomorrow when I will talk about more cool stuff.

I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.

Ed Wilson, Microsoft Scripting Guy 


Viewing all articles
Browse latest Browse all 3333

Trending Articles