Targeting Down Level Clients with the Get-CimInstance PowerShell Cmdlet

This past weekend, I attended PowerShell Saturday 002 in Charlotte, NC. One of the sessions I attended was “Discovering the Power of WMI & PowerShell in the Enterprise” by Brian Wilhite.

One of the cool things that Brian covered in his session was how to use the new PowerShell version 3 Get-CimInstance cmdlet to target down level clients that don’t have PowerShell installed or don’t have PowerShell remoting enabled.

There’s no parameter to change the protocol on the actual Get-CimInstance or New-CimSession cmdlets, but it can be added using the the New-CimSessionOption cmdlet.

$Opt = New-CimSessionOption -Protocol Dcom
$CimSession = New-CimSession -ComputerName 'server2', 'server3' -SessionOption $Opt
$CimSession

ps-cim0.jpg

I’m able to query a 2003 R2 server that doesn’t even have PowerShell installed and a 2008 R2 server that doesn’t have PowerShell remoting enabled:

Get-CimInstance -CimSession $CimSession -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim2.jpg

I’ve been switching back to the Get-WMIObject cmdlet for down level clients, but then I have to use one command for one set of machines and potentially another for the machines that have PowerShell Remoting enabled if I want to take advantage of it. Brian’s session got my wheels turning and I started thinking “What if I want to query machines that have PowerShell remoting enabled and down level clients via DCOM in the same command?”

I stored a couple of more CIM Sessions in a different variable using the default settings:

$CimSession2 = New-CimSession -ComputerName 'www', 'vmhost'
$CimSession2

ps-cim3.jpg

You can see all the sessions I have open and what protocol they’re using with the Get-CimSession cmdlet:

Get-CimSession

ps-cim4.jpg

The only way I was able to use more than one variable when one or more of them is an array (contains more than one item) was in a loop. I used the ForEach-Object cmdlet in this example but that seemed to be more complicated than it needed to be:

$CimSession, $CimSession2 |
ForEach-Object {Get-CimInstance -CimSession $_ -ClassName Win32_OperatingSystem -property csname, caption, version} |
select csname, caption, version |
Format-Table -AutoSize

ps-cim5.jpg

Let’s just start over. Removing the sessions and verifying they’re removed:

Get-CimSession | Remove-CimSession
Get-CimSession

ps-cim6.jpg

I’ve created the sessions similar to before except not placing them in a variable at the same time:

New-CimSession -ComputerName 'server2', 'server3' -SessionOption $Opt
New-CimSession -ComputerName 'www', 'vmhost'

ps-cim7.jpg

You can see the sessions just like before by using the Get-CimSession cmdlet:

Get-CimSession

ps-cim8.jpg

I’ll place all of them into one variable, both the DCOM and WSMAN ones:

$CimSession = Get-CimSession
$CimSession

ps-cim9.jpg

Now to run a simple one liner which query’s all four clients, two with DCOM and two with WSMAN:

Get-CimInstance -CimSession $CimSession -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim10.jpg

Is that cool or what? No more switching back and forth between Get-WMIObject and Get-CimInstance for me.

After thinking about this for a while, my thought was “Why do I even need to store this in a variable?”. I’ll just put the Get-CimSession cmdlet in parenthesis:

Get-CimInstance -CimSession (Get-CimSession) -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim11.jpg

I took a look at the CimSession parameter of Get-CimInstance cmdlet to see if it accepts pipeline input. It does and the Get-CimSession cmdlet produces a CimSession Object for its output:

help Get-CimInstance -Parameter CimSession
Get-CimSession | Get-Member

ps-cim14.jpg

A lightbulb came on. I can pipe the output of Get-CimSession to Get-CimInstance:

Get-CimSession |
Get-CimInstance -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim13.jpg

I decided to take one last look at this. If I’m only going to use the variable for setting the New-CimSessionOption once, then I can use parenthesis and skip the variable. I double checked and New-CimSessionOption can’t be piped to New-CimSession. An additional option I found while researching the different parameters is that the CIM sessions can be named as shown in this example where I’ve used group1 for the first set of servers and group2 for the second set of servers:

New-CimSession -ComputerName 'server2', 'server3' -SessionOption (New-CimSessionOption -Protocol Dcom) -Name group1
New-CimSession -ComputerName 'www', 'vmhost' -Name group2

ps-cim15.jpg

This way I can easily run commands against all of the servers that I’ve named group# by specifying group*. This is helpful if other CIM sessions besides the ones named group# exist.

Get-CimSession -Name group* |
Get-CimInstance -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim16.jpg

Or I can run commands against each group of servers individually:

Get-CimSession -Name group1 |
Get-CimInstance -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

Get-CimSession -Name group2 |
Get-CimInstance -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim17.jpg

If I have half a dozen different groups of servers defined as group1 through 6, I can pick and chose the different groups of servers as shown below instead of having to pick all the individual CIM sessions:

Get-CimSession -Name group1, group2 |
Get-CimInstance -ClassName Win32_OperatingSystem -property csname, caption, version |
select csname, caption, version |
Format-Table -AutoSize

ps-cim18.jpg

µ