go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/packages/windows_packages.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package packages 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io" 10 "time" 11 12 "github.com/cockroachdb/errors" 13 "github.com/rs/zerolog/log" 14 "go.mondoo.com/cnquery/providers-sdk/v1/inventory" 15 "go.mondoo.com/cnquery/providers/os/connection/shared" 16 "go.mondoo.com/cnquery/providers/os/detector/windows" 17 "go.mondoo.com/cnquery/providers/os/resources/powershell" 18 ) 19 20 // ProcessorArchitecture Enum 21 // https://docs.microsoft.com/en-us/uwp/api/windows.system.processorarchitecture 22 // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.processorarchitecture?redirectedfrom=MSDN&view=netframework-4.8 23 // Microsoft.Windows.Appx.PackageManager.Commands.AppxPackage 24 // https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/appxpackaging.idl#L60-L67 25 const ( 26 WinArchX86 = 0 27 WinArchArm = 5 28 WinArchX64 = 9 29 WinArchNeutral = 11 30 WinArchArm64 = 12 31 // The Arm64 processor architecture emulating the X86 architecture 32 WinArchX86OnArm64 = 14 33 ) 34 35 // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/ff357803(v=vs.85) 36 var ( 37 wsusClassificationGUID = map[string]WSUSClassification{ 38 "5c9376ab-8ce6-464a-b136-22113dd69801 ": Application, 39 "434de588-ed14-48f5-8eed-a15e09a991f6": Connectors, 40 "e6cf1350-c01b-414d-a61f-263d14d133b4": CriticalUpdates, 41 "e0789628-ce08-4437-be74-2495b842f43b": DefinitionUpdates, 42 "e140075d-8433-45c3-ad87-e72345b3607": DeveloperKits, 43 "b54e7d24-7add-428f-8b75-90a396fa584f ": FeaturePacks, 44 "9511D615-35B2-47BB-927F-F73D8E9260BB": Guidance, 45 "0fa1201d-4330-4fa8-8ae9-b877473b6441": SecurityUpdates, 46 "68c5b0a3-d1a6-4553-ae49-01d3a7827828": ServicePacks, 47 "b4832bd8-e735-4761-8daf-37f882276dab": Tools, 48 "28bc880e-0592-4cbf-8f95-c79b17911d5f": UpdateRollups, 49 "cd5ffd1e-e932-4e3a-bf74-18bf0b1bbd83": Updates, 50 "ebfc1fc5-71a4-4f7b-9aca-3b9a503104a0": Drivers, 51 "8c3fcc84-7410-4a95-8b89-a166a0190486": Defender, 52 } 53 54 appxArchitecture = map[int]string{ 55 WinArchNeutral: "neutral", 56 WinArchX86: "x86", 57 WinArchX64: "x64", 58 WinArchArm64: "arm64", 59 WinArchArm: "arm", 60 WinArchX86OnArm64: "x86onarm", 61 } 62 ) 63 64 type WSUSClassification int 65 66 const ( 67 Application WSUSClassification = iota 68 Connectors 69 CriticalUpdates 70 DefinitionUpdates 71 DeveloperKits 72 FeaturePacks 73 Guidance 74 SecurityUpdates 75 ServicePacks 76 Tools 77 UpdateRollups 78 Updates 79 Drivers 80 Defender 81 ) 82 83 var ( 84 WINDOWS_QUERY_HOTFIXES = `Get-HotFix | Select-Object -Property Status, Description, HotFixId, Caption, InstalledOn, InstalledBy | ConvertTo-Json` 85 WINDOWS_QUERY_APPX_PACKAGES = `Get-AppxPackage -AllUsers | Select Name, PackageFullName, Architecture, Version | ConvertTo-Json` 86 ) 87 88 type powershellWinAppxPackages struct { 89 Name string `json:"Name"` 90 FullName string `json:"PackageFullName"` 91 Architecture int `json:"Architecture"` 92 Version string `json:"Version"` 93 } 94 95 // Good read: https://www.wintips.org/view-installed-apps-and-packages-in-windows-10-8-1-8-from-powershell/ 96 func ParseWindowsAppxPackages(input io.Reader) ([]Package, error) { 97 data, err := io.ReadAll(input) 98 if err != nil { 99 return nil, err 100 } 101 102 var appxPackages []powershellWinAppxPackages 103 104 // handle case where no packages are installed 105 if len(data) == 0 { 106 return []Package{}, nil 107 } 108 109 err = json.Unmarshal(data, &appxPackages) 110 if err != nil { 111 return nil, err 112 } 113 114 pkgs := make([]Package, len(appxPackages)) 115 for i := range appxPackages { 116 arch, ok := appxArchitecture[appxPackages[i].Architecture] 117 if !ok { 118 log.Warn().Int("arch", appxPackages[i].Architecture).Msg("unknown architecture value for windows appx package") 119 arch = "unknown" 120 } 121 122 pkgs[i] = Package{ 123 Name: appxPackages[i].Name, 124 Version: appxPackages[i].Version, 125 Arch: arch, 126 Format: "windows/appx", 127 } 128 } 129 return pkgs, nil 130 } 131 132 type PowershellWinHotFix struct { 133 Status string `json:"Status"` 134 Description string `json:"Description"` 135 HotFixId string `json:"HotFixId"` 136 Caption string `json:"Caption"` 137 InstalledOn struct { 138 Value string `json:"value"` 139 DateTime string `json:"DateTime"` 140 } `json:"InstalledOn"` 141 InstalledBy string `json:"InstalledBy"` 142 } 143 144 func (hf PowershellWinHotFix) InstalledOnTime() *time.Time { 145 return powershell.PSJsonTimestamp(hf.InstalledOn.Value) 146 } 147 148 func ParseWindowsHotfixes(input io.Reader) ([]PowershellWinHotFix, error) { 149 data, err := io.ReadAll(input) 150 if err != nil { 151 return nil, err 152 } 153 154 // for empty result set do not get the '{}', therefore lets abort here 155 if len(data) == 0 { 156 return []PowershellWinHotFix{}, nil 157 } 158 159 var powershellWinHotFixPkgs []PowershellWinHotFix 160 err = json.Unmarshal(data, &powershellWinHotFixPkgs) 161 if err != nil { 162 return nil, err 163 } 164 165 return powershellWinHotFixPkgs, nil 166 } 167 168 func HotFixesToPackages(hotfixes []PowershellWinHotFix) []Package { 169 pkgs := make([]Package, len(hotfixes)) 170 for i := range hotfixes { 171 pkgs[i] = Package{ 172 Name: hotfixes[i].HotFixId, 173 Description: hotfixes[i].Description, 174 Format: "windows/hotfix", 175 } 176 } 177 return pkgs 178 } 179 180 type WinPkgManager struct { 181 conn shared.Connection 182 platform *inventory.Platform 183 } 184 185 func (w *WinPkgManager) Name() string { 186 return "Windows Package Manager" 187 } 188 189 func (w *WinPkgManager) Format() string { 190 return "win" 191 } 192 193 const installedAppsScript = ` 194 Get-ItemProperty (@( 195 'HKLM:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*', 196 'HKCU:\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*', 197 'HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*', 198 'HKCU:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*' 199 ) | Where-Object { Test-Path $_ }) | 200 Select-Object -Property DisplayName,DisplayVersion,Publisher,EstimatedSize,InstallSource,UninstallString | ConvertTo-Json -Compress 201 ` 202 203 // returns installed appx packages as well as hot fixes 204 func (w *WinPkgManager) List() ([]Package, error) { 205 b, err := windows.Version(w.platform.Version) 206 if err != nil { 207 return nil, err 208 } 209 210 pkgs := []Package{} 211 212 cmd, err := w.conn.RunCommand(powershell.Encode(installedAppsScript)) 213 if err != nil { 214 return nil, fmt.Errorf("could not read app package list") 215 } 216 appPkgs, err := ParseWindowsAppPackages(cmd.Stdout) 217 if err != nil { 218 return nil, fmt.Errorf("could not read app package list") 219 } 220 pkgs = append(pkgs, appPkgs...) 221 222 // only win 10+ are compatible with app x packages 223 if b.Build > 10240 { 224 cmd, err := w.conn.RunCommand(powershell.Wrap(WINDOWS_QUERY_APPX_PACKAGES)) 225 if err != nil { 226 return nil, fmt.Errorf("could not read appx package list") 227 } 228 appxPkgs, err := ParseWindowsAppxPackages(cmd.Stdout) 229 if err != nil { 230 return nil, fmt.Errorf("could not read appx package list") 231 } 232 pkgs = append(pkgs, appxPkgs...) 233 } 234 235 // hotfixes 236 cmd, err = w.conn.RunCommand(powershell.Wrap(WINDOWS_QUERY_HOTFIXES)) 237 if err != nil { 238 return nil, errors.Wrap(err, "could not fetch hotfixes") 239 } 240 hotfixes, err := ParseWindowsHotfixes(cmd.Stdout) 241 if err != nil { 242 return nil, errors.Wrapf(err, "could not parse hotfix results") 243 } 244 hotfixAsPkgs := HotFixesToPackages(hotfixes) 245 246 pkgs = append(pkgs, hotfixAsPkgs...) 247 248 return pkgs, nil 249 } 250 251 func ParseWindowsAppPackages(input io.Reader) ([]Package, error) { 252 data, err := io.ReadAll(input) 253 if err != nil { 254 return nil, err 255 } 256 257 // for empty result set do not get the '{}', therefore lets abort here 258 if len(data) == 0 { 259 return []Package{}, nil 260 } 261 262 type pwershellUninstallEntry struct { 263 DisplayName string `json:"DisplayName"` 264 DisplayVersion string `json:"DisplayVersion"` 265 Publisher string `json:"Publisher"` 266 InstallSource string `json:"InstallSource"` 267 EstimatedSize int `json:"EstimatedSize"` 268 UninstallString string `json:"UninstallString"` 269 } 270 271 var entries []pwershellUninstallEntry 272 err = json.Unmarshal(data, &entries) 273 if err != nil { 274 return nil, err 275 } 276 277 pkgs := []Package{} 278 for i := range entries { 279 entry := entries[i] 280 if entry.UninstallString == "" { 281 continue 282 } 283 pkgs = append(pkgs, Package{ 284 Name: entry.DisplayName, 285 Version: entry.DisplayVersion, 286 Format: "windows/app", 287 }) 288 } 289 290 return pkgs, nil 291 } 292 293 func (win *WinPkgManager) Available() (map[string]PackageUpdate, error) { 294 return map[string]PackageUpdate{}, nil 295 }