diff --git a/cfbs.json b/cfbs.json index 555f71a..01d577f 100644 --- a/cfbs.json +++ b/cfbs.json @@ -154,6 +154,25 @@ "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-module-winget": { + "description": "Package method for Windows winget package manager.", + "subdirectory": "management/package-module-winget", + "dependencies": ["powershell-execution-policy"], + "steps": [ + "directory ./ services/cfbs/modules/package-module-winget/", + "policy_files services/cfbs/modules/package-module-winget/", + "bundles winget_installed" + ] + }, "promise-type-ansible": { "description": "Promise type to manage systemd services.", "subdirectory": "promise-types/ansible", diff --git a/management/package-module-winget/install-winget-powershell-module.ps1 b/management/package-module-winget/install-winget-powershell-module.ps1 new file mode 100644 index 0000000..3e002c6 --- /dev/null +++ b/management/package-module-winget/install-winget-powershell-module.ps1 @@ -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 diff --git a/management/package-module-winget/install-winget.ps1 b/management/package-module-winget/install-winget.ps1 new file mode 100644 index 0000000..1a778fe --- /dev/null +++ b/management/package-module-winget/install-winget.ps1 @@ -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 diff --git a/management/package-module-winget/package-module-winget.cf b/management/package-module-winget/package-module-winget.cf new file mode 100644 index 0000000..dbbf9ea --- /dev/null +++ b/management/package-module-winget/package-module-winget.cf @@ -0,0 +1,21 @@ +# 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 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_add_command => "$(sys.winsysdir)\\WindowsPowerShell\\v1.0\\powershell.exe -Command \"winget install --accept-source-agreements --accept-package-agreements "; + + # 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 "; +} diff --git a/management/package-module-winget/winget-installed.cf b/management/package-module-winget/winget-installed.cf new file mode 100644 index 0000000..e3afb6e --- /dev/null +++ b/management/package-module-winget/winget-installed.cf @@ -0,0 +1,22 @@ +bundle agent winget_installed +{ + methods: + windows:: + "powershell_execution_policy_set" usebundle => powershell_execution_policy_set("LocalMachine", "Unrestricted"), + classes => if_ok("execution_policy_ok"); + + 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_installed" expression => returnszero("winget -v | out-null", "powershell"); + "winget_module_installed" expression => 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_module_installed.execution_policy_ok:: + "powershell.exe -File '${this.promise_dirname}/install-winget-powershell-module.ps1'" + contain => powershell; +} diff --git a/management/powershell-execution-policy/powershell-execution-policy.cf b/management/powershell-execution-policy/powershell-execution-policy.cf new file mode 100644 index 0000000..4f65904 --- /dev/null +++ b/management/powershell-execution-policy/powershell-execution-policy.cf @@ -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 -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; +}