Summary: Microsoft Scripting Guy, Ed Wilson, helps a reader compare two objects in Windows PowerShell.
Hey, Scripting Guy! I am having a problem with a script. I wrote a function to compare virtual machines with virtual drives that I have on my hard drive, and it works just fine. But when I try to put the thing into a larger script, and get the path from those drives, well, it just won’t work. In fact, all I get is gibberish. I am pasting my code into a different email. I hope you can help me.
—RL
Hello RL,
Microsoft Scripting Guy, Ed Wilson, is here. This morning I am sipping a wonderful cup of Gunpowder green tea, with a hint of lemon, cinnamon, and a single bit of raw sugar. It is a great tea to drink in the morning.
You know RL, there are some things one should never do: never brew green tea with water that is hotter than 175 degrees Fahrenheit, never feed a gremlin after midnight, and never ever use a Format* type of Windows PowerShell cmdlet inside of a function. The first two admonitions are probably self-evident, so let's spend a bit of time talking about the nature of Format* type of cmdlets and functions in Windows PowerShell.
What happens in a function…
What happens in a function does not really stay in a function. Instead, Windows PowerShell functions automatically return stuff. Your first function appears to work because it simply permits the Format-Table cmdlet to output data. That data appears to go to the output screen, and so you think it is working OK. Here is your function:
Function CompareHVHD{
Compare-Object (get-vm) (Get-ChildItem -Path e:\) |
ft -Property InputObject -HideTableHeaders }
CompareHVHD
The following is an aimage of your function and the output:
In your revised script, you attempt to pick up the paths to your virtual machines. It does not appear to work. Here is your second script:
Function CompareHVHD{
Compare-Object (get-vm) (Get-ChildItem -Path e:\) |
ft -Property InputObject -HideTableHeaders
}
$diff = CompareHVHD
foreach ($path in $diff) {
write-host "e:\$path" }
And here is the output associated with your revised script:
The difference between your first function and the second script is that in the first function, you send the output of the Format-Table directly from your function, and it appears in the output pane of the Windows PowerShell ISE. In the second script, you are capturing that output, and then attempting to manipulate the data.
What is confusing is that in the first function, it looks like you are doing what you want, and in the second script, it is not working. This is because you are displaying the output in the first function, and in your second script, you are capturing the output and trying to manipulate it.
The key to understanding this is that a Windows PowerShell function always returns objects. If you capture that object, you can do stuff with it; otherwise, it goes to the default output, which in this case, is your Windows PowerShell ISE output pane. Perhaps an easier example will illustrate this point better.
Understanding function output
The following function is basically the same as the one you wrote. It adds two numbers together, and then sends the output to the Format-Table. Here is the function:
Function add
{
(1 + 1)| ft
}
add
When I run the function, it appears to work great. Here is the output:
Suppose I want to add additional numbers to it. I modify my script so that I call the function, and then I add 5 to the output. I expect to see 7 as my result. However, what I really see is an error message. This is shown here:
The reason for the error is that I convert the object in the function from being an integer to being a table. The error message says I cannot add a number to FormatEntryData, which is one of the objects Windows PowerShell uses to create a table. Interestingly enough, if I look back up at the second picture, in the output, I will also find this FormatEntryData object in the output.
So, what do I need to do? Well, I need to remove the Format-Table cmdlet from the function. Here is the revised code, and it works just fine:
Function add
{
(1 + 1)
}
(add) + 5
How would I revise your original function? I would remove the Format-Table cmdlet from your function. Then I would look at what I was really trying to compare. I assume that you are comparing your virtual machine names with the disks associated with them. In addition, I assume that your disks contain the name of your virtual machines. Based on this assumption, here is what I might be inclined to do:
Function CompareHVHD{
Compare-Object (get-vm).name (Get-ChildItem -Path e:\vms -Recurse -Filter *.vhd*).basename }
CompareHVHD
RL, that is all there is to using Windows PowerShell to compare objects and avoid using Format* type cmdlets in a function. Troubleshooting Week will continue 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