ByValue & ByPropertyName

Recently I had the pleasure of attending a PowerShell course. Although it was not exactly what I expected I still picked up a few things here and there. It was good to see that people attending the course got a bit more enthusiastic on PowerShell during the course and they acquired a solid base to start using PowerShell.

On of the items that was discussed was how to use the pipeline. Casting the output of a cmdlet to another. Something obvious as “get-service -name “bits”| start-service” was used during a demo. That’s great for starters, however it gets a little more confusing when cmdlets with a different noun are used. As an example, lets try these two cmdlets to play nice with each other.

Resolve-DnsName <something> | Test-NetConnection

Let first try these two cmdlets individually to see how they actually work. Remember you can always use the help system for more information.

help Resolve-DnsName -Full

Without knowing anything about the cmdlet itself, I’m guessing that it will need a hostname, fqdn or ip address. Just trying a hostname with the name “server” resolves into this:

ByValueProprtyName 01

So apparently I was correct. But how does this actually work? What does this cmdlet actually expect as input? In PowerShell this is really easy to figure out. The next command gets the parameter that’s required for this cmdlet.

get-help Resolve-DnsName -Parameter * | where {($_.Position -eq "0") -and ($_.Required -like "true")}

ByValueProprtyName 02

Here we see that a single input object is required, which is called “Name”. We didn’t specify the Name parameter but it still worked because according to the help system anything on position 0 (The first parameter) is automatically assigned to the first parameter. Let’s try this on “Test-NetConnection”.

ByValueProprtyName 03

Okay… weird that produces absolutely nothing. Not to worry, the cmdlet construction isn’t faulty, “Test-NetConnection” just doesn’t require any input. Running the cmdlet by itself checks connectivity to the Internet.

ByValueProprtyName 04

Since we want to try testing an internal server it would make sense that a certain parameter would accept input. Using “get-help” we would need to figure that out.

get-help Test-NetConnection -Parameter *

ByValueProprtyName 05

Because this cmdlet doesn’t require input, the parameter position “0” is not available. This is now transferred to position “1”. Looking at the help file a parameter with the name “Computername” would do the trick. Let’s try it first without specifying the actual parameter name itself.

ByValueProprtyName 06

Eureka! This works. So combining the two cmdlets would work. Let’s try that.

ByValueProprtyName 07

Hmm no go, bummer. “Test-NetConnection” apparently has no idea what we want to accomplish. Let’s figure out what the result of “Resolve-DnsName” actually is? We do this with

Resolve-DnsName server | gm

GM stands for Get-Members, or “give me all stuff from an object”.

ByValueProprtyName 08

What’s important here is the Typename, “Microsoft.DnsClient.Commands.DnsRecord_A”. This “Type” is the object type that is passed over the pipeline. This object type needs to be something that the receiving end understands. This is the PowerShell technique that is called “ByValue”. So the value type of the object that’s being passed needs to be of the same type.  PowerShell under the hood does it’s magic by trying to match the output of the first cmdlet to the input of the second one IF the object type is the same. So let’s see what “Test-NetConnection” expect.

ByValueProprtyName 09

Using the “Get-help” function again we take a closer look at the “Computername” parameter. The input is specified directly after the parameter name “[<string>]”. So it expects the “ByValue” object name property that is passed over the pipeline to be of the string type. Let’s try the simplistic version first. Just pass a string as input.

“server” | Test-NetConnection

ByValueProprtyName 10

So, now we know that this actually works. Let’s convert the output of the “Resolve-Dnsname” and see what happens.

(Resolve-DnsName server).name.ToString() | Test-NetConnection

ByValueProprtyName 11

That seems to work as well! Please note that you could technically skip the “.ToString” part because the property “name” is already a string, regardless of the original type of the entire object. This wouldn’t work for other properties that have a different type. Use “GM” or the “.GetType()” method again to see the actual type.

ByValueProprtyName 12

Please note that by using “ByValue” you can only pass a single property over the pipeline!

ByPropertyName

One of the other options that we could also use is the “ByPropertyName” option. As we can see from the help text this option is available for the “Computername” parameter of “Test-NetConnection”.

What this simply means is that the property name of the output of the first cmdlet must match the input parameter of the second cmdlet. In our example the property “Name” of the “Resolve-DnsName” output must match the input parameter “Computername” of the “Test-NetConnection” cmdlet. It doesn’t by now but we can make it so by creating an expression like this:

Resolve-DnsName server | select @{Name="Computername";Expression={$_.name}} | Test-NetConnection

The select statement is where the magic happens. This “Name=” part tells PowerShell that a new property with the name “Computername” should be created. The “Expression” fills that newly created property with the value of the “Name” property of the first cmdlet. The best part here is that the Object type here doesn’t matter, as long as the type of the property matches. In this case it still needs to be a string. However the object type can remain a different type. In this case a “Selected.Microsoft.DnsClient.Commands.DnsRecord_A” object type.

ByValueProprtyName 13

Putting this all together we end up with the following command line:

Resolve-DnsName server | select @{Name="Computername";Expression={$_.name}} | Test-NetConnection

ByValueProprtyName 14

And there we have what we wanted to accomplish!!

As you can see there are multiple ways to go about constructing your entire command line and casting properties over the pipeline. Specially with the “ByValue” and “ByPropertyName” options. I hope that this post added in understanding the differences.

References


Learn About Using PowerShell Value Binding by Property Name
https://blogs.technet.microsoft.com/heyscriptingguy/2013/03/25/learn-about-using-powershell-value-binding-by-property-name/

Two Ways To Accept Pipeline Input In PowerShell
https://blogs.technet.microsoft.com/askpfeplat/2016/11/21/two-ways-to-accept-pipeline-input-in-powershell/

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s