Efficient Windows PowerShell Administration with WMI and CIM
Let's imagine that you and I started a business manufacturing and selling network interface cards (NICs). Industry standards would be pretty important to us, right? How could we make it easier for our Ethernet NICs to work natively with systems based on, say, Windows, Linux, and OS X? What about compatibility with different network architectures, protocols, and client/server applications? (Whoa—I'm glad we don't really need to worry about that particular set of problems!)
Windows systems administrators rely on several Distributed Management Task Force (DMTF) industry standards to make our lives easier. The DMTF is an industry consortium whose membership includes major computer hardware and software manufacturers. Their goal is to agree on standards so their products work together as seamlessly as possible.
In this article, we'll look at how to apply a couple of key DMTF standards to help us be more effective with Windows PowerShell–based systems administration.
Understanding the Relationship Between CIM and WMI
The Common Information Model (CIM, pronounced sim) is a DMTF specification that describes computer hardware and software components. CIM is part of a larger systems-management framework called Web-Based Enterprise Management (WBEM).
Every Windows server or client computer has a local CIM repository. As systems administrators, we can tap into that CIM repository to fetch and set properties and take action on the repository data.
Although it's a long-time DMTF member, a while back Microsoft made the ill-advised decision to write its own abstraction layer on top of CIM, called Windows Management Instrumentation (WMI).
What's confusing to many admins is that in Windows PowerShell v3 and later we can access the CIM repository by using either WMI or CIM calls. One of my goals in this article is to show you the pros and cons of each approach.
Let's begin by running through a simple example to help us visualize the CIM repository. At the moment I'm running a Windows 8.1 computer on which I've installed the free and open-source WMI Explorer desktop application. Figure 1 shows an annotated user interface.
Figure 1 WMI Explorer.
We start using WMI Explorer by clicking Connect to load the current computer's CIM repository (annotation A in Figure 1). The namespace is the highest level in the CIM hierarchy. In my experience, we use ROOT\CIMV2 almost exclusively for Windows systems management. When we double-click ROOT\CIMV2, after a moment the Classes pane populates (annotation B). Whereas a namespace defines a group of related classes, the class itself is a blueprint (definition) for a particular hardware or software component.
Type service in the Quick Filter list and double-click Win32_Service to load all service instances on the local computer (annotation C). If we think of a class as a generic object blueprint, an instance is an individual copy of that blueprint.
Any Windows computer has many services running, so WMI Explorer displays a mighty big list of service instances. Type spooler in the Quick Filter list and double-click Win32_Service.Name="Spooler" to load the properties of that instance (annotation D).
At the bottom of the WMI Explorer window (annotation E) is the following query:
SELECT * FROM Win32_Service WHERE Name='Spooler'
Earlier I explained that WMI is Microsoft's implementation of CIM. Microsoft also created the WMI Query Language (WQL) to give admins a method that works like Structured Query Language (SQL) for accessing CIM object data. If you don't yet know SQL, I'd encourage you to learn it, because you can apply that syntax in WQL to query system configuration data.
Finally, spend some time clicking across the six tabs marked at annotation F:
- Instances: Defines the object and shows selected attributes (properties) that describe the object).
- Properties: Full list of properties, along with their descriptions. The window that shows the MSDN documentation is especially helpful here.
- Methods: Actions that an object can perform. For example, we can call StartService() and StopService(), respectively, to start and stop the given service.
- Query: Use WQL syntax to run ad hoc queries against the current object.
- Script: Generate a PowerShell script from the current query.
- Logging: Status messages reported from the CIM repository itself.
WMI in Action
I don't want to spend too much time on the WMI cmdlets because, frankly, Microsoft is deprecating them in favor of its own CIM cmdlets. However, you'll still need the legacy WMI commands if you're supporting computers running Windows PowerShell v1 or v2 or if legacy scripts are used in your environment.
Let's enumerate the WMI cmdlets:
Get-Command -Noun wmi* | Select-Object -Property Name Name ---- Get-WmiObject Invoke-WmiMethod Register-WmiEvent Remove-WmiObject Set-WmiInstance
For accessing the local computer's CIM repository, Get-WmiObject works pretty well. The command defaults to the ROOT\CIMv2 namespace, so all we need to do is to supply the appropriate class name:
Get-WmiObject -Class Win32_OperatingSystem SystemDirectory : C:\Windows\system32 Organization : BuildNumber : 9600 RegisteredUser : Windows User SerialNumber : 00261-80246-78149-AA747 Version : 6.3.9600
The WMI situation begins to show its age when we use the -ComputerName parameter to retrieve WMI information from remote computers:
Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='Spooler'" -ComputerName localhost,mem1,mem2 | Format-List -Property PSComputerName,Name,State,Status PSComputerName : DC1 Name : Spooler State : Running Status : OK PSComputerName : MEM1 Name : Spooler State : Running Status : OK PSComputerName : MEM2 Name : Spooler State : Running Status : OK
Sure, it works, but at what cost?
Notice the handy -Query parameter in the previous example. If you've worked with relational databases and the SQL data access language, you'll feel right at home with using WQL to query CIM repository data. Here's the deal: The Get-WmiObject cmdlet uses old-school PowerShell remoting, which involves the following issues:
- The underlying network transport involves DCOM and RPC, which are older, "heavier" protocols with corresponding reduced network performance.
- DCOM and RPC use dynamic port allocation, which means that you'll have difficulty on most networks unless the appropriate firewall rules are configured.
- Remote computers are queried serially rather than in parallel.
These three issues (especially the firewall port issue) can plague your work with annoying problems. For instance, frustrating "No RPC server available" error messages might crop up when you use Get-WmiObject, because the cmdlet is trying to use random TCP ports.
If the WMI cmdlets have any advantage, it's that you can take action on CIM repository data from remote computers, using methods available to you because DCOM and RPCs build up and tear down persistent connections to the remote CIM repositories for each connection request. Take a look:
$mem2spool = Get-WmiObject -Query "SELECT * FROM win32_service WHERE name='Spooler'" $mem2spool | Get-Member -MemberType Method | Select-Object -Property Name Name ---- Change ChangeStartMode Delete GetSecurityDescriptor InterrogateService PauseService ResumeService SetSecurityDescriptor StartService StopService UserControlService
That capability is actually pretty cool, because we can control that remote service by using dot notation:
$mem2spool.StopService() $mem2spool.StartService()
As we'll see next, the "new school" CIM commands don't have methods—or at least not initially.
CIM in Action
Whereas the old WMI cmdlets use stateful connections for remote access and trip over existing firewall rules, the new CIM cmdlets run in a much leaner, meaner fashion, thanks to their use of the DMTF Web Services-Management (WS-Man) protocols.
Current Windows OS versions rely on the Windows Remote Management (WinRM) service to make standards-based PowerShell remoting possible. PowerShell WS-Man remoting brings these possibilities:
- HTTP/HTTPS transport and XML serialized data streams make the remote access extremely firewall-friendly.
- WS-Man remoting is stateless, which means faster performance than with the older DCOM/RPC methods.
- Remote computers are queried in parallel, in what Microsoft calls a "fan out" remote management scenario.
Pretty exciting stuff!
Let's enumerate the CIM cmdlets:
Get-Command -Module CimCmdlets | Select-Object -Property Name Name ---- Export-BinaryMiLog Get-CimAssociatedInstance Get-CimClass Get-CimInstance Get-CimSession Import-BinaryMiLog Invoke-CimMethod New-CimInstance New-CimSession New-CimSessionOption Register-CimIndicationEvent Remove-CimInstance Remove-CimSession Set-CimInstance
The Get-CimInstance command is the direct analog to Get-WmiObject, so I suggest that you learn how to use this command post-haste. Let's get help:
Get-Help Get-CimInstance -ShowWindow
One great thing about the CIM cmdlets (unlike the WMI cmdlets) is that they support tab completion! This feature is tremendously useful when you aren't exactly sure which class name you need:
PS C:\> Get-CimInstance -ClassName Win32_BIOS SMBIOSBIOSVersion : 6.00 Manufacturer : Phoenix Technologies LTD Name : PhoenixBIOS 4.0 Release 6.0 SerialNumber : VMware-56 4d f5 d0 c3 4b d8 9e-3b bf 2e fa 04 4d 67 d7 Version : INTEL - 6040000
Following are two examples in which we look for processes whose names start with the letter n. Notice that we can use -Query with a WQL expression or -Filter with a more PowerShell-native filter expression. Both examples return the same results:
Get-CimInstance -Query "SELECT * FROM Win32_Process WHERE name LIKE 'n%'" ProcessId Name HandleCount WorkingSetSize VirtualSize --------- ---- ----------- -------------- ----------- 664 notepad.exe 76 7524352 2199118839808 1664 notepad.exe 76 7491584 2199122526208 Get-CimInstance -ClassName win32_process -Filter "name like 'n%'" ProcessId Name HandleCount WorkingSetSize VirtualSize --------- ---- ----------- -------------- ----------- 664 notepad.exe 76 7524352 2199118839808 1664 notepad.exe 76 7491584 2199122526208
But wait—what about remote access, and what I said about methods earlier in this article? Take a look:
$mem1spooler = Get-CimInstance -ComputerName mem1 -query "SELECT * FROM win32_service WHERE name='Spooler'" $mem1spooler | gm -MemberType Method TypeName: Microsoft.Management.Infrastructure.CimInstance#root/cimv2/Win32_Service Name MemberType Definition ---- ---------- ---------- Clone Method System.Object ICloneable.Clone() Dispose Method void Dispose(), void IDisposable.Dispose() Equals Method bool Equals(System.Object obj) GetCimSessionComputerName Method string GetCimSessionComputerName() GetCimSessionInstanceId Method guid GetCimSessionInstanceId() GetHashCode Method int GetHashCode() GetObjectData Method void GetObjectData(System.Runtime.Serialization.S... GetType Method type GetType() ToString Method string ToString()
Remember those useful methods that become available when we use Get-WmiObject to create a variable to hold a remote server's spooler service? Not so with Get-CimInstance. The reason is simple: WS-Man remoting is stateless, so there's no persistent link to the remote computer's CIM repository.
Also, WS-Man remoting uses SOAP and XML to serialize the data stream from the remote host to your local server, so you're not dealing with live objects as you are with Get-WmiObject. Long story short—no methods. However, we can absolutely leverage the PowerShell pipeline and the Invoke-CimMethod cmdlet to call a method. Suppose I stop the Spooler service on my mem1 server, like this:
Get-CimInstance -ComputerName mem1 -query "SELECT * FROM win32_service WHERE name='Spooler'" | Invoke-CimMethod -MethodName StopService
In case you wondered how I knew to call StopService as my method, let me draw your attention to the Get-CimClass cmdlet:
Get-CimClass -ClassName Win32_Service | Select-Object -ExpandProperty CimClassMethods Name ReturnType Parameters ---- ---------- ---------- StartService UInt32 {} StopService UInt32 {} PauseService UInt32 {} ResumeService UInt32 {} InterrogateService UInt32 {} UserControlService UInt32 {ControlCode} Create UInt32 {DesktopInteract, DisplayName, ErrorControl, LoadOrde... Change UInt32 {DesktopInteract, DisplayName, ErrorControl, LoadOrde... ChangeStartMode UInt32 {StartMode} Delete UInt32 {} GetSecurityDescriptor UInt32 {Descriptor} SetSecurityDescriptor UInt32 {Descriptor}
Next Steps
If you understood what we covered in this article, you've come a great distance in mastering PowerShell-based management with CIM. Your next step is to learn how to use CIM sessions to make even more efficient use of network bandwidth when managing remote computers. To that point, I'll leave you with a few references to check out:
Happy PowerShelling!