Chapter 9
Using WMI with CIM Cmdlets

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.

Reviewing WMI Architecture in Windows

Before proceeding, it's useful to have a better understanding of the WMI architecture, which you can view in Figure 9.1.

image

Figure 9.1: WMI architecture

The WMI architecture comprises multiple components:

  • CIM cmdlets: The CIM cmdlets provide access to the functions and features of WMI. The CIM cmdlets shipped initially with PowerShell 3 as an alternative to the WMI cmdlets that shipped with PowerShell 1. With PowerShell 7, the earlier WMI cmdlets are no longer available or supported, so all WMI access is via the CIM cmdlets.
  • .NET Framework: PowerShell cmdlets, including the CIM cmdlets, make use of the .NET Framework, which provides a mechanism to access COM and interact with WMI.
  • Protocol Transport: WMI needs some mechanism to communicate between the various components. The WBEM specification used HTTP/S as a transport layer. However, Microsoft implemented WMI using COM (and DCOM) to enable individual WMI components to communicate and interoperate. WMI is, in effect, a large multicomponent application built in COM. The WMI COM API enables .NET applications such as PowerShell to access this COM application. For remote access, the CIM cmdlets use WinRM by default as a transport but can work with DCOM for interoperability reasons.
  • CIM Object Manager (CIMOM): At the heart of WMI is the CIMOM, which manages access to the objects within WMI. Some objects are stored in the CIM database, while others are synthesized via WMI providers. The CIM cmdlets communicate with the CIMOM to manage access to data in the CIM database.
  • CIM Database (CIMDB): WMI stores information about WMI‐Managed objects in the CIMDB, also known as the WMI repository. The CIM cmdlets allow you to query and update this repository. This database is, in effect, a hierarchical database based on namespaces and classes.
  • Provider: A provider manages entities in WMI. It implements class methods and events (see “Managing WMI Events” and “Implementing Permanent WMI Event Handling” later in this chapter). A provider enables WMI to interoperate with various Windows components and subsystems (including AD, networking, and more). A provider can also create WMI objects dynamically as an alternative to storing them in the CIMDB. For more details on the providers built into Windows, see docs.microsoft.com/en-us/windows/win32/wmisdk/wmi-providers.
  • Managed Entity: This refers to any component that WMI exposes to the CIM cmdlets, such as the BIOS, a network card, or the operating system running at the time. Each managed entity is represented by one or more WMI classes. You use the CIM cmdlets to manage these entities.

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.

Obtaining WMI Data

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.

Using the CIM Cmdlets

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.

Systems Used in This Chapter

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.

Exploring WMI Namespaces

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.

Viewing Classes in the Root Namespace

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.

image

Figure 9.2: Viewing classes in root namespace

Viewing Namespaces Below the Root

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.

Enumerating Classes in root\CIMV2

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.

image

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.

image

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.”

Discovering All Namespaces in WMI

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.

image

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.

Viewing Some WMI 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.

image

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.

Counting WMI Classes

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.

image

Figure 9.7: Counting classes

Viewing Namespaces on a Remote Server

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.

image

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.

Counting Namespaces/Classes on SRV2

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.

image

Figure 9.9: Counting namespaces and classes on SRV2

Counting Namespaces/Classes on DC2

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.

image

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.

Exploring 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.

Examining the Win32_Share Class

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.

image

Figure 9.11: Viewing the Win32_Share WMI class

Viewing Class Properties

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.

image

Figure 9.12: Counting namespaces and classes on DC1

Viewing Class Methods

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.

image

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.

Viewing Class Details in a Specified Namespace

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.

image

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.

Getting Local and Remote Objects

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.

Using Get‐CimInstance

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.

image

Figure 9.15: Viewing details of the Win32_Share class

Each WMI object returned represents a single SMB share on the host.

Getting Objects from a Non‐default Namespace

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.

image

Figure 9.16: Viewing details of a class in a non‐default namespace

Using a WMI Filter

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.

image

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 *.

Using a WMI Query

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.

image

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.

Getting Remote WMI Objects

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.

image

Figure 9.19: Getting remote WMI objects

As you can see, getting remote CIM objects is straightforward.

Invoking WMI Methods

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.

Reviewing Static Methods of a Class

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.

image

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 Constructor 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.

Reviewing Properties of a Class

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.

image

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.

Creating a New Share

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.

image

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.

Viewing the SMB Share Using Get‐SMBShare

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.

image

Figure 9.23: Viewing the SMB share

Viewing the SMB Share Using Get‐CimInstance

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.

image

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.

Removing an SMB Share

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).

image

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.

Managing WMI Events

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.

Registering for an Event

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).

Running a Windows Process

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.

Getting the WMI Event

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.

Displaying Event Details

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.

image

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.

Unregistering for a WMI Event

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'

Creating an Extrinsic Event Registration

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.

image

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.

Modifying the Registry

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.

image

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.”

Unregister the Registry Event

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.

Examining Result Details

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.

image

Figure 9.29: Viewing event details

Defining a WQL Event Query

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.

Creating a Temporary WMI Event Subscription

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.

image

Figure 9.30: Creating a temporary WMI event subscription

Adding to the Enterprise Admins Group

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.

image

Figure 9.31: Adding a user to an AD group

Viewing the Event

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.

image

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.

Unregistering the WMI Event

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.

Implementing Permanent WMI Event Handling

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:

  • Active Script Consumer: You use this to run a specific VBS script.
  • Log File Consumer: This handler writes details of events to your own log files.
  • NT Event Log Consumer: This consumer writes event details into the Windows Event Log.
  • SMTP Event Consumer: You can use this consumer to send an SMTP email message when an event occurs.
  • Command Line Consumer: You use this consumer to run a program, such as PowerShell 7, and pass a script filename. When the event occurs, the script has access to the event details and can do pretty much anything.

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:

  • Define an event filter: This involves adding an instance to an event class. The event filter tells WMI which specific event it should look for. The event filter is basically the same as you used in “Managing WMI Events,” but you save the query in the WMI database.
  • Define an event consumer: This involves defining the action you want to take when an event occurs.
  • Bind the event filter and event consumer: This tells WMI that whenever the event occurs, it should perform the action defined by an event consumer. You do this by adding a further occurrence to another WMI class.

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.

Specifying Valid Users

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.

Defining Helper Functions

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.

Creating an Event Query

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.”

Creating an Event Filter

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.

Creating a Script for the Event Handler to Run: Monitor.ps1

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).

Creating an Event Consumer

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.

Binding Event Filter and Event Consumer

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.

Displaying Event Filter Details

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.

image

Figure 9.33: Viewing event details

Testing Event Filtering

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.

Viewing Results

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.

image

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.

Removing Event Filter Details from WMI

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.

Summary

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.