WMI Commands

PowerShell supports WMI with a number of cmdlets. We'll take a look at how they work, and how you get started with WMI.

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.