diff --git a/.gitignore b/.gitignore index 301ef85..f2fad48 100644 --- a/.gitignore +++ b/.gitignore @@ -271,7 +271,6 @@ test-results/ dev/ # Development packages and scripts -aggregator-agent/pkg/ aggregator-server/scripts/ # Build artifacts diff --git a/aggregator-agent/pkg/windowsupdate/enum.go b/aggregator-agent/pkg/windowsupdate/enum.go new file mode 100644 index 0000000..0963ba9 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/enum.go @@ -0,0 +1,25 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +// OperationResultCode defines the possible results of a download, install, uninstall, or verification operation on an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode +const ( + OperationResultCodeOrcNotStarted int32 = iota + OperationResultCodeOrcInProgress + OperationResultCodeOrcSucceeded + OperationResultCodeOrcSucceededWithErrors + OperationResultCodeOrcFailed + OperationResultCodeOrcAborted +) diff --git a/aggregator-agent/pkg/windowsupdate/icategory.go b/aggregator-agent/pkg/windowsupdate/icategory.go new file mode 100644 index 0000000..59c3c21 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/icategory.go @@ -0,0 +1,126 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// ICategory represents the category to which an update belongs. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-icategory +type ICategory struct { + disp *ole.IDispatch + CategoryID string + Children []*ICategory + Description string + Image *IImageInformation + Name string + Order int32 + Parent *ICategory + Type string + Updates []*IUpdate +} + +func toICategories(categoriesDisp *ole.IDispatch) ([]*ICategory, error) { + count, err := toInt32Err(oleutil.GetProperty(categoriesDisp, "Count")) + if err != nil { + return nil, err + } + + categories := make([]*ICategory, 0, count) + for i := 0; i < int(count); i++ { + categoryDisp, err := toIDispatchErr(oleutil.GetProperty(categoriesDisp, "Item", i)) + if err != nil { + return nil, err + } + + category, err := toICategory(categoryDisp) + if err != nil { + return nil, err + } + + categories = append(categories, category) + } + return categories, nil +} + +func toICategory(categoryDisp *ole.IDispatch) (*ICategory, error) { + var err error + iCategory := &ICategory{ + disp: categoryDisp, + } + + if iCategory.CategoryID, err = toStringErr(oleutil.GetProperty(categoryDisp, "CategoryID")); err != nil { + return nil, err + } + + childrenDisp, err := toIDispatchErr(oleutil.GetProperty(categoryDisp, "Children")) + if err != nil { + return nil, err + } + if childrenDisp != nil { + if iCategory.Children, err = toICategories(childrenDisp); err != nil { + return nil, err + } + } + + if iCategory.Description, err = toStringErr(oleutil.GetProperty(categoryDisp, "Description")); err != nil { + return nil, err + } + + imageDisp, err := toIDispatchErr(oleutil.GetProperty(categoryDisp, "Image")) + if err != nil { + return nil, err + } + if imageDisp != nil { + if iCategory.Image, err = toIImageInformation(imageDisp); err != nil { + return nil, err + } + } + + if iCategory.Name, err = toStringErr(oleutil.GetProperty(categoryDisp, "Name")); err != nil { + return nil, err + } + + if iCategory.Order, err = toInt32Err(oleutil.GetProperty(categoryDisp, "Order")); err != nil { + return nil, err + } + + // parentDisp, err := toIDispatchErr(oleutil.GetProperty(categoryDisp, "Parent")) + // if err != nil { + // return nil, err + // } + // if parentDisp != nil { + // if iCategory.Parent, err = toICategory(parentDisp); err != nil { + // return nil, err + // } + // } + + if iCategory.Type, err = toStringErr(oleutil.GetProperty(categoryDisp, "Type")); err != nil { + return nil, err + } + + // updatesDisp, err := toIDispatchErr(oleutil.GetProperty(categoryDisp, "Updates")) + // if err != nil { + // return nil, err + // } + // if updatesDisp != nil { + // if iCategory.Updates, err = toIUpdates(updatesDisp); err != nil { + // return nil, err + // } + // } + + return iCategory, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/idownloadresult.go b/aggregator-agent/pkg/windowsupdate/idownloadresult.go new file mode 100644 index 0000000..40194e9 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/idownloadresult.go @@ -0,0 +1,66 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IDownloadResult represents the result of a download operation. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-idownloadresult +type IDownloadResult struct { + disp *ole.IDispatch + HResult int32 + ResultCode int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode +} + +func toIDownloadResult(downloadResultDisp *ole.IDispatch) (*IDownloadResult, error) { + var err error + iDownloadResult := &IDownloadResult{ + disp: downloadResultDisp, + } + + if iDownloadResult.HResult, err = toInt32Err(oleutil.GetProperty(downloadResultDisp, "HResult")); err != nil { + return nil, err + } + + if iDownloadResult.ResultCode, err = toInt32Err(oleutil.GetProperty(downloadResultDisp, "ResultCode")); err != nil { + return nil, err + } + + return iDownloadResult, nil +} + +// GetUpdateResult returns an IUpdateDownloadResult interface that contains the download information for a specified update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-idownloadresult-getupdateresult +func (iDownloadResult *IDownloadResult) GetUpdateResult(updateIndex int32) (*IUpdateDownloadResult, error) { + var err error + iUpdateDownloadResult := &IUpdateDownloadResult{ + disp: iDownloadResult.disp, + } + updatesDisp, err := toIDispatchErr(oleutil.CallMethod(iDownloadResult.disp, "GetUpdateResult", updateIndex)) + if err != nil { + return nil, err + } + + if iUpdateDownloadResult.HResult, err = toInt32Err(oleutil.GetProperty(updatesDisp, "HResult")); err != nil { + return nil, err + } + + if iUpdateDownloadResult.ResultCode, err = toInt32Err(oleutil.GetProperty(updatesDisp, "ResultCode")); err != nil { + return nil, err + } + return iUpdateDownloadResult, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iimageinformation.go b/aggregator-agent/pkg/windowsupdate/iimageinformation.go new file mode 100644 index 0000000..f1580c4 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iimageinformation.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IImageInformation contains information about a localized image that is associated with an update or a category. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iimageinformation +type IImageInformation struct { + disp *ole.IDispatch + AltText string + Height int64 + Source string + Width int64 +} + +func toIImageInformation(imageInformationDisp *ole.IDispatch) (*IImageInformation, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iinstallationbehavior.go b/aggregator-agent/pkg/windowsupdate/iinstallationbehavior.go new file mode 100644 index 0000000..c696e2f --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iinstallationbehavior.go @@ -0,0 +1,33 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IInstallationBehavior represents the installation and uninstallation options of an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iinstallationbehavior +type IInstallationBehavior struct { + disp *ole.IDispatch + CanRequestUserInput bool + Impact int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-installationimpact + RebootBehavior int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-installationrebootbehavior + RequiresNetworkConnectivity bool +} + +func toIInstallationBehavior(installationBehaviorDisp *ole.IDispatch) (*IInstallationBehavior, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iinstallationresult.go b/aggregator-agent/pkg/windowsupdate/iinstallationresult.go new file mode 100644 index 0000000..45efb02 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iinstallationresult.go @@ -0,0 +1,71 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IInstallationResult represents the result of an installation or uninstallation. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iinstallationresult +type IInstallationResult struct { + disp *ole.IDispatch + HResult int32 + RebootRequired bool + ResultCode int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode +} + +func toIInstallationResult(installationResultDisp *ole.IDispatch) (*IInstallationResult, error) { + var err error + iInstallationResult := &IInstallationResult{ + disp: installationResultDisp, + } + + if iInstallationResult.HResult, err = toInt32Err(oleutil.GetProperty(installationResultDisp, "HResult")); err != nil { + return nil, err + } + + if iInstallationResult.RebootRequired, err = toBoolErr(oleutil.GetProperty(installationResultDisp, "RebootRequired")); err != nil { + return nil, err + } + + if iInstallationResult.ResultCode, err = toInt32Err(oleutil.GetProperty(installationResultDisp, "ResultCode")); err != nil { + return nil, err + } + + return iInstallationResult, nil +} + +// GetUpdateResult returns an IInstallationResult interface that contains the installation information for a specified update. +// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iinstallationresult +func (iInstallationResult *IInstallationResult) GetUpdateResult(updateIndex int32) (*IInstallationResult, error) { + var err error + iUpdateInstallationResult := &IInstallationResult{ + disp: iInstallationResult.disp, + } + updatesDisp, err := toIDispatchErr(oleutil.CallMethod(iInstallationResult.disp, "GetUpdateResult", updateIndex)) + if err != nil { + return nil, err + } + + if iUpdateInstallationResult.HResult, err = toInt32Err(oleutil.GetProperty(updatesDisp, "HResult")); err != nil { + return nil, err + } + + if iUpdateInstallationResult.ResultCode, err = toInt32Err(oleutil.GetProperty(updatesDisp, "ResultCode")); err != nil { + return nil, err + } + return iUpdateInstallationResult, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/isearchresult.go b/aggregator-agent/pkg/windowsupdate/isearchresult.go new file mode 100644 index 0000000..2e7183d --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/isearchresult.go @@ -0,0 +1,72 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// ISearchResult represents the result of a search. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-isearchresult +type ISearchResult struct { + disp *ole.IDispatch + ResultCode int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode + RootCategories []*ICategory + Updates []*IUpdate + Warnings []*IUpdateException +} + +func toISearchResult(searchResultDisp *ole.IDispatch) (*ISearchResult, error) { + var err error + iSearchResult := &ISearchResult{ + disp: searchResultDisp, + } + + if iSearchResult.ResultCode, err = toInt32Err(oleutil.GetProperty(searchResultDisp, "ResultCode")); err != nil { + return nil, err + } + + rootCategoriesDisp, err := toIDispatchErr(oleutil.GetProperty(searchResultDisp, "RootCategories")) + if err != nil { + return nil, err + } + if rootCategoriesDisp != nil { + if iSearchResult.RootCategories, err = toICategories(rootCategoriesDisp); err != nil { + return nil, err + } + } + + updatesDisp, err := toIDispatchErr(oleutil.GetProperty(searchResultDisp, "Updates")) + if err != nil { + return nil, err + } + if updatesDisp != nil { + if iSearchResult.Updates, err = toIUpdates(updatesDisp); err != nil { + return nil, err + } + } + + warningsDisp, err := toIDispatchErr(oleutil.GetProperty(searchResultDisp, "Warnings")) + if err != nil { + return nil, err + } + if warningsDisp != nil { + if iSearchResult.Warnings, err = toIUpdateExceptions(warningsDisp); err != nil { + return nil, err + } + } + + return iSearchResult, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/istringcollection.go b/aggregator-agent/pkg/windowsupdate/istringcollection.go new file mode 100644 index 0000000..0234634 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/istringcollection.go @@ -0,0 +1,34 @@ +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-istringcollection +func iStringCollectionToStringArrayErr(disp *ole.IDispatch, err error) ([]string, error) { + if err != nil { + return nil, err + } + + if disp == nil { + return nil, nil + } + + count, err := toInt32Err(oleutil.GetProperty(disp, "Count")) + if err != nil { + return nil, err + } + + stringCollection := make([]string, count) + + for i := 0; i < int(count); i++ { + str, err := toStringErr(oleutil.GetProperty(disp, "Item", i)) + if err != nil { + return nil, err + } + + stringCollection[i] = str + } + return stringCollection, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdate.go b/aggregator-agent/pkg/windowsupdate/iupdate.go new file mode 100644 index 0000000..7066544 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdate.go @@ -0,0 +1,363 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "time" + + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdate contains the properties and methods that are available to an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdate +type IUpdate struct { + disp *ole.IDispatch + AutoSelectOnWebSites bool + BundledUpdates []*IUpdateIdentity + CanRequireSource bool + Categories []*ICategory + Deadline *time.Time + DeltaCompressedContentAvailable bool + DeltaCompressedContentPreferred bool + DeploymentAction int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-deploymentaction + Description string + DownloadContents []*IUpdateDownloadContent + DownloadPriority int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-downloadpriority + EulaAccepted bool + EulaText string + HandlerID string + Identity *IUpdateIdentity + Image *IImageInformation + InstallationBehavior *IInstallationBehavior + IsBeta bool + IsDownloaded bool + IsHidden bool + IsInstalled bool + IsMandatory bool + IsUninstallable bool + KBArticleIDs []string + Languages []string + LastDeploymentChangeTime *time.Time + MaxDownloadSize int64 + MinDownloadSize int64 + MoreInfoUrls []string + MsrcSeverity string + RecommendedCpuSpeed int32 + RecommendedHardDiskSpace int32 + RecommendedMemory int32 + ReleaseNotes string + SecurityBulletinIDs []string + SupersededUpdateIDs []string + SupportUrl string + Title string + UninstallationBehavior *IInstallationBehavior + UninstallationNotes string + UninstallationSteps []string +} + +func toIUpdates(updatesDisp *ole.IDispatch) ([]*IUpdate, error) { + count, err := toInt32Err(oleutil.GetProperty(updatesDisp, "Count")) + if err != nil { + return nil, err + } + + updates := make([]*IUpdate, 0, count) + for i := 0; i < int(count); i++ { + updateDisp, err := toIDispatchErr(oleutil.GetProperty(updatesDisp, "Item", i)) + if err != nil { + return nil, err + } + + update, err := toIUpdate(updateDisp) + if err != nil { + return nil, err + } + + updates = append(updates, update) + } + return updates, nil +} + +// toIUpdates takes a IUpdateCollection and returns the a +// []*IUpdateIdentity of the contained IUpdates. This is *not* recursive, though possible should be +func toIUpdatesIdentities(updatesDisp *ole.IDispatch) ([]*IUpdateIdentity, error) { + if updatesDisp == nil { + return nil, nil + } + + count, err := toInt32Err(oleutil.GetProperty(updatesDisp, "Count")) + if err != nil { + return nil, err + } + + identities := make([]*IUpdateIdentity, count) + for i := 0; i < int(count); i++ { + updateDisp, err := toIDispatchErr(oleutil.GetProperty(updatesDisp, "Item", i)) + if err != nil { + return nil, err + } + + identityDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "Identity")) + if err != nil { + return nil, err + } + if identityDisp != nil { + if identities[i], err = toIUpdateIdentity(identityDisp); err != nil { + return nil, err + } + } + } + return identities, nil +} + +func toIUpdate(updateDisp *ole.IDispatch) (*IUpdate, error) { + var err error + iUpdate := &IUpdate{ + disp: updateDisp, + } + + if iUpdate.AutoSelectOnWebSites, err = toBoolErr(oleutil.GetProperty(updateDisp, "AutoSelectOnWebSites")); err != nil { + return nil, err + } + + bundledUpdatesDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "BundledUpdates")) + if err != nil { + return nil, err + } + if bundledUpdatesDisp != nil { + if iUpdate.BundledUpdates, err = toIUpdatesIdentities(bundledUpdatesDisp); err != nil { + return nil, err + } + } + + if iUpdate.CanRequireSource, err = toBoolErr(oleutil.GetProperty(updateDisp, "CanRequireSource")); err != nil { + return nil, err + } + + categoriesDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "Categories")) + if err != nil { + return nil, err + } + if categoriesDisp != nil { + if iUpdate.Categories, err = toICategories(categoriesDisp); err != nil { + return nil, err + } + } + + if iUpdate.Deadline, err = toTimeErr(oleutil.GetProperty(updateDisp, "Deadline")); err != nil { + return nil, err + } + + if iUpdate.DeltaCompressedContentAvailable, err = toBoolErr(oleutil.GetProperty(updateDisp, "DeltaCompressedContentAvailable")); err != nil { + return nil, err + } + + if iUpdate.DeltaCompressedContentPreferred, err = toBoolErr(oleutil.GetProperty(updateDisp, "DeltaCompressedContentPreferred")); err != nil { + return nil, err + } + + if iUpdate.DeploymentAction, err = toInt32Err(oleutil.GetProperty(updateDisp, "DeploymentAction")); err != nil { + return nil, err + } + + if iUpdate.Description, err = toStringErr(oleutil.GetProperty(updateDisp, "Description")); err != nil { + return nil, err + } + + downloadContentsDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "DownloadContents")) + if err != nil { + return nil, err + } + if downloadContentsDisp != nil { + if iUpdate.DownloadContents, err = toIUpdateDownloadContents(downloadContentsDisp); err != nil { + return nil, err + } + } + + if iUpdate.DownloadPriority, err = toInt32Err(oleutil.GetProperty(updateDisp, "DownloadPriority")); err != nil { + return nil, err + } + + if iUpdate.EulaAccepted, err = toBoolErr(oleutil.GetProperty(updateDisp, "EulaAccepted")); err != nil { + return nil, err + } + + if iUpdate.EulaText, err = toStringErr(oleutil.GetProperty(updateDisp, "EulaText")); err != nil { + return nil, err + } + + if iUpdate.HandlerID, err = toStringErr(oleutil.GetProperty(updateDisp, "HandlerID")); err != nil { + return nil, err + } + + identityDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "Identity")) + if err != nil { + return nil, err + } + if identityDisp != nil { + if iUpdate.Identity, err = toIUpdateIdentity(identityDisp); err != nil { + return nil, err + } + } + + imageDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "Image")) + if err != nil { + return nil, err + } + if imageDisp != nil { + if iUpdate.Image, err = toIImageInformation(imageDisp); err != nil { + return nil, err + } + } + + installationBehaviorDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "InstallationBehavior")) + if err != nil { + return nil, err + } + if installationBehaviorDisp != nil { + if iUpdate.InstallationBehavior, err = toIInstallationBehavior(installationBehaviorDisp); err != nil { + return nil, err + } + } + + if iUpdate.IsBeta, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsBeta")); err != nil { + return nil, err + } + + if iUpdate.IsDownloaded, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsDownloaded")); err != nil { + return nil, err + } + + if iUpdate.IsHidden, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsHidden")); err != nil { + return nil, err + } + + if iUpdate.IsInstalled, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsInstalled")); err != nil { + return nil, err + } + + if iUpdate.IsMandatory, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsMandatory")); err != nil { + return nil, err + } + + if iUpdate.IsUninstallable, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsUninstallable")); err != nil { + return nil, err + } + + if iUpdate.KBArticleIDs, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "KBArticleIDs"))); err != nil { + return nil, err + } + + if iUpdate.Languages, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "Languages"))); err != nil { + return nil, err + } + + if iUpdate.LastDeploymentChangeTime, err = toTimeErr(oleutil.GetProperty(updateDisp, "LastDeploymentChangeTime")); err != nil { + return nil, err + } + + if iUpdate.MaxDownloadSize, err = toInt64Err(oleutil.GetProperty(updateDisp, "MaxDownloadSize")); err != nil { + return nil, err + } + + if iUpdate.MinDownloadSize, err = toInt64Err(oleutil.GetProperty(updateDisp, "MinDownloadSize")); err != nil { + return nil, err + } + + if iUpdate.MoreInfoUrls, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "MoreInfoUrls"))); err != nil { + return nil, err + } + + if iUpdate.MsrcSeverity, err = toStringErr(oleutil.GetProperty(updateDisp, "MsrcSeverity")); err != nil { + return nil, err + } + + if iUpdate.RecommendedCpuSpeed, err = toInt32Err(oleutil.GetProperty(updateDisp, "RecommendedCpuSpeed")); err != nil { + return nil, err + } + + if iUpdate.RecommendedHardDiskSpace, err = toInt32Err(oleutil.GetProperty(updateDisp, "RecommendedHardDiskSpace")); err != nil { + return nil, err + } + + if iUpdate.RecommendedMemory, err = toInt32Err(oleutil.GetProperty(updateDisp, "RecommendedMemory")); err != nil { + return nil, err + } + + if iUpdate.ReleaseNotes, err = toStringErr(oleutil.GetProperty(updateDisp, "ReleaseNotes")); err != nil { + return nil, err + } + + if iUpdate.SecurityBulletinIDs, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "SecurityBulletinIDs"))); err != nil { + return nil, err + } + + if iUpdate.SupersededUpdateIDs, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "SupersededUpdateIDs"))); err != nil { + return nil, err + } + + if iUpdate.SupportUrl, err = toStringErr(oleutil.GetProperty(updateDisp, "SupportUrl")); err != nil { + return nil, err + } + + if iUpdate.Title, err = toStringErr(oleutil.GetProperty(updateDisp, "Title")); err != nil { + return nil, err + } + + uninstallationBehaviorDisp, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "UninstallationBehavior")) + if err != nil { + return nil, err + } + if uninstallationBehaviorDisp != nil { + if iUpdate.UninstallationBehavior, err = toIInstallationBehavior(uninstallationBehaviorDisp); err != nil { + return nil, err + } + } + + if iUpdate.UninstallationNotes, err = toStringErr(oleutil.GetProperty(updateDisp, "UninstallationNotes")); err != nil { + return nil, err + } + + if iUpdate.UninstallationSteps, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateDisp, "UninstallationSteps"))); err != nil { + return nil, err + } + + return iUpdate, nil +} + +func toIUpdateCollection(updates []*IUpdate) (*ole.IDispatch, error) { + unknown, err := oleutil.CreateObject("Microsoft.Update.UpdateColl") + if err != nil { + return nil, err + } + coll, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + return nil, err + } + for _, update := range updates { + _, err := oleutil.CallMethod(coll, "Add", update.disp) + if err != nil { + return nil, err + } + } + return coll, nil +} + +// AcceptEula accepts the Microsoft Software License Terms that are associated with Windows Update. Administrators and power users can call this method. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdate-accepteula +func (iUpdate *IUpdate) AcceptEula() error { + _, err := oleutil.CallMethod(iUpdate.disp, "AcceptEula") + return err +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatedownloadcontent.go b/aggregator-agent/pkg/windowsupdate/iupdatedownloadcontent.go new file mode 100644 index 0000000..2593ff6 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatedownloadcontent.go @@ -0,0 +1,30 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IUpdateDownloadContent represents the download content of an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatedownloadcontent +type IUpdateDownloadContent struct { + disp *ole.IDispatch + DownloadUrl string +} + +func toIUpdateDownloadContents(updateDownloadContentsDisp *ole.IDispatch) ([]*IUpdateDownloadContent, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatedownloader.go b/aggregator-agent/pkg/windowsupdate/iupdatedownloader.go new file mode 100644 index 0000000..1a4667a --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatedownloader.go @@ -0,0 +1,78 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateDownloader downloads updates from the server. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatedownloaders +type IUpdateDownloader struct { + disp *ole.IDispatch + ClientApplicationID string + IsForced bool + Priority int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-downloadpriority + Updates []*IUpdate +} + +func toIUpdateDownloader(updateDownloaderDisp *ole.IDispatch) (*IUpdateDownloader, error) { + var err error + iUpdateDownloader := &IUpdateDownloader{ + disp: updateDownloaderDisp, + } + + if iUpdateDownloader.ClientApplicationID, err = toStringErr(oleutil.GetProperty(updateDownloaderDisp, "ClientApplicationID")); err != nil { + return nil, err + } + + if iUpdateDownloader.IsForced, err = toBoolErr(oleutil.GetProperty(updateDownloaderDisp, "IsForced")); err != nil { + return nil, err + } + + if iUpdateDownloader.Priority, err = toInt32Err(oleutil.GetProperty(updateDownloaderDisp, "Priority")); err != nil { + return nil, err + } + + updatesDisp, err := toIDispatchErr(oleutil.GetProperty(updateDownloaderDisp, "Updates")) + if err != nil { + return nil, err + } + if updatesDisp != nil { + if iUpdateDownloader.Updates, err = toIUpdates(updatesDisp); err != nil { + return nil, err + } + } + + return iUpdateDownloader, nil +} + +// Download starts a synchronous download of the content files that are associated with the updates. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatedownloader-download +func (iUpdateDownloader *IUpdateDownloader) Download(updates []*IUpdate) (*IDownloadResult, error) { + updatesDisp, err := toIUpdateCollection(updates) + if err != nil { + return nil, err + } + if _, err = oleutil.PutProperty(iUpdateDownloader.disp, "Updates", updatesDisp); err != nil { + return nil, err + } + + downloadResultDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateDownloader.disp, "Download")) + if err != nil { + return nil, err + } + return toIDownloadResult(downloadResultDisp) +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatedownloadresult.go b/aggregator-agent/pkg/windowsupdate/iupdatedownloadresult.go new file mode 100644 index 0000000..2467017 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatedownloadresult.go @@ -0,0 +1,31 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IUpdateDownloadResult contains the properties that indicate the status of a download operation for an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatedownloadresult +type IUpdateDownloadResult struct { + disp *ole.IDispatch + HResult int32 + ResultCode int32 +} + +func toIUpdateDownloadResult(iUpdateDownloadResultDisp *ole.IDispatch) (*IUpdateDownloadResult, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdateexception.go b/aggregator-agent/pkg/windowsupdate/iupdateexception.go new file mode 100644 index 0000000..c263cf4 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdateexception.go @@ -0,0 +1,32 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IUpdateException represents info about the aspects of search results returned in the ISearchResult object that were incomplete. For more info, see Remarks. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdateexception +type IUpdateException struct { + disp *ole.IDispatch + Context int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-updateexceptioncontext + HResult int64 + Message string +} + +func toIUpdateExceptions(updateExceptionsDisp *ole.IDispatch) ([]*IUpdateException, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatehistoryentry.go b/aggregator-agent/pkg/windowsupdate/iupdatehistoryentry.go new file mode 100644 index 0000000..96e4404 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatehistoryentry.go @@ -0,0 +1,135 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "time" + + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateHistoryEntry represents the recorded history of an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatehistoryentry +type IUpdateHistoryEntry struct { + disp *ole.IDispatch + ClientApplicationID string + Date *time.Time + Description string + HResult int32 + Operation int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-updateoperation + ResultCode int32 // enum https://docs.microsoft.com/en-us/windows/win32/api/wuapi/ne-wuapi-operationresultcode + ServerSelection int32 // enum + ServiceID string + SupportUrl string + Title string + UninstallationNotes string + UninstallationSteps []string + UnmappedResultCode int32 + UpdateIdentity *IUpdateIdentity +} + +func toIUpdateHistoryEntries(updateHistoryEntriesDisp *ole.IDispatch) ([]*IUpdateHistoryEntry, error) { + count, err := toInt32Err(oleutil.GetProperty(updateHistoryEntriesDisp, "Count")) + if err != nil { + return nil, err + } + + updateHistoryEntries := make([]*IUpdateHistoryEntry, 0, count) + for i := 0; i < int(count); i++ { + updateHistoryEntryDisp, err := toIDispatchErr(oleutil.GetProperty(updateHistoryEntriesDisp, "Item", i)) + if err != nil { + return nil, err + } + + updateHistoryEntry, err := toIUpdateHistoryEntry(updateHistoryEntryDisp) + if err != nil { + return nil, err + } + + updateHistoryEntries = append(updateHistoryEntries, updateHistoryEntry) + } + return updateHistoryEntries, nil +} + +func toIUpdateHistoryEntry(updateHistoryEntryDisp *ole.IDispatch) (*IUpdateHistoryEntry, error) { + var err error + iUpdateHistoryEntry := &IUpdateHistoryEntry{ + disp: updateHistoryEntryDisp, + } + + if iUpdateHistoryEntry.ClientApplicationID, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "ClientApplicationID")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.Date, err = toTimeErr(oleutil.GetProperty(updateHistoryEntryDisp, "Date")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.Description, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "Description")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.HResult, err = toInt32Err(oleutil.GetProperty(updateHistoryEntryDisp, "HResult")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.Operation, err = toInt32Err(oleutil.GetProperty(updateHistoryEntryDisp, "Operation")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.ResultCode, err = toInt32Err(oleutil.GetProperty(updateHistoryEntryDisp, "ResultCode")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.ServerSelection, err = toInt32Err(oleutil.GetProperty(updateHistoryEntryDisp, "ServerSelection")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.ServiceID, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "ServiceID")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.SupportUrl, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "SupportUrl")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.Title, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "Title")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.UninstallationNotes, err = toStringErr(oleutil.GetProperty(updateHistoryEntryDisp, "UninstallationNotes")); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.UninstallationSteps, err = iStringCollectionToStringArrayErr(toIDispatchErr(oleutil.GetProperty(updateHistoryEntryDisp, "UninstallationSteps"))); err != nil { + return nil, err + } + + if iUpdateHistoryEntry.UnmappedResultCode, err = toInt32Err(oleutil.GetProperty(updateHistoryEntryDisp, "UnmappedResultCode")); err != nil { + return nil, err + } + + updateIdentityDisp, err := toIDispatchErr(oleutil.GetProperty(updateHistoryEntryDisp, "UpdateIdentity")) + if err != nil { + return nil, err + } + if updateIdentityDisp != nil { + if iUpdateHistoryEntry.UpdateIdentity, err = toIUpdateIdentity(updateIdentityDisp); err != nil { + return nil, err + } + } + + return iUpdateHistoryEntry, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdateidentity.go b/aggregator-agent/pkg/windowsupdate/iupdateidentity.go new file mode 100644 index 0000000..c22cd92 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdateidentity.go @@ -0,0 +1,44 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateIdentity represents the unique identifier of an update. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdateidentity +type IUpdateIdentity struct { + disp *ole.IDispatch + RevisionNumber int32 + UpdateID string +} + +func toIUpdateIdentity(updateIdentityDisp *ole.IDispatch) (*IUpdateIdentity, error) { + var err error + iUpdateIdentity := &IUpdateIdentity{ + disp: updateIdentityDisp, + } + + if iUpdateIdentity.RevisionNumber, err = toInt32Err(oleutil.GetProperty(updateIdentityDisp, "RevisionNumber")); err != nil { + return nil, err + } + + if iUpdateIdentity.UpdateID, err = toStringErr(oleutil.GetProperty(updateIdentityDisp, "UpdateID")); err != nil { + return nil, err + } + + return iUpdateIdentity, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdateinstaller.go b/aggregator-agent/pkg/windowsupdate/iupdateinstaller.go new file mode 100644 index 0000000..4802800 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdateinstaller.go @@ -0,0 +1,127 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateInstaller installs or uninstalls updates from or onto a computer. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdateinstaller +type IUpdateInstaller struct { + disp *ole.IDispatch + AllowSourcePrompts bool + ClientApplicationID string + IsBusy bool + IsForced bool + ForceQuiet bool + // ParentHwnd HWND + // ParentWindow IUnknown + RebootRequiredBeforeInstallation bool + Updates []*IUpdate +} + +func toIUpdateInstaller(updateInstallerDisp *ole.IDispatch) (*IUpdateInstaller, error) { + var err error + iUpdateInstaller := &IUpdateInstaller{ + disp: updateInstallerDisp, + } + + if iUpdateInstaller.AllowSourcePrompts, err = toBoolErr(oleutil.GetProperty(updateInstallerDisp, "AllowSourcePrompts")); err != nil { + return nil, err + } + + if iUpdateInstaller.ClientApplicationID, err = toStringErr(oleutil.GetProperty(updateInstallerDisp, "ClientApplicationID")); err != nil { + return nil, err + } + + if iUpdateInstaller.IsBusy, err = toBoolErr(oleutil.GetProperty(updateInstallerDisp, "IsBusy")); err != nil { + return nil, err + } + + if iUpdateInstaller.IsForced, err = toBoolErr(oleutil.GetProperty(updateInstallerDisp, "IsForced")); err != nil { + return nil, err + } + + if iUpdateInstaller.ForceQuiet, err = toBoolErr(oleutil.GetProperty(updateInstallerDisp, "ForceQuiet")); err != nil { + return nil, err + } + + if iUpdateInstaller.RebootRequiredBeforeInstallation, err = toBoolErr(oleutil.GetProperty(updateInstallerDisp, "RebootRequiredBeforeInstallation")); err != nil { + return nil, err + } + + updatesDisp, err := toIDispatchErr(oleutil.GetProperty(updateInstallerDisp, "Updates")) + if err != nil { + return nil, err + } + if updatesDisp != nil { + if iUpdateInstaller.Updates, err = toIUpdates(updatesDisp); err != nil { + return nil, err + } + } + + return iUpdateInstaller, nil +} + +// Install starts a synchronous installation of the updates. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdateinstaller-install +func (iUpdateInstaller *IUpdateInstaller) Install(updates []*IUpdate) (*IInstallationResult, error) { + updatesDisp, err := toIUpdateCollection(updates) + if err != nil { + return nil, err + } + if _, err = oleutil.PutProperty(iUpdateInstaller.disp, "Updates", updatesDisp); err != nil { + return nil, err + } + + installationResultDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateInstaller.disp, "Install")) + if err != nil { + return nil, err + } + return toIInstallationResult(installationResultDisp) +} + +// Finalizes updates that were previously staged or installed. +// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdateinstaller4-commit +func (iUpdateInstaller *IUpdateInstaller) Commit(dwFlags int32) error { + _, err := toIDispatchErr(oleutil.CallMethod(iUpdateInstaller.disp, "Commit", dwFlags)) + if err != nil { + return err + } + return nil +} + +// Sets a Boolean value that indicates whether Windows Installer is forced to install the updates without user interaction. +// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdateinstaller2-put_forcequiet +func (iUpdateInstaller *IUpdateInstaller) PutForceQuiet(value bool) error { + _, err := toIDispatchErr(oleutil.PutProperty(iUpdateInstaller.disp, "ForceQuiet", value)) + if err != nil { + return err + } + iUpdateInstaller.ForceQuiet = value + return nil +} + +// Sets a Boolean value that indicates whether to forcibly install or uninstall an update. +// https://learn.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdateinstaller-put_isforced +func (iUpdateInstaller *IUpdateInstaller) PutIsForced(value bool) error { + _, err := toIDispatchErr(oleutil.PutProperty(iUpdateInstaller.disp, "IsForced", value)) + if err != nil { + return err + } + iUpdateInstaller.IsForced = value + return nil +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatesearcher.go b/aggregator-agent/pkg/windowsupdate/iupdatesearcher.go new file mode 100644 index 0000000..11c7bfa --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatesearcher.go @@ -0,0 +1,99 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateSearcher searches for updates on a server. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatesearcher +type IUpdateSearcher struct { + disp *ole.IDispatch + CanAutomaticallyUpgradeService bool + ClientApplicationID string + IncludePotentiallySupersededUpdates bool + Online bool + ServerSelection int32 + ServiceID string +} + +func toIUpdateSearcher(updateSearcherDisp *ole.IDispatch) (*IUpdateSearcher, error) { + var err error + iUpdateSearcher := &IUpdateSearcher{ + disp: updateSearcherDisp, + } + + if iUpdateSearcher.CanAutomaticallyUpgradeService, err = toBoolErr(oleutil.GetProperty(updateSearcherDisp, "CanAutomaticallyUpgradeService")); err != nil { + return nil, err + } + + if iUpdateSearcher.ClientApplicationID, err = toStringErr(oleutil.GetProperty(updateSearcherDisp, "ClientApplicationID")); err != nil { + return nil, err + } + + if iUpdateSearcher.IncludePotentiallySupersededUpdates, err = toBoolErr(oleutil.GetProperty(updateSearcherDisp, "IncludePotentiallySupersededUpdates")); err != nil { + return nil, err + } + + if iUpdateSearcher.Online, err = toBoolErr(oleutil.GetProperty(updateSearcherDisp, "Online")); err != nil { + return nil, err + } + + if iUpdateSearcher.ServerSelection, err = toInt32Err(oleutil.GetProperty(updateSearcherDisp, "ServerSelection")); err != nil { + return nil, err + } + + if iUpdateSearcher.ServiceID, err = toStringErr(oleutil.GetProperty(updateSearcherDisp, "ServiceID")); err != nil { + return nil, err + } + + return iUpdateSearcher, nil +} + +// Search performs a synchronous search for updates. The search uses the search options that are currently configured. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesearcher-search +func (iUpdateSearcher *IUpdateSearcher) Search(criteria string) (*ISearchResult, error) { + searchResultDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateSearcher.disp, "Search", criteria)) + if err != nil { + return nil, err + } + return toISearchResult(searchResultDisp) +} + +// QueryHistory synchronously queries the computer for the history of the update events. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesearcher-queryhistory +func (iUpdateSearcher *IUpdateSearcher) QueryHistory(startIndex int32, count int32) ([]*IUpdateHistoryEntry, error) { + updateHistoryEntriesDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateSearcher.disp, "QueryHistory", startIndex, count)) + if err != nil { + return nil, err + } + return toIUpdateHistoryEntries(updateHistoryEntriesDisp) +} + +// GetTotalHistoryCount returns the number of update events on the computer. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesearcher-gettotalhistorycount +func (iUpdateSearcher *IUpdateSearcher) GetTotalHistoryCount() (int32, error) { + return toInt32Err(oleutil.CallMethod(iUpdateSearcher.disp, "GetTotalHistoryCount")) +} + +// QueryHistoryAll synchronously queries the computer for the history of all update events. +func (iUpdateSearcher *IUpdateSearcher) QueryHistoryAll() ([]*IUpdateHistoryEntry, error) { + count, err := iUpdateSearcher.GetTotalHistoryCount() + if err != nil { + return nil, err + } + return iUpdateSearcher.QueryHistory(0, count) +} diff --git a/aggregator-agent/pkg/windowsupdate/iupdatesession.go b/aggregator-agent/pkg/windowsupdate/iupdatesession.go new file mode 100644 index 0000000..19dec7f --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iupdatesession.go @@ -0,0 +1,100 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" + "github.com/go-ole/go-ole/oleutil" +) + +// IUpdateSession represents a session in which the caller can perform operations that involve updates. +// For example, this interface represents sessions in which the caller performs a search, download, installation, or uninstallation operation. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iupdatesession +type IUpdateSession struct { + disp *ole.IDispatch + ClientApplicationID string + ReadOnly bool + WebProxy *IWebProxy +} + +func toIUpdateSession(updateSessionDisp *ole.IDispatch) (*IUpdateSession, error) { + var err error + iUpdateSession := &IUpdateSession{ + disp: updateSessionDisp, + } + + if iUpdateSession.ClientApplicationID, err = toStringErr(oleutil.GetProperty(updateSessionDisp, "ClientApplicationID")); err != nil { + return nil, err + } + + if iUpdateSession.ReadOnly, err = toBoolErr(oleutil.GetProperty(updateSessionDisp, "ReadOnly")); err != nil { + return nil, err + } + + webProxyDisp, err := toIDispatchErr(oleutil.GetProperty(updateSessionDisp, "WebProxy")) + if err != nil { + return nil, err + } + if webProxyDisp != nil { + if iUpdateSession.WebProxy, err = toIWebProxy(webProxyDisp); err != nil { + return nil, err + } + } + + return iUpdateSession, nil +} + +// NewUpdateSession creates a new IUpdateSession interface. +func NewUpdateSession() (*IUpdateSession, error) { + unknown, err := oleutil.CreateObject("Microsoft.Update.Session") + if err != nil { + return nil, err + } + disp, err := unknown.QueryInterface(ole.IID_IDispatch) + if err != nil { + return nil, err + } + return toIUpdateSession(disp) +} + +// CreateUpdateDownloader returns an IUpdateDownloader interface for this session. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesession-createupdatedownloader +func (iUpdateSession *IUpdateSession) CreateUpdateDownloader() (*IUpdateDownloader, error) { + updateDownloaderDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateSession.disp, "CreateUpdateDownloader")) + if err != nil { + return nil, err + } + return toIUpdateDownloader(updateDownloaderDisp) +} + +// CreateUpdateInstaller returns an IUpdateInstaller interface for this session. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesession-createupdateinstaller +func (iUpdateSession *IUpdateSession) CreateUpdateInstaller() (*IUpdateInstaller, error) { + updateInstallerDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateSession.disp, "CreateUpdateInstaller")) + if err != nil { + return nil, err + } + return toIUpdateInstaller(updateInstallerDisp) +} + +// CreateUpdateSearcher returns an IUpdateSearcher interface for this session. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nf-wuapi-iupdatesession-createupdatesearcher +func (iUpdateSession *IUpdateSession) CreateUpdateSearcher() (*IUpdateSearcher, error) { + updateSearcherDisp, err := toIDispatchErr(oleutil.CallMethod(iUpdateSession.disp, "CreateUpdateSearcher")) + if err != nil { + return nil, err + } + + return toIUpdateSearcher(updateSearcherDisp) +} diff --git a/aggregator-agent/pkg/windowsupdate/iwebproxy.go b/aggregator-agent/pkg/windowsupdate/iwebproxy.go new file mode 100644 index 0000000..14ef6d4 --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/iwebproxy.go @@ -0,0 +1,35 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "github.com/go-ole/go-ole" +) + +// IWebProxy contains the HTTP proxy settings. +// https://docs.microsoft.com/en-us/windows/win32/api/wuapi/nn-wuapi-iwebproxy +type IWebProxy struct { + disp *ole.Dispatch + Address string + AutoDetect bool + BypassList []string + BypassProxyOnLocal bool + ReadOnly bool + UserName string +} + +func toIWebProxy(webProxyDisp *ole.IDispatch) (*IWebProxy, error) { + // TODO + return nil, nil +} diff --git a/aggregator-agent/pkg/windowsupdate/oleconv.go b/aggregator-agent/pkg/windowsupdate/oleconv.go new file mode 100644 index 0000000..dea233c --- /dev/null +++ b/aggregator-agent/pkg/windowsupdate/oleconv.go @@ -0,0 +1,141 @@ +/* +Copyright 2022 Zheng Dayu +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. +*/ + +package windowsupdate + +import ( + "time" + + "github.com/go-ole/go-ole" +) + +func toIDispatchErr(result *ole.VARIANT, err error) (*ole.IDispatch, error) { + if err != nil { + return nil, err + } + return variantToIDispatch(result), nil +} + +func toInt64Err(result *ole.VARIANT, err error) (int64, error) { + if err != nil { + return 0, err + } + return variantToInt64(result), nil +} + +func toInt32Err(result *ole.VARIANT, err error) (int32, error) { + if err != nil { + return 0, err + } + return variantToInt32(result), nil +} + +func toFloat64Err(result *ole.VARIANT, err error) (float64, error) { + if err != nil { + return 0, err + } + return variantToFloat64(result), nil +} + +func toFloat32Err(result *ole.VARIANT, err error) (float32, error) { + if err != nil { + return 0, err + } + return variantToFloat32(result), nil +} + +func toStringErr(result *ole.VARIANT, err error) (string, error) { + if err != nil { + return "", err + } + return variantToString(result), nil +} + +func toBoolErr(result *ole.VARIANT, err error) (bool, error) { + if err != nil { + return false, err + } + return variantToBool(result), nil +} + +func toTimeErr(result *ole.VARIANT, err error) (*time.Time, error) { + if err != nil { + return nil, err + } + return variantToTime(result), nil +} + +func variantToIDispatch(v *ole.VARIANT) *ole.IDispatch { + value := v.Value() + if value == nil { + return nil + } + return v.ToIDispatch() +} + +func variantToInt64(v *ole.VARIANT) int64 { + value := v.Value() + if value == nil { + return 0 + } + return value.(int64) +} + +func variantToInt32(v *ole.VARIANT) int32 { + value := v.Value() + if value == nil { + return 0 + } + return value.(int32) +} + +func variantToFloat64(v *ole.VARIANT) float64 { + value := v.Value() + if value == nil { + return 0 + } + return value.(float64) +} + +func variantToFloat32(v *ole.VARIANT) float32 { + value := v.Value() + if value == nil { + return 0 + } + return value.(float32) +} + +func variantToString(v *ole.VARIANT) string { + value := v.Value() + if value == nil { + return "" + } + return value.(string) +} + +func variantToBool(v *ole.VARIANT) bool { + value := v.Value() + if value == nil { + return false + } + return value.(bool) +} + +func variantToTime(v *ole.VARIANT) *time.Time { + value := v.Value() + if value == nil { + return nil + } + valueTime := value.(time.Time) + return &valueTime +}