├── .gitignore ├── README.md ├── session.go ├── examples ├── important │ └── main.go └── langpackja │ └── main.go ├── important_updates.go ├── download.go ├── LICENSE ├── oleconv.go ├── install.go └── search.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | windowsupdate 2 | ============= 3 | 4 | A library written in Go using [go-ole/go-ole](https://github.com/go-ole/go-ole) and [Windows Update Agent API](https://msdn.microsoft.com/en-us/library/windows/desktop/aa387099\(v=vs.85%29.aspx). 5 | 6 | I am not intended to cover all APIs in Windows Update Agent API. 7 | I'd rather implement minimal features for my use cases. 8 | 9 | ## License 10 | 11 | MIT 12 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | 3 | import ( 4 | "github.com/go-ole/go-ole" 5 | "github.com/go-ole/go-ole/oleutil" 6 | ) 7 | 8 | type Session ole.IDispatch 9 | 10 | func NewSession() (*Session, error) { 11 | unknown, err := oleutil.CreateObject("Microsoft.Update.Session") 12 | if err != nil { 13 | return nil, err 14 | } 15 | disp, err := unknown.QueryInterface(ole.IID_IDispatch) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return (*Session)(disp), nil 20 | } 21 | 22 | func (s *Session) Release() { 23 | (*ole.IDispatch)(s).Release() 24 | } 25 | -------------------------------------------------------------------------------- /examples/important/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hnakamur/windowsupdate" 7 | "github.com/go-ole/go-ole" 8 | ) 9 | 10 | func main() { 11 | ole.CoInitialize(0) 12 | defer ole.CoUninitialize() 13 | 14 | updatesToInstall, result, err := windowsupdate.InstallImportantUpdates() 15 | if err != nil { 16 | fmt.Printf("Error: %v\n", err) 17 | return 18 | } 19 | 20 | fmt.Printf("Installed %d important updates. ResultCode=%d, RebootRequired=%v\n", len(updatesToInstall), result.ResultCode, result.RebootRequired) 21 | for i := 0; i < len(updatesToInstall); i++ { 22 | u := updatesToInstall[i] 23 | ur := result.UpdateResults[i] 24 | fmt.Printf("ID=%s, Title=%s, ResultCode=%d, RebootRequired=%v\n", u.ID, u.Title, ur.ResultCode, ur.RebootRequired) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /important_updates.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | func InstallImportantUpdates() (updatesToInstall []Update, result InstallationResult, err error) { 3 | session, err := NewSession() 4 | if err != nil { 5 | return 6 | } 7 | defer session.Release() 8 | 9 | updatesToInstall, err = session.Search("IsInstalled=0 and Type='Software' and AutoSelectOnWebSites=1") 10 | if err != nil { 11 | return 12 | } 13 | 14 | if len(updatesToInstall) == 0 { 15 | return 16 | } 17 | 18 | updatesToDownload := selectUpdatesToDownload(updatesToInstall) 19 | if len(updatesToDownload) > 0 { 20 | err = session.Download(updatesToDownload) 21 | if err != nil { 22 | return 23 | } 24 | } 25 | 26 | result, err = session.Install(updatesToInstall) 27 | return 28 | } 29 | 30 | func selectUpdatesToDownload(updatesToInstall []Update) []Update { 31 | updates := []Update{} 32 | for _, update := range updatesToInstall { 33 | if !update.IsDownloaded { 34 | updates = append(updates, update) 35 | } 36 | } 37 | return updates 38 | } 39 | -------------------------------------------------------------------------------- /download.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | 3 | import ( 4 | "github.com/go-ole/go-ole" 5 | "github.com/go-ole/go-ole/oleutil" 6 | ) 7 | 8 | func (s *Session) Download(updates []Update) error { 9 | downloader, err := toIDispatchErr(oleutil.CallMethod((*ole.IDispatch)(s), "CreateUpdateDownloader")) 10 | if err != nil { 11 | return err 12 | } 13 | 14 | coll, err := toUpdateCollection(updates) 15 | if err != nil { 16 | return err 17 | } 18 | _, err = oleutil.PutProperty(downloader, "Updates", coll) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | _, err = toIDispatchErr(oleutil.CallMethod(downloader, "Download")) 24 | return err 25 | } 26 | 27 | func toUpdateCollection(updates []Update) (*ole.IDispatch, error) { 28 | unknown, err := oleutil.CreateObject("Microsoft.Update.UpdateColl") 29 | if err != nil { 30 | return nil, err 31 | } 32 | coll, err := unknown.QueryInterface(ole.IID_IDispatch) 33 | if err != nil { 34 | return nil, err 35 | } 36 | for _, update := range updates { 37 | _, err := oleutil.CallMethod(coll, "Add", update.disp) 38 | if err != nil { 39 | return nil, err 40 | } 41 | } 42 | return coll, nil 43 | } 44 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hiroaki Nakamura 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /oleconv.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | 3 | import "github.com/go-ole/go-ole" 4 | 5 | func toIDispatchErr(result *ole.VARIANT, err error) (*ole.IDispatch, error) { 6 | if err != nil { 7 | return nil, err 8 | } 9 | return result.ToIDispatch(), nil 10 | } 11 | 12 | func toInt64Err(result *ole.VARIANT, err error) (int64, error) { 13 | if err != nil { 14 | return 0, err 15 | } 16 | return variantToInt64(result), nil 17 | } 18 | 19 | func toInt32Err(result *ole.VARIANT, err error) (int32, error) { 20 | if err != nil { 21 | return 0, err 22 | } 23 | return variantToInt32(result), nil 24 | } 25 | 26 | func toStrErr(result *ole.VARIANT, err error) (string, error) { 27 | if err != nil { 28 | return "", err 29 | } 30 | return variantToStr(result), nil 31 | } 32 | 33 | func toBoolErr(result *ole.VARIANT, err error) (bool, error) { 34 | if err != nil { 35 | return false, err 36 | } 37 | return variantToBool(result), nil 38 | } 39 | 40 | func variantToInt64(v *ole.VARIANT) int64 { 41 | return v.Value().(int64) 42 | } 43 | 44 | func variantToInt32(v *ole.VARIANT) int32 { 45 | return v.Value().(int32) 46 | } 47 | 48 | func variantToStr(v *ole.VARIANT) string { 49 | return v.Value().(string) 50 | } 51 | 52 | func variantToBool(v *ole.VARIANT) bool { 53 | return v.Value().(bool) 54 | } 55 | -------------------------------------------------------------------------------- /examples/langpackja/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-ole/go-ole" 7 | "github.com/hnakamur/windowsupdate" 8 | ) 9 | 10 | const ( 11 | JapaneseLanguagePackUpdateID = "00a156d4-3876-4cd5-bd38-517679c6ba59" 12 | ) 13 | 14 | func InstallJapaneseLanguagePack() error { 15 | session, err := windowsupdate.NewSession() 16 | if err != nil { 17 | return err 18 | } 19 | defer session.Release() 20 | 21 | fmt.Printf("Start searching...\n") 22 | update, err := session.FindByUpdateID(JapaneseLanguagePackUpdateID) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | if update.Installed { 28 | fmt.Printf("already installed. exiting\n") 29 | return nil 30 | } 31 | 32 | updates := []windowsupdate.Update{update} 33 | 34 | if update.Downloaded { 35 | fmt.Printf("already downloaded, skip downloading\n") 36 | } else { 37 | err = session.Download(updates) 38 | if err != nil { 39 | return err 40 | } 41 | } 42 | 43 | result, err := session.Install(updates) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | fmt.Printf("ResultCode=%d, RebootRequired=%v\n", result.ResultCode, result.RebootRequired) 49 | for i, ur := range result.UpdateResults { 50 | fmt.Printf("UpdateResult[%d] ResultCode=%d, RebootRequired=%v\n", i, ur.ResultCode, ur.RebootRequired) 51 | } 52 | 53 | return nil 54 | } 55 | 56 | func main() { 57 | ole.CoInitialize(0) 58 | defer ole.CoUninitialize() 59 | 60 | err := InstallJapaneseLanguagePack() 61 | if err != nil { 62 | fmt.Printf("Error: %v\n", err) 63 | return 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /install.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | 3 | import ( 4 | "github.com/go-ole/go-ole" 5 | "github.com/go-ole/go-ole/oleutil" 6 | ) 7 | 8 | const ( 9 | OrcNotStarted = 0 10 | OrcInProgress = 1 11 | OrcSucceeded = 2 12 | OrcSucceededWithErrors = 3 13 | OrcFailed = 4 14 | OrcAborted = 5 15 | ) 16 | 17 | type InstallationResult struct { 18 | disp *ole.IDispatch 19 | RebootRequired bool 20 | ResultCode int 21 | UpdateResults []UpdateResult 22 | } 23 | 24 | type UpdateResult struct { 25 | RebootRequired bool 26 | ResultCode int 27 | } 28 | 29 | func (s *Session) Install(updates []Update) (InstallationResult, error) { 30 | empty := InstallationResult{} 31 | installer, err := toIDispatchErr(oleutil.CallMethod((*ole.IDispatch)(s), "CreateUpdateInstaller")) 32 | if err != nil { 33 | return empty, err 34 | } 35 | 36 | coll, err := toUpdateCollection(updates) 37 | if err != nil { 38 | return empty, err 39 | } 40 | _, err = oleutil.PutProperty(installer, "Updates", coll) 41 | if err != nil { 42 | return empty, err 43 | } 44 | 45 | resultDisp, err := toIDispatchErr(oleutil.CallMethod(installer, "Install")) 46 | if err != nil { 47 | return empty, err 48 | } 49 | 50 | return toInstallationResult(resultDisp, len(updates)) 51 | } 52 | 53 | func toInstallationResult(resultDisp *ole.IDispatch, updateCount int) (InstallationResult, error) { 54 | result := InstallationResult{disp: resultDisp} 55 | rebootRequired, err := toBoolErr(oleutil.GetProperty(resultDisp, "RebootRequired")) 56 | if err != nil { 57 | return result, err 58 | } 59 | result.RebootRequired = rebootRequired 60 | 61 | resultCode, err := toInt32Err(oleutil.GetProperty(resultDisp, "ResultCode")) 62 | if err != nil { 63 | return result, err 64 | } 65 | result.ResultCode = int(resultCode) 66 | 67 | for i := 0; i < updateCount; i++ { 68 | urDisp, err := toIDispatchErr(oleutil.CallMethod(resultDisp, "GetUpdateResult", i)) 69 | if err != nil { 70 | return result, err 71 | } 72 | rebootRequired, err := toBoolErr(oleutil.GetProperty(urDisp, "RebootRequired")) 73 | if err != nil { 74 | return result, err 75 | } 76 | 77 | resultCode, err := toInt32Err(oleutil.GetProperty(urDisp, "ResultCode")) 78 | if err != nil { 79 | return result, err 80 | } 81 | result.UpdateResults = append(result.UpdateResults, 82 | UpdateResult{ResultCode: int(resultCode), RebootRequired: rebootRequired}) 83 | } 84 | return result, nil 85 | } 86 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | package windowsupdate 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/go-ole/go-ole" 7 | "github.com/go-ole/go-ole/oleutil" 8 | ) 9 | 10 | type Update struct { 11 | disp *ole.IDispatch 12 | Identity IUpdateIdentity 13 | Title string 14 | IsDownloaded bool 15 | IsInstalled bool 16 | } 17 | 18 | type IUpdateIdentity struct { 19 | RevisionNumber int32 20 | UpdateID string 21 | } 22 | 23 | var UpdateNotFoundError = errors.New("Update not found") 24 | 25 | func (s *Session) FindByUpdateID(updateID string) (Update, error) { 26 | updates, err := s.Search("UpdateID='" + updateID + "'") 27 | if err != nil { 28 | return Update{}, err 29 | } 30 | if len(updates) == 0 { 31 | return Update{}, UpdateNotFoundError 32 | } 33 | return updates[0], nil 34 | } 35 | 36 | func (s *Session) Search(criteria string) ([]Update, error) { 37 | searcher, err := toIDispatchErr(oleutil.CallMethod((*ole.IDispatch)(s), "CreateUpdateSearcher")) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | result, err := toIDispatchErr(oleutil.CallMethod(searcher, "Search", criteria)) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | updatesDisp, err := toIDispatchErr(oleutil.GetProperty(result, "Updates")) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return toUpdates(updatesDisp) 53 | } 54 | 55 | func toUpdates(updatesDisp *ole.IDispatch) ([]Update, error) { 56 | count, err := toInt32Err(oleutil.GetProperty(updatesDisp, "Count")) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | var updates []Update 62 | for i := 0; i < int(count); i++ { 63 | updateDisp, err := toIDispatchErr(oleutil.GetProperty(updatesDisp, "Item", i)) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | update, err := toUpdate(updateDisp) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | updates = append(updates, update) 74 | } 75 | return updates, nil 76 | } 77 | 78 | func toUpdate(updateDisp *ole.IDispatch) (update Update, err error) { 79 | update.disp = updateDisp 80 | identity, err := toIDispatchErr(oleutil.GetProperty(updateDisp, "Identity")) 81 | if err != nil { 82 | return update, err 83 | } 84 | 85 | if update.Identity.RevisionNumber, err = toInt32Err(oleutil.GetProperty(identity, "RevisionNumber")); err != nil { 86 | return update, err 87 | } 88 | if update.Identity.UpdateID, err = toStrErr(oleutil.GetProperty(identity, "UpdateID")); err != nil { 89 | return update, err 90 | } 91 | 92 | if update.Title, err = toStrErr(oleutil.GetProperty(updateDisp, "Title")); err != nil { 93 | return update, err 94 | } 95 | 96 | if update.IsDownloaded, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsDownloaded")); err != nil { 97 | return update, err 98 | } 99 | 100 | if update.IsInstalled, err = toBoolErr(oleutil.GetProperty(updateDisp, "IsInstalled")); err != nil { 101 | return update, err 102 | } 103 | 104 | return update, nil 105 | } 106 | --------------------------------------------------------------------------------