Skip to content

Commit 55ee701

Browse files
committed
Use associator to find WMI instances for volume APIs
1 parent a9bd679 commit 55ee701

File tree

4 files changed

+72
-229
lines changed

4 files changed

+72
-229
lines changed

docs/IMPLEMENTATION.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ func CallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result
121121
}
122122
```
123123

124+
### Association
125+
126+
Association can be used to retrieve all instances that are associated with
127+
a particular source instance.
128+
129+
There are a few Association classes in WMI.
130+
131+
For example, association class [MSFT_PartitionToVolume](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume)
132+
can be used to retrieve a volume (`MSFT_Volume`) from a partition (`MSFT_Partition`), and vice versa.
133+
134+
```go
135+
collection, err := part.GetAssociated("MSFT_PartitionToVolume", "MSFT_Volume", "Volume", "Partition")
136+
```
137+
124138
<a name="debug-powershell"></a>
125139
## Debug with PowerShell
126140

@@ -181,6 +195,13 @@ PS C:\Users\Administrator> $vol.FileSystem
181195
NTFS
182196
```
183197

198+
### Association
199+
200+
```powershell
201+
PS C:\Users\Administrator> $partition = (Get-CimInstance -Namespace root\Microsoft\Windows\Storage -ClassName MSFT_Partition -Filter "DiskNumber = 0")[0]
202+
PS C:\Users\Administrator> Get-CimAssociatedInstance -InputObject $partition -Association MSFT_PartitionToVolume
203+
```
204+
184205
### Call Class Method
185206

186207
You may get Class Methods for a single CIM class using `$class.CimClassMethods`.

pkg/cim/volume.go

Lines changed: 45 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99

1010
"github.com/microsoft/wmi/pkg/base/query"
1111
"github.com/microsoft/wmi/pkg/errors"
12-
cim "github.com/microsoft/wmi/pkg/wmiinstance"
1312
"github.com/microsoft/wmi/server2019/root/microsoft/windows/storage"
1413
)
1514

@@ -129,131 +128,84 @@ func ListPartitionsWithFilters(selectorList []string, filters ...*query.WmiQuery
129128
return partitions, nil
130129
}
131130

132-
// ListPartitionToVolumeMappings builds a mapping between partition and volume with partition Object ID as the key.
133-
//
134-
// The equivalent WMI query is:
135-
//
136-
// SELECT [selectors] FROM MSFT_PartitionToVolume
137-
//
138-
// Partition | Volume
139-
// --------- | ------
140-
// MSFT_Partition (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Win...) | MSFT_Volume (ObjectId = "{1}\\WIN-8E2EVAQ9QS...
141-
//
142-
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume
143-
// for the WMI class definition.
144-
func ListPartitionToVolumeMappings() (map[string]string, error) {
145-
return ListWMIInstanceMappings(WMINamespaceStorage, "MSFT_PartitionToVolume", nil,
146-
mappingObjectRefIndexer("Partition", "MSFT_Partition", "ObjectId"),
147-
mappingObjectRefIndexer("Volume", "MSFT_Volume", "ObjectId"),
148-
)
149-
}
150-
151-
// ListVolumeToPartitionMappings builds a mapping between volume and partition with volume Object ID as the key.
152-
//
153-
// The equivalent WMI query is:
131+
// FindPartitionsByVolume finds all partitions associated with the given volumes
132+
// using MSFT_PartitionToVolume association.
154133
//
155-
// SELECT [selectors] FROM MSFT_PartitionToVolume
134+
// WMI association MSFT_PartitionToVolume:
156135
//
157-
// Partition | Volume
158-
// --------- | ------
159-
// MSFT_Partition (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Win...) | MSFT_Volume (ObjectId = "{1}\\WIN-8E2EVAQ9QS...
136+
// Partition | Volume
137+
// --------- | ------
138+
// MSFT_Partition (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Win...) | MSFT_Volume (ObjectId = "{1}\\WIN-8E2EVAQ9QS...
160139
//
161140
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume
162141
// for the WMI class definition.
163-
func ListVolumeToPartitionMappings() (map[string]string, error) {
164-
return ListWMIInstanceMappings(WMINamespaceStorage, "MSFT_PartitionToVolume", nil,
165-
mappingObjectRefIndexer("Volume", "MSFT_Volume", "ObjectId"),
166-
mappingObjectRefIndexer("Partition", "MSFT_Partition", "ObjectId"),
167-
)
168-
}
169-
170-
// FindPartitionsByVolume finds all partitions associated with the given volumes
171-
// using partition-to-volume mapping.
172-
func FindPartitionsByVolume(partitions []*storage.MSFT_Partition, volumes []*storage.MSFT_Volume) ([]*storage.MSFT_Partition, error) {
173-
var partitionInstances []*cim.WmiInstance
174-
for _, part := range partitions {
175-
partitionInstances = append(partitionInstances, part.WmiInstance)
176-
}
177-
178-
var volumeInstances []*cim.WmiInstance
179-
for _, volume := range volumes {
180-
volumeInstances = append(volumeInstances, volume.WmiInstance)
181-
}
182-
183-
partitionToVolumeMappings, err := ListPartitionToVolumeMappings()
184-
if err != nil {
185-
return nil, err
186-
}
187-
188-
filtered, err := FindInstancesByObjectIDMapping(partitionInstances, volumeInstances, partitionToVolumeMappings)
189-
if err != nil {
190-
return nil, err
191-
}
192-
142+
func FindPartitionsByVolume(volumes []*storage.MSFT_Volume) ([]*storage.MSFT_Partition, error) {
193143
var result []*storage.MSFT_Partition
194-
for _, instance := range filtered {
195-
part, err := storage.NewMSFT_PartitionEx1(instance)
144+
for _, vol := range volumes {
145+
collection, err := vol.GetAssociated("MSFT_PartitionToVolume", "MSFT_Partition", "Partition", "Volume")
196146
if err != nil {
197-
return nil, fmt.Errorf("failed to query partition %v. error: %v", instance, err)
147+
return nil, fmt.Errorf("failed to query associated partition for %v. error: %v", vol, err)
198148
}
199149

200-
result = append(result, part)
150+
for _, instance := range collection {
151+
part, err := storage.NewMSFT_PartitionEx1(instance)
152+
if err != nil {
153+
return nil, fmt.Errorf("failed to query partition %v. error: %v", instance, err)
154+
}
155+
156+
result = append(result, part)
157+
}
201158
}
202159

203160
return result, nil
204161
}
205162

206163
// FindVolumesByPartition finds all volumes associated with the given partitions
207-
// using volume-to-partition mapping.
208-
func FindVolumesByPartition(volumes []*storage.MSFT_Volume, partitions []*storage.MSFT_Partition) ([]*storage.MSFT_Volume, error) {
209-
var volumeInstances []*cim.WmiInstance
210-
for _, volume := range volumes {
211-
volumeInstances = append(volumeInstances, volume.WmiInstance)
212-
}
213-
214-
var partitionInstances []*cim.WmiInstance
215-
for _, part := range partitions {
216-
partitionInstances = append(partitionInstances, part.WmiInstance)
217-
}
218-
219-
volumeToPartitionMappings, err := ListVolumeToPartitionMappings()
220-
if err != nil {
221-
return nil, err
222-
}
223-
224-
filtered, err := FindInstancesByObjectIDMapping(volumeInstances, partitionInstances, volumeToPartitionMappings)
225-
if err != nil {
226-
return nil, err
227-
}
228-
164+
// using MSFT_PartitionToVolume association.
165+
//
166+
// WMI association MSFT_PartitionToVolume:
167+
//
168+
// Partition | Volume
169+
// --------- | ------
170+
// MSFT_Partition (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Win...) | MSFT_Volume (ObjectId = "{1}\\WIN-8E2EVAQ9QS...
171+
//
172+
// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-partitiontovolume
173+
// for the WMI class definition.
174+
func FindVolumesByPartition(partitions []*storage.MSFT_Partition) ([]*storage.MSFT_Volume, error) {
229175
var result []*storage.MSFT_Volume
230-
for _, instance := range filtered {
231-
volume, err := storage.NewMSFT_VolumeEx1(instance)
176+
for _, part := range partitions {
177+
collection, err := part.GetAssociated("MSFT_PartitionToVolume", "MSFT_Volume", "Volume", "Partition")
232178
if err != nil {
233-
return nil, fmt.Errorf("failed to query volume %v. error: %v", instance, err)
179+
return nil, fmt.Errorf("failed to query associated volumes for %v. error: %v", part, err)
234180
}
235181

236-
result = append(result, volume)
182+
for _, instance := range collection {
183+
volume, err := storage.NewMSFT_VolumeEx1(instance)
184+
if err != nil {
185+
return nil, fmt.Errorf("failed to query volume %v. error: %v", instance, err)
186+
}
187+
188+
result = append(result, volume)
189+
}
237190
}
238191

239192
return result, nil
240193
}
241194

242195
// GetPartitionByVolumeUniqueID retrieves a specific partition from a volume identified by its unique ID.
243-
func GetPartitionByVolumeUniqueID(volumeID string, partitionSelectorList []string) (*storage.MSFT_Partition, error) {
196+
func GetPartitionByVolumeUniqueID(volumeID string) (*storage.MSFT_Partition, error) {
244197
volume, err := QueryVolumeByUniqueID(volumeID, []string{"ObjectId"})
245198
if err != nil {
246199
return nil, err
247200
}
248201

249-
partitions, err := ListPartitionsWithFilters(partitionSelectorList)
202+
result, err := FindPartitionsByVolume([]*storage.MSFT_Volume{volume})
250203
if err != nil {
251204
return nil, err
252205
}
253206

254-
result, err := FindPartitionsByVolume(partitions, []*storage.MSFT_Volume{volume})
255-
if err != nil {
256-
return nil, err
207+
if len(result) == 0 {
208+
return nil, errors.NotFound
257209
}
258210

259211
return result[0], nil
@@ -269,12 +221,7 @@ func GetVolumeByDriveLetter(driveLetter string, partitionSelectorList []string)
269221
return nil, err
270222
}
271223

272-
volumes, err := ListVolumes(partitionSelectorList)
273-
if err != nil {
274-
return nil, err
275-
}
276-
277-
result, err := FindVolumesByPartition(volumes, partitions)
224+
result, err := FindVolumesByPartition(partitions)
278225
if err != nil {
279226
return nil, err
280227
}

pkg/cim/wmi.go

Lines changed: 2 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package cim
55

66
import (
77
"fmt"
8-
"strings"
98

109
"github.com/go-ole/go-ole"
1110
"github.com/go-ole/go-ole/oleutil"
@@ -16,7 +15,7 @@ import (
1615
)
1716

1817
const (
19-
WMINamespaceRoot = "Root\\CimV2"
18+
WMINamespaceCimV2 = "Root\\CimV2"
2019
WMINamespaceStorage = "Root\\Microsoft\\Windows\\Storage"
2120
)
2221

@@ -29,7 +28,7 @@ type InstanceIndexer func(instance *cim.WmiInstance) (string, error)
2928
// to root namespace if none specified.
3029
func NewWMISession(namespace string) (*cim.WmiSession, error) {
3130
if namespace == "" {
32-
namespace = WMINamespaceRoot
31+
namespace = WMINamespaceCimV2
3332
}
3433

3534
sessionManager := cim.NewWmiSessionManager()
@@ -246,122 +245,3 @@ func IgnoreNotFound(err error) error {
246245
}
247246
return err
248247
}
249-
250-
// parseObjectRef extracts the object ID from a WMI object reference string.
251-
// The result string is in this format
252-
// {1}\\WIN-8E2EVAQ9QSB\ROOT/Microsoft/Windows/Storage/Providers_v2\WSP_Partition.ObjectId="{b65bb3cd-da86-11ee-854b-806e6f6e6963}:PR:{00000000-0000-0000-0000-100000000000}\\?\scsi#disk&ven_vmware&prod_virtual_disk#4&2c28f6c4&0&000000#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}"
253-
// from an escape string
254-
func parseObjectRef(input, objectClass, refName string) (string, error) {
255-
tokens := strings.Split(input, fmt.Sprintf("%s.%s=", objectClass, refName))
256-
if len(tokens) < 2 {
257-
return "", fmt.Errorf("invalid object ID value: %s", input)
258-
}
259-
260-
objectID := tokens[1]
261-
objectID = strings.ReplaceAll(objectID, "\\\"", "\"")
262-
objectID = strings.ReplaceAll(objectID, "\\\\", "\\")
263-
objectID = objectID[1 : len(objectID)-1]
264-
return objectID, nil
265-
}
266-
267-
// ListWMIInstanceMappings queries WMI instances and creates a map using custom indexing functions
268-
// to extract keys and values from each instance.
269-
func ListWMIInstanceMappings(namespace, mappingClassName string, selectorList []string, keyIndexer InstanceIndexer, valueIndexer InstanceIndexer) (map[string]string, error) {
270-
q := query.NewWmiQueryWithSelectList(mappingClassName, selectorList)
271-
mappingInstances, err := QueryInstances(namespace, q)
272-
if err != nil {
273-
return nil, err
274-
}
275-
276-
result := make(map[string]string)
277-
for _, mapping := range mappingInstances {
278-
key, err := keyIndexer(mapping)
279-
if err != nil {
280-
return nil, err
281-
}
282-
283-
value, err := valueIndexer(mapping)
284-
if err != nil {
285-
return nil, err
286-
}
287-
288-
result[key] = value
289-
}
290-
291-
return result, nil
292-
}
293-
294-
// FindInstancesByMapping filters instances based on a mapping relationship,
295-
// matching instances through custom indexing and mapping functions.
296-
func FindInstancesByMapping(instanceToFind []*cim.WmiInstance, instanceToFindIndex InstanceIndexer, associatedInstances []*cim.WmiInstance, associatedInstanceIndexer InstanceIndexer, instanceMappings map[string]string) ([]*cim.WmiInstance, error) {
297-
associatedInstanceObjectIDMapping := map[string]*cim.WmiInstance{}
298-
for _, inst := range associatedInstances {
299-
key, err := associatedInstanceIndexer(inst)
300-
if err != nil {
301-
return nil, err
302-
}
303-
304-
associatedInstanceObjectIDMapping[key] = inst
305-
}
306-
307-
var filtered []*cim.WmiInstance
308-
for _, inst := range instanceToFind {
309-
key, err := instanceToFindIndex(inst)
310-
if err != nil {
311-
return nil, err
312-
}
313-
314-
valueObjectID, ok := instanceMappings[key]
315-
if !ok {
316-
continue
317-
}
318-
319-
_, ok = associatedInstanceObjectIDMapping[strings.ToUpper(valueObjectID)]
320-
if !ok {
321-
continue
322-
}
323-
filtered = append(filtered, inst)
324-
}
325-
326-
if len(filtered) == 0 {
327-
return nil, errors.NotFound
328-
}
329-
330-
return filtered, nil
331-
}
332-
333-
// mappingObjectRefIndexer indexes an WMI object by the Object ID reference from a specified property.
334-
func mappingObjectRefIndexer(propertyName, className, refName string) InstanceIndexer {
335-
return func(instance *cim.WmiInstance) (string, error) {
336-
valueVal, err := instance.GetProperty(propertyName)
337-
if err != nil {
338-
return "", err
339-
}
340-
341-
refValue, err := parseObjectRef(valueVal.(string), className, refName)
342-
return strings.ToUpper(refValue), err
343-
}
344-
}
345-
346-
// stringPropertyIndexer indexes a WMI object from a string property.
347-
func stringPropertyIndexer(propertyName string) InstanceIndexer {
348-
return func(instance *cim.WmiInstance) (string, error) {
349-
valueVal, err := instance.GetProperty(propertyName)
350-
if err != nil {
351-
return "", err
352-
}
353-
354-
return strings.ToUpper(valueVal.(string)), err
355-
}
356-
}
357-
358-
var (
359-
// objectIDPropertyIndexer indexes a WMI object from its ObjectId property.
360-
objectIDPropertyIndexer = stringPropertyIndexer("ObjectId")
361-
)
362-
363-
// FindInstancesByObjectIDMapping filters instances based on ObjectId mapping
364-
// between two sets of WMI instances.
365-
func FindInstancesByObjectIDMapping(instanceToFind []*cim.WmiInstance, associatedInstances []*cim.WmiInstance, instanceMappings map[string]string) ([]*cim.WmiInstance, error) {
366-
return FindInstancesByMapping(instanceToFind, objectIDPropertyIndexer, associatedInstances, objectIDPropertyIndexer, instanceMappings)
367-
}

0 commit comments

Comments
 (0)