Skip to content

Commit

Permalink
Windows Update: query info with configurable interval
Browse files Browse the repository at this point in the history
  • Loading branch information
requilence committed Oct 8, 2018
1 parent 723fb2d commit 4aaa72e
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 22 deletions.
6 changes: 6 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ type Cagent struct {

SystemFields []string `toml:"system_fields"`

WindowsUpdatesWatcherInterval int `toml:"windows_updates_watcher_interval"`

// internal use
hubHttpClient *http.Client

Expand Down Expand Up @@ -100,6 +102,10 @@ func New() *Cagent {
SystemFields: []string{"uname", "os_kernel", "os_family", "os_arch", "cpu_model", "fqdn", "memory_total_B"},
}

if runtime.GOOS == "windows" {
ca.WindowsUpdatesWatcherInterval = 3600
}

if rootCertsPath != "" {
if _, err := os.Stat(rootCertsPath); err == nil {
certPool := x509.NewCertPool()
Expand Down
5 changes: 4 additions & 1 deletion example.config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,7 @@ net_interface_exclude_loopback = true
net_metrics = ['in_B_per_s', 'out_B_per_s', 'errors_per_s','dropped_per_s'] # default['in_B_per_s', 'out_B_per_s']

# System
system_fields = ['uname','os_kernel','os_family','os_arch','cpu_model','fqdn','memory_total_B'] # default ['uname','os_kernel','os_family','os_arch','cpu_model','fqdn','memory_total_B']
system_fields = ['uname','os_kernel','os_family','os_arch','cpu_model','fqdn','memory_total_B'] # default ['uname','os_kernel','os_family','os_arch','cpu_model','fqdn','memory_total_B']

# Windows
windows_updates_watcher_interval = 3600 # default 3600
2 changes: 1 addition & 1 deletion handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ func (ca *Cagent) Run(outputFile *os.File, interrupt chan struct{}, once bool) {
if runtime.GOOS == "windows" {
wu, err := wuw.WindowsUpdates()

results.Measurements = results.Measurements.AddWithPrefix("windows.", wu)
results.Measurements = results.Measurements.AddWithPrefix("windows_update.", wu)

if err != nil {
// no need to log because already done inside MemResults()
Expand Down
97 changes: 79 additions & 18 deletions windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
package cagent

import (
"fmt"
"time"

"github.com/go-ole/go-ole"
Expand All @@ -22,39 +21,52 @@ const (
WUStatusAborted
)

func (ca *Cagent) WindowsUpdates() (MeasurementsMap, error) {
results := MeasurementsMap{}
type WindowsUpdateWatcher struct {
LastFetchedAt time.Time
Available int
Pending int
Err error

ca *Cagent
}

func windowsUpdates() (available int, pending int, err error) {
start := time.Now()
err := ole.CoInitializeEx(0, 0)
err = ole.CoInitializeEx(0, 0)
if err != nil {
log.Error("OLE CoInitializeEx: ", err.Error())
log.Error("[Windows Updates] OLE CoInitializeEx: ", err.Error())
}

defer ole.CoUninitialize()
mus, err := oleutil.CreateObject("Microsoft.Update.Session")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed to create Microsoft.Update.Session: %s", err.Error())
log.Errorf("[Windows Updates] Failed to create Microsoft.Update.Session: %s", err.Error())
return
}

defer mus.Release()
update, err := mus.QueryInterface(ole.IID_IDispatch)
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed to create QueryInterface: %s", err.Error())
log.Error("[Windows Updates] Failed to create QueryInterface:: ", err.Error())
return
}

defer update.Release()
oleutil.PutProperty(update, "ClientApplicationID", "Cagent")

us, err := oleutil.CallMethod(update, "CreateUpdateSearcher")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed CallMethod CreateUpdateSearcher: %s", err.Error())
log.Error("[Windows Updates] Failed CallMethod CreateUpdateSearcher: ", err.Error())
return
}

usd := us.ToIDispatch()
defer usd.Release()

usr, err := oleutil.CallMethod(usd, "Search", "IsInstalled=0 and Type='Software' and IsHidden=0")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed CallMethod Search: %s", err.Error())
log.Error("[Windows Updates] Failed CallMethod Search: ", err.Error())
return
}
log.Debugf("[Windows Updates] OLE query took %.1fs", time.Since(start).Seconds())

Expand All @@ -63,40 +75,44 @@ func (ca *Cagent) WindowsUpdates() (MeasurementsMap, error) {

upd, err := oleutil.GetProperty(usrd, "Updates")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed to get Updates property: %s", err.Error())
log.Error("[Windows Updates] Failed to get Updates property: ", err.Error())
return
}

updd := upd.ToIDispatch()
defer updd.Release()

updn, err := oleutil.GetProperty(updd, "Count")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed to get Count property: %s", err.Error())
log.Error("[Windows Updates] Failed to get Count property: ", err.Error())
return
}

results["updates_available"] = updn.Val
available = int(updn.Val)

thc, err := oleutil.CallMethod(usd, "GetTotalHistoryCount")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed CallMethod GetTotalHistoryCount: %s", err.Error())
log.Error("[Windows Updates] Failed CallMethod GetTotalHistoryCount: ", err.Error())
return
}

thcn := int(thc.Val)

uhistRaw, err := oleutil.CallMethod(usd, "QueryHistory", 0, thcn)
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed CallMethod QueryHistory: %s", err.Error())
log.Error("[Windows Updates] Failed CallMethod QueryHistory: ", err.Error())
return
}

uhist := uhistRaw.ToIDispatch()
defer uhist.Release()

countUhist, err := oleutil.GetProperty(uhist, "Count")
if err != nil {
return nil, fmt.Errorf("Windows Updates: Failed to get Count property: %s", err.Error())
log.Error("[Windows Updates] Failed to get Count property: ", err.Error())
return
}

pendingCount := 0
for i := 0; i < int(countUhist.Val); i++ {
itemRaw, err := oleutil.GetProperty(uhist, "Item", i)
if err != nil {
Expand All @@ -115,11 +131,56 @@ func (ca *Cagent) WindowsUpdates() (MeasurementsMap, error) {
}

if WindowsUpdateStatus(int(resultCode.Val)) == WUStatusPending {
pendingCount++
pending++
}
}
return
}

func (ca *Cagent) WindowsUpdatesWatcher() *WindowsUpdateWatcher {
wuw := &WindowsUpdateWatcher{ca: ca}

go func() {
for {
available, pending, err := windowsUpdates()
wuw.LastFetchedAt = time.Now()
wuw.Err = err
wuw.Available = available
wuw.Pending = pending

time.Sleep(time.Second * time.Duration(ca.WindowsUpdatesWatcherInterval))
}
}()

return wuw
}

func (wuw *WindowsUpdateWatcher) WindowsUpdates() (MeasurementsMap, error) {
results := MeasurementsMap{}
if wuw.LastFetchedAt.IsZero() {
results["updates_available"] = nil
results["updates_pending"] = nil
results["query_state"] = "pending"
results["query_timestamp"] = nil

return results, nil
}

log.Debugf("[Windows Updates] last time fetched: %.1f seconds ago", time.Since(wuw.LastFetchedAt).Seconds())
results["query_timestamp"] = wuw.LastFetchedAt.Unix()

if wuw.Err != nil {
results["updates_available"] = nil
results["updates_pending"] = nil
results["query_state"] = "failed"
results["query_message"] = wuw.Err.Error()

return results, nil
}

results["updates_needs_reboot"] = pendingCount
results["updates_available"] = wuw.Available
results["updates_pending"] = wuw.Pending
results["query_state"] = "succeeded"

return results, nil
}
10 changes: 8 additions & 2 deletions windows_fallback.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

package cagent

func (ca *Cagent) WindowsUpdates() (MeasurementsMap, error){
type EmptyWindowsUpdateWatcher struct{}

func (ca *Cagent) WindowsUpdatesWatcher() EmptyWindowsUpdateWatcher {
return EmptyWindowsUpdateWatcher{}
}

func (ca *EmptyWindowsUpdateWatcher) WindowsUpdates() (MeasurementsMap, error) {
return MeasurementsMap{}, nil
}
}

0 comments on commit 4aaa72e

Please sign in to comment.