Windows Management Instrumentation (WMI) is Microsoft's implementation of Web‐Based Enterprise Management (WBEM) and the Common Information Model (CIM). WBEM is a standards‐based management tool that unifies the management of distributed computing environments. The CIM is a standard for defining how device and application characteristics are represented. The Distributed Management Task Force (DMTF) created both standards with considerable industry input.
WBEM was originally designed to use web‐based protocols, such as HTTP, to communicate. The Microsoft implementation was WMI, based instead on using the Component Object Model (COM) and Distributed COM (DCOM). Both COM and DCOM were popular and relatively fast at the time. Later versions of PowerShell and WMI incorporated Windows Remote Management (WinRM) remoting for WMI. This enabled you to access WMI on remote systems.
Internally, WMI is based on COM and not .NET. The .NET Framework provides applications with a way to access and leverage COM components. When the WMI subsystem returns data, .NET encapsulates it in .NET objects. When the literature talks about WMI objects with respect to PowerShell, technically they are talking about WMI data returned in a .NET object.
The great thing about this is that you use WMI objects (aka WMI data wrapped in a .NET object) the same way that you use other .NET objects. Invoking WMI methods is a little different than invoking .NET methods, as explained in “Invoking Static and Dynamic Methods.”
PowerShell 7 is based on .NET. Cmdlets such as
Get‐Process
are little more than wrappers around objects provided by the .NET Framework. When you call
Get‐Process
, for example, the cmdlet uses the
System.Diagnostics.Process
class to obtain the Windows process details via the
GetProcesses()
method. For details about the class, you can view docs.microsoft.com/dotnet/api/system.diagnostics.process?view=netcore-3.0.
In this book so far, you have dealt with a large number of .NET objects. You looked at the AD‐related objects in Chapter 3, “Managing Active Directory,” and at the various objects used by Hyper‐V in Chapter 8, “Managing Hyper‐V.” The terminology for WMI is similar to that for .NET objects but with some small differences. This chapter covers the following topics:
WMI is a complex subject, and this chapter does not have the space to go into a lot of detail. You can read more about WMI in Richard Siddaway's PowerShell and WMI (Manning, 2012). Note that this book goes into depth about WMI, and all of its code examples use the WMI cmdlets rather than the CIM cmdlets we use here. That being said, much of its discussion of WMI features and functions is still relevant even if you now access them via different cmdlets.
Before proceeding, it's useful to have a better understanding of the WMI architecture, which you can view in Figure 9.1.
Figure 9.1: WMI architecture
The WMI architecture comprises multiple components:
In WMI, you use the CIM cmdlets to interact with managed entities. The cmdlets enable you to add, update, and remove information from the CIMDB (and react to events that occur within a Windows system). WMI providers do much of the actual work involved, with the other layers just part of the plumbing.
The CIMDB is a hierarchical database, which WMI uses as its data store. It consists of a hierarchy of namespaces and classes. A WMI namespace is a container object that can contain child namespaces and classes. The root WMI namespace is named
root
. The CIM cmdlets use the namespace
root\CIMV2
as a default namespace.
A WMI class is a definition of a WMI‐Managed object. A WMI class, which lives in a namespace, contains members including properties and methods. One example is the
Win32_Share
class, which defines an SMB share. Although you can use WMI to manage SMB shares, you are more likely to use the SMB cmdlets described in Chapter 6, “Managing Shared Data.”
A property is an attribute of a WMI class. A
Win32_Share
has a property
Name
, which is the name of the SMB share. Each property has a data type, such as
String
or
Int
. The
Name
property in the
Win32_Share
class is
String
. Some classes (and properties) have names that begin with two underscore characters (__). These are system classes and properties and generally are not of much use to IT professionals, with some exceptions.
A method is some action that a WMI object or class is able to carry out. There are two method types you can use: dynamic and static. A dynamic method is one based on an occurrence of a class. For example, the
Delete()
method in the
Win32_Share
class deletes the SMB share related to a specific share object. A static method is one that is carried out by the class itself. The
Win32_Share
class has a static method
Create()
, which you could use to create an SMB share.
A WMI event is created, or fired, whenever a specific event occurs within Windows. You can subscribe to events and view the information provided. The
__ClassOperationEvent
, for example, is fired whenever an occurrence of a class is added, changed, or removed within a namespace. Events in WMI are powerful, as you can see in “Managing WMI Events.”
When you use the CIM cmdlets to retrieve class instances, PowerShell returns the data wrapped in a .NET object. For example, when you retrieve the SMB share objects from the
Win32_Share
class, the data representing each SMB share is returned contained in an object of the type
Microsoft.Management.Infrastructure.CimInstance
. This approach simplifies retrieving data from WMI, as the objects are basically the same as the .NET objects you have used throughout this book.
With the first version of Windows PowerShell, Microsoft shipped a set of WMI cmdlets that enabled basic access into WMI, although they were not functionally complete and could be hard to use. The WMI cmdlets also relied on DCOM for accessing WMI on remote machines. Although these initial cmdlets were not complete, workarounds were available for the functionality gaps.
With version 3 of Windows PowerShell, Microsoft did some reengineering of WMI. This simplified the implementation of WMI providers, created a new set of cmdlets, and led a drive to push WMI (WBEM) into Linux.
The CIM cmdlets were introduced with PowerShell 3 and provided greatly improved feature coverage. The CIM cmdlets also use different .NET class wrappers, which are smaller. This can reduce the bandwidth when accessing WMI remotely.
Another major improvement is that the CIM cmdlets use WinRM for remote communications. WinRM is easier to secure and can be more lightweight than using DCOM. WinRM is also standards‐based, opening up interoperation with Linux and Macintosh hosts. The Open Management Infrastructure project has created an open source version of WMI for Linux.
With the CIM cmdlets, you can create a CIM session with a remote machine. Once the session is established, you can run multiple WMI operations on the remote machine without incurring the relatively large setup costs. If necessary, you can create a CIM session using DCOM, which allows you to use the newer commands against older hosts that do not support WinRM.
This chapter uses several hosts:
DC1
and
DC2
, which are domain controllers in the Reskit.Org domain, and
SRV2
, which is a member server in the domain. You have used all these servers in previous chapters in this book.
As mentioned in the chapter introduction, WMI data comes from instances within WMI classes that reside in WMI namespaces. In WMI, the root namespace is named
ROOT
. This root namespace contains both classes and additional namespaces. In every namespace, WMI has an internal class
__NAMESPACE
that contains the names of namespaces below the current one. This enables you to discover the namespaces available in WMI. Although there are a number of namespaces, not all of them are overly useful to the IT professional.
You can begin your exploration of WMI data by looking at the classes that WMI contains in the root namespace, using
Get‐CimClass
like this:
# 1. View CIM classes in the root namespace
Get-CimClass -Namespace 'root' | Select-Object -First 20
You can see the output from this command in Figure 9.2.
This output shows the first 20 classes in the root WMI namespace. As you can see, many these classes begin with __ (two underline characters). These are internal or system classes used by WMI and typically contain little of use for the IT professional. From a structural point of view, WMI applications can use the
__NAMESPACE
class as the key to building a complete hierarchy of namespaces within WMI, as you will see shortly.
Figure 9.2: Viewing classes in root namespace
The WMI root namespace is named
root
. The namespaces directly below
root
are contained in the WMI class
__NAMESPACE
. You can view the top‐level namespaces—that is, the namespaces that are children of
root
—by getting the occurrences of this class, as follows:
# 2. Look in __NAMESPACE in Root
Get-CimInstance -Namespace 'root' -ClassName __NAMESPACE |
Sort-Object -Property Name
You can see the output from this command in Figure 9.3.
The classes in the
root\CIMV2
namespace are the ones that are most useful for IT professionals. To simplify access to the most commonly used classes, WMI uses the
root\CIMV2
as the default namespace for scripting.
You could use the
WmiMgmt.msc
management console to change this default. If you are using classes in non‐default namespaces from the command line, this might be convenient, but that could be dangerous if you have production scripts that assume the default; changing the default could break those scripts. So make sure you test any changes to the default carefully—or just leave the default as is.
Figure 9.3: Viewing namespaces in root
To get and count all the classes in this important WMI namespace, you can use the
Get‐CimClass
cmdlet, like this:
# 3. Get and count classes in root\CIMV2
$Classes = Get-CimClass -Namespace 'root\CIMV2'
"There are $($Classes.Count) classes in root\CIMV2"
You can view the number of classes in the
root\CIMV2
namespace in Figure 9.4.
Figure 9.4: Getting classes in the
root\CIMV2
namespace
Depending on the features you have installed, the exact number of classes could vary. There are around 400 classes that might be of use to the IT professional. The
root\CIMV2
namespace contains around 350 performance‐related classes, which you examine later in this chapter and in Chapter 10, “Reporting.”
Since every namespace contains a
__NAMESPACE
class containing the names of child namespaces, you can discover the names of all the namespaces within WMI, as follows:
# 4. Discovering ALL namespaces on DC1
Function Get-WMINamespaceEnum {
[CmdletBinding()]
Param($NS)
Write-Output $NS
Get-CimInstance "__Namespace" -Namespace $NS -ErrorAction SilentlyContinue |
ForEach-Object { Get-WMINamespaceEnum "$ns\$($
_.name
)" }
} # End of function
$Namespaces = Get-WMINamespaceEnum 'root' | Sort-Object
"There are $($Namespaces.Count) WMI namespaces on this host"
You can see the output from these commands in Figure 9.5.
Figure 9.5: Getting all namespaces
This code snippet, which was originally written by Alan Renouf (see akaplan.com/2019/02/get-wmi-namespaces-with-powershell
/
), defines a recursive function,
Get‐WMINameSpaceEnum
, which initially gets all the namespaces in the root namespace, and then calls the function recursively to get all the namespaces within each child namespace. This produces a complete set of namespaces, which is then sorted in alphabetic order.
As you can see, there are 108 namespaces on
DC1
. Adding applications or Windows features can add additional WMI namespaces as well as adding classes to existing namespaces.
You can view some of the namespaces in WMI on
DC1
with this syntax:
# 5. View some of the namespaces
$Namespaces |
Select-Object -First 20
You can see the first 20 namespaces on WMI in Figure 9.6.
Figure 9.6: Viewing some namespaces
Most of these namespaces are of little interest, but a few are useful as you see later in this chapter. In particular, you use the
root\CIMV2
and
root\directory\LDAP
namespaces in later exercises.
You can use the set of namespaces, combined with
Get‐CimClass
, to display a count of all the classes within WMI on
DC1
using this snippet:
# 6. Counting WMI classes on DC1
$WMIClasses = @()
Foreach ($Namespace in $Namespaces) {
$WMIClasses += Get-CimClass -Namespace $Namespace
}
"There are $($WMIClasses.count) classes on $(hostname)"
As shown in Figure 9.7, this displays a count of all the WMI classes on
DC1
.
This count of available WMI classes demonstrates the richness of WMI. Once you have retrieved all the WMI classes, you can use PowerShell to find classes that might be useful. You could pipe
$WMIClasses
to
Where‐Object
, looking for class names that might be of interest.
Figure 9.7: Counting classes
The CIM commands also work well across the network. It's easy to discover the namespaces on a remote host, such as
SRV2
, with this syntax:
# 7. View namespaces on SRV2
Get-CimInstance -Namespace root -ClassName __NAMESPACE -CimSession SRV2
In Figure 9.8 you can see the results of this command, namely, the top‐level WMI namespaces on
SRV2
.
Figure 9.8: Viewing namespaces on SRV2
The
‐CimSession
parameter accepts either a CIM session (that is, one that you previously created) or a computer name. If you pass a computer name,
Get‐CimInstance
creates a CIM session with the remote host, runs the command over that session, and then destroys the session.
As you can see, the namespaces are not listed in alphabetic order. You can pipe the output of this command to
Sort‐Object
to sort the list into alphabetic order.
To show how easy it is to use the CIM cmdlets across a network and to show the differences in available CIM classes, you can count the total number of WMI namespaces and classes on a remote host. To do that, you can combine some of the earlier code snippets into a script block and then run that script block on a remote host, like this:
# 8. Enumerate all namespaces and Classes on SRV2
$SB = {
Function Get-WMINamespaceEnum {
[CmdletBinding()]
Param(
$NS
)
Write-Output $NS
Get-CimInstance "__Namespace" -Namespace $NS -ErrorAction SilentlyContinue |
ForEach-Object { Get-WMINamespaceEnum "$ns\$($
_.name
)" }
} # End of function
$Namespaces = Get-WMINamespaceEnum 'root' | Sort-Object
$WMIClasses = @()
Foreach ($Namespace in $Namespaces) {
$WMIClasses += Get-CimClass -Namespace $Namespace
}
"There are $($Namespaces.count) WMI namespaces on $(hostname)"
"There are $($WMIClasses.count) classes on $(hostname)"
}
Invoke-Command -ComputerName SRV2 -ScriptBlock $SB
You can see, from the output shown in Figure 9.9, that there are 104 WMI namespaces and 14,120 WMI classes on
SRV2
.
When you add features or applications to a Windows host, you can find added WMI namespaces and classes. It is normal that the number of classes differs between systems.
Figure 9.9: Counting namespaces and classes on SRV2
You can also run this script block on the other domain controller in the Reskit.Org domain (
DC2
), like this:
# 9. Run the script block on DC2
Invoke-Command -ComputerName DC2 -ScriptBlock $SB
You can see the output from this command in Figure 9.10.
Figure 9.10: Counting namespaces and classes on DC2
As you can see from the figures in this section, the number of namespaces and classes on the three servers varies. This is to be expected. You have installed AD on both
DC1
and
DC2
, which adds namespaces and classes to WMI.
SRV2
has other Windows features loaded (including FSRM), which likewise adds classes. Many of the features in Windows come with WMI providers that add namespaces and classes. The classes in WMI provide IT professionals with more options for managing Windows systems.
There is no central registry of all WMI namespaces and classes within a Windows host. Documentation for many namespaces and classes is thin or nonexistent. The Win32 WMI provider, which provides many of the classes in the
root\CIMV2
namespace, is covered by some documentation on docs.microsoft.com, although it is developer‐focused. Your search engine is a good source of information on useful WMI classes.
As you discovered in the previous section, WMI on a Windows Server 2019 host contains more than 15,000 WMI classes. Each of those WMI classes can have zero, one, or more than one occurrence. Each occurrence of a class is a separate WMI object, which you can manage using the CIM cmdlets. Before looking at the occurrences, it's useful to explore the WMI class.
The vast majority of the classes in WMI today are of limited or no use to IT professionals. But many can be useful. You use the
Get‐CimClass
cmdlet to discover what classes exist in a namespace as well as obtaining details about any given WMI class. Details include the WMI methods supported by the class and the properties on WMI objects.
You can use the
Get‐CimClass
cmdlet to discover the details of the
Win32_Share
class. This class, which is in the
root\CIMV2
namespace, represents the print and file shares offered by the Windows Server service. Each WMI object in this class represents a single share.
While most IT professionals use the SMB share module to manage these shares, as you did in Chapter 6, WMI is an alternative.
To find out more about this class, you use the
Get‐CimClass
cmdlet.
# 1. View Win32_Share class
Get-CimClass -ClassName Win32_Share
Figure 9.11 shows the output from this command.
Figure 9.11: Viewing the
Win32_Share
WMI class
You can use the
Get‐CimClass
cmdlet to retrieve details of occurrences of a class. This allows you to see the properties of a given WMI object as well as to discover the data type for each property. You can use the
Select‐Object
cmdlet to retrieve property details.
# 2. Get Win32_Share class properties
Get-CimClass -ClassName Win32_Share |
Select-Object -ExpandProperty CimClassProperties |
Sort-Object -Property Name |
Format-Table -Property Name, CimType
Figure 9.12 shows the output, consisting of the property and data type for the properties of an object of the class
Win32_Share
.
Figure 9.12: Counting namespaces and classes on
DC1
Methods are actions that WMI can perform on either a class or a WMI object (an occurrence of a WMI class). WMI methods are generally implemented by WMI providers. You can use the
Get‐CimClass
cmdlet to explore details of the methods for a given WMI class.
# 3. Get class methods
Get-CimClass -ClassName Win32_Share |
Select-Object -ExpandProperty CimClassMethods
You can see the methods supported by the
Win32_Share
class in Figure 9.13.
When you use
Get‐CimClass
and do not specify a namespace, WMI assumes the namespace in which to find the specified class is
root\CIMv2
.
Figure 9.13: Viewing the methods supported by the
Win32_Share
class
For each method returned, you can see the names of the parameters (where the methods have any). You can see that the
Delete()
method takes no parameters, whereas the
Create
method takes seven. You can use the previous command to confirm the data type for each parameter.
If you want to view details of a class in a namespace other than the default, you specify it with the
‐Namespace
parameter.
# 4. Get classes in a non-default namespace
Get-CimClass -Namespace root\directory\LDAP |
Where-Object CimClassName -match '^ds_Group'
You can see the class details in Figure 9.14.
Figure 9.14: Viewing class details with a specified namespace
The AD WMI provider implements the
ds_Group
class in the
\root\directory\LDAP
namespace. Each instance of this class represents an AD group, complete with all the properties you can retrieve using the
Get‐ADGroup
command.
As you have seen, WMI contains a large number of namespaces and classes. While most of the classes are of little or no use to the IT professional, many contain information that can be useful. You use the
Get‐CimInstance
cmdlet to return instances of a WMI class.
This cmdlet gets the relevant rows from WMI and returns that information wrapped in a .NET object.
You use the
Get‐CimInstance
cmdlet to retrieve WMI objects within a given WMI class, like this:
# 1. Using Get-CimInstance in default Namespace
Get-CimInstance -ClassName Win32_Share
As shown in Figure 9.15, this command returns the objects in the
Win32_Share
class.
Figure 9.15: Viewing details of the
Win32_Share
class
Each WMI object returned represents a single SMB share on the host.
To retrieve the WMI objects from any class that is not in the default
root\WIN32
namespace, you must also specify the namespace, as shown here:
# 2. Get WMI objects from non-default namespace
$GCIMHT1 = @{
Namespace = 'root\directory\LDAP'
ClassName = 'ds_group'
}
Get-CimInstance @GCIMHT1|
Sort-Object -Property Name |
Select-Object -First 10 |
Format-Table -Property DS_name, DS_distinguishedName
The output from these commands, which is similar to the previous step, is shown in Figure 9.16.
As you can see, retrieving objects from any non‐default namespace is pretty simple; you just specify the namespace.
Figure 9.16: Viewing details of a class in a non‐default namespace
A WMI filter is an expression that filters the objects returned from commands such as
Get‐CimIstance
. A filter is similar to the ‐
FilterScript
parameter to the
Where‐Object
cmdlet, but it uses a different syntax, based on the ANSI SQL query language. An important difference between WMI filters and
Where‐Object
is that a WMI filter controls occurrences at the source rather than instantiating them and dropping the ones you are not interested in. This is known as early filtering and is more efficient.
You apply a WMI filter to
Get‐CimInstance
.
# 3. Using -Filter
$Filter = "ds_Name LIKE '%operator%' "
Get-CimInstance @GCIMHT1 -Filter $Filter |
Format-Table -Property ds_Name
You can see the output of these commands in Figure 9.17.
Figure 9.17: Using the
‐Filter
parameter
In this example, you use the WMI filter to return only those objects where the
ds_Name
property contains the string “operator.”
Note that with the
LIKE
clause, WQL uses
%
to match one or more characters, unlike PowerShell's
‐Like
operator, which uses
*
.
As mentioned, WMI contains a specialized query language, WMI Query Language (WQL), which is, in effect, a limited subset of the ANSI SQL language. WQL contains just a single command (
SELECT
), which has a few useful clauses. A full WQL query enables you to specify conditions on which instances are returned and which specific properties are returned, which can reduce the bandwidth used when you retrieve remote WMI objects.
You can specify a WMI query to the
Get‐CimInstance
command like this:
# 4. Use a WMI Query
$Q = @"
SELECT * from ds_group
WHERE ds_Name like '%operator%'
"@
Get-CimInstance -Query $q -Namespace 'root\directory\LDAP' |
Format-Table ds_Name
You can see the output of these commands, the CIM classes from this nonstandard namespace that contain “operator” in their names, in Figure 9.18.
Figure 9.18: Using a WQL query
There are two important advantages of using WMI queries, especially when querying remote hosts. First, the
SELECT command
instructs WMO to return only specific properties of each class occurrence. In a large WMI class, this can improve performance by not passing unneeded properties. You can also use the
WHERE
clause to specify a filter. The filter specified in the
WHERE
clause is the same as you specified in the
Filter
parameter. The
WHERE
clause is similar in concept to
Where‐Object
, except that the filtering is done by WMI when the objects are being returned. This is known as early filtering. Additionally, the syntax of the
WHERE
clause is not the same as you use with PowerShell and the
‐LIKE
operator.
With the CIM cmdlets, you can get WMI objects from a remote machine. You use the same cmdlet (
Get‐CimInstance
) and use the
‐ComputerName
property. You can get class instances from a remote system with this syntax:
# 5. Get WMI Object from a remote system
Get-CimInstance -CimSession SRV2 -ClassName Win32_ComputerSystem
You can see the output of these commands in Figure 9.19.
Figure 9.19: Getting remote WMI objects
As you can see, getting remote CIM objects is straightforward.
A method, in many programming languages, is an action that an object (or class) is able to perform. In WMI in Windows, methods are generally implemented by WMI providers. In many cases, these methods duplicate functionality provided to the IT professional via PowerShell commands.
WMI has two types of method: static and instance. A static method is one that WMI performs based on the class, while an instance method is one that WMI performs on an instance. As you saw in “Viewing Class Methods,” the
Win32_Share
class has a static method
Create
, which creates a new SMB share. WMI objects that are instances of this class have a
Delete
method, which deletes the specific SMB share. The
Delete
method is an instance method. In general, you use static methods to create WMI instances and instance methods to act on instances once they are created.
As you have already seen, the
Get‐CimClass
cmdlet provides useful information about a WMI class, including the methods available and the parameters that those methods use. The object returned by the
Get‐CimClass
cmdlet has a property,
CimClassMethods
, which describes each available method, including each one's parameter names and whether the method is static or instance‐based. You can use
Select‐Object
to expand and then view the method’s details.
# 1. Review methods of Win32_Share Class
Get-CimClass -ClassName Win32_Share |
Select-Object -ExpandProperty CimClassMethods
You can see the output from these commands, the methods provided by the
Win32_Share
class, in Figure 9.20.
Figure 9.20: Viewing methods of the
Win32_Share
class
In WMI, both classes and methods can have qualifiers, which provide information about the class or method. The
Constructor
qualifier shows that the
Create
method creates instances of this class, while the
Destructor
qualifier documents that the
Delete
method deletes instances of this class. Finally, the
Implemented
qualifier notes that this method is implemented. During the development of a WMI provider, you might find that some parts of a WMI object are defined but not fully implemented. But this is not something you normally see in released versions of Windows. The qualifiers shown for the
Create
method include the
Static
and
Constructo
r qualifiers. This tells you that the
Create
method is a static method used to construct new instances of the class.
For more details on WMI qualifiers, see docs.microsoft.com/windows/win32/wmisdk/wmi-qualifiers.
You can use the
Get‐CimClass
cmdlet to provide details of the properties of the class. Each WMI object has one or more properties that are, like .NET objects, of different types. For example, you can view the properties of an instance of the
Win32_Share
class with this code:
# 2. Review properties of Win32_Share class
Get-CimClass -ClassName Win32_Share |
Select-Object -ExpandProperty CimClassProperties |
Format-Table -Property Name, CimType
Figure 9.21 shows the output from these commands, detailing the properties of an instance of the
Win32_Share
class.
Figure 9.21: Viewing methods of the
Win32_Share
class
As you can see in this output, the properties of this class are similar to the properties of the objects returned from the
Get‐SMBShare
command. Knowing the data types of the parameters can be important for interoperation and when you invoke CIM methods.
To create a new SMB share via WMI, you use the Invoke‐CimMethod cmdlet to invoke the
Create
method.
# 3. Create Hash Table of new share properties using static method
$NSHT = @{
Name = 'TestShare1'
Path = 'C:\Foo'
Description = 'Test Share'
Type = [uint32] 0 # disk
}
Invoke-CimMethod -ClassName Win32_Share -MethodName Create -Arguments $NSHT
As you can see in Figure 9.22, the only output from these commands is a result code, where zero means success.
Figure 9.22: Invoking a WMI method
When you invoke a WMI method, you pass the parameters via a hash table. In the hash table, the keys represent the names of the class properties and the values are those you want the new class occurrence to have. In this case, you specify the SMB share name, path, description, and type. To create a disk share, you specify
Type
with a value of 0 (zero).
Note that in this snippet, you pass the method arguments (that is, the details of the share that the cmdlet is to create) as a hash table. This is different from using a hash table to splat parameter values, so you use the notation
$NSHT
to pass the method parameters.
Now that you have created the share using the WMI method, you can use the
Get‐SMBShare
command to view the details of the share.
# 4. View the new SMB Share
Get-SMBShare -Name 'TestShare1'
Figure 9.23 shows the output from this command, the properties of an instance of the
Win32_Share
class.
Figure 9.23: Viewing the SMB share
You can also view the newly created SMB share by using a WMI filter.
# 5. View the new SMB Share using Get-CimInstance
Get-CimInstance -Class Win32_Share -Filter "Name = 'TestShare1'"
You can see the output from this command, showing details about this newly created share, in Figure 9.24.
Figure 9.24: Viewing an SMB share
Note that the syntax for the filter is based on the ANSI SQL language and does not adopt normal PowerShell patterns.
You used the
Create
static method to create an SMB share. Each SMB share instance has an instance method,
Delete
, which deletes a share. You can remove the newly created share with this code:
# 6. Remove the share
Get-CimInstance -Class Win32_Share -Filter "Name = 'TestShare1'" |
Invoke-CimMethod -MethodName Delete
You can see the output of these commands in Figure 9.25. As you saw when creating a new share with WMI, the output indicates only that WMI has removed the share successfully from WMI (and Windows).
Figure 9.25: Removing an SMB share
This snippet first gets the WMI object related to the new share and then pipes that to
Invoke‐CimMethod
. WMI then removes the share instance from the
Win32_Share
class.
As you have seen, you can use WMI to create and remove various objects, in this case an SMB share. A final reminder: although you can use WMI to manage SMB shares, it is easier to use the native cmdlets, in this case the SMB cmdlets.
WMI has rich event‐management functionality. WMI and its providers implement a range of event classes that enable you to detect and handle the wide range of events that occur in a Windows computer.
WMI itself can raise events whenever any WMI object—that is, any underlying data in the CIM database—is added, modified, or removed. This includes any changes to a namespace, changes to classes within a namespace, and changes to class occurrences. Such events are known as intrinsic events. An example would be Windows starting a new process and creating a new instance of the
Win32_Process
class.
WMI providers also implement provider‐specific events, known as extrinsic events. The WMI registry provider offers events that detect changes to the registry. The Active Directory WMI provider has events that fire whenever objects, such as an AD group, are changed.
With WMI event handling, you need to create an event subscription that tells WMI which event you are interested in. Additionally, you can define an event handler that performs some action(s) when the event occurs. For more information on the kinds of events that WMI can generate, see docs.microsoft.com/en-us/windows/win32/wmisdk/determining-the-type-of-event-to-receive.
WMI provides two kinds of event handling you can use. The first, temporary event handling, makes use of
Register‐CimEvent
to subscribe to a specific event such as a change to the registry. The second is permanent eventing, which you'll explore in “Implementing Permanent WMI Event Handling.” In both cases, you must first register for the event, sometimes referred to as subscribing to the event.
With temporary eventing, when you register for an event, you can also specify a script block, which you specify using the
‐Action
parameter to
Register‐CimIndicationEvent
. As long as you keep the PowerShell session open, when the event occurs, PowerShell executes the action block. The script inside the action block runs inside a background PowerShell job. PowerShell creates the job when you register for the event. PowerShell runs the job in the background and buffers any output from the script block. You can view that output by using
Receive‐Job
. Also, if the script block contains
Write‐Host
statements, PowerShell writes the output directly to the console and not to the background job output. The first time this happens, it can be a bit confusing since the output from the
Write‐Host
statement(s) just appears in the console.
If you do not specify an action block when registering for the event, PowerShell queues any events that occur in the event log. You use the
Get‐WinEvent
cmdlet to retrieve the event records and process them accordingly.
When any WMI event occurs, WMI creates an event record that contains details of the event. However, the information included in the event record is often less than IT professionals might like. For example, WMI can detect when someone changes the membership of an AD group, but WMI does not record who made the change or the IP address of the host they used to make the change. Similarly, the Registry WMI provider can raise an event when a Registry key value is changed, but the event record does not contain the before and after values (or who made the change). WMI eventing, combined with your use of other tools, can be extremely powerful as part of troubleshooting.
You use the
Register‐CimIndicationEvent
cmdlet to tell WMI what event you want to register for, as shown here:
# 1. Register for an intrinsic event
$Query1 = "SELECT * FROM __InstanceCreationEvent WITHIN 2
WHERE TargetInstance ISA 'Win32_Process'"
$CEHT = @{
Query = $Query1
SourceIdentifier = 'NewProcessEvent'
}
Register-CimIndicationEvent @CEHT
These commands specify a WQL query that describes the event you want to subscribe to. The query tells WMI to generate an event any time a new WMI object is added to the
Win32_Process
class. Any time you run a process, WMI raises this event.
After you register for this event, WMI writes to a PowerShell event queue each time Windows starts a new process (that is, runs some program).
You can test the event subscription by running a Windows program.
# 2. Run Notepad
notepad.exe
This command runs the Notepad Windows application, and you see the application open. At this point, WMI has raised the event and has queued the event details in the PowerShell event queue.
To retrieve details of this event from the event queue, you use the
Get‐Event
command like this:
# 3. Get Event
$Event = Get-Event -SourceIdentifier 'NewProcessEvent' |
Select -Last 1
This command returns any event in the event queue with the identifier
NewProcessEvent
. Depending on how busy your system is and what you have running on it, the command may return many events, so we select the most recent one. Depending on how long you leave this query running, Windows may start up several processes as part of normal operation. Each time this happens, WMI writes a further entry to the event queue.
The WMI event, held in the
$Event
variable, is a complex object with a lot of properties. Most of those properties are not overly useful for the IT professional. If you view
$Event.SourceEventArgs.NewEvent.TargetInstance
, you get the information WMI recorded about the event, like this:
# 4. Display event details
$Event.SourceEventArgs.NewEvent.TargetInstance
You can see the output in Figure 9.26.
Figure 9.26: Viewing WMI event details
As you can see in the output, the event details show that Windows started a
notepad.exe
process.
Once you have completed looking at a given event, there are two ways to remove the event and clean up the environment. The simplest way is to just close PowerShell, which removes all temporary event subscriptions. You can also explicitly deregister for the event by using the
Unregister‐Event
command, like this:
# 5. Unregister Event
Unregister-Event -SourceIdentifier 'NewProcessEvent'
An extrinsic WMI event is one raised by a WMI provider. The WMI registry provider can raise an event whenever the registry changes. As noted earlier, you can also specify an action block—a script block that WMI executes whenever the event occurs, as follows:
# 6. Create and Register Extrinsic event query — handled by provider
New-Item -Path 'HKLM:\SOFTWARE\Wiley' | Out-Null
$Query2 = "SELECT * FROM RegistryValueChangeEvent
WHERE Hive='HKEY_LOCAL_MACHINE'
AND KeyPath='SOFTWARE\\Wiley' AND ValueName='MOLTUAE'"
$Action2 = {
Write-Host -Object "Registry Value Change Event Occurred"
$Global:RegEvent = $Event }
Register-CimIndicationEvent -Query $Query2 -Action $Action2 -Source RegChange
Figure 9.27 shows the output from these commands.
Figure 9.27: Creating an extrinsic event subscription
These commands first create a new registry key. Then you define a WQL query that looks for a change in any registry value. You create an action block, which is a PowerShell script block that you want WMI to run when the event occurs. You then register for this event, specifying the action block. The output you see is the summary of the new background job, which PowerShell uses to invoke the code in the action block. The action block has two commands: the first uses
Write‐Host
to write a message to the PowerShell host, and the other sets a global PowerShell variable to hold the event details.
Now that you have the new registry key created and have registered the event, you can test this event registration. To do that, you update a value entry on the registry key being watched, using the following commands:
# 7. Create a new registry key and change a value entry
$Q2HT = [ordered] @{
Type = 'DWord'
Name = 'MOLTUAE'
Path = 'HKLM:\Software\Wiley'
Value = 42
}
Set-ItemProperty @Q2HT
Get-ItemProperty -Path HKLM:\SOFTWARE\Wiley
These commands update a registry value and create the output you can see in Figure 9.28.
Figure 9.28: Updating a registry value
You can see the updated registry value in the figure. A few seconds after using
Set‐ItemProperty
, the WMI event handler sends the message to the console that “Registry Value Change Event Occurred.”
To remove the event registration, you can use the
Unregister‐Event
command.
# 8. Unregister for the event
Unregister-Event -SourceIdentifier 'RegChange'
This command removes the registration, and no further events are raised for this subscription.
Because the action block saved the results of the most recent event in the
$RegEvent
variable, you can examine this variable to see details of the event, like this:
# 9. Look at result details
$RegEvent.SourceEventArgs.NewEvent
You can see the output from this command in Figure 9.29.
Figure 9.29: Viewing event details
The WQL query you create monitors any changes to the
ds_group
WMI class.
# 10. Create WQL Event Query
$Group = 'Enterprise Admins'
$Query1 = @"
Select * From __InstanceModificationEvent Within 5
Where TargetInstance ISA 'ds_group' AND
TargetInstance.ds_name = '$Group'
"@
In this query, you are asking WMI to generate the event any time there is any change to an object in the
ds_group
class (that is, any time a change to the group occurs). This query also tells WMI how often to look for the event.
Now that you have the WQL query, you can create the event subscription by using
Register‐CimIndicationEvent
.
# 11. Create a temporary WMI event indication
$Event = @{
Namespace = 'root\directory\LDAP'
SourceID = 'DSGroupChange'
Query = $Query1
Action = {
$Global:ADEvent = $Event
Write-Host 'We have a group change'
}
}
Register-CimIndicationEvent @Event
Since you specified an action block, PowerShell runs a batch job to perform the script block. You can see the job details in Figure 9.30.
Figure 9.30: Creating a temporary WMI event subscription
To test the subscription, you need to add a user to the Enterprise Admins group, using
Add‐ADGroupMember
.
# 12. Add a user to the Enterprise Admins group
Add-ADGroupMember -Identity 'Enterprise Admins' -Members Sylvester
This command adds the user Sylvester to the Enterprise Admins group. You created this user in Chapter 3. The command itself does not produce any output. But when WMI detects the event, it outputs the message “We have a group change” to the console, as you can see in Figure 9.31.
Figure 9.31: Adding a user to an AD group
Once WMI has detected the group change and generated an event, it executes the action block. In addition to emitting the message to the console, this block saved details of the event to the
$ADEvent
variable. You can view the details of the event by examining the variable, as shown here:
# 13. View who was added
$ADEvent.SourceEventArgs.NewEvent.TargetInstance |
Format-Table -Property DS_sAMAccountName,DS_Member
You can see the event information in Figure 9.32.
Figure 9.32: Viewing event details
Although you can see the user added to the AD, WMI does not capture which user made the change or details that might help you determine who made the change.
To complete this section, you remove the WMI event subscription as follows:
# 15. Unregister for the event
Unregister-Event -SourceIdentifier 'DSGroupChange'
This removed the event registration. As an alternative, you could have just exited from PowerShell.
In this section, you examined temporary WMI event handling. You set up an intrinsic query and two extrinsic queries and observed how WMI handles events.
In the previous section, you saw how to make use of temporary event handling with WMI. You created a subscription, with or without an action block, and are able to consume and handle events when they occur. This event handling is temporary in that when you exit from PowerShell, the event subscription is removed, and no further events are generated.
Temporary event handling works only in a given PowerShell session, which makes it a great diagnostic tool as part of troubleshooting. Cleanup is also simple—just close the PowerShell session. This means you might need to open, and keep open, a PowerShell console with which to carry out WMI event monitoring. To avoid this, you can use permanent event handling.
With permanent event handling, you configure WMI to both subscribe to an event and then perform some predefined action when that event occurs. You first store details of the specific event and how to respond to it as new instances in the WMI CIM database by adding objects to two WMI classes. Then you bind these two new instances, which tells WMI to run the action whenever the event occurs.
WMI in Windows provides five built‐in permanent event handlers:
Microsoft developed the Active Script consumer in the days of Visual Basic and VBS scripts. Unfortunately, the consumer does not support PowerShell scripts.
The Log File consumer enables you to write information to a log file of your choice. You specify the message you want to write and can ask WMI to add information about the event to the entry. See docs.microsoft.com/windows/win32/wmisdk/writing-to-a-log-file-based-on-an-event for more information on how to do this.
The NT Log File consumer writes details to the Windows Event log. This is useful if you have a tool such as System Center Operations Manager that tracks event logs and responds to events.
The Command Line consumer enables you to run any program, with parameters. You can specify
pwsh.exe
as the command to run and pass the name of a script file that WMI runs each time the event fires. This section uses this consumer.
To implement permanent event handling, you must do three things:
When you create the event filter, you define a WQL query that directs WMI to watch for events of a particular event class. For example, you can specify you want the event to fire whenever the Enterprise Admins group changes. You also specify a polling or refresh interval—how often WMI should look for that event. The shorter the refresh interval, the higher the load on the system. A change to a high‐privilege group like this could be a precursor to an attack on the server, but at the same time, you do not cripple your DCs looking for changes every second.
The AD WMI provider implements a wide range of events you can subscribe to. In WMI, every namespace provides events that fire whenever any class instance is added, modified, or removed in that namespace. For example, the class
__InstanceModificationEvent
in the namespace
root/directory/LDAP
detects changes to any AD group.
Creating a permanent event subscription, therefore, requires you to add three WMI objects to three WMI classes. Once you have bound the event handler to the action, WMI monitors events and invokes the action accordingly. This event handling continues even after a reboot.
A word of caution: be careful when experimenting with permanent event handling. Before you add permanent event handlers (that is, add a new object to each of the three WMI event classes), you should understand how to remove those objects to remove the event handler. Unless you remove a WMI permanent event handler (that is, remove the three related WMI objects), WMI continues to watch and handle the permanent event, consuming host resources. It is also useful to generate two functions that display the event subscription and that delete it.
Finally, be careful when changing an event filter's refresh time. As you decrease the refresh time, you add load (mainly CPU and memory) to the host. For the most part, checking more often than every five seconds is probably overkill.
In this section, you detect an attempt to add an unauthorized user to the Enterprise Admins group. You begin by creating a file of authorized users and save that into a file named
C:\Foo\OKUsers.Txt
, as follows:
# 1. Create a list of valid users for Enterprise Admins
$OKUsersFile = 'C:\Foo\OKUsers.Txt'
$OKUsers = 'Administrator'
$OKUsers | Out-File -FilePath $OKUsersFile
In practice, the file of valid users would most likely contain more users. You can adjust the file accordingly.
Implementing a permanent event handler means that WMI carries out the filtering and action‐invoking until you remove the handler. Unlike the filters you used in “Managing WMI Events,” these items (or at least the bindings) must be removed to stop WMI from handling the event.
It is useful to create functions to show the details of the WMI permanent event filter—that is, the WMI objects that make up this filter—and to delete the relevant records, especially for testing. You define the two functions as follows:
# 2. Define two helper functions
Function Get-WMIPE {
Write-Output -InputObject '***Event Filters Defined:'
Get-CimInstance -Namespace root\subscription -ClassName __EventFilter |
Where-Object Name -eq "EventFilter1" |
Format-Table Name, Query
Write-Output -InputObject '***Consumer Defined:'
$NS = 'root\subscription'
$CN = 'CommandLineEventConsumer'
Get-CimInstance -Namespace $NS -Classname $CN |
Where-Object {$_.name -eq "EventConsumer1"} |
Format-Table Name, Commandlinetemplate
Write-Output -InputObject'***Bindings Defined:'
Get-CimInstance -Namespace root\subscription -ClassName __FilterToConsumerBinding |
Where-Object -FilterScript {$_.Filter.Name -eq "EventFilter1"} |
Format-Table Filter, Consumer
}
Function Remove-WMIPE {
Get-CimInstance -Namespace root\subscription __EventFilter |
Where-Object Name -eq "EventFilter1" |
Remove-CimInstance
Get-CimInstance -Namespace root\subscription CommandLineEventConsumer |
Where-Object Name -eq 'EventConsumer1' |
Remove-CimInstance
Get-CimInstance -Namespace root\subscription __FilterToConsumerBinding |
Where-Object -FilterScript {$_.Filter.Name -eq 'EventFilter1'} |
Remove-CimInstance
}
The first function,
Get‐WMIPE,
gets the details of the specific permanent event hander (that is, the event filter, event consumer, and binding), while the second,
Remove‐WMIPE,
removes the relevant records. These commands create two functions and set an alias for each one. If you are creating a WMI permanent event filter, you should create these functions, if only for testing.
The event query is a WQL
SELECT
statement, as shown here:
# 3. Create an event filter query
$Group = 'Enterprise Admins'
$Query = @"
SELECT * From __InstanceModificationEvent WITHIN 10
WHERE TargetInstance ISA 'ds_group' AND
TargetInstance.ds_name = '$Group'
"@
These commands define a WMI event query, which is similar to the query you created in “Managing WMI Events.”
You next add the event filter details into the CIM database, with this code:
# 4. Create an event filter
$Param = @{
QueryLanguage = 'WQL'
Query = $Query
Name = "EventFilter1"
EventNameSpace = "root/directory/LDAP"
}
$IHT = @{
ClassName = '__EventFilter'
Namespace = 'root/subscription'
Property = $Param
}
$InstanceFilter = New-CimInstance @IHT
These commands add a new item to the
__EventFilter
class in the
root/subscription
WMI namespace. You store the details of the new CIM instance in a variable for use later.
With this permanent event handler, you want WMI to run a script whenever the event occurs. You can create a simple script and save it, like this:
# 5. Create Monitor.ps1 that is to run each time
# the Enterprise Admins group membership changes
$MONITOR = @'
$LogFile = 'C:\Foo\Grouplog.Txt'
$Group = 'Enterprise Admins'
"On: [$(Get-Date)] Group [$Group] was changed" |
Out-File -Force $LogFile -Append -Encoding Ascii
$ADGM = Get-ADGroupMember -Identity $Group
# Display who's in the group
$ADGM | Format-Table Name, DistinguishedName |
Out-File -Force $LogFile -Append -Encoding Ascii
$OKUsers = Get-Content -Path C:\Foo\OKUsers.Txt
# Look at who is not authorized
foreach ($User in $ADGM) {
if ($User.Name -notin $OKUsers) {
"Unauthorizsed user [$($User.Name)] added to $Group" |
Out-File -Force $LogFile -Append -Encoding Ascii
}
}
"**********************************`n`n" |
Out-File -Force $LogFile -Append -Encoding Ascii
'@
$MONITOR | Out-File -Path C:\Foo\Monitor.ps1
These commands create a file,
C:\Foo\Monitor.ps1
, which you want WMI to run each time the event occurs. The script outputs information about the current membership of the Enterprise Admins group. Then it looks in the
C:\Foo\OKUsers.txt
file to ensure that all users in the AD group are valid and outputs a message to the
C:\Foo\Grouplog.txt
file if not. Depending on your needs, you could adjust the
Monitor.ps1
script to remove any users in the Enterprise Admins group that are not contained in the
OKUsers.txt
file and take any other actions appropriate for your situation (such as sending an email message to a help desk or to a security group).
The event consumer tells WMI what to do if and when an event occurs. For permanent event handlers, this means defining the specific event handler you want WMI to invoke when the event occurs—in this case the Command Line event consumer. You also provide the name of the program you want to run (which is PowerShell 7) and the parameters for that program, namely, the script file you want to run. To create the event consumer, you add a further object to the CIM database, with this snippet:
# 6. Create an Event Consumer
# The consumer runs PowerShell 7 to execute C:\Foo\Monitor.ps1
$CLT = 'Pwsh.exe -File C:\Foo\Monitor.ps1'
$Param =[ordered] @{
Name = 'EventConsumer1'
CommandLineTemplate = $CLT
}
$ECHT = @{
Namespace = 'root/subscription'
ClassName = "CommandLineEventConsumer"
Property = $Param
}
$InstanceConsumer = New-CimInstance @ECHT
These commands add a new WMI object to the
CommandLineEventConsumer
class in the root/subscription WMI namespace. As with the event filter, you save the results of
New‐CimInstance
in a variable for later use.
Now that you have the event filter and event consumer, you create a third WMI object that binds these two objects together. In effect, you instruct WMI to listen for a specific event; when that event occurs, WMI is to invoke the event consumer. You achieve this binding by adding a further CIM instance to the
__FilterToConsumerBinding
class in the
root/subscription
namespace, like this:
# 7. Bind the filter and consumer
$Param = @{
Filter = [ref]$InstanceFilter
Consumer = [ref]$InstanceConsumer
}
$IBHT = @{
Namespace = 'root/subscription'
ClassName = '__FilterToConsumerBinding'
Property = $Param
}
$InstanceBinding = New-CimInstance @IBHT
These commands create a new instance in the
__FilterToConsumerBinding
object, which tells WMI to start listening for this event and invoke the event consumer when the event happens. Since Windows runs the program (that is, PowerShell 7, running
Monitor.ps1
) in a separate process, any
Write‐Host
statements in
Monitor.ps1
do not appear on your console, unlike temporary WMI event handling.
As you are adding the records, and especially after you have all three added, it's useful to ensure you have the right objects added. You can do that by using the
Get‐WMIPE
function you created in “Defining Helper Functions,” like this:
# 8. Get the event filter details
Get-WMIPE
You can see in Figure 9.33 the three related WMI objects.
Figure 9.33: Viewing event details
Once you define the binding, WMI is able to carry out the filtering. To test this, you add a user to the Enterprise Admins group.
# 9. Add a user to the Enterprise Admins group
Add-ADGroupMember -Identity 'Enterprise Admins' -Members Sylvester
This adds the user Sylvester (created in Chapter 3), to the group. Unlike with temporary event handling, WMI does not display any output to indicate that the event has occurred and has been handled.
When the event occurs, assuming correct configuration, WMI runs
Monitor.ps1
, which outputs the current membership of the group and displays any nonapproved users, as follows:
# 10. View Grouplog.txt file
Get-Content -Path C:\Foo\Grouplog.txt
You can see the output of this command in Figure 9.34.
Figure 9.34: Viewing event details
In the figure, you see the current membership of the group. Since Sylvester is not an approved user (that username is not contained in the
OKUSers.Txt
file). Depending on your situation, you could extend
Monitor.ps1
to remove the unapproved user from the group.
Once you have defined and tested the filter, you can leave it in place to protect the membership of the group. In that case, you should probably extend
Monitor.ps1
to alert you when the event occurs. Alternatively, you can remove the relevant records by running the
Remove‐WMIPE
function and removing Sylvester from the Enterprise Admins group.
# 11. Tidy up
Remove-WMIPE
$RGMHT = @{
Identity = 'Enterprise Admins'
Member = 'Sylvester'
Confirm = $false
}
Remove-ADGroupMember @RGMHT
Get-WMIPE
These commands run the
Remove‐WMIPE
function to remove the three objects from the CIM database, remove Sylvester from the Enterprise Admins group, and then rerun
Get‐WMIPE
to verify that the filter details are no longer contained in WMI.
In this chapter, you have taken a look at WMI in Windows using PowerShell 7. You began by discovering details about the namespaces that exist on a host and the classes contained in those namespaces. You then saw how to retrieve data from the WMI CIM database using the
Get‐CimInstance
command. Next you looked at invoking WMI methods and how you can use a WMI method to add an object to the CIM database. You concluded with looking at both temporary and permanent event handling.