Query WMI from Go
Asked Answered
C

4

15

I would like to run WMI queries from Go. There are ways to call DLL functions from Go. My understanding is that there must be some DLL somewhere which, with the correct call, will return some data I can parse and use. I'd prefer to avoid calling into C or C++, especially since I would guess those are wrappers over the Windows API itself.

I've examined the output of dumpbin.exe /exports c:\windows\system32\wmi.dll, and the following entry looks promising:

WmiQueryAllDataA (forwarded to wmiclnt.WmiQueryAllDataA)

However I'm not sure what to do from here. What arguments does this function take? What does it return? Searching for WmiQueryAllDataA is not helpful. And that name only appears in a comment of c:\program files (x86)\windows kits\8.1\include\shared\wmistr.h, but with no function signature.

Are there better methods? Is there another DLL? Am I missing something? Should I just use a C wrapper?

Running a WMI query in Linqpad with .NET Reflector shows the use of WmiNetUtilsHelper:ExecQueryWmi (and a _f version), but neither have a viewable implementation.

Update: use the github.com/StackExchange/wmi package which uses the solution in the accepted answer.

Crossjack answered 4/12, 2013 at 1:44 Comment(1)
Kevin's answer below is now implemented in a Go package on Github. See godoc.org/github.com/StackExchange/wmiGermanophobe
L
22

Welcome to the wonderful world of COM, Object Oriented Programming in C from when C++ was "a young upstart".

On github mattn has thrown together a little wrapper in Go, which I used to throw together a quick example program. "This repository was created for experimentation and should be considered unstable." instills all sorts of confidence.

I'm leaving out a lot of error checking. Trust me when I say, you'll want to add it back.

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())
    }
}

The real meat is the call to ExecQuery, I happen to grab Win32_Process from the available classes because it's easy to understand and print.

On my machine, this prints:

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

I'm not running it elevated or with UAC disabled, but some WMI providers are gonna require a privileged user.

I'm also not 100% that this won't leak a little, you'll want to dig into that. COM objects are reference counted, so defer should be a pretty good fit there (provided the method isn't crazy long running) but go-ole may have some magic inside I didn't notice.

Lick answered 4/12, 2013 at 4:0 Comment(8)
Is using COM the only way to do this? Although the above works, is it possible to call a DLL directly?Crossjack
@mjibson Everything's wrapping calls to COM, it's the native interface to WMI. (See: msdn.microsoft.com/en-us/library/aa384642(v=vs.85).aspx; in particular "All the WMI interfaces are based on the Component Object Model (COM)")Lick
@mjibson That being said, there are a few languages that have facilities for more easily using COM. Like C# :) . To the best of my knowledge, unless you want to work in a .NET language, or C++, you're stuck doing some of the COM dance "C-style". The particular method you noticed is undocumented (and that A at the end is pretty scary too).Lick
.NET > 2 was ruled out due to problems with having it installed with SQL Server, so it was decided to attempt a non .NET language. Also considered: running wmic.exe and parsing its output. Was told this doesn't get you quite the power you need.Crossjack
@mjibson Yeah, I'm not seriously suggesting a tech change. COM can work with just about any language by design, but it certainly isn't very pretty in most of them.Lick
My eyes are crying blood. Your go looks like VBScript (no offense) :-)Formulary
@mjibson, BTW you might consider writing your program in VBS or JScript through WSH (Windows Scripting Host) -- this platform has support for transparent weakly-typed COM access since ages, and it's built into any sensible version of Windows. Google for "WMI+VBS" to get the idea. (I, personally, am a Go fan but I'm inclined to pick the best tool for the job).Fable
Have a look at the open-source "WMI Delphi Code Creator", which makes it simple to write basic to advanced code to interact with WMI in Delphi, C#, Microsoft C++, C++ Builder, FreePascal, and Oxygene. github.com/RRUZ/wmi-delphi-code-creatorShelving
S
8

I'm commenting over a year later, but there is a solution here on github (and posted below for posterity).

// +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()
}
Shelving answered 5/2, 2015 at 20:41 Comment(0)
H
2

To access the winmgmts object or a namespace (which is the same), you can use the code below. Basically, you need to specify the namespace as parameter, which is not documented properly in go-ole.

In the code below, you can also see how to access a class within this namespace and execute a method.

package main

import (
    "log"

    "github.com/go-ole/go-ole"
    "github.com/go-ole/go-ole/oleutil"
)

func main() {
    ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED)
    defer ole.CoUninitialize()

    unknown, err := oleutil.CreateObject("WbemScripting.SWbemLocator")
    if err != nil {
        log.Panic(err)
    }
    defer unknown.Release()

    wmi, err := unknown.QueryInterface(ole.IID_IDispatch)
    if err != nil {
        log.Panic(err)
    }
    defer wmi.Release()

    // Connect to namespace
    // root/PanasonicPC = winmgmts:\\.\root\PanasonicPC
    serviceRaw, err := oleutil.CallMethod(wmi, "ConnectServer", nil, "root/PanasonicPC")
    if err != nil {
        log.Panic(err)
    }
    service := serviceRaw.ToIDispatch()
    defer serviceRaw.Clear()

    // Get class
    setBiosRaw, err := oleutil.CallMethod(service, "Get", "SetBIOS4Conf")
    if err != nil {
        log.Panic(err)
    }
    setBios := setBiosRaw.ToIDispatch()
    defer setBiosRaw.Clear()

    // Run method
    resultRaw, err := oleutil.CallMethod(setBios, "AccessAuthorization", "letmein")
    resultVal := resultRaw.Value().(int32)

    log.Println("Return Code:", resultVal)
}
Hebner answered 6/11, 2018 at 10:57 Comment(0)
T
-1
import(
  "os/exec"
)

 
 ​func​ (​lcu​ ​*​LCU​) ​GrabToken​() { 
 ​        ​cmd​ ​:=​ ​exec​.​Command​(​"powershell"​, ​"$cmdline = Get-WmiObject -Class Win32_Process"​) 
 ​        ​
 ​        ​out​, ​err​ ​:=​ ​cmd​.​CombinedOutput​() 
 ​        ​if​ ​err​ ​!=​ ​nil​ { 
 ​                ​fmt​.​Println​(​err​) 
 ​        } 
 ​        ​outstr​ ​:=​ ​string(out)
 ​}
Thusly answered 19/11, 2021 at 3:51 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Pentateuch

© 2022 - 2024 — McMap. All rights reserved.