Do you know WMI (Windows Management Instrumentation)? It’s a service built into every Windows since Windows 2000, and you can even update Win 9x and NT 4.0 to use it. WMI is designed to locally and remotely manage systems, and many scripts take advantage of its rich capabilities. Time to look at WMI from a PowerShell perspective.
Getting Instances
WMI represents the ingredients of your computer in terms of classes. There are classes for anything. Win32_BIOS for example represents your BIOS settings, while Win32_Service represents a service. Once you know the name of a class, you can ask WMI to get you all instances of it:
Get-WMIObject Win32_BIOS
Get-WMIObject Win32_Service
Note that the first command returns a single instance because there is only one BIOS in your computer. The second line returns a wealth of information and lists all the services on your computer as individual instances.
Note also that each command returns exactly four properties per object. That’s to prevent WMI to flood your screen because WMI instances contain a wealth of information. To see all properties, you’d type:
Get-WMIObject Win32_BIOS | fl -property *
Get-WMIObject Win32_Service | fl -property *
Now you see a lot more information per instance.
How do you know which other WMI classes are available? Easy:
You get an extensive list of all of the WMI classes that live in the current namespace. Namespace? Huh?
WMI is a hierarchically organized class storage. Most of the important classes reside in the namespace root\cimv2 but there are others, too. Windows comes with a handful, and software and hardware vendors can add their own places, as well, to provide a location for their classes. We’ll talk about namespaces in more detail, later. If you don’t care about namespaces, you automatically end up in root\cimv2 which is good enough for the moment.
Getting Documentation
How do you know what a particular class instance can do for you and what kind of information it contains? Simply look it up! WMI is pretty well documented:
http://msdn2.microsoft.com/en-us/library/aa394554.aspx
Unfortunately, MSDN has chosen to rename their documentation web pages so you no longer can "guess" the URL for a specific class. Still, MSDN has a redirector running, so the following script by Alex Angelopoulos currently still works:
function Get-WmiHelp
{
Param([string]$class)
$wmibase = "http://msdn.microsoft.com/library/en-us/wmisdk/wmi/"
if ($global:MSDNViewer -eq $null)
{
$global:MSDNViewer = new-object -ComObject InternetExplorer.Application
}
$global:MSDNViewer.Navigate2($wmibase + $class + ".asp")
$global:MSDNViewer.Visible = $TRUE
}
With this function, you can open the appropriate documentation web page at MSDN like this:
get-wmihelp Win32_Process
We'll create our own Get-WMIHelp function a bit later which makes visiting MSDN pages unnecessary, though. You can download it here: http://powershelllive.com/blogs/lunch/attachment/55.ashx.
But wait. What can I do with WMI, anyway?
Accessing a Specific Instance
Actually, the instances WMI gets for you are live objects. They contain properties and methods, just like any other object, too. So you could read property information or call specific methods. To be able to do just that, however, you need access to specific instances.
There are three ways for you to access a specific instance of a class (rather than a number of instances as get-wmiobject usually does).
The first one is writing a query that is specific enough to return only the one needed instance.
The second is to enumerate a collection of instances using foreach-object. Inside the foreach-object loop, you get access to all of the instances through $_, one at a time, and can call their methods.
The third uses a type accelerator: [wmi] and the internal unique path to the instance you want to mess with.
1. Specific Queries
Let's look at all three and start with a specific query:
get-wmiobject -query "select * from Win32_Service where Name='Alerter'"
Because there is only one Service with the name “Alerter”, the query returns a single instance and not an array. So you can save this instance in a variable and find out which properties and methods it supports:
$service = get-wmiobject -query "select * from Win32_Service where Name='Alerter'"
$service | get-member
You’ll immediately see that services have a number of properties and methods, and if you’d like to stop this service, you could invoke the StopService method:
Whether or not your action succeeded is documented in the ReturnValue property that gets dumped along with some additional properties. You can save the result and then do a comparison to check for the return value:
$result = $service.StopService()
PS C:\Software> if ($result.ReturnValue -eq 0) {
>> "OK!"
>> }
>> else
>> {
>> "Error: " + $result.ReturnValue
>> }
>>
Error: 5
What does “5” mean, then? Look up the class in MSDN (use the link above) and you’ll know:
|
Return code |
Description |
|
0 |
Success. |
|
1 |
Not supported. |
|
2 |
Access denied. |
|
3 |
Dependent services running. |
|
4 |
Invalid service control. |
|
5 |
Service cannot accept control. |
|
6 |
Service not active. |
|
7 |
Service request timeout. |
|
8 |
Unknown failure. |
|
9 |
Path not found. |
|
10 |
Service already stopped. |
|
11 |
Service database locked. |
|
12 |
Service dependency deleted. |
|
13 |
Service dependency failure. |
|
14 |
Service disabled. |
|
15 |
Service logon failed. |
|
16 |
Service marked for deletion. |
|
17 |
Service no thread. |
|
18 |
Status circular dependency. |
|
19 |
Status duplicate name. |
|
20 |
Status - invalid name. |
|
21 |
Status - invalid parameter. |
|
22 |
Status - invalid service account. |
|
23 |
Status - service exists. |
|
24 |
Service already paused. |
Actually, “5” means the service can’t stop because it is stopped already. So let’s try this:
Voilá! Or not. If the alerter service is disabled on your system, you get a ReturnValue of “14”. You may want to enable it first – or vice versa:
$service.ChangeStartMode("Disabled")
$service.ChangeStartMode("Manual")
$service.ChangeStartMode("Auto")
You can also read the object properties:
$service.StartMode
$service.State
Most properties in WMI are read-only. The confusing thing though is that PowerShell won’t raise an error if you try and change a read-only property:
$service.StartMode = "Auto"
In fact, the property appears to be changed. Double-checking in services.msc reveals though that the StartMode did not change. Ok, you’ll say because you are a smart guy, you need to “save” the property first to make it permanent. True. Let’s try:
$service.StartMode = "Auto"
$service.Put()
Again, no errors. And no luck. So with WMI, you need to know whether or not a property is writeable. In most cases, it is not and you need to find the appropriate method that does the change for you, like ChangeStartMode in the example above.
To prove that changing a property can work if the property is writable, let’s consider this example:
$drive = get-wmiobject -query "select * from win32_logicaldisk where name='C:'"
$drive.VolumeName = "My Harddrive"
$drive.Put()
Double-check in explorer: the label for drive C: has changed!
So up to now, you can use get-wmiobject with its -query parameter to query for a specific instance of a class. You just need to make sure your query really is specific. Otherwise, you end up with more than one instance.
2. Many Instances
When you get back more than one instance, you can still access individual instances and play with them, but now you have to use foreach-object or its short form: % { ... }. Let's get more than one instance and see how you still get access to individual instances:
The next example shows you how to use wildcards in your query. This time, you get two services which both start with “Al”:
get-wmiobject -query "select * from win32_service where name like 'al%'"
Whenever you get back more than one instance, you cannot call any methods or read properties because what you get is an array. If you really want to process all the instances in the same way, you can do it like this, though:
$bag = get-wmiobject -query "select * from win32_service where name like 'al%'"
$bag
$bag | % { $_.name }
$bag | % { $_.ChangeStartMode("Manual")}
Remember that % { … } is just a shortcut for foreach-object { … }. So the curly brackets work like a loop and go through all the instances in your $bag. Each instance processed is available through “$_”, so this is how to call methods or ask for properties for all instances if you got more than one.
3. The [WMI] Type Accelerator
Finally, there is yet another way of accessing individual instances. Each instance has a unique “path”, and if you know that path, you are set. Let’s find out that path for the two service instances in our $bag:
PS C:\ > $bag | % { $_.__PATH}
\\NOTE1\root\cimv2:Win32_Service.Name="Alerter"
\\NOTE1\root\cimv2:Win32_Service.Name="ALG"
PS C:\ >
You get the idea: There are unique key properties. While all other properties can contain any value, key properties must be unique to an instance. They work sort of as their “filename”. In the case of services, the key property is the “Name” property.
So whenever you know the name of a service, you can access this service directly using its path. Let’s take the “Spooler” service, for example:
$spooler = [wmi]'\\.\root\cimv2:Win32_Service.Name="Spooler"'
$spooler.state
Note how I replaced my PC name with a “.”. The “.” always denotes your local computer, and that’s good so your script really runs on any computer and is not hard-coded to one specific machine.
Since most of the path and even the name of the key property are default stuff, you can simplify if you want the defaults anyway:
$spooler = [wmi]'Win32_Service="Spooler"'
$spooler.state
On the other hand, you could also specify a remote computer name. If you had access to that machine via network, no firewalls in your way and local admin privileges on the target machine, you could easily get the desired info from the remote machine. Without moving your body too much.
But what’s [wmi]? It is a type accelerator as the PowerShell likes to call that. And it’s very similar to the [ADSI] thing you discovered in Day 4. It’s a quick way of getting a (specific) WMI instance.
With [wmi], you don’t need to actually know the key property name (as you have just seen), just its value is needed. So you could also use this line:
$spooler = [wmi]'\\.\root\cimv2:Win32_Service="Spooler"'
Let’s see how that translates. Say you want to access drive C: on your local computer to see what WMI can do with it. All you need is the class name that represents drives. It’s called Win32_LogicalDisk:
PS C:\> $disk = [wmi]'\\.\root\cimv2:Win32_LogicalDisk="C:"'
PS C:\> $disk
DeviceID : C:
DriveType : 3
ProviderName :
FreeSpace : 1901047808
Size : 79587446784
VolumeName :
PS C:\> $disk | fl -property *
Status :
Availability :
(...)
VolumeDirty : False
VolumeName :
VolumeSerialNumber : 5C180925
PS C:\>
So by cutting all default stuff from your WMI path, the type accelerator really is a useful and easy-to-use shortcut to get to individual instances:
$disk = [wmi]'Win32_LogicalDisk="C:"'
$disk
Next time you want to kill a process and know the process id, it’s as simple as this:
$proc = [wmi]'Win32_Process=5872'
$proc
$proc.Terminate()
Or in one line:
([wmi]'Win32_Process=5060').Terminate()
Now for the real geeks: Whenever you get an instance either from get-wmiobject or from the [wmi] type accelerator, it is not the raw wmi object anymore. PowerShell has squeezed it through its internal type adapter to make some adjustments. For example, the PowerShell team has added a couple of properties to convert dates and times from WMI format to human readable format and back. And it has filtered out some stuff.
If you want to see the raw WMI object, you can bypass the type adapter altogether. Here’s an example:
$disk_cooked = [wmi]'Win32_LogicalDisk="C:"'
$disk_raw = ([wmi]'Win32_LogicalDisk="C:"').PSBase
You may notice that getting disk_cooked takes a bit longer than getting disk_raw. When you look at both, they look quite different:
PS C:\> $disk_cooked
DeviceID : C:
DriveType : 3
ProviderName :
FreeSpace : 1901035520
Size : 79587446784
VolumeName :
PS C:\> $disk_raw
Scope : System.Management.ManagementScope
Path : \\NOTE1\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
Options : System.Management.ObjectGetOptions
ClassPath : \\NOTE1\root\cimv2:Win32_LogicalDisk
Properties : {Access, Availability, BlockSize, Caption...}
SystemProperties : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
Qualifiers : {dynamic, Locale, provider, UUID}
Site :
Container :
PS C:\>
Try and run both objects through get-member, and you’ll see what the type adapter really did. It created the properties and methods specific to that WMI class, and because extracting those from WMI takes some milliseconds, that’s why the creation time was longer than for the base object.
Why would you ever want the raw object, then? For speed? Maybe. Most importantly, the base object gives access to interesting internal informations such as the qualifiers. For example, to programmatically find out which methods and properties a given WMI class supports (without cheating and looking them up at MSDN), you connect to the class itself using yet another type accelerator:
$class = [wmiclass]'Win32_LogicalDisk'
$class.psbase.Options.UseAmendedQualifiers = $true
$class.psbase.methods | % { $_.name ; $_.Qualifiers }
$class.psbase.properties | % { $_.name ; $_.Qualifiers }
$class.psbase.qualifiers
I told you that’s geeks stuff, and you may not care too much about that. Among the qualifiers you’ll find extensive documentation including the listing of possible return values and their meaning, so from here you could create your very own WMI documentation tool. And that's exactly what we are about to do. Just a second.
"Static" and "Dynamic" Methods
WMI Classes you get from [wmiclass] are not only useful to get to internal documentation. You can also use classes to call static methods. What’s that again?
When you get an instance of a class, let’s say an instance representing a specific process or service, the methods available from that instance are called “dynamic” because they apply to the very instance you are looking at. So with these methods, you control the individual entity represented by the instance.
Now, what if you want to create a new process? Who do you turn to? When you ask for an instance of an existing process, you can Terminate() that, but you cannot create a new process.
That’s where static methods enter the stage. They are not bound to an actual instance of something. They are provided by the class itself.
So to launch a process, you get the class responsible for processes and create your process from there:
$class = [wmiclass]'Win32_Process'
$class.create("Notepad.exe")
Most of the time, you use static methods when you want to create new stuff because then you don’t have an instance to start with. Sometimes, you also find static methods for global settings. To find out all the static methods in Win32_NetworkAdapterConfiguration, you could check for the “Static” qualifier on the methods in that class:
$class = [wmiclass]'Win32_NetworkAdapterConfiguration'
$class.psbase.methods | %{ $meth = $_.name; $_.Qualifiers | % { if ($_.name -eq "Static") {if ($_.Value -eq $true) {$meth} } } }
Or, another way:
$class.psbase.methods | ?{ trap {continue} ($_.psbase.qualifiers.item("Static")).value} | % { $_.Name }
As you see, all of these methods affect global things, and that’s why they are static and not bound to a specific instance of a network adapter’s configuration. To release all DHCP leases from all adapters, you would use this:
([wmiclass]'Win32_NetworkAdapterConfiguration').RenewDHCPLeaseAll()
Finally, there is a third type accelerator. Compare these two lines:
get-wmiobject -query "select * from win32_process where name='notepad.exe'"
([wmisearcher] "select * from win32_process where name='notepad.exe'").get()
They are identical in their results and both list all instances of all running notepad.exes.
WMI Events
One last thought goes to WMI events. Up to now, you have initiated the action, and WMI has done as told. Let’s turn that around and have your script wait until WMI says it’s time to rock.
Look at this:
$alarm = new-object Management.EventQuery
$alarm.QueryString = "Select * from __InstanceCreationEvent WITHIN 1 WHERE targetinstance isa 'Win32_Process' AND targetinstance.name = 'notepad.exe'"
$watch = new-object Management.ManagementEventWatcher $alarm
$result = $watch.WaitForNextEvent()
$result.targetinstance.terminate()
$result.targetinstance
$path = $result.targetinstance.__path
$live = [wmi]$path
$live.terminate()
When you run this script, your script stops. It continues only when you launch a new notepad.exe which it kills. A sniper script. How did that happen?
In the first part, the script creates an event query and a watcher and asks the watcher to stop the script until the event occurs that you are after. Events are wrapped in a query like this:
· __InstanceCreationEvent: Fire when a new instance of something is created
· __InstanceModificationEvent: Fire when an existing instances changes, for example a service goes from Running to Stopped
· __InstanceDeletionEvent: Fire when an existing instance goes away, for example a process is termianated
· __InstanceOperationEvent: Fire when any of the above happens
· WITHIN: Number of seconds to use as monitoring interval.
· WHERE: Your filter to be notified only when specific instances trigger the event. You refer to the triggering instance as “targetinstance”. In the case of an __InstanceModificationEvent, you can also refer to “previousinstance” to compare before and after
Once you start notepad, the script continues. The instance that triggered the event is saved in $result.targetinstance, so you would assume you can access its methods, for example to immediately kill notepad. However, that raises an error.
While you can read all properties from the triggering instance, it is just a snapshot in time, and you cannot invoke its methods. To do that, you need to read the WMI path out of the result and get you a fresh new object instance using the [wmi] type accelerator. With this, you can then successfully terminate the notepad.
Getting Help with Get-WMIHelp
So WMI is really quite accessible from PowerShell if you know which classes there are, which methods and properties exist and how to call them with the right arguments. You could look up that information at MSDN, but you can also use our new Get-WMIHelp function. It is very easy to use:
Listing of all classes in default namespace:
Get-wmihelp -namespace root\default
Exploring a non-default namespace; switch applies to all other areas below as well
Listing of all classes starting with “Win32”
Listing of all classes that contain “service” in their name
Get-wmihelp win32_service
Listing of all properties and methods of this class
Get-wmihelp win32_service ChangeStartMode
Listing detailed description for method ChangeStartMode including PowerShell sample source code
In fact, you don't need to care much about the theoretic stuff we discussed so far because our Get-WMIHelp not only returns all important information including arguments, descriptions and the meaning of return values. It also auto-generates PowerShell code for you.
Here's the function which bypasses extensively the PowerShell type adaptor and works with the underlying "real" WMI objects to expose their rich information.
I have attached it also as downloadable file (http://powershelllive.com/blogs/lunch/attachment/55.ashx). Please let us know if it works for you.
function get-wmihelp {
# (C) 2007 Dr. Tobias Weltner
param([string]$classname="", [string]$membername="", [string]$namespace = "root\cimv2")
if ($classname -match "(\*)")
{
$list = get-wmiobject -list -namespace $namespace| ? {$_.__CLASS -ilike $classname }
if ($list -eq $null) {
"No matches found."
} else {
$list
}
}
elseif ($classname -eq "")
{