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  }