|
| 1 | +# CSI Proxy API Implementation with WMI |
| 2 | +<a name="top"></a> |
| 3 | + |
| 4 | +## Table of Contents |
| 5 | + |
| 6 | +- [Windows Management Instrumentation](#wmi) |
| 7 | +- [microsoft/wmi library](#microsoft-wmi-library) |
| 8 | +- [How to make WMI queries and debug with PowerShell](#debug-powershell) |
| 9 | + |
| 10 | + |
| 11 | +<a name="wmi"></a> |
| 12 | +## Windows Management Instrumentation |
| 13 | + |
| 14 | +Windows Management Instrumentation (WMI) is the infrastructure for management data and operations on Windows-based operating systems. |
| 15 | +Refer to [WMI start page](https://learn.microsoft.com/en-us/windows/win32/wmisdk/wmi-start-page) for more details. |
| 16 | + |
| 17 | +The purpose of WMI is to define a proprietary set of environment-independent specifications that enable sharing management information between management apps. |
| 18 | + |
| 19 | +CSI-proxy makes WMI queries using `microsoft/wmi` library. Refer to for the call graph below. |
| 20 | + |
| 21 | +<a name="microsoft-wmi-library"></a> |
| 22 | +## microsoft/wmi library |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +`microsoft/wmi` library leverages the traditional COM interfaces (`IDispatch`) to call the WMI. |
| 27 | + |
| 28 | +COM interfaces wrap the parameters and return value in a `VARIANT` struct. |
| 29 | +`microsoft/wmi` library converts the `VARIANT` to native Go types and struct. |
| 30 | + |
| 31 | +A typical WMI query may need to obtain a WMI session of the target machine first, which |
| 32 | +can be done by the helper methods `NewWMISession` and `QueryFromWMI` in `pkg/cim`. |
| 33 | + |
| 34 | +A query like `SELECT * FROM MSFT_Volume` may return all the volumes on the current node. |
| 35 | + |
| 36 | +### Queries |
| 37 | + |
| 38 | +The query may return a list of WMI objects of the generic type `cim.WmiInstance`. You may further cast |
| 39 | +the object down to a specific WMI class (e.g. `MSFT_Disk`). You may find the WMI class definition |
| 40 | +from the API doc. |
| 41 | + |
| 42 | +For example, the property `PartitionStyle` on [MSFT_Disk](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-disk#properties) is defined as |
| 43 | + |
| 44 | +| API constraints | Settings | |
| 45 | +|-----------------|---------------------------------------| |
| 46 | +| Property | PartitionStyle | |
| 47 | +| Data type | UInt16 | |
| 48 | +| Access type | Read-only | |
| 49 | +| Qualifiers | Required | |
| 50 | +| Description | The partition style used by the disk. | |
| 51 | + |
| 52 | +You may use `GetProperty` to get the value of `PartitionStyle` to get the value from the `VARAINT` and |
| 53 | +converts it back to Go types. |
| 54 | + |
| 55 | +```go |
| 56 | +retValue, err := disk.GetProperty("PartitionStyle") |
| 57 | +if err != nil { |
| 58 | + return false, fmt.Errorf("failed to query partition style of disk %d: %w", diskNumber, err) |
| 59 | +} |
| 60 | + |
| 61 | +partitionStyle = retValue.(int32) |
| 62 | +``` |
| 63 | + |
| 64 | +Note that some auto-generated wrapper methods in `microsoft/wmi` may have wrong data types mapping to Go native types. |
| 65 | +It's always recommended to use `GetProperty` instead of these pre-defined wrapper methods. |
| 66 | + |
| 67 | +### Class Method |
| 68 | + |
| 69 | +A WMI class may have some Class Method to call for a specific operation (e.g., creating a new partition). |
| 70 | + |
| 71 | +You may use the method `InvokeMethodWithReturn`. |
| 72 | + |
| 73 | +```go |
| 74 | +result, err := disk.InvokeMethodWithReturn( |
| 75 | + "CreatePartition", |
| 76 | + nil, // Size |
| 77 | + true, // UseMaximumSize |
| 78 | + nil, // Offset |
| 79 | + nil, // Alignment |
| 80 | + nil, // DriveLetter |
| 81 | + false, // AssignDriveLetter |
| 82 | + nil, // MbrType, |
| 83 | + cim.GPTPartitionTypeBasicData, // GPT Type |
| 84 | + false, // IsHidden |
| 85 | + false, // IsActive, |
| 86 | +) |
| 87 | +// 42002 is returned by driver letter failed to assign after partition |
| 88 | +if (result != 0 && result != 42002) || err != nil { |
| 89 | + return fmt.Errorf("error creating partition on disk %d. result: %d, err: %v", diskNumber, result, err) |
| 90 | +} |
| 91 | +``` |
| 92 | + |
| 93 | +Both input and output parameters can be found in the [CreatePartition API doc](https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/createpartition-msft-disk). |
| 94 | + |
| 95 | +```c |
| 96 | +UInt32 CreatePartition( |
| 97 | + [in] UInt64 Size, |
| 98 | + [in] Boolean UseMaximumSize, |
| 99 | + [in] UInt64 Offset, |
| 100 | + [in] UInt32 Alignment, |
| 101 | + [in] Char16 DriveLetter, |
| 102 | + [in] Boolean AssignDriveLetter, |
| 103 | + [in] UInt16 MbrType, |
| 104 | + [in] String GptType, |
| 105 | + [in] Boolean IsHidden, |
| 106 | + [in] Boolean IsActive, |
| 107 | + [out] String CreatedPartition, |
| 108 | + [out] String ExtendedStatus |
| 109 | +); |
| 110 | +``` |
| 111 | + |
| 112 | +There parameters will be wrapped in `VARIANT` with the corresponding types. |
| 113 | + |
| 114 | +Eventually the method `CreatePartition` on the WMI object will be called via `IDispatch` interface in native COM/OLE calls. |
| 115 | + |
| 116 | +Refer to [CallMethod](https://github.com/go-ole/go-ole/blob/master/oleutil/oleutil.go#L49-L52) if you need to know the details of a COM/OLE call. |
| 117 | + |
| 118 | +```go |
| 119 | +func CallMethod(disp *ole.IDispatch, name string, params ...interface{}) (result *ole.VARIANT, err error) { |
| 120 | + return disp.InvokeWithOptionalArgs(name, ole.DISPATCH_METHOD, params) |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +<a name="debug-powershell"></a> |
| 125 | +## Debug with PowerShell |
| 126 | + |
| 127 | +### How to make WMI call with PowerShell |
| 128 | + |
| 129 | +You will find the `Query` for each method in `pkg/cim` package. |
| 130 | +For example, this is the comment of `ListVolume` |
| 131 | + |
| 132 | +```go |
| 133 | +// ListVolumes retrieves all available volumes on the system. |
| 134 | +// |
| 135 | +// The equivalent WMI query is: |
| 136 | +// |
| 137 | +// SELECT [selectors] FROM MSFT_Volume |
| 138 | +// |
| 139 | +// Refer to https://learn.microsoft.com/en-us/windows-hardware/drivers/storage/msft-volume |
| 140 | +// for the WMI class definition. |
| 141 | +``` |
| 142 | + |
| 143 | +You may use the same query with PowerShell cmdlet `Get-CimInstance` targeting |
| 144 | +the corresponding namespace `Root\Microsoft\Windows\Storage`. |
| 145 | + |
| 146 | +The return result will be an object of `MSFT_Volume` WMI class. |
| 147 | + |
| 148 | +e.g. if we're going to list the details of the first volume on Windows system: |
| 149 | + |
| 150 | +```powershell |
| 151 | +PS C:\Users\Administrator> $vol = (Get-CimInstance -Namespace "Root\Microsoft\Windows\Storage" -Query "SELECT * FROM MSFT_Volume")[0] |
| 152 | +PS C:\Users\Administrator> $vol |
| 153 | +
|
| 154 | +ObjectId : {1}\\WIN-8E2EVAQ9QSB\root/Microsoft/Windows/Storage/Providers_v2\WSP_Volume.ObjectId= |
| 155 | +"{b65bb3cd-da86-11ee-854b-806e6f6e6963}:VO:\\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c |
| 156 | +02527}\" |
| 157 | +PassThroughClass : |
| 158 | +PassThroughIds : |
| 159 | +PassThroughNamespace : |
| 160 | +PassThroughServer : |
| 161 | +UniqueId : \\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c02527}\ |
| 162 | +AllocationUnitSize : 4096 |
| 163 | +DedupMode : 4 |
| 164 | +DriveLetter : C |
| 165 | +DriveType : 3 |
| 166 | +FileSystem : NTFS |
| 167 | +FileSystemLabel : |
| 168 | +FileSystemType : 14 |
| 169 | +HealthStatus : 1 |
| 170 | +OperationalStatus : {53261} |
| 171 | +Path : \\?\Volume{1781d1eb-2c0a-47ed-987f-c229b9c02527}\ |
| 172 | +Size : 536198770688 |
| 173 | +SizeRemaining : 407553982464 |
| 174 | +PSComputerName : |
| 175 | +``` |
| 176 | + |
| 177 | +Then you may use `obj.FileSystem` to get the file system of the volume. |
| 178 | + |
| 179 | +```powershell |
| 180 | +PS C:\Users\Administrator> $vol.FileSystem |
| 181 | +NTFS |
| 182 | +``` |
| 183 | + |
| 184 | +### Call Class Method |
| 185 | + |
| 186 | +You may get Class Methods for a single CIM class using `$class.CimClassMethods`. |
| 187 | + |
| 188 | +```powershell |
| 189 | +
|
| 190 | +PS C:\Users\Administrator> $class = Get-CimClass -ClassName MSFT_StorageSetting -Namespace "Root\Microsoft\Windows\Storage" |
| 191 | +PS C:\Users\Administrator> $class.CimClassMethods |
| 192 | +
|
| 193 | +Name ReturnType Parameters Qualifiers |
| 194 | +---- ---------- ---------- ---------- |
| 195 | +Get UInt32 {StorageSetting} {implemented, static} |
| 196 | +Set UInt32 {NewDiskPolicy, ScrubPolicy} {implemented, static} |
| 197 | +UpdateHostStorageCache UInt32 {} {implemented, static} |
| 198 | +``` |
| 199 | + |
| 200 | +You may use `Invoke-CimMethod` to invoke those static methods on the `CimClass` object. |
| 201 | + |
| 202 | +```powershell |
| 203 | +PS C:\Users\Administrator> Invoke-CimMethod -CimClass $class -MethodName UpdateHostStorageCache @{} |
| 204 | +
|
| 205 | +ReturnValue PSComputerName |
| 206 | +----------- -------------- |
| 207 | + 0 |
| 208 | +
|
| 209 | +``` |
0 commit comments