The WQL Query Syntax used by WMI is an adaption of the common SQL query that you may know from databases. With WQL, you can select the WMI class instances you want to retrieve.
Let’s explore WQL by looking at Get-CimInstance
. A typical query uses the parameters -ClassName, -Filter, and -Property.
Querying WMI with individual parameters
The code below looks for all instances of the class Win32_Service (Windows Services) that are not running but have a StartMode set to Auto.
# WMI class name to query
$className = 'Win32_Service'
# properties to return
$properties = 'Name', 'StartName', 'Description', 'PathName', 'ExitCode'
# conditions that must be met
$filter = 'Started = FALSE AND StartMode = "Auto"'
# perform query...
Get-CimInstance -ClassName $className -Filter $filter -Property $properties |
# keep only the requested properties
# (because all other properties are empty but still present)
Select-Object -Property $properties
Note that
Get-CimInstance
returns only the property values specified in -Property. Any property not specified in -Property is empty but still present.To remove all unwanted properties, the result needs to be piped to
Select-Object
.
The same can be achieved with a single WQL Query.
Querying WMI with WQL
WQL combines the values of -ClassName, -Filter, and -Property in a SQL-like syntax:
Select Name, StartName, Description, PathName, ExitCode
from Win32_Service
Where Started = FALSE AND StartMode = "Auto"
Here is the script:
# WMI class name to query
$className = 'Win32_Service'
# properties to return
$properties = 'Name', 'StartName', 'Description', 'PathName', 'ExitCode'
# conditions that must be met
$filter = 'Started = FALSE AND StartMode = "Auto"'
# combine everything in a WQL query:
$wql = "Select $($Properties -join ', ') from $className Where $filter"
# perform query...
Get-CimInstance -Query $wql |
# keep only the requested properties
# (because all other properties are empty but still present)
Select-Object -Property $properties
# return WQL used:
Write-Warning $wql
Finding running services that were manually started
$wql = 'SELECT * FROM _InstanceModificationEvent within 5 where Target Instance isa "Win32Service" AND TargetInstance.State="Stopped"'
# perform query...
Get-CimInstance -Query $wql
Data Types
WQL supports all WMI Datatypes. Note that string and char types need to be quoted. All numeric types must not be quoted.
Filtering String Values
String values must be quoted. Below code lists all instances of Win32_Service with a StartMode set to Auto. Since this property is a string data type, Auto needs to be put in quotes:
Get-CimInstance -Query 'Select * From Win32_Service Where StartMode = "Auto"'
You can use the WQL Escape Character (“\”) to use double-quotes inside double-quoted strings:
Get-CimInstance -Query "Select * From Win32_Service Where StartMode = \"Auto\""
IMPORTANT: always make sure you escape the escape character when you want to use it literally, i.e. in paths:
Get-CimInstance -Query 'Select * From Win32_AutochkSetting Where SettingID LIKE "%\\Device\\Harddisk0\\Partition3"'
Filtering Numeric Values
Numeric values must not be quoted.
Boolean values are considered numeric values and represented by the key words TRUE and FALSE.
Below code identifies suspicious services: it finds all services that are set to start automatically but are not running, with a return code other than 0 (which typically indicates some sort of error condition).
- StartMode is a string value and must be quoted.
- Started is a boolean value and must not be quoted. Either use the keywords TRUE and FALSE, or the numeric alternatives 1 and 0.
- ExitCode is a numeric value and must not be quoted.
# wql
$wql = 'Select Name, StartName, PathName from Win32_Service Where Started=FALSE AND StartMode="Auto" AND ExitCode<>0'
# perform query...
Get-CimInstance -Query $wql |
# remove empty properties:
Select-Object -Property Name, StartName, PathName
If the code below does not yield any results, then that is actually a good thing: no suspicious services were found.
Replace
ExitCode<>0
withExitCode=0
in this case to see what the output would look like.
Operators
WQL is not PowerShell code and uses its own set of operators:
WQL Operator | PowerShell | Description |
---|---|---|
= | -eq | equals |
< | -lt | lower than |
> | -gt | greater than |
<= | -le | lower equals |
>= | -ge | greater equals |
!= | -ne | not equal |
<> | -ne | not equal |
LIKE | -like | equals (including wildcards) the WQL wildcard character is %. |
IS NULL | -eq $null | is undefined |
IS NOT NULL | -ne $null | is defined |
ISA | -is | is of type |
Supported Wildcards
The operator LIKE supports the following wildcards:
Wildcard | Example | Description |
---|---|---|
% | %test% “test” anywhere in a string |
string of zero or more characters |
_ | “Windows__“ any text starting with Windows, followed by exactly two characters |
any one character. To use “_” literally, enclose it in square brackets |
[] | [a-f0-9] a hex number |
Character in a range [a-f] or a list of characters [aix]. The order of characters does not matter. |
^[] | ^[a]% any text that does not start with a |
Characters not in a range |
Logical Operators
To combine comparisons, WQL supports case-insensitive logical operators:
WQL Operator | PowerShell | Description |
---|---|---|
And | -and | both expressions must be met |
Or | -or | either one expression must be met |
WQL supports parens to group conditions. While not always necessary, parens make expressions more readable and easier to understand.
Key Words
WQL supports the following case-insensitive default key words that work very similar to SQL in database queries:
Keyword | Description |
---|---|
Select | Properties to return. CIM Cmdlets will still return all properties, but properties not specified by Select are empty. |
From | WMI Class name to query |
Where | Condition to be met. Use any WMI Class property with any of the supported WQL Operators. If the condition evaluates to true, the class instance is returned. |
WQL Example: Installed Hotfixes starting with KB45
Returns all installed Hotfixes with an ID starting with KB45.
Get-CimInstance -Query 'Select InstalledOn, HotFixId, Description, InstalledBy From Win32_QuickFixEngineering Where HotfixId LIKE "KB45%"' |
Select-Object -Property InstalledOn, HotFixId, Description, InstalledBy
If you omit
Select-Object
, you get the same information but a lot of empty properties.Win32_QuickFixEngineering basically returns the same information that you get from
Get-Hotfix
, except with WMI you can filter with wildcards and access remote systems.
In addition, WQL supports keywords to query class relationships. You can identify ancestors and child inheritance as well as relationships:
Keyword | Description |
---|---|
Group | Groups events to simplify event handling: when a group of events fires within a certain time frame, this is treated as one event |
Having | Number of events until an event is fired |
Within | Used for event subscribers and specifies the time intervals (in seconds) to monitor for changes. The shorter the interval, the quicker will WMI respond to property changes, but the more CPU load will be created. |
Finally, there are specific keywords for event handling:
Keyword | Description |
---|---|
Associators Of | Returns associated classes |
References Of | Returns inherited classes |
Limitations with Arrays
WQL is a very powerful query language but has limited support for Array data types. It misses operators to check for individual array elements or whether an array is empty.
Example for limitations with arrays
The WMI Class Win32_NetworkAdapterConfiguration represents the configuration settings of network adapters. It includes a wide range of adapters including pseudo network adapters:
$wql = 'SELECT Caption, IPAddress FROM Win32_NetworkAdapterConfiguration'
Get-CimInstance -Query $wql |
Select-Object -Property Caption, IPAddress
Array Content can not be Filtered
To filter out instances with no assigned IP address, you would have to check whether the property IPAddress is empty.
Since IPAddress is an array (a network adapter can have more than one assigned IP address), and since WQL Operators do not support array data types, this query fails:
$wql = 'SELECT Caption, IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPAddress=NULL'
Get-CimInstance -Query $wql |
Select-Object -Property Caption, IPAddress
Workaround
To work around this limitation, try and identify a non-array property that describes the condition you want to use for filtering. As it turns out, the property IPEnabled is set to TRUE whenever at least one IP address is assigned:
$wql = 'SELECT IPAddress FROM Win32_NetworkAdapterConfiguration WHERE IPEnabled=TRUE'
Get-CimInstance -Query $wql |
Select-Object -Property Caption, IPAddress
Alternatives
If you can’t get queries to work, you can always resort to PowerShell and client-side filtering:
$wql = 'SELECT IPAddress FROM Win32_NetworkAdapterConfiguration'
Get-CimInstance -Query $wql |
# filter out all instances where "IPAddress" is empty:
Where-Object IPAddress |
Select-Object -Property Caption, IPAddress
Client-side filtering is less effective and uses more network bandwidth when querying remote machines.
Over time, PowerShell and the Windows operating system started to natively support many of the areas originally covered by WMI. If you are running at least Windows 8/Server 2012, to list assigned IP addresses, all you probably need is something like this:
Get-NetIPConfiguration | Select-Object -Property InterfaceAlias, IPv4Address
Event Queries
WQL statements can be used to define temporary and permanent WMI Event Subscribers: the query defines the type of event you are listening to, and what event conditions you want to monitor.
Waiting For Events
There are three generic WMI Classes that can trigger events:
Class | Description |
---|---|
__InstanceCreationEvent | fires whenever a new WMI Instance is created, i.e. a new process was launched |
__InstanceModificationEvent | fires whenever an existing WMI Instance changed, i.e. a running service has stopped, or a notebook battery was disconnected from the charger. |
__InstanceDeletionEvent | fires whenever an existing WMI Instance is removed, i.e. a process exited. |
While the three classes above implement a generic event wrapper that can be used with any WMI Class, specific event-enabled WMI Classes exist such as RegistryTreeChangeEvent, RegistryKeyChangeEvent, RegistryValueChangeEvent, Win32_ComputerSystemEvent, Win32_ComputerShutdownEvent, Win32_IPv4RouteTableEvent, Win32_ProcessStartTrace, Win32_ProcessStopTrace, Win32_SystemConfigurationChangeEvent, or Win32_PowerManagementEvent (to mention a few) that you can use in place of any of the three generic event-enabled WMI Classes above.
Waiting for New Process Launch
The PowerShell code below waits for new processes to be launched, and responds within 5 seconds. The names and start times of all processes are written to the PowerShell console untol you press CTRL+C
.
# respond within a maximum delay of 5 seconds to __InstanceCreationEvents
# if the instance that triggered this is of type "Win32_Process"
# (a newly launched process)
$wql = 'Select * From __InstanceCreationEvent within 5 where TargetInstance isa "Win32_Process"'
Write-Warning "Monitoring enabled. Waiting for process starts."
Write-Warning "Press CTRL+C to abort monitoring."
# important: submit a source identifier that *you* know
# so you can later clean up and remove event subscribers
$id = (New-Guid).Guid
# register the WMI event. A PowerShell background job is returned:
$job = Register-WmiEvent -Query $wql -SourceIdentifier $id
# wait endless for events. Embed this in a try...finally block
# so you can respond when the user aborts the script via CTRL+C,
# and clean up in the finally block:
try
{
do
{
# Wait-Event waits for the event to fire, or a maximum of 1 second,
# whichever comes first. This allows the user to abort the script every second
# when PowerShell temporarily regains control:
$eventData = Wait-Event -Timeout 1
# $eventData is either $null (when no event occured within the timeout period)
# or returns the information about the event that triggered
if ($eventData -ne $null)
{
# for __InstanceModificationEvents, there is both a
# NewEvent and OldEvent property that can be used to compare
# the before/after configuration of an object.
$eventData.SourceEventArgs.NewEvent.TargetInstance |
Select-Object -Property TimeGenerated, Name, ProcessId, CommandLine
# Important: you MUST remove the event from the queue, or else it
# will be returned by the next call to Wait-Event again and again:
$eventData | Remove-Event
}
# run forever (until the user aborts the script via CTRL+C or closes PowerShell)
} while ($true)
}
finally
{
# runs when the script ends. Remove the WMI event handler here:
# unregister event:
Get-EventSubscriber -SourceIdentifier $id | Unregister-Event
# remove background job:
Remove-Job -Name $id
Write-Warning "Monitoring disabled."
}
Aggregating Events
Events can be aggregated for more efficiency. Instead of handling every single event, all events of a given type are grouped over a certain period of time, and when there were enough events to meet a threshold, one single aggregate event is fired.
WQL uses the keywords GROUP (to define the time frame) and HAVING (to define the minimum number of events threshold).
Combining Events with HAVING and GROUP
Events can be grouped and treated as one event with a WQL Query like this:
SELECT * FROM EventClass [WHERE property = value]
GROUP WITHIN interval [BY property_list]
HAVING NumberOfEvents operator constant
A real-life example could look like this:
$wql = 'SELECT * FROM __InstanceCreationEvent
WHERE TargetInstance ISA "Win32_NTLogEvent"
GROUP WITHIN 600 BY TargetInstance.SourceName
HAVING NumberOfEvents > 25'
This query would group all events of type __InstanceCreationEvent of type Win32_NTLogEvent within 600 seconds (10 minutes) after the first event was received, and would trigger an own event only when there have been more than 25 events received.
In other words, rather than firing an event for every new eventlog entry, this query would aggregate events for 10 minutes, then look whether there was a critical mass of events, and only then fire an event.