ospatch/updates_windows.go (169 lines of code) (raw):
// Copyright 2018 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !test
// +build !test
package ospatch
import (
"context"
"fmt"
"strings"
"github.com/GoogleCloudPlatform/osconfig/clog"
"github.com/GoogleCloudPlatform/osconfig/packages"
"golang.org/x/sys/windows/registry"
)
// SystemRebootRequired checks whether a system reboot is required.
func SystemRebootRequired(ctx context.Context) (bool, error) {
// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexw#remarks
clog.Debugf(ctx, "Checking for PendingFileRenameOperations")
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager`, registry.QUERY_VALUE)
if err == nil {
val, _, err := k.GetStringsValue("PendingFileRenameOperations")
if err == nil {
k.Close()
if len(val) > 0 {
clog.Infof(ctx, "PendingFileRenameOperations indicate a reboot is required: %q", val)
return true, nil
}
} else if err != registry.ErrNotExist {
return false, err
}
} else if err != registry.ErrNotExist {
return false, err
}
regKeys := []string{
`SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired`,
// Skip checking CBS for now until we implement rate limiting on reboots, this key
// will not be reset in some instances for a few minutes after a reboot. This should
// not prevent updates from running as this mainly indicates a feature install.
// `SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending`,
}
for _, key := range regKeys {
clog.Debugf(ctx, "Checking if reboot required by testing the existance of %s", key)
k, err := registry.OpenKey(registry.LOCAL_MACHINE, key, registry.QUERY_VALUE)
if err == nil {
k.Close()
clog.Infof(ctx, "%s exists indicating a reboot is required.", key)
return true, nil
} else if err != registry.ErrNotExist {
return false, err
}
}
return false, nil
}
func checkFilters(ctx context.Context, updt *packages.IUpdate, kbExcludes, classFilter, exclusive_patches []string) (ok bool, err error) {
title, err := updt.GetProperty("Title")
if err != nil {
return false, fmt.Errorf(`updt.GetProperty("Title"): %v`, err)
}
defer title.Clear()
defer func() {
if ok == true {
clog.Debugf(ctx, "Update %q not excluded by any filters.", title.ToString())
}
}()
kbArticleIDsRaw, err := updt.GetProperty("KBArticleIDs")
if err != nil {
return false, fmt.Errorf(`updt.GetProperty("KBArticleIDs"): %v`, err)
}
defer kbArticleIDsRaw.Clear()
kbArticleIDs := kbArticleIDsRaw.ToIDispatch()
defer kbArticleIDs.Release()
kbArticleIDsCount, err := packages.GetCount(kbArticleIDs)
if err != nil {
return false, err
}
if len(exclusive_patches) > 0 {
for i := 0; i < int(kbArticleIDsCount); i++ {
kbRaw, err := kbArticleIDs.GetProperty("Item", i)
if err != nil {
return false, err
}
defer kbRaw.Clear()
for _, e := range exclusive_patches {
if e == kbRaw.ToString() {
// until now we have only seen at most 1 kbarticles
// in a patch update. So, if we get a match, we just
// install the update
return true, nil
}
}
}
// since there are exclusive_patches to be installed,
// other fields like excludes, classfilter are void
return false, nil
}
if len(kbExcludes) > 0 {
for i := 0; i < int(kbArticleIDsCount); i++ {
kbRaw, err := kbArticleIDs.GetProperty("Item", i)
if err != nil {
return false, err
}
defer kbRaw.Clear()
for _, e := range kbExcludes {
// kbArticleIDs is just the IDs, but users are used to using the KB prefix.
if strings.TrimLeft(e, "KkBb") == kbRaw.ToString() {
clog.Debugf(ctx, "Update %q (%s) matched exclude filter", title.ToString(), kbRaw.ToString())
return false, nil
}
}
}
}
if len(classFilter) == 0 {
return true, nil
}
categoriesRaw, err := updt.GetProperty("Categories")
if err != nil {
return false, fmt.Errorf(`updt.GetProperty("Categories"): %v`, err)
}
defer categoriesRaw.Clear()
categories := categoriesRaw.ToIDispatch()
defer categories.Release()
categoriesCount, err := packages.GetCount(categories)
if err != nil {
return false, err
}
for i := 0; i < int(categoriesCount); i++ {
catRaw, err := categories.GetProperty("Item", i)
if err != nil {
return false, fmt.Errorf(`categories.GetProperty("Item", i): %v`, err)
}
defer catRaw.Clear()
cat := catRaw.ToIDispatch()
defer cat.Release()
catIdRaw, err := cat.GetProperty("CategoryID")
if err != nil {
return false, fmt.Errorf(`cat.GetProperty("CategoryID"): %v`, err)
}
defer catIdRaw.Clear()
for _, c := range classFilter {
if c == catIdRaw.ToString() {
return true, nil
}
}
}
clog.Debugf(ctx, "Update %q not found in classification filter", title.ToString())
return false, nil
}
// GetWUAUpdates gets WUA updates based on optional classFilter and kbExcludes.
func GetWUAUpdates(ctx context.Context, session *packages.IUpdateSession, classFilter, kbExcludes, exclusivePatches []string) (*packages.IUpdateCollection, error) {
// Search for all not installed updates but filter out ones that will be installed after a reboot.
filter := "IsInstalled=0 AND RebootRequired=0"
clog.Debugf(ctx, "Searching for WUA updates with query %q", filter)
updts, err := session.GetWUAUpdateCollection(ctx, filter)
if err != nil {
return nil, fmt.Errorf("GetWUAUpdateCollection error: %v", err)
}
if len(classFilter) == 0 && len(kbExcludes) == 0 && len(exclusivePatches) == 0 {
return updts, nil
}
defer updts.Release()
count, err := updts.Count()
if err != nil {
return nil, err
}
clog.Debugf(ctx, "Found %d total updates avaiable (pre filter).", count)
newUpdts, err := packages.NewUpdateCollection()
if err != nil {
return nil, err
}
clog.Debugf(ctx, "Using filters: Excludes: %q, Classifications: %q, ExclusivePatches: %q", kbExcludes, classFilter, exclusivePatches)
for i := 0; i < int(count); i++ {
updt, err := updts.Item(i)
if err != nil {
return nil, err
}
ok, err := checkFilters(ctx, updt, kbExcludes, classFilter, exclusivePatches)
if err != nil {
return nil, err
}
if !ok {
continue
}
if err := newUpdts.Add(updt); err != nil {
return nil, err
}
}
return newUpdts, nil
}