Show notes: The agent is in - Episode 37 - Windows package management

Posted by Nick Anderson
May 30, 2024

Curious about package management with CFEngine on Windows?

After sharing some history on Microsoft’s global advertising campaign for “Where do you want to go today?” Craig shared some of his recent experiments with several windows based package managers as well as their related challenges.

Craig discussed difficulties with the msiexec package module, such as distinguishing which packages need installation through msi while also identifying software for removal by name, a task that can be challenging. He demonstrated this using examples from winget, chocolatey, Scoop, and PowerShell’s install-module commands.

Policy

Here is the policy used during the episode. Most of the policy was added to bundle agent mpf_main in services/main.cf

services/main.cf
bundle agent mpf_main
# User Defined Service Catalogue
{
  reports:
    "Which packages do you want to install today?";

  # "Where do you want to go today?" was the title of Microsoft's second global image advertising campaign.
  # The broadcast, print and outdoor advertising campaign was launched in November 1994 through
  # the advertising agency Wieden+Kennedy.
  # The campaign had Microsoft spending $100 million through July 1995,
  # of which $25 million would be spent during the holiday shopping season ending in December 1994.
  # https://en.wikipedia.org/wiki/Where_do_you_want_to_go_today%3F

  packages:
    # built-in/standard msiexec package module
    # includes installed software reporting
    #"c:$(const.dirsep)Users$(const.dirsep)craig$(const.dirsep)Downloads$(const.dirsep)putty-64bit-0.81-installer.msi"
    #  policy => "present",
    #  package_module => msiexec;
    # wmic product get name,version | select-string putty
    #"PuTTY release 0.81 (64-bit)"
    #  policy => "absent",
    #  package_module => msiexec;

    # winget package method
    # winget lists software not installed by winget - could be included in reporting
    # winget search putty
    #"PuTTY.PuTTY"
    #  package_policy => "add", # add or delete
    #  package_method => winget;

    # choco package method
    # chocolatey installs normal style like msi/winget
    # licensed version synchronizes choco list with Programs and Features
    # https://docs.chocolatey.org/en-us/features/package-synchronization/
    #"putty"
    #  package_policy => "delete", # add or delete
    #  package_method => choco;

    # scoop package method
    # has no view of Programs and Features
    # installs in user directory, can't install for for all users
    # gow package includes putty.exe (GNU on Windows)
    # https://github.com/bmatzelle/gow
    #"gow"
    #  package_policy => "add", # add or delete
    #  package_method => scoop;

    # install-module package method
    # focused on powershell code, not packages/programs/features
    # https://www.powershellgallery.com/packages/Putty/1.0
    # The module contains functionality to download and extract the contents of Putty to a local path
    # https://www.powershellgallery.com/packages/Putty/1.0/Content/Putty.psm1
    # $env:TEMP is C:\Users\craig\AppData\Local\Temp
    # putty.zip and PUTTY.EXE are there after "add" and running "putty" script
    "Putty"
      package_policy => "add",
      package_method => install_module;

    # nuget, dot net libraries?

    # TODO: Windows Features, Get-WindowsFeature, Install-, Uninstall-
    # https://learn.microsoft.com/en-us/powershell/module/servermanager/install-windowsfeature?view=windowsserver2022-ps

    # SMS
    # https://en.wikipedia.org/wiki/Microsoft_Configuration_Manager
    # InTune
    # PowerShell DSC (Design State Configuration) (dying?) 1.1 everything, 2.0/3.0 azure only, makes a lot of assumptions
}

body package_method winget
{
      package_changes => "bulk";
      package_name_convention   => "$(name)";
      package_delete_convention => "$(name)";

      package_installed_regex => ".*";
      # caveat, no commas between fields in Get-WinGetPackage | ConvertTo-Csv output allowed, so not in Id or InstalledVersion
      package_list_name_regex => '^"([^"]*)",.*';
      package_list_version_regex => '.*,"([^"]*)".*';

# winget output doesn't work well, not "object" output
# and not easily parseable because Name (column 1) includes spaces
#      package_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"winget install ";
#      package_delete_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"winget uninstall ";
#      package_list_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"winget list ";

# so use PowerShell module instead
      package_add_command    => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Install-WinGetPackage ";
      package_delete_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Uninstall-WinGetPackage ";
      package_list_command   => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Get-WinGetPackage | Select Id,InstalledVersion | ConvertTo-Csv ";
}

body package_method choco
{
      package_changes => "bulk";
      package_name_convention   => "$(name)";
      package_delete_convention => "$(name)";

      package_installed_regex => ".*";

      package_list_name_regex => "(\S+).*";
      package_list_version_regex => "\S+\s(\S+).*";

      package_add_command => "choco install --yes ";
      package_delete_command => "choco uninstall --yes --force-dependencies ";
      package_list_command => "choco list ";
}

# scoop needs git to update itself so bootstrap with
# scoop install git
body package_method scoop
{
      package_changes => "bulk";
      package_name_convention   => "$(name)";
      package_delete_convention => "$(name)";

      package_installed_regex => ".*";

      package_list_name_regex => "(\S+).*";
      package_list_version_regex => "\S+\s(\S+).*";

      package_add_command => "scoop install ";
      package_delete_command => "scoop uninstall ";
      package_list_command => "scoop list ";
}

# https://learn.microsoft.com/en-us/powershell/module/powershellget/?view=powershellget-3.x
body package_method install_module
{
      package_changes => "bulk";
      package_name_convention   => "$(name)";
      package_delete_convention => "$(name)";

      package_installed_regex => ".*";

      package_list_name_regex => '^"([^"]*)",.*';
      package_list_version_regex => '.*,"([^"]*)".*';

      package_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Install-Module -force -Name ";
      package_delete_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Uninstall-Module -Name ";
      package_list_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Get-InstalledModule | Select Name,Version | ConvertTo-Csv";
}

The default package module for windows was commented out in lib/packages.cf as the examples leveraged the older package_method based implementation of packages promises.

lib/packages.cf
# In order for package methods to be used as list of installed software
# must comment out the following package module platform_default.
#    windows::
#      "platform_default" string => "msiexec";

Video

The video recording is available on YouTube:

At the end of every webinar, we stop the recording for a nice and relaxed, off-the-record chat with attendees. Join the next webinar to not miss this discussion.