Query WMI from Go

mjibson picture mjibson · Dec 4, 2013 · Viewed 9.1k times · Source

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.

Answer

Kevin Montrose picture Kevin Montrose · Dec 4, 2013

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.