The other day fellow PowerShell MVP Adam Bertram published an article about using custom properties with Select-Object. It is a good article in that it gets you thinking about PowerShell in terms of objects and not simple text. But I want to take Adam's article as a jumping off point and take his ideas a bit further. I'm going to use Adam's same example as a learning tool. Don't get distracted by other ways to get the same information. The process and techniques are what matter here.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
Whenever I'm working with PowerShell, I'm always thinking about how I can use this at scale. How can I get this same information for 10 or 100 servers? And of course, at this point I need to make sure I include a computername in the results. First, I'll try something with a single computer.
Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName chi-hvr1 | Select PSComputername,@{Name = "MemoryGB";Expression = { ($_.Capacity | Measure -sum).sum/1GB}}
Close but not quite. Get-CimInstance is writing multiple objects to the pipeline. The server in question has 2 8GB sticks of memory which is what you see in the output. I need something more along Adam's original idea to that this becomes 16GB.
What I really want is the Sum property from Measure-Object and to that I need to add a Computername property. I'll turn things around a bit.
Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName chi-hvr1 -PipelineVariable pv| Measure-Object -Property capacity -Sum | Select @{Name="Computername";Expression={$pv.pscomputername}}, @{Name="MemoryGB";Expression = {$_.sum/1GB}}
This works because I'm using the common PipelineVariable parameter introduced in PowerShell 4. What happens is that the pipeline output from Get-CimInstance is stored in a variable, pv, which I can access later in the expression. In my case I'm defining a new property for the computername using $pv and adding it to the selected output from Measure-Object.
However, if I try this for multiple computer names, I don't get the expected result.
That's because I'm adding up the physical memory instances from all servers, which isn't really what I want. Instead, this is a situation where I have to process each computer individually.
$computers = "chi-hvr1","chi-hvr2","chi-web02","chi-win10","chi-tp04","win81-ent-01" foreach ($computer in $computers) { Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName $computer -PipelineVariable pv| Measure-Object -Property capacity -Sum | Select @{Name="Computername";Expression={$pv.pscomputername}}, @{Name="MemoryGB";Expression = {$_.sum/1GB}} }
One thing to be careful of when using the ForEach enumerator is that you can't pipe the output to another cmdlet like Export-CSV, unless you explicitly save the results to a variable.
$data = Foreach ($computer in $computers) { …
Then you can pipe $data to other cmdlets. You can use ForEach-Object although it might be little harder to follow.
$computers | foreach { Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName $_ -PipelineVariable pv| Measure-Object -Property capacity -Sum | Select @{Name="Computername";Expression={$pv.pscomputername}}, @{Name="MemoryGB";Expression = {$_.sum/1GB}} } | Sort MemoryGB,Computername
But this makes it easier if you need to pipe the output to something else.
To wrap this up let's go all out and define a few more custom properties.
$computers | foreach { Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName $_ -PipelineVariable pv| Measure-Object -Property capacity -Sum | Select @{Name = "Computername";Expression={$pv.pscomputername}}, @{Name = "Computer";Expression = { $cs = Get-CimInstance Win32_computersystem -ComputerName $pv.psComputername #construct a string with manufacturer and model "$($cs.Manufacturer):$($cs.Model)" }}, @{Name = "MemoryGB";Expression = {$_.sum/1GB}}, @{Name="NumberSticks";Expression = {$_.Count}} } | Sort MemoryGB,Computername
Even though I'm selecting a few properties from the output of Measure-Object, I'm defining several others which are calculated on the fly. There is so much you can do with this technique, but if I lost you anywhere please let me know.
You could always group by pscomputername first, and then apply the custom property.
$computers = “chi-hvr1″,”chi-hvr2″,”chi-web02″,”chi-win10″,”chi-tp04″,”win81-ent-01″
$MemoryGB = @{Name=”MemoryGB”;Expression = {($_.Group.Capacity | Measure -Sum).Sum/1GB}}
Get-CimInstance -ClassName Win32_PhysicalMemory -ComputerName $Computers | group pscomputername | Select Name,$MemoryGB
Love custom properties, use them every day! Also useful is using Add-Member to add your property to the existing object, or a creating new custom object.
Sure. There are many ways you could apply these ideas. The primary takeaway is for people to think about working with objects in PowerShell and not text. Once you get that in your head there are many roads you can take.
When you run Get-CimInstance does the connection open then close right away or does it stay open for a little bit extra? Wouldn’t it be better to call New-CimSession then use that session with Get-CimInstance?
If you are referring to the last code example, yes, I am technically opening a 2nd connection for the nested Get-CimInstance command. The whole thing could probably be revised to use CimSessions, although the main point of the article is about using custom properties. I just happened to use a Cim command for my demonstration.
Jeffery, thanks for that great article!
But, how does that work when I have already a variable containing CIM sessions?
The following doesn’t work:
Get-CimInstance -ClassName Win32_PhysicalMemory -CimSession $AllCimSession -PipelineVariable pv |
Measure-Object -Property capacity -Sum |
Select @{Name = “Computername”;Expression={$pv.pscomputername}},
@{Name = “Computer”;Expression = {
$cs = Get-CimInstance Win32_computersystem -ComputerName $pv.psComputername
#construct a string with manufacturer and model
“$($cs.Manufacturer):$($cs.Model)”
}},
@{Name = “MemoryGB”;Expression = {$_.sum/1GB}},
@{Name=”NumberSticks”;Expression = {$_.Count}}
It doesn’t. This example doesn’t take existing sessions into account. I just happened to use this command to illustrate my point about custom properties.