├── data └── common.yaml ├── .yardopts ├── pdk.yaml ├── .rspec ├── CODEOWNERS ├── .gitattributes ├── .vscode └── extensions.json ├── .gitpod.yml ├── .github ├── workflows │ ├── release.yml │ ├── mend.yml │ ├── nightly.yml │ ├── release_prep.yml │ └── ci.yml └── pull_request_template.md ├── spec ├── spec_helper_acceptance.rb ├── run_pester.ps1 ├── default_facts.yml ├── functions │ ├── parse_scheduled_install_day_spec.rb │ └── parse_auto_update_option_spec.rb ├── spec_helper.ps1 ├── spec_helper.rb ├── tasks │ └── update_history.Tests.ps1 ├── acceptance │ └── wsus_client_spec.rb └── classes │ └── init_spec.rb ├── .devcontainer ├── Dockerfile ├── devcontainer.json └── README.md ├── .puppet-lint.rc ├── .fixtures.yml ├── .gitignore ├── .rubocop_todo.yml ├── provision.yaml ├── NOTICE ├── hiera.yaml ├── .sync.yml ├── .pdkignore ├── lib └── puppet │ └── parser │ └── functions │ ├── validate_in_range.rb │ ├── parse_auto_update_option.rb │ └── parse_scheduled_install_day.rb ├── tasks ├── update_history.json └── update_history.ps1 ├── Rakefile ├── .gitpod.Dockerfile ├── metadata.json ├── manifests ├── setting.pp └── init.pp ├── HISTORY.md ├── README.md ├── Gemfile ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── REFERENCE.md └── .rubocop.yml /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /pdk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: [] 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the modules team 2 | * @puppetlabs/modules 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb eol=lf 2 | *.erb eol=lf 3 | *.pp eol=lf 4 | *.sh eol=lf 5 | *.epp eol=lf 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "puppet.puppet-vscode", 4 | "Shopify.ruby-lsp" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - init: pdk bundle install 6 | 7 | vscode: 8 | extensions: 9 | - puppet.puppet-vscode@1.2.0:f5iEPbmOj6FoFTOV6q8LTg== 10 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "Publish module" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release.yml@main" 9 | secrets: "inherit" 10 | -------------------------------------------------------------------------------- /spec/spec_helper_acceptance.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet_litmus' 4 | require 'spec_helper_acceptance_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_acceptance_local.rb')) 5 | 6 | PuppetLitmus.configure! 7 | -------------------------------------------------------------------------------- /spec/run_pester.ps1: -------------------------------------------------------------------------------- 1 | if ($ENV:APPVEYOR -eq 'True') { 2 | Write-Host "Installing Pester ..." 3 | & choco install pester --version 4.10.1 4 | } 5 | 6 | Import-Module Pester 7 | 8 | Write-Host "Running Pester ..." 9 | 10 | Invoke-Pester @args spec/tasks 11 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM puppet/pdk:latest 2 | 3 | # [Optional] Uncomment this section to install additional packages. 4 | # RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ 5 | # && apt-get -y install --no-install-recommends 6 | 7 | -------------------------------------------------------------------------------- /spec/default_facts.yml: -------------------------------------------------------------------------------- 1 | # Use default_module_facts.yml for module specific facts. 2 | # 3 | # Facts specified here will override the values provided by rspec-puppet-facts. 4 | --- 5 | networking: 6 | ip: "172.16.254.254" 7 | ip6: "FE80:0000:0000:0000:AAAA:AAAA:AAAA" 8 | mac: "AA:AA:AA:AA:AA:AA" 9 | is_pe: false 10 | -------------------------------------------------------------------------------- /.github/workflows/mend.yml: -------------------------------------------------------------------------------- 1 | name: "mend" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | schedule: 8 | - cron: "0 0 * * *" 9 | workflow_dispatch: 10 | 11 | jobs: 12 | 13 | mend: 14 | uses: "puppetlabs/cat-github-actions/.github/workflows/mend_ruby.yml@main" 15 | secrets: "inherit" 16 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Puppet Development Kit (Community)", 3 | "dockerFile": "Dockerfile", 4 | 5 | "settings": { 6 | "terminal.integrated.profiles.linux": { 7 | "bash": { 8 | "path": "bash" 9 | } 10 | } 11 | }, 12 | 13 | "extensions": [ 14 | "puppet.puppet-vscode", 15 | "rebornix.Ruby" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --fail-on-warnings 2 | --relative 3 | --no-80chars-check 4 | --no-140chars-check 5 | --no-class_inherits_from_params_class-check 6 | --no-autoloader_layout-check 7 | --no-documentation-check 8 | --no-single_quote_string_with_variables-check 9 | --ignore-paths=.vendor/**/*.pp,.bundle/**/*.pp,pkg/**/*.pp,spec/**/*.pp,tests/**/*.pp,types/**/*.pp,vendor/**/*.pp 10 | -------------------------------------------------------------------------------- /.github/workflows/nightly.yml: -------------------------------------------------------------------------------- 1 | name: "nightly" 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | Spec: 10 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 11 | secrets: "inherit" 12 | 13 | Acceptance: 14 | needs: Spec 15 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_acceptance.yml@main" 16 | secrets: "inherit" 17 | -------------------------------------------------------------------------------- /.fixtures.yml: -------------------------------------------------------------------------------- 1 | fixtures: 2 | repositories: 3 | stdlib: "https://github.com/puppetlabs/puppetlabs-stdlib.git" 4 | registry: "https://github.com/puppetlabs/puppetlabs-registry.git" 5 | facts: 'https://github.com/puppetlabs/puppetlabs-facts.git' 6 | puppet_agent: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git' 7 | provision: 'https://github.com/puppetlabs/provision.git' 8 | symlinks: 9 | "wsus_client": "#{source_dir}" 10 | -------------------------------------------------------------------------------- /.github/workflows/release_prep.yml: -------------------------------------------------------------------------------- 1 | name: "Release Prep" 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: "Module version to be released. Must be a valid semver string. (1.2.3)" 8 | required: true 9 | 10 | jobs: 11 | release_prep: 12 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_release_prep.yml@main" 13 | with: 14 | version: "${{ github.event.inputs.version }}" 15 | secrets: "inherit" 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "ci" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "main" 7 | workflow_dispatch: 8 | 9 | jobs: 10 | Spec: 11 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_ci.yml@main" 12 | secrets: "inherit" 13 | 14 | Acceptance: 15 | needs: Spec 16 | uses: "puppetlabs/cat-github-actions/.github/workflows/module_acceptance.yml@main" 17 | with: 18 | flags: "--latest-agent" 19 | secrets: "inherit" 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | Provide a detailed description of all the changes present in this pull request. 3 | 4 | ## Additional Context 5 | Add any additional context about the problem here. 6 | - [ ] Root cause and the steps to reproduce. (If applicable) 7 | - [ ] Thought process behind the implementation. 8 | 9 | ## Related Issues (if any) 10 | Mention any related issues or pull requests. 11 | 12 | ## Checklist 13 | - [ ] 🟢 Spec tests. 14 | - [ ] 🟢 Acceptance tests. 15 | - [ ] Manually verified. (For example `puppet apply`) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/* 20 | /tmp/ 21 | /vendor/ 22 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-11-29 05:48:33 UTC using RuboCop version 1.48.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 1 10 | # Configuration parameters: EnforcedStyle, IgnoreSharedExamples. 11 | # SupportedStyles: always, named_only 12 | RSpec/NamedSubject: 13 | Exclude: 14 | - 'spec/classes/init_spec.rb' 15 | -------------------------------------------------------------------------------- /provision.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: 3 | provisioner: abs 4 | images: 5 | - win-2016-x86_64 6 | vagrant: 7 | provisioner: vagrant 8 | images: 9 | - gusztavvargadr/windows-server 10 | release_checks: 11 | provisioner: abs 12 | images: 13 | - win-2008-x86_64 14 | - win-2008r2-x86_64 15 | - win-2012-x86_64 16 | - win-2012r2-x86_64 17 | - win-2016-core-x86_64 18 | - win-2019-core-x86_64 19 | - win-7-x86_64 20 | - win-81-x86_64 21 | - win-10-pro-x86_64 22 | release_checks_7: 23 | provisioner: abs 24 | images: 25 | - win-2012r2-x86_64 26 | - win-2016-core-x86_64 27 | - win-2019-core-x86_64 28 | - win-10-pro-x86_64 29 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Puppet Module - puppetlabs-wsus_client 2 | 3 | Copyright 2015 - 2018 Puppet, Inc. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /hiera.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 5 3 | 4 | defaults: # Used for any hierarchy level that omits these keys. 5 | datadir: data # This path is relative to hiera.yaml's directory. 6 | data_hash: yaml_data # Use the built-in YAML backend. 7 | 8 | hierarchy: 9 | - name: "osfamily/major release" 10 | paths: 11 | # Used to distinguish between Debian and Ubuntu 12 | - "os/%{facts.os.name}/%{facts.os.release.major}.yaml" 13 | - "os/%{facts.os.family}/%{facts.os.release.major}.yaml" 14 | # Used for Solaris 15 | - "os/%{facts.os.family}/%{facts.kernelrelease}.yaml" 16 | - name: "osfamily" 17 | paths: 18 | - "os/%{facts.os.name}.yaml" 19 | - "os/%{facts.os.family}.yaml" 20 | - name: 'common' 21 | path: 'common.yaml' 22 | -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ".gitlab-ci.yml": 3 | delete: true 4 | appveyor.yml: 5 | delete: true 6 | .rubocop.yml: 7 | include_todos: true 8 | 9 | Rakefile: 10 | extras: 11 | - desc "Run PowerShell unit tests" 12 | - task :spec_pester do 13 | - ' exec ("powershell -NoProfile -NoLogo -NonInteractive -Command \". spec/run_pester.ps1 -EnableExit\"")' 14 | - end 15 | spec/spec_helper.rb: 16 | coverage_report: true 17 | 18 | .gitpod.Dockerfile: 19 | unmanaged: false 20 | .gitpod.yml: 21 | unmanaged: false 22 | 23 | .github/workflows/auto_release.yml: 24 | unmanaged: false 25 | .github/workflows/ci.yml: 26 | unmanaged: false 27 | .github/workflows/nightly.yml: 28 | unmanaged: false 29 | .github/workflows/release.yml: 30 | unmanaged: false 31 | .travis.yml: 32 | delete: true 33 | -------------------------------------------------------------------------------- /.pdkignore: -------------------------------------------------------------------------------- 1 | .git/ 2 | .*.sw[op] 3 | .metadata 4 | .yardoc 5 | .yardwarns 6 | *.iml 7 | /.bundle/ 8 | /.idea/ 9 | /.vagrant/ 10 | /coverage/ 11 | /bin/ 12 | /doc/ 13 | /Gemfile.local 14 | /Gemfile.lock 15 | /junit/ 16 | /log/ 17 | /pkg/ 18 | /spec/fixtures/manifests/ 19 | /spec/fixtures/modules/* 20 | /tmp/ 21 | /vendor/ 22 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | /.fixtures.yml 37 | /Gemfile 38 | /.gitattributes 39 | /.github/ 40 | /.gitignore 41 | /.pdkignore 42 | /.puppet-lint.rc 43 | /Rakefile 44 | /rakelib/ 45 | /.rspec 46 | /..yml 47 | /.yardopts 48 | /spec/ 49 | /.vscode/ 50 | /.sync.yml 51 | /.devcontainer/ 52 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/validate_in_range.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # To write custom funtion, we use the legacy Ruby functions API, which uses the Puppet::Parser::Functions namespace. 5 | # Custom function: validate_in_range 6 | # 7 | module Puppet::Parser::Functions 8 | newfunction(:validate_in_range, doc: <<-DOCUMENTATION 9 | @summary 10 | Validate the incoming value is in a certain range. 11 | 12 | @return 13 | Raises an error if the given value fails this validation. 14 | 15 | DOCUMENTATION 16 | ) do |args| 17 | data, min, max = *args 18 | 19 | data = Integer(data) 20 | min = Integer(min) 21 | max = Integer(max) 22 | raise("Expected #{data} to be greater or equal to #{min}, got #{data}") if data < min 23 | raise("Expected #{data} to be less or equal to #{min}, got #{data}") if data > max 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /tasks/update_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "Returns a history of installed Windows Updates.", 3 | "parameters": { 4 | "detailed": { 5 | "description": "Return detailed update information. Default is to return basic information", 6 | "type": "Optional[Boolean]" 7 | }, 8 | "title": { 9 | "description": "Return updates which match the specified regular expression. Default is to all updates", 10 | "type": "Optional[String]" 11 | }, 12 | "updateid": { 13 | "description": "Return updates which the specified Update ID. Default is to all updates", 14 | "type": "Optional[String]" 15 | }, 16 | "maximumupdates": { 17 | "description": "Limit the size of the history returned. Default is to return a maximum of 300 items", 18 | "type": "Optional[String]" 19 | } 20 | }, 21 | "input_method": "powershell" 22 | } 23 | -------------------------------------------------------------------------------- /.devcontainer/README.md: -------------------------------------------------------------------------------- 1 | # devcontainer 2 | 3 | 4 | For format details, see https://aka.ms/devcontainer.json. 5 | 6 | For config options, see the README at: 7 | https://github.com/microsoft/vscode-dev-containers/tree/v0.140.1/containers/puppet 8 | 9 | ``` json 10 | { 11 | "name": "Puppet Development Kit (Community)", 12 | "dockerFile": "Dockerfile", 13 | 14 | // Set *default* container specific settings.json values on container create. 15 | "settings": { 16 | "terminal.integrated.profiles.linux": { 17 | "bash": { 18 | "path": "bash", 19 | } 20 | } 21 | }, 22 | 23 | // Add the IDs of extensions you want installed when the container is created. 24 | "extensions": [ 25 | "puppet.puppet-vscode", 26 | "rebornix.Ruby" 27 | ], 28 | 29 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 30 | "forwardPorts": [], 31 | 32 | // Use 'postCreateCommand' to run commands after the container is created. 33 | "postCreateCommand": "pdk --version", 34 | } 35 | ``` 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler' 4 | require 'puppet_litmus/rake_tasks' if Gem.loaded_specs.key? 'puppet_litmus' 5 | require 'puppetlabs_spec_helper/rake_tasks' 6 | require 'puppet-syntax/tasks/puppet-syntax' 7 | require 'puppet-strings/tasks' if Gem.loaded_specs.key? 'puppet-strings' 8 | 9 | PuppetLint.configuration.send('disable_relative') 10 | PuppetLint.configuration.send('disable_80chars') 11 | PuppetLint.configuration.send('disable_140chars') 12 | PuppetLint.configuration.send('disable_class_inherits_from_params_class') 13 | PuppetLint.configuration.send('disable_autoloader_layout') 14 | PuppetLint.configuration.send('disable_documentation') 15 | PuppetLint.configuration.send('disable_single_quote_string_with_variables') 16 | PuppetLint.configuration.fail_on_warnings = true 17 | PuppetLint.configuration.ignore_paths = [".vendor/**/*.pp", ".bundle/**/*.pp", "pkg/**/*.pp", "spec/**/*.pp", "tests/**/*.pp", "types/**/*.pp", "vendor/**/*.pp"] 18 | 19 | desc "Run PowerShell unit tests" 20 | task :spec_pester do 21 | exec ("powershell -NoProfile -NoLogo -NonInteractive -Command \". spec/run_pester.ps1 -EnableExit\"") 22 | end 23 | -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gitpod/workspace-full 2 | RUN sudo wget https://apt.puppet.com/puppet-tools-release-bionic.deb && \ 3 | wget https://apt.puppetlabs.com/puppet6-release-bionic.deb && \ 4 | sudo dpkg -i puppet6-release-bionic.deb && \ 5 | sudo dpkg -i puppet-tools-release-bionic.deb && \ 6 | sudo apt-get update && \ 7 | sudo apt-get install -y pdk zsh puppet-agent && \ 8 | sudo apt-get clean && \ 9 | sudo rm -rf /var/lib/apt/lists/* 10 | RUN sudo usermod -s $(which zsh) gitpod && \ 11 | sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" && \ 12 | echo "plugins=(git gitignore github gem pip bundler python ruby docker docker-compose)" >> /home/gitpod/.zshrc && \ 13 | echo 'PATH="$PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/opt/puppetlabs/bin:/opt/puppetlabs/puppet/bin"' >> /home/gitpod/.zshrc && \ 14 | sudo /opt/puppetlabs/puppet/bin/gem install puppet-debugger hub -N && \ 15 | mkdir -p /home/gitpod/.config/puppet && \ 16 | /opt/puppetlabs/puppet/bin/ruby -r yaml -e "puts ({'disabled' => true}).to_yaml" > /home/gitpod/.config/puppet/analytics.yml 17 | RUN rm -f puppet6-release-bionic.deb puppet-tools-release-bionic.deb 18 | ENTRYPOINT /usr/bin/zsh 19 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-wsus_client", 3 | "version": "6.2.0", 4 | "author": "puppetlabs", 5 | "summary": "Manage WSUS (Windows Server Update Service) settings for client nodes", 6 | "license": "Apache-2.0", 7 | "source": "https://github.com/puppetlabs/puppetlabs-wsus_client", 8 | "project_page": "https://github.com/puppetlabs/puppetlabs-wsus_client", 9 | "issues_url": "https://github.com/puppetlabs/puppetlabs-wsus_client/issues", 10 | "dependencies": [ 11 | { 12 | "name": "puppetlabs/stdlib", 13 | "version_requirement": ">= 4.6.0 < 10.0.0" 14 | }, 15 | { 16 | "name": "puppetlabs/registry", 17 | "version_requirement": ">= 1.0.0 < 6.0.0" 18 | } 19 | ], 20 | "operatingsystem_support": [ 21 | { 22 | "operatingsystem": "Windows", 23 | "operatingsystemrelease": [ 24 | "10", 25 | "2012", 26 | "2012 R2", 27 | "2016", 28 | "2019", 29 | "2022" 30 | ] 31 | } 32 | ], 33 | "requirements": [ 34 | { 35 | "name": "puppet", 36 | "version_requirement": ">= 8.0.0 < 9.0.0" 37 | } 38 | ], 39 | "pdk-version": "3.5.0", 40 | "template-url": "https://github.com/puppetlabs/pdk-templates.git#main", 41 | "template-ref": "tags/3.5.1.2-0-gfa96b82" 42 | } 43 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/parse_auto_update_option.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # To write custom funtion, we use the legacy Ruby functions API, which uses the Puppet::Parser::Functions namespace. 5 | # Custom function: parse_auto_update_option 6 | # 7 | module Puppet::Parser::Functions 8 | newfunction(:parse_auto_update_option, type: :rvalue, arity: 1, doc: <<-DOCUMENTATION 9 | @summary 10 | Parse the incoming value to the corresponding integer, if integer is supplied simply return value 11 | 12 | @return [Integer] option auto_update_option as an integer 13 | 14 | > *Note:* 15 | Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5 16 | DOCUMENTATION 17 | ) do |args| 18 | autoupdate_hash = { 'notifyonly' => 2, 19 | 'autonotify' => 3, 20 | 'scheduled' => 4, 21 | 'autoinstall' => 5, 22 | 'notifyrestart' => 7 } 23 | 24 | option = args[0] 25 | error_msg = "Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|NotifyRestart|2|3|4|5|7, provided '#{option}'" 26 | if option.is_a?(Numeric) || option =~ %r{^\d$} 27 | option = Integer(option) if option.is_a?(String) 28 | raise Puppet::ParseError, error_msg if option < 2 || option > 7 29 | 30 | return option 31 | end 32 | 33 | return autoupdate_hash[option.downcase] if autoupdate_hash.key?(option.downcase) 34 | 35 | raise Puppet::ParseError, error_msg 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/functions/parse_scheduled_install_day_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'parse_scheduled_install_day' do 6 | expect_day = { 'Everyday' => 0, 7 | 'Sunday' => 1, 8 | 'Monday' => 2, 9 | 'Tuesday' => 3, 10 | 'Wednesday' => 4, 11 | 'Thursday' => 5, 12 | 'Friday' => 6, 13 | 'Saturday' => 7 } 14 | 15 | expect_day.each_key do |day| 16 | describe "when parsing #{day}" do 17 | it { 18 | expect(scope.function_parse_scheduled_install_day([day])).to eq(expect_day[day]) 19 | } 20 | end 21 | 22 | dday = day.downcase 23 | describe "when parsing #{dday}" do 24 | it { 25 | expect(scope.function_parse_scheduled_install_day([dday])).to eq(expect_day[day]) 26 | } 27 | end 28 | 29 | uday = day.upcase 30 | describe "when parsing #{uday}" do 31 | it { 32 | expect(scope.function_parse_scheduled_install_day([uday])).to eq(expect_day[day]) 33 | } 34 | end 35 | end 36 | expect_day.each_value do |day| 37 | describe "when parsing #{day}" do 38 | it { 39 | expect(scope.function_parse_scheduled_install_day([day])).to eq(day) 40 | } 41 | end 42 | end 43 | describe "when passing 'Whatthe'" do 44 | it 'raises error' do 45 | expect { 46 | scope.function_parse_scheduled_install_day(['Whatthe']) 47 | }.to raise_error(Puppet::Error, 48 | "Valid options for scheduled_install_day are #{expect_day.keys.join('|')}|0-7, provided 'Whatthe'") 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/puppet/parser/functions/parse_scheduled_install_day.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # To write custom funtion, we use the legacy Ruby functions API, which uses the Puppet::Parser::Functions namespace. 5 | # Custom function: parse_scheduled_install_day 6 | # 7 | module Puppet::Parser::Functions 8 | newfunction(:parse_scheduled_install_day, type: :rvalue, arity: 1, doc: <<-DOCUMENTATION 9 | @summary 10 | Parse the incoming value to the corresponding integer, if integer is supplied simply return value 11 | 12 | @return [Integer] option scheduled_install_day as an integer 13 | 14 | > *Note:* 15 | Valid options for scheduled_install_day are Everyday|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|0-7 16 | DOCUMENTATION 17 | ) do |args| 18 | day_hash = { 'Everyday' => 0, 19 | 'Sunday' => 1, 20 | 'Monday' => 2, 21 | 'Tuesday' => 3, 22 | 'Wednesday' => 4, 23 | 'Thursday' => 5, 24 | 'Friday' => 6, 25 | 'Saturday' => 7 } 26 | 27 | option = args[0] 28 | if option.is_a?(Numeric) || option =~ %r{^\d$} 29 | option = Integer(option) if option.is_a?(String) 30 | raise Puppet::ParseError, "Valid options for scheduled_install_day are #{day_hash.keys.join('|')}|0-7, provided '#{option}'" if option.negative? || option > 7 31 | 32 | return option 33 | end 34 | 35 | return day_hash[option.capitalize] if day_hash.key?(option.capitalize) 36 | 37 | raise Puppet::ParseError, "Valid options for scheduled_install_day are #{day_hash.keys.join('|')}|0-7, provided '#{option}'" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/functions/parse_auto_update_option_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'parse_auto_update_option' do 6 | expected_hash = { 'NotifyOnly' => 2, 7 | 'AutoNotify' => 3, 8 | 'Scheduled' => 4, 9 | 'AutoInstall' => 5, 10 | 'NotifyRestart' => 7 } 11 | 12 | expected_hash.each_key do |auto_update_option| 13 | describe "when parsing #{auto_update_option}" do 14 | it { 15 | expect(scope.function_parse_auto_update_option([auto_update_option])).to eq(expected_hash[auto_update_option]) 16 | } 17 | end 18 | 19 | describe "when parsing #{auto_update_option.upcase}" do 20 | it { 21 | expect(scope.function_parse_auto_update_option([auto_update_option.upcase])).to eq(expected_hash[auto_update_option]) 22 | } 23 | end 24 | 25 | describe "when parsing #{auto_update_option.downcase}" do 26 | it { 27 | expect(scope.function_parse_auto_update_option([auto_update_option.downcase])).to eq(expected_hash[auto_update_option]) 28 | } 29 | end 30 | end 31 | 32 | expected_hash.each_value do |auto_update_value| 33 | describe "when parsing #{auto_update_value}" do 34 | it { 35 | expect(scope.function_parse_auto_update_option([auto_update_value])).to eq(auto_update_value) 36 | } 37 | end 38 | end 39 | 40 | describe "when passing 'Whatthe'" do 41 | it 'raises error' do 42 | expect { 43 | scope.function_parse_auto_update_option(['Whatthe']) 44 | }.to raise_error(Puppet::Error, 45 | "Valid options for auto_update_option are #{expected_hash.keys.join('|')}|2|3|4|5|7, provided 'Whatthe'") 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /manifests/setting.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # Manages wsus_client settings 3 | # 4 | # @param ensure 5 | # Specifies whether the setting should exist. Valid options: 'present', 'absent', and 'file' 6 | # 7 | # @param key 8 | # Specifies registry_value 9 | # 10 | # @param data 11 | # Incoming data 12 | # 13 | # @param type 14 | # Data type. default value: dword 15 | # 16 | # @param has_enabled 17 | # Specifies whether the key should be enabled. Boolean value 18 | # 19 | # @param validate_range 20 | # Specifies whether the data should be validated as a number in a certain range 21 | # 22 | # @param validate_bool 23 | # Specifies whether the data should be validated as a boolean value 24 | # 25 | define wsus_client::setting ( 26 | Enum['present', 'absent', 'file'] $ensure = 'present', 27 | String $key = $title, 28 | Optional[Variant[String,Integer,Boolean,Stdlib::HTTPUrl]] $data = undef, 29 | String $type = 'dword', 30 | Boolean $has_enabled = true, 31 | Optional[Tuple[Integer, Integer]] $validate_range = undef, 32 | Boolean $validate_bool = false, 33 | 34 | ) { 35 | assert_private() 36 | if $data != undef { 37 | if $has_enabled { 38 | registry_value { "${key}Enabled": 39 | type => dword, 40 | data => bool2num($data != false), 41 | } 42 | } 43 | if ($data and $data != true) or $validate_bool { 44 | if $validate_range { 45 | validate_in_range($data,$validate_range[0],$validate_range[1]) 46 | } 47 | if $validate_bool { 48 | assert_type(Boolean, $data) 49 | } 50 | $_data = $validate_bool ? { 51 | true => bool2num($data), 52 | false => $data 53 | } 54 | registry_value { $key: 55 | ensure => $ensure, 56 | type => $type, 57 | data => $_data, 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /spec/spec_helper.ps1: -------------------------------------------------------------------------------- 1 | $DebugPreference = "SilentlyContinue" 2 | 3 | $here = Split-Path -Parent $MyInvocation.MyCommand.Definition 4 | $src = Resolve-Path -Path "$($here)\.." 5 | Function New-MockUpdate($UpdateID, $Title) { 6 | $properties = @{ 7 | Operation = Get-Random -Minimum 1 -Maximum 3 8 | ResultCode = Get-Random -Minimum 0 -Maximum 6 9 | Date = [DateTime]::Now 10 | UpdateIdentity = @{ 11 | RevisionNumber = Get-Random -Minimum 1 -Maximum 11 12 | UpdateID = [GUID]::NewGuid().ToString() 13 | } 14 | Title = "Mock Update Title $(Get-Random)" 15 | ServiceID = [GUID]::NewGuid().ToString() 16 | Categories = @() # TODO 17 | HResult = Get-Random -Minimum 0 -Maximum 32768 18 | Description = "Mock Description $(Get-Random)" 19 | UnmappedResultCode = Get-Random -Minimum 0 -Maximum 32768 20 | 21 | ClientApplicationID = "Mock ClientApplicationID $(Get-Random)" 22 | ServerSelection = Get-Random -Minimum 0 -Maximum 4 23 | UninstallationSteps = @("Mock UninstallationStep $(Get-Random)") 24 | UninstallationNotes = "Mock UninstallationNotes $(Get-Random)" 25 | SupportUrl = "Mock SupportUrl $(Get-Random)" 26 | } 27 | 28 | if (-Not [String]::IsNullOrEmpty($UpdateID)) { $properties.UpdateIdentity.UpdateID = $UpdateID} 29 | if (-Not [String]::IsNullOrEmpty($Title)) { $properties.Title = $Title} 30 | 31 | New-Object -TypeName PSObject -Property $properties 32 | } 33 | Function New-MockUpdateSession($UpdateCount = 0, $UpdateObjects = @()) { 34 | $mock = New-Object -TypeName PSObject 35 | 36 | $mock | Add-Member -MemberType NoteProperty -Name MockGetTotalHistoryCount -Value $UpdateCount | Out-Null 37 | 38 | # Create a random update list 39 | $Updates = $UpdateObjects 40 | While ($Updates.Count -lt $UpdateCount) { 41 | $Updates += New-MockUpdate 42 | } 43 | $mock | Add-Member -MemberType NoteProperty -Name MockQueryHistory -Value $Updates | Out-Null 44 | 45 | # The following are methods not a properties and we can't do closures so just mirror the mock properties 46 | $mock | Add-Member -MemberType ScriptMethod -Name GetTotalHistoryCount -Value { $this.MockGetTotalHistoryCount } | Out-Null 47 | $mock | Add-Member -MemberType ScriptMethod -Name QueryHistory -Value { param($start, $count) $this.MockQueryHistory[$start..($start + $count - 1)] } | Out-Null 48 | 49 | Write-Output $mock 50 | } 51 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |c| 4 | c.mock_with :rspec 5 | end 6 | 7 | require 'puppetlabs_spec_helper/module_spec_helper' 8 | require 'rspec-puppet-facts' 9 | 10 | require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) 11 | 12 | include RspecPuppetFacts 13 | 14 | default_facts = { 15 | puppetversion: Puppet.version, 16 | facterversion: Facter.version, 17 | } 18 | 19 | default_fact_files = [ 20 | File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), 21 | File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), 22 | ] 23 | 24 | default_fact_files.each do |f| 25 | next unless File.exist?(f) && File.readable?(f) && File.size?(f) 26 | 27 | begin 28 | require 'deep_merge' 29 | default_facts.deep_merge!(YAML.safe_load_file(f, permitted_classes: [], permitted_symbols: [], aliases: true)) 30 | rescue StandardError => e 31 | RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" 32 | end 33 | end 34 | 35 | # read default_facts and merge them over what is provided by facterdb 36 | default_facts.each do |fact, value| 37 | add_custom_fact fact, value, merge_facts: true 38 | end 39 | 40 | RSpec.configure do |c| 41 | c.default_facts = default_facts 42 | c.before :each do 43 | # set to strictest setting for testing 44 | # by default Puppet runs at warning level 45 | Puppet.settings[:strict] = :warning 46 | Puppet.settings[:strict_variables] = true 47 | end 48 | c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] 49 | c.after(:suite) do 50 | RSpec::Puppet::Coverage.report!(0) 51 | end 52 | 53 | # Filter backtrace noise 54 | backtrace_exclusion_patterns = [ 55 | %r{spec_helper}, 56 | %r{gems}, 57 | ] 58 | 59 | if c.respond_to?(:backtrace_exclusion_patterns) 60 | c.backtrace_exclusion_patterns = backtrace_exclusion_patterns 61 | elsif c.respond_to?(:backtrace_clean_patterns) 62 | c.backtrace_clean_patterns = backtrace_exclusion_patterns 63 | end 64 | end 65 | 66 | # Ensures that a module is defined 67 | # @param module_name Name of the module 68 | def ensure_module_defined(module_name) 69 | module_name.split('::').reduce(Object) do |last_module, next_module| 70 | last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) 71 | last_module.const_get(next_module, false) 72 | end 73 | end 74 | 75 | # 'spec_overrides' from sync.yml will appear below this line 76 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## v2.0.0 2 | 3 | ### Summary 4 | 5 | Major release which removes support for older versions of Puppet-Agent. 6 | 7 | ### Features 8 | 9 | - Add Puppet Strings docs [MODULES-9421](https://tickets.puppetlabs.com/browse/MODULES-9421) 10 | 11 | ### Bugfixes 12 | 13 | - Update acceptance tests to improve the quality and efficiency [MODULES-9411](https://tickets.puppetlabs.com/browse/MODULES-9411) 14 | 15 | ### Changed 16 | 17 | - Raise lower Puppet bound to 5.5.10 [MODULES-9414](https://tickets.puppetlabs.com/browse/MODULES-9414) 18 | 19 | ## 2018-10-31 - Supported Release 1.1.0 20 | ### Summary 21 | 22 | A feature release for Puppet 5, Puppet6, Windows Server 2016, and Windows Desktop Operating Systems 23 | 24 | ### Bugfixes 25 | 26 | - Allow module to be used with Stdlib v6 - [MODULES-7705](https://tickets.puppetlabs.com/browse/MODULES-7705) 27 | 28 | ### Features 29 | 30 | - Add support for Puppet 5 - [MODULES-5144](https://tickets.puppetlabs.com/browse/MODULES-5144) 31 | - Add support for Puppet 6 - [MODULES-7833](https://tickets.puppetlabs.com/browse/MODULES-7833) 32 | - Add Testmode Switcher for acceptance testing - [MODULES-6735](https://tickets.puppetlabs.com/browse/MODULES-6735) 33 | - Add support for Windows Server 2016 and Windows Desktop Operating Systems - [MODULES-4271](https://tickets.puppetlabs.com/browse/MODULES-4271) 34 | - Convert module to PDK format - [MODULES-7407](https://tickets.puppetlabs.com/browse/MODULES-7407) 35 | - Add PowerShell task to get Update History - [MODULES-7761](https://tickets.puppetlabs.com/browse/MODULES-7761) 36 | 37 | ## 2016-12-13 - Supported Release 1.0.3 38 | ### Summary 39 | 40 | Small release supporting Always Automatically Reboot at Scheduled Time setting. 41 | 42 | ### Bugfixes 43 | 44 | - Ensure wuaserv service is idempotent - [MODULES-2420](https://tickets.puppetlabs.com/browse/MODULES-2420) 45 | 46 | ### Features 47 | 48 | - Support AlwaysAutoRebootAtScheduledTimeMinutes - [MODULES-3475](https://tickets.puppetlabs.com/browse/MODULES-3475) 49 | - Support AlwaysAutoRebootAtScheduledTime - [MODULES-3016](https://tickets.puppetlabs.com/browse/MODULES-3016) 50 | 51 | ## 2016-05-03 - Supported Release 1.0.2 52 | ### Summary 53 | 54 | Small release with monior bugfixes 55 | 56 | ### Bugfixes 57 | - Fix links and dependencies in metadata.json 58 | - Fix acceptance tests 59 | 60 | ## 2015-12-08 - Supported Release 1.0.1 61 | ### Summary 62 | 63 | Small release for support of newer PE versions. 64 | 65 | ## 2015-09-02 - Supported release 1.0.0 66 | ### Summary 67 | 68 | First supported release 69 | 70 | ### Features 71 | - Add metadata for Puppet 4 and PE 2015.2.0 72 | - Update documentation 73 | 74 | ## 2015-07-02 - Unsupported release 0.1.3 75 | ### Summary 76 | 77 | Fix the max value of RebootRelaunchTimeout 78 | 79 | ### Features 80 | - Increase RebootRelaunchTimeout to 1440 instead of 440 81 | 82 | ## 2015-06-25 - Unsupported release 0.1.2 83 | ### Summary 84 | 85 | Readme fix, metadata addition of puppet versions, and add of CHANGELOG 86 | 87 | ## 2015-06-18 - Unsupported release 0.1.1 88 | ### Summary 89 | 90 | Update metadata for project and source urls 91 | 92 | ## 2015-06-18 - Initial Release 0.1.0 93 | ### Summary 94 | 95 | Initial release to provide user the ability to manage registry keys pertaining to windows update service 96 | -------------------------------------------------------------------------------- /tasks/update_history.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Mandatory = $False)] 4 | [Switch]$Detailed, 5 | 6 | [Parameter(Mandatory = $False)] 7 | [String]$Title, 8 | 9 | [Parameter(Mandatory = $False)] 10 | [String]$UpdateID, 11 | 12 | [Parameter(Mandatory = $False)] 13 | [Int]$MaximumUpdates = 300, 14 | 15 | [Parameter(Mandatory = $False)] 16 | [Switch]$NoOperation 17 | ) 18 | 19 | Function Get-SafeString($value) { 20 | if ($value -eq $null) { 21 | Write-Output '' 22 | } else { 23 | Write-Output $value.ToString() 24 | } 25 | } 26 | 27 | Function Get-SafeDateTime($value) { 28 | if ($value -eq $null) { 29 | Write-Output '' 30 | } else { 31 | Write-Output $value.ToString('u') 32 | } 33 | } 34 | 35 | Function Convert-ToServerSelectionString($value) { 36 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa387280(v=vs.85).aspx 37 | switch ($value) { 38 | 0 { Write-Output 'Default' } 39 | 1 { Write-Output 'ManagedServer' } 40 | 2 { Write-Output 'WindowsUpdate' } 41 | 3 { Write-Output 'Other' } 42 | Default { Write-Output "Unknown ${value}"} 43 | } 44 | } 45 | 46 | Function Convert-ToOperationResultCodeString($value) { 47 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa387095(v=vs.85).aspx 48 | switch ($value) { 49 | 0 { Write-Output 'Not Started' } 50 | 1 { Write-Output 'In Progress' } 51 | 2 { Write-Output 'Succeeded' } 52 | 3 { Write-Output 'Succeeded With Errors' } 53 | 4 { Write-Output 'Failed' } 54 | 5 { Write-Output 'Aborted' } 55 | Default { Write-Output "Unknown ${value}"} 56 | } 57 | } 58 | 59 | Function Convert-ToUpdateOperationString($value) { 60 | # https://msdn.microsoft.com/en-us/library/windows/desktop/aa387282(v=vs.85).aspx 61 | switch ($value) { 62 | 1 { Write-Output 'Installation' } 63 | 2 { Write-Output 'Uninstallation' } 64 | Default { Write-Output "Unknown ${value}"} 65 | } 66 | } 67 | 68 | Function Get-UpdateSessionObject() { 69 | $Session = New-Object -ComObject "Microsoft.Update.Session" 70 | Write-Output $Session.CreateUpdateSearcher() 71 | } 72 | 73 | Function Invoke-ExecuteTask($Detailed, $Title, $UpdateID, $MaximumUpdates) { 74 | $Searcher = Get-UpdateSessionObject 75 | # Returns IUpdateSearcher https://msdn.microsoft.com/en-us/library/windows/desktop/aa386515(v=vs.85).aspx 76 | 77 | $historyCount = $Searcher.GetTotalHistoryCount() 78 | if ($historyCount -gt $MaximumUpdates) { $historyCount = $MaximumUpdates } 79 | $Result = $Searcher.QueryHistory(0, $historyCount) | 80 | Where-Object { [String]::IsNullOrEmpty($Title) -or ($_.Title -match $Title) } | 81 | Where-Object { [String]::IsNullOrEmpty($UpdateID) -or ($_.UpdateIdentity.UpdateID -eq $UpdateID) } | 82 | ForEach-Object -Process { 83 | # Returns IUpdateHistoryEntry https://msdn.microsoft.com/en-us/library/windows/desktop/aa386400(v=vs.85).aspx 84 | 85 | # Basic Settings 86 | $props = @{ 87 | 'Operation' = Convert-ToUpdateOperationString $_.Operation 88 | 'ResultCode' = Convert-ToOperationResultCodeString $_.ResultCode 89 | 'Date' = Get-SafeDateTime $_.Date 90 | 'UpdateIdentity' = @{} 91 | 'Title' = Get-SafeString $_.Title 92 | 'ServiceID' = Get-SafeString $_.ServiceID 93 | 'Categories' = @() 94 | } 95 | $_.Categories | % { $props.Categories += $_.Name } | Out-Null 96 | $props['UpdateIdentity']['RevisionNumber'] = $_.UpdateIdentity.RevisionNumber 97 | $props['UpdateIdentity']['UpdateID'] = $_.UpdateIdentity.UpdateID 98 | 99 | # Detailed Settings 100 | if ($Detailed) { 101 | $props['HResult'] = $_.HResult 102 | $props['Description'] = Get-SafeString $_.Description 103 | $props['UnmappedResultCode'] = $_.UnmappedResultCode 104 | $props['ClientApplicationID'] = Get-SafeString $_.ClientApplicationID 105 | $props['ServerSelection'] = Convert-ToServerSelectionString $_.ServerSelection 106 | $props['UninstallationSteps'] = @() 107 | $props['UninstallationNotes'] = Get-SafeString $_.UninstallationNotes 108 | $props['SupportUrl'] = Get-SafeString $_.SupportUrl 109 | $_.UninstallationSteps | % { $props.UninstallationSteps += $_ } | Out-Null 110 | } 111 | 112 | New-Object -TypeName PSObject -Property $props 113 | } 114 | 115 | if ($Result -ne $null) { 116 | if ($Result.GetType().ToString() -ne 'System.Object[]') { 117 | '[ ' + ($Result | ConvertTo-JSON) + ' ]' 118 | } else { 119 | $Result | ConvertTo-JSON 120 | } 121 | } else { 122 | '[ ]' 123 | } 124 | } 125 | 126 | if (-Not $NoOperation) { Invoke-ExecuteTask -Detailed $Detailed -Title $Title -UpdateID $UpdateID -MaximumUpdates $MaximumUpdates } 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wsus_client 2 | 3 | #### Table of Contents 4 | 5 | 1. [Overview](#overview) 6 | 2. [Module Description](#module-description) 7 | * [What wsus_client affects](#what-wsus_client-affects) 8 | 3. [Setup](#setup) 9 | * [Beginning with wsus_client](#beginning-with-wsus_client) 10 | 4. [Usage](#usage) 11 | * [Schedule updates](#schedule-updates) 12 | 5. [Reference](#reference) 13 | 6. [Limitations](#limitations) 14 | 7. [License](#license) 15 | 8. [Development](#development) 16 | 17 | ## Overview 18 | 19 | The Windows Server Update Service (WSUS) lets Windows administrators manage operating system updates using their own servers instead of Microsoft's Windows Update servers. 20 | 21 | ## Module Description 22 | 23 | This module configures Puppet agents to schedule update downloads and installations from a WSUS server, manage user access to update settings, and configure automatic updates. 24 | 25 | ### What wsus_client affects 26 | 27 | This module modifies registry keys in `HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate`. For details about how registry key-driven WSUS configuration works, see the [Microsoft TechNet documentation](https://technet.microsoft.com/en-us/library/dd939844.aspx). 28 | 29 | **Note**: Because this module modifies registry keys on clients, it is incompatible with Group Policy Objects that manage the same WSUS settings. **Do not use wsus_client to configure WSUS access or automatic updates if you use Group Policies to configure such options on clients**, as doing so can lead to unexpected behavior. Instead, consult Microsoft's documentation on [configuring automatic updates using Group Policy](https://technet.microsoft.com/en-us/library/dd939933.aspx). 30 | 31 | ## Setup 32 | 33 | To use wsus_client, you must have a configured and running WSUS server, and your clients must run Windows Server 2003 or newer. For more information about deploying WSUS, see Microsoft's [WSUS Deployment Guide](https://technet.microsoft.com/en-us/library/dd939906.aspx). 34 | 35 | To install this module on your Puppet server, run this command: 36 | 37 | ~~~ 38 | $ puppet module install [--modulepath ] puppetlabs/wsus_client 39 | ~~~ 40 | 41 | If necessary, use the optional `--modulepath` argument to specify your Puppet server's `modulepath`. 42 | 43 | ### Beginning with wsus_client 44 | 45 | To have the client use a WSUS server and set the server's location, declare the `wsus_client` class with the WSUS server's url in the `server_url` parameter. 46 | 47 | For example, to point a node at a WSUS server located at `http://myserver` on port 8530, declare this class: 48 | 49 | ~~~ puppet 50 | class { 'wsus_client': 51 | server_url => 'http://myserver:8530', 52 | } 53 | ~~~ 54 | 55 | ## Usage 56 | 57 | ### Schedule updates 58 | 59 | To schedule when to retrieve and automatically install updates from a WSUS server, declare the `wsus_client` class with a WSUS [`server_url`][] as well as the [`auto_update_option`][], [`scheduled_install_day`][], and [`scheduled_install_hour`][] parameters. 60 | 61 | For example, to schedule weekly updates at 2 a.m. on Tuesdays using a WSUS server at `http://myserver:8530`, declare this class: 62 | 63 | ~~~ puppet 64 | class { 'wsus_client': 65 | server_url => 'http://myserver:8530', 66 | auto_update_option => "Scheduled", 67 | scheduled_install_day => "Tuesday", 68 | scheduled_install_hour => 2, 69 | } 70 | ~~~ 71 | 72 | Clients can report update events to a WSUS status server as defined by the `WUStatusServer` registry key, which must have the same value as the `WUServer` policy to be valid for automatic updates. For details, see the [Microsoft TechNet documentation](TechNet). 73 | 74 | To report the client's status to the WSUS server, use the `enable_status_server` parameter. For example, to configure a client to use `http://myserver:8530` for both updates and status reporting, declare this class: 75 | 76 | ~~~ puppet 77 | class { 'wsus_client': 78 | server_url => 'http://myserver:8530', 79 | enable_status_server => true, 80 | } 81 | ~~~ 82 | 83 | ## Reference 84 | 85 | For information on the classes and types, see the [REFERENCE.md](https://github.com/puppetlabs/puppetlabs-wsus_client/blob/main/REFERENCE.md). 86 | 87 | ## Limitations 88 | 89 | This module requires clients running Windows Server 2003 or newer, and a configured and active [WSUS server](https://technet.microsoft.com/en-us/library/hh852338.aspx) to use all of the module's options except `purge_values`. For detailed compatibility information, see the [supported module compatibility matrix](https://forge.puppet.com/supported#compat-matrix). 90 | 91 | ## License 92 | 93 | This codebase is licensed under the Apache2.0 licensing, however due to the nature of the codebase the open source dependencies may also use a combination of [AGPL](https://opensource.org/license/agpl-v3/), [BSD-2](https://opensource.org/license/bsd-2-clause/), [BSD-3](https://opensource.org/license/bsd-3-clause/), [GPL2.0](https://opensource.org/license/gpl-2-0/), [LGPL](https://opensource.org/license/lgpl-3-0/), [MIT](https://opensource.org/license/mit/) and [MPL](https://opensource.org/license/mpl-2-0/) Licensing. 94 | 95 | ## Development 96 | 97 | If you would like to contribute to this module, please follow the rules in the [CONTRIBUTING.md](https://github.com/puppetlabs/puppetlabs-wsus_client/blob/main/CONTRIBUTING.md). For more information, see our [module contribution guide](https://puppet.com/docs/puppet/latest/contributing.html). To see who's already involved, see the list of [contributors](https://github.com/puppetlabs/puppetlabs-wsus_client/graphs/contributors). 98 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # For puppetcore, set GEM_SOURCE_PUPPETCORE = 'https://rubygems-puppetcore.puppet.com' 4 | gemsource_default = ENV['GEM_SOURCE'] || 'https://rubygems.org' 5 | gemsource_puppetcore = if ENV['PUPPET_FORGE_TOKEN'] 6 | 'https://rubygems-puppetcore.puppet.com' 7 | else 8 | ENV['GEM_SOURCE_PUPPETCORE'] || gemsource_default 9 | end 10 | source gemsource_default 11 | 12 | def location_for(place_or_constraint, fake_constraint = nil, opts = {}) 13 | git_url_regex = /\A(?(?:https?|git)[:@][^#]*)(?:#(?.*))?/ 14 | file_url_regex = %r{\Afile://(?.*)} 15 | 16 | if place_or_constraint && (git_url = place_or_constraint.match(git_url_regex)) 17 | # Git source → ignore :source, keep fake_constraint 18 | [fake_constraint, { git: git_url[:url], branch: git_url[:branch], require: false }].compact 19 | 20 | elsif place_or_constraint && (file_url = place_or_constraint.match(file_url_regex)) 21 | # File source → ignore :source, keep fake_constraint or default >= 0 22 | [fake_constraint || '>= 0', { path: File.expand_path(file_url[:path]), require: false }] 23 | 24 | else 25 | # Plain version constraint → merge opts (including :source if provided) 26 | [place_or_constraint, { require: false }.merge(opts)] 27 | end 28 | end 29 | 30 | # Print debug information if DEBUG_GEMS or VERBOSE is set 31 | def print_gem_statement_for(gems) 32 | puts 'DEBUG: Gem definitions that will be generated:' 33 | gems.each do |gem_name, gem_params| 34 | puts "DEBUG: gem #{([gem_name.inspect] + gem_params.map(&:inspect)).join(', ')}" 35 | end 36 | end 37 | 38 | group :development do 39 | gem "json", '= 2.6.1', require: false if Gem::Requirement.create(['>= 3.1.0', '< 3.1.3']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 40 | gem "json", '= 2.6.3', require: false if Gem::Requirement.create(['>= 3.2.0', '< 4.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 41 | gem "racc", '~> 1.4.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 42 | gem "deep_merge", '~> 1.2.2', require: false 43 | gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false 44 | gem "facterdb", '~> 2.1', require: false if Gem::Requirement.create(['< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 45 | gem "facterdb", '~> 3.0', require: false if Gem::Requirement.create(['>= 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 46 | gem "metadata-json-lint", '~> 4.0', require: false 47 | gem "json-schema", '< 5.1.1', require: false 48 | gem "rspec-puppet-facts", '~> 4.0', require: false if Gem::Requirement.create(['< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 49 | gem "rspec-puppet-facts", '~> 5.0', require: false if Gem::Requirement.create(['>= 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 50 | gem "dependency_checker", '~> 1.0.0', require: false 51 | gem "parallel_tests", '= 3.12.1', require: false 52 | gem "pry", '~> 0.10', require: false 53 | gem "simplecov-console", '~> 0.9', require: false 54 | gem "puppet-debugger", '~> 1.6', require: false 55 | gem "rubocop", '~> 1.73.0', require: false 56 | gem "rubocop-performance", '~> 1.24.0', require: false 57 | gem "rubocop-rspec", '~> 3.5.0', require: false 58 | gem "rubocop-rspec_rails", '~> 2.31.0', require: false 59 | gem "rubocop-factory_bot", '~> 2.27.0', require: false 60 | gem "rubocop-capybara", '~> 2.22.0', require: false 61 | gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] 62 | gem "bigdecimal", '< 3.2.2', require: false, platforms: [:mswin, :mingw, :x64_mingw] 63 | end 64 | group :development, :release_prep do 65 | gem "puppet-strings", '~> 4.0', require: false 66 | gem "puppetlabs_spec_helper", '~> 8.0', require: false 67 | gem "puppet-blacksmith", '~> 7.0', require: false 68 | end 69 | group :system_tests do 70 | gem "puppet_litmus", '~> 2.0', require: false, platforms: [:ruby, :x64_mingw] if !ENV['PUPPET_FORGE_TOKEN'].to_s.empty? 71 | gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] if ENV['PUPPET_FORGE_TOKEN'].to_s.empty? 72 | gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] 73 | gem "serverspec", '~> 2.41', require: false 74 | end 75 | 76 | gems = {} 77 | bolt_version = ENV.fetch('BOLT_GEM_VERSION', nil) 78 | puppet_version = ENV.fetch('PUPPET_GEM_VERSION', nil) 79 | facter_version = ENV.fetch('FACTER_GEM_VERSION', nil) 80 | hiera_version = ENV.fetch('HIERA_GEM_VERSION', nil) 81 | 82 | gems['bolt'] = location_for(bolt_version, nil, { source: gemsource_puppetcore }) 83 | gems['puppet'] = location_for(puppet_version, nil, { source: gemsource_puppetcore }) 84 | gems['facter'] = location_for(facter_version, nil, { source: gemsource_puppetcore }) 85 | gems['hiera'] = location_for(hiera_version, nil, {}) if hiera_version 86 | 87 | # Generate the gem definitions 88 | print_gem_statement_for(gems) if ENV['DEBUG'] 89 | gems.each do |gem_name, gem_params| 90 | gem gem_name, *gem_params 91 | end 92 | 93 | # Evaluate Gemfile.local and ~/.gemfile if they exist 94 | extra_gemfiles = [ 95 | "#{__FILE__}.local", 96 | File.join(Dir.home, '.gemfile') 97 | ] 98 | 99 | extra_gemfiles.each do |gemfile| 100 | next unless File.file?(gemfile) && File.readable?(gemfile) 101 | 102 | # rubocop:disable Security/Eval 103 | eval(File.read(gemfile), binding) 104 | # rubocop:enable Security/Eval 105 | end 106 | # vim: syntax=ruby 107 | -------------------------------------------------------------------------------- /spec/tasks/update_history.Tests.ps1: -------------------------------------------------------------------------------- 1 | $here = Split-Path -Parent $MyInvocation.MyCommand.Path 2 | $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".") 3 | $helper = Join-Path (Split-Path -Parent $here) 'spec_helper.ps1' 4 | . $helper 5 | $sut = Join-Path -Path $src -ChildPath "tasks/${sut}" 6 | 7 | . $sut -NoOperation 8 | 9 | Describe 'Convert-ToServerSelectionString' { 10 | It 'should enumerate default server' { 11 | Convert-ToServerSelectionString 0 | Should Be 'Default' 12 | } 13 | 14 | It 'should enumerate managed server' { 15 | Convert-ToServerSelectionString 1 | Should Be 'ManagedServer' 16 | } 17 | 18 | It 'should enumerate a Windows Update server' { 19 | Convert-ToServerSelectionString 2 | Should Be 'WindowsUpdate' 20 | } 21 | 22 | It 'should enumerate an other server' { 23 | Convert-ToServerSelectionString 3 | Should Be 'Other' 24 | } 25 | } 26 | 27 | Describe 'Convert-ToOperationResultCodeString' { 28 | It 'should enumerate not started operation' { 29 | Convert-ToOperationResultCodeString 0 | Should Be 'Not Started' 30 | } 31 | 32 | It 'should enumerate in progress operation' { 33 | Convert-ToOperationResultCodeString 1 | Should Be 'In Progress' 34 | } 35 | 36 | It 'should enumerate a succesful operation' { 37 | Convert-ToOperationResultCodeString 2 | Should Be 'Succeeded' 38 | } 39 | 40 | It 'should enumerate a succesful with errors operation' { 41 | Convert-ToOperationResultCodeString 3 | Should Be 'Succeeded With Errors' 42 | } 43 | 44 | It 'should enumerate a failed operation' { 45 | Convert-ToOperationResultCodeString 4 | Should Be 'Failed' 46 | } 47 | 48 | It 'should enumerate an abort operation' { 49 | Convert-ToOperationResultCodeString 5 | Should Be 'Aborted' 50 | } 51 | } 52 | 53 | Describe 'Convert-ToUpdateOperationString' { 54 | It 'should enumerate installation operations' { 55 | Convert-ToUpdateOperationString 1 | Should Be 'Installation' 56 | } 57 | 58 | It 'should enumerate uninstallation operations' { 59 | Convert-ToUpdateOperationString 2 | Should Be 'Uninstallation' 60 | } 61 | } 62 | 63 | Describe 'Invoke-ExecuteTask' { 64 | $DefaultExecuteParams = @{ 65 | Detailed = $false; 66 | Title = $null 67 | UpdateID = $null 68 | MaximumUpdates = 300 69 | } 70 | 71 | It 'should return empty JSON if no history' { 72 | Mock Get-UpdateSessionObject { New-MockUpdateSession 0 } 73 | 74 | $Result = Invoke-ExecuteTask @DefaultExecuteParams | ConvertFrom-JSON 75 | $Result | Should -HaveCount 0 76 | } 77 | 78 | It 'should return a JSON array for a single element' { 79 | Mock Get-UpdateSessionObject { New-MockUpdateSession 1 } 80 | 81 | $ResultJSON = Invoke-ExecuteTask @DefaultExecuteParams 82 | $ResultJSON | Should -Match "^\[" 83 | $ResultJSON | Should -Match "\]$" 84 | 85 | $Result = $ResultJSON | ConvertFrom-JSON 86 | $Result | Should -HaveCount 1 87 | } 88 | 89 | It 'should not return detailed information when Detailed specified as false' { 90 | Mock Get-UpdateSessionObject { New-MockUpdateSession 1 } 91 | $ExecuteParams = $DefaultExecuteParams.Clone() 92 | $ExecuteParams.Detailed = $false 93 | 94 | $Result = Invoke-ExecuteTask @ExecuteParams | ConvertFrom-JSON 95 | $Result | Should -HaveCount 1 96 | $Result[0].HResult | Should -BeNullOrEmpty 97 | $Result[0].Description | Should -BeNullOrEmpty 98 | $Result[0].UnmappedResultCode | Should -BeNullOrEmpty 99 | $Result[0].ClientApplicationID | Should -BeNullOrEmpty 100 | $Result[0].ServerSelection | Should -BeNullOrEmpty 101 | $Result[0].UninstallationSteps | Should -BeNullOrEmpty 102 | $Result[0].UninstallationNotes | Should -BeNullOrEmpty 103 | $Result[0].SupportUrl | Should -BeNullOrEmpty 104 | $Result[0].UnmappedResultCode | Should -BeNullOrEmpty 105 | $Result[0].UnmappedResultCode | Should -BeNullOrEmpty 106 | } 107 | 108 | It 'should return detailed information when Detailed specified as true' { 109 | Mock Get-UpdateSessionObject { New-MockUpdateSession 1 } 110 | $ExecuteParams = $DefaultExecuteParams.Clone() 111 | $ExecuteParams.Detailed = $true 112 | 113 | $Result = Invoke-ExecuteTask @ExecuteParams | ConvertFrom-JSON 114 | $Result | Should -HaveCount 1 115 | $Result[0].HResult | Should -Not -BeNullOrEmpty 116 | $Result[0].Description | Should -Not -BeNullOrEmpty 117 | $Result[0].UnmappedResultCode | Should -Not -BeNullOrEmpty 118 | $Result[0].ClientApplicationID | Should -Not -BeNullOrEmpty 119 | $Result[0].ServerSelection | Should -Not -BeNullOrEmpty 120 | $Result[0].UninstallationSteps | Should -Not -BeNullOrEmpty 121 | $Result[0].UninstallationNotes | Should -Not -BeNullOrEmpty 122 | $Result[0].SupportUrl | Should -Not -BeNullOrEmpty 123 | $Result[0].UnmappedResultCode | Should -Not -BeNullOrEmpty 124 | $Result[0].UnmappedResultCode | Should -Not -BeNullOrEmpty 125 | } 126 | 127 | It 'should return only the maximum number of updates when specified' { 128 | Mock Get-UpdateSessionObject { New-MockUpdateSession 20 } 129 | $ExecuteParams = $DefaultExecuteParams 130 | $ExecuteParams.MaximumUpdates = 5 131 | 132 | $Result = Invoke-ExecuteTask @ExecuteParams | ConvertFrom-JSON 133 | $Result | Should -HaveCount 5 134 | } 135 | 136 | It 'should return a single update when UpdateID is specified' { 137 | $UpdateGUID = [GUID]::NewGuid().ToString() 138 | $UpdateObject = New-MockUpdate -UpdateID $UpdateGUID 139 | Mock Get-UpdateSessionObject { New-MockUpdateSession 10 @($UpdateObject) } 140 | $ExecuteParams = $DefaultExecuteParams.Clone() 141 | $ExecuteParams.UpdateID = $UpdateGUID 142 | 143 | $Result = Invoke-ExecuteTask @ExecuteParams | ConvertFrom-JSON 144 | $Result | Should -HaveCount 1 145 | } 146 | 147 | It 'should return a matching updates when Title is specified' { 148 | $UpdateObjects = @( 149 | New-MockUpdate -Title 'asserttitle' 150 | New-MockUpdate -Title 'zzAssertTitlezz' 151 | ) 152 | Mock Get-UpdateSessionObject { New-MockUpdateSession 10 $UpdateObjects } 153 | $ExecuteParams = $DefaultExecuteParams.Clone() 154 | $ExecuteParams.Title = 'AssertTitle' 155 | 156 | $Result = Invoke-ExecuteTask @ExecuteParams | ConvertFrom-JSON 157 | $Result | Should -HaveCount 2 158 | 159 | $UpdateTitles = $Result | ForEach-Object { Write-Output $_.Title } 160 | $UpdateTitles | Should -Contain 'asserttitle' 161 | $UpdateTitles | Should -Contain 'zzAssertTitlezz' 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | # Changelog 3 | 4 | All notable changes to this project will be documented in this file. 5 | 6 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org). 7 | 8 | ## [v6.2.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v6.2.0) - 2025-08-05 9 | 10 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v6.1.0...v6.2.0) 11 | 12 | ### Added 13 | 14 | - (MODULES-11590) Add support for option 7 to parameter 'auto_update_option' [#230](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/230) ([shubhamshinde360](https://github.com/shubhamshinde360)) 15 | 16 | ### Other 17 | 18 | - Update documentation for 'auto_update_option' parameter [#232](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/232) ([shubhamshinde360](https://github.com/shubhamshinde360)) 19 | 20 | ## [v6.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v6.1.0) - 2023-06-20 21 | 22 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v6.0.0...v6.1.0) 23 | 24 | ### Added 25 | 26 | - pdksync - (MAINT) - Allow Stdlib 9.x [#209](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/209) ([LukasAud](https://github.com/LukasAud)) 27 | 28 | ### Fixed 29 | 30 | - (CONT-967) Replace all uses of validate_*() with assert_type() [#206](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/206) ([david22swan](https://github.com/david22swan)) 31 | 32 | ## [v6.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v6.0.0) - 2023-04-20 33 | 34 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v5.0.1...v6.0.0) 35 | 36 | ### Changed 37 | 38 | - (CONT-804) Add Support for Puppet 8 / Drop Support for Puppet 6 [#204](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/204) ([david22swan](https://github.com/david22swan)) 39 | 40 | ## [v5.0.1](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v5.0.1) - 2023-04-20 41 | 42 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v5.0.0...v5.0.1) 43 | 44 | ### Fixed 45 | 46 | - (CONT-860) Update registry dependency [#202](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/202) ([LukasAud](https://github.com/LukasAud)) 47 | 48 | ## [v5.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v5.0.0) - 2023-03-09 49 | 50 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v4.0.0...v5.0.0) 51 | 52 | ### Changed 53 | 54 | - (gh-cat-9) Add specific data types [#181](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/181) ([LukasAud](https://github.com/LukasAud)) 55 | 56 | ### Added 57 | 58 | - pdksync - (FM-8922) - Add Support for Windows 2022 [#175](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/175) ([david22swan](https://github.com/david22swan)) 59 | 60 | ### Fixed 61 | 62 | - (MAINT) Drop support for Windows 7, 8, 2008 (Server) and 2008 R2 (Server) [#185](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/185) ([jordanbreen28](https://github.com/jordanbreen28)) 63 | - adjusted upper limit [#184](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/184) ([prolixalias](https://github.com/prolixalias)) 64 | 65 | ## [v4.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v4.0.0) - 2021-03-01 66 | 67 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.2.0...v4.0.0) 68 | 69 | ### Changed 70 | 71 | - pdksync - Remove Puppet 5 from testing and bump minimal version to 6.0.0 [#148](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/148) ([carabasdaniel](https://github.com/carabasdaniel)) 72 | 73 | ## [v3.2.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.2.0) - 2021-02-18 74 | 75 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.1.0...v3.2.0) 76 | 77 | ### Added 78 | 79 | - pdksync - (IAC-973) - Update travis/appveyor to run on new default branch `main` [#134](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/134) ([david22swan](https://github.com/david22swan)) 80 | 81 | ## [v3.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.1.0) - 2020-01-06 82 | 83 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.0.0...v3.1.0) 84 | 85 | ### Added 86 | 87 | - Update metadata.json [#114](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/114) ([sootysec](https://github.com/sootysec)) 88 | 89 | ## [v3.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.0.0) - 2019-10-18 90 | 91 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v2.0.0...v3.0.0) 92 | 93 | ### Fixed 94 | 95 | - (maint) - Fixes to HISTORY.md and metadata.json [#110](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/110) ([david22swan](https://github.com/david22swan)) 96 | 97 | ## [v2.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v2.0.0) - 2019-07-25 98 | 99 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.1.0...v2.0.0) 100 | 101 | ### Added 102 | 103 | - MODULES-9421 - Stringify module [#101](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/101) ([lionce](https://github.com/lionce)) 104 | 105 | ## [1.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.1.0) - 2018-10-25 106 | 107 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.3...1.1.0) 108 | 109 | ### Changed 110 | 111 | - (MODULES-4837) Update puppet compatibility with 4.7 as lower bound [#71](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/71) ([lbayerlein](https://github.com/lbayerlein)) 112 | 113 | ### Added 114 | 115 | - pdksync - (MODULES-7705) - Bumping stdlib dependency from < 5.0.0 to < 6.0.0 [#86](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/86) ([pmcmaw](https://github.com/pmcmaw)) 116 | - (MODULES-7222) Create a task to list the Update History [#83](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/83) ([glennsarti](https://github.com/glennsarti)) 117 | 118 | ## [1.0.3](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.3) - 2016-12-14 119 | 120 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.2...1.0.3) 121 | 122 | ### Added 123 | 124 | - (MODULES-3475) Support AlwaysAutoRebootAtScheduledTimeMinutes [#48](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/48) ([adasko](https://github.com/adasko)) 125 | - (MODULES-3016) Support AlwaysAutoRebootAtScheduledTime [#47](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/47) ([jpogran](https://github.com/jpogran)) 126 | 127 | ### Fixed 128 | 129 | - (MODULES-3632) Use json_pure always [#61](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/61) ([hunner](https://github.com/hunner)) 130 | - (MODULES-2420) omit ensure => running due to "trigger start" [#56](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/56) ([MosesMendoza](https://github.com/MosesMendoza)) 131 | 132 | ## [1.0.2](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.2) - 2016-05-04 133 | 134 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.1...1.0.2) 135 | 136 | ## [1.0.1](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.1) - 2015-12-07 137 | 138 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.0...1.0.1) 139 | 140 | ## [1.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.0) - 2015-08-31 141 | 142 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v0.1.3...1.0.0) 143 | 144 | ## [v0.1.3](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v0.1.3) - 2015-07-02 145 | 146 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/702fb9da2b7f7ca745262889912ede6c54f8543c...v0.1.3) 147 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Puppet modules 2 | 3 | So you want to contribute to a Puppet module: Great! Below are some instructions to get you started doing 4 | that very thing while setting expectations around code quality as well as a few tips for making the 5 | process as easy as possible. 6 | 7 | ### Table of Contents 8 | 9 | 1. [Getting Started](#getting-started) 10 | 1. [Commit Checklist](#commit-checklist) 11 | 1. [Submission](#submission) 12 | 1. [More about commits](#more-about-commits) 13 | 1. [Testing](#testing) 14 | - [Running Tests](#running-tests) 15 | - [Writing Tests](#writing-tests) 16 | 1. [Get Help](#get-help) 17 | 18 | ## Getting Started 19 | 20 | - Fork the module repository on GitHub and clone to your workspace 21 | 22 | - Make your changes! 23 | 24 | ## Commit Checklist 25 | 26 | ### The Basics 27 | 28 | - [x] my commit is a single logical unit of work 29 | 30 | - [x] I have checked for unnecessary whitespace with "git diff --check" 31 | 32 | - [x] my commit does not include commented out code or unneeded files 33 | 34 | ### The Content 35 | 36 | - [x] my commit includes tests for the bug I fixed or feature I added 37 | 38 | - [x] my commit includes appropriate documentation changes if it is introducing a new feature or changing existing functionality 39 | 40 | - [x] my code passes existing test suites 41 | 42 | ### The Commit Message 43 | 44 | - [x] the first line of my commit message includes: 45 | 46 | - [x] an issue number (if applicable), e.g. "(MODULES-xxxx) This is the first line" 47 | 48 | - [x] a short description (50 characters is the soft limit, excluding ticket number(s)) 49 | 50 | - [x] the body of my commit message: 51 | 52 | - [x] is meaningful 53 | 54 | - [x] uses the imperative, present tense: "change", not "changed" or "changes" 55 | 56 | - [x] includes motivation for the change, and contrasts its implementation with the previous behavior 57 | 58 | ## Submission 59 | 60 | ### Pre-requisites 61 | 62 | - Make sure you have a [GitHub account](https://github.com/join) 63 | 64 | - [Create a ticket](https://tickets.puppet.com/secure/CreateIssue!default.jspa), or [watch the ticket](https://tickets.puppet.com/browse/) you are patching for. 65 | 66 | ### Push and PR 67 | 68 | - Push your changes to your fork 69 | 70 | - [Open a Pull Request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) against the repository in the puppetlabs organization 71 | 72 | ## More about commits 73 | 74 | 1. Make separate commits for logically separate changes. 75 | 76 | Please break your commits down into logically consistent units 77 | which include new or changed tests relevant to the rest of the 78 | change. The goal of doing this is to make the diff easier to 79 | read for whoever is reviewing your code. In general, the easier 80 | your diff is to read, the more likely someone will be happy to 81 | review it and get it into the code base. 82 | 83 | If you are going to refactor a piece of code, please do so as a 84 | separate commit from your feature or bug fix changes. 85 | 86 | We also really appreciate changes that include tests to make 87 | sure the bug is not re-introduced, and that the feature is not 88 | accidentally broken. 89 | 90 | Describe the technical detail of the change(s). If your 91 | description starts to get too long, that is a good sign that you 92 | probably need to split up your commit into more finely grained 93 | pieces. 94 | 95 | Commits which plainly describe the things which help 96 | reviewers check the patch and future developers understand the 97 | code are much more likely to be merged in with a minimum of 98 | bike-shedding or requested changes. Ideally, the commit message 99 | would include information, and be in a form suitable for 100 | inclusion in the release notes for the version of Puppet that 101 | includes them. 102 | 103 | Please also check that you are not introducing any trailing 104 | whitespace or other "whitespace errors". You can do this by 105 | running "git diff --check" on your changes before you commit. 106 | 107 | 2. Sending your patches 108 | 109 | To submit your changes via a GitHub pull request, we _highly_ 110 | recommend that you have them on a topic branch, instead of 111 | directly on "main". 112 | It makes things much easier to keep track of, especially if 113 | you decide to work on another thing before your first change 114 | is merged in. 115 | 116 | GitHub has some pretty good 117 | [general documentation](http://help.github.com/) on using 118 | their site. They also have documentation on 119 | [creating pull requests](https://help.github.com/articles/creating-a-pull-request-from-a-fork/). 120 | 121 | In general, after pushing your topic branch up to your 122 | repository on GitHub, you can switch to the branch in the 123 | GitHub UI and click "Pull Request" towards the top of the page 124 | in order to open a pull request. 125 | 126 | 3. Update the related JIRA issue. 127 | 128 | If there is a JIRA issue associated with the change you 129 | submitted, then you should update the ticket to include the 130 | location of your branch, along with any other commentary you 131 | may wish to make. 132 | 133 | # Testing 134 | 135 | ## Getting Started 136 | 137 | Our Puppet modules provide [`Gemfile`](./Gemfile)s, which can tell a Ruby package manager such as [bundler](http://bundler.io/) what Ruby packages, 138 | or Gems, are required to build, develop, and test this software. 139 | 140 | Please make sure you have [bundler installed](http://bundler.io/#getting-started) on your system, and then use it to 141 | install all dependencies needed for this project in the project root by running 142 | 143 | ```shell 144 | % bundle install --path .bundle/gems 145 | Fetching gem metadata from https://rubygems.org/........ 146 | Fetching gem metadata from https://rubygems.org/.. 147 | Using rake (10.1.0) 148 | Using builder (3.2.2) 149 | -- 8><-- many more --><8 -- 150 | Using rspec-system-puppet (2.2.0) 151 | Using serverspec (0.6.3) 152 | Using rspec-system-serverspec (1.0.0) 153 | Using bundler (1.3.5) 154 | Your bundle is complete! 155 | Use `bundle show [gemname]` to see where a bundled gem is installed. 156 | ``` 157 | 158 | NOTE: some systems may require you to run this command with sudo. 159 | 160 | If you already have those gems installed, make sure they are up-to-date: 161 | 162 | ```shell 163 | % bundle update 164 | ``` 165 | 166 | ## Running Tests 167 | 168 | With all dependencies in place and up-to-date, run the tests: 169 | 170 | ### Unit Tests 171 | 172 | ```shell 173 | % bundle exec rake spec 174 | ``` 175 | 176 | This executes all the [rspec tests](http://rspec-puppet.com/) in the directories defined [here](https://github.com/puppetlabs/puppetlabs_spec_helper/blob/699d9fbca1d2489bff1736bb254bb7b7edb32c74/lib/puppetlabs_spec_helper/rake_tasks.rb#L17) and so on. 177 | rspec tests may have the same kind of dependencies as the module they are testing. Although the module defines these dependencies in its [metadata.json](./metadata.json), 178 | rspec tests define them in [.fixtures.yml](./fixtures.yml). 179 | 180 | ### Acceptance Tests 181 | 182 | Some Puppet modules also come with acceptance tests, which use [beaker][]. These tests spin up a virtual machine under 183 | [VirtualBox](https://www.virtualbox.org/), controlled with [Vagrant](http://www.vagrantup.com/), to simulate scripted test 184 | scenarios. In order to run these, you need both Virtualbox and Vagrant installed on your system. 185 | 186 | Run the tests by issuing the following command 187 | 188 | ```shell 189 | % bundle exec rake spec_clean 190 | % bundle exec rspec spec/acceptance 191 | ``` 192 | 193 | This will now download a pre-fabricated image configured in the [default node-set](./spec/acceptance/nodesets/default.yml), 194 | install Puppet, copy this module, and install its dependencies per [spec/spec_helper_acceptance.rb](./spec/spec_helper_acceptance.rb) 195 | and then run all the tests under [spec/acceptance](./spec/acceptance). 196 | 197 | ## Writing Tests 198 | 199 | ### Unit Tests 200 | 201 | When writing unit tests for Puppet, [rspec-puppet][] is your best friend. It provides tons of helper methods for testing your manifests against a 202 | catalog (e.g. contain_file, contain_package, with_params, etc). It would be ridiculous to try and top rspec-puppet's [documentation][rspec-puppet_docs] 203 | but here's a tiny sample: 204 | 205 | Sample manifest: 206 | 207 | ```puppet 208 | file { "a test file": 209 | ensure => present, 210 | path => "/etc/sample", 211 | } 212 | ``` 213 | 214 | Sample test: 215 | 216 | ```ruby 217 | it 'does a thing' do 218 | expect(subject).to contain_file("a test file").with({:path => "/etc/sample"}) 219 | end 220 | ``` 221 | 222 | ### Acceptance Tests 223 | 224 | Writing acceptance tests for Puppet involves [beaker][] and its cousin [beaker-rspec][]. A common pattern for acceptance tests is to create a test manifest, apply it 225 | twice to check for idempotency or errors, then run expectations. 226 | 227 | ```ruby 228 | it 'does an end-to-end thing' do 229 | pp = <<-EOF 230 | file { 'a test file': 231 | ensure => present, 232 | path => "/etc/sample", 233 | content => "test string", 234 | } 235 | 236 | apply_manifest(pp, :catch_failures => true) 237 | apply_manifest(pp, :catch_changes => true) 238 | 239 | end 240 | 241 | describe file("/etc/sample") do 242 | it { is_expected.to contain "test string" } 243 | end 244 | 245 | ``` 246 | 247 | # If you have commit access to the repository 248 | 249 | Even if you have commit access to the repository, you still need to go through the process above, and have someone else review and merge 250 | in your changes. The rule is that **all changes must be reviewed by a project developer that did not write the code to ensure that 251 | all changes go through a code review process.** 252 | 253 | The record of someone performing the merge is the record that they performed the code review. Again, this should be someone other than the author of the topic branch. 254 | 255 | # Get Help 256 | 257 | ### On the web 258 | * [Puppet help messageboard](http://puppet.com/community/get-help) 259 | * [Writing tests](https://docs.puppet.com/guides/module_guides/bgtm.html#step-three-module-testing) 260 | * [General GitHub documentation](http://help.github.com/) 261 | * [GitHub pull request documentation](http://help.github.com/send-pull-requests/) 262 | 263 | ### On chat 264 | * Slack (slack.puppet.com) #forge-modules, #puppet-dev, #windows, #voxpupuli 265 | * IRC (freenode) #puppet-dev, #voxpupuli 266 | 267 | 268 | [rspec-puppet]: http://rspec-puppet.com/ 269 | [rspec-puppet_docs]: http://rspec-puppet.com/documentation/ 270 | [beaker]: https://github.com/puppetlabs/beaker 271 | [beaker-rspec]: https://github.com/puppetlabs/beaker-rspec 272 | -------------------------------------------------------------------------------- /spec/acceptance/wsus_client_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | RSpec.describe 'wsus_client' do 5 | let(:reg_type) { :type_dword_converted } 6 | 7 | base_key = 'HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate' 8 | au_key = "#{base_key}\\AU" 9 | 10 | def clear_registry 11 | pp = <<~PP 12 | service {'wuauserv': 13 | ensure => stopped, 14 | }-> 15 | registry_key{'HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU': 16 | ensure => absent, 17 | purge_values => true, 18 | }-> 19 | registry_key{'HKLM\\Software\\Policies\\Microsoft\\Windows\\WindowsUpdate': 20 | ensure => absent, 21 | purge_values => true, 22 | } 23 | PP 24 | apply_manifest(pp, catch_failures: false) 25 | end 26 | 27 | def create_apply_manifest(params, clear_first = true) 28 | clear_registry if clear_first 29 | pp = "class {'wsus_client':" 30 | params.each do |k, v| 31 | v = "'#{v}'" if v.is_a? String 32 | pp += "\n #{k} => #{v}," 33 | end 34 | pp += '}' 35 | apply_manifest(pp, catch_failures: true) 36 | end 37 | 38 | shared_examples 'registry_value' do |property, key = base_key| 39 | describe windows_registry_key(key) do 40 | it { is_expected.to exist } 41 | 42 | it { 43 | expect(subject).to have_property_value(property, reg_type, reg_data) unless reg_data.nil? 44 | expect(subject).to have_property(property, reg_type) 45 | } 46 | end 47 | end 48 | 49 | shared_examples 'registry_value undefined' do |property, key = base_key| 50 | describe windows_registry_key(key) do 51 | it { is_expected.not_to have_property(property, reg_type) } 52 | end 53 | end 54 | 55 | shared_examples 'boolean values' do |param, property, key = base_key| 56 | [true, false].each do |enabled| 57 | describe enabled.to_s do 58 | let(:reg_data) { enabled ? 1 : 0 } 59 | 60 | it { create_apply_manifest param => enabled } 61 | 62 | it_behaves_like 'registry_value', property, key 63 | end 64 | end 65 | end 66 | 67 | shared_examples 'enabled range' do |param, property, array_range, key = base_key| 68 | array_range.each do |valid_value| 69 | describe valid_value.to_s do 70 | before :all do 71 | create_apply_manifest param => valid_value 72 | end 73 | 74 | describe windows_registry_key(key) do 75 | it { is_expected.to exist } 76 | it { is_expected.to have_property_value(property, :type_dword_converted, valid_value) } 77 | it { is_expected.to have_property_value("#{property}Enabled", :type_dword_converted, 1) } 78 | end 79 | end 80 | end 81 | 82 | describe 'false' do 83 | it { create_apply_manifest param => false } 84 | 85 | describe windows_registry_key(key) do 86 | it { is_expected.to exist } 87 | it { is_expected.to have_property_value("#{property}Enabled", :type_dword_converted, 0) } 88 | end 89 | end 90 | end 91 | 92 | context 'with server_url =>', testrail: ['70183', '70185', '70184'] do 93 | let(:reg_type) { :type_string } 94 | 95 | ['http://SERVER:8530', 'https://SERVER:8531'].each do |wsus_url| 96 | describe wsus_url do 97 | let(:reg_data) { wsus_url } 98 | 99 | it { create_apply_manifest server_url: wsus_url } 100 | 101 | it_behaves_like 'registry_value', 'WUServer' 102 | it_behaves_like 'registry_value undefined', 'WUStatusServer' 103 | it_behaves_like 'registry_value', 'UseWUServer', au_key do 104 | let(:reg_data) { 1 } 105 | let(:reg_type) { :type_dword_converted } 106 | end 107 | end 108 | end 109 | 110 | describe 'true', testrail: ['70189'] do 111 | let(:reg_data) { 'http://myserver:8530' } 112 | 113 | it { 114 | create_apply_manifest( 115 | server_url: 'http://myserver:8530', 116 | enable_status_server: true, 117 | ) 118 | } 119 | 120 | it_behaves_like 'registry_value', 'WUStatusServer' 121 | it_behaves_like 'registry_value', 'WUServer' 122 | end 123 | 124 | describe 'false', testrail: ['70190'] do 125 | let(:reg_data) { 'http://myserver:8530' } 126 | 127 | it { 128 | create_apply_manifest( 129 | { server_url: 'http://myserver:8530', 130 | enable_status_server: false }, false 131 | ) 132 | } 133 | 134 | it_behaves_like 'registry_value undefined', 'WUStatusServer' 135 | it_behaves_like 'registry_value', 'WUServer' 136 | end 137 | end 138 | 139 | context 'with auto_update_option =>' do 140 | { 'NotifyOnly' => 2, 141 | 'AutoNotify' => 3, 142 | 'AutoInstall' => 5 }.each do |key, au_opt| 143 | describe au_opt.to_s, testcase: ['70197', '70198', '70200'] do 144 | it { create_apply_manifest auto_update_option: au_opt } 145 | 146 | it_behaves_like 'registry_value', 'AUOptions', au_key do 147 | let(:reg_data) { au_opt } 148 | end 149 | end 150 | 151 | describe key.to_s, testcase: ['70201', '70202', '70204'] do 152 | it { create_apply_manifest auto_update_option: key } 153 | 154 | it_behaves_like 'registry_value', 'AUOptions', au_key do 155 | let(:reg_data) { au_opt } 156 | end 157 | end 158 | end 159 | ['Scheduled', 4].each do |scheduled| 160 | describe 'Scheduled', testrail: ['70203', '70199'] do 161 | it { 162 | create_apply_manifest( 163 | auto_update_option: scheduled, 164 | scheduled_install_day: 0, 165 | scheduled_install_hour: 19, 166 | ) 167 | } 168 | 169 | it_behaves_like 'registry_value', 'AUOptions', au_key do 170 | let(:reg_data) { 4 } 171 | end 172 | it_behaves_like 'registry_value', 'ScheduledInstallDay', au_key do 173 | let(:reg_data) { 0 } 174 | end 175 | it_behaves_like 'registry_value', 'ScheduledInstallTime', au_key do 176 | let(:reg_data) { 19 } 177 | end 178 | end 179 | end 180 | end 181 | 182 | context 'with accept_trusted_publisher_certs =>', testrail: ['70193', '70194'] do 183 | it_behaves_like 'boolean values', 184 | :accept_trusted_publisher_certs, 185 | 'AcceptTrustedPublisherCerts' 186 | end 187 | 188 | context 'with auto_install_minor_updates =>', testrail: ['70210', '70211'] do 189 | it_behaves_like 'boolean values', 190 | :auto_install_minor_updates, 191 | 'AutoInstallMinorUpdates', au_key 192 | end 193 | 194 | context 'with detection_frequency_hours =>', testrail: ['70213', '70214', '70215'] do 195 | it_behaves_like 'enabled range', 196 | :detection_frequency_hours, 197 | 'DetectionFrequency', 198 | [1, 22], 199 | au_key 200 | end 201 | 202 | context 'with disable_windows_update_access =>', testrail: ['70220', '70221'] do 203 | it_behaves_like 'boolean values', 204 | :disable_windows_update_access, 205 | 'DisableWindowsUpdateAccess' 206 | end 207 | 208 | context 'with elevate_non_admins =>', testrail: ['70223', '70224'] do 209 | it_behaves_like 'boolean values', 210 | :elevate_non_admins, 211 | 'ElevateNonAdmins' 212 | end 213 | 214 | context 'with no_auto_reboot_with_logged_on_users =>', testrail: ['70226', '70227'] do 215 | it_behaves_like 'boolean values', 216 | :no_auto_reboot_with_logged_on_users, 217 | 'NoAutoRebootWithLoggedOnUsers', 218 | au_key 219 | end 220 | 221 | context 'with no_auto_update =>', testrail: ['70229', '70230'] do 222 | it_behaves_like 'boolean values', 223 | :no_auto_update, 224 | 'NoAutoUpdate', 225 | au_key 226 | end 227 | 228 | context 'with reboot_relaunch_timeout_minutes =>', testrail: ['70232', '70233', '70234'] do 229 | it_behaves_like 'enabled range', 230 | :reboot_relaunch_timeout_minutes, 231 | 'RebootRelaunchTimeout', 232 | [1, 1440], 233 | au_key 234 | end 235 | 236 | context 'with reboot_warning_timeout_minutes =>', testrail: ['70239', '70240', '70241'] do 237 | it_behaves_like 'enabled range', 238 | :reboot_warning_timeout_minutes, 239 | 'RebootWarningTimeout', 240 | [1, 30], 241 | au_key 242 | end 243 | 244 | context 'with reschedule_wait_time_minutes =>', testrail: ['70246', '70247', '70248'] do 245 | it_behaves_like 'enabled range', 246 | :reschedule_wait_time_minutes, 247 | 'RescheduleWaitTime', 248 | [1, 60], 249 | au_key 250 | end 251 | 252 | context 'with scheduled_install_day =>', testrail: ['70253', '70254', '70255', '70256'] do 253 | { 'Everyday' => 0, 'Tuesday' => 3, 0 => 0, 3 => 3 }.each do |day, expected_value| 254 | describe day.to_s do 255 | it { 256 | create_apply_manifest auto_update_option: 'Scheduled', 257 | scheduled_install_day: day, 258 | scheduled_install_hour: 18 259 | } 260 | 261 | it_behaves_like 'registry_value', 'ScheduledInstallDay', au_key do 262 | let(:reg_data) { expected_value } 263 | end 264 | it_behaves_like 'registry_value', 'AUOptions', au_key do 265 | let(:reg_data) { 4 } 266 | end 267 | end 268 | end 269 | end 270 | 271 | context 'with scheduled_install_hour =>', testrail: ['70263', '70264'] do 272 | [0, 23].each do |hour| 273 | describe hour.to_s do 274 | it { 275 | create_apply_manifest auto_update_option: 'Scheduled', 276 | scheduled_install_day: 'Tuesday', 277 | scheduled_install_hour: hour 278 | } 279 | 280 | it_behaves_like 'registry_value', 'ScheduledInstallTime', au_key do 281 | let(:reg_data) { hour } 282 | end 283 | it_behaves_like 'registry_value', 'AUOptions', au_key do 284 | let(:reg_data) { 4 } 285 | end 286 | end 287 | end 288 | end 289 | 290 | context 'with target_group =>', testrail: ['70268'] do 291 | describe 'testTargetGroup' do 292 | it { 293 | create_apply_manifest target_group: 'testTargetGroup' 294 | } 295 | 296 | it_behaves_like 'registry_value', 'TargetGroup' do 297 | let(:reg_data) { 'testTargetGroup' } 298 | let(:reg_type) { :type_string } 299 | end 300 | it_behaves_like 'registry_value', 'TargetGroupEnabled' do 301 | let(:reg_data) { 1 } 302 | let(:reg_type) { :type_dword_converted } 303 | end 304 | end 305 | 306 | describe 'false', testrail: ['89606'] do 307 | it { 308 | create_apply_manifest target_group: false 309 | } 310 | 311 | it_behaves_like 'registry_value', 'TargetGroupEnabled' do 312 | let(:reg_data) { 0 } 313 | let(:reg_type) { :type_dword_converted } 314 | end 315 | end 316 | end 317 | end 318 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /spec/classes/init_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'wsus_client' do 6 | let(:reg_type) { 'dword' } 7 | let(:enabled_bit) { 1 } 8 | let(:reg_ensure) { 'present' } 9 | 10 | test_hash = { 11 | '2012' => { base_key: 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate', 12 | au_key: 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' }, 13 | '2008' => { base_key: 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate', 14 | au_key: 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU' } 15 | } 16 | 17 | shared_examples 'registry_value' do 18 | it { 19 | expect(subject).to contain_registry_value(reg_key).with( 20 | 'ensure' => reg_ensure, 21 | 'type' => reg_type, 22 | 'data' => reg_data, 23 | ) 24 | } 25 | end 26 | 27 | shared_examples 'registry_value undefined' do 28 | it { is_expected.not_to contain_registry_value(reg_key) } 29 | end 30 | 31 | shared_examples 'fail validation' do 32 | it { 33 | expect { catalogue }.to raise_error(Puppet::Error, error_message) 34 | } 35 | end 36 | 37 | shared_examples 'valid range' do |range = []| 38 | range.each do |int| 39 | describe int.to_s do 40 | let(:params) do 41 | { 42 | param_sym => int 43 | } 44 | end 45 | let(:reg_data) { int } 46 | 47 | it_behaves_like 'registry_value' 48 | end 49 | end 50 | end 51 | 52 | shared_examples 'below range' do 53 | let(:params) { { param_sym => below_range } } 54 | let(:error_message) { %r{expects a value of type Undef, Integer\[#{range[0]}, #{range[1]}\], or Boolean} } 55 | 56 | it { 57 | expect { catalogue.to_s }.to raise_error(error_message) 58 | } 59 | end 60 | 61 | shared_examples 'above range' do 62 | let(:params) { { param_sym => above_range } } 63 | let(:error_message) { %r{expects a value of type Undef, Integer\[#{range[0]}, #{range[1]}\], or Boolean} } 64 | 65 | it { 66 | expect { catalogue }.to raise_error(error_message) 67 | } 68 | end 69 | 70 | shared_examples 'bool value' do 71 | [true, false].each do |v| 72 | describe v.to_s do 73 | let(:params) do 74 | { 75 | param_sym => v 76 | } 77 | end 78 | let(:reg_data) { v ? 1 : 0 } 79 | 80 | it_behaves_like 'registry_value' 81 | end 82 | end 83 | describe 'unset when undef' do 84 | it { is_expected.not_to contain_registry_value(reg_key) } 85 | end 86 | end 87 | 88 | shared_examples 'enabled feature' do |valid_non_bool_value| 89 | describe 'unset when undef' do 90 | it { is_expected.not_to contain_registry_value("#{reg_key}Enabled") } 91 | it { is_expected.not_to contain_registry_value(reg_key) } 92 | end 93 | 94 | [true, false].each do |enabled| 95 | describe enabled.to_s do 96 | let(:params) { { param_sym => enabled } } 97 | 98 | it { 99 | expect(subject).to contain_registry_value("#{reg_key}Enabled").with( 100 | 'type' => 'dword', 101 | 'data' => (enabled ? 1 : 0), 102 | ) 103 | } 104 | 105 | it { is_expected.not_to contain_registry_value(reg_key) } 106 | end 107 | end 108 | describe "enabled as set with #{valid_non_bool_value}" do 109 | let(:params) { { param_sym => valid_non_bool_value } } 110 | 111 | it { is_expected.to contain_registry_value("#{reg_key}Enabled") } 112 | 113 | it { 114 | expect(subject).to contain_registry_value(reg_key).with( 115 | 'data' => valid_non_bool_value, 116 | ) 117 | } 118 | end 119 | end 120 | 121 | shared_examples 'non enabled feature' do |valid_value = true| 122 | let(:params) { { param_sym => valid_value } } 123 | 124 | it { is_expected.not_to contain_registry_value("#{reg_key}Enabled") } 125 | end 126 | 127 | test_hash.each do |os, settings| 128 | context "with Windows #{os}" do 129 | let(:facts) do 130 | { 131 | operatingsystem: 'windows', 132 | operatingsystemrelease: "Server #{os}", 133 | osfamily: 'windows' 134 | } 135 | end 136 | 137 | base_key = settings[:base_key] 138 | au_key = settings[:au_key] 139 | context 'with base keys' do 140 | booleans = [true, false] 141 | booleans.each do |purge| 142 | describe "purge_values => #{purge}" do 143 | let(:params) { { purge_values: purge } } 144 | 145 | [base_key, au_key].each do |key| 146 | it { 147 | expect(subject).to contain_registry_key(key).with( 148 | 'purge_values' => purge, 149 | ) 150 | } 151 | end 152 | end 153 | end 154 | end 155 | 156 | context 'with server_url =>' do 157 | let(:reg_type) { 'string' } 158 | let(:reg_data) { 'https://SERVER:8530' } 159 | 160 | describe 'WUServer setting' do 161 | let(:params) do 162 | { 163 | server_url: 'https://SERVER:8530' 164 | } 165 | end 166 | 167 | it_behaves_like 'non enabled feature', 'https://SERVER:8530' do 168 | let(:param_sym) { :server_url } 169 | let(:reg_key) { "#{base_key}\\WUServer" } 170 | end 171 | it_behaves_like 'registry_value' do 172 | let(:reg_key) { "#{base_key}\\WUServer" } 173 | end 174 | it_behaves_like 'registry_value undefined' do 175 | let(:reg_key) { "#{base_key}\\WUStatusServer" } 176 | end 177 | it_behaves_like 'registry_value' do 178 | let(:reg_key) { "#{au_key}\\UseWUServer" } 179 | let(:reg_data) { 1 } 180 | let(:reg_type) { 'dword' } 181 | end 182 | end 183 | 184 | describe 'WUStatusServer =>' do 185 | describe 'true' do 186 | let(:params) do 187 | { 188 | server_url: 'https://SERVER:8530', 189 | enable_status_server: true 190 | } 191 | end 192 | 193 | it_behaves_like 'registry_value' do 194 | let(:reg_key) { "#{base_key}\\WUServer" } 195 | end 196 | it_behaves_like 'registry_value' do 197 | let(:reg_key) { "#{base_key}\\WUStatusServer" } 198 | end 199 | it_behaves_like 'registry_value' do 200 | let(:reg_key) { "#{au_key}\\UseWUServer" } 201 | let(:reg_data) { 1 } 202 | let(:reg_type) { 'dword' } 203 | end 204 | end 205 | 206 | describe 'false' do 207 | let(:params) do 208 | { 209 | server_url: 'https://SERVER:8530', 210 | enable_status_server: false 211 | } 212 | end 213 | 214 | it_behaves_like 'registry_value' do 215 | let(:reg_key) { "#{base_key}\\WUServer" } 216 | end 217 | it_behaves_like 'registry_value' do 218 | let(:reg_key) { "#{base_key}\\WUStatusServer" } 219 | let(:reg_ensure) { 'absent' } 220 | end 221 | it_behaves_like 'registry_value' do 222 | let(:reg_key) { "#{au_key}\\UseWUServer" } 223 | let(:reg_data) { 1 } 224 | let(:reg_type) { 'dword' } 225 | end 226 | end 227 | end 228 | end 229 | 230 | context 'with auto_update_option =>' do 231 | let(:reg_key) { "#{au_key}\\AUOptions" } 232 | let(:param_sym) { :auto_update_option } 233 | 234 | it_behaves_like 'valid range', [2, 3, 5] 235 | it_behaves_like 'non enabled feature', 2 236 | range_one = [1, 6] 237 | range_one.each do |au_opt| 238 | describe au_opt.to_s do 239 | let(:params) do 240 | { 241 | auto_update_option: au_opt 242 | } 243 | end 244 | let(:error_message) { %r{expects a value of type Undef, Enum\['AutoInstall', 'AutoNotify', 'NotifyOnly', 'NotifyRestart', 'Scheduled'\], Integer\[2, 5\], or Integer\[7, 7\]} } 245 | 246 | it_behaves_like 'fail validation' 247 | end 248 | end 249 | range_two = ['Scheduled', 4] 250 | range_two.each do |param| 251 | describe 'require scheduled_install_day scheduled_install_hour' do 252 | let(:params) do 253 | { 254 | auto_update_option: param 255 | } 256 | end 257 | let(:error_message) { %r{scheduled_install_day and scheduled_install_hour required when specifying auto_update_option => '#{param}'} } 258 | 259 | it_behaves_like 'fail validation' 260 | it_behaves_like 'fail validation' do 261 | let(:params) do 262 | { 263 | auto_update_option: param, 264 | scheduled_install_day: 4 265 | } 266 | end 267 | end 268 | end 269 | end 270 | end 271 | 272 | context 'with accept_trusted_publisher_certs =>' do 273 | let(:reg_key) { "#{base_key}\\AcceptTrustedPublisherCerts" } 274 | let(:param_sym) { :accept_trusted_publisher_certs } 275 | 276 | it_behaves_like 'bool value' 277 | it_behaves_like 'registry_value undefined' 278 | it_behaves_like 'non enabled feature' 279 | end 280 | 281 | context 'with auto_install_minor_updates =>' do 282 | let(:reg_key) { "#{au_key}\\AutoInstallMinorUpdates" } 283 | let(:param_sym) { :auto_install_minor_updates } 284 | 285 | it_behaves_like 'bool value' 286 | it_behaves_like 'registry_value undefined' 287 | it_behaves_like 'non enabled feature' 288 | end 289 | 290 | context 'with detection_frequency_hours =>' do 291 | let(:reg_key) { "#{au_key}\\DetectionFrequency" } 292 | let(:range) { [1, 22] } 293 | let(:below_range) { range[0] - 1 } 294 | let(:above_range) { range[1] + 1 } 295 | let(:param_sym) { :detection_frequency_hours } 296 | 297 | it_behaves_like 'valid range', [1, 11, 22] 298 | it_behaves_like 'below range' 299 | it_behaves_like 'above range' 300 | it_behaves_like 'enabled feature', 11 301 | end 302 | 303 | context 'with disable_windows_update_access =>' do 304 | let(:reg_key) { "#{base_key}\\DisableWindowsUpdateAccess" } 305 | let(:param_sym) { :disable_windows_update_access } 306 | 307 | it_behaves_like 'bool value' 308 | it_behaves_like 'non enabled feature' 309 | end 310 | 311 | context 'with elevate_non_admins =>' do 312 | let(:reg_key) { "#{base_key}\\ElevateNonAdmins" } 313 | let(:param_sym) { :elevate_non_admins } 314 | 315 | it_behaves_like 'bool value' 316 | it_behaves_like 'non enabled feature' 317 | end 318 | 319 | context 'with no_auto_reboot_with_logged_on_users =>' do 320 | let(:reg_key) { "#{au_key}\\NoAutoRebootWithLoggedOnUsers" } 321 | let(:param_sym) { :no_auto_reboot_with_logged_on_users } 322 | 323 | it_behaves_like 'bool value' 324 | it_behaves_like 'non enabled feature' 325 | end 326 | 327 | context 'with no_auto_update =>' do 328 | let(:reg_key) { "#{au_key}\\NoAutoUpdate" } 329 | let(:param_sym) { :no_auto_update } 330 | 331 | it_behaves_like 'bool value' 332 | it_behaves_like 'non enabled feature' 333 | end 334 | 335 | context 'with reboot_relaunch_timeout_minutes =>' do 336 | let(:reg_key) { "#{au_key}\\RebootRelaunchTimeout" } 337 | let(:range) { [1, 1440] } 338 | let(:below_range) { range[0] - 1 } 339 | let(:above_range) { range[1] + 1 } 340 | let(:param_sym) { :reboot_relaunch_timeout_minutes } 341 | 342 | it_behaves_like 'valid range', [1, 720, 1440] 343 | it_behaves_like 'below range' 344 | it_behaves_like 'above range' 345 | it_behaves_like 'enabled feature', 720 346 | end 347 | 348 | context 'with reboot_warning_timeout_minutes =>' do 349 | let(:reg_key) { "#{au_key}\\RebootWarningTimeout" } 350 | let(:range) { [1, 30] } 351 | let(:below_range) { range[0] - 1 } 352 | let(:above_range) { range[1] + 1 } 353 | let(:param_sym) { :reboot_warning_timeout_minutes } 354 | 355 | it_behaves_like 'valid range', [1, 15, 30] 356 | it_behaves_like 'below range' 357 | it_behaves_like 'above range' 358 | it_behaves_like 'enabled feature', 15 359 | end 360 | 361 | context 'with reschedule_wait_time_minutes =>' do 362 | let(:reg_key) { "#{au_key}\\RescheduleWaitTime" } 363 | let(:range) { [1, 60] } 364 | let(:below_range) { range[0] - 1 } 365 | let(:above_range) { range[1] + 1 } 366 | let(:param_sym) { :reschedule_wait_time_minutes } 367 | 368 | it_behaves_like 'valid range', [1, 31, 60] 369 | it_behaves_like 'below range' 370 | it_behaves_like 'above range' 371 | it_behaves_like 'enabled feature', 30 372 | end 373 | 374 | context 'with scheduled_install_day =>' do 375 | let(:reg_key) { "#{au_key}\\ScheduledInstallDay" } 376 | let(:param_sym) { :scheduled_install_day } 377 | let(:above_range) { 8 } 378 | 379 | it_behaves_like 'valid range', [0, 4, 7] 380 | it_behaves_like 'registry_value undefined' # when unset should be missing 381 | it_behaves_like 'non enabled feature', 4 382 | days = ['Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] 383 | 8.times do |day_int| 384 | describe "convert #{days[day_int]}" do 385 | let(:params) { { param_sym => days[day_int] } } 386 | let(:reg_data) { day_int } 387 | 388 | it_behaves_like 'registry_value' 389 | end 390 | end 391 | end 392 | 393 | context 'with scheduled_install_hour =>' do 394 | let(:reg_key) { "#{au_key}\\ScheduledInstallTime" } 395 | let(:range) { [0, 23] } 396 | let(:below_range) { range[0] - 1 } 397 | let(:above_range) { range[1] + 1 } 398 | let(:param_sym) { :scheduled_install_hour } 399 | 400 | it_behaves_like 'valid range', [0, 12, 23] 401 | it_behaves_like 'above range' 402 | it_behaves_like 'registry_value undefined' # when unset should be missing 403 | it_behaves_like 'non enabled feature', 12 404 | end 405 | 406 | context 'with target_group =>' do 407 | let(:reg_key) { "#{base_key}\\TargetGroup" } 408 | let(:param_sym) { :target_group } 409 | 410 | it_behaves_like 'enabled feature', 'UberUserGroup' 411 | end 412 | 413 | context 'with always_auto_reboot_at_scheduled_time =>' do 414 | let(:reg_key) { "#{au_key}\\AlwaysAutoRebootAtScheduledTime" } 415 | let(:param_sym) { :always_auto_reboot_at_scheduled_time } 416 | 417 | it_behaves_like 'bool value' 418 | it_behaves_like 'non enabled feature' 419 | end 420 | 421 | context 'with always_auto_reboot_at_scheduled_time_minutes =>' do 422 | let(:reg_key) { "#{au_key}\\AlwaysAutoRebootAtScheduledTimeMinutes" } 423 | let(:param_sym) { :always_auto_reboot_at_scheduled_time_minutes } 424 | let(:range) { [15, 180] } 425 | let(:below_range) { range[0] - 1 } 426 | let(:above_range) { range[1] + 1 } 427 | 428 | it_behaves_like 'valid range', [15, 83, 180] 429 | it_behaves_like 'below range' 430 | it_behaves_like 'above range' 431 | it_behaves_like 'non enabled feature' 432 | end 433 | end 434 | end 435 | end 436 | -------------------------------------------------------------------------------- /manifests/init.pp: -------------------------------------------------------------------------------- 1 | # @summary 2 | # This module manages operating system updates. 3 | # 4 | # This module configures Puppet agents to schedule update downloads and installations from a WSUS server, 5 | # manage user access to update settings, and configure automatic updates. 6 | # 7 | # @example 8 | # class { 'wsus_client': } 9 | # 10 | # @param server_url 11 | # Sets the URL at which your WSUS server can be reached. Valid options: fully qualified URL starting with 'http' or 'https', including 12 | # protocol and port; 'false'; or undef. Default: undef. 13 | # When set to a URL, Puppet sets the WUServer registry key to this parameter's value and the UseWUServer registry key to '1' (true). 14 | # If this parameter is set to 'false', Puppet sets UseWUServer to false, disabling WSUS updates on the client. If undefined, Puppet 15 | # does not manage WUServer or UseWUServer. 16 | # Even if HTTPS is required for authentication, you can use 'http' URLs instead of 'https'. WSUS automatically switches to an HTTPS 17 | # connection when required and increments the provided port by 1. For example, if the server_url is 'http://myserver:8530' and the 18 | # WSUS server requires HTTPS access, the client automatically uses 'https://myserver:8531' to authenticate, then downloads the updates 19 | # without encryption via the server_url. This performs better than using SSL to encrypt binary downloads. 20 | # Note: The server_url parameter is central to using wsus_client to manage updates from a WSUS server. While not strictly required 21 | # to use the class, note that you must manage the WUServer and UseWUServer registry keys yourself if you do not set server_url 22 | # and enable_status_server. 23 | # 24 | # @param enable_status_server 25 | # Determines whether Puppet also sets the WUStatusServer registry key, which sets the client status reporting destination. 26 | # Valid options: 'true', 'false', and undef. Default: undef. 27 | # If this parameter is set to true, Puppet sets the value for the WUStatusServer registry key to the server_url parameter's value. 28 | # Therefore, when setting this parameter to true, you must also set the server_url parameter to a valid URL or your Puppet run 29 | # will fail with an error. 30 | # If enable_status_server is set to 'false', Puppet removes the WUStatusServer registry key. 31 | # Note: Windows requires the same value for WUStatusServer and WUServer, so wsus_client does not provide an option to set a 32 | # different status server URL. 33 | # 34 | # @param accept_trusted_publisher_certs 35 | # Determines whether to accept trusted non-Microsoft publisher certificates when checking for updates. 36 | # Valid options: 'true', 'false', and undef. 37 | # Default: undef. 38 | # If 'true', the WSUS server distributes signed non-Microsoft updates. 39 | # If 'false', the WSUS server only distributes Microsoft updates. 40 | # 41 | # @param auto_update_option 42 | # Sets the automatic update option you would like to use. Valid values: 'NotifyOnly', 'AutoNotify', 'Scheduled', 'AutoInstall' and 'NotifyRestart'. 43 | # You can also refer to these five values using integers 2 through 5, and 7, respectively. 44 | # Default: undef. 45 | # 46 | # See the AUOptions key values on the Microsoft TechNet documentation for detailed descriptions of these options. In summary: 47 | # 48 | # * 'NotifyOnly': Notifies users before downloading updates. 49 | # * 'AutoNotify': Automatically downloads updates and notifies users. 50 | # * 'Scheduled': Automatically downloads updates and schedules automatic installation. 51 | # * 'NotifyRestart': Automatically downloads updates and notifies users before both install and restart. (Windows Server 2016 and later only) 52 | # 53 | # If set to this value, scheduled_install_day and scheduled_install_hour are required. 54 | # This parameter must be set to this value to use reschedule_wait_time_minutes. 55 | # 'AutoInstall': Requires fully automatic updates that users can configure if allowed. 56 | # 57 | # @param auto_install_minor_updates 58 | # Determines whether to silently install minor updates automatically. Valid options: 'true', 'false', and undef. 59 | # If 'true', Windows installs minor updates without user interaction. 60 | # If 'false', Windows treats them as any other update, which depends on other settings such as auto_update_option. 61 | # 62 | # @param detection_frequency_hours 63 | # Sets an interval in hours for clients to check for updates. Valid values: integers 1 through 22. 64 | # Default: undef. 65 | # If this enabled parameter has a valid value, Puppet sets the DetectionFrequency registry key to its value and the 66 | # DetectionFrequencyEnabled Boolean registry key to 'true'. 67 | # Otherwise, Puppet sets DetectionFrequencyEnabled to 'false' and Windows ignores the value of DetectionFrequency, falling 68 | # back to the Windows default value of 22 hours. 69 | # 70 | # @param disable_windows_update_access 71 | # Determines whether non-administrators can access Windows Update. 72 | # Valid options: 'true' (disable access), 'false' (enable access), and undef. 73 | # Default: undef. 74 | # 75 | # @param elevate_non_admins 76 | # Determines which security groups can approve or refuse updates. Valid options: 'true', 'false', and undef. 77 | # Default: undef. 78 | # If 'true', members of the Users group can approve or refuse updates. 79 | # If 'false', only members of the Administrators group can approve or refuse updates. 80 | # 81 | # @param no_auto_reboot_with_logged_on_users 82 | # Determines whether to automatically reboot while a user is logged in to the client. 83 | # Valid options: 'true', 'false', and undef. Default: undef. 84 | # If 'true', Windows will not restart the client after installing updates, even if a reboot is required to finish installing the update. 85 | # If 'false', Windows notifies the user that the client will restart 15 minutes after installing an update that requires a reboot. 86 | # 87 | # @param no_auto_update 88 | # Disables automatic updates. Valid options: 'true', 'false' (automatic updates enabled), and undef. Default: undef. 89 | # Windows disables automatic updates when this parameter is set to 'true' and enables them if it's set to 'false'. 90 | # 91 | # @param reboot_relaunch_timeout_minutes 92 | # Sets a delay in minutes to wait before attempting to reboot after installing an update that requires one. 93 | # Valid values: integers 1 through 1440. Default: undef. 94 | # If this enabled parameter has a valid value, Puppet sets the RebootRelaunchTimeout registry key to its value and the 95 | # RebootRelaunchTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootRelaunchTimeoutEnabled to 'false' 96 | # and Windows ignores the value of RebootRelaunchTimeout, falling back to the Windows default value of 10 minutes. 97 | # 98 | # @param reboot_warning_timeout_minutes 99 | # Sets how many minutes users can wait before responding to a prompt to reboot the client after installing an update that requires 100 | # a reboot. Valid values: integers 1 through 30. Default: undef. 101 | # If this enabled parameter has a valid value, Puppet sets the RebootWarningTimeout registry key to its value and the 102 | # RebootWarningTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootWarningTimeoutEnabled to 'false' and 103 | # Windows ignores the value of RebootWarningTimeout, falling back to the Windows default value of 5 minutes. 104 | # 105 | # @param reschedule_wait_time_minutes 106 | # Sets how many minutes the client's automatic update service waits at startup before applying updates from a missed scheduled update. 107 | # Valid values: integers 1 through 60. Default: undef. 108 | # This enabled parameter is used only when automatic updates are enabled and auto_update_option is set to 'Scheduled' or '4'. 109 | # If this parameter is set to a valid value, Puppet sets the RescheduleWaitTime registry key to that value and the 110 | # RescheduleWaitTimeEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RescheduleWaitTimeEnabled to 'false' and Windows 111 | # ignores the value of RescheduleWaitTime, falling back to the Windows default behavior of re-attempting installation at the next 112 | # scheduled update time. 113 | # 114 | # @param scheduled_install_day 115 | # Schedules a day of the week to automatically install updates. Valid values: 'Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 116 | # 'Thursday', 'Friday', and 'Saturday'. You can also refer to these eight values using the integers 0 through 7, respectively. 117 | # Default: undef. 118 | # This parameter depends on a valid scheduled_install_hour value and is required when auto_update_option is set to 'Scheduled' or '4'. 119 | # 120 | # @param scheduled_install_hour 121 | # Schedules an hour of the day to automatically install updates. Valid values: an integer from 0 through 23. Default: undef. 122 | # This parameter depends on a valid scheduled_install_day value and is required when auto_update_option is set to 'Scheduled' or '4'. 123 | # 124 | # @param always_auto_reboot_at_scheduled_time 125 | # Determines whether to automatically reboot. Valid options: 'true', 'false', and undef. Default: undef. 126 | # 127 | # @param always_auto_reboot_at_scheduled_time_minutes 128 | # Sets the timer to warning a signed-in user that a restart is going to occur. Valid values: integers 15 through 180. Default: undef. 129 | # When the timer runs out, the restart will proceed even if the PC has signed-in users. 130 | # 131 | # @param purge_values 132 | # Determines whether Puppet purges values of unmanaged registry keys under the WindowsUpdate parent key. Valid options: Boolean. 133 | # Default: 'false'. 134 | # 135 | # @param target_group 136 | # Sets the client's target group. Valid values: a string. Default: undef. 137 | # This enabled parameter is only respected when the WSUS server allows clients to modify this setting via the TargetGroup and 138 | # TargetGroupEnabled registry keys. 139 | # 140 | class wsus_client ( 141 | Optional[Variant[Stdlib::HTTPUrl,Boolean]] $server_url = undef, 142 | Optional[Boolean] $enable_status_server = undef, 143 | Optional[Boolean] $accept_trusted_publisher_certs = undef, 144 | Optional[Variant[Enum['NotifyOnly', 'AutoNotify', 'Scheduled', 'AutoInstall', 'NotifyRestart'],Integer[2,5],Integer[7,7]]] 145 | $auto_update_option = undef, 146 | Optional[Boolean] $auto_install_minor_updates = undef, 147 | Optional[Variant[Integer[1,22],Boolean]] $detection_frequency_hours = undef, 148 | Optional[Boolean] $disable_windows_update_access = undef, 149 | Optional[Boolean] $elevate_non_admins = undef, 150 | Optional[Boolean] $no_auto_reboot_with_logged_on_users = undef, 151 | Optional[Boolean] $no_auto_update = undef, 152 | Optional[Variant[Integer[1,1440],Boolean]] $reboot_relaunch_timeout_minutes = undef, 153 | Optional[Variant[Integer[1,30],Boolean]] $reboot_warning_timeout_minutes = undef, 154 | Optional[Variant[Integer[1,60],Boolean]] $reschedule_wait_time_minutes = undef, 155 | Optional[Variant[Enum['Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday', 'Saturday'],Integer[0,7]]] 156 | $scheduled_install_day = undef, 157 | Optional[Variant[Integer[0,23],Boolean]] $scheduled_install_hour = undef, 158 | Optional[Boolean] $always_auto_reboot_at_scheduled_time = undef, 159 | Optional[Variant[Integer[15,180],Boolean]] $always_auto_reboot_at_scheduled_time_minutes = undef, 160 | Boolean $purge_values = false, 161 | Optional[Variant[String,Boolean]] $target_group = undef, 162 | ) { 163 | $_basekey = 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate' 164 | 165 | $_au_base = "${_basekey}\\AU" 166 | 167 | registry_key { $_basekey: 168 | ensure => present, 169 | purge_values => $purge_values, 170 | } 171 | 172 | registry_key { $_au_base: 173 | ensure => present, 174 | purge_values => $purge_values, 175 | } 176 | 177 | service { 'wuauserv': 178 | enable => true, 179 | } 180 | 181 | Registry_value { require => Registry_key[$_basekey], notify => Service['wuauserv'] } 182 | 183 | if ($server_url == undef or $server_url == false) and $enable_status_server { 184 | fail('server_url is required when specifying enable_status_server => true') 185 | } 186 | 187 | if $server_url != undef { 188 | wsus_client::setting { "${_au_base}\\UseWUServer": 189 | data => bool2num($server_url != false), 190 | has_enabled => false, 191 | } 192 | if $server_url { 193 | assert_type(Stdlib::HTTPUrl, $server_url) 194 | wsus_client::setting { "${_basekey}\\WUServer": 195 | type => 'string', 196 | data => $server_url, 197 | has_enabled => false, 198 | } 199 | if $enable_status_server != undef { 200 | assert_type(Boolean, $enable_status_server) 201 | $_ensure_status_server = $enable_status_server ? { 202 | true => 'present', 203 | false => 'absent', 204 | } 205 | wsus_client::setting { "${_basekey}\\WUStatusServer": 206 | ensure => $_ensure_status_server, 207 | type => 'string', 208 | data => $server_url, 209 | has_enabled => false, 210 | } 211 | } 212 | } 213 | } 214 | 215 | if $auto_update_option { 216 | $_parsed_auto_update_option = parse_auto_update_option($auto_update_option) 217 | 218 | # Option 7 is only supported on Windows Server 2016 and later. 219 | if $_parsed_auto_update_option == 7 { 220 | # Windows 2012's major version in facter is "2012 R2" which cannot be converted to integer directly. 221 | # So, extract the leading digits from the major release string. 222 | $_windows_version = regsubst($facts['os']['release']['major'], '^(\d+).*$', '\1') 223 | if (Integer($_windows_version) < 2016) { 224 | fail('auto_update_option value 7 is only supported on Windows Server 2016 and later.') 225 | } 226 | } 227 | if $_parsed_auto_update_option == 4 and !($scheduled_install_day and $scheduled_install_hour) { 228 | fail("scheduled_install_day and scheduled_install_hour required when specifying auto_update_option => '${auto_update_option}'") 229 | } 230 | wsus_client::setting { "${_au_base}\\AUOptions": 231 | data => $_parsed_auto_update_option, 232 | has_enabled => false, 233 | } 234 | } 235 | 236 | wsus_client::setting { "${_basekey}\\AcceptTrustedPublisherCerts": 237 | data => $accept_trusted_publisher_certs, 238 | has_enabled => false, 239 | validate_bool => true, 240 | } 241 | 242 | wsus_client::setting { "${_au_base}\\AutoInstallMinorUpdates": 243 | data => $auto_install_minor_updates, 244 | has_enabled => false, 245 | validate_bool => true, 246 | } 247 | 248 | wsus_client::setting { "${_au_base}\\DetectionFrequency": 249 | data => $detection_frequency_hours, 250 | validate_range => [1,22], 251 | } 252 | 253 | wsus_client::setting { "${_basekey}\\DisableWindowsUpdateAccess": 254 | data => $disable_windows_update_access, 255 | has_enabled => false, 256 | validate_bool => true, 257 | } 258 | 259 | wsus_client::setting { "${_basekey}\\ElevateNonAdmins": 260 | data => $elevate_non_admins, 261 | has_enabled => false, 262 | validate_bool => true, 263 | } 264 | 265 | wsus_client::setting { "${_au_base}\\NoAutoRebootWithLoggedOnUsers": 266 | data => $no_auto_reboot_with_logged_on_users, 267 | has_enabled => false, 268 | validate_bool => true, 269 | } 270 | 271 | wsus_client::setting { "${_au_base}\\NoAutoUpdate": 272 | data => $no_auto_update, 273 | has_enabled => false, 274 | validate_bool => true, 275 | } 276 | 277 | wsus_client::setting { "${_au_base}\\RebootRelaunchTimeout": 278 | data => $reboot_relaunch_timeout_minutes, 279 | validate_range => [1,1440], 280 | } 281 | 282 | wsus_client::setting { "${_au_base}\\RebootWarningTimeout": 283 | data => $reboot_warning_timeout_minutes, 284 | validate_range => [1,30], 285 | } 286 | 287 | wsus_client::setting { "${_au_base}\\RescheduleWaitTime": 288 | data => $reschedule_wait_time_minutes, 289 | validate_range => [1,60], 290 | } 291 | 292 | $_scheduled_install_day = $scheduled_install_day ? { 293 | true => true, 294 | false => false, 295 | /\w+|\d/ => parse_scheduled_install_day($scheduled_install_day), 296 | default => $scheduled_install_day 297 | } 298 | 299 | wsus_client::setting { "${_au_base}\\ScheduledInstallDay": 300 | data => $_scheduled_install_day, 301 | validate_range => [0,7], 302 | has_enabled => false, 303 | } 304 | 305 | wsus_client::setting { "${_au_base}\\ScheduledInstallTime": 306 | data => $scheduled_install_hour, 307 | validate_range => [0,23], 308 | has_enabled => false, 309 | } 310 | 311 | wsus_client::setting { "${_basekey}\\TargetGroup": 312 | type => 'string', 313 | data => $target_group, 314 | } 315 | 316 | wsus_client::setting { "${_au_base}\\AlwaysAutoRebootAtScheduledTime": 317 | data => $always_auto_reboot_at_scheduled_time, 318 | has_enabled => false, 319 | validate_bool => true, 320 | } 321 | wsus_client::setting { "${_au_base}\\AlwaysAutoRebootAtScheduledTimeMinutes": 322 | data => $always_auto_reboot_at_scheduled_time_minutes, 323 | validate_range => [15,180], 324 | has_enabled => false, 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /REFERENCE.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | 4 | 5 | ## Table of Contents 6 | 7 | ### Classes 8 | 9 | * [`wsus_client`](#wsus_client): This module manages operating system updates. 10 | 11 | ### Defined types 12 | 13 | * [`wsus_client::setting`](#wsus_client--setting): Manages wsus_client settings 14 | 15 | ### Functions 16 | 17 | * [`parse_auto_update_option`](#parse_auto_update_option): Parse the incoming value to the corresponding integer, if integer is supplied simply return value 18 | * [`parse_scheduled_install_day`](#parse_scheduled_install_day): Parse the incoming value to the corresponding integer, if integer is supplied simply return value 19 | * [`validate_in_range`](#validate_in_range): Validate the incoming value is in a certain range. 20 | 21 | ### Tasks 22 | 23 | * [`update_history`](#update_history): Returns a history of installed Windows Updates. 24 | 25 | ## Classes 26 | 27 | ### `wsus_client` 28 | 29 | This module configures Puppet agents to schedule update downloads and installations from a WSUS server, 30 | manage user access to update settings, and configure automatic updates. 31 | 32 | #### Examples 33 | 34 | ##### 35 | 36 | ```puppet 37 | class { 'wsus_client': } 38 | ``` 39 | 40 | #### Parameters 41 | 42 | The following parameters are available in the `wsus_client` class: 43 | 44 | * [`server_url`](#-wsus_client--server_url) 45 | * [`enable_status_server`](#-wsus_client--enable_status_server) 46 | * [`accept_trusted_publisher_certs`](#-wsus_client--accept_trusted_publisher_certs) 47 | * [`auto_update_option`](#-wsus_client--auto_update_option) 48 | * [`auto_install_minor_updates`](#-wsus_client--auto_install_minor_updates) 49 | * [`detection_frequency_hours`](#-wsus_client--detection_frequency_hours) 50 | * [`disable_windows_update_access`](#-wsus_client--disable_windows_update_access) 51 | * [`elevate_non_admins`](#-wsus_client--elevate_non_admins) 52 | * [`no_auto_reboot_with_logged_on_users`](#-wsus_client--no_auto_reboot_with_logged_on_users) 53 | * [`no_auto_update`](#-wsus_client--no_auto_update) 54 | * [`reboot_relaunch_timeout_minutes`](#-wsus_client--reboot_relaunch_timeout_minutes) 55 | * [`reboot_warning_timeout_minutes`](#-wsus_client--reboot_warning_timeout_minutes) 56 | * [`reschedule_wait_time_minutes`](#-wsus_client--reschedule_wait_time_minutes) 57 | * [`scheduled_install_day`](#-wsus_client--scheduled_install_day) 58 | * [`scheduled_install_hour`](#-wsus_client--scheduled_install_hour) 59 | * [`always_auto_reboot_at_scheduled_time`](#-wsus_client--always_auto_reboot_at_scheduled_time) 60 | * [`always_auto_reboot_at_scheduled_time_minutes`](#-wsus_client--always_auto_reboot_at_scheduled_time_minutes) 61 | * [`purge_values`](#-wsus_client--purge_values) 62 | * [`target_group`](#-wsus_client--target_group) 63 | 64 | ##### `server_url` 65 | 66 | Data type: `Optional[Variant[Stdlib::HTTPUrl,Boolean]]` 67 | 68 | Sets the URL at which your WSUS server can be reached. Valid options: fully qualified URL starting with 'http' or 'https', including 69 | protocol and port; 'false'; or undef. Default: undef. 70 | When set to a URL, Puppet sets the WUServer registry key to this parameter's value and the UseWUServer registry key to '1' (true). 71 | If this parameter is set to 'false', Puppet sets UseWUServer to false, disabling WSUS updates on the client. If undefined, Puppet 72 | does not manage WUServer or UseWUServer. 73 | Even if HTTPS is required for authentication, you can use 'http' URLs instead of 'https'. WSUS automatically switches to an HTTPS 74 | connection when required and increments the provided port by 1. For example, if the server_url is 'http://myserver:8530' and the 75 | WSUS server requires HTTPS access, the client automatically uses 'https://myserver:8531' to authenticate, then downloads the updates 76 | without encryption via the server_url. This performs better than using SSL to encrypt binary downloads. 77 | Note: The server_url parameter is central to using wsus_client to manage updates from a WSUS server. While not strictly required 78 | to use the class, note that you must manage the WUServer and UseWUServer registry keys yourself if you do not set server_url 79 | and enable_status_server. 80 | 81 | Default value: `undef` 82 | 83 | ##### `enable_status_server` 84 | 85 | Data type: `Optional[Boolean]` 86 | 87 | Determines whether Puppet also sets the WUStatusServer registry key, which sets the client status reporting destination. 88 | Valid options: 'true', 'false', and undef. Default: undef. 89 | If this parameter is set to true, Puppet sets the value for the WUStatusServer registry key to the server_url parameter's value. 90 | Therefore, when setting this parameter to true, you must also set the server_url parameter to a valid URL or your Puppet run 91 | will fail with an error. 92 | If enable_status_server is set to 'false', Puppet removes the WUStatusServer registry key. 93 | Note: Windows requires the same value for WUStatusServer and WUServer, so wsus_client does not provide an option to set a 94 | different status server URL. 95 | 96 | Default value: `undef` 97 | 98 | ##### `accept_trusted_publisher_certs` 99 | 100 | Data type: `Optional[Boolean]` 101 | 102 | Determines whether to accept trusted non-Microsoft publisher certificates when checking for updates. 103 | Valid options: 'true', 'false', and undef. 104 | Default: undef. 105 | If 'true', the WSUS server distributes signed non-Microsoft updates. 106 | If 'false', the WSUS server only distributes Microsoft updates. 107 | 108 | Default value: `undef` 109 | 110 | ##### `auto_update_option` 111 | 112 | Data type: `Optional[Variant[Enum['NotifyOnly', 'AutoNotify', 'Scheduled', 'AutoInstall', 'NotifyRestart'],Integer[2,5],Integer[7,7]]]` 113 | 114 | Sets the automatic update option you would like to use. Valid values: 'NotifyOnly', 'AutoNotify', 'Scheduled', 'AutoInstall' and 'NotifyRestart'. 115 | You can also refer to these five values using integers 2 through 5, and 7, respectively. 116 | Default: undef. 117 | 118 | See the AUOptions key values on the Microsoft TechNet documentation for detailed descriptions of these options. In summary: 119 | 120 | * 'NotifyOnly': Notifies users before downloading updates. 121 | * 'AutoNotify': Automatically downloads updates and notifies users. 122 | * 'Scheduled': Automatically downloads updates and schedules automatic installation. 123 | * 'NotifyRestart': Automatically downloads updates and notifies users before both install and restart. (Windows Server 2016 and later only) 124 | 125 | If set to this value, scheduled_install_day and scheduled_install_hour are required. 126 | This parameter must be set to this value to use reschedule_wait_time_minutes. 127 | 'AutoInstall': Requires fully automatic updates that users can configure if allowed. 128 | 129 | Default value: `undef` 130 | 131 | ##### `auto_install_minor_updates` 132 | 133 | Data type: `Optional[Boolean]` 134 | 135 | Determines whether to silently install minor updates automatically. Valid options: 'true', 'false', and undef. 136 | If 'true', Windows installs minor updates without user interaction. 137 | If 'false', Windows treats them as any other update, which depends on other settings such as auto_update_option. 138 | 139 | Default value: `undef` 140 | 141 | ##### `detection_frequency_hours` 142 | 143 | Data type: `Optional[Variant[Integer[1,22],Boolean]]` 144 | 145 | Sets an interval in hours for clients to check for updates. Valid values: integers 1 through 22. 146 | Default: undef. 147 | If this enabled parameter has a valid value, Puppet sets the DetectionFrequency registry key to its value and the 148 | DetectionFrequencyEnabled Boolean registry key to 'true'. 149 | Otherwise, Puppet sets DetectionFrequencyEnabled to 'false' and Windows ignores the value of DetectionFrequency, falling 150 | back to the Windows default value of 22 hours. 151 | 152 | Default value: `undef` 153 | 154 | ##### `disable_windows_update_access` 155 | 156 | Data type: `Optional[Boolean]` 157 | 158 | Determines whether non-administrators can access Windows Update. 159 | Valid options: 'true' (disable access), 'false' (enable access), and undef. 160 | Default: undef. 161 | 162 | Default value: `undef` 163 | 164 | ##### `elevate_non_admins` 165 | 166 | Data type: `Optional[Boolean]` 167 | 168 | Determines which security groups can approve or refuse updates. Valid options: 'true', 'false', and undef. 169 | Default: undef. 170 | If 'true', members of the Users group can approve or refuse updates. 171 | If 'false', only members of the Administrators group can approve or refuse updates. 172 | 173 | Default value: `undef` 174 | 175 | ##### `no_auto_reboot_with_logged_on_users` 176 | 177 | Data type: `Optional[Boolean]` 178 | 179 | Determines whether to automatically reboot while a user is logged in to the client. 180 | Valid options: 'true', 'false', and undef. Default: undef. 181 | If 'true', Windows will not restart the client after installing updates, even if a reboot is required to finish installing the update. 182 | If 'false', Windows notifies the user that the client will restart 15 minutes after installing an update that requires a reboot. 183 | 184 | Default value: `undef` 185 | 186 | ##### `no_auto_update` 187 | 188 | Data type: `Optional[Boolean]` 189 | 190 | Disables automatic updates. Valid options: 'true', 'false' (automatic updates enabled), and undef. Default: undef. 191 | Windows disables automatic updates when this parameter is set to 'true' and enables them if it's set to 'false'. 192 | 193 | Default value: `undef` 194 | 195 | ##### `reboot_relaunch_timeout_minutes` 196 | 197 | Data type: `Optional[Variant[Integer[1,1440],Boolean]]` 198 | 199 | Sets a delay in minutes to wait before attempting to reboot after installing an update that requires one. 200 | Valid values: integers 1 through 1440. Default: undef. 201 | If this enabled parameter has a valid value, Puppet sets the RebootRelaunchTimeout registry key to its value and the 202 | RebootRelaunchTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootRelaunchTimeoutEnabled to 'false' 203 | and Windows ignores the value of RebootRelaunchTimeout, falling back to the Windows default value of 10 minutes. 204 | 205 | Default value: `undef` 206 | 207 | ##### `reboot_warning_timeout_minutes` 208 | 209 | Data type: `Optional[Variant[Integer[1,30],Boolean]]` 210 | 211 | Sets how many minutes users can wait before responding to a prompt to reboot the client after installing an update that requires 212 | a reboot. Valid values: integers 1 through 30. Default: undef. 213 | If this enabled parameter has a valid value, Puppet sets the RebootWarningTimeout registry key to its value and the 214 | RebootWarningTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootWarningTimeoutEnabled to 'false' and 215 | Windows ignores the value of RebootWarningTimeout, falling back to the Windows default value of 5 minutes. 216 | 217 | Default value: `undef` 218 | 219 | ##### `reschedule_wait_time_minutes` 220 | 221 | Data type: `Optional[Variant[Integer[1,60],Boolean]]` 222 | 223 | Sets how many minutes the client's automatic update service waits at startup before applying updates from a missed scheduled update. 224 | Valid values: integers 1 through 60. Default: undef. 225 | This enabled parameter is used only when automatic updates are enabled and auto_update_option is set to 'Scheduled' or '4'. 226 | If this parameter is set to a valid value, Puppet sets the RescheduleWaitTime registry key to that value and the 227 | RescheduleWaitTimeEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RescheduleWaitTimeEnabled to 'false' and Windows 228 | ignores the value of RescheduleWaitTime, falling back to the Windows default behavior of re-attempting installation at the next 229 | scheduled update time. 230 | 231 | Default value: `undef` 232 | 233 | ##### `scheduled_install_day` 234 | 235 | Data type: `Optional[Variant[Enum['Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday', 'Saturday'],Integer[0,7]]]` 236 | 237 | Schedules a day of the week to automatically install updates. Valid values: 'Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 238 | 'Thursday', 'Friday', and 'Saturday'. You can also refer to these eight values using the integers 0 through 7, respectively. 239 | Default: undef. 240 | This parameter depends on a valid scheduled_install_hour value and is required when auto_update_option is set to 'Scheduled' or '4'. 241 | 242 | Default value: `undef` 243 | 244 | ##### `scheduled_install_hour` 245 | 246 | Data type: `Optional[Variant[Integer[0,23],Boolean]]` 247 | 248 | Schedules an hour of the day to automatically install updates. Valid values: an integer from 0 through 23. Default: undef. 249 | This parameter depends on a valid scheduled_install_day value and is required when auto_update_option is set to 'Scheduled' or '4'. 250 | 251 | Default value: `undef` 252 | 253 | ##### `always_auto_reboot_at_scheduled_time` 254 | 255 | Data type: `Optional[Boolean]` 256 | 257 | Determines whether to automatically reboot. Valid options: 'true', 'false', and undef. Default: undef. 258 | 259 | Default value: `undef` 260 | 261 | ##### `always_auto_reboot_at_scheduled_time_minutes` 262 | 263 | Data type: `Optional[Variant[Integer[15,180],Boolean]]` 264 | 265 | Sets the timer to warning a signed-in user that a restart is going to occur. Valid values: integers 15 through 180. Default: undef. 266 | When the timer runs out, the restart will proceed even if the PC has signed-in users. 267 | 268 | Default value: `undef` 269 | 270 | ##### `purge_values` 271 | 272 | Data type: `Boolean` 273 | 274 | Determines whether Puppet purges values of unmanaged registry keys under the WindowsUpdate parent key. Valid options: Boolean. 275 | Default: 'false'. 276 | 277 | Default value: `false` 278 | 279 | ##### `target_group` 280 | 281 | Data type: `Optional[Variant[String,Boolean]]` 282 | 283 | Sets the client's target group. Valid values: a string. Default: undef. 284 | This enabled parameter is only respected when the WSUS server allows clients to modify this setting via the TargetGroup and 285 | TargetGroupEnabled registry keys. 286 | 287 | Default value: `undef` 288 | 289 | ## Defined types 290 | 291 | ### `wsus_client::setting` 292 | 293 | Manages wsus_client settings 294 | 295 | #### Parameters 296 | 297 | The following parameters are available in the `wsus_client::setting` defined type: 298 | 299 | * [`ensure`](#-wsus_client--setting--ensure) 300 | * [`key`](#-wsus_client--setting--key) 301 | * [`data`](#-wsus_client--setting--data) 302 | * [`type`](#-wsus_client--setting--type) 303 | * [`has_enabled`](#-wsus_client--setting--has_enabled) 304 | * [`validate_range`](#-wsus_client--setting--validate_range) 305 | * [`validate_bool`](#-wsus_client--setting--validate_bool) 306 | 307 | ##### `ensure` 308 | 309 | Data type: `Enum['present', 'absent', 'file']` 310 | 311 | Specifies whether the setting should exist. Valid options: 'present', 'absent', and 'file' 312 | 313 | Default value: `'present'` 314 | 315 | ##### `key` 316 | 317 | Data type: `String` 318 | 319 | Specifies registry_value 320 | 321 | Default value: `$title` 322 | 323 | ##### `data` 324 | 325 | Data type: `Optional[Variant[String,Integer,Boolean,Stdlib::HTTPUrl]]` 326 | 327 | Incoming data 328 | 329 | Default value: `undef` 330 | 331 | ##### `type` 332 | 333 | Data type: `String` 334 | 335 | Data type. default value: dword 336 | 337 | Default value: `'dword'` 338 | 339 | ##### `has_enabled` 340 | 341 | Data type: `Boolean` 342 | 343 | Specifies whether the key should be enabled. Boolean value 344 | 345 | Default value: `true` 346 | 347 | ##### `validate_range` 348 | 349 | Data type: `Optional[Tuple[Integer, Integer]]` 350 | 351 | Specifies whether the data should be validated as a number in a certain range 352 | 353 | Default value: `undef` 354 | 355 | ##### `validate_bool` 356 | 357 | Data type: `Boolean` 358 | 359 | Specifies whether the data should be validated as a boolean value 360 | 361 | Default value: `false` 362 | 363 | ## Functions 364 | 365 | ### `parse_auto_update_option` 366 | 367 | Type: Ruby 3.x API 368 | 369 | > *Note:* 370 | Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5 371 | 372 | #### `parse_auto_update_option()` 373 | 374 | > *Note:* 375 | Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5 376 | 377 | Returns: `Integer` option auto_update_option as an integer 378 | 379 | ### `parse_scheduled_install_day` 380 | 381 | Type: Ruby 3.x API 382 | 383 | > *Note:* 384 | Valid options for scheduled_install_day are Everyday|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|0-7 385 | 386 | #### `parse_scheduled_install_day()` 387 | 388 | > *Note:* 389 | Valid options for scheduled_install_day are Everyday|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|0-7 390 | 391 | Returns: `Integer` option scheduled_install_day as an integer 392 | 393 | ### `validate_in_range` 394 | 395 | Type: Ruby 3.x API 396 | 397 | Validate the incoming value is in a certain range. 398 | 399 | #### `validate_in_range()` 400 | 401 | The validate_in_range function. 402 | 403 | Returns: `Any` Raises an error if the given value fails this validation. 404 | 405 | ## Tasks 406 | 407 | ### `update_history` 408 | 409 | Returns a history of installed Windows Updates. 410 | 411 | **Supports noop?** false 412 | 413 | #### Parameters 414 | 415 | ##### `detailed` 416 | 417 | Data type: `Optional[Boolean]` 418 | 419 | Return detailed update information. Default is to return basic information 420 | 421 | ##### `title` 422 | 423 | Data type: `Optional[String]` 424 | 425 | Return updates which match the specified regular expression. Default is to all updates 426 | 427 | ##### `updateid` 428 | 429 | Data type: `Optional[String]` 430 | 431 | Return updates which the specified Update ID. Default is to all updates 432 | 433 | ##### `maximumupdates` 434 | 435 | Data type: `Optional[String]` 436 | 437 | Limit the size of the history returned. Default is to return a maximum of 300 items 438 | 439 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | plugins: 3 | - rubocop-performance 4 | - rubocop-rspec 5 | - rubocop-rspec_rails 6 | - rubocop-factory_bot 7 | - rubocop-capybara 8 | AllCops: 9 | NewCops: enable 10 | DisplayCopNames: true 11 | TargetRubyVersion: 3.1 12 | Include: 13 | - "**/*.rb" 14 | Exclude: 15 | - bin/* 16 | - ".vendor/**/*" 17 | - "**/Gemfile" 18 | - "**/Rakefile" 19 | - pkg/**/* 20 | - spec/fixtures/**/* 21 | - vendor/**/* 22 | - "**/Puppetfile" 23 | - "**/Vagrantfile" 24 | - "**/Guardfile" 25 | inherit_from: ".rubocop_todo.yml" 26 | Layout/LineLength: 27 | Description: People have wide screens, use them. 28 | Max: 200 29 | RSpec/BeforeAfterAll: 30 | Description: Beware of using after(:all) as it may cause state to leak between tests. 31 | A necessary evil in acceptance testing. 32 | Exclude: 33 | - spec/acceptance/**/*.rb 34 | RSpec/HookArgument: 35 | Description: Prefer explicit :each argument, matching existing module's style 36 | EnforcedStyle: each 37 | RSpec/DescribeSymbol: 38 | Exclude: 39 | - spec/unit/facter/**/*.rb 40 | Style/BlockDelimiters: 41 | Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to 42 | be consistent then. 43 | EnforcedStyle: braces_for_chaining 44 | Style/ClassAndModuleChildren: 45 | Description: Compact style reduces the required amount of indentation. 46 | EnforcedStyle: compact 47 | Style/EmptyElse: 48 | Description: Enforce against empty else clauses, but allow `nil` for clarity. 49 | EnforcedStyle: empty 50 | Style/FormatString: 51 | Description: Following the main puppet project's style, prefer the % format format. 52 | EnforcedStyle: percent 53 | Style/FormatStringToken: 54 | Description: Following the main puppet project's style, prefer the simpler template 55 | tokens over annotated ones. 56 | EnforcedStyle: template 57 | Style/Lambda: 58 | Description: Prefer the keyword for easier discoverability. 59 | EnforcedStyle: literal 60 | Style/RegexpLiteral: 61 | Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 62 | EnforcedStyle: percent_r 63 | Style/TernaryParentheses: 64 | Description: Checks for use of parentheses around ternary conditions. Enforce parentheses 65 | on complex expressions for better readability, but seriously consider breaking 66 | it up. 67 | EnforcedStyle: require_parentheses_when_complex 68 | Style/TrailingCommaInArguments: 69 | Description: Prefer always trailing comma on multiline argument lists. This makes 70 | diffs, and re-ordering nicer. 71 | EnforcedStyleForMultiline: comma 72 | Style/TrailingCommaInArrayLiteral: 73 | Description: Prefer always trailing comma on multiline literals. This makes diffs, 74 | and re-ordering nicer. 75 | EnforcedStyleForMultiline: comma 76 | Style/SymbolArray: 77 | Description: Using percent style obscures symbolic intent of array's contents. 78 | EnforcedStyle: brackets 79 | RSpec/MessageSpies: 80 | EnforcedStyle: receive 81 | Style/Documentation: 82 | Exclude: 83 | - lib/puppet/parser/functions/**/* 84 | - spec/**/* 85 | Style/WordArray: 86 | EnforcedStyle: brackets 87 | Performance/AncestorsInclude: 88 | Enabled: true 89 | Performance/BigDecimalWithNumericArgument: 90 | Enabled: true 91 | Performance/BlockGivenWithExplicitBlock: 92 | Enabled: true 93 | Performance/CaseWhenSplat: 94 | Enabled: true 95 | Performance/ConstantRegexp: 96 | Enabled: true 97 | Performance/MethodObjectAsBlock: 98 | Enabled: true 99 | Performance/RedundantSortBlock: 100 | Enabled: true 101 | Performance/RedundantStringChars: 102 | Enabled: true 103 | Performance/ReverseFirst: 104 | Enabled: true 105 | Performance/SortReverse: 106 | Enabled: true 107 | Performance/Squeeze: 108 | Enabled: true 109 | Performance/StringInclude: 110 | Enabled: true 111 | Performance/Sum: 112 | Enabled: true 113 | Style/CollectionMethods: 114 | Enabled: true 115 | Style/MethodCalledOnDoEndBlock: 116 | Enabled: true 117 | Style/StringMethods: 118 | Enabled: true 119 | Bundler/GemFilename: 120 | Enabled: false 121 | Bundler/InsecureProtocolSource: 122 | Enabled: false 123 | Capybara/CurrentPathExpectation: 124 | Enabled: false 125 | Capybara/VisibilityMatcher: 126 | Enabled: false 127 | FactoryBot/AttributeDefinedStatically: 128 | Enabled: false 129 | FactoryBot/CreateList: 130 | Enabled: false 131 | FactoryBot/FactoryClassName: 132 | Enabled: false 133 | Gemspec/DuplicatedAssignment: 134 | Enabled: false 135 | Gemspec/OrderedDependencies: 136 | Enabled: false 137 | Gemspec/RequiredRubyVersion: 138 | Enabled: false 139 | Gemspec/RubyVersionGlobalsUsage: 140 | Enabled: false 141 | Layout/ArgumentAlignment: 142 | Enabled: false 143 | Layout/BeginEndAlignment: 144 | Enabled: false 145 | Layout/ClosingHeredocIndentation: 146 | Enabled: false 147 | Layout/EmptyComment: 148 | Enabled: false 149 | Layout/EmptyLineAfterGuardClause: 150 | Enabled: false 151 | Layout/EmptyLinesAroundArguments: 152 | Enabled: false 153 | Layout/EmptyLinesAroundAttributeAccessor: 154 | Enabled: false 155 | Layout/EndOfLine: 156 | Enabled: false 157 | Layout/FirstArgumentIndentation: 158 | Enabled: false 159 | Layout/HashAlignment: 160 | Enabled: false 161 | Layout/HeredocIndentation: 162 | Enabled: false 163 | Layout/LeadingEmptyLines: 164 | Enabled: false 165 | Layout/SpaceAroundMethodCallOperator: 166 | Enabled: false 167 | Layout/SpaceInsideArrayLiteralBrackets: 168 | Enabled: false 169 | Layout/SpaceInsideReferenceBrackets: 170 | Enabled: false 171 | Lint/BigDecimalNew: 172 | Enabled: false 173 | Lint/BooleanSymbol: 174 | Enabled: false 175 | Lint/ConstantDefinitionInBlock: 176 | Enabled: false 177 | Lint/DeprecatedOpenSSLConstant: 178 | Enabled: false 179 | Lint/DisjunctiveAssignmentInConstructor: 180 | Enabled: false 181 | Lint/DuplicateElsifCondition: 182 | Enabled: false 183 | Lint/DuplicateRequire: 184 | Enabled: false 185 | Lint/DuplicateRescueException: 186 | Enabled: false 187 | Lint/EmptyConditionalBody: 188 | Enabled: false 189 | Lint/EmptyFile: 190 | Enabled: false 191 | Lint/ErbNewArguments: 192 | Enabled: false 193 | Lint/FloatComparison: 194 | Enabled: false 195 | Lint/HashCompareByIdentity: 196 | Enabled: false 197 | Lint/IdentityComparison: 198 | Enabled: false 199 | Lint/InterpolationCheck: 200 | Enabled: false 201 | Lint/MissingCopEnableDirective: 202 | Enabled: false 203 | Lint/MixedRegexpCaptureTypes: 204 | Enabled: false 205 | Lint/NestedPercentLiteral: 206 | Enabled: false 207 | Lint/NonDeterministicRequireOrder: 208 | Enabled: false 209 | Lint/OrderedMagicComments: 210 | Enabled: false 211 | Lint/OutOfRangeRegexpRef: 212 | Enabled: false 213 | Lint/RaiseException: 214 | Enabled: false 215 | Lint/RedundantCopEnableDirective: 216 | Enabled: false 217 | Lint/RedundantRequireStatement: 218 | Enabled: false 219 | Lint/RedundantSafeNavigation: 220 | Enabled: false 221 | Lint/RedundantWithIndex: 222 | Enabled: false 223 | Lint/RedundantWithObject: 224 | Enabled: false 225 | Lint/RegexpAsCondition: 226 | Enabled: false 227 | Lint/ReturnInVoidContext: 228 | Enabled: false 229 | Lint/SafeNavigationConsistency: 230 | Enabled: false 231 | Lint/SafeNavigationWithEmpty: 232 | Enabled: false 233 | Lint/SelfAssignment: 234 | Enabled: false 235 | Lint/SendWithMixinArgument: 236 | Enabled: false 237 | Lint/ShadowedArgument: 238 | Enabled: false 239 | Lint/StructNewOverride: 240 | Enabled: false 241 | Lint/ToJSON: 242 | Enabled: false 243 | Lint/TopLevelReturnWithArgument: 244 | Enabled: false 245 | Lint/TrailingCommaInAttributeDeclaration: 246 | Enabled: false 247 | Lint/UnreachableLoop: 248 | Enabled: false 249 | Lint/UriEscapeUnescape: 250 | Enabled: false 251 | Lint/UriRegexp: 252 | Enabled: false 253 | Lint/UselessMethodDefinition: 254 | Enabled: false 255 | Lint/UselessTimes: 256 | Enabled: false 257 | Metrics/AbcSize: 258 | Enabled: false 259 | Metrics/BlockLength: 260 | Enabled: false 261 | Metrics/BlockNesting: 262 | Enabled: false 263 | Metrics/ClassLength: 264 | Enabled: false 265 | Metrics/CyclomaticComplexity: 266 | Enabled: false 267 | Metrics/MethodLength: 268 | Enabled: false 269 | Metrics/ModuleLength: 270 | Enabled: false 271 | Metrics/ParameterLists: 272 | Enabled: false 273 | Metrics/PerceivedComplexity: 274 | Enabled: false 275 | Migration/DepartmentName: 276 | Enabled: false 277 | Naming/AccessorMethodName: 278 | Enabled: false 279 | Naming/BlockParameterName: 280 | Enabled: false 281 | Naming/HeredocDelimiterCase: 282 | Enabled: false 283 | Naming/HeredocDelimiterNaming: 284 | Enabled: false 285 | Naming/MemoizedInstanceVariableName: 286 | Enabled: false 287 | Naming/MethodParameterName: 288 | Enabled: false 289 | Naming/RescuedExceptionsVariableName: 290 | Enabled: false 291 | Naming/VariableNumber: 292 | Enabled: false 293 | Performance/BindCall: 294 | Enabled: false 295 | Performance/DeletePrefix: 296 | Enabled: false 297 | Performance/DeleteSuffix: 298 | Enabled: false 299 | Performance/InefficientHashSearch: 300 | Enabled: false 301 | Performance/UnfreezeString: 302 | Enabled: false 303 | Performance/UriDefaultParser: 304 | Enabled: false 305 | RSpec/Be: 306 | Enabled: false 307 | RSpec/ContainExactly: 308 | Enabled: false 309 | RSpec/ContextMethod: 310 | Enabled: false 311 | RSpec/ContextWording: 312 | Enabled: false 313 | RSpec/DescribeClass: 314 | Enabled: false 315 | RSpec/Dialect: 316 | Enabled: false 317 | RSpec/EmptyHook: 318 | Enabled: false 319 | RSpec/EmptyLineAfterExample: 320 | Enabled: false 321 | RSpec/EmptyLineAfterExampleGroup: 322 | Enabled: false 323 | RSpec/EmptyLineAfterHook: 324 | Enabled: false 325 | RSpec/ExampleLength: 326 | Enabled: false 327 | RSpec/ExampleWithoutDescription: 328 | Enabled: false 329 | RSpec/ExpectChange: 330 | Enabled: false 331 | RSpec/ExpectInHook: 332 | Enabled: false 333 | RSpec/HooksBeforeExamples: 334 | Enabled: false 335 | RSpec/ImplicitBlockExpectation: 336 | Enabled: false 337 | RSpec/ImplicitSubject: 338 | Enabled: false 339 | RSpec/LeakyConstantDeclaration: 340 | Enabled: false 341 | RSpec/LetBeforeExamples: 342 | Enabled: false 343 | RSpec/MatchArray: 344 | Enabled: false 345 | RSpec/MissingExampleGroupArgument: 346 | Enabled: false 347 | RSpec/MultipleExpectations: 348 | Enabled: false 349 | RSpec/MultipleMemoizedHelpers: 350 | Enabled: false 351 | RSpec/MultipleSubjects: 352 | Enabled: false 353 | RSpec/NestedGroups: 354 | Enabled: false 355 | RSpec/PredicateMatcher: 356 | Enabled: false 357 | RSpec/ReceiveCounts: 358 | Enabled: false 359 | RSpec/ReceiveNever: 360 | Enabled: false 361 | RSpec/RepeatedExampleGroupBody: 362 | Enabled: false 363 | RSpec/RepeatedExampleGroupDescription: 364 | Enabled: false 365 | RSpec/RepeatedIncludeExample: 366 | Enabled: false 367 | RSpec/ReturnFromStub: 368 | Enabled: false 369 | RSpec/SharedExamples: 370 | Enabled: false 371 | RSpec/StubbedMock: 372 | Enabled: false 373 | RSpec/UnspecifiedException: 374 | Enabled: false 375 | RSpec/VariableDefinition: 376 | Enabled: false 377 | RSpec/VoidExpect: 378 | Enabled: false 379 | RSpec/Yield: 380 | Enabled: false 381 | Security/Open: 382 | Enabled: false 383 | Style/AccessModifierDeclarations: 384 | Enabled: false 385 | Style/AccessorGrouping: 386 | Enabled: false 387 | Style/BisectedAttrAccessor: 388 | Enabled: false 389 | Style/CaseLikeIf: 390 | Enabled: false 391 | Style/ClassEqualityComparison: 392 | Enabled: false 393 | Style/ColonMethodDefinition: 394 | Enabled: false 395 | Style/CombinableLoops: 396 | Enabled: false 397 | Style/CommentedKeyword: 398 | Enabled: false 399 | Style/Dir: 400 | Enabled: false 401 | Style/DoubleCopDisableDirective: 402 | Enabled: false 403 | Style/EmptyBlockParameter: 404 | Enabled: false 405 | Style/EmptyLambdaParameter: 406 | Enabled: false 407 | Style/Encoding: 408 | Enabled: false 409 | Style/EvalWithLocation: 410 | Enabled: false 411 | Style/ExpandPathArguments: 412 | Enabled: false 413 | Style/ExplicitBlockArgument: 414 | Enabled: false 415 | Style/ExponentialNotation: 416 | Enabled: false 417 | Style/FloatDivision: 418 | Enabled: false 419 | Style/FrozenStringLiteralComment: 420 | Enabled: false 421 | Style/GlobalStdStream: 422 | Enabled: false 423 | Style/HashAsLastArrayItem: 424 | Enabled: false 425 | Style/HashLikeCase: 426 | Enabled: false 427 | Style/HashTransformKeys: 428 | Enabled: false 429 | Style/HashTransformValues: 430 | Enabled: false 431 | Style/IfUnlessModifier: 432 | Enabled: false 433 | Style/KeywordParametersOrder: 434 | Enabled: false 435 | Style/MinMax: 436 | Enabled: false 437 | Style/MixinUsage: 438 | Enabled: false 439 | Style/MultilineWhenThen: 440 | Enabled: false 441 | Style/NegatedUnless: 442 | Enabled: false 443 | Style/NumericPredicate: 444 | Enabled: false 445 | Style/OptionalBooleanParameter: 446 | Enabled: false 447 | Style/OrAssignment: 448 | Enabled: false 449 | Style/RandomWithOffset: 450 | Enabled: false 451 | Style/RedundantAssignment: 452 | Enabled: false 453 | Style/RedundantCondition: 454 | Enabled: false 455 | Style/RedundantConditional: 456 | Enabled: false 457 | Style/RedundantFetchBlock: 458 | Enabled: false 459 | Style/RedundantFileExtensionInRequire: 460 | Enabled: false 461 | Style/RedundantRegexpCharacterClass: 462 | Enabled: false 463 | Style/RedundantRegexpEscape: 464 | Enabled: false 465 | Style/RedundantSelfAssignment: 466 | Enabled: false 467 | Style/RedundantSort: 468 | Enabled: false 469 | Style/RescueStandardError: 470 | Enabled: false 471 | Style/SingleArgumentDig: 472 | Enabled: false 473 | Style/SlicingWithRange: 474 | Enabled: false 475 | Style/SoleNestedConditional: 476 | Enabled: false 477 | Style/StderrPuts: 478 | Enabled: false 479 | Style/StringConcatenation: 480 | Enabled: false 481 | Style/Strip: 482 | Enabled: false 483 | Style/SymbolProc: 484 | Enabled: false 485 | Style/TrailingBodyOnClass: 486 | Enabled: false 487 | Style/TrailingBodyOnMethodDefinition: 488 | Enabled: false 489 | Style/TrailingBodyOnModule: 490 | Enabled: false 491 | Style/TrailingCommaInHashLiteral: 492 | Enabled: false 493 | Style/TrailingMethodEndStatement: 494 | Enabled: false 495 | Style/UnpackFirst: 496 | Enabled: false 497 | Capybara/MatchStyle: 498 | Enabled: false 499 | Capybara/NegationMatcher: 500 | Enabled: false 501 | Capybara/SpecificActions: 502 | Enabled: false 503 | Capybara/SpecificFinders: 504 | Enabled: false 505 | Capybara/SpecificMatcher: 506 | Enabled: false 507 | FactoryBot/ConsistentParenthesesStyle: 508 | Enabled: false 509 | FactoryBot/FactoryNameStyle: 510 | Enabled: false 511 | FactoryBot/SyntaxMethods: 512 | Enabled: false 513 | Gemspec/DeprecatedAttributeAssignment: 514 | Enabled: false 515 | Gemspec/DevelopmentDependencies: 516 | Enabled: false 517 | Gemspec/RequireMFA: 518 | Enabled: false 519 | Layout/LineContinuationLeadingSpace: 520 | Enabled: false 521 | Layout/LineContinuationSpacing: 522 | Enabled: false 523 | Layout/LineEndStringConcatenationIndentation: 524 | Enabled: false 525 | Layout/SpaceBeforeBrackets: 526 | Enabled: false 527 | Lint/AmbiguousAssignment: 528 | Enabled: false 529 | Lint/AmbiguousOperatorPrecedence: 530 | Enabled: false 531 | Lint/AmbiguousRange: 532 | Enabled: false 533 | Lint/ConstantOverwrittenInRescue: 534 | Enabled: false 535 | Lint/DeprecatedConstants: 536 | Enabled: false 537 | Lint/DuplicateBranch: 538 | Enabled: false 539 | Lint/DuplicateMagicComment: 540 | Enabled: false 541 | Lint/DuplicateMatchPattern: 542 | Enabled: false 543 | Lint/DuplicateRegexpCharacterClassElement: 544 | Enabled: false 545 | Lint/EmptyBlock: 546 | Enabled: false 547 | Lint/EmptyClass: 548 | Enabled: false 549 | Lint/EmptyInPattern: 550 | Enabled: false 551 | Lint/IncompatibleIoSelectWithFiberScheduler: 552 | Enabled: false 553 | Lint/LambdaWithoutLiteralBlock: 554 | Enabled: false 555 | Lint/NoReturnInBeginEndBlocks: 556 | Enabled: false 557 | Lint/NonAtomicFileOperation: 558 | Enabled: false 559 | Lint/NumberedParameterAssignment: 560 | Enabled: false 561 | Lint/OrAssignmentToConstant: 562 | Enabled: false 563 | Lint/RedundantDirGlobSort: 564 | Enabled: false 565 | Lint/RefinementImportMethods: 566 | Enabled: false 567 | Lint/RequireRangeParentheses: 568 | Enabled: false 569 | Lint/RequireRelativeSelfPath: 570 | Enabled: false 571 | Lint/SymbolConversion: 572 | Enabled: false 573 | Lint/ToEnumArguments: 574 | Enabled: false 575 | Lint/TripleQuotes: 576 | Enabled: false 577 | Lint/UnexpectedBlockArity: 578 | Enabled: false 579 | Lint/UnmodifiedReduceAccumulator: 580 | Enabled: false 581 | Lint/UselessRescue: 582 | Enabled: false 583 | Lint/UselessRuby2Keywords: 584 | Enabled: false 585 | Metrics/CollectionLiteralLength: 586 | Enabled: false 587 | Naming/BlockForwarding: 588 | Enabled: false 589 | Performance/CollectionLiteralInLoop: 590 | Enabled: false 591 | Performance/ConcurrentMonotonicTime: 592 | Enabled: false 593 | Performance/MapCompact: 594 | Enabled: false 595 | Performance/RedundantEqualityComparisonBlock: 596 | Enabled: false 597 | Performance/RedundantSplitRegexpArgument: 598 | Enabled: false 599 | Performance/StringIdentifierArgument: 600 | Enabled: false 601 | RSpec/BeEq: 602 | Enabled: false 603 | RSpec/BeNil: 604 | Enabled: false 605 | RSpec/ChangeByZero: 606 | Enabled: false 607 | RSpec/ClassCheck: 608 | Enabled: false 609 | RSpec/DuplicatedMetadata: 610 | Enabled: false 611 | RSpec/ExcessiveDocstringSpacing: 612 | Enabled: false 613 | RSpec/IdenticalEqualityAssertion: 614 | Enabled: false 615 | RSpec/NoExpectationExample: 616 | Enabled: false 617 | RSpec/PendingWithoutReason: 618 | Enabled: false 619 | RSpec/RedundantAround: 620 | Enabled: false 621 | RSpec/SkipBlockInsideExample: 622 | Enabled: false 623 | RSpec/SortMetadata: 624 | Enabled: false 625 | RSpec/SubjectDeclaration: 626 | Enabled: false 627 | RSpec/VerifiedDoubleReference: 628 | Enabled: false 629 | RSpecRails/AvoidSetupHook: 630 | Enabled: false 631 | RSpecRails/HaveHttpStatus: 632 | Enabled: false 633 | RSpecRails/InferredSpecType: 634 | Enabled: false 635 | RSpecRails/MinitestAssertions: 636 | Enabled: false 637 | RSpecRails/TravelAround: 638 | Enabled: false 639 | Security/CompoundHash: 640 | Enabled: false 641 | Security/IoMethods: 642 | Enabled: false 643 | Style/ArgumentsForwarding: 644 | Enabled: false 645 | Style/ArrayIntersect: 646 | Enabled: false 647 | Style/CollectionCompact: 648 | Enabled: false 649 | Style/ComparableClamp: 650 | Enabled: false 651 | Style/ConcatArrayLiterals: 652 | Enabled: false 653 | Style/DataInheritance: 654 | Enabled: false 655 | Style/DirEmpty: 656 | Enabled: false 657 | Style/DocumentDynamicEvalDefinition: 658 | Enabled: false 659 | Style/EmptyHeredoc: 660 | Enabled: false 661 | Style/EndlessMethod: 662 | Enabled: false 663 | Style/EnvHome: 664 | Enabled: false 665 | Style/FetchEnvVar: 666 | Enabled: false 667 | Style/FileEmpty: 668 | Enabled: false 669 | Style/FileRead: 670 | Enabled: false 671 | Style/FileWrite: 672 | Enabled: false 673 | Style/HashConversion: 674 | Enabled: false 675 | Style/HashExcept: 676 | Enabled: false 677 | Style/IfWithBooleanLiteralBranches: 678 | Enabled: false 679 | Style/InPatternThen: 680 | Enabled: false 681 | Style/MagicCommentFormat: 682 | Enabled: false 683 | Style/MapCompactWithConditionalBlock: 684 | Enabled: false 685 | Style/MapToHash: 686 | Enabled: false 687 | Style/MapToSet: 688 | Enabled: false 689 | Style/MinMaxComparison: 690 | Enabled: false 691 | Style/MultilineInPatternThen: 692 | Enabled: false 693 | Style/NegatedIfElseCondition: 694 | Enabled: false 695 | Style/NestedFileDirname: 696 | Enabled: false 697 | Style/NilLambda: 698 | Enabled: false 699 | Style/NumberedParameters: 700 | Enabled: false 701 | Style/NumberedParametersLimit: 702 | Enabled: false 703 | Style/ObjectThen: 704 | Enabled: false 705 | Style/OpenStructUse: 706 | Enabled: false 707 | Style/OperatorMethodCall: 708 | Enabled: false 709 | Style/QuotedSymbols: 710 | Enabled: false 711 | Style/RedundantArgument: 712 | Enabled: false 713 | Style/RedundantConstantBase: 714 | Enabled: false 715 | Style/RedundantDoubleSplatHashBraces: 716 | Enabled: false 717 | Style/RedundantEach: 718 | Enabled: false 719 | Style/RedundantHeredocDelimiterQuotes: 720 | Enabled: false 721 | Style/RedundantInitialize: 722 | Enabled: false 723 | Style/RedundantLineContinuation: 724 | Enabled: false 725 | Style/RedundantSelfAssignmentBranch: 726 | Enabled: false 727 | Style/RedundantStringEscape: 728 | Enabled: false 729 | Style/SelectByRegexp: 730 | Enabled: false 731 | Style/StringChars: 732 | Enabled: false 733 | Style/SwapValues: 734 | Enabled: false 735 | --------------------------------------------------------------------------------