WMI (Windows Management Instrumentation) is part of Windows since Windows 2000. It is a service that can query almost every detail about a computer system. PowerShell ships with full WMI support.
These are the most important CIM (Common Information Model) cmdlets:
Cmdlet | Purpose |
---|---|
Get-CimInstance | Gets instances of WMI classes. This is the most important cmdlet. |
Get-CimClass | Gets a WMI class. Use it to access static WMI methods. |
Invoke-CimMethod | Runs a WMI method, and returns results. |
Set-CimInstance | Writes back changed properties. |
New-CimSession | Creates a new remoting session to one or more remote systems |
New-CimSessionOption | Defines special options for remoting connections. |
Remove-CimSession | Closes and removes a remoting session that is no longer used. |
New-CimInstance | Creates a new instances of a WMI class, i.e. for temporary use. |
All of these ship in the module CimCmdlets that was introduced in PowerShell 3. To get a full list of CIM cmdlets, run the command below:
Get-Command -Module CimCmdlets
Getting Information
To get useful information from WMI, you need to know where the information resides. WMI uses namespaces and classes to organize its content:
- Namespace: a namespace works like a folder. WMI uses a namespace tree to organize its classes. The top of this tree is always called root. The namespace root/cimv2 is the default namespace. If you want to use a different namespace, you need to explicitly use the parameter -Namespace.
- Class: a class consists of properties (information) and methods (commands). Most properties are read-only.
The default namespace root/cimv2 contains most of the WMI classes used in scripting. So unless you need to access more exotic WMI classes in other namespaces, you don’t need to care much about namespaces.
If you do care, and would like to investigate the available namespaces (and classes), please refer to this sample code, or peek a bit down this article.
Querying Information
To get information from WMI, you ask for instances of a given class. The PowerShell code for this is always the same, so the hardest part in WMI is to figure out the name of the class that describes what you are after.
You can either use the reference and browse through the classes available in a given namespace. Or you can simply guess: most WMI classes start with “Win32_”, followed by an English word (singular) describing what you are after.
So to get information about your computer BIOS, the appropriate class name would be Win32_BIOS.
There are two cmdlets you can use to query information: Get-WmiObject
and Get-CimInstance
:
# original cmdlet, discontinued in PowerShell 6:
Get-WmiObject -Class Win32_BIOS
# new cmdlet, introduced in PowerShell 3:
Get-CimInstance -ClassName Win32_BIOS
Get-WmiObject
was introduced in PowerShell 1 and is available in all version of Windows PowerShell.
The cmdlet was replaced by the more sophisticated Get-CimInstance
in PowerShell 3. Today, Get-WmiObject
is considered obsolete and should be replaced by Get-CimInstance
. In *PowerShell 6 and better, Get-WmiObject
was removed.
Filtering Information
WMI can filter results and return only instances where a given condition exists. For example, the WMI class Win32_LogicalDisk represents all logical drives:
Get-CimInstance -ClassName Win32_LogicalDisk
These can be all kinds of drives, and the result may look like this:
DeviceID DriveType ProviderName VolumeName Size FreeSpace
-------- --------- ------------ ---------- ---- ---------
C: 3 OS 1007210721280 517862002688
D: 2 64421232640 64418349056
Z: 4 \\127.0.0.1\c$ OS 1007210721280 517862002688
The property DriveType apparently identifies the type of drive, and from looking at the data, the number 3 seems to identify fixed disks.
If you want the exact meaning for WMI codes, you can always look up the class in our reference and get a precise listing of friendly names for code numbers.
In order to get back only fixed disks, you can filter the results. Either use -Filter and specify the condition that must be met, or replace -ClassName by -Query and submit a SQL-like query:
Get-CimInstance -ClassName Win32_LogicalDisk -Filter 'DriveType=3'
Get-CimInstance -Query 'Select * From Win32_LogicalDisk Where DriveType=3'
The result is the same in both cases.
Try and avoid using Where-Object
to filter WMI results. Where-Object
does client-side filtering and is inefficient: all data needs to be retrieved, and Where-Object
is discarding unwanted data in a second step. WMI filtering in contrast produces only the information required which is much more efficient, especially across a network.
Listing Namespaces
Remember? WMI namespaces work like folders, and each namespace can contain a number of WMI classes. The default namespace root/cimv2 is the most important, but there are many more. In fact, every vendor and software producer can add their own.
There is no cmdlet to list all WMI namespaces. To examine the available namespaces, use Get-CimInstance
from above: each child namespace is represented by an instance of the WMI class __Namespace inside the parent namespace.
To find all available namespaces, start with the top namespace root, and use a queue or recursive calls to examine all child namespaces:
# create a new queue
$namespaces = [System.Collections.Queue]::new()
# add an initial namespace to the queue
# any namespace in the queue will later be processed
$namespaces.Enqueue('root')
# process all elements on the queue until all are taken
While ($namespaces.Count -gt 0 -and ($current = $namespaces.Dequeue()))
{
# find child namespaces
Get-CimInstance -Namespace $current -ClassName __Namespace -ErrorAction Ignore |
# ignore localization namespaces
Where-Object Name -NotMatch '^ms_\d{2}' |
ForEach-Object {
# construct the full namespace name
$childnamespace = '{0}\{1}' -f $current, $_.Name
# add namespace to queue
$namespaces.Enqueue($childnamespace)
}
# output current namespace
$current
}
For more information about namespaces and a list of typical namespaces, please refer to this article.
Listing WMI Classes
Remember? WMI classes are where the beef is. Each class represents something specific, i.e. the class Win32_Processor represents CPUs, whereas the class Win32_NetworkAdapter represents network cards. To work with WMI, you need to know the exact class name that is representing what you’d like examine.
Get-CimClass
returns all classes in a given namespace. Since root/cimv2 is the default namespace, this will create a sorted list of all class names living in namespace root/cimv2:
Get-CimClass | Select-Object -ExpandProperty CimClassName | Sort-Object
For more information about classes, and how to identify useful classes for scripting, please refer to this article.
Changing Information
WMI is primarily a way to describe the computer and its settings and to retrieve information. The vast majority of properties are read-only. WMI is not widely used to configure and change settings.
In some scenarios, WMI can actually change things and settings. Let’s take a look at the class Win32_Environment. This class represents Windows Environment variables, so to list all of them, ask WMI to return all instances of this class:
Get-CimInstance -ClassName Win32_Environment
Adding New Instances
When you create a new Windows Environment variable, regardless of how you do this, as a result of this, WMI adds a new instance of Win32_Environment.
As you may know, Windows Environment variables can be defined per User and per Machine. Both of these are supported by WMI. When you launch a program, it receives a copy of them (called the process set). These copied (and temporary) environment variables are not represented by Win32_Environment.
So when you create a new environment variable in PowerShell using the drive env:, this will not change the instances of Win32_Environment:
$before = Get-CimInstance -ClassName Win32_Environment # create a temporary environment variable $env:myNewEnv = 1 $after = Get-CimInstance -ClassName Win32_Environment $new = $after.Count - $before.Count "Detected $new new environment variables"
However, when you create a permanent new environment variable, i.e. via an API call, this does add a new instance to Win32_Environment:
$before = Get-CimInstance -ClassName Win32_Environment # create a permanent environment variable [Environment]::SetEnvironmentVariable('myNewVar', 124, 'User') $after = Get-CimInstance -ClassName Win32_Environment $new = $after.Count - $before.Count "Detected $new new environment variables"
You can reverse this: by using New-CimInstance
to manually add a new instance of Win32_Environment, you can actually create a new Windows Environment variable. The code below creates a new environment variable named SomeInfo in the context of the current user.
Warning: you cannot create new instances from just about any WMI class this way. In fact, most WMI classes do not support creating new instances manually. Instead, they use methods to add new instances, or don’t support adding new instances at all.
# create username for current user
$username = '{0}\{1}' -f $env:USERDOMAIN, $env:USERNAME
# define initial values to create a new instance of
# Win32_Environment (new environment variable)
$initialValues = @{
Name = 'SomeInfo'
VariableValue = '123'
UserName = $username
}
# create new instance
$newEnv = New-CimInstance -ClassName Win32_Environment -Property $initialValues
$newEnv
This is a permanent change, and from now on, you can access this variable via WMI like so:
$variable = Get-CimInstance -ClassName Win32_Environment -Filter 'Name="SomeInfo"'
$variable.VariableValue
Changing Properties
Information returned by WMI objects are read-only. So when you followed the example above and created and retrieved the environment variable SomeInfo, you can read its value. When you change its value, though, the environment variable will not change.
Even though the WMI object happily accepts any change to its properties, and even stores them, they are not permanent:
# get the environment variable "SomeInfo"
# NOTE: this variable must exist! Use previous example to create it.
$variable = Get-CimInstance -ClassName Win32_Environment -Filter 'Name="SomeInfo"'
$variable.VariableValue
# properties can be changed:
$variable.VariableValue = 'NEW!'
$variable.VariableValue
# however these changes are not permanent and do not affect the
# underlying environment variable
Once you retrieve the original object again, the old value is restored:
$variable = Get-CimInstance -ClassName Win32_Environment -Filter 'Name="SomeInfo"'
# old value is restored:
$variable.VariableValue
To permanently change the property, use Set-CimInstance
:
# get the environment variable "SomeInfo"
# NOTE: this variable must exist! Use previous example to create it.
$variable = Get-CimInstance -ClassName Win32_Environment -Filter 'Name="SomeInfo"'
$variable.VariableValue
# assign a new property value:
$variable.VariableValue = "NEW"
# Update the change:
$variable | Set-CimInstance
# retrieve the object again
$variable = Get-CimInstance -ClassName Win32_Environment -Filter 'Name="SomeInfo"'
# change is permanent:
$variable.VariableValue
Warning: most properties won’t allow you to change their values. Instead, they use methods to change and manage settings (if supported at all).
Calling WMI Methods
Some WMI classes can contain methods. Methods can either be implemented in specific instances that you received from Get-CimInstance
, or directly in WMI classes that you received from Get-CimClass
.
Use the code below to figure out more about implemented methods.
# figure out the methods supported by Win32_Process:
$className = 'Win32_Process'
# get the underlying WMI class:
$class = Get-CimClass -ClassName $className
# define a calculated property named "Implemented":
$implemented = @{
Name = 'Implemented'
Expression = {
# read the method qualifiers:
$qualifiers = $_.Qualifiers.Name
# check to see who implements this method:
if ($qualifiers -notcontains 'Implemented')
{
# this method is not implemented by anyone
# it cannot be used:
'Not implemented'
}
elseif ($qualifiers -contains 'Static')
{
# this is a static methods implemented by
# the WMI class itself:
'Class'
}
else
{
# this is a dynamic method implemented by
# every instance of this class:
'Instance'
}
}
}
# find out who implements the methods
# get all methods from this class:
$class.CimClassMethods |
# output method details and implementation:
Select-Object -Property Name, $implemented, ReturnType, Parameters
The result looks similar to this:
Name Implemented ReturnType Parameters
---- ----------- ---------- ----------
Create Class UInt32 {CommandLine, CurrentDirector...
Terminate Instance UInt32 {Reason}
GetOwner Instance UInt32 {Domain, User}
GetOwnerSid Instance UInt32 {Sid}
SetPriority Instance UInt32 {Priority}
AttachDebugger Instance UInt32 {}
GetAvailableVirtualSize Instance UInt32 {AvailableVirtualSize}
Calling Instance Method
If a method is implemented on class instances, use Get-CimInstance
to get the WMI instance(s) you need. The example below retrieves the process that is currently running your PowerShell code:
# get the process where property "ProcessID" equals the process id
# of the current PowerShell process (always available in $pid):
$me = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$Pid"
$me
Once you have the instances, use Invoke-CimMethod
to call the method. For example, to find out the process owner, call the method GetOwner():
$me = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$Pid"
Invoke-CimMethod -InputObject $me -MethodName GetOwner
You can combine both calls, too:
# get owner of current powershell process
Invoke-CimMethod -Query "Select * From Win32_Process Where ProcessId=$Pid" -MethodName GetOwner
GetOwner() is a method that does not take any arguments. See the next section for an example on how to call methods with arguments.
Calling Class Methods
Methods implemented by the class are not related to specific instances. For Win32_Process and processes, these methods are not related to existing processes. The method Create() creates a new process.
Before you can call Create(), you need to know more about the arguments it requires. Unlike GetOwner() in the previous example, Create() does require specific information from you so it knows which process to launch.
# figure out the arguments required by the method "Create"
# exposed by the WMI class "Win32_Process":
$className = 'Win32_Process'
$methodName = 'Create'
# list all parameters
$class.CimClassMethods.Item($methodName).Parameters |
# take only input arguments:
Where-Object { $_.Qualifiers.Name -contains 'In' } |
# return name and data type:
Select-Object -Property Name, CimType
The result looks like this:
Name CimType
---- -------
CommandLine String
CurrentDirectory String
ProcessStartupInformation Instance
As you can see, you can submit up to three arguments to the method. Arguments are submitted to the method using a hashtable (so order does not matter). This example launches a new instance of a notepad process:
# add your arguments to a hashtable and use the
# parameter names as keys:
$arguments = @{
CommandLine = 'notepad.exe'
}
# call the method on the WMI class, and submit the arguments:
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments $arguments
Using Data Type “Instance”
When you look at the arguments supported by Create(), the argument ProcessStartupInformation is marked as using the data type Instance: this argument can be an instance of an object of type ProcessStartupInformation.
When you look up this method in our reference, you learn that this object is an instance of the WMI class Win32_ProcessStartup.
The code below creates a new PowerShell process, changes the console title, background and foreground colors, and positions the console window on the desktop:
# using a new instance of Win32_ProcessStartup to create a
# new powershell process and manipulate the console window:
# create a new instance of Win32_ProcessStartup:
# (use -ClientOnly to create a temporary in-memory instance)
$class = Get-CimClass -ClassName Win32_ProcessStartup
$instance = New-CimInstance -CimClass $class -ClientOnly
# fill properties:
$instance.Title = 'My Console Window'
$instance.X = 100
$instance.Y = 200
$instance.FillAttribute = 20
# add your arguments to a hashtable and use the
# parameter names as keys:
$arguments = @{
CommandLine = 'powershell.exe'
ProcessStartupInformation = $instance
}
# call the method on the WMI class, and submit the arguments:
Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments $arguments
Accessing Remote Computers
WMI comes with powerful remoting techniques built-in.
If you are working in a modern Windows environment, and your current user account has sufficient privileges on the target system, you may be able to successfully connect to and run WMI code on a remote machine simply by adding -ComputerName:
# get information from local machine
Get-CimInstance -ClassName Win32_BIOS
# get information from remote machine
Get-CimInstance -ClassName Win32_BIOS -ComputerName server123
Get-CimInstance -ClassName Win32_BIOS -ComputerName 10.12.131.156
If calls fail, these are typical issues:
- Access Denied: your current user account is not authorized to access the remote system. See below on how to use a different user account to log on
- RPC Unavailable: a firewall blocks the call, or the target machine is turned off
- Other: the target machine may not be set up for remoting calls (see below)
Using Different User Accounts
Get-CimInstance
and all the other WMI CIM cmdlets provide no -Credential parameter to use a different user account. Instead, they all work with reusable CIM Sessions.
Whenever the default connection does not work for you, create your own CIM Session, and use this session instead. Here is code that connects to a remote machine using a different user account:
# one or more computer names or IP addresses:
$computers = 'server1', 'server2'
# authenticate with a different identity:
$cred = Get-Credential -Message 'Authenticate to retrieve WMI information:'
# create a CIM session
$session = New-CimSession -ComputerName $computers -Credential $cred
# use the CIM cmdlets with the session you previously created:
$result = Get-CimInstance Win32_Bus -CimSession $session
# when done, remove the CIM session (it can be reused with multiple
# calls until you remove it)
Remove-CimSession -CimSession $session
$result
The user account you specify needs to have Administrator privileges on the target side. Typically, on servers, built-in remoting is enabled by default. On clients, it is disabled by default and can be manually enabled using this command:
Enable-PSRemoting -SkipNetworkProfileCheck -Force
Using WinRM or DCOM Protocol
One of the fundamental differences between the old Get-WmiObject
and the new Get-CimInstance
is the way how machines connect. Get-WmiObject
uses the old-fashioned DCOM protocol (and cannot use anything else). Some old servers may only be able to use DCOM.
Get-CimInstance
uses the much more modern and safer WinRM protocol by default. So if the target machine is relatively old, or if the use of WinRM wasn’t approved in your enterprise and your servers are still connecting over DCOM, then you may experience a situation where Get-WmiObject
works on remote machines whereas Get-CimInstance
fails.
CIM Session uses WinRM by default but can be configured to fall back to DCOM instead. So for troubleshooting, or to access very old servers, use code like this:
$computername = '127.0.0.1'
# use old DCOM protocol:
$options = New-CimSessionOption -Protocol Dcom
# create CIM session
$session = New-CimSession -SessionOption $option -ComputerName $computername
# use session
Get-CimInstance -ClassName Win32_BIOS -CimSession $session
# remove session
Remove-CimSession -CimSession $session
Next Steps
Now that you know how to use the built-in WMI cmdlets and fundamental PowerShell principles, take a look at the hundreds of classes in the default namespace, and adapt the code samples found here to these classes.