Pytanie Zapytanie WMI od Go


Chciałbym uruchomić kwerendy WMI z Go. Są sposoby na to wywołać funkcje DLL z Go. Rozumiem, że gdzieś musi istnieć jakaś biblioteka DLL, która przy prawidłowym wywołaniu zwróci niektóre dane, które mogę przeanalizować i wykorzystać. Wolałbym unikać wywoływania w C lub C ++, zwłaszcza, że ​​przypuszczam, że są one owijkami w samym interfejsie API systemu Windows.

Zbadałem wyjście z dumpbin.exe /exports c:\windows\system32\wmi.dll, a następujący wpis wygląda obiecująco:

WmiQueryAllDataA (forwarded to wmiclnt.WmiQueryAllDataA)

Jednak nie jestem pewien, co robić dalej. Jakie argumenty przyjmuje ta funkcja? Co zwraca? Szukać WmiQueryAllDataA nie jest pomocne. A to imię pojawia się tylko w komentarzu c:\program files (x86)\windows kits\8.1\include\shared\wmistr.h, ale bez podpisu funkcji.

Czy istnieją lepsze metody? Czy istnieje inna biblioteka DLL? Czy czegoś brakuje? Czy powinienem używać tylko opakowania C?

Uruchamianie kwerendy WMI w Linqpad z .NET Reflector pokazuje użycie WmiNetUtilsHelper:ExecQueryWmi (i _f wersja), ale żadna nie ma implementacji widocznej.

Aktualizacja: Użyj github.com/StackExchange/wmi pakiet, który używa rozwiązania w zaakceptowanej odpowiedzi.


14
2017-12-04 01:44


pochodzenie


Odpowiedź Kevina poniżej jest teraz zaimplementowana w pakiecie Go na Github. Widzieć godoc.org/github.com/StackExchange/wmi - Greg Bray


Odpowiedzi:


Witaj w cudownym świecie COM, Programowanie obiektowe w C od czasu, gdy C ++ było "młodym upstartem".

Na github mattn rzucił razem małe opakowanie w Go, z których korzystałem, aby szybko przygotować przykładowy program. "To repozytorium zostało stworzone do eksperymentów i powinno być uznane za niestabilne."wpaja wszelkie rodzaje zaufania.

Pomijam wiele błędów. Zaufaj mi, gdy powiem, że będziesz chciał go dodać.

package main

import (
        "github.com/mattn/go-ole"
        "github.com/mattn/go-ole/oleutil"
)

func main() {
    // init COM, oh yeah
    ole.CoInitialize(0)
    defer ole.CoUninitialize()

    unknown, _ := oleutil.CreateObject("WbemScripting.SWbemLocator")
    defer unknown.Release()

    wmi, _ := unknown.QueryInterface(ole.IID_IDispatch)
    defer wmi.Release()

    // service is a SWbemServices
    serviceRaw, _ := oleutil.CallMethod(wmi, "ConnectServer")
    service := serviceRaw.ToIDispatch()
    defer service.Release()

    // result is a SWBemObjectSet
    resultRaw, _ := oleutil.CallMethod(service, "ExecQuery", "SELECT * FROM Win32_Process")
    result := resultRaw.ToIDispatch()
    defer result.Release()

    countVar, _ := oleutil.GetProperty(result, "Count")
    count := int(countVar.Val)

    for i :=0; i < count; i++ {
        // item is a SWbemObject, but really a Win32_Process
        itemRaw, _ := oleutil.CallMethod(result, "ItemIndex", i)
        item := itemRaw.ToIDispatch()
        defer item.Release()

        asString, _ := oleutil.GetProperty(item, "Name")

        println(asString.ToString())
    }
}

Prawdziwe mięso to wezwanie ExecQuery, Zdarza mi się złapać Win32_Process z dostępne klasy ponieważ jest łatwy do zrozumienia i drukowania.

Na moim komputerze to drukuje:

System Idle Process
System
smss.exe
csrss.exe
wininit.exe
services.exe
lsass.exe
svchost.exe
svchost.exe
atiesrxx.exe
svchost.exe
svchost.exe
svchost.exe
svchost.exe
svchost.exe
spoolsv.exe
svchost.exe
AppleOSSMgr.exe
AppleTimeSrv.exe
... and so on
go.exe
main.exe

Nie korzystam z niego z podniesionym lub wyłączonym UAC, ale niektórzy dostawcy WMI będą wymagać uprzywilejowanego użytkownika.

Nie jestem też w 100%, że to nie wycieknie, będziesz chciał się w to zagłębić. Obiekty COM są zliczane, więc defer powinno być tam całkiem niezłe (pod warunkiem, że metoda nie jest szalona od dawna działająca), ale go-ole może mieć trochę magii w środku, którego nie zauważyłem.


18
2017-12-04 04:00



Czy użycie COM jest jedynym sposobem, aby to zrobić? Chociaż powyższe działa, czy możliwe jest wywołanie biblioteki DLL bezpośrednio? - mjibson
@mjibson Wszystko zawija połączenia do COM, to natywny interfejs do WMI. (Widzieć: msdn.microsoft.com/en-us/library/aa384642(v=vs.85).aspx; w szczególności "Wszystkie interfejsy WMI oparte są na modelu COM (Component Object Model)") - Kevin Montrose♦
@mjibson To powiedziawszy, jest kilka języków, które mają ułatwienia w korzystaniu z COM. Jak C # :). Zgodnie z moją wiedzą, jeśli nie chcesz pracować w języku .NET lub C ++, utkniesz w tańcu COM "C-style". Zauważyłeś szczególną metodę nieudokumentowany (i że A na końcu też jest dość przerażające). - Kevin Montrose♦
.NET> 2 został wykluczony z powodu problemów z zainstalowaniem go z SQL Server, więc zdecydowano się na próbę języka innego niż .NET. Rozważono również: uruchamianie programu Wmic.exe i analizowanie jego wyników. Powiedziano mi, że to nie daje ci wystarczającej mocy, jakiej potrzebujesz. - mjibson
@mjibson Tak, nie sugeruję poważnie zmiany technologii. COM może pracować z dowolnym językiem według projektu, ale z pewnością nie jest bardzo ładny w większości z nich. - Kevin Montrose♦


Komentuję ponad rok później, ale istnieje rozwiązanie na Github (i zamieszczone poniżej dla potomności).

// +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/mattn/go-ole"
    "github.com/mattn/go-ole/oleutil"
)

var l = log.New(os.Stdout, "", log.LstdFlags)

var (
    ErrInvalidEntityType = errors.New("wmi: invalid entity type")
    lock                 sync.Mutex
)

// 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.
func 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 {
        oleerr := err.(*ole.OleError)
        // S_FALSE           = 0x00000001 // CoInitializeEx was already called on this thread
        if oleerr.Code() != ole.S_OK && oleerr.Code() != 0x00000001 {
            return err
        }
    } else {
        // Only invoke CoUninitialize if the thread was not initizlied before.
        // This will allow other go packages based on go-ole play along
        // with this library.
        defer ole.CoUninitialize()
    }

    unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
    if err != nil {
        return err
    }
    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
    }

    // Initialize a slice with Count capacity
    dv.Set(reflect.MakeSlice(dv.Type(), 0, int(count)))

    var errFieldMismatch error
    for i := int64(0); i < count; i++ {
        err := func() error {
            // item is a SWbemObject, but really a Win32_Process
            itemRaw, err := oleutil.CallMethod(result, "ItemIndex", i)
            if err != nil {
                return err
            }
            item := itemRaw.ToIDispatch()
            defer itemRaw.Clear()

            ev := reflect.New(elemType)
            if err = 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 loadEntity(dst interface{}, src *ole.IDispatch) (errFieldMismatch error) {
    v := reflect.ValueOf(dst).Elem()
    for i := 0; i < v.NumField(); i++ {
        f := v.Field(i)
        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: f.Type(),
                FieldName:  n,
                Reason:     "CanSet() is false",
            }
        }
        prop, err := oleutil.GetProperty(src, n)
        if err != nil {
            errFieldMismatch = &ErrFieldMismatch{
                StructType: f.Type(),
                FieldName:  n,
                Reason:     "no such struct field",
            }
            continue
        }
        defer prop.Clear()

        switch val := prop.Value().(type) {
        case int, int64:
            var v int64
            switch val := val.(type) {
            case int:
                v = int64(val)
            case int64:
                v = val
            default:
                panic("unexpected type")
            }
            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: f.Type(),
                    FieldName:  n,
                    Reason:     "not an integer class",
                }
            }
        case string:
            iv, err := strconv.ParseInt(val, 10, 64)
            switch f.Kind() {
            case reflect.String:
                f.SetString(val)
            case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
                if err != nil {
                    return err
                }
                f.SetInt(iv)
            case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
                if err != nil {
                    return err
                }
                f.SetUint(uint64(iv))
            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: f.Type(),
                    FieldName:  n,
                    Reason:     "not a bool",
                }
            }
        default:
            typeof := reflect.TypeOf(val)
            if isPtr && typeof == nil {
                break
            }
            return &ErrFieldMismatch{
                StructType: f.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()
}

5
2018-02-05 20:41