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

417 lines
14 KiB
Go

//go:build windows
// +build windows
package wmiext
import (
"errors"
"fmt"
"syscall"
"unsafe"
"github.com/go-ole/go-ole"
)
type IWbemLocatorVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
ConnectServer uintptr
}
type Service struct {
service *ole.IUnknown
vTable *IWbemServicesVtbl
}
type IWbemServicesVtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
OpenNamespace uintptr
CancelAsyncCall uintptr
QueryObjectSink uintptr
GetObject uintptr
GetObjectAsync uintptr
PutClass uintptr
PutClassAsync uintptr
DeleteClass uintptr
DeleteClassAsync uintptr
CreateClassEnum uintptr
CreateClassEnumAsync uintptr
PutInstance uintptr
PutInstanceAsync uintptr
DeleteInstance uintptr
DeleteInstanceAsync uintptr
CreateInstanceEnum uintptr
CreateInstanceEnumAsync uintptr
ExecQuery uintptr
ExecQueryAsync uintptr
ExecNotificationQuery uintptr
ExecNotificationQueryAsync uintptr
ExecMethod uintptr
ExecMethodAsync uintptr
}
func connectService(namespace string) (*Service, error) {
if wmiWbemLocator == nil {
return nil, errors.New("WMI failed initialization, service calls can not proceed")
}
var err error
var res uintptr
var strResource *uint16
var strLocale *uint16
var service *ole.IUnknown
loc := fmt.Sprintf(`\\.\%s`, namespace)
if strResource, err = syscall.UTF16PtrFromString(loc); err != nil {
return nil, err
}
// Connect with en_US LCID since we do pattern matching against English key values
if strLocale, err = syscall.UTF16PtrFromString("MS_409"); err != nil {
return nil, err
}
myVTable := (*IWbemLocatorVtbl)(unsafe.Pointer(wmiWbemLocator.RawVTable))
res, _, _ = syscall.SyscallN(
myVTable.ConnectServer, // IWbemLocator::ConnectServer(
uintptr(unsafe.Pointer(wmiWbemLocator)), // IWbemLocator ptr
uintptr(unsafe.Pointer(strResource)), // [in] const BSTR strNetworkResource,
uintptr(0), // [in] const BSTR strUser,
uintptr(0), // [in] const BSTR strPassword,
uintptr(unsafe.Pointer(strLocale)), // [in] const BSTR strLocale,
uintptr(WBEM_FLAG_CONNECT_USE_MAX_WAIT), // [in] long lSecurityFlags,
uintptr(0), // [in] const BSTR strAuthority,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(&service))) // [out] IWbemServices **ppNamespace)
if res != 0 {
return nil, NewWmiError(res)
}
if err = CoSetProxyBlanket(service); err != nil {
return nil, err
}
return newService(service), nil
}
func newService(service *ole.IUnknown) *Service {
return &Service{
service: service,
vTable: (*IWbemServicesVtbl)(unsafe.Pointer(service.RawVTable)),
}
}
const (
WBEM_FLAG_CONNECT_USE_MAX_WAIT = 0x80
)
func CoSetProxyBlanket(service *ole.IUnknown) (err error) {
res, _, _ := procCoSetProxyBlanket.Call( //CoSetProxyBlanket(
uintptr(unsafe.Pointer(service)), // [in] IUnknown *pProxy,
uintptr(RPC_C_AUTHN_WINNT), // [in] DWORD dwAuthnSvc,
uintptr(RPC_C_AUTHZ_NONE), // [in] DWORD dwAuthzSvc,
uintptr(0), // [in, opt] OLECHAR *pServerPrincName,
uintptr(RPC_C_AUTHN_LEVEL_CALL), // [in] DWORD dwAuthnLevel,
uintptr(RPC_C_IMP_LEVEL_IMPERSONATE), // [in] DWORD dwImpLevel,
uintptr(0), // [in, opt] RPC_AUTH_IDENTITY_HANDLE pAuthInfo,
uintptr(EOAC_NONE)) // [in] DWORD dwCapabilities)
if res != 0 {
return NewWmiError(res)
}
return nil
}
// NewLocalService creates a service and connect it to the local system at the specified namespace
func NewLocalService(namespace string) (s *Service, err error) {
return connectService(namespace)
}
// Close frees all associated memory with this service
func (s *Service) Close() {
if s != nil && s.service != nil {
s.service.Release()
}
}
// ExecQuery executes a WQL query and returns an enumeration to iterate the result set.
// Queries are executed in a semi-synchronous fashion.
func (s *Service) ExecQuery(wqlQuery string) (*Enum, error) {
var err error
var pEnum *ole.IUnknown
var strQuery *uint16
var strQL *uint16
if strQL, err = syscall.UTF16PtrFromString("WQL"); err != nil {
return nil, err
}
if strQuery, err = syscall.UTF16PtrFromString(wqlQuery); err != nil {
return nil, err
}
// Semisynchronous mode = return immed + forward (for perf)
flags := WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY
hres, _, _ := syscall.SyscallN(
s.vTable.ExecQuery, // IWbemServices::ExecQuery(
uintptr(unsafe.Pointer(s.service)), // IWbemServices ptr
uintptr(unsafe.Pointer(strQL)), // [in] const BSTR strQueryLanguage,
uintptr(unsafe.Pointer(strQuery)), // [in] const BSTR strQuery,
uintptr(flags), // [in] long lFlags,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(&pEnum))) // [out] IEnumWbemClassObject **ppEnum)
if hres != 0 {
return nil, NewWmiError(hres)
}
if err = CoSetProxyBlanket(pEnum); err != nil {
return nil, err
}
return newEnum(pEnum, s), nil
}
// GetObject obtains a single WMI class or instance given its path
func (s *Service) GetObject(objectPath string) (instance *Instance, err error) {
var pObject *ole.IUnknown
var strObjectPath *uint16
if strObjectPath, err = syscall.UTF16PtrFromString(objectPath); err != nil {
return
}
// Synchronous call
flags := WBEM_FLAG_RETURN_WBEM_COMPLETE
res, _, _ := syscall.SyscallN(
s.vTable.GetObject, // IWbemServices::GetObject(
uintptr(unsafe.Pointer(s.service)), // IWbemServices ptr
uintptr(unsafe.Pointer(strObjectPath)), // [in] const BSTR strObjectPath,
uintptr(flags), // [in] long lFlags,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(&pObject)), // [out] IWbemClassObject **ppObject,
uintptr(0)) // [out] IWbemCallResult **ppCallResult)
if int(res) < 0 {
// returns WBEM_E_PROVIDER_NOT_FOUND when no entry found
return nil, NewWmiError(res)
}
return newInstance(pObject, s), nil
}
// GetObjectAsObject gets an object by its path and set all fields of the passed in target to match the instance's
// properties. Conversion is performed as appropriate.
func (s *Service) GetObjectAsObject(objPath string, target interface{}) error {
instance, err := s.GetObject(objPath)
if err != nil {
return err
}
defer instance.Close()
return instance.GetAll(target)
}
// CreateInstanceEnum creates an enumerator that iterates all registered object instances for a given className.
func (s *Service) CreateInstanceEnum(className string) (*Enum, error) {
var err error
var pEnum *ole.IUnknown
var strFilter *uint16
if strFilter, err = syscall.UTF16PtrFromString(className); err != nil {
return nil, err
}
// No subclasses in result set
flags := WBEM_FLAG_SHALLOW
res, _, _ := syscall.SyscallN(
s.vTable.CreateInstanceEnum, // IWbemServices::CreateInstanceEnum(
uintptr(unsafe.Pointer(s.service)), // IWbemServices ptr
uintptr(unsafe.Pointer(strFilter)), // [in] const BSTR strFilter,
uintptr(flags), // [in] long lFlags,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(&pEnum))) // [out] IEnumWbemClassObject **ppEnum)
if int(res) < 0 {
return nil, NewWmiError(res)
}
if err = CoSetProxyBlanket(pEnum); err != nil {
return nil, err
}
return newEnum(pEnum, s), nil
}
// ExecMethod executes a method using the specified class and parameter payload instance. The parameter payload
// instance can be constructed using Instance.GetMethodParameters(). This is an advanced method, it is
// recommended to use BeginInvoke() instead, where possible.
func (s *Service) ExecMethod(className string, methodName string, inParams *Instance) (*Instance, error) {
var err error
var outParams *ole.IUnknown
var strObjectPath *uint16
var strMethodName *uint16
if strObjectPath, err = syscall.UTF16PtrFromString(className); err != nil {
return nil, err
}
if strMethodName, err = syscall.UTF16PtrFromString(methodName); err != nil {
return nil, err
}
res, _, _ := syscall.SyscallN(
s.vTable.ExecMethod, // IWbemServices::ExecMethod(
uintptr(unsafe.Pointer(s.service)), // IWbemServices ptr
uintptr(unsafe.Pointer(strObjectPath)), // [in] const BSTR strObjectPath,
uintptr(unsafe.Pointer(strMethodName)), // [in] const BSTR strMethodName,
uintptr(0), // [in] long lFlags,
uintptr(0), // [in] IWbemContext *pCtx,
uintptr(unsafe.Pointer(inParams.object)), // [in] IWbemClassObject *pInParams,
uintptr(unsafe.Pointer(&outParams)), // [out] IWbemClassObject **ppOutParams,
uintptr(0)) // [out] IWbemCallResult **ppCallResult)
if int(res) < 0 {
return nil, NewWmiError(res)
}
return newInstance(outParams, s), nil
}
// FindFirstInstance find and returns the first WMI Instance in the result set for a WSL query.
func (s *Service) FindFirstInstance(wql string) (*Instance, error) {
var enum *Enum
var err error
if enum, err = s.ExecQuery(wql); err != nil {
return nil, err
}
defer enum.Close()
instance, err := enum.Next()
if err != nil {
return nil, err
}
if instance == nil {
return nil, ErrNoResults
}
return instance, nil
}
// FindFirstRelatedInstance finds and returns a related associator of the specified WMI object path of the
// expected className type.
func (s *Service) FindFirstRelatedInstance(objPath string, className string) (*Instance, error) {
wql := fmt.Sprintf("ASSOCIATORS OF {%s} WHERE ResultClass = %s", objPath, className)
return s.FindFirstInstance(wql)
}
// FindFirstRelatedInstanceThrough finds and returns a related associator of the specified WMI object path of the
// expected className type, and only through the expected association type.
func (s *Service) FindFirstRelatedInstanceThrough(objPath string, resultClass string, assocClass string) (*Instance, error) {
wql := fmt.Sprintf("ASSOCIATORS OF {%s} WHERE AssocClass = %s ResultClass = %s ", objPath, assocClass, resultClass)
return s.FindFirstInstance(wql)
}
// FindFirstRelatedObject finds and returns a related associator of the specified WMI object path of the
// expected className type, and populates the passed in struct with its fields
func (s *Service) FindFirstRelatedObject(objPath string, className string, target interface{}) error {
wql := fmt.Sprintf("ASSOCIATORS OF {%s} WHERE ResultClass = %s", objPath, className)
return s.FindFirstObject(wql, target)
}
// FindFirstObject finds and returns the first WMI Instance in the result set for a WSL query, and
// populates the specified struct pointer passed in through the target parameter.
func (s *Service) FindFirstObject(wql string, target interface{}) error {
var enum *Enum
var err error
if enum, err = s.ExecQuery(wql); err != nil {
return err
}
defer enum.Close()
done, err := NextObject(enum, target)
if err != nil {
return err
}
if done {
return errors.New("no results found")
}
return nil
}
// GetSingletonInstance gets the first WMI instance of the specified object class type. This is a
// shortcut method for uses where only one instance is expected.
func (s *Service) GetSingletonInstance(className string) (*Instance, error) {
var (
enum *Enum
instance *Instance
err error
)
if enum, err = s.CreateInstanceEnum(className); err != nil {
return nil, err
}
defer enum.Close()
if instance, err = enum.Next(); err != nil {
return nil, err
}
return instance, nil
}
// CreateInstance creates a new WMI object class instance of the specified className, and sets
// all properties according to the passed in struct pointer through the src
// parameter, converting appropriately.
func (s *Service) CreateInstance(className string, src interface{}) (*Instance, error) {
instance, err := s.SpawnInstance(className)
if err != nil {
return nil, err
}
return instance, instance.PutAll(src)
}
// SpawnInstance creates a new zeroed WMI instance. This instance will not contain expected values.
// Those must be retrieved and set separately, or CreateInstance() can be used instead.
func (s *Service) SpawnInstance(className string) (*Instance, error) {
var class *Instance
var err error
if class, err = s.GetObject(className); err != nil {
return nil, err
}
defer class.Close()
return class.SpawnInstance()
}
// RefetchObject re-fetches the object and returns a new instance. The original instance will not
// automatically Close(). Callers of this method will need to manually close the
// original.
func (s *Service) RefetchObject(instance *Instance) (*Instance, error) {
path, err := instance.Path()
if err != nil {
return instance, err
}
return s.GetObject(path)
}
// GetClassInstance gets the WMI class instance associated with the specified object instance.
// This method is used to perform schema queries.
func (s *Service) GetClassInstance(obj *Instance) (*Instance, error) {
name, err := obj.GetClassName()
if err != nil {
return nil, err
}
return s.GetObject(name)
}