podman-build/vendor/github.com/containers/libhvee/pkg/wmiext/instance.go
2025-10-11 12:30:35 +09:00

653 lines
20 KiB
Go

//go:build windows
// +build windows
package wmiext
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"syscall"
"time"
"unsafe"
"github.com/go-ole/go-ole"
"github.com/sirupsen/logrus"
)
const (
WmiPathKey = "__PATH"
)
var (
WindowsEpoch = time.Date(1601, 1, 1, 0, 0, 0, 0, time.UTC)
)
type Instance struct {
object *ole.IUnknown
vTable *IWbemClassObjectVtbl
service *Service
}
type IWbemClassObjectVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetQualifierSet uintptr
Get uintptr
Put uintptr
Delete uintptr
GetNames uintptr
BeginEnumeration uintptr
Next uintptr
EndEnumeration uintptr
GetPropertyQualifierSet uintptr
Clone uintptr
GetObjectText uintptr
SpawnDerivedClass uintptr
SpawnInstance uintptr
CompareTo uintptr
GetPropertyOrigin uintptr
InheritsFrom uintptr
GetMethod uintptr
PutMethod uintptr
DeleteMethod uintptr
BeginMethodEnumeration uintptr
NextMethod uintptr
EndMethodEnumeration uintptr
GetMethodQualifierSet uintptr
GetMethodOrigin uintptr
}
type CIMTYPE_ENUMERATION uint32
const (
CIM_ILLEGAL CIMTYPE_ENUMERATION = 0xFFF
CIM_EMPTY CIMTYPE_ENUMERATION = 0
CIM_SINT8 CIMTYPE_ENUMERATION = 16
CIM_UINT8 CIMTYPE_ENUMERATION = 17
CIM_SINT16 CIMTYPE_ENUMERATION = 2
CIM_UINT16 CIMTYPE_ENUMERATION = 18
CIM_SINT32 CIMTYPE_ENUMERATION = 3
CIM_UINT32 CIMTYPE_ENUMERATION = 19
CIM_SINT64 CIMTYPE_ENUMERATION = 20
CIM_UINT64 CIMTYPE_ENUMERATION = 21
CIM_REAL32 CIMTYPE_ENUMERATION = 4
CIM_REAL64 CIMTYPE_ENUMERATION = 5
CIM_BOOLEAN CIMTYPE_ENUMERATION = 11
CIM_STRING CIMTYPE_ENUMERATION = 8
CIM_DATETIME CIMTYPE_ENUMERATION = 101
CIM_REFERENCE CIMTYPE_ENUMERATION = 102
CIM_CHAR16 CIMTYPE_ENUMERATION = 103
CIM_OBJECT CIMTYPE_ENUMERATION = 13
CIM_FLAG_ARRAY CIMTYPE_ENUMERATION = 0x2000
)
type WBEM_FLAVOR_TYPE uint32
const (
WBEM_FLAVOR_DONT_PROPAGATE WBEM_FLAVOR_TYPE = 0
WBEM_FLAVOR_FLAG_PROPAGATE_TO_INSTANCE WBEM_FLAVOR_TYPE = 0x1
WBEM_FLAVOR_FLAG_PROPAGATE_TO_DERIVED_CLASS WBEM_FLAVOR_TYPE = 0x2
WBEM_FLAVOR_MASK_PROPAGATION WBEM_FLAVOR_TYPE = 0xf
WBEM_FLAVOR_OVERRIDABLE WBEM_FLAVOR_TYPE = 0
WBEM_FLAVOR_NOT_OVERRIDABLE WBEM_FLAVOR_TYPE = 0x10
WBEM_FLAVOR_MASK_PERMISSIONS WBEM_FLAVOR_TYPE = 0x10
WBEM_FLAVOR_ORIGIN_LOCAL WBEM_FLAVOR_TYPE = 0
WBEM_FLAVOR_ORIGIN_PROPAGATED WBEM_FLAVOR_TYPE = 0x20
WBEM_FLAVOR_ORIGIN_SYSTEM WBEM_FLAVOR_TYPE = 0x40
WBEM_FLAVOR_MASK_ORIGIN WBEM_FLAVOR_TYPE = 0x60
WBEM_FLAVOR_NOT_AMENDED WBEM_FLAVOR_TYPE = 0
WBEM_FLAVOR_AMENDED WBEM_FLAVOR_TYPE = 0x80
WBEM_FLAVOR_MASK_AMENDED WBEM_FLAVOR_TYPE = 0x80
)
func newInstance(object *ole.IUnknown, service *Service) *Instance {
instance := &Instance{
object: object,
vTable: (*IWbemClassObjectVtbl)(unsafe.Pointer(object.RawVTable)),
service: service,
}
return instance
}
// Close cleans up all memory associated with this instance.
func (i *Instance) Close() {
if i != nil && i.object != nil {
i.object.Release()
}
}
// GetClassName Gets the WMI class name for this WMI object instance
func (i *Instance) GetClassName() (className string, err error) {
return i.GetAsString(`__CLASS`)
}
// Path gets the WMI object path of this instance
func (i *Instance) Path() (string, error) {
ref, _, _, err := i.GetAsAny(WmiPathKey)
return ref.(string), err
}
// IsReferenceProperty returns whether the property is of type CIM_REFERENCE, a string which points to
// an object path of another instance.
func (i *Instance) IsReferenceProperty(name string) (bool, error) {
_, cimType, _, err := i.GetAsAny(name)
return cimType == CIM_REFERENCE, err
}
// SpawnInstance create a new WMI object instance that is zero-initialized. The returned instance
// will not respect expected default values, which must be populated by other means.
func (i *Instance) SpawnInstance() (instance *Instance, err error) {
var res uintptr
var newUnknown *ole.IUnknown
res, _, _ = syscall.SyscallN(
i.vTable.SpawnInstance, // IWbemClassObject::SpawnInstance(
uintptr(unsafe.Pointer(i.object)), // IWbemClassObject ptr
uintptr(0), // [in] long lFlags,
uintptr(unsafe.Pointer(&newUnknown))) // [out] IWbemClassObject **ppNewInstance)
if res != 0 {
return nil, NewWmiError(res)
}
return newInstance(newUnknown, i.service), nil
}
// CloneInstance create a new cloned copy of this WMI instance.
func (i *Instance) CloneInstance() (*Instance, error) {
classObj := i.object
vTable := (*IWbemClassObjectVtbl)(unsafe.Pointer(classObj.RawVTable))
var cloned *ole.IUnknown
ret, _, _ := syscall.SyscallN(
vTable.Clone, // IWbemClassObject::Clone(
uintptr(unsafe.Pointer(classObj)), // IWbemClassObject ptr
uintptr(unsafe.Pointer(&cloned))) // [out] IWbemClassObject **ppCopy)
if ret != 0 {
return nil, NewWmiError(ret)
}
return newInstance(cloned, i.service), nil
}
// PutAll sets all fields of this instance to the passed src parameter's fields, converting accordingly.
// The src parameter must be a pointer to a struct, otherwise an error will be returned.
func (i *Instance) PutAll(src interface{}) error {
val := reflect.ValueOf(src)
if val.Kind() == reflect.Pointer {
val = val.Elem()
}
if val.Kind() != reflect.Struct {
return errors.New("not a struct or pointer to struct")
}
props, err := i.GetAllProperties()
if err != nil {
return err
}
return i.instancePutAllTraverse(val, props)
}
func (i *Instance) instancePutAllTraverse(val reflect.Value, propMap map[string]interface{}) error {
for j := 0; j < val.NumField(); j++ {
fieldVal := val.Field(j)
fieldType := val.Type().Field(j)
if fieldType.Type.Kind() == reflect.Struct && fieldType.Anonymous {
if err := i.instancePutAllTraverse(fieldVal, propMap); err != nil {
return err
}
continue
}
if strings.HasPrefix(fieldType.Name, "S__") {
continue
}
if !fieldType.IsExported() {
continue
}
if _, exists := propMap[fieldType.Name]; !exists {
continue
}
if fieldVal.Kind() == reflect.String && fieldVal.Len() == 0 {
continue
}
if err := i.Put(fieldType.Name, fieldVal.Interface()); err != nil {
return err
}
}
return nil
}
// Put sets the specified property to the passed Golang value, converting appropriately.
func (i *Instance) Put(name string, value interface{}) (err error) {
var variant ole.VARIANT
switch cast := value.(type) {
case ole.VARIANT:
variant = cast
case *ole.VARIANT:
variant = *cast
default:
variant, err = NewAutomationVariant(value)
if err != nil {
return err
}
}
var wszName *uint16
if wszName, err = syscall.UTF16PtrFromString(name); err != nil {
return
}
classObj := i.object
vTable := (*IWbemClassObjectVtbl)(unsafe.Pointer(classObj.RawVTable))
res, _, _ := syscall.SyscallN(
vTable.Put, // IWbemClassObject::Put(
uintptr(unsafe.Pointer(classObj)), // IWbemClassObject ptr
uintptr(unsafe.Pointer(wszName)), // [in] LPCWSTR wszName,
uintptr(0), // [in] long lFlags,
uintptr(unsafe.Pointer(&variant)), // [in] VARIANT *pVal,
uintptr(0)) // [in] CIMTYPE Type)
if res != 0 {
return NewWmiError(res)
}
_ = variant.Clear()
return
}
// GetCimText returns the CIM XML representation of this instance. Some WMI methods use a string
// parameter to represent a full complex object, and this method is used to generate
// the expected format.
func (i *Instance) GetCimText() string {
type wmiWbemTxtSrcVtable struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
GetTxt uintptr
}
const CIM_XML_FORMAT = 1
classObj := i.object
vTable := (*wmiWbemTxtSrcVtable)(unsafe.Pointer(wmiWbemTxtLocator.RawVTable))
var retString *uint16
res, _, _ := syscall.SyscallN(
vTable.GetTxt, // IWbemObjectTextSrc::GetText()
uintptr(unsafe.Pointer(wmiWbemLocator)), // IWbemObjectTextSrc ptr
uintptr(0), // [in] long lFlags
uintptr(unsafe.Pointer(classObj)), // [in] IWbemClassObject *pObj
uintptr(CIM_XML_FORMAT), // [in] ULONG uObjTextFormat,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(&retString))) // [out] BSTR *strText)
if res != 0 {
return ""
}
itemStr := ole.BstrToString(retString)
return itemStr
}
// GetAll gets all fields that map to a target struct and populates all struct fields according to
// the expected type information. The target parameter should be a pointer to a struct, and
// will return an error otherwise.
func (i *Instance) GetAll(target interface{}) error {
elem := reflect.ValueOf(target)
if elem.Kind() != reflect.Ptr || elem.IsNil() {
return errors.New("invalid destination type for mapping a WMI instance to an object")
}
// deref pointer
elem = elem.Elem()
var err error
if err = i.BeginEnumeration(); err != nil {
return err
}
properties := make(map[string]*ole.VARIANT)
for {
var name string
var value *ole.VARIANT
var done bool
if done, name, value, _, _, err = i.NextAsVariant(); err != nil {
return err
}
if done {
break
}
if value != nil {
properties[name] = value
}
}
defer func() {
for _, v := range properties {
_ = v.Clear()
}
}()
_ = i.EndEnumeration()
return i.instanceGetAllPopulate(elem, elem.Type(), properties)
}
// GetAsAny gets a property and converts it to a Golang type that matches the internal
// variant automation type passed back from WMI. For usage with predictable static
// type mapping, use GetAsString(), GetAsUint(), or GetAll() instead of this method.
func (i *Instance) GetAsAny(name string) (interface{}, CIMTYPE_ENUMERATION, WBEM_FLAVOR_TYPE, error) {
variant, cimType, flavor, err := i.GetAsVariant(name)
if err != nil {
return nil, cimType, flavor, err
}
defer func() {
if err := variant.Clear(); err != nil {
logrus.Error(err)
}
}()
// Since there is no type information only perform the stock conversion
result := convertToGenericValue(variant)
return result, cimType, flavor, err
}
// GetAsString gets a property value as a string value, converting if necessary
func (i *Instance) GetAsString(name string) (value string, err error) {
variant, _, _, err := i.GetAsVariant(name)
if err != nil || variant == nil {
return "", err
}
defer func() {
if err := variant.Clear(); err != nil {
logrus.Error(err)
}
}()
// TODO: replace with something better
return fmt.Sprintf("%v", convertToGenericValue(variant)), nil
}
// GetAsUint gets a property value as a uint value, if conversion is possible. Otherwise,
// returns an error.
func (i *Instance) GetAsUint(name string) (uint, error) {
val, _, _, err := i.GetAsAny(name)
if err != nil {
return 0, err
}
switch ret := val.(type) {
case int:
return uint(ret), nil
case int8:
return uint(ret), nil
case int16:
return uint(ret), nil
case int32:
return uint(ret), nil
case int64:
return uint(ret), nil
case uint:
return ret, nil
case uint8:
return uint(ret), nil
case uint16:
return uint(ret), nil
case uint32:
return uint(ret), nil
case uint64:
return uint(ret), nil
case string:
parse, err := strconv.ParseUint(ret, 10, 64)
return uint(parse), err
default:
return 0, fmt.Errorf("type conversion from %T on param %s not supported", val, name)
}
}
// GetAsVariant obtains a specified property value, if it exists.
func (i *Instance) GetAsVariant(name string) (*ole.VARIANT, CIMTYPE_ENUMERATION, WBEM_FLAVOR_TYPE, error) {
var variant ole.VARIANT
var err error
var wszName *uint16
var cimType CIMTYPE_ENUMERATION
var flavor WBEM_FLAVOR_TYPE
if wszName, err = syscall.UTF16PtrFromString(name); err != nil {
return nil, 0, 0, err
}
classObj := i.object
vTable := (*IWbemClassObjectVtbl)(unsafe.Pointer(classObj.RawVTable))
res, _, _ := syscall.SyscallN(
vTable.Get, // IWbemClassObject::Get(
uintptr(unsafe.Pointer(classObj)), // IWbemClassObject ptr
uintptr(unsafe.Pointer(wszName)), // [in] LPCWSTR wszName,
uintptr(0), // [in] long lFlags,
uintptr(unsafe.Pointer(&variant)), // [out] VARIANT *pVal,
uintptr(unsafe.Pointer(&cimType)), // [out, optional] CIMTYPE *pType,
uintptr(unsafe.Pointer(&flavor))) // [out, optional] long *plFlavor)
if res != 0 {
return nil, 0, 0, NewWmiError(res)
}
return &variant, cimType, flavor, nil
}
// Next retrieves the next property as a Golang type when iterating the properties using an enumerator
// created by BeginEnumeration(). The returned value's type represents the internal automation type
// used by WMI. It is usually preferred to use GetAsXXX(), GetAll(), or GetAll Properties() over this
// method.
func (i *Instance) Next() (done bool, name string, value interface{}, cimType CIMTYPE_ENUMERATION, flavor WBEM_FLAVOR_TYPE, err error) {
var variant *ole.VARIANT
done, name, variant, cimType, flavor, err = i.NextAsVariant()
if err == nil && !done {
defer func() {
if err := variant.Clear(); err != nil {
logrus.Error(err)
}
}()
value = convertToGenericValue(variant)
}
return
}
// NextAsVariant retrieves the next property as a VARIANT type when iterating the properties using an enumerator
// created by BeginEnumeration(). The returned value's type represents the internal automation type
// used by WMI. It is usually preferred to use GetAsXXX(), GetAll(), or GetAllProperties() over this
// method. Callers are responsible for clearing the VARIANT, otherwise associated memory will leak.
func (i *Instance) NextAsVariant() (bool, string, *ole.VARIANT, CIMTYPE_ENUMERATION, WBEM_FLAVOR_TYPE, error) {
var res uintptr
var strName *uint16
var variant ole.VARIANT
var cimType CIMTYPE_ENUMERATION
var flavor WBEM_FLAVOR_TYPE
res, _, _ = syscall.SyscallN(
i.vTable.Next, // IWbemClassObject::Next(
uintptr(unsafe.Pointer(i.object)), // IWbemClassObject ptr
uintptr(0), // [in] long lFlags,
uintptr(unsafe.Pointer(&strName)), // [out] BSTR *strName,
uintptr(unsafe.Pointer(&variant)), // [out] VARIANT *pVal,
uintptr(unsafe.Pointer(&cimType)), // [out, optional] CIMTYPE *pType,
uintptr(unsafe.Pointer(&flavor))) // [out, optional] long *plFlavor
if int(res) < 0 {
return false, "", nil, cimType, flavor, NewWmiError(res)
}
if res == WBEM_S_NO_MORE_DATA {
return true, "", nil, cimType, flavor, nil
}
defer ole.SysFreeString((*int16)(unsafe.Pointer(strName))) //nolint:errcheck
name := ole.BstrToString(strName)
return false, name, &variant, cimType, flavor, nil
}
// GetAllProperties gets all properties on this instance. The returned map is keyed by the field name and the value
// is a Golang type which matches the WMI internal implementation. For static type conversions,
// it's recommended to use either GetAll(), which uses struct fields for type information, or
// the GetAsXXX() methods.
func (i *Instance) GetAllProperties() (map[string]interface{}, error) {
var err error
properties := make(map[string]interface{})
if err = i.BeginEnumeration(); err != nil {
return nil, err
}
defer func() {
if err := i.EndEnumeration(); err != nil {
logrus.Error(err)
}
}()
for {
var name string
var value interface{}
var done bool
if done, name, value, _, _, err = i.Next(); err != nil || done {
return properties, err
}
properties[name] = value
}
}
// GetMethodParameters returns a WMI class object which represents the [in] method parameters for a method invocation.
// This is an advanced method, used for dynamic introspection or manual method invocation. In most
// cases it is recommended to use BeginInvoke() instead, which constructs the parameter payload
// automatically.
func (i *Instance) GetMethodParameters(method string) (*Instance, error) {
var err error
var res uintptr
var inSignature *ole.IUnknown
var wszName *uint16
if wszName, err = syscall.UTF16PtrFromString(method); err != nil {
return nil, err
}
res, _, _ = syscall.SyscallN(
i.vTable.GetMethod, // IWbemClassObject::GetMethod(
uintptr(unsafe.Pointer(i.object)), // IWbemClassObject ptr
uintptr(unsafe.Pointer(wszName)), // [in] LPCWSTR wszName
uintptr(0), // [in] long lFlags,
uintptr(unsafe.Pointer(&inSignature)), // [out] IWbemClassObject **ppInSignature,
uintptr(0)) // [out] IWbemClassObject **ppOutSignature)
if res != 0 {
return nil, NewWmiError(res)
}
return newInstance(inSignature, i.service), nil
}
func (i *Instance) instanceGetAllPopulate(elem reflect.Value, elemType reflect.Type, properties map[string]*ole.VARIANT) error {
var err error
for j := 0; j < elemType.NumField(); j++ {
fieldType := elemType.Field(j)
fieldVal := elem.Field(j)
if !fieldType.IsExported() {
continue
}
if fieldType.Type.Kind() == reflect.Struct && fieldType.Anonymous {
if err := i.instanceGetAllPopulate(fieldVal, fieldType.Type, properties); err != nil {
return err
}
continue
}
fieldName := fieldType.Name
if strings.HasPrefix(fieldName, "S__") {
fieldName = fieldName[1:]
}
if variant, ok := properties[fieldName]; ok {
var val interface{}
if val, err = convertToGoType(variant, fieldVal, fieldType.Type); err != nil {
return err
}
if val != nil {
fieldVal.Set(reflect.ValueOf(val))
}
}
}
return nil
}
// BeginEnumeration begins iterating the property list on this instance. This is an advanced method.
// In most cases, the GetAsXXX() methods, GetAll(), and GetAllProperties() methods should be
// preferred.
func (i *Instance) BeginEnumeration() error {
classObj := i.object
vTable := (*IWbemClassObjectVtbl)(unsafe.Pointer(classObj.RawVTable))
result, _, _ := syscall.SyscallN(
vTable.BeginEnumeration, // IWbemClassObject::BeginEnumeration(
uintptr(unsafe.Pointer(classObj)), // IWbemClassObject ptr,
uintptr(0)) // [in] long lEnumFlags) // 0 = defaults
if result != 0 {
return NewWmiError(result)
}
return nil
}
// EndEnumeration completes iterating a property list on this instance. This is an advanced method.
// In most cases, the GetAsXXX() methods, GetAll(), and GetAllProperties() methods
// should be preferred.
func (i *Instance) EndEnumeration() error {
res, _, _ := syscall.SyscallN(
i.vTable.EndEnumeration, // IWbemClassObject::EndEnumeration(
uintptr(unsafe.Pointer(i.object))) // IWbemClassObject ptr)
if res != 0 {
return NewWmiError(res)
}
return nil
}
// BeginInvoke invokes a method on this Instance. Returns a MethodExecutor builder object
// that is used to construct the input parameters (via calls to In()), perform the
// invocation (using calls to Execute()), retrieve output parameters (via calls to
// Out()), and finally the method return value (using a call to End())
func (i *Instance) BeginInvoke(method string) *MethodExecutor {
objPath, err := i.Path()
if err != nil {
return &MethodExecutor{err: err}
}
var class, inParam *Instance
if class, err = i.service.GetClassInstance(i); err == nil {
inParam, err = class.GetMethodParameters(method)
class.Close()
}
return &MethodExecutor{method: method, path: objPath, service: i.service, inParam: inParam, err: err}
}