├── .devcontainer ├── Dockerfile ├── README.md └── devcontainer.json ├── .fixtures.yml ├── .gitattributes ├── .github ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── mend.yml │ ├── nightly.yml │ ├── release.yml │ └── release_prep.yml ├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .pdkignore ├── .puppet-lint.rc ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .sync.yml ├── .vscode └── extensions.json ├── .yardopts ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── Gemfile ├── HISTORY.md ├── LICENSE ├── NOTICE ├── README.md ├── REFERENCE.md ├── Rakefile ├── data └── common.yaml ├── hiera.yaml ├── lib └── puppet │ └── parser │ └── functions │ ├── parse_auto_update_option.rb │ ├── parse_scheduled_install_day.rb │ └── validate_in_range.rb ├── manifests ├── init.pp └── setting.pp ├── metadata.json ├── pdk.yaml ├── provision.yaml ├── spec ├── acceptance │ └── wsus_client_spec.rb ├── classes │ └── init_spec.rb ├── default_facts.yml ├── functions │ ├── parse_auto_update_option_spec.rb │ └── parse_scheduled_install_day_spec.rb ├── run_pester.ps1 ├── spec_helper.ps1 ├── spec_helper.rb ├── spec_helper_acceptance.rb └── tasks │ └── update_history.Tests.ps1 └── tasks ├── update_history.json └── update_history.ps1 /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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: 7 | repo: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git' 8 | ref: v4.13.0 9 | provision: 'https://github.com/puppetlabs/provision.git' 10 | symlinks: 11 | "wsus_client": "#{source_dir}" 12 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.rb eol=lf 2 | *.erb eol=lf 3 | *.pp eol=lf 4 | *.sh eol=lf 5 | *.epp eol=lf 6 | -------------------------------------------------------------------------------- /.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`) -------------------------------------------------------------------------------- /.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 | secrets: "inherit" 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | /convert_report.txt 23 | /update_report.txt 24 | .DS_Store 25 | .project 26 | .envrc 27 | /inventory.yaml 28 | /spec/fixtures/litmus_inventory.yaml 29 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | /convert_report.txt 23 | /update_report.txt 24 | .DS_Store 25 | .project 26 | .envrc 27 | /inventory.yaml 28 | /spec/fixtures/litmus_inventory.yaml 29 | /.fixtures.yml 30 | /Gemfile 31 | /.gitattributes 32 | /.github/ 33 | /.gitignore 34 | /.pdkignore 35 | /.puppet-lint.rc 36 | /Rakefile 37 | /rakelib/ 38 | /.rspec 39 | /..yml 40 | /.yardopts 41 | /spec/ 42 | /.vscode/ 43 | /.sync.yml 44 | /.devcontainer/ 45 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --relative 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | --- 2 | require: 3 | - rubocop-performance 4 | - rubocop-rspec 5 | AllCops: 6 | NewCops: enable 7 | DisplayCopNames: true 8 | TargetRubyVersion: '2.7' 9 | Include: 10 | - "**/*.rb" 11 | Exclude: 12 | - bin/* 13 | - ".vendor/**/*" 14 | - "**/Gemfile" 15 | - "**/Rakefile" 16 | - pkg/**/* 17 | - spec/fixtures/**/* 18 | - vendor/**/* 19 | - "**/Puppetfile" 20 | - "**/Vagrantfile" 21 | - "**/Guardfile" 22 | inherit_from: ".rubocop_todo.yml" 23 | Layout/LineLength: 24 | Description: People have wide screens, use them. 25 | Max: 200 26 | RSpec/BeforeAfterAll: 27 | Description: Beware of using after(:all) as it may cause state to leak between tests. 28 | A necessary evil in acceptance testing. 29 | Exclude: 30 | - spec/acceptance/**/*.rb 31 | RSpec/HookArgument: 32 | Description: Prefer explicit :each argument, matching existing module's style 33 | EnforcedStyle: each 34 | RSpec/DescribeSymbol: 35 | Exclude: 36 | - spec/unit/facter/**/*.rb 37 | Style/BlockDelimiters: 38 | Description: Prefer braces for chaining. Mostly an aesthetical choice. Better to 39 | be consistent then. 40 | EnforcedStyle: braces_for_chaining 41 | Style/ClassAndModuleChildren: 42 | Description: Compact style reduces the required amount of indentation. 43 | EnforcedStyle: compact 44 | Style/EmptyElse: 45 | Description: Enforce against empty else clauses, but allow `nil` for clarity. 46 | EnforcedStyle: empty 47 | Style/FormatString: 48 | Description: Following the main puppet project's style, prefer the % format format. 49 | EnforcedStyle: percent 50 | Style/FormatStringToken: 51 | Description: Following the main puppet project's style, prefer the simpler template 52 | tokens over annotated ones. 53 | EnforcedStyle: template 54 | Style/Lambda: 55 | Description: Prefer the keyword for easier discoverability. 56 | EnforcedStyle: literal 57 | Style/RegexpLiteral: 58 | Description: Community preference. See https://github.com/voxpupuli/modulesync_config/issues/168 59 | EnforcedStyle: percent_r 60 | Style/TernaryParentheses: 61 | Description: Checks for use of parentheses around ternary conditions. Enforce parentheses 62 | on complex expressions for better readability, but seriously consider breaking 63 | it up. 64 | EnforcedStyle: require_parentheses_when_complex 65 | Style/TrailingCommaInArguments: 66 | Description: Prefer always trailing comma on multiline argument lists. This makes 67 | diffs, and re-ordering nicer. 68 | EnforcedStyleForMultiline: comma 69 | Style/TrailingCommaInArrayLiteral: 70 | Description: Prefer always trailing comma on multiline literals. This makes diffs, 71 | and re-ordering nicer. 72 | EnforcedStyleForMultiline: comma 73 | Style/SymbolArray: 74 | Description: Using percent style obscures symbolic intent of array's contents. 75 | EnforcedStyle: brackets 76 | RSpec/MessageSpies: 77 | EnforcedStyle: receive 78 | Style/Documentation: 79 | Exclude: 80 | - lib/puppet/parser/functions/**/* 81 | - spec/**/* 82 | Style/WordArray: 83 | EnforcedStyle: brackets 84 | Performance/AncestorsInclude: 85 | Enabled: true 86 | Performance/BigDecimalWithNumericArgument: 87 | Enabled: true 88 | Performance/BlockGivenWithExplicitBlock: 89 | Enabled: true 90 | Performance/CaseWhenSplat: 91 | Enabled: true 92 | Performance/ConstantRegexp: 93 | Enabled: true 94 | Performance/MethodObjectAsBlock: 95 | Enabled: true 96 | Performance/RedundantSortBlock: 97 | Enabled: true 98 | Performance/RedundantStringChars: 99 | Enabled: true 100 | Performance/ReverseFirst: 101 | Enabled: true 102 | Performance/SortReverse: 103 | Enabled: true 104 | Performance/Squeeze: 105 | Enabled: true 106 | Performance/StringInclude: 107 | Enabled: true 108 | Performance/Sum: 109 | Enabled: true 110 | Style/CollectionMethods: 111 | Enabled: true 112 | Style/MethodCalledOnDoEndBlock: 113 | Enabled: true 114 | Style/StringMethods: 115 | Enabled: true 116 | Bundler/GemFilename: 117 | Enabled: false 118 | Bundler/InsecureProtocolSource: 119 | Enabled: false 120 | Capybara/CurrentPathExpectation: 121 | Enabled: false 122 | Capybara/VisibilityMatcher: 123 | Enabled: false 124 | Gemspec/DuplicatedAssignment: 125 | Enabled: false 126 | Gemspec/OrderedDependencies: 127 | Enabled: false 128 | Gemspec/RequiredRubyVersion: 129 | Enabled: false 130 | Gemspec/RubyVersionGlobalsUsage: 131 | Enabled: false 132 | Layout/ArgumentAlignment: 133 | Enabled: false 134 | Layout/BeginEndAlignment: 135 | Enabled: false 136 | Layout/ClosingHeredocIndentation: 137 | Enabled: false 138 | Layout/EmptyComment: 139 | Enabled: false 140 | Layout/EmptyLineAfterGuardClause: 141 | Enabled: false 142 | Layout/EmptyLinesAroundArguments: 143 | Enabled: false 144 | Layout/EmptyLinesAroundAttributeAccessor: 145 | Enabled: false 146 | Layout/EndOfLine: 147 | Enabled: false 148 | Layout/FirstArgumentIndentation: 149 | Enabled: false 150 | Layout/HashAlignment: 151 | Enabled: false 152 | Layout/HeredocIndentation: 153 | Enabled: false 154 | Layout/LeadingEmptyLines: 155 | Enabled: false 156 | Layout/SpaceAroundMethodCallOperator: 157 | Enabled: false 158 | Layout/SpaceInsideArrayLiteralBrackets: 159 | Enabled: false 160 | Layout/SpaceInsideReferenceBrackets: 161 | Enabled: false 162 | Lint/BigDecimalNew: 163 | Enabled: false 164 | Lint/BooleanSymbol: 165 | Enabled: false 166 | Lint/ConstantDefinitionInBlock: 167 | Enabled: false 168 | Lint/DeprecatedOpenSSLConstant: 169 | Enabled: false 170 | Lint/DisjunctiveAssignmentInConstructor: 171 | Enabled: false 172 | Lint/DuplicateElsifCondition: 173 | Enabled: false 174 | Lint/DuplicateRequire: 175 | Enabled: false 176 | Lint/DuplicateRescueException: 177 | Enabled: false 178 | Lint/EmptyConditionalBody: 179 | Enabled: false 180 | Lint/EmptyFile: 181 | Enabled: false 182 | Lint/ErbNewArguments: 183 | Enabled: false 184 | Lint/FloatComparison: 185 | Enabled: false 186 | Lint/HashCompareByIdentity: 187 | Enabled: false 188 | Lint/IdentityComparison: 189 | Enabled: false 190 | Lint/InterpolationCheck: 191 | Enabled: false 192 | Lint/MissingCopEnableDirective: 193 | Enabled: false 194 | Lint/MixedRegexpCaptureTypes: 195 | Enabled: false 196 | Lint/NestedPercentLiteral: 197 | Enabled: false 198 | Lint/NonDeterministicRequireOrder: 199 | Enabled: false 200 | Lint/OrderedMagicComments: 201 | Enabled: false 202 | Lint/OutOfRangeRegexpRef: 203 | Enabled: false 204 | Lint/RaiseException: 205 | Enabled: false 206 | Lint/RedundantCopEnableDirective: 207 | Enabled: false 208 | Lint/RedundantRequireStatement: 209 | Enabled: false 210 | Lint/RedundantSafeNavigation: 211 | Enabled: false 212 | Lint/RedundantWithIndex: 213 | Enabled: false 214 | Lint/RedundantWithObject: 215 | Enabled: false 216 | Lint/RegexpAsCondition: 217 | Enabled: false 218 | Lint/ReturnInVoidContext: 219 | Enabled: false 220 | Lint/SafeNavigationConsistency: 221 | Enabled: false 222 | Lint/SafeNavigationWithEmpty: 223 | Enabled: false 224 | Lint/SelfAssignment: 225 | Enabled: false 226 | Lint/SendWithMixinArgument: 227 | Enabled: false 228 | Lint/ShadowedArgument: 229 | Enabled: false 230 | Lint/StructNewOverride: 231 | Enabled: false 232 | Lint/ToJSON: 233 | Enabled: false 234 | Lint/TopLevelReturnWithArgument: 235 | Enabled: false 236 | Lint/TrailingCommaInAttributeDeclaration: 237 | Enabled: false 238 | Lint/UnreachableLoop: 239 | Enabled: false 240 | Lint/UriEscapeUnescape: 241 | Enabled: false 242 | Lint/UriRegexp: 243 | Enabled: false 244 | Lint/UselessMethodDefinition: 245 | Enabled: false 246 | Lint/UselessTimes: 247 | Enabled: false 248 | Metrics/AbcSize: 249 | Enabled: false 250 | Metrics/BlockLength: 251 | Enabled: false 252 | Metrics/BlockNesting: 253 | Enabled: false 254 | Metrics/ClassLength: 255 | Enabled: false 256 | Metrics/CyclomaticComplexity: 257 | Enabled: false 258 | Metrics/MethodLength: 259 | Enabled: false 260 | Metrics/ModuleLength: 261 | Enabled: false 262 | Metrics/ParameterLists: 263 | Enabled: false 264 | Metrics/PerceivedComplexity: 265 | Enabled: false 266 | Migration/DepartmentName: 267 | Enabled: false 268 | Naming/AccessorMethodName: 269 | Enabled: false 270 | Naming/BlockParameterName: 271 | Enabled: false 272 | Naming/HeredocDelimiterCase: 273 | Enabled: false 274 | Naming/HeredocDelimiterNaming: 275 | Enabled: false 276 | Naming/MemoizedInstanceVariableName: 277 | Enabled: false 278 | Naming/MethodParameterName: 279 | Enabled: false 280 | Naming/RescuedExceptionsVariableName: 281 | Enabled: false 282 | Naming/VariableNumber: 283 | Enabled: false 284 | Performance/BindCall: 285 | Enabled: false 286 | Performance/DeletePrefix: 287 | Enabled: false 288 | Performance/DeleteSuffix: 289 | Enabled: false 290 | Performance/InefficientHashSearch: 291 | Enabled: false 292 | Performance/UnfreezeString: 293 | Enabled: false 294 | Performance/UriDefaultParser: 295 | Enabled: false 296 | RSpec/Be: 297 | Enabled: false 298 | RSpec/Capybara/FeatureMethods: 299 | Enabled: false 300 | RSpec/ContainExactly: 301 | Enabled: false 302 | RSpec/ContextMethod: 303 | Enabled: false 304 | RSpec/ContextWording: 305 | Enabled: false 306 | RSpec/DescribeClass: 307 | Enabled: false 308 | RSpec/EmptyHook: 309 | Enabled: false 310 | RSpec/EmptyLineAfterExample: 311 | Enabled: false 312 | RSpec/EmptyLineAfterExampleGroup: 313 | Enabled: false 314 | RSpec/EmptyLineAfterHook: 315 | Enabled: false 316 | RSpec/ExampleLength: 317 | Enabled: false 318 | RSpec/ExampleWithoutDescription: 319 | Enabled: false 320 | RSpec/ExpectChange: 321 | Enabled: false 322 | RSpec/ExpectInHook: 323 | Enabled: false 324 | RSpec/FactoryBot/AttributeDefinedStatically: 325 | Enabled: false 326 | RSpec/FactoryBot/CreateList: 327 | Enabled: false 328 | RSpec/FactoryBot/FactoryClassName: 329 | Enabled: false 330 | RSpec/HooksBeforeExamples: 331 | Enabled: false 332 | RSpec/ImplicitBlockExpectation: 333 | Enabled: false 334 | RSpec/ImplicitSubject: 335 | Enabled: false 336 | RSpec/LeakyConstantDeclaration: 337 | Enabled: false 338 | RSpec/LetBeforeExamples: 339 | Enabled: false 340 | RSpec/MatchArray: 341 | Enabled: false 342 | RSpec/MissingExampleGroupArgument: 343 | Enabled: false 344 | RSpec/MultipleExpectations: 345 | Enabled: false 346 | RSpec/MultipleMemoizedHelpers: 347 | Enabled: false 348 | RSpec/MultipleSubjects: 349 | Enabled: false 350 | RSpec/NestedGroups: 351 | Enabled: false 352 | RSpec/PredicateMatcher: 353 | Enabled: false 354 | RSpec/ReceiveCounts: 355 | Enabled: false 356 | RSpec/ReceiveNever: 357 | Enabled: false 358 | RSpec/RepeatedExampleGroupBody: 359 | Enabled: false 360 | RSpec/RepeatedExampleGroupDescription: 361 | Enabled: false 362 | RSpec/RepeatedIncludeExample: 363 | Enabled: false 364 | RSpec/ReturnFromStub: 365 | Enabled: false 366 | RSpec/SharedExamples: 367 | Enabled: false 368 | RSpec/StubbedMock: 369 | Enabled: false 370 | RSpec/UnspecifiedException: 371 | Enabled: false 372 | RSpec/VariableDefinition: 373 | Enabled: false 374 | RSpec/VoidExpect: 375 | Enabled: false 376 | RSpec/Yield: 377 | Enabled: false 378 | Security/Open: 379 | Enabled: false 380 | Style/AccessModifierDeclarations: 381 | Enabled: false 382 | Style/AccessorGrouping: 383 | Enabled: false 384 | Style/BisectedAttrAccessor: 385 | Enabled: false 386 | Style/CaseLikeIf: 387 | Enabled: false 388 | Style/ClassEqualityComparison: 389 | Enabled: false 390 | Style/ColonMethodDefinition: 391 | Enabled: false 392 | Style/CombinableLoops: 393 | Enabled: false 394 | Style/CommentedKeyword: 395 | Enabled: false 396 | Style/Dir: 397 | Enabled: false 398 | Style/DoubleCopDisableDirective: 399 | Enabled: false 400 | Style/EmptyBlockParameter: 401 | Enabled: false 402 | Style/EmptyLambdaParameter: 403 | Enabled: false 404 | Style/Encoding: 405 | Enabled: false 406 | Style/EvalWithLocation: 407 | Enabled: false 408 | Style/ExpandPathArguments: 409 | Enabled: false 410 | Style/ExplicitBlockArgument: 411 | Enabled: false 412 | Style/ExponentialNotation: 413 | Enabled: false 414 | Style/FloatDivision: 415 | Enabled: false 416 | Style/FrozenStringLiteralComment: 417 | Enabled: false 418 | Style/GlobalStdStream: 419 | Enabled: false 420 | Style/HashAsLastArrayItem: 421 | Enabled: false 422 | Style/HashLikeCase: 423 | Enabled: false 424 | Style/HashTransformKeys: 425 | Enabled: false 426 | Style/HashTransformValues: 427 | Enabled: false 428 | Style/IfUnlessModifier: 429 | Enabled: false 430 | Style/KeywordParametersOrder: 431 | Enabled: false 432 | Style/MinMax: 433 | Enabled: false 434 | Style/MixinUsage: 435 | Enabled: false 436 | Style/MultilineWhenThen: 437 | Enabled: false 438 | Style/NegatedUnless: 439 | Enabled: false 440 | Style/NumericPredicate: 441 | Enabled: false 442 | Style/OptionalBooleanParameter: 443 | Enabled: false 444 | Style/OrAssignment: 445 | Enabled: false 446 | Style/RandomWithOffset: 447 | Enabled: false 448 | Style/RedundantAssignment: 449 | Enabled: false 450 | Style/RedundantCondition: 451 | Enabled: false 452 | Style/RedundantConditional: 453 | Enabled: false 454 | Style/RedundantFetchBlock: 455 | Enabled: false 456 | Style/RedundantFileExtensionInRequire: 457 | Enabled: false 458 | Style/RedundantRegexpCharacterClass: 459 | Enabled: false 460 | Style/RedundantRegexpEscape: 461 | Enabled: false 462 | Style/RedundantSelfAssignment: 463 | Enabled: false 464 | Style/RedundantSort: 465 | Enabled: false 466 | Style/RescueStandardError: 467 | Enabled: false 468 | Style/SingleArgumentDig: 469 | Enabled: false 470 | Style/SlicingWithRange: 471 | Enabled: false 472 | Style/SoleNestedConditional: 473 | Enabled: false 474 | Style/StderrPuts: 475 | Enabled: false 476 | Style/StringConcatenation: 477 | Enabled: false 478 | Style/Strip: 479 | Enabled: false 480 | Style/SymbolProc: 481 | Enabled: false 482 | Style/TrailingBodyOnClass: 483 | Enabled: false 484 | Style/TrailingBodyOnMethodDefinition: 485 | Enabled: false 486 | Style/TrailingBodyOnModule: 487 | Enabled: false 488 | Style/TrailingCommaInHashLiteral: 489 | Enabled: false 490 | Style/TrailingMethodEndStatement: 491 | Enabled: false 492 | Style/UnpackFirst: 493 | Enabled: false 494 | Capybara/MatchStyle: 495 | Enabled: false 496 | Capybara/NegationMatcher: 497 | Enabled: false 498 | Capybara/SpecificActions: 499 | Enabled: false 500 | Capybara/SpecificFinders: 501 | Enabled: false 502 | Capybara/SpecificMatcher: 503 | Enabled: false 504 | Gemspec/DeprecatedAttributeAssignment: 505 | Enabled: false 506 | Gemspec/DevelopmentDependencies: 507 | Enabled: false 508 | Gemspec/RequireMFA: 509 | Enabled: false 510 | Layout/LineContinuationLeadingSpace: 511 | Enabled: false 512 | Layout/LineContinuationSpacing: 513 | Enabled: false 514 | Layout/LineEndStringConcatenationIndentation: 515 | Enabled: false 516 | Layout/SpaceBeforeBrackets: 517 | Enabled: false 518 | Lint/AmbiguousAssignment: 519 | Enabled: false 520 | Lint/AmbiguousOperatorPrecedence: 521 | Enabled: false 522 | Lint/AmbiguousRange: 523 | Enabled: false 524 | Lint/ConstantOverwrittenInRescue: 525 | Enabled: false 526 | Lint/DeprecatedConstants: 527 | Enabled: false 528 | Lint/DuplicateBranch: 529 | Enabled: false 530 | Lint/DuplicateMagicComment: 531 | Enabled: false 532 | Lint/DuplicateRegexpCharacterClassElement: 533 | Enabled: false 534 | Lint/EmptyBlock: 535 | Enabled: false 536 | Lint/EmptyClass: 537 | Enabled: false 538 | Lint/EmptyInPattern: 539 | Enabled: false 540 | Lint/IncompatibleIoSelectWithFiberScheduler: 541 | Enabled: false 542 | Lint/LambdaWithoutLiteralBlock: 543 | Enabled: false 544 | Lint/NoReturnInBeginEndBlocks: 545 | Enabled: false 546 | Lint/NonAtomicFileOperation: 547 | Enabled: false 548 | Lint/NumberedParameterAssignment: 549 | Enabled: false 550 | Lint/OrAssignmentToConstant: 551 | Enabled: false 552 | Lint/RedundantDirGlobSort: 553 | Enabled: false 554 | Lint/RefinementImportMethods: 555 | Enabled: false 556 | Lint/RequireRangeParentheses: 557 | Enabled: false 558 | Lint/RequireRelativeSelfPath: 559 | Enabled: false 560 | Lint/SymbolConversion: 561 | Enabled: false 562 | Lint/ToEnumArguments: 563 | Enabled: false 564 | Lint/TripleQuotes: 565 | Enabled: false 566 | Lint/UnexpectedBlockArity: 567 | Enabled: false 568 | Lint/UnmodifiedReduceAccumulator: 569 | Enabled: false 570 | Lint/UselessRescue: 571 | Enabled: false 572 | Lint/UselessRuby2Keywords: 573 | Enabled: false 574 | Metrics/CollectionLiteralLength: 575 | Enabled: false 576 | Naming/BlockForwarding: 577 | Enabled: false 578 | Performance/CollectionLiteralInLoop: 579 | Enabled: false 580 | Performance/ConcurrentMonotonicTime: 581 | Enabled: false 582 | Performance/MapCompact: 583 | Enabled: false 584 | Performance/RedundantEqualityComparisonBlock: 585 | Enabled: false 586 | Performance/RedundantSplitRegexpArgument: 587 | Enabled: false 588 | Performance/StringIdentifierArgument: 589 | Enabled: false 590 | RSpec/BeEq: 591 | Enabled: false 592 | RSpec/BeNil: 593 | Enabled: false 594 | RSpec/ChangeByZero: 595 | Enabled: false 596 | RSpec/ClassCheck: 597 | Enabled: false 598 | RSpec/DuplicatedMetadata: 599 | Enabled: false 600 | RSpec/ExcessiveDocstringSpacing: 601 | Enabled: false 602 | RSpec/FactoryBot/ConsistentParenthesesStyle: 603 | Enabled: false 604 | RSpec/FactoryBot/FactoryNameStyle: 605 | Enabled: false 606 | RSpec/FactoryBot/SyntaxMethods: 607 | Enabled: false 608 | RSpec/IdenticalEqualityAssertion: 609 | Enabled: false 610 | RSpec/NoExpectationExample: 611 | Enabled: false 612 | RSpec/PendingWithoutReason: 613 | Enabled: false 614 | RSpec/Rails/AvoidSetupHook: 615 | Enabled: false 616 | RSpec/Rails/HaveHttpStatus: 617 | Enabled: false 618 | RSpec/Rails/InferredSpecType: 619 | Enabled: false 620 | RSpec/Rails/MinitestAssertions: 621 | Enabled: false 622 | RSpec/Rails/TravelAround: 623 | Enabled: false 624 | RSpec/RedundantAround: 625 | Enabled: false 626 | RSpec/SkipBlockInsideExample: 627 | Enabled: false 628 | RSpec/SortMetadata: 629 | Enabled: false 630 | RSpec/SubjectDeclaration: 631 | Enabled: false 632 | RSpec/VerifiedDoubleReference: 633 | Enabled: false 634 | Security/CompoundHash: 635 | Enabled: false 636 | Security/IoMethods: 637 | Enabled: false 638 | Style/ArgumentsForwarding: 639 | Enabled: false 640 | Style/ArrayIntersect: 641 | Enabled: false 642 | Style/CollectionCompact: 643 | Enabled: false 644 | Style/ComparableClamp: 645 | Enabled: false 646 | Style/ConcatArrayLiterals: 647 | Enabled: false 648 | Style/DirEmpty: 649 | Enabled: false 650 | Style/DocumentDynamicEvalDefinition: 651 | Enabled: false 652 | Style/EmptyHeredoc: 653 | Enabled: false 654 | Style/EndlessMethod: 655 | Enabled: false 656 | Style/EnvHome: 657 | Enabled: false 658 | Style/FetchEnvVar: 659 | Enabled: false 660 | Style/FileEmpty: 661 | Enabled: false 662 | Style/FileRead: 663 | Enabled: false 664 | Style/FileWrite: 665 | Enabled: false 666 | Style/HashConversion: 667 | Enabled: false 668 | Style/HashExcept: 669 | Enabled: false 670 | Style/IfWithBooleanLiteralBranches: 671 | Enabled: false 672 | Style/InPatternThen: 673 | Enabled: false 674 | Style/MagicCommentFormat: 675 | Enabled: false 676 | Style/MapCompactWithConditionalBlock: 677 | Enabled: false 678 | Style/MapToHash: 679 | Enabled: false 680 | Style/MapToSet: 681 | Enabled: false 682 | Style/MinMaxComparison: 683 | Enabled: false 684 | Style/MultilineInPatternThen: 685 | Enabled: false 686 | Style/NegatedIfElseCondition: 687 | Enabled: false 688 | Style/NestedFileDirname: 689 | Enabled: false 690 | Style/NilLambda: 691 | Enabled: false 692 | Style/NumberedParameters: 693 | Enabled: false 694 | Style/NumberedParametersLimit: 695 | Enabled: false 696 | Style/ObjectThen: 697 | Enabled: false 698 | Style/OpenStructUse: 699 | Enabled: false 700 | Style/OperatorMethodCall: 701 | Enabled: false 702 | Style/QuotedSymbols: 703 | Enabled: false 704 | Style/RedundantArgument: 705 | Enabled: false 706 | Style/RedundantConstantBase: 707 | Enabled: false 708 | Style/RedundantDoubleSplatHashBraces: 709 | Enabled: false 710 | Style/RedundantEach: 711 | Enabled: false 712 | Style/RedundantHeredocDelimiterQuotes: 713 | Enabled: false 714 | Style/RedundantInitialize: 715 | Enabled: false 716 | Style/RedundantSelfAssignmentBranch: 717 | Enabled: false 718 | Style/RedundantStringEscape: 719 | Enabled: false 720 | Style/SelectByRegexp: 721 | Enabled: false 722 | Style/StringChars: 723 | Enabled: false 724 | Style/SwapValues: 725 | Enabled: false 726 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "puppet.puppet-vscode", 4 | "rebornix.Ruby" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /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.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v6.1.0) - 2023-06-20 9 | 10 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v6.0.0...v6.1.0) 11 | 12 | ### Added 13 | 14 | - pdksync - (MAINT) - Allow Stdlib 9.x [#209](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/209) ([LukasAud](https://github.com/LukasAud)) 15 | 16 | ### Fixed 17 | 18 | - (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)) 19 | 20 | ## [v6.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v6.0.0) - 2023-04-20 21 | 22 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v5.0.1...v6.0.0) 23 | 24 | ### Changed 25 | - (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)) 26 | 27 | ## [v5.0.1](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v5.0.1) - 2023-04-20 28 | 29 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v5.0.0...v5.0.1) 30 | 31 | ### Fixed 32 | 33 | - (CONT-860) Update registry dependency [#202](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/202) ([LukasAud](https://github.com/LukasAud)) 34 | 35 | ## [v5.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v5.0.0) - 2023-03-09 36 | 37 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v4.0.0...v5.0.0) 38 | 39 | ### Added 40 | 41 | - pdksync - (FM-8922) - Add Support for Windows 2022 [#175](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/175) ([david22swan](https://github.com/david22swan)) 42 | 43 | ### Changed 44 | - (gh-cat-9) Add specific data types [#181](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/181) ([LukasAud](https://github.com/LukasAud)) 45 | 46 | ### Fixed 47 | 48 | - (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)) 49 | - adjusted upper limit [#184](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/184) ([prolixalias](https://github.com/prolixalias)) 50 | 51 | ## [v4.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v4.0.0) - 2021-03-01 52 | 53 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.2.0...v4.0.0) 54 | 55 | ### Changed 56 | - 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)) 57 | 58 | ## [v3.2.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.2.0) - 2021-02-18 59 | 60 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.1.0...v3.2.0) 61 | 62 | ### Added 63 | 64 | - 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)) 65 | 66 | ## [v3.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.1.0) - 2020-01-06 67 | 68 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v3.0.0...v3.1.0) 69 | 70 | ### Added 71 | 72 | - Update metadata.json [#114](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/114) ([sootysec](https://github.com/sootysec)) 73 | 74 | ## [v3.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v3.0.0) - 2019-10-18 75 | 76 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v2.0.0...v3.0.0) 77 | 78 | ### Fixed 79 | 80 | - (maint) - Fixes to HISTORY.md and metadata.json [#110](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/110) ([david22swan](https://github.com/david22swan)) 81 | 82 | ## [v2.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v2.0.0) - 2019-07-25 83 | 84 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.1.0...v2.0.0) 85 | 86 | ### Added 87 | 88 | - MODULES-9421 - Stringify module [#101](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/101) ([lionce](https://github.com/lionce)) 89 | 90 | ## [1.1.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.1.0) - 2018-10-25 91 | 92 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.3...1.1.0) 93 | 94 | ### Added 95 | 96 | - 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)) 97 | - (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)) 98 | 99 | ### Changed 100 | - (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)) 101 | 102 | ## [1.0.3](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.3) - 2016-12-14 103 | 104 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.2...1.0.3) 105 | 106 | ### Added 107 | 108 | - (MODULES-3475) Support AlwaysAutoRebootAtScheduledTimeMinutes [#48](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/48) ([adasko](https://github.com/adasko)) 109 | - (MODULES-3016) Support AlwaysAutoRebootAtScheduledTime [#47](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/47) ([jpogran](https://github.com/jpogran)) 110 | 111 | ### Fixed 112 | 113 | - (MODULES-3632) Use json_pure always [#61](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/61) ([hunner](https://github.com/hunner)) 114 | - (MODULES-2420) omit ensure => running due to "trigger start" [#56](https://github.com/puppetlabs/puppetlabs-wsus_client/pull/56) ([MosesMendoza](https://github.com/MosesMendoza)) 115 | 116 | ## [1.0.2](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.2) - 2016-05-04 117 | 118 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.1...1.0.2) 119 | 120 | ## [1.0.1](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.1) - 2015-12-07 121 | 122 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/1.0.0...1.0.1) 123 | 124 | ## [1.0.0](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/1.0.0) - 2015-08-31 125 | 126 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/v0.1.3...1.0.0) 127 | 128 | ## [v0.1.3](https://github.com/puppetlabs/puppetlabs-wsus_client/tree/v0.1.3) - 2015-07-02 129 | 130 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-wsus_client/compare/702fb9da2b7f7ca745262889912ede6c54f8543c...v0.1.3) 131 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the modules team 2 | * @puppetlabs/modules 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source ENV['GEM_SOURCE'] || 'https://rubygems.org' 2 | 3 | def location_for(place_or_version, fake_version = nil) 4 | git_url_regex = %r{\A(?(https?|git)[:@][^#]*)(#(?.*))?} 5 | file_url_regex = %r{\Afile:\/\/(?.*)} 6 | 7 | if place_or_version && (git_url = place_or_version.match(git_url_regex)) 8 | [fake_version, { git: git_url[:url], branch: git_url[:branch], require: false }].compact 9 | elsif place_or_version && (file_url = place_or_version.match(file_url_regex)) 10 | ['>= 0', { path: File.expand_path(file_url[:path]), require: false }] 11 | else 12 | [place_or_version, { require: false }] 13 | end 14 | end 15 | 16 | group :development do 17 | gem "json", '= 2.1.0', require: false if Gem::Requirement.create(['>= 2.5.0', '< 2.7.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 18 | gem "json", '= 2.3.0', require: false if Gem::Requirement.create(['>= 2.7.0', '< 3.0.0']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 19 | gem "json", '= 2.5.1', require: false if Gem::Requirement.create(['>= 3.0.0', '< 3.0.5']).satisfied_by?(Gem::Version.new(RUBY_VERSION.dup)) 20 | 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)) 21 | 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)) 22 | 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)) 23 | gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false 24 | gem "facterdb", '~> 1.18', require: false 25 | gem "metadata-json-lint", '~> 3.0', require: false 26 | gem "puppetlabs_spec_helper", '~> 6.0', require: false 27 | gem "rspec-puppet-facts", '~> 2.0', require: false 28 | gem "codecov", '~> 0.2', require: false 29 | gem "dependency_checker", '~> 1.0.0', require: false 30 | gem "parallel_tests", '= 3.12.1', require: false 31 | gem "pry", '~> 0.10', require: false 32 | gem "simplecov-console", '~> 0.5', require: false 33 | gem "puppet-debugger", '~> 1.0', require: false 34 | gem "rubocop", '= 1.48.1', require: false 35 | gem "rubocop-performance", '= 1.16.0', require: false 36 | gem "rubocop-rspec", '= 2.19.0', require: false 37 | gem "puppet-strings", '~> 4.0', require: false 38 | gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] 39 | end 40 | group :system_tests do 41 | gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] 42 | gem "serverspec", '~> 2.41', require: false 43 | end 44 | group :release_prep do 45 | gem "puppet-strings", '~> 4.0', require: false 46 | gem "puppetlabs_spec_helper", '~> 6.0', require: false 47 | end 48 | 49 | puppet_version = ENV['PUPPET_GEM_VERSION'] 50 | facter_version = ENV['FACTER_GEM_VERSION'] 51 | hiera_version = ENV['HIERA_GEM_VERSION'] 52 | 53 | gems = {} 54 | 55 | gems['puppet'] = location_for(puppet_version) 56 | 57 | # If facter or hiera versions have been specified via the environment 58 | # variables 59 | 60 | gems['facter'] = location_for(facter_version) if facter_version 61 | gems['hiera'] = location_for(hiera_version) if hiera_version 62 | 63 | gems.each do |gem_name, gem_params| 64 | gem gem_name, *gem_params 65 | end 66 | 67 | # Evaluate Gemfile.local and ~/.gemfile if they exist 68 | extra_gemfiles = [ 69 | "#{__FILE__}.local", 70 | File.join(Dir.home, '.gemfile'), 71 | ] 72 | 73 | extra_gemfiles.each do |gemfile| 74 | if File.file?(gemfile) && File.readable?(gemfile) 75 | eval(File.read(gemfile), binding) 76 | end 77 | end 78 | # vim: syntax=ruby 79 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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'],Integer[2,5]]]` 113 | 114 | Sets the automatic update option you would like to use. Valid values: 'NotifyOnly', 'AutoNotify', 'Scheduled', and 'AutoInstall'. 115 | You can also refer to these four values using integers 2 through 5, 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 | 124 | If set to this value, scheduled_install_day and scheduled_install_hour are required. 125 | This parameter must be set to this value to use reschedule_wait_time_minutes. 126 | 'AutoInstall': Requires fully automatic updates that users can configure if allowed. 127 | 128 | Default value: `undef` 129 | 130 | ##### `auto_install_minor_updates` 131 | 132 | Data type: `Optional[Boolean]` 133 | 134 | Determines whether to silently install minor updates automatically. Valid options: 'true', 'false', and undef. 135 | If 'true', Windows installs minor updates without user interaction. 136 | If 'false', Windows treats them as any other update, which depends on other settings such as auto_update_option. 137 | 138 | Default value: `undef` 139 | 140 | ##### `detection_frequency_hours` 141 | 142 | Data type: `Optional[Variant[Integer[1,22],Boolean]]` 143 | 144 | Sets an interval in hours for clients to check for updates. Valid values: integers 1 through 22. 145 | Default: undef. 146 | If this enabled parameter has a valid value, Puppet sets the DetectionFrequency registry key to its value and the 147 | DetectionFrequencyEnabled Boolean registry key to 'true'. 148 | Otherwise, Puppet sets DetectionFrequencyEnabled to 'false' and Windows ignores the value of DetectionFrequency, falling 149 | back to the Windows default value of 22 hours. 150 | 151 | Default value: `undef` 152 | 153 | ##### `disable_windows_update_access` 154 | 155 | Data type: `Optional[Boolean]` 156 | 157 | Determines whether non-administrators can access Windows Update. 158 | Valid options: 'true' (disable access), 'false' (enable access), and undef. 159 | Default: undef. 160 | 161 | Default value: `undef` 162 | 163 | ##### `elevate_non_admins` 164 | 165 | Data type: `Optional[Boolean]` 166 | 167 | Determines which security groups can approve or refuse updates. Valid options: 'true', 'false', and undef. 168 | Default: undef. 169 | If 'true', members of the Users group can approve or refuse updates. 170 | If 'false', only members of the Administrators group can approve or refuse updates. 171 | 172 | Default value: `undef` 173 | 174 | ##### `no_auto_reboot_with_logged_on_users` 175 | 176 | Data type: `Optional[Boolean]` 177 | 178 | Determines whether to automatically reboot while a user is logged in to the client. 179 | Valid options: 'true', 'false', and undef. Default: undef. 180 | If 'true', Windows will not restart the client after installing updates, even if a reboot is required to finish installing the update. 181 | If 'false', Windows notifies the user that the client will restart 15 minutes after installing an update that requires a reboot. 182 | 183 | Default value: `undef` 184 | 185 | ##### `no_auto_update` 186 | 187 | Data type: `Optional[Boolean]` 188 | 189 | Disables automatic updates. Valid options: 'true', 'false' (automatic updates enabled), and undef. Default: undef. 190 | Windows disables automatic updates when this parameter is set to 'true' and enables them if it's set to 'false'. 191 | 192 | Default value: `undef` 193 | 194 | ##### `reboot_relaunch_timeout_minutes` 195 | 196 | Data type: `Optional[Variant[Integer[1,1440],Boolean]]` 197 | 198 | Sets a delay in minutes to wait before attempting to reboot after installing an update that requires one. 199 | Valid values: integers 1 through 1440. Default: undef. 200 | If this enabled parameter has a valid value, Puppet sets the RebootRelaunchTimeout registry key to its value and the 201 | RebootRelaunchTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootRelaunchTimeoutEnabled to 'false' 202 | and Windows ignores the value of RebootRelaunchTimeout, falling back to the Windows default value of 10 minutes. 203 | 204 | Default value: `undef` 205 | 206 | ##### `reboot_warning_timeout_minutes` 207 | 208 | Data type: `Optional[Variant[Integer[1,30],Boolean]]` 209 | 210 | Sets how many minutes users can wait before responding to a prompt to reboot the client after installing an update that requires 211 | a reboot. Valid values: integers 1 through 30. Default: undef. 212 | If this enabled parameter has a valid value, Puppet sets the RebootWarningTimeout registry key to its value and the 213 | RebootWarningTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootWarningTimeoutEnabled to 'false' and 214 | Windows ignores the value of RebootWarningTimeout, falling back to the Windows default value of 5 minutes. 215 | 216 | Default value: `undef` 217 | 218 | ##### `reschedule_wait_time_minutes` 219 | 220 | Data type: `Optional[Variant[Integer[1,60],Boolean]]` 221 | 222 | Sets how many minutes the client's automatic update service waits at startup before applying updates from a missed scheduled update. 223 | Valid values: integers 1 through 60. Default: undef. 224 | This enabled parameter is used only when automatic updates are enabled and auto_update_option is set to 'Scheduled' or '4'. 225 | If this parameter is set to a valid value, Puppet sets the RescheduleWaitTime registry key to that value and the 226 | RescheduleWaitTimeEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RescheduleWaitTimeEnabled to 'false' and Windows 227 | ignores the value of RescheduleWaitTime, falling back to the Windows default behavior of re-attempting installation at the next 228 | scheduled update time. 229 | 230 | Default value: `undef` 231 | 232 | ##### `scheduled_install_day` 233 | 234 | Data type: `Optional[Variant[Enum['Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday', 'Saturday'],Integer[0,7]]]` 235 | 236 | Schedules a day of the week to automatically install updates. Valid values: 'Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 237 | 'Thursday', 'Friday', and 'Saturday'. You can also refer to these eight values using the integers 0 through 7, respectively. 238 | Default: undef. 239 | This parameter depends on a valid scheduled_install_hour value and is required when auto_update_option is set to 'Scheduled' or '4'. 240 | 241 | Default value: `undef` 242 | 243 | ##### `scheduled_install_hour` 244 | 245 | Data type: `Optional[Variant[Integer[0,23],Boolean]]` 246 | 247 | Schedules an hour of the day to automatically install updates. Valid values: an integer from 0 through 23. Default: undef. 248 | This parameter depends on a valid scheduled_install_day value and is required when auto_update_option is set to 'Scheduled' or '4'. 249 | 250 | Default value: `undef` 251 | 252 | ##### `always_auto_reboot_at_scheduled_time` 253 | 254 | Data type: `Optional[Boolean]` 255 | 256 | Determines whether to automatically reboot. Valid options: 'true', 'false', and undef. Default: undef. 257 | 258 | Default value: `undef` 259 | 260 | ##### `always_auto_reboot_at_scheduled_time_minutes` 261 | 262 | Data type: `Optional[Variant[Integer[15,180],Boolean]]` 263 | 264 | Sets the timer to warning a signed-in user that a restart is going to occur. Valid values: integers 15 through 180. Default: undef. 265 | When the timer runs out, the restart will proceed even if the PC has signed-in users. 266 | 267 | Default value: `undef` 268 | 269 | ##### `purge_values` 270 | 271 | Data type: `Boolean` 272 | 273 | Determines whether Puppet purges values of unmanaged registry keys under the WindowsUpdate parent key. Valid options: Boolean. 274 | Default: 'false'. 275 | 276 | Default value: `false` 277 | 278 | ##### `target_group` 279 | 280 | Data type: `Optional[Variant[String,Boolean]]` 281 | 282 | Sets the client's target group. Valid values: a string. Default: undef. 283 | This enabled parameter is only respected when the WSUS server allows clients to modify this setting via the TargetGroup and 284 | TargetGroupEnabled registry keys. 285 | 286 | Default value: `undef` 287 | 288 | ## Defined types 289 | 290 | ### `wsus_client::setting` 291 | 292 | Manages wsus_client settings 293 | 294 | #### Parameters 295 | 296 | The following parameters are available in the `wsus_client::setting` defined type: 297 | 298 | * [`ensure`](#-wsus_client--setting--ensure) 299 | * [`key`](#-wsus_client--setting--key) 300 | * [`data`](#-wsus_client--setting--data) 301 | * [`type`](#-wsus_client--setting--type) 302 | * [`has_enabled`](#-wsus_client--setting--has_enabled) 303 | * [`validate_range`](#-wsus_client--setting--validate_range) 304 | * [`validate_bool`](#-wsus_client--setting--validate_bool) 305 | 306 | ##### `ensure` 307 | 308 | Data type: `Enum['present', 'absent', 'file']` 309 | 310 | Specifies whether the setting should exist. Valid options: 'present', 'absent', and 'file' 311 | 312 | Default value: `'present'` 313 | 314 | ##### `key` 315 | 316 | Data type: `String` 317 | 318 | Specifies registry_value 319 | 320 | Default value: `$title` 321 | 322 | ##### `data` 323 | 324 | Data type: `Optional[Variant[String,Integer,Boolean,Stdlib::HTTPUrl]]` 325 | 326 | Incoming data 327 | 328 | Default value: `undef` 329 | 330 | ##### `type` 331 | 332 | Data type: `String` 333 | 334 | Data type. default value: dword 335 | 336 | Default value: `'dword'` 337 | 338 | ##### `has_enabled` 339 | 340 | Data type: `Boolean` 341 | 342 | Specifies whether the key should be enabled. Boolean value 343 | 344 | Default value: `true` 345 | 346 | ##### `validate_range` 347 | 348 | Data type: `Optional[Tuple[Integer, Integer]]` 349 | 350 | Specifies whether the data should be validated as a number in a certain range 351 | 352 | Default value: `undef` 353 | 354 | ##### `validate_bool` 355 | 356 | Data type: `Boolean` 357 | 358 | Specifies whether the data should be validated as a boolean value 359 | 360 | Default value: `false` 361 | 362 | ## Functions 363 | 364 | ### `parse_auto_update_option` 365 | 366 | Type: Ruby 3.x API 367 | 368 | > *Note:* 369 | Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5 370 | 371 | #### `parse_auto_update_option()` 372 | 373 | > *Note:* 374 | Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5 375 | 376 | Returns: `Integer` option auto_update_option as an integer 377 | 378 | ### `parse_scheduled_install_day` 379 | 380 | Type: Ruby 3.x API 381 | 382 | > *Note:* 383 | Valid options for scheduled_install_day are Everyday|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|0-7 384 | 385 | #### `parse_scheduled_install_day()` 386 | 387 | > *Note:* 388 | Valid options for scheduled_install_day are Everyday|Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|0-7 389 | 390 | Returns: `Integer` option scheduled_install_day as an integer 391 | 392 | ### `validate_in_range` 393 | 394 | Type: Ruby 3.x API 395 | 396 | Validate the incoming value is in a certain range. 397 | 398 | #### `validate_in_range()` 399 | 400 | The validate_in_range function. 401 | 402 | Returns: `Any` Raises an error if the given value fails this validation. 403 | 404 | ## Tasks 405 | 406 | ### `update_history` 407 | 408 | Returns a history of installed Windows Updates. 409 | 410 | **Supports noop?** false 411 | 412 | #### Parameters 413 | 414 | ##### `detailed` 415 | 416 | Data type: `Optional[Boolean]` 417 | 418 | Return detailed update information. Default is to return basic information 419 | 420 | ##### `title` 421 | 422 | Data type: `Optional[String]` 423 | 424 | Return updates which match the specified regular expression. Default is to all updates 425 | 426 | ##### `updateid` 427 | 428 | Data type: `Optional[String]` 429 | 430 | Return updates which the specified Update ID. Default is to all updates 431 | 432 | ##### `maximumupdates` 433 | 434 | Data type: `Optional[String]` 435 | 436 | Limit the size of the history returned. Default is to return a maximum of 300 items 437 | 438 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /data/common.yaml: -------------------------------------------------------------------------------- 1 | --- {} 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 23 | option = args[0] 24 | error_msg = "Valid options for auto_update_option are NotifyOnly|AutoNotify|Scheduled|AutoInstall|2|3|4|5, provided '#{option}'" 25 | if option.is_a?(Numeric) || option =~ %r{^\d$} 26 | option = Integer(option) if option.is_a?(String) 27 | raise Puppet::ParseError, error_msg if option < 2 || option > 5 28 | 29 | return option 30 | end 31 | 32 | return autoupdate_hash[option.downcase] if autoupdate_hash.key?(option.downcase) 33 | 34 | raise Puppet::ParseError, error_msg 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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', and 'AutoInstall'. 43 | # You can also refer to these four values using integers 2 through 5, 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 | # 52 | # If set to this value, scheduled_install_day and scheduled_install_hour are required. 53 | # This parameter must be set to this value to use reschedule_wait_time_minutes. 54 | # 'AutoInstall': Requires fully automatic updates that users can configure if allowed. 55 | # 56 | # @param auto_install_minor_updates 57 | # Determines whether to silently install minor updates automatically. Valid options: 'true', 'false', and undef. 58 | # If 'true', Windows installs minor updates without user interaction. 59 | # If 'false', Windows treats them as any other update, which depends on other settings such as auto_update_option. 60 | # 61 | # @param detection_frequency_hours 62 | # Sets an interval in hours for clients to check for updates. Valid values: integers 1 through 22. 63 | # Default: undef. 64 | # If this enabled parameter has a valid value, Puppet sets the DetectionFrequency registry key to its value and the 65 | # DetectionFrequencyEnabled Boolean registry key to 'true'. 66 | # Otherwise, Puppet sets DetectionFrequencyEnabled to 'false' and Windows ignores the value of DetectionFrequency, falling 67 | # back to the Windows default value of 22 hours. 68 | # 69 | # @param disable_windows_update_access 70 | # Determines whether non-administrators can access Windows Update. 71 | # Valid options: 'true' (disable access), 'false' (enable access), and undef. 72 | # Default: undef. 73 | # 74 | # @param elevate_non_admins 75 | # Determines which security groups can approve or refuse updates. Valid options: 'true', 'false', and undef. 76 | # Default: undef. 77 | # If 'true', members of the Users group can approve or refuse updates. 78 | # If 'false', only members of the Administrators group can approve or refuse updates. 79 | # 80 | # @param no_auto_reboot_with_logged_on_users 81 | # Determines whether to automatically reboot while a user is logged in to the client. 82 | # Valid options: 'true', 'false', and undef. Default: undef. 83 | # If 'true', Windows will not restart the client after installing updates, even if a reboot is required to finish installing the update. 84 | # If 'false', Windows notifies the user that the client will restart 15 minutes after installing an update that requires a reboot. 85 | # 86 | # @param no_auto_update 87 | # Disables automatic updates. Valid options: 'true', 'false' (automatic updates enabled), and undef. Default: undef. 88 | # Windows disables automatic updates when this parameter is set to 'true' and enables them if it's set to 'false'. 89 | # 90 | # @param reboot_relaunch_timeout_minutes 91 | # Sets a delay in minutes to wait before attempting to reboot after installing an update that requires one. 92 | # Valid values: integers 1 through 1440. Default: undef. 93 | # If this enabled parameter has a valid value, Puppet sets the RebootRelaunchTimeout registry key to its value and the 94 | # RebootRelaunchTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootRelaunchTimeoutEnabled to 'false' 95 | # and Windows ignores the value of RebootRelaunchTimeout, falling back to the Windows default value of 10 minutes. 96 | # 97 | # @param reboot_warning_timeout_minutes 98 | # Sets how many minutes users can wait before responding to a prompt to reboot the client after installing an update that requires 99 | # a reboot. Valid values: integers 1 through 30. Default: undef. 100 | # If this enabled parameter has a valid value, Puppet sets the RebootWarningTimeout registry key to its value and the 101 | # RebootWarningTimeoutEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RebootWarningTimeoutEnabled to 'false' and 102 | # Windows ignores the value of RebootWarningTimeout, falling back to the Windows default value of 5 minutes. 103 | # 104 | # @param reschedule_wait_time_minutes 105 | # Sets how many minutes the client's automatic update service waits at startup before applying updates from a missed scheduled update. 106 | # Valid values: integers 1 through 60. Default: undef. 107 | # This enabled parameter is used only when automatic updates are enabled and auto_update_option is set to 'Scheduled' or '4'. 108 | # If this parameter is set to a valid value, Puppet sets the RescheduleWaitTime registry key to that value and the 109 | # RescheduleWaitTimeEnabled Boolean registry key to 'true'. Otherwise, Puppet sets RescheduleWaitTimeEnabled to 'false' and Windows 110 | # ignores the value of RescheduleWaitTime, falling back to the Windows default behavior of re-attempting installation at the next 111 | # scheduled update time. 112 | # 113 | # @param scheduled_install_day 114 | # Schedules a day of the week to automatically install updates. Valid values: 'Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 115 | # 'Thursday', 'Friday', and 'Saturday'. You can also refer to these eight values using the integers 0 through 7, respectively. 116 | # Default: undef. 117 | # This parameter depends on a valid scheduled_install_hour value and is required when auto_update_option is set to 'Scheduled' or '4'. 118 | # 119 | # @param scheduled_install_hour 120 | # Schedules an hour of the day to automatically install updates. Valid values: an integer from 0 through 23. Default: undef. 121 | # This parameter depends on a valid scheduled_install_day value and is required when auto_update_option is set to 'Scheduled' or '4'. 122 | # 123 | # @param always_auto_reboot_at_scheduled_time 124 | # Determines whether to automatically reboot. Valid options: 'true', 'false', and undef. Default: undef. 125 | # 126 | # @param always_auto_reboot_at_scheduled_time_minutes 127 | # Sets the timer to warning a signed-in user that a restart is going to occur. Valid values: integers 15 through 180. Default: undef. 128 | # When the timer runs out, the restart will proceed even if the PC has signed-in users. 129 | # 130 | # @param purge_values 131 | # Determines whether Puppet purges values of unmanaged registry keys under the WindowsUpdate parent key. Valid options: Boolean. 132 | # Default: 'false'. 133 | # 134 | # @param target_group 135 | # Sets the client's target group. Valid values: a string. Default: undef. 136 | # This enabled parameter is only respected when the WSUS server allows clients to modify this setting via the TargetGroup and 137 | # TargetGroupEnabled registry keys. 138 | # 139 | class wsus_client ( 140 | Optional[Variant[Stdlib::HTTPUrl,Boolean]] $server_url = undef, 141 | Optional[Boolean] $enable_status_server = undef, 142 | Optional[Boolean] $accept_trusted_publisher_certs = undef, 143 | Optional[Variant[Enum['NotifyOnly', 'AutoNotify', 'Scheduled', 'AutoInstall'],Integer[2,5]]] $auto_update_option = undef, 144 | Optional[Boolean] $auto_install_minor_updates = undef, 145 | Optional[Variant[Integer[1,22],Boolean]] $detection_frequency_hours = undef, 146 | Optional[Boolean] $disable_windows_update_access = undef, 147 | Optional[Boolean] $elevate_non_admins = undef, 148 | Optional[Boolean] $no_auto_reboot_with_logged_on_users = undef, 149 | Optional[Boolean] $no_auto_update = undef, 150 | Optional[Variant[Integer[1,1440],Boolean]] $reboot_relaunch_timeout_minutes = undef, 151 | Optional[Variant[Integer[1,30],Boolean]] $reboot_warning_timeout_minutes = undef, 152 | Optional[Variant[Integer[1,60],Boolean]] $reschedule_wait_time_minutes = undef, 153 | Optional[Variant[Enum['Everyday', 'Sunday', 'Monday', 'Tuesday', 'Wednesday','Thursday', 'Friday', 'Saturday'],Integer[0,7]]] 154 | $scheduled_install_day = undef, 155 | Optional[Variant[Integer[0,23],Boolean]] $scheduled_install_hour = undef, 156 | Optional[Boolean] $always_auto_reboot_at_scheduled_time = undef, 157 | Optional[Variant[Integer[15,180],Boolean]] $always_auto_reboot_at_scheduled_time_minutes = undef, 158 | Boolean $purge_values = false, 159 | Optional[Variant[String,Boolean]] $target_group = undef, 160 | ) { 161 | $_basekey = 'HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate' 162 | 163 | $_au_base = "${_basekey}\\AU" 164 | 165 | registry_key { $_basekey: 166 | ensure => present, 167 | purge_values => $purge_values, 168 | } 169 | 170 | registry_key { $_au_base: 171 | ensure => present, 172 | purge_values => $purge_values, 173 | } 174 | 175 | service { 'wuauserv': 176 | enable => true, 177 | } 178 | 179 | Registry_value { require => Registry_key[$_basekey], notify => Service['wuauserv'] } 180 | 181 | if ($server_url == undef or $server_url == false) and $enable_status_server { 182 | fail('server_url is required when specifying enable_status_server => true') 183 | } 184 | 185 | if $server_url != undef { 186 | wsus_client::setting { "${_au_base}\\UseWUServer": 187 | data => bool2num($server_url != false), 188 | has_enabled => false, 189 | } 190 | if $server_url { 191 | assert_type(Stdlib::HTTPUrl, $server_url) 192 | wsus_client::setting { "${_basekey}\\WUServer": 193 | type => 'string', 194 | data => $server_url, 195 | has_enabled => false, 196 | } 197 | if $enable_status_server != undef { 198 | assert_type(Boolean, $enable_status_server) 199 | $_ensure_status_server = $enable_status_server ? { 200 | true => 'present', 201 | false => 'absent', 202 | } 203 | wsus_client::setting { "${_basekey}\\WUStatusServer": 204 | ensure => $_ensure_status_server, 205 | type => 'string', 206 | data => $server_url, 207 | has_enabled => false, 208 | } 209 | } 210 | } 211 | } 212 | 213 | if $auto_update_option { 214 | $_parsed_auto_update_option = parse_auto_update_option($auto_update_option) 215 | if $_parsed_auto_update_option == 4 and !($scheduled_install_day and $scheduled_install_hour) { 216 | fail("scheduled_install_day and scheduled_install_hour required when specifying auto_update_option => '${auto_update_option}'") 217 | } 218 | wsus_client::setting { "${_au_base}\\AUOptions": 219 | data => $_parsed_auto_update_option, 220 | has_enabled => false, 221 | } 222 | } 223 | 224 | wsus_client::setting { "${_basekey}\\AcceptTrustedPublisherCerts": 225 | data => $accept_trusted_publisher_certs, 226 | has_enabled => false, 227 | validate_bool => true, 228 | } 229 | 230 | wsus_client::setting { "${_au_base}\\AutoInstallMinorUpdates": 231 | data => $auto_install_minor_updates, 232 | has_enabled => false, 233 | validate_bool => true, 234 | } 235 | 236 | wsus_client::setting { "${_au_base}\\DetectionFrequency": 237 | data => $detection_frequency_hours, 238 | validate_range => [1,22], 239 | } 240 | 241 | wsus_client::setting { "${_basekey}\\DisableWindowsUpdateAccess": 242 | data => $disable_windows_update_access, 243 | has_enabled => false, 244 | validate_bool => true, 245 | } 246 | 247 | wsus_client::setting { "${_basekey}\\ElevateNonAdmins": 248 | data => $elevate_non_admins, 249 | has_enabled => false, 250 | validate_bool => true, 251 | } 252 | 253 | wsus_client::setting { "${_au_base}\\NoAutoRebootWithLoggedOnUsers": 254 | data => $no_auto_reboot_with_logged_on_users, 255 | has_enabled => false, 256 | validate_bool => true, 257 | } 258 | 259 | wsus_client::setting { "${_au_base}\\NoAutoUpdate": 260 | data => $no_auto_update, 261 | has_enabled => false, 262 | validate_bool => true, 263 | } 264 | 265 | wsus_client::setting { "${_au_base}\\RebootRelaunchTimeout": 266 | data => $reboot_relaunch_timeout_minutes, 267 | validate_range => [1,1440], 268 | } 269 | 270 | wsus_client::setting { "${_au_base}\\RebootWarningTimeout": 271 | data => $reboot_warning_timeout_minutes, 272 | validate_range => [1,30], 273 | } 274 | 275 | wsus_client::setting { "${_au_base}\\RescheduleWaitTime": 276 | data => $reschedule_wait_time_minutes, 277 | validate_range => [1,60], 278 | } 279 | 280 | $_scheduled_install_day = $scheduled_install_day ? { 281 | true => true, 282 | false => false, 283 | /\w+|\d/ => parse_scheduled_install_day($scheduled_install_day), 284 | default => $scheduled_install_day 285 | } 286 | 287 | wsus_client::setting { "${_au_base}\\ScheduledInstallDay": 288 | data => $_scheduled_install_day, 289 | validate_range => [0,7], 290 | has_enabled => false, 291 | } 292 | 293 | wsus_client::setting { "${_au_base}\\ScheduledInstallTime": 294 | data => $scheduled_install_hour, 295 | validate_range => [0,23], 296 | has_enabled => false, 297 | } 298 | 299 | wsus_client::setting { "${_basekey}\\TargetGroup": 300 | type => 'string', 301 | data => $target_group, 302 | } 303 | 304 | wsus_client::setting { "${_au_base}\\AlwaysAutoRebootAtScheduledTime": 305 | data => $always_auto_reboot_at_scheduled_time, 306 | has_enabled => false, 307 | validate_bool => true, 308 | } 309 | wsus_client::setting { "${_au_base}\\AlwaysAutoRebootAtScheduledTimeMinutes": 310 | data => $always_auto_reboot_at_scheduled_time_minutes, 311 | validate_range => [15,180], 312 | has_enabled => false, 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-wsus_client", 3 | "version": "6.1.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": ">= 7.0.0 < 9.0.0" 37 | } 38 | ], 39 | "pdk-version": "3.0.0", 40 | "template-url": "https://github.com/puppetlabs/pdk-templates.git#main", 41 | "template-ref": "heads/main-0-g79a2f93" 42 | } 43 | -------------------------------------------------------------------------------- /pdk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: [] 3 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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', 'Scheduled'\], or Integer\[2, 5\]} } 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | 11 | expected_hash.each_key do |auto_update_option| 12 | describe "when parsing #{auto_update_option}" do 13 | it { 14 | expect(scope.function_parse_auto_update_option([auto_update_option])).to eq(expected_hash[auto_update_option]) 15 | } 16 | end 17 | 18 | describe "when parsing #{auto_update_option.upcase}" do 19 | it { 20 | expect(scope.function_parse_auto_update_option([auto_update_option.upcase])).to eq(expected_hash[auto_update_option]) 21 | } 22 | end 23 | 24 | describe "when parsing #{auto_update_option.downcase}" do 25 | it { 26 | expect(scope.function_parse_auto_update_option([auto_update_option.downcase])).to eq(expected_hash[auto_update_option]) 27 | } 28 | end 29 | end 30 | 31 | expected_hash.each_value do |auto_update_value| 32 | describe "when parsing #{auto_update_value}" do 33 | it { 34 | expect(scope.function_parse_auto_update_option([auto_update_value])).to eq(auto_update_value) 35 | } 36 | end 37 | end 38 | 39 | describe "when passing 'Whatthe'" do 40 | it 'raises error' do 41 | expect { 42 | scope.function_parse_auto_update_option(['Whatthe']) 43 | }.to raise_error(Puppet::Error, 44 | "Valid options for auto_update_option are #{expected_hash.keys.join('|')}|2|3|4|5, provided 'Whatthe'") 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | default_facts.merge!(YAML.safe_load(File.read(f), permitted_classes: [], permitted_symbols: [], aliases: true)) 29 | rescue StandardError => e 30 | RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" 31 | end 32 | end 33 | 34 | # read default_facts and merge them over what is provided by facterdb 35 | default_facts.each do |fact, value| 36 | add_custom_fact fact, value 37 | end 38 | 39 | RSpec.configure do |c| 40 | c.default_facts = default_facts 41 | c.before :each do 42 | # set to strictest setting for testing 43 | # by default Puppet runs at warning level 44 | Puppet.settings[:strict] = :warning 45 | Puppet.settings[:strict_variables] = true 46 | end 47 | c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] 48 | c.after(:suite) do 49 | RSpec::Puppet::Coverage.report!(0) 50 | end 51 | 52 | # Filter backtrace noise 53 | backtrace_exclusion_patterns = [ 54 | %r{spec_helper}, 55 | %r{gems}, 56 | ] 57 | 58 | if c.respond_to?(:backtrace_exclusion_patterns) 59 | c.backtrace_exclusion_patterns = backtrace_exclusion_patterns 60 | elsif c.respond_to?(:backtrace_clean_patterns) 61 | c.backtrace_clean_patterns = backtrace_exclusion_patterns 62 | end 63 | end 64 | 65 | # Ensures that a module is defined 66 | # @param module_name Name of the module 67 | def ensure_module_defined(module_name) 68 | module_name.split('::').reduce(Object) do |last_module, next_module| 69 | last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) 70 | last_module.const_get(next_module, false) 71 | end 72 | end 73 | 74 | # 'spec_overrides' from sync.yml will appear below this line 75 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------