mirror of https://github.com/EasyDarwin/EasyDarwin
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
490 lines
13 KiB
490 lines
13 KiB
// +build windows |
|
|
|
/* |
|
Package wmi provides a WQL interface for WMI on Windows. |
|
|
|
Example code to print names of running processes: |
|
|
|
type Win32_Process struct { |
|
Name string |
|
} |
|
|
|
func main() { |
|
var dst []Win32_Process |
|
q := wmi.CreateQuery(&dst, "") |
|
err := wmi.Query(q, &dst) |
|
if err != nil { |
|
log.Fatal(err) |
|
} |
|
for i, v := range dst { |
|
println(i, v.Name) |
|
} |
|
} |
|
|
|
*/ |
|
package wmi |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"fmt" |
|
"log" |
|
"os" |
|
"reflect" |
|
"runtime" |
|
"strconv" |
|
"strings" |
|
"sync" |
|
"time" |
|
|
|
"github.com/go-ole/go-ole" |
|
"github.com/go-ole/go-ole/oleutil" |
|
) |
|
|
|
var l = log.New(os.Stdout, "", log.LstdFlags) |
|
|
|
var ( |
|
ErrInvalidEntityType = errors.New("wmi: invalid entity type") |
|
// ErrNilCreateObject is the error returned if CreateObject returns nil even |
|
// if the error was nil. |
|
ErrNilCreateObject = errors.New("wmi: create object returned nil") |
|
lock sync.Mutex |
|
) |
|
|
|
// S_FALSE is returned by CoInitializeEx if it was already called on this thread. |
|
const S_FALSE = 0x00000001 |
|
|
|
// QueryNamespace invokes Query with the given namespace on the local machine. |
|
func QueryNamespace(query string, dst interface{}, namespace string) error { |
|
return Query(query, dst, nil, namespace) |
|
} |
|
|
|
// Query runs the WQL query and appends the values to dst. |
|
// |
|
// dst must have type *[]S or *[]*S, for some struct type S. Fields selected in |
|
// the query must have the same name in dst. Supported types are all signed and |
|
// unsigned integers, time.Time, string, bool, or a pointer to one of those. |
|
// Array types are not supported. |
|
// |
|
// By default, the local machine and default namespace are used. These can be |
|
// changed using connectServerArgs. See |
|
// http://msdn.microsoft.com/en-us/library/aa393720.aspx for details. |
|
// |
|
// Query is a wrapper around DefaultClient.Query. |
|
func Query(query string, dst interface{}, connectServerArgs ...interface{}) error { |
|
if DefaultClient.SWbemServicesClient == nil { |
|
return DefaultClient.Query(query, dst, connectServerArgs...) |
|
} |
|
return DefaultClient.SWbemServicesClient.Query(query, dst, connectServerArgs...) |
|
} |
|
|
|
// A Client is an WMI query client. |
|
// |
|
// Its zero value (DefaultClient) is a usable client. |
|
type Client struct { |
|
// NonePtrZero specifies if nil values for fields which aren't pointers |
|
// should be returned as the field types zero value. |
|
// |
|
// Setting this to true allows stucts without pointer fields to be used |
|
// without the risk failure should a nil value returned from WMI. |
|
NonePtrZero bool |
|
|
|
// PtrNil specifies if nil values for pointer fields should be returned |
|
// as nil. |
|
// |
|
// Setting this to true will set pointer fields to nil where WMI |
|
// returned nil, otherwise the types zero value will be returned. |
|
PtrNil bool |
|
|
|
// AllowMissingFields specifies that struct fields not present in the |
|
// query result should not result in an error. |
|
// |
|
// Setting this to true allows custom queries to be used with full |
|
// struct definitions instead of having to define multiple structs. |
|
AllowMissingFields bool |
|
|
|
// SWbemServiceClient is an optional SWbemServices object that can be |
|
// initialized and then reused across multiple queries. If it is null |
|
// then the method will initialize a new temporary client each time. |
|
SWbemServicesClient *SWbemServices |
|
} |
|
|
|
// DefaultClient is the default Client and is used by Query, QueryNamespace |
|
var DefaultClient = &Client{} |
|
|
|
// Query runs the WQL query and appends the values to dst. |
|
// |
|
// dst must have type *[]S or *[]*S, for some struct type S. Fields selected in |
|
// the query must have the same name in dst. Supported types are all signed and |
|
// unsigned integers, time.Time, string, bool, or a pointer to one of those. |
|
// Array types are not supported. |
|
// |
|
// By default, the local machine and default namespace are used. These can be |
|
// changed using connectServerArgs. See |
|
// http://msdn.microsoft.com/en-us/library/aa393720.aspx for details. |
|
func (c *Client) Query(query string, dst interface{}, connectServerArgs ...interface{}) error { |
|
dv := reflect.ValueOf(dst) |
|
if dv.Kind() != reflect.Ptr || dv.IsNil() { |
|
return ErrInvalidEntityType |
|
} |
|
dv = dv.Elem() |
|
mat, elemType := checkMultiArg(dv) |
|
if mat == multiArgTypeInvalid { |
|
return ErrInvalidEntityType |
|
} |
|
|
|
lock.Lock() |
|
defer lock.Unlock() |
|
runtime.LockOSThread() |
|
defer runtime.UnlockOSThread() |
|
|
|
err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED) |
|
if err != nil { |
|
oleCode := err.(*ole.OleError).Code() |
|
if oleCode != ole.S_OK && oleCode != S_FALSE { |
|
return err |
|
} |
|
} |
|
defer ole.CoUninitialize() |
|
|
|
unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator") |
|
if err != nil { |
|
return err |
|
} else if unknown == nil { |
|
return ErrNilCreateObject |
|
} |
|
defer unknown.Release() |
|
|
|
wmi, err := unknown.QueryInterface(ole.IID_IDispatch) |
|
if err != nil { |
|
return err |
|
} |
|
defer wmi.Release() |
|
|
|
// service is a SWbemServices |
|
serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", connectServerArgs...) |
|
if err != nil { |
|
return err |
|
} |
|
service := serviceRaw.ToIDispatch() |
|
defer serviceRaw.Clear() |
|
|
|
// result is a SWBemObjectSet |
|
resultRaw, err := oleutil.CallMethod(service, "ExecQuery", query) |
|
if err != nil { |
|
return err |
|
} |
|
result := resultRaw.ToIDispatch() |
|
defer resultRaw.Clear() |
|
|
|
count, err := oleInt64(result, "Count") |
|
if err != nil { |
|
return err |
|
} |
|
|
|
enumProperty, err := result.GetProperty("_NewEnum") |
|
if err != nil { |
|
return err |
|
} |
|
defer enumProperty.Clear() |
|
|
|
enum, err := enumProperty.ToIUnknown().IEnumVARIANT(ole.IID_IEnumVariant) |
|
if err != nil { |
|
return err |
|
} |
|
if enum == nil { |
|
return fmt.Errorf("can't get IEnumVARIANT, enum is nil") |
|
} |
|
defer enum.Release() |
|
|
|
// Initialize a slice with Count capacity |
|
dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count))) |
|
|
|
var errFieldMismatch error |
|
for itemRaw, length, err := enum.Next(1); length > 0; itemRaw, length, err = enum.Next(1) { |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err := func() error { |
|
// item is a SWbemObject, but really a Win32_Process |
|
item := itemRaw.ToIDispatch() |
|
defer item.Release() |
|
|
|
ev := reflect.New(elemType) |
|
if err = c.loadEntity(ev.Interface(), item); err != nil { |
|
if _, ok := err.(*ErrFieldMismatch); ok { |
|
// We continue loading entities even in the face of field mismatch errors. |
|
// If we encounter any other error, that other error is returned. Otherwise, |
|
// an ErrFieldMismatch is returned. |
|
errFieldMismatch = err |
|
} else { |
|
return err |
|
} |
|
} |
|
if mat != multiArgTypeStructPtr { |
|
ev = ev.Elem() |
|
} |
|
dv.Set(reflect.Append(dv, ev)) |
|
return nil |
|
}() |
|
if err != nil { |
|
return err |
|
} |
|
} |
|
return errFieldMismatch |
|
} |
|
|
|
// ErrFieldMismatch is returned when a field is to be loaded into a different |
|
// type than the one it was stored from, or when a field is missing or |
|
// unexported in the destination struct. |
|
// StructType is the type of the struct pointed to by the destination argument. |
|
type ErrFieldMismatch struct { |
|
StructType reflect.Type |
|
FieldName string |
|
Reason string |
|
} |
|
|
|
func (e *ErrFieldMismatch) Error() string { |
|
return fmt.Sprintf("wmi: cannot load field %q into a %q: %s", |
|
e.FieldName, e.StructType, e.Reason) |
|
} |
|
|
|
var timeType = reflect.TypeOf(time.Time{}) |
|
|
|
// loadEntity loads a SWbemObject into a struct pointer. |
|
func (c *Client) loadEntity(dst interface{}, src *ole.IDispatch) (errFieldMismatch error) { |
|
v := reflect.ValueOf(dst).Elem() |
|
for i := 0; i < v.NumField(); i++ { |
|
f := v.Field(i) |
|
of := f |
|
isPtr := f.Kind() == reflect.Ptr |
|
if isPtr { |
|
ptr := reflect.New(f.Type().Elem()) |
|
f.Set(ptr) |
|
f = f.Elem() |
|
} |
|
n := v.Type().Field(i).Name |
|
if !f.CanSet() { |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "CanSet() is false", |
|
} |
|
} |
|
prop, err := oleutil.GetProperty(src, n) |
|
if err != nil { |
|
if !c.AllowMissingFields { |
|
errFieldMismatch = &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "no such struct field", |
|
} |
|
} |
|
continue |
|
} |
|
defer prop.Clear() |
|
|
|
if prop.Value() == nil { |
|
continue |
|
} |
|
|
|
switch val := prop.Value().(type) { |
|
case int8, int16, int32, int64, int: |
|
v := reflect.ValueOf(val).Int() |
|
switch f.Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
f.SetInt(v) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
f.SetUint(uint64(v)) |
|
default: |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "not an integer class", |
|
} |
|
} |
|
case uint8, uint16, uint32, uint64: |
|
v := reflect.ValueOf(val).Uint() |
|
switch f.Kind() { |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
f.SetInt(int64(v)) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
f.SetUint(v) |
|
default: |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "not an integer class", |
|
} |
|
} |
|
case string: |
|
switch f.Kind() { |
|
case reflect.String: |
|
f.SetString(val) |
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: |
|
iv, err := strconv.ParseInt(val, 10, 64) |
|
if err != nil { |
|
return err |
|
} |
|
f.SetInt(iv) |
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: |
|
uv, err := strconv.ParseUint(val, 10, 64) |
|
if err != nil { |
|
return err |
|
} |
|
f.SetUint(uv) |
|
case reflect.Struct: |
|
switch f.Type() { |
|
case timeType: |
|
if len(val) == 25 { |
|
mins, err := strconv.Atoi(val[22:]) |
|
if err != nil { |
|
return err |
|
} |
|
val = val[:22] + fmt.Sprintf("%02d%02d", mins/60, mins%60) |
|
} |
|
t, err := time.Parse("20060102150405.000000-0700", val) |
|
if err != nil { |
|
return err |
|
} |
|
f.Set(reflect.ValueOf(t)) |
|
} |
|
} |
|
case bool: |
|
switch f.Kind() { |
|
case reflect.Bool: |
|
f.SetBool(val) |
|
default: |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "not a bool", |
|
} |
|
} |
|
case float32: |
|
switch f.Kind() { |
|
case reflect.Float32: |
|
f.SetFloat(float64(val)) |
|
default: |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: "not a Float32", |
|
} |
|
} |
|
default: |
|
if f.Kind() == reflect.Slice { |
|
switch f.Type().Elem().Kind() { |
|
case reflect.String: |
|
safeArray := prop.ToArray() |
|
if safeArray != nil { |
|
arr := safeArray.ToValueArray() |
|
fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr)) |
|
for i, v := range arr { |
|
s := fArr.Index(i) |
|
s.SetString(v.(string)) |
|
} |
|
f.Set(fArr) |
|
} |
|
case reflect.Uint8: |
|
safeArray := prop.ToArray() |
|
if safeArray != nil { |
|
arr := safeArray.ToValueArray() |
|
fArr := reflect.MakeSlice(f.Type(), len(arr), len(arr)) |
|
for i, v := range arr { |
|
s := fArr.Index(i) |
|
s.SetUint(reflect.ValueOf(v).Uint()) |
|
} |
|
f.Set(fArr) |
|
} |
|
default: |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: fmt.Sprintf("unsupported slice type (%T)", val), |
|
} |
|
} |
|
} else { |
|
typeof := reflect.TypeOf(val) |
|
if typeof == nil && (isPtr || c.NonePtrZero) { |
|
if (isPtr && c.PtrNil) || (!isPtr && c.NonePtrZero) { |
|
of.Set(reflect.Zero(of.Type())) |
|
} |
|
break |
|
} |
|
return &ErrFieldMismatch{ |
|
StructType: of.Type(), |
|
FieldName: n, |
|
Reason: fmt.Sprintf("unsupported type (%T)", val), |
|
} |
|
} |
|
} |
|
} |
|
return errFieldMismatch |
|
} |
|
|
|
type multiArgType int |
|
|
|
const ( |
|
multiArgTypeInvalid multiArgType = iota |
|
multiArgTypeStruct |
|
multiArgTypeStructPtr |
|
) |
|
|
|
// checkMultiArg checks that v has type []S, []*S for some struct type S. |
|
// |
|
// It returns what category the slice's elements are, and the reflect.Type |
|
// that represents S. |
|
func checkMultiArg(v reflect.Value) (m multiArgType, elemType reflect.Type) { |
|
if v.Kind() != reflect.Slice { |
|
return multiArgTypeInvalid, nil |
|
} |
|
elemType = v.Type().Elem() |
|
switch elemType.Kind() { |
|
case reflect.Struct: |
|
return multiArgTypeStruct, elemType |
|
case reflect.Ptr: |
|
elemType = elemType.Elem() |
|
if elemType.Kind() == reflect.Struct { |
|
return multiArgTypeStructPtr, elemType |
|
} |
|
} |
|
return multiArgTypeInvalid, nil |
|
} |
|
|
|
func oleInt64(item *ole.IDispatch, prop string) (int64, error) { |
|
v, err := oleutil.GetProperty(item, prop) |
|
if err != nil { |
|
return 0, err |
|
} |
|
defer v.Clear() |
|
|
|
i := int64(v.Val) |
|
return i, nil |
|
} |
|
|
|
// CreateQuery returns a WQL query string that queries all columns of src. where |
|
// is an optional string that is appended to the query, to be used with WHERE |
|
// clauses. In such a case, the "WHERE" string should appear at the beginning. |
|
func CreateQuery(src interface{}, where string) string { |
|
var b bytes.Buffer |
|
b.WriteString("SELECT ") |
|
s := reflect.Indirect(reflect.ValueOf(src)) |
|
t := s.Type() |
|
if s.Kind() == reflect.Slice { |
|
t = t.Elem() |
|
} |
|
if t.Kind() != reflect.Struct { |
|
return "" |
|
} |
|
var fields []string |
|
for i := 0; i < t.NumField(); i++ { |
|
fields = append(fields, t.Field(i).Name) |
|
} |
|
b.WriteString(strings.Join(fields, ", ")) |
|
b.WriteString(" FROM ") |
|
b.WriteString(t.Name()) |
|
b.WriteString(" " + where) |
|
return b.String() |
|
}
|
|
|