从 Go 查询 WMI
Posted
技术标签:
【中文标题】从 Go 查询 WMI【英文标题】:Query WMI from Go 【发布时间】:2013-12-20 08:16:06 【问题描述】:我想从 Go 运行 WMI 查询。有很多方法可以从 Go 中call DLL functions。我的理解是某个地方必须有一些 DLL,通过正确的调用,它会返回一些我可以解析和使用的数据。我宁愿避免调用 C 或 C++,尤其是因为我猜它们是 Windows API 本身的包装器。
我检查了dumpbin.exe /exports c:\windows\system32\wmi.dll
的输出,以下条目看起来很有希望:
WmiQueryAllDataA (forwarded to wmiclnt.WmiQueryAllDataA)
但是我不知道从这里做什么。这个函数接受什么参数?它返回什么?搜索 WmiQueryAllDataA
没有帮助。而且这个名字只出现在c:\program files (x86)\windows kits\8.1\include\shared\wmistr.h
的评论中,没有函数签名。
有更好的方法吗?是否有另一个DLL?我错过了什么吗?我应该只使用 C 包装器吗?
使用 .NET Reflector 在 Linqpad 中运行 WMI 查询显示使用了 WmiNetUtilsHelper:ExecQueryWmi
(和 _f
版本),但都没有可见的实现。
更新:使用github.com/StackExchange/wmi 包,它使用已接受答案中的解决方案。
【问题讨论】:
Kevin 下面的答案现在在 Github 上的 Go 包中实现。见godoc.org/github.com/StackExchange/wmi 【参考方案1】:欢迎来到COM 的精彩世界,C++ 中的面向对象编程从 C++ 是“一个年轻的新贵”开始。
在 github 上mattn 已经拼凑了一个little wrapper in Go,我用它拼凑了一个快速示例程序。 “这个存储库是为实验而创建的,应该被认为是不稳定的。”灌输了各种信心。
我省略了很多错误检查。当我说的时候相信我,你会想把它加回来的。
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())
真正的肉是对ExecQuery的调用,我碰巧从available classes中抓取了Win32_Process,因为它易于理解和打印。
在我的机器上,打印:
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
我没有在提升或禁用 UAC 的情况下运行它,但一些 WMI 提供程序需要特权用户。
我也不是 100% 不会泄漏一点,你会想深入研究的。 COM 对象是引用计数的,所以 defer
应该非常适合那里(前提是该方法不是疯狂长时间运行)但 go-ole 可能有一些我没有注意到的魔法。
【讨论】:
使用 COM 是唯一的方法吗?虽然上述方法可行,但是否可以直接调用DLL? @mjibson 对 COM 的所有包装调用,它是 WMI 的本机接口。 (参见:msdn.microsoft.com/en-us/library/aa384642(v=vs.85).aspx;特别是“所有 WMI 接口都基于组件对象模型 (COM)”) @mjibson 话虽如此,有一些语言可以更轻松地使用 COM。像 C# :) 。据我所知,除非您想使用 .NET 语言或 C++ 工作,否则您会被困在做一些 COM 舞蹈“C 风格”。你注意到的特定方法是undocumented(最后的 A 也很可怕)。 .NET > 2 由于与 SQL Server 一起安装的问题而被排除,因此决定尝试非 .NET 语言。还考虑:运行 wmic.exe 并解析其输出。有人告诉您,这并不能让您获得所需的力量。 @mjibson 是的,我并不是在认真地建议进行技术变革。 COM 在设计上几乎可以与任何语言一起使用,但在大多数语言中它肯定不是很漂亮。【参考方案2】:我在一年多后发表评论,但there is a solution here on github(并在下面发布以供后代使用)。
// +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()
【讨论】:
【参考方案3】:要访问winmgmts
对象或命名空间(相同),您可以使用下面的代码。基本上,您需要将命名空间指定为参数,这在go-ole
中没有正确记录。
在下面的代码中,您还可以看到如何访问此命名空间内的类并执行方法。
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)
【讨论】:
【参考方案4】: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)
【讨论】:
您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center。以上是关于从 Go 查询 WMI的主要内容,如果未能解决你的问题,请参考以下文章
WQL 子查询作为字段值,CIMV2 WMI WQL 查询用于 WMI-Filter
是否可以在远程计算机上为 MicrosoftTPM 命名空间查询 WMI? [对 Win32_Tpm 类的远程 WMI 查询失败,HRESULT 0x80041013]