417 lines
14 KiB
Go
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)
|
|
}
|