Windows Management Instrumentation (WMI) was introduced as a downloadable component with Windows 95 and NT. Windows 2000 had WMI pre-installed, and it has since become a core part of the operating system.
You can use WMI to access a huge amount of information about the computer system. This includes printers, device drivers, user accounts, ODBC, and so on; there are hundreds of classes to explore.
In this chapter, we will be covering the following topics:
The scope of WMI is vast, which makes it a fantastic resource for automating processes. WMI classes are not limited to the core operating system; it is not uncommon to find classes created after software or device drivers have been installed.
Given the scope of WMI, finding an appropriate class can be difficult. PowerShell itself is well equipped to explore the available classes.
PowerShell, as a shell for working with objects, presents WMI classes in a similar manner to .NET classes or any other object. There are several parallels between WMI classes and .NET classes.
A WMI class is used as the recipe to create an instance of a WMI object. The WMI class defines properties and methods. The WMI class Win32_Process
is used to gather information about running processes in a similar manner to the Get-Process
command.
The Win32_Process
class has properties such as ProcessId
, Name
, and CommandLine
. It has a terminate
method that can be used to kill a process, as well as a create static
method that can be used to spawn a new process.
WMI classes reside within a WMI namespace. The default namespace is rootcimv2
; classes such as Win32_OperatingSystem
and Win32_LogicalDisk
reside in this namespace.
PowerShell has two different sets of commands dedicated to working with WMI.
Get-WmiObject
is included with PowerShell 1.0 to 5.1 but is not present in PowerShell 6 and above.
The CIM cmdlets were introduced with PowerShell 3.0. They are compatible with the Distributed Management Task Force (DMTF) standard DSP0004. A move toward compliance with open standards is critical as the Microsoft world becomes more diverse.
WMI itself is a proprietary implementation of the CIM server, using the Distributed Component Object Model (DCOM) API to communicate between the client and server.
Standards compliance and differences in approach aside, there are solid, practical reasons to consider when choosing which one to use.
Some properties of CIM cmdlets are as follows:
WSMAN
for remote connections by default but can be configured to use DCOM over RPC.Some properties of WMI cmdlets are as follows:
As mentioned in the previous section, the Common Information Model (CIM) cmdlets were introduced with PowerShell 3 and are the only commands available to access WMI in PowerShell 6 and above.
The CIM commands are as follows:
Get-CimAssociatedInstance
Get-CimClass
Get-CimInstance
Get-CimSession
Invoke-CimMethod
New-CimInstance
New-CimSession
New-CimSessionOption
Register-CimIndicationEvent
Remove-CimInstance
Remove-CimSession
Set-CimInstance
Each of these CIM cmdlets uses either the ComputerName
or CimSession
parameter to target the operation at another computer.
You can use the Get-CimInstance
command to execute queries for instances of WMI objects, as the following code shows:
Get-CimInstance -ClassName Win32_OperatingSystem
Get-CimInstance -ClassName Win32_Service
Get-CimInstance -ClassName Win32_Share
Several different parameters are available when using Get-CimInstance
. The command can be used with a filter, as follows:
Get-CimInstance Win32_Directory -Filter "Name='C:\Windows'"
Get-CimInstance Win32_Service -Filter "State='Running'"
Each command will return instances matching the filter. In the case of Win32_Directory
, a single object is returned, as the following output shows:
Name Hidden Archive Writeable LastModified
---- ------ ------- --------- ------------
C:Windows False False True 20/02/2021 09:25:20
The Filter
format is WQL and will be explored later in this chapter.
When returning large amounts of information, the Property
parameter can be used to reduce the number of fields returned by a query:
Get-CimInstance Win32_UserAccount -Property Name, SID
You can also use the Query
parameter, although it is rare to find a use for this that cannot be served by the individual parameters:
Get-CimInstance -Query "SELECT * FROM Win32_Process"
Get-CimInstance -Query "SELECT Name, SID FROM Win32_UserAccount"
The Get-CimClass
command is used to return the details of a WMI class:
PS> Get-CimClass Win32_Process
NameSpace: ROOT/cimv2
CimClassName CimClassMethods CimClassProperties
------------ --------------- ------------------
Win32_Process {Create, Terminate, Get...} {Caption, Description...}
The Class
object describes the capabilities of that class. By default, Get-CimClass
lists classes from the rootcimv2
namespace.
The Namespace
parameter will fill using tab completion, meaning if the following partial command is entered, pressing Tab repeatedly will cycle through the possible root namespaces:
Get-CimClass -Namespace <tab, tab, tab>
The child namespaces of a given namespace are listed in a __Namespace
class instance. For example, the following command returns the namespaces under root
:
Get-CimInstance __Namespace -Namespace root
Extending this technique, it is possible to recursively query __Namespace
to find all the possible namespace values. Certain WMI namespaces are only available to administrative users (run as administrator); the following function may display errors for some namespaces:
function Get-CimNamespace {
param (
$Namespace = 'root'
)
Get-CimInstance __Namespace -Namespace $Namespace | ForEach-Object {
$childNamespace = Join-Path -Path $Namespace -ChildPath $_.Name
$childNamespace
Get-CimNamespace -Namespace $childNamespace
}
}
Get-CimNamespace
CIM class objects describe the methods available for a class. These methods are used to change objects.
You can use the Invoke-CimMethod
command to call a method. The CIM class can be used to find details of the methods that a class supports:
PS> (Get-CimClass Win32_Process).CimClassMethods
Name ReturnType Parameters Qualifiers
---- ---------- ---------- ----------
Create UInt32 {CommandLine...} {Constructor...}
Terminate UInt32 {Reason} {Destructor...}
GetOwner UInt32 {Domain...} {Implemented...}
GetOwnerSid UInt32 {Sid} {Implemented...}
The method with the Constructor
qualifier can be used to create a new instance of Win32_Process
.
The Parameters
property of a specific WMI method can be explored to find out how to use a method:
PS> (Get-CimClass Win32_Process).CimClassMethods['Create'].Parameters
Name CimType Qualifiers
---- ------- ----------
CommandLine String {ID, In, MappingStrings}
CurrentDirectory String {ID, In, MappingStrings}
ProcessStartupInformation Instance {EmbeddedInstance, ID, In, MappingStrings}
ProcessId UInt32 {ID, MappingStrings, Out}
If an argument has the In
qualifier, it can be passed in when you're creating an object. If an argument has the Out
qualifier, it will be returned once the instance has been created. Arguments are passed in using a Hashtable.
When creating a process, the CommandLine
argument is required; the rest can be ignored until later:
$params = @{
ClassName = 'Win32_Process'
MethodName = 'Create'
Arguments = @{
CommandLine = 'notepad.exe'
}
}
$return = Invoke-CimMethod @params
The return
object holds three properties in the case of the Create
method of Win32_Process
. This includes ProcessId
and ReturnValue
. PowerShell adds a PSComputerName
property to these:
PS> $return
ProcessId ReturnValue PSComputerName
--------- ----------- --------------
15172 0
PSComputerName
is blank when a request is local. ProcessId
is the Out
property listed under the Create
method parameters. ReturnValue
indicates whether the operation succeeded, and 0
indicates that it was successful.
A nonzero return value indicates that something went wrong, but the values are not translated in PowerShell. The return values are documented in Microsoft Docs: https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-process.
The Create
method used here creates a new instance. The other methods for Win32_Process
act against an existing instance (an existing process).
Extending the preceding example, a process can be created and then terminated:
$params = @{
ClassName = 'Win32_Process'
MethodName = 'Create'
Arguments = @{
CommandLine = 'notepad.exe'
}
}
$return = Invoke-CimMethod @params
pause
Get-CimInstance Win32_Process -Filter "ProcessID=$($return.ProcessId)" |
Invoke-CimMethod -MethodName Terminate
The pause
command will wait for return to be pressed before continuing; this allows us to show that Notepad was opened before it was terminated.
The Terminate
method has an optional argument that is used as the exit code for the terminate process. This argument may be added using a Hashtable; in this case, a (made up) value of 5
is set as the exit code:
$invokeParams = @{
ClassName = 'Win32_Process'
MethodName = 'Create'
Arguments = @{
CommandLine = 'notepad.exe'
}
}
$return = Invoke-CimMethod @invokeParams
$getParams = @{
ClassName = 'Win32_Process'
Filter = 'ProcessId={0}' -f $return.ProcessId
}
Get-CimInstance @getParams |
Invoke-CimMethod -MethodName Terminate -Arguments @{Reason = 5}
Invoke-CimMethod
returns an object with a ReturnValue
. A return value of 0
indicates that the command succeeded. A nonzero value indicates an error condition. The meaning of the value will depend on the WMI class.
The return values associated with the Terminate
method of Win32_Process
are documented in Microsoft Docs: https://docs.microsoft.com/windows/win32/cimwin32prov/terminate-method-in-class-win32-process.
Some methods require or can use instances of CIM classes as arguments. In some cases, these instances must be created.
The arguments for Win32_Process
include a ProcessStartupInformation
parameter. ProcessStartupInformation
is described by a WMI class, Win32_ProcessStartup
.
There are no existing instances of Win32_ProcessStartup
; running Get-CimInstance Win32_ProcessStartup
will not find anything. The Win32_ProcessStartup
class doesn't have a Create
method (or any other constructor) that can be used to create an instance either.
You can use New-CimInstance
to create an instance of the class:
$class = Get-CimClass Win32_ProcessStartup
$startupInfo = New-CimInstance -CimClass $class -ClientOnly
You can also use New-Object
:
$class = Get-CimClass Win32_ProcessStartup
$startupInfo = New-Object CimInstance $class
Finally, the new
method may be used:
$class = Get-CimClass Win32_ProcessStartup
$startupInfo = [CimInstance]::new($class)
You can set properties on the created instance; the effect of each property is documented in Microsoft Docs: https://docs.microsoft.com/windows/win32/cimwin32prov/win32-processstartup.
In the following example, properties are set to dictate the position and title of a pwsh.exe
window:
$class = Get-CimClass Win32_ProcessStartup
$startupInfo = New-CimInstance -CimClass $class -ClientOnly
$startupInfo.X = 50
$startupInfo.Y = 50
$startupInfo.Title = 'This is the window title'
$params = @{
ClassName = 'Win32_Process'
MethodName = 'Create'
Arguments = @{
CommandLine = 'pwsh.exe'
ProcessStartupInformation = $startupInfo
}
}
$returnObject = Invoke-CimMethod @params
If the process starts successfully, $returnObject
will have a ReturnValue
of 0
.
As we mentioned earlier in this chapter, a key feature of the CIM cmdlets is their ability to change how connections are formed and used.
The Get-CimInstance
command has a ComputerName
parameter, and when you use this, the command automatically creates a session to a remote system using WSMAN
. The connection is destroyed as soon as the command completes.
The Get-CimSession
, New-CimSession
, New-CimSessionOption
, and Remove-CimSession
commands are optional commands that you can use to define the behavior of remote connections.
The New-CimSession
command creates a connection to a remote server. An example is as follows:
PS> $cimSession = New-CimSession -ComputerName Remote1
PS> $cimSession
Id : 1
Name : CimSession1
InstanceId : 1cc2a889-b649-418c-94a2-f24e033883b4
ComputerName : Remote1
Protocol : WSMAN
Alongside the other parameters, New-CimSession
has a Credential
parameter that can be used in conjunction with Get-Credential
to authenticate a connection.
If the remote system does not, for any reason, present access to WSMAN
, it is possible to switch the protocol down to DCOM
by using the New-CimSessionOption
command:
PS> $option = New-CimSessionOption -Protocol DCOM
PS> $cimSession = New-CimSession -ComputerName Remote1 –SessionOption $option
PS> $cimSession
Id : 2
Name : CimSession2
InstanceId : 62b2cb56-ec84-472c-a992-4bee59ee0618
ComputerName : Remote1
Protocol : DCOM
The New-CimSessionOption
command is not limited to protocol switching; it can affect many of the other properties of the connection, as shown in the help and the examples for the command.
Once a session has been created, it exists in memory until it is removed. The Get-CimSession
command shows a list of connections that have been formed, and the Remove-CimSession
command permanently removes connections.
The Get-CimAssociatedClass
command replaces the use of the ASSOCIATORS OF
query type when using the CIM cmdlets.
The following command gets the class instances associated with Win32_NetworkAdapterConfiguration
. As the arguments for the Get-CimInstance
command are long strings, splatting is used to pass the parameters into the command:
$params = @{
ClassName = 'Win32_NetworkAdapterConfiguration'
Filter = 'IPEnabled=TRUE AND DHCPEnabled=TRUE'
}
Get-CimInstance @params | Get-CimAssociatedInstance
The following example uses Get-CimAssociatedClass
to get the physical interface associated with the IP configuration:
$params = @{
ClassName = 'Win32_NetworkAdapterConfiguration'
Filter = 'IPEnabled=TRUE AND DHCPEnabled=TRUE'
}
Get-CimInstance @params | ForEach-Object {
$adapter = $_ | Get-CimAssociatedInstance -ResultClassName Win32_NetworkAdapter
[PSCustomObject]@{
NetConnectionID = $adapter.NetConnectionID
Speed = [Math]::Round($adapter.Speed / 1MB, 2)
IPAddress = $_.IPAddress
IPSubnet = $_.IPSubnet
Index = $_.Index
Gateway = $_.DefaultIPGateway
}
}
The preceding command returns details of every IP- and DHCP-enabled network adapter, merging the results of two different CIM classes into a single object.
WMI Query Language, or WQL, is used to query WMI in a similar style to SQL.
WQL implements a subset of Structured Query Language (SQL). The keywords are traditionally written in uppercase; however, WQL is not case-sensitive.
Both the CIM and the older WMI cmdlets support the Filter
and Query
parameters, which accept WQL queries.
The SELECT
, WHERE
, and FROM
keywords are used with the Query
parameter.
The generalized syntax for the Query
parameter is as follows:
SELECT <Properties> FROM <WMI Class>
SELECT <Properties> FROM <WMI Class> WHERE <Condition>
You can use the wildcard *
to request all available properties or a list of known properties:
Get-CimInstance -Query "SELECT * FROM Win32_Process"
Get-CimInstance -Query "SELECT ProcessID, CommandLine FROM Win32_Process"
The WHERE
keyword is used to filter results returned by SELECT
; for example, see the following:
Get-CimInstance -Query "SELECT * FROM Win32_Process WHERE ProcessID=$PID"
WQL and arrays
WQL cannot filter array-based properties; for example, the capabilities property of Win32_DiskDrive
.
The backslash character, , is used to escape the meaning of characters in a WMI query. You can use it to escape a wildcard character, quotes, or itself. For example, the following WMI query uses a path; each instance of
in the path must be escaped:
Get-CimInstance Win32_Process -Filter "ExecutablePath='C:\Windows\Explorer.exe'"
The preceding command returns any instances of the explorer.exe
process, as shown here:
ProcessId Name HandleCount WorkingSetSize VirtualSize
--------- ---- ----------- -------------- -----------
8320 explorer.exe 3412 198606848 2204322111488
The properties shown will vary from one computer to another.
About Win32_Process and the Path property
The Path
script property is added to the output from the Win32_Process
class by PowerShell. While it appears in the output, the property cannot be used to define a filter, nor can Path
be selected using the Property
parameter of either Get-CimInstance
or Get-WmiObject
.
Get-Member
shows that it is a ScriptProperty
, as follows:
Get-CimInstance Win32_Process -Filter "ProcessId=$pid" |
Get-Member -Name Path
Get-WmiObject Win32_Process -Filter "ProcessId=$pid" |
Get-Member -Name Path
WQL defines two wildcard characters that can be used with string queries:
%
(percentage) character matches any number of characters and is equivalent to using *
in a filesystem path or with the -like
operator._
(underscore) character matches a single character and is equivalent to using ?
in a filesystem path or with the -like
operator.The following query filters the results of Win32_Service
, including services with paths starting with a single drive letter and ending with .exe
:
Get-CimInstance Win32_Service -Filter 'PathName LIKE "_:\%.exe"'
Logic operators can be used with the Filter
and Query
parameters.
The examples in the following table are based on the following command:
Get-CimInstance Win32_Process -Filter "<Filter>"
Description |
Operator |
Syntax |
Example |
Logical and |
|
|
|
Logical or |
|
|
|
Logical not |
|
|
|
Table 11.1: WQL logical operators
Comparison operators may be used with the Filter
and Query
parameters.
The examples in the following table are based on the following command:
Get-CimInstance Win32_Process -Filter "<Filter>"
Description |
Operator |
Example |
Equal to |
|
|
Not equal to |
|
|
Greater than |
|
|
Greater than or equal to |
|
|
Less than |
|
|
Less than or equal to |
|
|
Is |
|
|
Like |
|
|
Table 11.2: WQL comparison operators
When building a WQL query, string values must be quoted; numeric and Boolean values do not need quotes.
As the filter is also a string, this often means nesting quotes within one another. The following techniques may be used to avoid needing to use PowerShell's escape character.
For filters or queries containing fixed string values, use either of the following styles. Use single quotes outside and double quotes inside:
Get-CimInstance Win32_Process -Filter 'Name="pwsh.exe"'
Alternatively, use double quotes outside and single quotes inside:
Get-CimInstance Win32_Process -Filter "Name='pwsh.exe'"
For filters or queries containing PowerShell variables or sub-expressions, use double quotes around the filter. Variables within single-quoted strings will not expand:
Get-CimInstance Win32_Process -Filter "ProcessId=$PID"
Get-CimInstance Win32_Process -Filter "ExecutablePath LIKE '$($pshome -replace '', '')%'"
Regex recap
The regular expression ''
represents a single literal ''
, as the backslash is normally the escape character. Each ''
in the pshome
path is replaced with ''
to account for WQL using ''
as an escape character as well.
In the previous example, the filter extends over a single line. If a filter is long, or contains several conditions, consider using the format operator to compose the filter string:
$params = @{
ClassName = 'Win32_Process'
Filter = "ExecutablePath LIKE '{0}%'" -f @(
$PSHOME -replace '', ''
)
}
Get-CimInstance @params
The format operator is used to add the escaped version of the path to the string.
The preceding command will show any instances of PowerShell that are running; for example:
ProcessId Name HandleCount WorkingSetSize VirtualSize
--------- ---- ----------- -------------- -----------
14868 pwsh.exe 1114 243924992 2204239736832
10952 pwsh.exe 1513 180326400 2204260892672
WQL filters are especially useful when working with CIM classes that contain many instances.
WMI classes often have several different associated or related classes; for example, each instance of Win32_Process
has an associated class, CIM_DataFile
.
Associations between two classes are expressed by a third class. In the case of Win32_Process
and CIM_DataFile
, the relationship is expressed by the CIM_ProcessExecutable
class.
The relationship is defined by using the antecedent and dependent properties, as shown in the following example:
PS> Get-CimInstance CIM_ProcessExecutable |
>> Where-Object Dependent -match $PID |
>> Select-Object -First 1
Antecedent : CIM_DataFile (Name = "C:WINDOWSSystem32Windo...)
Dependent : Win32_Process (Handle = "11672")
BaseAddress : 2340462460928
GlobalProcessCount :
ModuleInstance : 4000251904
ProcessCount : 0
PSComputerName :
This CIM_ProcessExecutable
class does not need to be used directly; it is only used to express the relationship between two other classes.
A WMI path is required to find classes associated with an instance. The WMI object path uniquely identifies a specific instance of a WMI class.
The object path is made up of several components:
<Namespace>:<ClassName>.<KeyName>=<Value>
The namespace can be omitted if the class is under the default namespace, rootcimv2
.
You can discover the KeyName
for a given WMI class in several ways. In the case of Win32_Process
, the key name might be discovered by using any of the following methods.
It can be discovered by using the CIM cmdlets:
(Get-CimClass Win32_Process).CimClassProperties |
Where-Object { $_.Flags -band 'Key' }
It can be discovered by using the Microsoft Docs website, which provides descriptions of each property (and method) exposed by the class: https://docs.microsoft.com/windows/win32/cimwin32prov/win32-process.
Having identified a key, only the value remains to be found. In the case of Win32_Process
, key
(handle) has the same value as the process ID. The object path for the Win32_Process
instance associated with a running PowerShell console is, therefore, as follows:
rootcimv2:Win32_Process.Handle=$PID
The namespace does not need to be included if it uses the default, rootcimv2
; the object path can be shortened to the following:
Win32_Process.Handle=$PID
Get-CimInstance
and Get-WmiObject
do not retrieve an instance from an object path, but the Wmi
type accelerator can:
PS> [Wmi]"Win32_Process.Handle=$PID" | Select-Object Name, Handle
Name Handle
---- ------
pwsh.exe 11672
The preceding object is somewhat equivalent to using a filter for Handle
when querying Win32_Process
.
Queries using ASSOCIATORS OF
are used to find instances of classes that are related to a specific object.
The ASSOCIATORS OF
query may be used for any given object path; for example, using the preceding object path results in the following command:
Get-CimInstance -Query "ASSOCIATORS OF {Win32_Process.Handle=$PID}"
This query will return objects from three different classes: Win32_LogonSession
, Win32_ComputerSystem
, and CIM_DataFile
. The classes that are returned are shown in the following example:
PS> $params = @{
>> Query = "ASSOCIATORS OF {Win32_Process.Handle=$PID}"
>> }
PS> Get-CimInstance @params | Select-Object CimClass -Unique
CimClass
--------
root/cimv2:Win32_ComputerSystem
root/cimv2:Win32_LogonSession
root/cimv2:CIM_DataFile
The query can be refined to filter a specific resulting class; an example is as follows:
Get-CimInstance -Query "ASSOCIATORS OF {Win32_Process.Handle=$PID} WHERE ResultClass = CIM_DATAFILE"
ResultClass values must not be quoted
The value in the ResultClass
condition is deliberately not quoted.
The result of this operation is a long list of files that are used by the PowerShell process. A snippet of this is shown as follows:
PS> Get-CimInstance -Query "ASSOCIATORS OF {Win32_Process.Handle=$PID} WHERE ResultClass = CIM_DATAFILE" |
>> Select-Object Name
Name
----
C:Program FilesPowerShell7pwsh.exe
C:WINDOWSSYSTEM32
tdll.dll
C:WINDOWSSystem32KERNEL32.DLL
C:WINDOWSSystem32KERNELBASE.dll
C:WINDOWSSystem32USER32.dll
C:WINDOWSSystem32win32u.dll
While the older Get-WmiObject
command has not been continued into PowerShell 6, the WMI type accelerators remain.
The WMI cmdlets were removed in PowerShell 6 and are not going to be reinstated.
The following type accelerators remain and can still be used:
Wmi
: System.Management.ManagementObject
WmiClass
: System.Management.ManagementClass
WmiSearcher
: System.Management.ManagementObjectSearcher
When necessary, these accelerators may be used to simulate the functionality provided by the older WMI cmdlets.
Both the Wmi
and WmiClass
type accelerators can be written to use a remote computer by including the computer name. An example is as follows:
[Wmi]"\RemoteComputer
ootcimv2:Win32_Process.Handle=$PID"
[WmiClass]"\RemoteComputer
ootcimv2:Win32_Process"
You can use these classes in PowerShell 6 and higher.
You can use the type accelerator WmiSearcher
to execute queries and retrieve results:
([WmiSearcher]"SELECT * FROM Win32_Process").Get()
The returned object is identical to the object that would have been returned by the Get-WmiObject
command.
WMI instances retrieved using type accelerators do not convert date-time properties into the DateTime
type. Querying the Win32_Process
class for the creation date of a process returns the date-time property as a long string:
PS> $query = '
>> SELECT Name, CreationDate
>> FROM Win32_Process
>> WHERE ProcessId={0}
>> ' -f $PID
PS> ([WmiSearcher]$query).Get() | Select-Object Name, CreationDate
Name CreationDate
---- ------------
pwsh.exe 20200510090416.263973+060
The .NET namespace, System.Management
, includes a class called ManagementDateTimeConverter
, dedicated to converting date and time formats found in WMI. This method is added to WMI objects in PowerShell as a ConvertToDateTime
script method.
The string in the preceding example may be converted as follows:
$query = '
SELECT Name, CreationDate
FROM Win32_Process
WHERE ProcessId={0}
' -f $PID
([WmiSearcher]$query).Get() | Select-Object @(
'Name'
@{
Name = 'CreationDate'
Expression = {
$_.ConvertToDateTime($_.CreationDate)
}
}
)
WMI classes may be used via the WmiClass
accelerator.
An instance of a class may be created using the WmiClass
accelerator, as the following code shows:
[WmiClass]'Win32_Process'
The class describes the methods and properties that can be used.
Calling a method on an existing instance of an object found using WmiSearcher
is like using any other .NET method call.
The following example gets and restarts the "Print Spooler" service. The following operation requires administrative access:
$query = '
SELECT *
FROM Win32_SERVICE
WHERE DisplayName="Print Spooler"
'
$service = ([WmiSearcher]$query).Get()
$service.StopService() # Call the StopService method
$service.StartService() # Call the StartService method
The WMI class can be used to find the details of a method; for example, the Create
method of Win32_Share
, as follows:
PS> ([WmiClass]'Win32_Share').Methods['Create']
Name : Create
InParameters : System.Management.ManagementBaseObject
OutParameters : System.Management.ManagementBaseObject
Origin : Win32_Share
Qualifiers : {Constructor, Implemented, MappingStrings, Static}
When the Invoke-CimMethod
command accepts a Hashtable, methods invoked on a WMI object expect arguments to be passed in a specific order. The order is described in the documentation for the class, such as https://docs.microsoft.com/windows/win32/cimwin32prov/create-method-in-class-win32-share.
The documentation shows which arguments are mandatory (not optional) in the syntax element at the top.
Alternatively, the order arguments must be written like so:
PS> ([WmiClass]'Win32_Share').Create.OverloadDefinitions -split '(?<=,)'
System.Management.ManagementBaseObject Create(System.String Path,
System.String Name,
System.UInt32 Type,
System.UInt32 MaximumAllowed,
System.String Description,
System.String Password,
System.Management.ManagementObject#Win32_SecurityDescriptor Access)
To create a share, the argument list must contain an argument for Access
, then Description
, then MaximumAllowed
, and so on. If the argument is optional, it can be ignored, or a null value may be provided:
([WmiClass]'Win32_Share').Create(
'C:TempShare1', # Path
'Share2', # Name
0 # Type (Disk Drive)
)
The Description
argument follows the MaximumAllowed
argument. If Description
were to be set (but not MaximumAllowed
), a null value can be added for that argument:
([WmiClass]'Win32_Share').Create(
'C:TempShare1', # Path
'Share3', # Name
0, # Type (Disk Drive),
$null, # MaximumAllowed
'Description' # Description
)
ReturnValue
describes the result of the operation; a ReturnValue
of 0
indicates success. As this operation requires administrator privileges (run as administrator), a ReturnValue
of 2
is used to indicate that it was run without sufficient rights.
If the folder used in the previous example does not exist, ReturnValue
will be set to 24
.
A less well-known alternative is available compared to passing arguments in an array. You can pass arguments by setting the values of an object. The object is retrieved using the GetMethodParameters
method on a WMI class:
$class = [WmiClass]'Win32_Share'
$params = $class.GetMethodParameters('Create')
$params.Name = 'Share1'
$params.Path = 'C:TempShare1'
$params.Type = 0
$class.InvokeMethod('Create', $params)
Creating an object to represent the parameters has a clear advantage in that each property has a clear name, rather than being reliant on discovering and using positional arguments.
An instance of a WMI class can be created using the CreateInstance
method of the class. The following example creates an instance of Win32_Trustee
:
([WmiClass]'Win32_Trustee').CreateInstance()
Objects returned by WmiSearcher
have a GetRelated
method that can be used to find associated instances.
The GetRelated
method accepts arguments that can be used to filter the results. The first argument, relatedClass
, is used to limit the instances that are returned to specific classes, as shown here:
([WmiSearcher]'SELECT * FROM Win32_LogonSession').Get() | ForEach-Object {
[PSCustomObject]@{
LogonName = $_.GetRelated('Win32_Account').Caption
SessionStarted = [System.Management.ManagementDateTimeConverter]::ToDateTime(
$_.StartTime
)
}
}
Working with permissions in WMI is more difficult than in .NET as the values in use are not given friendly names. However, the .NET classes can still be used, even if not quite as intended.
The following working examples demonstrate configuring the permissions.
Get-Acl
and Set-Acl
are fantastic tools for working with filesystem permissions, or permissions under other providers. However, these commands cannot be used to affect SMB share permissions.
The SmbShare module
The SmbShare
module has commands that affect share permissions. This example uses the older WMI classes to modify permissions. It might be used if the SmbShare
module cannot be.
The Get-SmbShareAccess
command might be used to verify the outcome of this example.
The following operations require administrative privileges; use PowerShell as an administrator if you're attempting to use these examples.
The following snippet creates a directory and shares that directory:
$path = 'C:TempWmiPermissions'
New-Item $path -ItemType Directory
$params = @{
ClassName = 'Win32_Share'
MethodName = 'Create'
Arguments = @{
Name = 'WmiPerms'
Path = $path
Type = [UInt32]0
}
}
Invoke-CimMethod @params
The Create
method used here will fail if the argument for Type
is not correctly defined as a UInt32
value. PowerShell will set the type to Int32
if the value of 0
is used without the cast.
The requirement for UInt32
, in this case, may be viewed by exploring the parameters required for the method:
PS> (Get-CimClass Win32_Share).CimClassMethods['Create'].Parameters |
>> Where-Object Name -eq Type
Name CimType Qualifiers ReferenceClassName
---- ------- ---------- ------------------
Type UInt32 {ID, In, MappingStrings}
Now that the share exists, you can retrieve the security descriptor assigned to the share.
When Get-Acl
is used, the object that it gets is a security descriptor. The security descriptor includes a set of control information (ownership and so on), along with the discretionary and system access control lists.
The WMI class Win32_LogicalShareSecuritySetting
is used to represent the security for each of the shares on a computer:
$params = @{
ClassName = 'Win32_LogicalShareSecuritySetting'
Filter = "Name='WmiPerms'"
}
$security = Get-CimInstance @params
The returned object has a limited number of properties:
Caption : Security settings of WmiPerms
Description : Security settings of WmiPerms
SettingID :
ControlFlags : 32772
Name : WmiPerms
PSComputerName :
This instance is important as it is required to use the GetSecurityDescriptor
method:
$return = $security | Invoke-CimMethod -MethodName GetSecurityDescriptor
$aclObject = $return.Descriptor
The security descriptor held in the aclObject
variable is different from the result returned by Get-Acl
:
PS> $aclObject
ControlFlags : 32772
DACL : {Win32_ACE}
Group :
Owner :
SACL :
TIME_CREATED :
PSComputerName :
The ControlFlags
value states that a DACL is present, and that the security descriptor information is stored in a contiguous block of memory. These values are described in Microsoft Docs: https://docs.microsoft.com/windows/win32/secauthz/security-descriptor-control.
The DACL
, or discretionary access control list, is used to describe the permission levels for each security principal (a user, group, or computer account). Each entry in this list is an instance of Win32_ACE
:
PS> $aclObject.DACL
AccessMask : 1179817
AceFlags : 0
AceType : 0
GuidInheritedObjectType :
GuidObjectType :
TIME_CREATED :
Trustee : Win32_Trustee
PSComputerName :
The Win32_ACE
object has a Trustee
property that holds the Name
, Domain
, and SID
properties of the security principal (in this case, the Everyone
principal):
PS> $aclObject.DACL.Trustee
Domain :
Name : Everyone
SID : {1, 1, 0, 0...}
SidLength : 12
SIDString : S-1-1-0
TIME_CREATED :
PSComputerName :
AceFlags
describes how an ACE is to be inherited. As this is a share, the AceFlags
property will always be 0
. Nothing can, or will, inherit this entry; .NET can be used to confirm this:
PS> [System.Security.AccessControl.AceFlags]0
None
AceType
is either AccessAllowed
(0
) or AccessDenied
(1
). Again, .NET can be used to confirm this:
PS> [System.Security.AccessControl.AceType]0
AccessAllowed
Finally, the AccessMask
property can be converted into a meaningful value with .NET as well. The access rights that can be granted on a share are a subset of those that might be assigned to a file or directory:
PS> [System.Security.AccessControl.FileSystemRights]1179817
ReadAndExecute, Synchronize
Putting this together, the entries in a shared DACL
can be made much easier to understand:
using namespace System.Security.AccessControl
$aclObject.DACL | ForEach-Object {
[PSCustomObject]@{
Rights = [FileSystemRights]$_.AccessMask
Type = [AceType]$_.AceType
Flags = [AceFlags]$_.AceFlags
Identity = $_.Trustee.Name
}
}
In the preceding example, the domain of the trustee is ignored. If the trustee is something other than Everyone
, the domain should be included.
To add an Access Control Entry (ACE) to an existing list, you must create a Win32_ACE
. Creating an ACE requires a Win32_Trustee
. The following trustee
is created from the current user:
$trustee = New-CimInstance (Get-CimClass Win32_Trustee) -ClientOnly
$trustee.Domain = $env:USERDOMAIN
$trustee.Name = $env:USERNAME
SID
does not need to be set on the trustee
object, but if the security principal is invalid, attempting to apply the change to security will fail.
Then, you can create Win32_ACE
. The following ACE grants full control of the share to trustee
:
using namespace System.Security.AccessControl
$ace = New-CimInstance (Get-CimClass Win32_ACE) -ClientOnly
$ace.AccessMask = [UInt32][FileSystemRights]'FullControl'
$ace.AceType = [UInt32][AceType]'AccessAllowed'
$ace.AceFlags = [UInt32]0
$ace.Trustee = $trustee
The ACE can be added to the DACL
using the +=
operator:
$aclObject.DACL += $ace
Once the ACL has been changed, the modified security descriptor must be set. The instance returned by Win32_LogicalShareSecuritySetting
contains a SetSecurityDescriptor
method:
$params = @{
MethodName = 'SetSecurityDescriptor'
Arguments = @{
Descriptor = $aclObject
}
}
$security | Invoke-CimMethod @params
A return value of 0
indicates that the change to the ACL has been successfully applied. You can view this change by looking at the properties of the share in File Explorer, as shown in the following example:
Figure 11.1: Modified share permissions
In early versions of PowerShell (and earlier versions of Windows), WMI was the only option for changing configurations like share permissions. The SmbShare
module is an easier-to-use set of commands than the convoluted process used here. It is also able to show the assigned share permissions:
PS> Get-SmbShareAccess -Name WmiPerms
Name ScopeName AccountName AccessControlType AccessRight
---- --------- ----------- ----------------- -----------
WmiPerms * Everyone Allow Read
WmiPerms * TITANChris Allow Full
While share permissions have better commands available, the preceding process provides a good basis for modifying any security presented via WMI. One of these is permissions defined on namespaces in WMI.
Getting and setting WMI security in PowerShell uses the same approach as share security. WMI permissions might be set using wmimgmt.msc
if the GUI is used. The content of the DACL
differs slightly.
The __SystemSecurity
class is used to access the security descriptor. Each WMI namespace has its own instance of the __SystemSecurity
class; an example is as follows:
Get-CimClass __SystemSecurity -Namespace root
Get-CimClass __SystemSecurity -Namespace rootcimv2
The security descriptor for a given namespace can be retrieved from the __SystemSecurity
class. By default, administrator privileges are required to get the security descriptor:
$security = Get-CimInstance __SystemSecurity -Namespace rootcimv2
$return = $security | Invoke-CimMethod -MethodName GetSecurityDescriptor
$aclObject = $return.Descriptor
The access mask defines which rights are assigned by the ACE. The values of an access mask in the DACL
are documented as access right constants in Microsoft Docs: https://docs.microsoft.com/windows/win32/wmisdk/namespace-access-rights-constants.
The standard access rights, ReadSecurity
and WriteSecurity
, are also relevant. The access mask is a composite of the values listed here:
EnableAccount
: 1
ExecuteMethods
: 2
FullWrite
: 4
PartialWrite
: 8
WriteProvider
: 16
RemoteEnable
: 32
ReadSecurity
: 131072
WriteSecurity
: 262144
You can use these values to add a new ACE and set the security descriptor in the same way, just like when setting permissions on a share in the previous section.
The WMI methods used in this subsection also allow a security descriptor to be exported to and imported from a Security Descriptor Definition Language (SDDL) string.
SDDL is used to describe the content of a security descriptor as a string. The string format is described in Microsoft Docs: https://docs.microsoft.com/windows/win32/secauthz/security-descriptor-string-format.
A security descriptor returned by Get-Acl
has a method that can convert the entire security descriptor into a string, as follows:
PS> (Get-Acl C:).GetSecurityDescriptorSddlForm('All')
O:S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464G:S-1-5-80-956008885-3418522649-1831038044-1853292631-2271478464D:PAI(A;;LC;;;AU)(A;OICIIO;SDGXGWGR;;;AU)(A;;FA;
;;SY)(A;OICIIO;GA;;;SY)(A;OICIIO;GA;;;BA)(A;;FA;;;BA)(A;OICI;0x1200a9;;;BU)
A security descriptor defined using SDDL
can also be imported. If the sddlString
variable is assumed to hold a valid security descriptor, then you can use the following command:
$acl = Get-Acl C:
$acl.SetSecurityDescriptorSddlForm($sddlString)
The imported security descriptor will not apply to the directory until Set-Acl
is used.
WMI security descriptors can be converted to and from different formats, including SDDL
. WMI has a specialized class for this: Win32_SecurityDescriptorHelper
. The methods for this class are shown here:
PS> (Get-CimClass Win32_SecurityDescriptorHelper).CimClassMethods
Name ReturnType Parameters Qualifiers
---- ---------- ---------- ----------
Win32SDToSDDL UInt32 {Descriptor, SDDL} {implemented, static}
Win32SDToBinarySD UInt32 {Descriptor, BinarySD} {implemented, static}
SDDLToWin32SD UInt32 {SDDL, Descriptor} {implemented, static}
SDDLToBinarySD UInt32 {SDDL, BinarySD} {implemented, static}
BinarySDToWin32SD UInt32 {BinarySD, Descriptor} {implemented, static}
BinarySDToSDDL UInt32 {BinarySD, SDDL} {implemented, static}
You might want to convert a WMI security descriptor into SDDL
to create a backup before it makes a change, as follows:
$security = Get-CimInstance __SystemSecurity -Namespace rootcimv2
$return = $security | Invoke-CimMethod -MethodName GetSecurityDescriptor
$aclObject = $return.Descriptor
$params = @{
ClassName = 'Win32_SecurityDescriptorHelper'
MethodName = 'Win32SDToSDDL'
Arguments = @{
Descriptor = $aclObject
}
}
$return = Invoke-CimMethod @params
If the operation succeeds (that is, if ReturnValue
is 0
), the security descriptor in SDDL
form will be available:
PS> $return.SDDL
O:BAG:BAD:AR(A;CI;CCDCWP;;;S-1-5-21-2114566378-1333126016-908539190-1001)(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A;CI;CCDCRP;;;AU)
You can import a security descriptor expressed as an SDDL
string:
$params = @{
ClassName = 'Win32_SecurityDescriptorHelper'
MethodName = 'SDDLToWin32SD'
Arguments = @{
SDDL = 'O:BAG:BAD:AR(A;CI;CCDCWP;;;S-1-5-21-2114566378-1333126016-908539190-1001)(A;CI;CCDCLCSWRPWPRCWD;;;BA)(A;CI;CCDCRP;;;NS)(A;CI;CCDCRP;;;LS)(A;CI;CCDCRP;;;AU)'
}
}
$return = Invoke-CimMethod @params
$aclObject = $return.Descriptor
If ReturnValue
is 0
, the aclObject
variable will contain the imported security descriptor:
PS> $aclObject
ControlFlags : 33028
DACL : {Win32_ACE, Win32_ACE, Win32_ACE, Win32_ACE...}
Group : Win32_Trustee
Owner : Win32_Trustee
SACL :
TIME_CREATED :
PSComputerName :
The ACL object, like much of WMI, is not particularly descriptive. The raw information is there, and the descriptor can be set. Reading the descriptor requires work, as you must translate all the values into human-readable equivalents.
This chapter explored working with WMI classes, the different available commands, and the WMI Query Language. You used CIM cmdlets as a means of working with WMI.
Since Get-WmiObject
has been removed from PowerShell, the WMI type accelerators were explored as an alternative means of working with WMI. This may be useful for the few rare classes that do not work using the CIM cmdlets. You used getting and setting permissions with WMI while using shared security and WMI security as examples.
Chapter 12, Working with HTML, XML, and JSON, will explore working with and generating and consuming data from a variety of different text-based formats.
3.138.175.180