Skip to content

Commit

Permalink
Added package-module-winget and needed dependency powershell-executio…
Browse files Browse the repository at this point in the history
…n-policy

Ticket: ENT-12144
  • Loading branch information
craigcomstock committed Sep 3, 2024
1 parent 2be0eee commit 7748fbe
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 0 deletions.
38 changes: 38 additions & 0 deletions cfbs.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,44 @@
"bundles maintainers_in_motd"
]
},
"powershell-execution-policy": {
"description": "Inventory and bundle for PowerShell Execution Policy",
"subdirectory": "management/powershell-execution-policy",
"steps": [
"directory ./ services/cfbs/powershell-execution-policy/",
"policy_files services/cfbs/powershell-execution-policy/",
"bundles powershell_execution_policy_inventory"
]
},
"package-method-winget": {
"description": "Package method for Windows winget package manager.",
"subdirectory": "management/package-method-winget",
"dependencies": ["powershell-execution-policy"],
"steps": [
"input ./input.json def.json",
"directory ./ services/cfbs/modules/package-method-winget/",
"policy_files services/cfbs/modules/package-method-winget/",
"bundles package_method_winget:package_method_winget winget_installed"
],
"input": [
{
"type": "string",
"variable": "accept_source_agreements",
"namespace": "package_method_winget",
"bundle": "package_method_winget",
"label": "Accept Source Agreements",
"question": "Would you like to accept source agreements for winget inventory and packages promises? [yes|no]"
},
{
"type": "string",
"variable": "accept_package_agreements",
"namespace": "package_method_winget",
"bundle": "package_method_winget",
"label": "Accept Package Agreements",
"question": "Would you like to accept package agreements for winget inventory and packages promises? [yes|no]"
}
]
},
"promise-type-ansible": {
"description": "Promise type to manage systemd services.",
"subdirectory": "promise-types/ansible",
Expand Down
30 changes: 30 additions & 0 deletions management/package-method-winget/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package-method-winget
---

This module enables a `winget` package method used with policy like:

```cf3
packages:
windows::
"Microsoft.WindowsTerminal"
package_method => winget,
package_policy => "add";
```

Note that this module uses both the `winget` command as well as the PowerShell WinGet Client cmdlets as they make it easier to gather software inventory of currently installed packages.

`winget` should be installed on most newer systems by default.

In order for `winget` to be installed and Microsoft.WinGet.Client (the cmdlets module) ps1 scripts must be run which requires PowerShell Execution policy Unrestricted for LocalMachine.
The policy by default will not make this change.
You must opt-in by defining the class `winget_installed_allow_powershell_execution_changes` either in Groups/Host data or Augments in the namespace `data`:

```json
{
"classes": {
"data:winget_installed_allow_powershell_execution_changes": ["any"]
}
}
```

As of 2024-08-30 testing Windows AWS instances, none of them have `winget` installed.
6 changes: 6 additions & 0 deletions management/package-method-winget/install-winget-cli.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# instructions from https://github.com/microsoft/winget-cli
# WinGet.Client needs NuGet
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-Module -Name Microsoft.WinGet.Client
Import-Module -Name Microsoft.WinGet.Client
9 changes: 9 additions & 0 deletions management/package-method-winget/install-winget.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# from https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget
$progressPreference = 'silentlyContinue'
Write-Information "Downloading WinGet and its dependencies..."
Invoke-WebRequest -Uri https://aka.ms/getwinget -OutFile Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
Invoke-WebRequest -Uri https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx -OutFile Microsoft.VCLibs.x64.14.00.Desktop.appx
Invoke-WebRequest -Uri https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx -OutFile Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.VCLibs.x64.14.00.Desktop.appx
Add-AppxPackage Microsoft.UI.Xaml.2.8.x64.appx
Add-AppxPackage Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle
67 changes: 67 additions & 0 deletions management/package-method-winget/package-method-winget.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# switch to module specific namespace to avoid name collisions
body file control
{
namespace => "package_method_winget";
}

# package_method_winget bundle's purpose is to look at inputs/data for acceptance of source and package agreements
# these MUST be agreed to in order for this package method to work properly.
bundle agent package_method_winget
{
vars:
# for group/host data, look in data:bundle.setting
# for cfbs inputs, look in bundle:bundle.setting as all three (namespace, bundle, variable) are required by cfbs
"var_accept_package_agreements" string => ifelse(
isvariable("data:package_method_winget.accept_package_agreements"),
"${data:package_method_winget.accept_package_agreements}",
"${package_method_winget:package_method_winget.accept_package_agreements}");
"var_accept_source_agreements" string => ifelse(
isvariable("data:package_method_winget.accept_source_agreements"),
"${data:package_method_winget.accept_source_agreements}",
"${package_method_winget:package_method_winget.accept_source_agreements}");

classes:
"accept_source_agreements" expression => regcmp("^[yY][eE]?[sS]?", "${var_accept_source_agreements}"),
scope => "namespace";
"accept_package_agreements" expression => regcmp("^[yY][eE]?[sS]?", "${var_accept_package_agreements}"),
scope => "namespace";

reports:
windows::
"Please specify if you wish to --accept-source-agreements when using winget package method promises with the cfbs inputs or the variable package_method_winget:package_method_winget.accept_source_agreements having the value yes or no"
if => "!accept_source_agreements";
"Please specify if you wish to --accept-package-agreements when using winget package method promises with cfbs inputs or the variable package_method_winget:package_method_winget.accept_package_agreements having the value yes or no"
if => "!accept_package_agreements";
}

# switch back to default namespace for ease of use in policy, e.g. refer to this package method as just `winget` instead of `package_method_winget:winget` in packages promises.
body file control
{
namespace => "default";
}
# NOTE: CASE MATTERS in package names, e.g. Docker.DockerDesktop will match but docker.dockerdesktop won't and so will re-install each agent run!
# TODO: fix case sensitive package names? ^^^
body package_method winget
{
package_changes => "bulk";
package_name_convention => "$(name)";
package_delete_convention => "$(name)";

package_installed_regex => ".*";
# Note that package_list_name_regex does not allow for commas inside of package names as parsing that from ConvertTo-Csv below would be too complex
package_list_name_regex => '^"([^"]*)",.*';
package_list_version_regex => '.*,"([^"]*)".*';


# Here we use the Get-WinGetPackage Cmdlet because it is easier to produce easily parsed information that way
package_list_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Get-WinGetPackage | Select Id,InstalledVersion | ConvertTo-Csv ";
package_delete_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Uninstall-WinGetPackage ";

# Here we use winget instead of PowerShell Cmdlets because we can provide the --accept-source-agreements and --accept-package-agreements this way which gets around dialog prompts
package_method_winget:accept_source_agreements.package_method_winget:accept_package_agreements::
package_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"winget install --accept-source-agreements --accept-package-agreements ";
!package_method_winget:accept_source_agreements|!package_method_winget:accept_package_agreements::
# the package name is appended to the end of this command, so we try here to make a command which conveys information only
package_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"Write-Host You must set some vars for package-method-winget to work;exit 1; rem Trying to add package:";
}

11 changes: 11 additions & 0 deletions management/package-method-winget/test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
cf-remote spawn --platform debian-12-arm64 --name hub --count 1 --role hub
cf-remote spawn --platform windows-2008 --role client --count 1 --name win2008
cf-remote spawn --platform windows-2012 --role client --count 1 --name win2012
cf-remote spawn --platform windows-2016 --role client --count 1 --name win2016
cf-remote spawn --platform windows-2019 --role client --count 1 --name win2019

clients=win2012 win2008 win2016 win2019
parallel cf-remote install --clients ::: $clients
for client in $clients; do
cf-remote run -H $client cf-agent -IB 172.31.18.178 # hub ip, internal, not public?
done
30 changes: 30 additions & 0 deletions management/package-method-winget/winget-installed.cf
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
bundle agent winget_installed
{
classes:
windows::
# TODO, I ~might~ need to make this "mandatory" in order for automated things to work, e.g. --accept-source-arguments --accept-package-arguments
# TODO make 'winget update --accept-source-arguments' a boolean cfbs input
"winget_not_installed" expression => not(returnszero("winget -v | out-null", "powershell"));
"winget_cli_not_installed" expression => not(returnszero("Get-WinGetPackage | out-null", "powershell"));

commands:
windows.!winget_installed.execution_policy_ok::
"powershell.exe -File '${this.promise_dirname}/install-winget.ps1'"
contain => powershell;
windows.!winget_cli_installed.execution_policy_ok::
"powershell.exe -File '${this.promise_dirname}/install-winget-cli.ps1'"
contain => powershell;

methods:
windows.(winget_not_installed|winget_cli_not_installed).data:winget_installed_allow_powershell_execution_changes::
"powershell_execution_policy_set" usebundle => powershell_execution_policy_set("LocalMachine", "Unrestricted"),
classes => if_ok("execution_policy_ok");

reports:
windows.winget_not_installed::
"In order for package-module-winget to function properly, winget must be installed.";
windows.winget_cli_not_installed::
"In order for package-module-winget to function properly, winget-cli must be installed and imported.";
windows.(winget_not_installed|winget_cli_not_installed).!data:winget_installed_allow_powershell_execution_changes::
"package-module-winget needs winget and/or winget-cli installed. Opt-in for this to be automated by this policy by settings the class winget_installed_allow_powershell_execution_changes in Groups/Hosts data or in augments as data:winget_installed_allow_powershell_execution_changes";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Technically this is PowerShell Execution Policy, not Windows specific (TODO: s/windows/powershell/)
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies?view=powershell-7.4
bundle agent powershell_execution_policy_inventory
{
vars:
windows::
"execution_policy_csv_file" string => "${sys.statedir}${const.dirsep}powershell_execution_policy_list_cache.csv";
"execution_policy_list_cache_command" string => "Get-ExecutionPolicy -list | ConvertTo-Csv -notypeinformation | select-object -skip 1 | Set-Content -Path '${execution_policy_csv_file}'";
"csv" data => readcsv("${execution_policy_csv_file}");
"i" slist => getindices("csv");
"execution_policy_${csv[${i}][0]}" string => "${csv[${i}][0]}:${csv[${i}][1]}",
meta => { "inventory", "attribute_name=PowerShell Execution Policy" };

commands:
windows::
"${execution_policy_list_cache_command}"
contain => powershell;
}

# see link below for valid values for scope and policy
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-executionpolicy?view=powershell-7.4
# This bundle runs a powershell command: Set-ExecutionPolicy -ExecutionPolicy <policy> -Scope <scope>
bundle agent powershell_execution_policy_set(scope, desired_policy)
{
classes:
"policy_not_ok" expression => returnszero("if((Get-ExecutionPolicy ${scope}) -ne '${desired_policy}'){exit 0}else{exit 1}", "powershell");

commands:
windows.policy_not_ok::
"Set-ExecutionPolicy -ExecutionPolicy ${desired_policy} -Scope ${scope}"
contain => powershell;
}

0 comments on commit 7748fbe

Please sign in to comment.