More Method Madness
In the last article, we explored the basics of invoking WMI methods using PowerShell's CIM cmdlets. I demonstrated a number of techniques for discovering WMI methods and their parameters. Now I need to show you how to run them. Today, I will dive deeper into the intricacies of invoking WMI methods, comparing the traditional WMI cmdlets with the CIM cmdlets, and providing practical examples to illustrate their usage.
Invoking a WMI Method
The traditional approach to invoking WMI methods in Windows PowerShell uses the WMI cmdlets, which have been available since the early versions of PowerShell. While these cmdlets are still functional, understanding their usage provides important context for appreciating the improvements offered by the newer CIM cmdlets. Remember, the WMI cmdlets are not available in PowerShell 7, but I want you to understand the process. You may find legacy code that you want to adapt so you need to understand how the legacy approach to invoking methods works
The relevant WMI cmdlets in Windows PowerShell are Get-WmiObject
and Invoke-WmiMethod
. The latter cmdlet provides direct access to WMI class methods.
In the days of VBScript, you can invoke the method directly from the object. This technique works in Windows PowerShell, and you might come across it in legacy code examples and scripts.
PS C:\> $svc = Get-WmiObject -Class Win32_Service -Filter "Name='Spooler'"
PS C:\> $svc.StopService()
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 1
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ReturnValue : 0
PSComputerName :
PS C:\> Get-Service spooler
Status Name DisplayName
------ ---- -----------
Stopped spooler Print Spooler
You need to include the parentheses, even if the method does not take any parameters. If the method accepts parameters, you must pass all of them in the correct order. This can be cumbersome, especially if you are not familiar with the method's signature.
Using Invoke-WmiMethod
The easier technique, and the one you should be using is the Invoke-WmiMethod
cmdlet. This cmdlet allows you to invoke WMI methods directly, providing a more structured approach to method invocation.
The fundamental syntax for Invoke-WmiMethod
follows this pattern:
Invoke-WmiMethod -Class <classname> -Name <methodname> -ArgumentList <parameters>
The method accepts pipeline input so you might follow this pattern:
$wmiObject | Invoke-WmiMethod -Name <methodname> -ArgumentList <parameters>
Practical Examples
Let's look at some practical examples of invoking WMI methods using Invoke-WmiMethod
. I demonstrated last time how to use the Get-CimClassMethod
function from the PSScriptTools module to discover WMI methods.
PS C:\> Get-CimClassMethod -ClassName Win32_Process -Method Create
Class: Root/Cimv2:Win32_Process
Name ResultType Parameters
---- ---------- ----------
Create UInt32 {CommandLine, CurrentDirectory, ProcessStartupInformation, ProcessId}
Since I don't have a WMI object to get, I'll invoke the method directly using Invoke-WmiMethod
. The Create
method of the Win32_Process
class allows you to create a new process.
# Create a new notepad process using WMI
$result = Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList "notepad.exe"
# Check the result
if ($result.ReturnValue -eq 0) {
Write-Host "Process created successfully with PID: $($result.ProcessId)"
} else {
Write-Host "Process creation failed with error code: $($result.ReturnValue)"
}
> Depending on your version of Windows, this might launch the legacy version of Notepad.
The first parameter is the command line to execute. Since I didn't specify anything else, PowerShell assumes NULL values for the remaining parameters.
The ReturnValue
property indicates the success or failure of the method invocation, with 0
typically indicating success.
PS C:\> $result
__GENUS : 2
__CLASS : __PARAMETERS
__SUPERCLASS :
__DYNASTY : __PARAMETERS
__RELPATH :
__PROPERTY_COUNT : 2
__DERIVATION : {}
__SERVER :
__NAMESPACE :
__PATH :
ProcessId : 5512
ReturnValue : 0
PSComputerName :
Depending on the class and method, the result may include additional properties. In this case, the ProcessId
property contains the ID of the newly created process.
Let's look at another example.
# Stop a service using WMI
PS C:\> $serviceName = "Spooler"
PS C:\> $service = Get-WmiObject -Class Win32_Service -Filter "Name='$serviceName'"
PS C:\> $result = $service | Invoke-WmiMethod -Name StartService
PS C:\> Write-Host "Stop service result: $($result.ReturnValue)"
Stop service result: 0
Passing WMI Parameters
Parameter passing in WMI cmdlets requires careful attention to parameter order and data types. The traditional WMI cmdlets use positional parameter passing through the -ArgumentList
parameter of Invoke-WmiMethod
.
However, there is a major "gotcha" using the -ArgumentList
parameter and one that will make you bang your head against the wall if you are not careful. I want to use the Create
method of the Win32_Share
class. I can see the method parameters documented online at https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-share. I can also discover them with Get-CimClass
or my Get-CimClassMethod
function.
PS C:\> Get-CimClassMethod Win32_Share -Method Create | Select -ExpandProperty Parameters
Name CimType Qualifiers ReferenceClassName
---- ------- ---------- ------------------
Path String {ID, In, MappingStrings}
Name String {ID, In, MappingStrings}
Type UInt32 {ID, In, MappingStrings}
MaximumAllowed UInt32 {ID, In, MappingStrings, Optional}
Description String {ID, In, MappingStrings, Optional}
Password String {ID, In, MappingStrings, Optional}
Access Instance {ID, In, MappingStrings, Optional...} Win32_SecurityDescriptor
However, if you try to pass the parameters is the documented order, you will get a type mismatch error. When using the legacy WMI cmdlets, you must pass the parameters in the order they are defined in the class, which is typically in alphabetical order.
PS C:\> $c = Get-CimClass win32_share
PS C:\> $c.CimClassMethods["Create"].parameters
Name CimType Qualifiers ReferenceClassName
---- ------- ---------- ------------------
Access Instance {EmbeddedInstance, ID, In, MappingStrings...}
Description String {ID, In, MappingStrings, Optional}
MaximumAllowed UInt32 {ID, In, MappingStrings, Optional}
Name String {ID, In, MappingStrings}
Password String {ID, In, MappingStrings, Optional}
Path String {ID, In, MappingStrings}
Type UInt32 {ID, In, MappingStrings}