forked from StackExchange/wmi
-
Notifications
You must be signed in to change notification settings - Fork 3
/
main.go
181 lines (160 loc) · 4.84 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package main
// In the example we are going to track some events happen on WMI subscriptions.
// This is a good way to show tricky cases in WMI results decoding.
import (
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"unsafe"
"github.com/bi-zone/wmi"
"github.com/go-ole/go-ole"
)
// Notifications source in "root\subscription".
// Important thing here is that we can get an event of 3 different types.
var query = `
SELECT * FROM __InstanceOperationEvent
WITHIN 5
WHERE
TargetInstance ISA '__EventFilter'
OR TargetInstance ISA '__FilterToConsumerBinding'
`
// wmiEvent has a straightforward implementation. The only non-usual thing here
// is access to the system property `Path_.Class`.
type wmiEvent struct {
TimeStamp uint64 `wmi:"TIME_CREATED"`
System struct {
Class string
} `wmi:"Path_"`
Instance instance `wmi:"TargetInstance"`
}
// `TargetInstance` property in `__InstanceOperationEvent` can contain one of
// 2 different classes (cos os our query). To handle this in statically typed
// language we could either use an interface or add all of them and fill the
// only one.
//
// Lets create a field for every result type + `.Class` field to select the
// right one.
type instance struct {
Class string
CreatorSID string
EventFilter *eventFilter `json:",omitempty"`
EventBinding *eventFilterBinding `json:",omitempty"`
}
// UnmarshalOLE extracts system properties of the resulting object and then
// unmarshalls the result into the proper `instance` field.
func (i *instance) UnmarshalOLE(d wmi.Decoder, src *ole.IDispatch) error {
// Here is a temp object for the fields common for both classes.
var commonProps struct {
System struct {
Class string
} `wmi:"Path_"`
CreatorSID []byte
}
if err := d.Unmarshal(src, &commonProps); err != nil {
return err
}
sid, err := unmarshalSID(commonProps.CreatorSID)
if err != nil {
return err
}
i.Class = commonProps.System.Class
i.CreatorSID = sid
// And here we unmarshal the right class based on the `class` string from
// the object system property.
switch i.Class {
case "__EventFilter":
i.EventFilter = &eventFilter{}
return d.Unmarshal(src, i.EventFilter)
case "__FilterToConsumerBinding":
i.EventBinding = &eventFilterBinding{}
return d.Unmarshal(src, i.EventBinding)
}
return fmt.Errorf("unknown target class %q", i.Class)
}
// Golang-core mad skillz.
// If you know a better way to unmarshal []byte SID - please open a PR.
func unmarshalSID(sid []byte) (string, error) {
p := unsafe.Pointer(&sid[0])
s := (*syscall.SID)(p)
return s.String()
}
// eventFilter is a simple struct with common fields.
type eventFilter struct {
Name string
EventNamespace string
Query string
QueryLanguage string
}
// eventFilterBinding has 2 reference fields, which is a bit more tricky.
type eventFilterBinding struct {
Consumer eventConsumer `wmi:",ref"`
Filter eventFilter `wmi:",ref"`
}
// eventConsumer is never returned as is - it is always some descendant, so
// the best thing we could do - extract a Type name.
type eventConsumer struct {
Type string `wmi:"-"`
}
func (e *eventConsumer) UnmarshalOLE(d wmi.Decoder, src *ole.IDispatch) error {
var systemProps struct {
Path struct {
Class string
} `wmi:"Path_"`
}
if err := d.Unmarshal(src, &systemProps); err != nil {
return err
}
e.Type = systemProps.Path.Class
return nil
}
// To produce an event you could use a powershell script, e.g.
// #Creating a new event filter
// $ServiceFilter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
//
// # Set the properties of the instance
// $ServiceFilter.QueryLanguage = 'WQL'
// $ServiceFilter.Query = "select * from __instanceModificationEvent within 5 where targetInstance isa 'win32_Service'"
// $ServiceFilter.Name = "ServiceFilter"
// $ServiceFilter.EventNamespace = 'root\cimv2'
//
// # Sets the instance in the namespace
// $FilterResult = $ServiceFilter.Put()
// $ServiceFilterObj = $FilterResult.Path
func main() {
events := make(chan wmiEvent)
q, err := wmi.NewNotificationQuery(events, query)
if err != nil {
log.Fatalf("Failed to create NotificationQuery; %s", err)
}
// Set namespace.
q.SetConnectServerArgs(nil, `root\subscription`)
// Set exit hook
sigs := make(chan os.Signal, 1)
done := make(chan error, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
done <- q.StartNotifications()
}()
log.Println("Listening for events")
for {
select {
case ev := <-events:
data, err := json.MarshalIndent(ev, "", " ")
if err != nil {
log.Printf("[ERR] Failed to marshal event; %s", err)
} else {
log.Println(string(data))
}
case sig := <-sigs:
log.Printf("Got system signal %s; stopping", sig)
q.Stop()
return
case err := <-done: // Query will never stop here w/o error.
log.Printf("[ERR] Got StartNotifications error; %s", err)
return
}
}
}