Tag Archives: WMI

PowerShell Great Debate: “Fixing” Output


When should a script (or more likely, function) output raw data, and when should it “massage” its output?

The classic example is something like disk space. You’re querying WMI, and it’s giving you disk space in bytes. Nobody cares about bytes. Should your function output bytes anyway, or output megabytes or gigabytes?

If you output raw data, how would you expect a user to get a more-useful version? Would you expect someone running your command to use Select-Object on their own to do the math, or would you perhaps provide a default formatting view (a la what Get-Process does) that manages the math?

The “Microsoft Way” is to use a default view – again, it’s what Get-Process does. But views are separate files, and they’re only really practical (many say) when they’re part of a module that can auto-load them.

What do you think?

[boilerplate greatdebate]

Philadelphia Meeting – September 5th, 2013


Please join us Thursday, September 5th when Mike F. Robbins will be joining us via Lync to present on Using CIM Cmdlets and CIM Sessions.

Years ago, needing something as simple as the serial number from a remote server often meant that administrators had to resort to making a trip to the remote location. Even back in those days, some administrators were savvy enough to query WMI with third party programs or scripting languages such as VBScript to retrieve this information. Although, querying WMI was kind of like black magic back in those days and it was often simply easier to travel to the remote site or spend the money to purchase a third party product that could make retrieving this type of information easier for an average administrator.

Now there’s PowerShell. PowerShell empowers an average skill-set administrator to be able to retrieve this type of information and just about anything else they want to know about their servers by querying WMI, and best of all, PowerShell is free and pre-installed on all modern versions of Windows. During this session, we’ll compare the differences in the legacy WMI cmdlets and the new CIM cmdlets.

We’ll discuss the methods that were available for querying WMI with PowerShell version 2, along with some of the obstacles you’re likely to encounter when trying to retrieve information from WMI on remote servers when using PowerShell version 2. Then we’ll move onto demonstrating how much easier and efficient the CIM cmdlets in PowerShell version 3 have made querying WMI. Anyone who followed me and my scripts during this year’s scripting games knows that I I’m a big fan of the CIM cmdlets.

Mike F Robbins is a Senior Systems Engineer with almost 20 years of professional experience as an IT Pro. During his career, Mike has provided enterprise computing solutions for educational, financial, healthcare, and manufacturing customers. He’s a PowerShell Enthusiast who uses PowerShell on a daily basis to administer Windows Server, Hyper-V, SQL Server, Exchange, SharePoint, Active Directory, Terminal Services, EqualLogic Storage Area Networks, AppAssure, and Backup Exec. Mike is the winner of the advanced category in the 2013 Scripting Games. He has written guest blog articles for the Hey, Scripting Guy! Blog, PowerShell.org, PowerShell Magazine, and a chapter in the PowerShell Deep Dives book. Mike is also the leader and co-founder of the Mississippi PowerShell User Group. He blogs at http://mikefrobbins.com and can be found on twitter @mikefrobbins.

Following Mike’s presentation we’ll break for a Script-n-Tell session where members are encouraged to share a script or two they’re working on with the group. This can be anything from a useful one-liner to a function or full on module. Maybe you found a neat way to tackle a problem at work or perhaps you’re looking for some constructive feedback on your script. Either way, bring a script or two and show the group what you’re working on.

Please register if you plan to attend in person or to receive the meeting URL to join us remotely.

Some Event 3 Notes


I didn’t see anyone (although I’ll admit I haven’t checked every entry) using my EnhancedHTML module from Creating HTML Reports in PowerShell. I am ensaddened.

But man, Event 3 shows that you can really do well by learning a wee bit of HTML. Knowing an H2 and HR tag makes for much pretty results. Take it as career advice.

As a nitpick, don’t use Convert as a function verb unless all the function is going to do is convert something. It shouldn’t “Get” as well. That said, because this event wants a single function that both gets and converts… which is something I’d ordinarily avoid packing into one function… no big. It’s interesting to see the function names folks picked out.

Folks, test your scripts. Seriously.

I kinda giggled when I saw this comment in an entry:

# I'd like to Splat this but I don't know how / ran out of time

Heh. In general, this is like a cooking show. If you know your food doesn’t taste good, don’t bring it to the judges. And if you do bring it to them, don’t tell them all the cool toppings you were going to add. Just give them what you made.

Note to self: Don’t write scenarios that require HTML. It messes up the Scripting Games Web site. Duh.

You know, overall, I’m seeing good stuff. I ran through some of the low-scoring entries and didn’t see anything that didn’t deserve a lowered score. If you constructed your own HTML instead of using ConvertTo-HTML, you pretty much got universally dinged, and I can understand and support that philosophy.

Oh, and ConvertTo-HTML doesn’t output to stdout, whoever wrote that. It writes to the pipeline. Big difference.

Folks, when using Get-WmiObject, use the -Filter parameter. Don’t get everything and pipe it to Where-Object. Get the filtering done early – this is a huge performance concept.

This was interesting:

[Parameter(Mandatory=$True,ValueFromPipeline=$True)]
            [ValidateScript({Test-Connection -ComputerName $_ -Quiet -Count 2})]
            [STRING[]]$ComputerName,

I’m entirely unsure how I feel about this. I like the idea. I keep telling people than a Ping doesn’t really tell you anything when you’re about to use WMI, though. If the computer responds, WMI might still fail; if the computer doesn’t respond, WMI might still succeed. A ping is not useful diagnostic information for WMI connections. I understand the desire to try and eliminate the WMI timeout, but you’re not doing so. What if I block ICMP traffic but not WMI traffic – a very common thing at a lot of my clients? Just bear that in mind.

We’re done with Hungarian notation ($objDisk, $strComputer). Time to move on.

Commenters: Dudes, you need to read. For example, this:

When localhost, an IP address, or an alias is provided, the actual computer name is not displayed on the web page and the file name is also incorrect. Consider using one of the properties from the WMI class that has the computer name instead of what the user provided on input.

Was next to a 1-star vote. Totally inappropriate. 1 star, as the voting page clearly indicates, is when the script is totally non-functional. “Bad entry – does not function at all” is what it says under 1 star. This comment was on a working script. Maybe it didn’t fulfill every requirement, but seriously, you’d fire a guy whose script simply put an IP address instead of a computer name? No. This was a 3-star script according to the guidelines.

Anyway… things are looking great. On to Event 4, which opens for voting real soon!

CIM vs WMI cmdlets-remote execution speed


Following on from my previous post we’ll look at how the two types of cmdlets compare for accessing remote machines.

I used a similar format to the previous tests but was accessing a remote machine.

First off was the WMI cmdlet – using DCOM to access the remote Windows 2012 server

PS> 1..100 |
foreach {
Measure-Command -Expression{1..100 | foreach {Get-WmiObject -Class Win32_ComputerSystem -ComputerName W12SUS }}
} |
Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 2084.122547
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

 

The CIM cmdlets are similar but apparently a bit slower – probably due to having to build the WSMAN connection and teat it down each time.

PS> 1..100 |
foreach {
Measure-Command -Expression{1..100 | foreach {Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName W12SUS }}
} |
Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 2627.287458
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

 

So what happens is you run the CIM command over a CIM session?

PS> $sess = New-CimSession -ComputerName W12SUS
PS> 1..100 |
foreach {
Measure-Command -Expression{1..100 | foreach {Get-CimInstance -ClassName Win32_ComputerSystem -CimSession $sess }}
} |
Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 877.746649999999
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

This removes the setup and tear-down of the WSMAN connection. It suggests that the actual retrieval time for the CIM cmdlets should be reduced to 1749.540808 milliseconds for 100 accesses which is faster than the WMI cmdlets

It looks like the fastest way to access WMI information is across a CIM session. Next time we’ll look at running multiple commands

CIM cmdlets vs WMI cmdlets–speed of execution


One question that came up at the summit was the comparative speed of execution of the new CIM cmdlets vs the old WMI cmdlets.  No of us knew the answer because we’d never tried measuring the speed.

I decided to perform some tests.

This first test is accessing the local machine.  In both cases the cmdlets are using COM.  WMI uses COM and CIM will use COM if a –ComputerName parameter isn’t used.

The results are as follows:

PS> 1..100 |
foreach {Measure-Command -Expression {
1..100 | foreach {Get-WmiObject -Class Win32_ComputerSystem} }
} | Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 2008.953978
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

 

PS> 1..100 |
foreach {Measure-Command -Expression {
1..100 | foreach {Get-CimInstance -ClassName Win32_ComputerSystem} }
} | Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 2078.763174
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

 

So for pure COM access the WMI cmdlets are marginally (3.4%) faster.

What if we use the ComputerName parameter?

PS> 1..100 |
foreach {
Measure-Command -Expression {
1..100 | foreach {Get-WmiObject -Class Win32_ComputerSystem -ComputerName $env:COMPUTERNAME } }
} | Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 1499.14379
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

PS> 1..100 |
foreach {
Measure-Command -Expression {
1..100 | foreach {Get-CimInstance -ClassName Win32_ComputerSystem -ComputerName $env:COMPUTERNAME } }
} | Measure-Object -Average TotalMilliseconds

Count    : 100
Average  : 3892.921851
Sum      :
Maximum  :
Minimum  :
Property : TotalMilliseconds

This one surprised me – the WMI cmdlets are 2.5 times faster.  I suspect that is because the CIM cmdlet has to build and then breakdown the WSMAN connection each time.

Next time we’ll look at accessing a remote machine.

Use PowerShell to Modify WMI Data Such as Drive Labels


Summary: The Scripting Wife learns how to use the CIM cmdlets and Windows PowerShell to assign a new drive label by modifying WMI data.

Weekend Scripter: Changing WMI information

Microsoft Scripting Guy, Ed Wilson, is here. Well yesterday, after we got through an extremely long security line at the airport, and we jiggled around in a very cramped airplane for over five hours on our flight to Seattle for the Windows PowerShell summit, we met up with Windows PowerShell MVP Jeff Wouters at the SEATAC airport. We gave him a ride into Redmond and spent some time hanging out. Here is a picture of Jeff and the Scripting Wife I took near the Seattle Space Needle. You see, Jeff has never been to Seattle, and so we thought we would show him around.

clip_image002

The Windows PowerShell summit has already been a success, in my mind, and the first session has not even kicked off. Of course, the biggest thing for the summit for me is not the sessions, but the chance to talk and to interact with members of the community. In this regard, the summit has already exceeded expectations.

I decided to head down to the lobby of the hotel to grab something to eat this morning. It is still very early for people from the West Coast, but for the Scripting Wife and I it is already late due to the three time zone change for us. Of course, for our European friends the change is twice as great. I am sipping a cup of English Breakfast tea, munching on a croissant, and checking my email, when a loud thudding sound accompanied by a sudden shaking of the table woke me from my revelry–it is the Scripting Wife.

I hazard a query, “Why are you are up so early?”

“Hey, it is nearly noon back in Charlotte–I am ready to go,” she chirped.

“Go? Go where?” I ventured.

“Out. We are getting together a group, and we are heading out,” she explained.

“Where is out? Have you no specific destination in mind?” I asked.

“Well, maybe downtown Seattle. There is great shopping there, a cool aquarium, ferry terminals, and wonderful restaurants. We are in a real city my friend, and I for one am not going to let the opportunity pass,” she pontificated.

“I can appreciate that. But before you go, let me show you something you might need to know for the 2013 Scripting Games,” I said.

“OK, but make it quick, because when the group shows up, I am out of here,” she said.

Using the CIM cmdlets to set a WMI property

I opened the Windows PowerShell console with admin rights by right clicking the Windows PowerShell console icon and selecting Run As Administrator from the action menu. Admin rights are required to modify WMI objects.

“OK. Look over here,” I directed. “Find WMI classes related to disks.”

Find disk classes

The Scripting Wife thought for a few minutes, and typed the following.

Get-Cimc<tab><space>*disk*<enter>

The command she typed appears here.

Get-CimClass *disk*

Get dynamic classes

“Now, narrow your output to only dynamic WMI classes,” I instructed.

She used the Up arrow to retrieve the previous command and she added the Dynamic qualifier to her command. Here is what she typed:

<up arrow><space>-q<tab>dynamic<enter>

The command looks like this when complete.

Get-CimClass *disk* -QualifierName dynamic

Get logical disk info

“Cool. Now what we want to do is to find out information about drive C. To do this use the Get-CimInstance cmdlet and retrieve information about the Win32_LogicalDisk WMI class,” I said.

The Scripting Wife thought for a second, and then she began to type. Here is what she did:

Get-CimI<tab><space>Win32_LogicalDisk<enter>

The command looks like the following:

Get-CimInstance win32_logicaldisk

“OK, now look at the output and see if you can tell the property that indicates that it references the drive names,” I said.

She looked at the laptop screen. The output looks like the following:

PS C:\> Get-CimInstance win32_logicaldisk

DeviceID DriveType ProviderName VolumeName Size FreeSpace

——– ——— ———— ———- —- ———

C: 3 159486308352 10707682…

D: 3 System Reserved 366997504 113971200

F: 3 ExtraDisk 499738734592 43558884…

Narrow down the drives by letter

“So you did a good job. You found information about all of the logical disks. What I want you to do now, is to return only drive C . To do that you will need to use the Filter property,” I said.

It did not take the Scripting Wife long at all before she used the Up arrow to retrieve her previous command and to add the Filter parameter. Here is what she typed.

<up arrow><space>-f<tab><space>’DeviceID=’c:’”<enter>

Here is the command she created.

Get-CimInstance win32_logicaldisk -Filter “deviceid=’c:'”

The command and output from the command are shown here:

PS C:\> Get-CimInstance win32_logicaldisk -Filter “deviceid=’c:'”

DeviceID DriveType ProviderName VolumeName Size FreeSpace

——– ——— ———— ———- —- ———

C: 3 159486308352 10707628…

Setting a WMI property

“Notice that you have no label, or VolumeName,” I said. “To set that you will use the Set-CimInstance cmdlet and use a hash table for the Property parameter,” I said.

“Huh?” she asked. “You know that I do not speak geek.”

“It makes more sense when you just do it,” I said.

“Well, hurry up. I just got an email on my phone and they will be here in a few minutes,” she said.

“OK. First of all, you need to use the Up arrow and retrieve your previous command. Then you need to pipe the command to the Set-CimInstance cmdlet. Do that, but do not press ENTER because there is more to the command than that,” I said.

The Scripting Wife thought for a few minutes, and then began typing. Here is what she typed.

<Up Arrow><space>Set-Cimi<tab>

She paused.

“OK, now you need to add the Property parameter and an ampersand and an opening and a closing curly bracket. Do not press ENTER,” I said.

This time, she did not hesitate. Here is what she typed:

-p<tab><space>@{}

“Good. Now inside the two curly brackets you add your ‘property name equals new value’ statement. Here, you are going to set a value for the VolumeName. Give it a value of SSD_Drive,” I said.

Here is what she typed:

volumename=’SSD_Drive’

“Awesome. You are nearly there. Add the PassThru parameter so the modified WMI object returns to the Windows PowerShell line, and press ENTER,” I said.

Here is what she typed:

-p<tab><enter>

The complete command is shown here. (This is a one-line command,broken at the pipe so it is more readable.)

Get-CimInstance -ClassName win32_Logicaldisk -Filter “deviceid=’c:'” |

Set-CimInstance -Property @{volumename=’SSD_Drive’} –PassThru

The command and the associated output are shown in the following image.

clip_image004

 

“Well, isnt that special,” she mocked.

“Actually, I think it is pretty cool,” I said somewhat defensively.

“I do too. I was just giving you a hard time,” She said.

At that instant, the automatic doors to the lobby swung open and in walked a couple of obvious PowerShellers. I could tell by the T-shirts, but the Scripting Wife obviously knew them.

“Well, I am outta here. Have fun working on your presentation,” she said.

“So when will I see you again?” I asked.

“We’re meeting Don Jones and others at Azteca at 5:00,” she said.

“Zulu?”

Without missing a beat, she said “No. Pacific Standard Time.” And she was gone.

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

Ed Wilson, Microsoft Scripting Guy

Advanced Practice Event


I want to direct your attention to this forums post, which I think is worth anyone’s time to look through. I’ve left a pretty long reply with some comments on the entry that would also be worth a read.

I find that a LOT of folks – like the gentleman who posted his script – have a really good approach to PowerShell scripts. They want to use parameters. They want verbose output. They want to proactively check for errors. Where I think folks get lost is in the fine points of how PowerShell enables these features. I see folks working harder than they need to, coding functionality that the shell will actually give them for free. I also see some not-entirely-perfect approaches to things like parameters and error handling, and some occasional mis-use of advanced features (I often see SupportsShouldProcess declared but not actually implemented).

Sometimes, this simply happens because a lot of these advanced features aren’t well-documented in one convenient spot – they’re all spread out – and because folks are learning from blog posts, which may themselves have been written by someone with an incomplete understanding. Or, they’re pasting bits together without really knowing what they’re doing. That’s cool – what you have to sometimes do is take a whack at something like this poster did, and get some feedback. I’m really glad he did, because it offers an opportunity to clear up some misunderstandings, which will just make his scripts even better in the future.

I hope everyone’s looking at the Games as a learning opportunity. I hope everyone will vote on folks’ entries and leave comments when they do; I hope as many people as possible spend some time blogging about what  they see, what they’ve learned, and what they don’t understand. That’s how we’ll all improve.

Let me give you a perfect example (we’re no longer discussing the forums post, here – I’m moving on to a new topic):

Try {
  $continue = $true
  $bios = Get-WmiObject -class Win32_BIOS -computername $computer -EA Stop
} Catch {
  $continue = $false
  $computer | Out-File errors.txt -append
}
if ($continue) {
 $os = Get-WmiObject -class Win32_OperatingSystem -computername $computer
 # and so on...
}

This is how I used to code for error handling when querying multiple WMI classes. I’d set a “flag” variable, $continue, to $false if the first WMI call failed, so that I didn’t waste time on subsequent calls. Note that this is just a snippet; it isn’t an entire script. Then I had a student who coded it this way:

Try {
  $bios = Get-WmiObject -class Win32_BIOS -computername $computer -EA Stop
  $os = Get-WmiObject -class Win32_OperatingSystem -computername $computer
  # and so on...
} Catch {
  $computer | Out-File errors.txt -append
}

Much more concise, and same effect. If the first WMI call fails, I jump into the Catch block, and skip the remaining code anyway. So there are constantly learning opportunities in seeing someone else’s approach. For me, I learn new approaches that are sometimes better than what I’ve been doing. I also learn how to better teach PowerShell to people, by seeing common mistakes and misunderstandings. It’s great to share your failures – that’s how we grow!

Update: Someone dropped me a line and made a couple of points, which I want to address:

In the reply to the blog post you say: “Please consider properly setting -ErrorAction on the command (Get-WmiObject, in your case) and using a Try/Catch construct to actually handle errors, not just hide them.” The example shown does the exact opposite. Any terminating error is caught, logged to a file, but not re-thrown effectively hiding the exception.

I disagree. First, handling an error may still involve suppressing the error message. But I’m suppressing it for just one command, not the entire script; I’m also handling the error by, in my case, logging it to a file. How you choose to handle may differ. What I don’t want to do is toss a terminating exception – I’m in a loop, and want my command to continue processing the next object.

Also the $os  = … part is missing the -errorAction STOP.

That’s deliberate. If there’s going to be an anticipated error – lack of connectivity, bad credentials, etc., I’m going to get an error on the first WMI call ($bios). I’ll trap it, log it, and move on to the next computer (one presumes those snippets of mine are running in a loop of some kind, processing one computer at a time). If there’s an unexpected error, like a corrupt WMI repository or something, the second WMI call ($os) will explode, generating an error that I very much want to see, because I didn’t anticipate it.

Notice a word that I used a lot there: “I.” I’m coding the script for the way I want to it to run. I want anticipated errors logged, and I want unanticipated errors to continue exploding. You may want your scripts to do different things. I’m not putting these snippets out there as the One True Way To Code, because there’s no such thing. What I am saying is that you need to think about why you’re coding the way you are, and have some justification for it.

Globally suppressing error messages, but not doing anything to handle errors that you do suppress, is a poor practice. Beyond that, do what you need to do. I’m fine with someone suppressing an error they’ve dealt with. But if your code isn’t dealing with it, then the person running the script needs to see something’s gone wrong.

 

Weekend Scripter: Use PowerShell to Find the Version of Windows


Summary: The Scripting Wife learns about using Windows PowerShell to find computer hardware information in prep for the 2013 Scripting Games.

Microsoft Scripting Guy, Ed Wilson, is here. This morning did not start with a nice leisurely cup of tea on the lanai at home. Instead, it started by rushing around to get packed, loading up the vehicle, vying for a parking place at the airport, taking a shuttle from parking to the Charlotte terminal, standing in line for an hour for a security screen that was more intimate than my last physical exam, putting shoes and belt back on and repacking my suitcase, and standing in another really long line to buy an overpriced cup of warm milk with a shot of acidic coffee in it. Then another long line to board a really, really cramped airplane. Luckily, the Scripting Wife is with me, and she helps make the entire process a little more pleasant. Yep, we are finally on our way to Seattle for the Windows PowerShell Summit.

Scripting Wife uses WMI

After takeoff, I open my laptop and the Windows PowerShell console and start playing around. Before long the Scripting Wife is poking me in my shoulder.

“Yes,” I say.

“So I think this would be the perfect time for you to talk to me about using WMI with Windows PowerShell,” she stated.

“OK, good idea,” I said as I slid my laptop over to allow her to use it.

“So what now?” she asked.

“Well the first thing to know about WMI is that there are lots of WMI classes. They are also arranged into different namespaces, but we will not get into that right now. To find WMI classes, use the Get-CimClass cmdlet in Windows PowerShell 3.0,” I said.

“Well, that makes sense. So what do I do if I need to find the name of my operating system?” she asked.

“Why don’t you try it? Use the Get-CimClass cmdlet, and put os into a pair of wildcard characters,” I suggested.

She typed the following:

Get-Cimc<tab><Space>*os*<enter>

The command is shown here:

Get-CimClass *os*

The command and the associated output are shown in the following image.

Image of command output

“Well, that did not do what I expected it to do,” she said.

“So, you have found out that WMI does not really have a class named OS, or anything similar to OS. What are you really looking for?” I asked.

“I want to know the version of Windows,” she said.

“The cool thing about Get-CimClass is that you can search for classes that contain a specific property. So why don’t you look for WMI classes that contain a property of version?” I suggested.

The Scripting Wife typed the following:

Get-Cimc<tab><space>-p<tab><space>version<enter>

The following is the command she created.

Get-CimClass -PropertyName version

The command and the output associated with the command are shown in the image that follows.

Image of command output

“It is still a lot of stuff,” she complained.

“Well one of the things you need to know about WMI is that there are abstract and dynamic classes,” I began.

“And why do I need to know that?” she interrupted.

“Maybe you do not need to know all that, but keep in mind you want to use dynamic classes,” I began again.

“Why?” she asked.

“Because dynamic WMI classes go off and get information. So they are the ones that you want to use. The abstract classes are more used by developers,” I said.

“Well OK. So I only need to remember to use dynamic classes. I got it. Now what?” she said.

“The Get-CimClass cmdlet has a QualifierName parameter. You can specify that you only want dynamic WMI classes returned. It will give you a better output,” I said.

“Cool. So let’s do it,” she said energetically.

“Use the Up arrow to retrieve your previous command. Go to the end of the command and add the QualifierName parameter,” I said. “And don’t forget to use tab completion to help you.”

The Scripting Wife typed:

<uparrow><space>-q<tab><space>dynamic<enter>

Here is the command she created:

Get-CimClass -PropertyName version -QualifierName dynamic

The command and associated output are shown here.

Image of command output

“Cool,” she said. “So where do I find the version?”

“Look at the output. See the Win32_OperatingSystem?” I started.

“Oh yeah, I get it,” she said.

“Now use Get-CimInstance to retrieve the operating system information,” I said.

She typed the following:

Get-Cimi<tab><space>Win32_Op<tab><enter>

Here is the command:

Get-CimInstance Win32_OperatingSystem

Here is the command and the output from the command:

Image of command output

“Well ain’t that just special,” she said as she pushed the laptop back over to me. She then opened her Surface and began reading a book that she had downloaded.

I returned to my Windows PowerShell console, and tried to make sense of life.

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

Ed Wilson, Microsoft Scripting Guy 

Get CIMInstance from PowerShell 2.0


I love the new CIM cmdlets in PowerShell 3.0. Querying WMI is a little faster because the CIM cmdlets query WMI using the WSMAN protocol instead of DCOM. The catch is that remote computers must be running PowerShell 3 which includes the latest version of the WSMAN protocol and the WinRM service. But if your computers are running 3.0 then you can simply run a command like this:

get-content computers.txt | get-ciminstance win32_operatingsystem

However, if one of the computers is running PowerShell 2.0, you’ll get an error.

get-ciminstance-error

In this example, CHI-DC02 is not running PowerShell 3.0. The solution is to create a CIMSession using a CIMSessionOption for DCOM.

$opt = New-CimSessionOption -Protocol Dcom
$cs = New-CimSession -ComputerName CHI-DC02 -SessionOption $opt
$cs | get-ciminstance win32_operatingsystem

get-ciminstance-dcom

So there is a workaround, but you have to know ahead of time which computers are not running PowerShell 3.0. When you use Get-CimInstance and specify a computername, the cmdlet setups up a temporary CIMSession. So why not create the temporary CIMSession with the DCOM option if it is needed? So I wrote a “wrapper” function called Get-MyCimInstance to do just that.

The heart of the function is a nested function to test if a remote computer is running WSMAN 3.0.

Function Test-IsWsman3 {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline)]
[string]$Computername=$env:computername
)

Begin {
    #a regular expression pattern to match the ending
    [regex]$rx="\d\.\d$"
}
Process {
    Try {
        $result = Test-WSMan -ComputerName $Computername -ErrorAction Stop
    }
    Catch {
        Write-Error $_.exception.message
    }
    if ($result) {
        $m = $rx.match($result.productversion).value
        if ($m -eq '3.0') {
            $True
        }
        else {
            $False
        }
    }
} #process
End {
 #not used
}
} #end Test-IsWSMan

The function uses Test-WSMan and a regular expression to get the remoting version. If it is 3.0 the function returns True. In Get-MyCIMInstance I test each computer and if not running 3.0, create the CIMSession option and include it when creating the temporary CIMSession.

Try {
#test if computer is running WSMAN 2
$isWSMAN3 = Test-IsWsman3 -Computername $computer -ErrorAction Stop

if (-NOT $isWSMAN3) {
    #create a CIM session using the DCOM protocol
    Write-Verbose "Creating a DCOM option"
    $opt = New-CimSessionOption -Protocol Dcom
    $sessparam.Add("SessionOption",$opt)
}
Else {
        Write-Verbose "Confirmed WSMAN 3.0"
}

Try {               
    $session = New-CimSession @sessParam
}
Catch {
    Write-Warning "Failed to create a CIM session to $computer"
    Write-Warning $_.Exception.Message
}

I’m using a Try/Catch block because if the computer is offline, my test function will throw an exception which I can catch.

Catch {
        Write-Warning "Unable to verify WSMAN on $Computer"
     }

Otherwise, all is good and  I can pass the rest of the parameters to Get-CimInstance.

#create the parameters to pass to Get-CIMInstance
        $paramHash=@{
         CimSession= $session
         Class = $class
        }

        $cimParams = "Filter","KeyOnly","Shallow","OperationTimeOutSec","Namespace"
        foreach ($param in $cimParams) {
          if ($PSBoundParameters.ContainsKey($param)) {
            Write-Verbose "Adding $param"
            $paramhash.Add($param,$PSBoundParameters.Item($param))
          } #if
        } #foreach param

        #execute the query
        Write-Verbose "Querying $class"
        Get-CimInstance @paramhash

At the end of the process, I remove the temporary CIMSession. With this, now I can query both v2 and v3 computers.
get-myciminstance01
Notice for CHI-DC02 I’m creating the DCOM option. Here’s the command without all the verboseness.
get-myciminstance02
I could have created a proxy function for Get-CimInstance, but not only are they more complicated, I didn’t want that much transparency. I wanted to know that I’m querying using my function and not Get-CimInstance. Here’s the complete script.

#requires -version 3.0

<#
  ****************************************************************
  * DO NOT USE IN A PRODUCTION ENVIRONMENT UNTIL YOU HAVE TESTED *
  * THOROUGHLY IN A LAB ENVIRONMENT. USE AT YOUR OWN RISK.  IF   *
  * YOU DO NOT UNDERSTAND WHAT THIS SCRIPT DOES OR HOW IT WORKS, *
  * DO NOT USE IT OUTSIDE OF A SECURE, TEST SETTING.             *
  ****************************************************************
 #>

Function Get-MyCimInstance {

<#
.Synopsis
Create on-the-fly CIMSessions to retrieve WMI data
.Description
The Get-CimInstance cmdlet in PowerShell 3 can be used to retrieve WMI information
from a remote computer using the WSMAN protocol instead of the legacy WMI service
that uses DCOM and RPC. However, the remote computers must be running PowerShell
3 and the latest version of the WSMAN protocol. When querying a remote computer,
Get-CIMInstance setups a temporary CIMSession. However, if the remote computer is
running PowerShell 2.0 this will fail. You have to manually create a CIMSession
with a CIMSessionOption to use the DCOM protocol.

This command does that for you automatically. It is designed to use computernames.
The computer is tested and if it is running PowerShell 2.0 then a temporary session
is created using DCOM. Otherwise a standard CIMSession is created. The remaining 
CIM parameters are then passed to Get-CIMInstance.

Get-MyCimInstance is essentially a wrapper around Get-CimInstance to make it easier
to query data from a mix of computers.
.Example
PS C:\> get-content computers.txt | get-myciminstance -class win32_logicaldisk -filter "drivetype=3"
.Notes
Last Updated: April 11, 2013
Version     : 1.0
Author      : Jeffery Hicks (@JeffHicks)

Read PowerShell:
Learn Windows PowerShell 3 in a Month of Lunches
Learn PowerShell Toolmaking in a Month of Lunches
PowerShell in Depth: An Administrator's Guide

.Link

http://jdhitsolutions.com/blog/2013/04/get-ciminstance-from-powershell-2-0

.Link
Get-CimInstance
New-CimSession
New-CimsessionOption

.Inputs
string

.Outputs
CIMInstance

#>

[cmdletbinding()]

Param(
[Parameter(Position=0,Mandatory,HelpMessage="Enter a class name",
ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[string]$Class,
[Parameter(Position=1,ValueFromPipelineByPropertyName,ValueFromPipeline)]
[ValidateNotNullorEmpty()]
[string[]]$Computername=$env:computername,
[Parameter(ValueFromPipelineByPropertyName)]
[string]$Filter,
[Parameter(ValueFromPipelineByPropertyName)]
[string[]]$Property,
[Parameter(ValueFromPipelineByPropertyName)]
[ValidateNotNullorEmpty()]
[string]$Namespace="root\cimv2",
[switch]$KeyOnly,
[uint32]$OperationTimeoutSec,
[switch]$Shallow,
[System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
)

Begin {
    Write-Verbose -Message "Starting $($MyInvocation.Mycommand)"  
    Write-verbose -Message ($PSBoundParameters | out-string)

    Function Test-IsWsman3 {
        [cmdletbinding()]
        Param(
        [Parameter(Position=0,ValueFromPipeline)]
        [string]$Computername=$env:computername
        )

        Begin {
            #a regular expression pattern to match the ending
            [regex]$rx="\d\.\d$"
        }
        Process {
            Try {
                $result = Test-WSMan -ComputerName $Computername -ErrorAction Stop
            }
            Catch {
                #Write the error to the pipeline if the computer is offline
                #or there is some other issue
                write-Error $_.exception.message
            }
            if ($result) {
                $m = $rx.match($result.productversion).value
                if ($m -eq '3.0') {
                    $True
                }
                else {
                    $False
                }
            }
        } #process
        End {
         #not used
        }
        } #end Test-IsWSMan

} #begin

Process {
    foreach ($computer in $computername) {
        Write-Verbose "Processing $computer"

        #hashtable of parameters for New-CimSession
        $sessParam=@{Computername=$computer;ErrorAction='Stop'}
        if ($credential) {
            Write-Verbose "Adding alternate credential for CIMSession"
            $sessParam.Add("Credential",$Credential)
        }
        Try {
        #test if computer is running WSMAN 2
        $isWSMAN3 = Test-IsWsman3 -Computername $computer -ErrorAction Stop

        if (-NOT $isWSMAN3) {
            #create a CIM session using the DCOM protocol
            Write-Verbose "Creating a DCOM option"
            $opt = New-CimSessionOption -Protocol Dcom
            $sessparam.Add("SessionOption",$opt)
        }
        Else {
                Write-Verbose "Confirmed WSMAN 3.0"
        }

        Try {               
            $session = New-CimSession @sessParam
        }
        Catch {
            Write-Warning "Failed to create a CIM session to $computer"
            Write-Warning $_.Exception.Message
        }

        #create the parameters to pass to Get-CIMInstance
        $paramHash=@{
         CimSession= $session
         Class = $class
        }

        $cimParams = "Filter","KeyOnly","Shallow","OperationTimeOutSec","Namespace"
        foreach ($param in $cimParams) {
          if ($PSBoundParameters.ContainsKey($param)) {
            Write-Verbose "Adding $param"
            $paramhash.Add($param,$PSBoundParameters.Item($param))
          } #if
        } #foreach param

        #execute the query
        Write-Verbose "Querying $class"
        Get-CimInstance @paramhash

        #remove the temporary cimsession
        Remove-CimSession $session
     } #Try
     Catch {
        Write-Warning "Unable to verify WSMAN on $Computer"
     }
    } #foreach computer

} #process

End {
    Write-Verbose -Message "Ending $($MyInvocation.Mycommand)"
} #end

} #end Get-MyCimInstance

I hope you’ll let me know what you think and if you find this useful.

UPDATE: I’ve revised this script and article since it’s original posting to better handle errors if you can’t test WSMAN. I also added support for alternate credentials, which is something you can’t do with Get-CimInstance.

Manning Deal of the Day – April 6 2013


My PowerShell and WMI book will be Manning’s deal of the day for 6 April 2013.  The deal will go live at Midnight US ET and will stay active for about 48 hours.

This is your chance to get the book with a 50% discount.

Use code dotd0406au at manning.com/siddaway2/

The Deal of the Day offer also applies to SharePoint Workflow in Action (http://www.manning.com/wicklund/).

Enjoy

Manning Deal of the Day – April 6 2013


My PowerShell and WMI book will be Manning’s deal of the day for 6 April 2013.  The deal will go live at Midnight US ET and will stay active for about 48 hours.

This is your chance to get the book with a 50% discount.

Use code dotd0406au at manning.com/siddaway2/

The Deal of the Day offer also applies to SharePoint Workflow in Action (http://www.manning.com/wicklund/).

Enjoy

Shutting down a remote computer


PowerShell provides the Stop-Computer cmdlet for closing down a remote machine. I find this especially useful in my virtual test environment. I’ll have several machines running but won’t necessarily have logged onto them. Using Stop-Computer means that I can shut them down cleanly without the hassle of logging onto them.

In modern Windows systems you have to explicitly enable remote WMI access through the Windows firewall. Stop-Computer uses WMI. If the WMI firewall ports aren’t enabled you can’t use Stop-Computer. I’ve taken to use the CIM cmdlets rather than WMI so sometimes don’t open the WMI firewall ports.

One quick function later and I have an answer

function invoke-cimshutdown {            
[CmdletBinding()]            
param (            
 [string]$computername            
)            
$comp = Get-CimInstance win32_operatingsystem -ComputerName $computername            
Invoke-CimMethod -InputObject $comp -MethodName Shutdown            
}

Pass the computer name as a parameter – I deliberately didn’t put a default

Use Get-CimInstance to get the Win32_operatingsystem class and use Invoke-CimMethod to call the Shutdown method.

Another reason not to enable WMI on my server 2012 firewalls.

You can use this on legacy versions of Windows if you have PowerShell v3, and therefore WSMAN v3, installed

CIM cmdlets


The CIM cmdlets are found in the CIMcmdlets module.

Get-Command -Module CimCmdlets  produces this list of names.  I’ve added some information on the tasks they perform

Get-CimAssociatedInstance  is for working with WMI associated classes
Get-CimClass  is for discovering the properties and methods of a WMI class
Get-CimInstance    is analogous to  Get-WmiObject
Get-CimSession 
Invoke-CimMethod    is analogous to Invoke-WMIMethod   
New-CimInstance  can be used for creating a new WMI instance in certain circumstances
New-CimSession
New-CimSessionOption
Register-CimIndicationEvent    is analogous to Register-WMIEvent
Remove-CimInstance  is analogous to Remove-WMIObject
Remove-CimSession
Set-CimInstance  is analogous to Set-WMIInstance

The CIM session cmdlets are for working with the CIm sessions which are analogous to PowerShell remoting sessions but are used by the CIM cmdlets AND the new WMI based cmdlets in Windows 8/2012 such as the networking cmdlets

WMI vs CIM


An email debate yesterday regarding the use of the CIM cmdlets (new in PowerShell 3) vs the WMI cmdlets made me realise that other people are probably wondering the same thing,

The question is really part of a the semi-philosophical debate about when you should adopt new technology.

In the case of the WMI/CIM cmdlets the resolution is fairly straightforward.

If you are using PowerShell v2 you have to use the WMI cmdlets.

If you are using PowerShell v3 – even if you are accessing legacy systems I would recommend the CIM cmdlets.  There are a number of benefits to using the CIM cmdlets:

  • use of WSMAN for remote access – no more DCOM error. You can drop back to DCOM for accessing systems with WSMAN 2 installed
  • use of CIM sessions for accessing multiple machines
  • Get-CIMClass for investigating WMI classes
  • improved way of dealing with WMI associations

As far as I am aware the only thing the CIM cmdlets can’t do is access amended qualifiers such as the class description. Seeing that many classes don’t that set it’s not a major hardship.

Now that I’ve recommended you should use them I’d better show you how – that will cover a mini-series of posts over the next few days

WMI Explorer from The PowerShell Guy


Several years ago, The PowerShell Guy, aka MoW, wrote a fantastic graphical PowerShell script that was a WMI Explorer. With this script you could connect to a computer and namespace, browse classes and view instances. A great way for discovering things about WMI.

wmibrowser

However Marc has moved on to other things I think and his original site, at least right now, no longer seems to be running. So in the spirit of community, and I hope he won’t mind, I’m sharing my version. Download PowerShellGuy-WmiExplorer.zip and extract the script file. Run the script, and enjoy.

Update 3/12/2013
I didn’t realize the script is digitally signed. However, most likely some of the certificates in the trust chain originally used 6-7 years ago have probably expired. You might get an error about unable to validate a certificate chain. If that is the case, open the script in the ISE or Notepad and go to the end. Delete the commented signature block, save the file and try again. If your execution policy is AllSigned, you will need to resign the script.