| 
 | 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.  | 
 | 20 | + | 
 | 21 | +<a name="microsoft-wmi-library"></a>  | 
 | 22 | +## microsoft/wmi library  | 
 | 23 | + | 
 | 24 | +[WMI based implementation](./WMI.png)  | 
 | 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