123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490 |
- // +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()
- }
|