WQL ASSOCIATORS OF Queries in PowerShell: From WMIC to CimCmdlets
The ASSOCIATORS OF
statement in SQL for WMI (WQL) retrieves objects related to a specified source instance, where the source and related objects belong to different classes and are connected through an association class. For example, Win32_LogicalDiskToPartition
is an association class that links Win32_DiskPartition
and Win32_LogicalDisk
. Using this association, you can list the disk partitions related to a specific logical disk (or vice versa). The object paths used to identify the source instances include the DeviceID
property, which is intuitive and easy to understand. For instance, DeviceID
values are in the form of C:
for a logical disk or volume, or Disk #1, Partition #1
for a disk partition, making them more human-readable.
In contrast, the Storage Management Provider (SMP) equivalent classes — MSFT_Partition
, MSFT_Volume
, and their association class MSFT_PartitionToVolume
— do not expose user-friendly object path values. These identifiers are not as intuitive as those in their CIMv2 counterparts. However, a practical workaround is to use the Get-CimAssociatedInstance
cmdlet. In this post, I'll demonstrate how this cmdlet simplifies working with such association classes.
1. The Object Path
The object path of a WMI/CIM class instance is a string that serves as its identifier. This path is essential when writing an ASSOCIATORS OF
statement in WQL, as it specifies the exact instance for which associated objects are to be retrieved.
In this section, I'll break down the components that make up an object path and demonstrate how to use it to retrieve associated instances of a given class instance. Additionally, I'll compare this method to the approach using the now-deprecated WMIC tool, which previously allowed retrieving associated instances without requiring a properly formatted object path — offering a simpler but now outdated alternative.
1.1 The Key Qualifier
On the official Microsoft Learn page for the ASSOCIATORS OF
statement, the examples may be misleading — especially for developers familiar with WMIC, who are now transitioning to PowerShell due to the deprecation of WMIC.
For instance, the query:
ASSOCIATORS OF {Win32_LogicalDisk.DeviceID="C:"}
could incorrectly suggest that other properties like Name
or Caption
can be used in place of DeviceID
since they share the same value. In reality, the object path must be constructed using the key qualifier defined in the class (in this case, DeviceID
). Using non-key properties either as identifiers or as general filters in the ASSOCIATORS OF
clause will result in errors in PowerShell.
# This command returns the associated instances as expected
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk.DeviceID='C:'}"
# These will throw errors because they use non-key properties
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk.Name='C:'}"
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk.VolumeSerialNumber='93C988A5'}"
Assuming that 93C988A5
is the serial number of the C:
drive.
For older versions of PowerShell, Get-WmiObject
can be used in place of Get-CimInstance
.
The working PowerShell command above is functionally equivalent to the following WMIC command:
wmic LogicalDisk WHERE "DeviceID='C:'" ASSOC
LogicalDisk
is the alias for Win32_LogicalDisk
.
However, unlike PowerShell, WMIC tolerates the use of non-key properties in the WHERE
clause of ASSOC
queries. The following WMIC commands also work, even though they use properties other than the key qualifier:
wmic LogicalDisk WHERE "Name='C:'" ASSOC
wmic LogicalDisk WHERE "VolumeSerialNumber='93C988A5'" ASSOC
Retrieving the Key Qualifier
To determine which property serves as the key qualifier for a given WMI class, you can inspect the class metadata using either Get-CimClass
from the CimCmdlets
module for a modern approach, or Get-WmiObject
with the switch parameter -List
from the Microsoft.PowerShell.Management
module for older PowerShell versions.
filter Get-KeyQualifier {
$_ |
Select-Object -ExpandProperty $args[0] |
Where-Object { $_.Qualifiers.Name -contains 'key' } |
Select-Object -ExpandProperty Name
}
# Using Get-WmiObject
Get-WmiObject Win32_LogicalDisk -List |
Get-KeyQualifier Properties
# Using Get-CimClass
Get-CimClass Win32_LogicalDisk |
Get-KeyQualifier CimClassProperties
Both commands return:
DeviceID
Multiple Key Qualifiers
A WMI class can define composite key qualifiers, meaning multiple properties are required to uniquely identify an instance.
For example, the Win32_UserAccount
class uses both the Domain
and Name
properties as key qualifiers:
PS > Get-CimClass Win32_UserAccount |
Get-KeyQualifier CimClassProperties
Domain
Name
When constructing a WMI object path for such classes, both key properties must be included. The keys order does not matter.
For example, the two queries below are functionally equivalent and both retrieve instances associated with the built-in Guest
account on the current host:
Get-CimInstance -Query "ASSOCIATORS OF {Win32_UserAccount.Name='Guest',Domain='$(hostname)'}"
Get-CimInstance -Query "ASSOCIATORS OF {Win32_UserAccount.Domain='$(hostname)',Name='Guest'}"
For WMI classes with composite keys, omitting the key property names in the object path is invalid and confusing. By contrast, for classes with a single key qualifier, omitting the key name is allowed.
For example:
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk='C:'}"
1.2 The Full Object Path
An important detail to understand when working with WMI/CIM queries is to identify the full object path. The following PowerShell and WMIC commands:
wmic "/NODE:'$Env:COMPUTERNAME'" /NAMESPACE:\\ROOT\CIMV2 PATH Win32_LogicalDisk WHERE "DeviceID='C:'" ASSOC
Get-CimInstance -ComputerName $Env:COMPUTERNAME -Namespace ROOT\CIMV2 -Query "ASSOCIATORS OF {\\$Env:COMPUTERNAME\ROOT\CIMV2:Win32_LogicalDisk.DeviceID='C:'}"
Get-CimInstance -ComputerName $Env:COMPUTERNAME -Namespace root\Microsoft\Windows\Storage -Query "ASSOCIATORS OF {\\$Env:COMPUTERNAME\root\Microsoft\Windows\Storage:MSFT_Volume.ObjectId=`"$volumeObjectId`"}"
The variable $volumeObjectId
is computed using methods described in later sections.
are equivalent to:
wmic PATH Win32_LogicalDisk WHERE "DeviceID='C:'" ASSOC
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk='C:'}"
Get-CimInstance -Namespace root\Microsoft\Windows\Storage -Query "ASSOCIATORS OF {MSFT_Volume=`"$volumeObjectId`"}"
The full path can often be shortened because the default WMI/CIM namespace is \\root\cimv2
(the namespace specified in the registry key HKEY_LOCAL_MACHINE\
) and the local computer is used by default if no hostname is specified. the name of the local computer can be retrieved via the hostname.exe
command-line tool or the COMPUTERNAME
environment variable. When working with remote systems, you can replace it with the appropriate machine name.
The string \\$Env:COMPUTERNAME\
represents the full object path of the specified instance of Win32_LogicalDisk
.
The Object Path Format
The general structure of a WMI object path is:
\\<host name>\<namespace>\<class name>.<key qualifier>=<value>
The path <namespace>:<class name>
is also referred to as the full class name.
The <Value>
must be properly escaped, especially if it contains special characters such as backslashes (\
) or quotation marks ("
). Use a backslash (\
) as the escape character within the string.
Retrieving the Full Object Path
Here are a few ways to retrieve the full object path for a WMI class instance — for example, Win32_LogicalDisk
, assuming the current host is named MyHost
:
# Using WMIC
function Get-WmiObjectPath {
end {
$input |
Where-Object { $_.StartsWith('__PATH') } |
Select-Object -First 1 |
ForEach-Object { $_ -split '=',2 } |
Select-Object -Last 1
}
}
wmic PATH Win32_LogicalDisk WHERE "DeviceID='C:'" ASSOC:List |
Get-WmiObjectPath
# Using Get-WmiObject
Get-WmiObject -Query 'SELECT __PATH FROM Win32_LogicalDisk WHERE DeviceID="C:"' |
Select-Object -ExpandProperty __PATH
Get-WmiObject
is not available in PowerShell Core, though it remains useful for quickly accessing metadata like __PATH
.
Both methods return:
\\MyHost\ROOT\CIMV2:Win32_LogicalDisk.DeviceID="C:"
WMIC vs. Get-WmiObject
: A Caution with Encoded Entities
When using wmic
to retrieve object paths, be aware that special characters like ampersands (&
) may be encoded as XML entities (&
). This can cause issues when the path is reused in a WQL query without decoding.
For example, consider the MSFT_Partition
class (SMP equivalent of Win32_DiskPartition
). Its object path might contain &
, which will break a WQL query:
PS > $ErrorView = 'CategoryView'
PS > $objectPath1 = wmic /NAMESPACE:\\root\Microsoft\Windows\Storage PATH MSFT_Partition WHERE "DriveLetter='C'" ASSOC:List |
Get-WmiObjectPath
PS > Get-WmiObject -Query "ASSOCIATORS OF {$objectPath1}" -Namespace root\Microsoft\Windows\Storage
InvalidOperation: (:) [Get-WmiObject], ManagementException
The root cause:
PS > $objectPath.Contains('&')
True
Whereas, retrieving the object path using Get-WmiObject
avoids this problem:
PS > $objectPath2 = Get-WmiObject MSFT_Partition -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='C'" -Property __PATH |
Select-Object -ExpandProperty __PATH
PS > Get-WmiObject -Query "ASSOCIATORS OF {$objectPath2}" -Namespace root\Microsoft\Windows\Storage |
Select-Object -ExpandProperty __CLASS
MSFT_StorageSubSystem
MSFT_Disk
MSFT_Volume
You can verify the paths are the same after decoding:
PS > $objectPath1.Replace('&', '&') -eq $objectPath2
True
Using Get-CimInstance
(manually building the object path):
Because Get-CimInstance
does not expose the __PATH
property directly, you must construct the object path yourself. This makes Get-CimInstance
slightly more cumbersome than Get-WmiObject
, which exposes the __PATH
property directly. However, Get-CimInstance
is the recommended cmdlet to use, as Get-WmiObject
is not available in modern PowerShell environments.
This Get-ObjectPath
filter uses the Get-KeyQualifier
defined earlier to dynamically build the full object path for a CimInstance
:
AugmentedGetCimInstance.ps1
filter Get-ObjectPath {
$object = $_
$namespace,$classname = $object.CimClass -split ':'
$keyQualifiers = Get-CimClass $classname -Namespace $namespace |
Get-KeyQualifier CimClassProperties |
ForEach-Object {
$keyValue = $object.$_ -replace '\\','\\' -replace '"','\"'
"$_=`"$keyValue`""
}
$keyQualifiers = $keyQualifiers -join ','
$fullclassname = $object.CimClass -replace '/','\'
return "\\$Env:COMPUTERNAME\$fullclassname.$keyQualifiers"
}
The following version of Get-CimInstance
wraps the original CimCmdlets\Get-CimInstance
and augments each returned object with a computed __PATH
property:
AugmentedGetCimInstance.ps1
function Get-CimInstance {
[CmdletBinding(DefaultParameterSetName = 'ClassName')]
param(
[Parameter(ParameterSetName = 'Query', Position = 0)]
[string]$Query,
[Parameter(ParameterSetName = 'ClassName', Position = 0)]
[string]$ClassName,
[string]$Namespace = (Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\WBEM\Scripting" -Name "Default Namespace"),
[string]$ComputerName = $Env:COMPUTERNAME,
[string]$Filter,
[string[]]$Property,
[switch]$KeyOnly
)
$instances = CimCmdlets\Get-CimInstance @PSBoundParameters
foreach ($obj in $instances) {
# Augment each object (add __PATH note property)
$obj |
Add-Member -NotePropertyName '__PATH' -NotePropertyValue (
$obj | Get-ObjectPath
) -Force
}
return $instances
}
Example usage:
PS > . .\AugmentedGetCimInstance.ps1
# Get associated instances to the current process object
PS > $object = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID"
PS > Get-CimInstance -Query "ASSOCIATORS OF {$($object.__PATH)} WHERE ClassDefsOnly" |
Select -ExpandProperty CimClass
ROOT/CIMV2:Win32_ComputerSystem
ROOT/cimv2:Win32_LogonSession
ROOT/cimv2:CIM_DataFile
# Get associated instances to the Guest user account object
PS > $object = Get-CimInstance -ClassName Win32_UserAccount -Filter "Name='Guest'"
PS > Get-CimInstance -Query "ASSOCIATORS OF {$($object.__PATH)} WHERE ClassDefsOnly" |
Select -ExpandProperty CimClass
ROOT/CIMV2:Win32_ComputerSystem
ROOT/CIMV2:Win32_Desktop
ROOT/cimv2:Win32_SID
ROOT/CIMV2:Win32_Group
# Get associated instances to the C: drive object
PS > $object = Get-CimInstance -Namespace root\Microsoft\Windows\Storage -ClassName MSFT_Volume -Filter "DriveLetter='C'"
PS > Get-CimInstance -Query "ASSOCIATORS OF {$($object.__PATH)} WHERE ClassDefsOnly" -Namespace root\Microsoft\Windows\Storage |
Select -ExpandProperty CimClass
ROOT/Microsoft/Windows/Storage:MSFT_StorageNode
ROOT/Microsoft/Windows/Storage:MSFT_StorageSubSystem
ROOT/Microsoft/Windows/Storage:MSFT_FileShare
ROOT/Microsoft/Windows/Storage:MSFT_Partition
ROOT/Microsoft/Windows/Storage:MSFT_FileServer
2. WMIC vs Get-CimAssociatedInstance
In this section, I will demonstrate how to use Get-CimAssociatedInstance
to retrieve associated instances of a source WMI instance without needing to reference its key qualifier directly — similar to how WMIC operates.
When dealing with opaque or inconvenient key qualifiers, we've fallen back to WMIC, which allows querying using more readable properties, as mentioned in the previous section.
To illustrate this, consider the ObjectId
property of an MSFT_Volume
instance. Although it uniquely identifies the instance, its value is not user-friendly. Fortunately, MSFT_Volume
also exposes a more intuitive property: DriveLetter
. This single-character value (e.g., C
) corresponds to the actual drive letter assigned to the volume and is unique — similar to Win32_LogicalDisk
.
To find the associated MSFT_Partition
for the volume with drive letter C
, you can use the following approach with WMIC:
PS > $associatedInstance = wmic /NAMESPACE:\\root\Microsoft\Windows\Storage PATH MSFT_Volume WHERE "DriveLetter='C'" ASSOC:List /RESULTCLASS:MSFT_Partition
PS > $associatedInstance = $associatedInstance -match '^(PartitionNumber)|(DiskNumber)|(__CLASS)' -join [Environment]::NewLine
PS > $associatedInstance = ConvertFrom-StringData $associatedInstance
PS > [pscustomobject] $associatedInstance
DiskNumber PartitionNumber __CLASS
---------- --------------- -------
0 3 MSFT_Partition
As demonstrated, parsing WMIC output is not ideal since the result is just a string dump. We use ConvertFrom-StringData
(or similar workarounds) to convert the output into a usable object.
A cleaner and more PowerShell-native approach is to use the Get-CimAssociatedInstance
cmdlet in combination with Get-CimInstance
.
PS > Get-CimInstance MSFT_Volume -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='C'" |
Get-CimAssociatedInstance -ResultClassName MSFT_Partition |
Select-Object -Property DiskNumber,PartitionNumber,CimClass
DiskNumber PartitionNumber CimClass
---------- --------------- --------
0 3 root/Microsoft/Windows/Storage:MSFT_Partition
This method is functionally equivalent to the WMIC approach but returns full PowerShell objects, making it the preferred method in modern scripting — especially considering WMIC's deprecation.
3. Application to the Custom Drive Letter Assigner
In the Custom Drive Letter Assigner script, it was necessary to identify the disk partition associated with a volume, and vice versa. To handle this, I developed two functions:
:getPartitionName
- returns the partition name (e.g.,Disk #0, Partition #2
) for a given drive letter.:getDriveLetter
- performs the reverse operation, retrieving the drive letter for a given partition.
Both functions are implemented in the two scripts I created for the project: swap.bat
, where I experimented with CIMv2
WMI classes, and assign.bat
, the main script, which uses the more modern SMP
classes (underlying the PowerShell Storage
module).
swap.bat
As explained in this post, I used two straightforward ASSOCIATORS OF
WQL statements to establish the relationship between disk partitions and drive letters. The key qualifier DeviceID
is defined for both the Win32_DiskPartition
(which provides the partition name) and the Win32_LogicalDisk
(which provides the drive letter).
-- Used in :getPartitionName
ASSOCIATORS OF {Win32_LogicalDisk="<drive letter>"} WHERE ResultClass=Win32_DiskPartition
-- Used in :getDriveLetter
ASSOCIATORS OF {Win32_DiskPartition="<disk partition name>"} WHERE ResultClass=Win32_LogicalDisk
In the :getPartitionName
function, the drive letter is passed as the first argument. The corresponding WMIC command embedded in the batch script is:
wmic LogicalDisk WHERE DeviceID="%~1" ASSOC:List /RESULTCLASS:Win32_DiskPartition 2> nul | findstr /b "Name="
I later transitioned this logic into an embedded PowerShell command. The PowerShell equivalent (where $args[0]
is the drive letter) is:
Get-CimInstance -Query "ASSOCIATORS OF {Win32_LogicalDisk='$($args[0])'} WHERE ResultClass=Win32_DiskPartition" |
Select-Object -ExpandProperty Name
For the :getDriveLetter
function, the commands are analogous — the class names in the ASSOCIATORS OF
queries are simply swapped to reverse the lookup.
assign.bat
As explained in this post, I eventually adopted Get-CimAssociatedInstance
to return the partition information from an MSFT_Volume
instance. It's essentially a “hack” of the following query by replacing the <unused object id>
with the assigned volume drive letter, accessed via the DriveLetter
property:
-- Used in :getPartitionName
ASSOCIATORS OF {\\MyHost\root\Microsoft\Windows\Storage:MSFT_Volume="<unused object id>"} WHERE ResultClass=MSFT_Partition
The PowerShell equivalent later embedded in the batch script is:
Get-CimInstance MSFT_Volume -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='$($args[0])'" -Property ObjectId |
Get-CimAssociatedInstance -ResultClassName MSFT_Partition |
Format-Table DiskNumber,PartitionNumber
I particularly liked this solution because embedding this PowerShell command in a for /f
loop within a batch file is much simpler than using the ObjectId
key.
# Using Get-WmiObject
Get-WmiObject MSFT_Volume -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='$($args[0])'" -Property __PATH |
Select-Object -ExpandProperty __PATH |
ForEach-Object {
Get-WmiObject -Query "ASSOCIATORS OF {$_} WHERE ResultClass=MSFT_Partition" -Namespace root\Microsoft\Windows\Storage
} |
Format-Table DiskNumber,PartitionNumber
# Using Get-CimInstance (even more complex escaping)
Get-CimInstance MSFT_Volume -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='$($args[0])'" -Property ObjectId |
ForEach-Object {
$_.ObjectId -replace '\\','\\' -replace '"','\"'
} |
ForEach-Object {
Get-CimInstance -Query ('ASSOCIATORS OF {{MSFT_Volume="{0}"}} WHERE ResultClass=MSFT_Partition' -f $_) -Namespace root\Microsoft\Windows\Storage
} |
Format-Table DiskNumber,PartitionNumber
To be exhaustive, here is the WMIC equivalent of the embedded PowerShell command:
wmic /NAMESPACE:\\root\Microsoft\Windows\Storage PATH MSFT_Volume WHERE DriveLetter="%~1" ASSOC:List /RESULTCLASS:MSFT_Partition 2> nul | findstr /b "DiskNumber= PartitionNumber="
Ultimately, this exploration was more of an experiment. In fact, a simple SELECT
query on the MSFT_Partition
class is sufficient to retrieve both the partition name and the drive letter properties, since they're directly exposed:
Get-CimInstance MSFT_Partition -Namespace root\Microsoft\Windows\Storage -Filter "DriveLetter='$($args[0])'" -Property DiskNumber,PartitionNumber |
Format-Table DiskNumber,PartitionNumber
4. Conclusion
In summary, the ASSOCIATORS OF
query remains a powerful tool in WMI and CIM for navigating relationships between system components — especially through association classes. While WMIC offered a more forgiving syntax and easier access to object relationships, its deprecation highlights the need to transition to modern alternatives like Get-CimInstance
and Get-CimAssociatedInstance
. These cmdlets offer robust capabilities, but they require a deeper understanding of object paths, key qualifiers, and namespace conventions. By mastering these concepts—and by leveraging techniques such as programmatically constructing object paths or inspecting class metadata—you can write precise, efficient queries that maintain full compatibility with current and future PowerShell environments.
Comments
Post a Comment