├── .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 ├── MIGRATION.md ├── NOTICE ├── README.md ├── REFERENCE.md ├── Rakefile ├── data └── common.yaml ├── hiera.yaml ├── lib ├── facter │ └── iis_version.rb ├── puppet │ ├── feature │ │ └── iis_web_server.rb │ ├── provider │ │ ├── iis_application │ │ │ └── webadministration.rb │ │ ├── iis_application_pool │ │ │ └── webadministration.rb │ │ ├── iis_common.rb │ │ ├── iis_feature │ │ │ └── default.rb │ │ ├── iis_powershell.rb │ │ ├── iis_site │ │ │ ├── iisadministration.rb │ │ │ └── webadministration.rb │ │ ├── iis_virtual_directory │ │ │ └── webadministration.rb │ │ └── templates │ │ │ ├── iisadministration │ │ │ └── _getwebsites.ps1.erb │ │ │ └── webadministration │ │ │ ├── _getapppools.ps1.erb │ │ │ ├── _getvirtualdirectories.ps1.erb │ │ │ ├── _getwebsites.ps1.erb │ │ │ ├── _newwebsite.ps1.erb │ │ │ ├── _setwebsite.ps1.erb │ │ │ ├── bindingproperty.ps1.erb │ │ │ ├── generalproperties.ps1.erb │ │ │ ├── getapps.ps1.erb │ │ │ ├── json_1.7.ps1.erb │ │ │ ├── limitsproperty.ps1.erb │ │ │ ├── logproperties.ps1.erb │ │ │ ├── newapplication.ps1.erb │ │ │ ├── serviceautostartprovider.ps1.erb │ │ │ └── trysetitemproperty.ps1.erb │ └── type │ │ ├── iis_application.rb │ │ ├── iis_application_pool.rb │ │ ├── iis_feature.rb │ │ ├── iis_site.rb │ │ └── iis_virtual_directory.rb └── puppet_x │ ├── puppetlabs │ └── iis │ │ ├── bindings.rb │ │ ├── iis_features.rb │ │ ├── iis_version.rb │ │ └── property │ │ ├── authenticationinfo.rb │ │ ├── hash.rb │ │ ├── name.rb │ │ ├── path.rb │ │ ├── positive_integer.rb │ │ ├── read_only.rb │ │ ├── string.rb │ │ ├── timeformat.rb │ │ └── whole_number.rb │ └── templates │ └── iis │ └── init_ps.ps1 ├── metadata.json ├── pdk.yaml ├── provision.yaml └── spec ├── README.md ├── acceptance ├── iis_application_pool_spec.rb ├── iis_application_spec.rb ├── iis_feature_spec.rb ├── iis_minimal_config_spec.rb ├── iis_site_spec.rb ├── iis_unicode_site_spec.rb ├── iis_virtual_directory_spec.rb └── iis_virtual_directory_test_spec.rb ├── default_facts.yml ├── exit-27.ps1 ├── spec_helper.rb ├── spec_helper_acceptance.rb ├── spec_helper_acceptance_local.rb ├── spec_helper_local.rb ├── support ├── files │ └── www.puppet.local.pfx ├── matchers │ ├── puppet_resource_should_show.rb │ └── type_provider_interface.rb └── utilities │ ├── iis_application.rb │ ├── iis_application_pool.rb │ ├── iis_site.rb │ ├── iis_virtual_directory.rb │ └── utility_helper.rb └── unit ├── puppet ├── provider │ ├── iis_application │ │ └── webadministration_spec.rb │ ├── iis_application_pool │ │ └── webadministration_spec.rb │ ├── iis_powershell_spec.rb │ ├── iis_site │ │ └── webadministration_spec.rb │ └── iis_virtual_directory │ │ └── webadministration_spec.rb └── type │ ├── iis_application_pool_spec.rb │ ├── iis_application_spec.rb │ ├── iis_site_spec.rb │ └── iis_virtual_directory_spec.rb └── puppet_x └── puppetlabs └── iis └── iis_version_spec.rb /.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 | forge_modules: 3 | pwshlib: 'puppetlabs/pwshlib' 4 | ruby_task_helper: 'puppetlabs/ruby_task_helper' 5 | repositories: 6 | stdlib: 'https://github.com/puppetlabs/puppetlabs-stdlib.git' 7 | facts: 'https://github.com/puppetlabs/puppetlabs-facts.git' 8 | puppet_agent: 9 | repo: 'https://github.com/puppetlabs/puppetlabs-puppet_agent.git' 10 | ref: v4.13.0 11 | provision: 'https://github.com/puppetlabs/provision.git' 12 | symlinks: 13 | iis: "#{source_dir}" 14 | -------------------------------------------------------------------------------- /.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 | 18 | -------------------------------------------------------------------------------- /.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 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | -------------------------------------------------------------------------------- /.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.0.0:oSzfTkDf6Cmc1jOjgW33VA== 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 | /.vendor/ 23 | /convert_report.txt 24 | /update_report.txt 25 | .DS_Store 26 | .project 27 | .envrc 28 | /inventory.yaml 29 | /spec/fixtures/litmus_inventory.yaml 30 | .resource_types 31 | .modules 32 | .task_cache.json 33 | .plan_cache.json 34 | .rerun.json 35 | bolt-debug.log 36 | /.fixtures.yml 37 | /Gemfile 38 | /.gitattributes 39 | /.github/ 40 | /.gitignore 41 | /.pdkignore 42 | /.puppet-lint.rc 43 | /Rakefile 44 | /rakelib/ 45 | /.rspec 46 | /..yml 47 | /.yardopts 48 | /spec/ 49 | /.vscode/ 50 | /.sync.yml 51 | /.devcontainer/ 52 | -------------------------------------------------------------------------------- /.puppet-lint.rc: -------------------------------------------------------------------------------- 1 | --relative 2 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format documentation 3 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2023-11-29 05:16:15 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 | # This cop supports safe autocorrection (--autocorrect). 11 | Lint/RedundantCopDisableDirective: 12 | Exclude: 13 | - 'lib/puppet/provider/iis_feature/default.rb' 14 | 15 | # Offense count: 4 16 | RSpec/AnyInstance: 17 | Exclude: 18 | - 'spec/unit/puppet_x/puppetlabs/iis/iis_version_spec.rb' 19 | 20 | # Offense count: 16 21 | # This cop supports unsafe autocorrection (--autocorrect-all). 22 | RSpec/EmptyExampleGroup: 23 | Exclude: 24 | - 'spec/acceptance/iis_application_spec.rb' 25 | - 'spec/acceptance/iis_feature_spec.rb' 26 | - 'spec/acceptance/iis_minimal_config_spec.rb' 27 | - 'spec/acceptance/iis_site_spec.rb' 28 | - 'spec/acceptance/iis_virtual_directory_spec.rb' 29 | 30 | # Offense count: 1 31 | # Configuration parameters: AssignmentOnly. 32 | RSpec/InstanceVariable: 33 | Exclude: 34 | - 'spec/acceptance/iis_application_spec.rb' 35 | 36 | # Offense count: 12 37 | # This cop supports unsafe autocorrection (--autocorrect-all). 38 | # Configuration parameters: EnforcedStyle. 39 | # SupportedStyles: nested, compact 40 | Style/ClassAndModuleChildren: 41 | Exclude: 42 | - 'lib/puppet_x/puppetlabs/iis/bindings.rb' 43 | - 'lib/puppet_x/puppetlabs/iis/iis_features.rb' 44 | - 'lib/puppet_x/puppetlabs/iis/iis_version.rb' 45 | - 'lib/puppet_x/puppetlabs/iis/property/hash.rb' 46 | - 'lib/puppet_x/puppetlabs/iis/property/name.rb' 47 | - 'lib/puppet_x/puppetlabs/iis/property/path.rb' 48 | - 'lib/puppet_x/puppetlabs/iis/property/positive_integer.rb' 49 | - 'lib/puppet_x/puppetlabs/iis/property/read_only.rb' 50 | - 'lib/puppet_x/puppetlabs/iis/property/string.rb' 51 | - 'lib/puppet_x/puppetlabs/iis/property/timeformat.rb' 52 | - 'lib/puppet_x/puppetlabs/iis/property/whole_number.rb' 53 | -------------------------------------------------------------------------------- /.sync.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ".gitlab-ci.yml": 3 | delete: true 4 | ".rubocop.yml": 5 | include_todos: true 6 | appveyor.yml: 7 | delete: true 8 | 9 | Gemfile: 10 | use_travis: true 11 | optional: 12 | ":development": 13 | - gem: 'ruby-pwsh' 14 | Rakefile: 15 | unmanaged: true 16 | spec/spec_helper.rb: 17 | mock_with: ":rspec" 18 | coverage_report: true 19 | .github/workflows/auto_release.yml: 20 | unmanaged: false 21 | .github/workflows/ci.yml: 22 | unmanaged: false 23 | .github/workflows/nightly.yml: 24 | unmanaged: false 25 | .github/workflows/release.yml: 26 | unmanaged: false 27 | .travis.yml: 28 | delete: true 29 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "puppet.puppet-vscode", 4 | "Shopify.ruby-lsp" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown 2 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Setting ownership to the modules team 2 | * @puppetlabs/modules 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Puppet modules 2 | 3 | Check out our [Contributing to Supported Modules Blog Post](https://puppetlabs.github.io/iac/docs/contributing_to_a_module.html) to find all the information that you will need. 4 | -------------------------------------------------------------------------------- /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 "deep_merge", '~> 1.2.2', require: false 24 | gem "voxpupuli-puppet-lint-plugins", '~> 5.0', require: false 25 | gem "facterdb", '~> 2.1', require: false 26 | gem "metadata-json-lint", '~> 4.0', require: false 27 | gem "rspec-puppet-facts", '~> 4.0', require: false 28 | gem "dependency_checker", '~> 1.0.0', require: false 29 | gem "parallel_tests", '= 3.12.1', require: false 30 | gem "pry", '~> 0.10', require: false 31 | gem "simplecov-console", '~> 0.9', require: false 32 | gem "puppet-debugger", '~> 1.0', require: false 33 | gem "rubocop", '~> 1.50.0', require: false 34 | gem "rubocop-performance", '= 1.16.0', require: false 35 | gem "rubocop-rspec", '= 2.19.0', require: false 36 | gem "rb-readline", '= 0.5.5', require: false, platforms: [:mswin, :mingw, :x64_mingw] 37 | gem "rexml", '>= 3.3.9', require: false 38 | gem "ruby-pwsh", require: false 39 | end 40 | group :development, :release_prep do 41 | gem "puppet-strings", '~> 4.0', require: false 42 | gem "puppetlabs_spec_helper", '~> 7.0', require: false 43 | end 44 | group :system_tests do 45 | gem "puppet_litmus", '~> 1.0', require: false, platforms: [:ruby, :x64_mingw] 46 | gem "CFPropertyList", '< 3.0.7', require: false, platforms: [:mswin, :mingw, :x64_mingw] 47 | gem "serverspec", '~> 2.41', require: false 48 | end 49 | 50 | puppet_version = ENV['PUPPET_GEM_VERSION'] 51 | facter_version = ENV['FACTER_GEM_VERSION'] 52 | hiera_version = ENV['HIERA_GEM_VERSION'] 53 | 54 | gems = {} 55 | 56 | gems['puppet'] = location_for(puppet_version) 57 | 58 | # If facter or hiera versions have been specified via the environment 59 | # variables 60 | 61 | gems['facter'] = location_for(facter_version) if facter_version 62 | gems['hiera'] = location_for(hiera_version) if hiera_version 63 | 64 | gems.each do |gem_name, gem_params| 65 | gem gem_name, *gem_params 66 | end 67 | 68 | # Evaluate Gemfile.local and ~/.gemfile if they exist 69 | extra_gemfiles = [ 70 | "#{__FILE__}.local", 71 | File.join(Dir.home, '.gemfile'), 72 | ] 73 | 74 | extra_gemfiles.each do |gemfile| 75 | if File.file?(gemfile) && File.readable?(gemfile) 76 | eval(File.read(gemfile), binding) 77 | end 78 | end 79 | # vim: syntax=ruby 80 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | ## [v6.0.0](https://github.com/puppetlabs/puppetlabs-iis/tree/v6.0.0) (2019-10-30) 2 | 3 | **The 6.0.0 release only contains maintenance work and should have been 5.0.1.** 4 | 5 | [Full Changelog](https://github.com/puppetlabs/puppetlabs-iis/compare/v5.0.0...v6.0.0) 6 | 7 | ## v5.0.0 8 | 9 | ### Changed 10 | 11 | - Increase the named pipe timeout to 180 seconds to prevent runs from failing waiting for a pipe to open ([MODULES-9087](https://tickets.puppetlabs.com/browse/MODULES-9087)). 12 | - Update minimum Puppet version to 5.5.10 ([MODULES-9349](https://tickets.puppetlabs.com/browse/MODULES-9349)) 13 | 14 | ### Fixed 15 | 16 | - Ensure setting `iis_application_pool` state is idempotent ([MODULES-7700](https://tickets.puppetlabs.com/browse/MODULES-7700)). 17 | - Ensure setting `:managed_runtime_version` to `''` results in `iis_app_pool` being set to `No Managed Code` idempotently ([MODULES-7820](https://tickets.puppetlabs.com/browse/MODULES-7820)). 18 | - Ensure ability to specify timespans which include days, such as `1.05:00:00` ([MODULES-8381]. (https://tickets.puppetlabs.com/browse/MODULES-8381)). Thanks, Trey Dockendorf ([@treydock](https://github.com/treydock))! 19 | - Ensure iis_feature source property is used when provided in a manifest ([MODULES-8254](https://tickets.puppetlabs.com/browse/MODULES-8254)) 20 | 21 | ## [4.5.1] - 2019-03-02 22 | 23 | ### Added 24 | 25 | - Windows Server 2019 added to supported OS list ([FM-7693](https://tickets.puppetlabs.com/browse/FM-7693)) 26 | 27 | ### Fixed 28 | 29 | - Ensure removal of virtual directories is idempotent ([MODULES-6080](https://tickets.puppetlabs.com/browse/MODULES-6080)). 30 | - Case sensitive path comparisons ([MODULES-8346](https://tickets.puppetlabs.com/browse/MODULES-8346)) 31 | - Virtual directories did not correct config drift ([MODULES-6061](https://tickets.puppetlabs.com/browse/MODULES-6061)) 32 | 33 | ## [4.5.0] - 2018-10-23 34 | 35 | ### Fixed 36 | 37 | - `iis_application` cannot manage two applications with the same name under different web sites ([MODULES-5493](https://tickets.puppetlabs.com/browse/MODULES-5493)) 38 | - `applicationname` parameter cannot start with '/' character. Fixed as a by product of [MODULES-5493](https://tickets.puppetlabs.com/brows/MODULES-5493). 39 | - Removing an IIS feature using the module results in an error. ([MODULES-7174](https://tickets.puppetlabs.com/browse/MODULES-7174)). Thanks Brian Fekete ([@bFekete](https://github.com/bfekete)). 40 | 41 | ### Changed 42 | 43 | - The direction of slashes used in the title of an `iis_application` resource no longer matters. This is true both for the slash that separates the `sitename` portion of the title from the `applicationname` name, and also for the path separator used if the application path is nested deeply in folders under the web site. 44 | 45 | ## [4.4.0] - 2018-09-05 46 | 47 | ### Added 48 | 49 | - Added additional valid binding protocols for `iis_application`. ([MODULES-6947](https://tickets.puppetlabs.com/browse/MODULES-6947)). Thanks Pedro Cunha ([@Pedro-Cunha](https://github.com/Pedro-Cunha)). 50 | 51 | ### Fixed 52 | 53 | - Fixed password escaping for the `iis_application_pool` and `iis_virtual_directory` types. ([MODULES-6870](https://tickets.puppetlabs.com/browse/MODULES-6870)) 54 | 55 | ## [4.3.2] - 2018-06-13 56 | 57 | ### Fixed 58 | 59 | - `iis_website`, with a port binding of 443, does not start. ([MODULES-7173](https://tickets.puppetlabs.com/browse/MODULES-7173)) 60 | - Custom PowerShell host unreliable on some versions of Windows 2008 ([MODULES-6928](https://tickets.puppetlabs.com/browse/MODULES-6928)) 61 | 62 | ## [4.3.1] - 2018-03-22 63 | 64 | ### Fixed 65 | 66 | - `iis_website` causes state changes on each run when `ensure` property is set to `present`. ([MODULES-6673](https://tickets.puppetlabs.com/browse/MODULES-6673)) 67 | - `iis_website` has port conflict on create if only host name is different in binding information ([MODULES-6637](https://tickets.puppetlabs.com/browse/MODULES-6637)) 68 | - `iis_site` does not support `authenticationinfo` ([MODULES-5229](https://tickets.puppetlabs.com/browse/MODULES-5229)) 69 | 70 | ## [4.3.0] - 2018-01-26 71 | 72 | ### Added 73 | 74 | - Setting site limits for [iis_site](https://github.com/puppetlabs/puppetlabs-iis#limits) ([MODULES-6144](https://tickets.puppetlabs.com/browse/MODULES-6144)) 75 | 76 | ### Fixed 77 | 78 | - `iis_application` resource cannot manage applications in nested site folders ([MODULES-6257](https://tickets.puppetlabs.com/browse/MODULES-6257)) 79 | - Resources require a second run when iis feature is installed ([MODULES-5465](https://tickets.puppetlabs.com/browse/MODULES-5465)) 80 | - `iis_site` binds to port 80 regardless of binding override on first run ([MODULES-6385](https://tickets.puppetlabs.com/browse/MODULES-6385)) 81 | - Puppet resource `iis_virtual_directory` doesn't fail with a meaningful error when sitename is omitted ([MODULES-6166](https://tickets.puppetlabs.com/browse/MODULES-6166)) 82 | - PowerShell manager code was updated to use named pipes to match the improvements in the [puppetlabs-powershell](https://github.com/puppetlabs/puppetlabs-powershell) module. ([MODULES-6283](https://tickets.puppetlabs.com/browse/MODULES-6283)) 83 | 84 | ## [4.2.1] - 2017-12-01 85 | 86 | ### Added 87 | 88 | - Added support for user_name and password when using a UNC physicalpath with `iis_virtual_directory` ([MODULES-6195](https://tickets.puppetlabs.com/browse/MODULES-6195)) 89 | 90 | ### Fixed 91 | 92 | - IIS physicalpath regex doesn't match UNC paths ([MODULES-5264](https://tickets.puppetlabs.com/browse/MODULES-5264)) 93 | - IIS identity information is applied to application pool every agent run ([MODULES-5270](https://tickets.puppetlabs.com/browse/MODULES-5270)) 94 | - IIS virtual directory can't use UNC path ([MODULES-5642](https://tickets.puppetlabs.com/browse/MODULES-5642)) 95 | - IIS module remove warning already initialized constant ([MODULES-5954](https://tickets.puppetlabs.com/browse/MODULES-5954)) 96 | - IIS module cannot change application pool of existing `iis_application` ([MODULES-6020](https://tickets.puppetlabs.com/browse/MODULES-6020)) 97 | - IIS `iis_virtual_directory` calls update after destroy ([MODULES-6062](https://tickets.puppetlabs.com/browse/MODULES-6062)) 98 | - IIS `iis_site` applicationpool does not allow valid characters ([MODULES-6069](https://tickets.puppetlabs.com/browse/MODULES-6069)) 99 | 100 | ## [4.2.0] - 2017-11-10 101 | 102 | ### Added 103 | 104 | - Added support for IIS 10 (Server 2016) ([MODULES-5801](https://tickets.puppetlabs.com/browse/MODULES-5801)) 105 | - Added support for Server 2016 Core ([MODULES-5803](https://tickets.puppetlabs.com/browse/MODULES-5803)) 106 | - Added a GitHub Pull Request template to help community submissions 107 | 108 | ## [4.1.2] - 2017-11-04 109 | 110 | ### Fixed 111 | 112 | - Loosen restriction on names for `iis_site` ([MODULES-5293](https://tickets.puppetlabs.com/browse/MODULES-5293)) 113 | - Loosen restriction on name for `iis_application_pool` ([MODULES-5626](https://tickets.puppetlabs.com/browse/MODULES-5626)) 114 | - Loosen restriction on `iis_application` applicationname parameter ([MODULES-5627](https://tickets.puppetlabs.com/browse/MODULES-5627)) 115 | - Fix `iis_virtual_directory` idempotency ([MODULES-5344](https://tickets.puppetlabs.com/browse/MODULES-5344)) 116 | - Add support for net.pipe protocol to `iis_site` ([MODULES-5521](https://tickets.puppetlabs.com/browse/MODULES-5521)) 117 | 118 | ## [4.1.1] - 2017-09-26 119 | 120 | ### Added 121 | 122 | - Enabled `iis_site` preleoadenabled ([MODULES-5576](https://tickets.puppetlabs.com/browse/MODULES-5576)) 123 | - Added 'No Managed Code' value to managed_runtime_version in `iis_site` ([MODULES-5381](https://tickets.puppetlabs.com/browse/MODULES-5381)) 124 | 125 | ### Fixed 126 | 127 | - Allow valid characters in title and name for `iis_site` ([MODULES-5443](https://tickets.puppetlabs.com/browse/MODULES-5443)) 128 | 129 | ## [4.1.0] - 2017-08-18 130 | 131 | ### Added 132 | 133 | - Added ability to update physical path and application pool for sites ([MODULES-5125](https://tickets.puppetlabs.com/browse/MODULES-5125)) 134 | - Added testing of module on Puppet 5 ([MODULES-5187](https://tickets.puppetlabs.com/browse/MODULES-5187)) 135 | - Added more acceptance testing of Application Pool settings ([MODULES-5195](https://tickets.puppetlabs.com/browse/MODULES-5195)) 136 | - Added `iis_virtual_directory` to README ([MODULES-5433](https://tickets.puppetlabs.com/browse/MODULES-5433)) 137 | - Updated metadata to add support Puppet 5 ([MODULES-5144](https://tickets.puppetlabs.com/browse/MODULES-5144)) 138 | 139 | ### Fixed 140 | 141 | - Removed redundant ordering in README examples 142 | - Fixed various formatting issues in README ([MODULES-5433](https://tickets.puppetlabs.com/browse/MODULES-5433)) 143 | - Fixed certificate thumbprints to be case insensitive and handle nil values ([MODULES-5259](https://tickets.puppetlabs.com/browse/MODULES-5259)) 144 | - Fixed `iis_application_pool` settings not being idempotent ([MODULES-5169](https://tickets.puppetlabs.com/browse/MODULES-5169)) 145 | - Fixed `iis_site` settings not being idempotent ([MODULES-5429](https://tickets.puppetlabs.com/browse/MODULES-5429)) 146 | 147 | 148 | ## [4.0.0] - 2017-06-05 149 | 150 | ### Added 151 | 152 | - Added support for Windows Server 2008 R2 (IIS 7.5) ([MODULES-4484](https://tickets.puppetlabs.com/browse/MODULES-4484), [MODULES-4378](https://tickets.puppetlabs.com/browse/MODULES-4378)) 153 | - `iis_site` autorequires a `iis_application_pool` resource ([MODULES-4297](https://tickets.puppetlabs.com/browse/MODULES-4297)) 154 | - Added Types/Providers 155 | - `iis_application` ([MODULES-3050](https://tickets.puppetlabs.com/browse/MODULES-3050)) 156 | - `iis_virtual_directory` ([MODULES-3053](https://tickets.puppetlabs.com/browse/MODULES-3053)) 157 | - Added MIGRATION.md for migrating the IIS module from voxpupuli to puppetlabs 158 | 159 | ### Fixed 160 | 161 | - Fixed minor typo in the `iis_feature` provider 162 | - Fix error message for SSL settings on HTTP binding ([MODULES-4762](https://tickets.puppetlabs.com/browse/MODULES-4762)) 163 | - Update documentation for new types and providers ([MODULES-4752](https://tickets.puppetlabs.com/browse/MODULES-4752), [MODULES-4220](https://tickets.puppetlabs.com/browse/MODULES-4220), [MODULES-4564](https://tickets.puppetlabs.com/browse/MODULES-4564), [MODULES-4976](https://tickets.puppetlabs.com/browse/MODULES-4976)) 164 | - Fix testing the `iis_feature` provider ([MODULES-4818](https://tickets.puppetlabs.com/browse/MODULES-4818)) 165 | 166 | ### Removed 167 | 168 | - Removed the usage of APPCMD 169 | 170 | ## 0.1.0 - 2017-03-16 171 | 172 | ### Added 173 | 174 | - Added `iis_version` fact 175 | - Added Types/Providers 176 | - `iis_application_pool` ([MODULES-4185](https://tickets.puppetlabs.com/browse/MODULES-4185), [MODULES-4220](https://tickets.puppetlabs.com/browse/MODULES-4220)) 177 | - `iis_site` ([MODULES-3049](https://tickets.puppetlabs.com/browse/MODULES-3049), [MODULES-3887](https://tickets.puppetlabs.com/browse/MODULES-3887)) 178 | - `iis_feature` ([MODULES-4434](https://tickets.puppetlabs.com/browse/MODULES-4434)) 179 | 180 | [Unreleased]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.5.1...main 181 | [4.5.1]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.5.0...4.5.1 182 | [4.5.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.4.0...4.5.0 183 | [4.4.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.3.2...4.4.0 184 | [4.3.2]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.3.1...4.3.2 185 | [4.3.1]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.3.0...4.3.1 186 | [4.3.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.2.1...4.3.0 187 | [4.2.1]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.2.0...4.2.1 188 | [4.2.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.1.2...4.2.0 189 | [4.1.2]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.1.1...4.1.2 190 | [4.1.1]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.1.0...4.1.1 191 | [4.1.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/4.0.0...4.1.0 192 | [4.0.0]: https://github.com/puppetlabs/puppetlabs-iis/compare/0.1.0...4.0.0 193 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /MIGRATION.md: -------------------------------------------------------------------------------- 1 | # Migrating from puppet-iis to puppetlabs-iis 2 | 3 | When migrating from puppet-iis to puppetlabs-iis, most functionality can be directly mapped over as described below. Any functionality that is not directly mapped is described towards the end of the document. 4 | 5 | ## Mapped functionality from VP -> PL 6 | 7 | ### `iis_pool` 8 | 9 | This type is now called `iis_application_pool` 10 | 11 | #### Parameter mapping: 12 | 13 | puppet-iis | puppetlabs-iis 14 | --------------|-------------- 15 | ensure | identical 16 | name | identical 17 | enable_32_bit | enable32_bit_app_on_win64 18 | runtime | managed_runtime_version 19 | pipeline | managed_pipeline_mode 20 | 21 | ### `iis::manage_app_pool` 22 | 23 | All of these properties are managed directly by the `iis_application_pool` resource and are not declared separately, as the `iis_pool` and `iis::manage_app_pool` resources are. 24 | 25 | #### Parameter mapping: 26 | 27 | puppet-iis | puppetlabs-iis 28 | ---------------------------------|-------------- 29 | app_pool_name | not needed 30 | enable_32_bit | enable32_bit_app_on_win64 31 | managed_runtime_version | identical 32 | managed_pipeline_mode | identical 33 | ensure | identical 34 | start_mode | identical 35 | rapid_fail_protection | identical 36 | apppool_identitytype | identity_type 37 | apppool_username | user_name 38 | apppool_userpw | password 39 | apppool_idle_timeout_minutes | idle_timeout 40 | apppool_max_processes | max_processes 41 | apppool_max_queue_length | queue_length 42 | apppool_recycle_periodic_minutes | restart_time_limit 43 | apppool_recycle_schedule | restart_schedule 44 | apppool_recycle_logging | log_event_on_recycle 45 | apppool_idle_timeout_action | idle_timeout_action 46 | 47 | ### `iis_site` 48 | 49 | This type is still called `iis_site` 50 | 51 | #### Parameter mapping: 52 | 53 | puppet-iis | puppetlabs-iis 54 | ------------|-------------- 55 | ensure | identical 56 | name | identical 57 | path | physicalpath 58 | app_pool | applicationpool 59 | host_header | bindings hostname* 60 | protocol | bindings protocol* 61 | ip | bindings bindinginformation* 62 | port | bindings bindinginformation* 63 | ssl | bindings sslflags* 64 | 65 | \* The `bindings` parameter on the new `iis_site` takes an array of one or more bindings as a hash and has the following keys per hash: 66 | - protocol 67 | - bindinginformation 68 | - sslflags 69 | 70 | The `bindinginformation` value is in the form `:` 71 | 72 | ### `iis::manage_binding` 73 | 74 | All of these properties are managed directly by the `iis_site::bindings` property and are not declared separately, as the `iis_site` and `iis::manage_binding` resources are. The `iis_site::bindings` hash keys for the map are as follows: 75 | 76 | #### Parameter mapping: 77 | 78 | puppet-iis | puppetlabs-iis 79 | -----------------------|-------------- 80 | site_name | not needed 81 | ensure | not needed 82 | require_site | not needed 83 | protocol | identical 84 | host_header | hostname 85 | ip_address | first half of bindinginformation 86 | port | last half of bindinginformation 87 | certificate_thumbprint | certificatehash 88 | store | certificatestorename 89 | ssl_flag | sslflags 90 | 91 | ### `iis_application` 92 | 93 | This type is still called `iis_application` 94 | 95 | #### Parameter mapping: 96 | 97 | puppet-iis | puppetlabs-iis 98 | -----------|-------------- 99 | ensure | identical 100 | name | identical 101 | path | physicalpath 102 | site | sitename * 103 | app_pool | applicationpool 104 | 105 | \* `site` is a reserved word in puppet 4 and will cause failures, so `sitename` is the new name. 106 | 107 | ### `iis_virtualdirectory` 108 | 109 | This type is now called `iis_virtual_directory` 110 | 111 | #### Parameter mapping: 112 | 113 | puppet-iis | puppetlabs-iis 114 | -----------|-------------- 115 | ensure | identical 116 | name | identical 117 | path | physicalpath 118 | site | sitename * 119 | app_pool | applicationpool 120 | 121 | \* `site` is a reserved word in puppet 4 and will cause failures, so `sitename` is the new name. 122 | 123 | ## Unmapped functionality 124 | 125 | ### Class iis 126 | 127 | Declares six groups of features via `iis::features::*` to allow them to be easily installed. This functionality is available via `iis_feature` but must be explicitly declared. Please see README.md for information. 128 | 129 | The list of feature groups are: 130 | - application deployment 131 | - common http 132 | - health and diagnostics 133 | - management tools 134 | - performance 135 | - security 136 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Puppet Module - puppetlabs-iis 2 | 3 | Copyright 2017 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 | # iis 2 | 3 | #### Table of Contents 4 | 5 | 1. [Overview](#overview) 6 | 2. [Module Description](#module-description) 7 | 3. [Setup](#setup) 8 | * [Beginning with puppetlabs-iis](#beginning-with-puppetlabs-iis) 9 | 4. [Usage](#usage) 10 | 5. [Reference](#reference) 11 | 6. [Limitations](#limitations) 12 | 7. [License](#license) 13 | 8. [Development](#development) 14 | 15 | ## Description 16 | 17 | This module adds a provider to manage IIS sites and application pools. 18 | 19 | ## Setup 20 | 21 | ### Beginning with puppetlabs-iis 22 | 23 | This module can both manage and install IIS on your server. For example, a minimal IIS install can be accomplished by ensuring the `Web-WebServer` and `Web-Scripting-Tools` Windows Features are present. 24 | 25 | Here is an example that installs IIS and creates a web site using the default application pool. 26 | 27 | ```puppet 28 | $iis_features = ['Web-WebServer','Web-Scripting-Tools'] 29 | 30 | iis_feature { $iis_features: 31 | ensure => 'present', 32 | } 33 | 34 | # Delete the default website to prevent a port binding conflict. 35 | iis_site {'Default Web Site': 36 | ensure => absent, 37 | require => Iis_feature['Web-WebServer'], 38 | } 39 | 40 | iis_site { 'minimal': 41 | ensure => 'started', 42 | physicalpath => 'c:\\inetpub\\minimal', 43 | applicationpool => 'DefaultAppPool', 44 | require => [ 45 | File['minimal'], 46 | Iis_site['Default Web Site'] 47 | ], 48 | } 49 | 50 | file { 'minimal': 51 | ensure => 'directory', 52 | path => 'c:\\inetpub\\minimal', 53 | } 54 | ``` 55 | 56 | ## Usage 57 | 58 | This minimal example will create a web site named 'complete' using an application pool named 'minimal_site_app_pool'. 59 | 60 | ```puppet 61 | iis_application_pool { 'minimal_site_app_pool': 62 | ensure => 'present', 63 | state => 'started', 64 | managed_pipeline_mode => 'Integrated', 65 | managed_runtime_version => 'v4.0', 66 | } -> 67 | 68 | iis_site { 'minimal': 69 | ensure => 'started', 70 | physicalpath => 'c:\\inetpub\\minimal', 71 | applicationpool => 'minimal_site_app_pool', 72 | require => File['minimal'], 73 | } 74 | 75 | file { 'minimal': 76 | ensure => 'directory', 77 | path => 'c:\\inetpub\\minimal', 78 | } 79 | ``` 80 | 81 | This complete example will create a web site named 'complete' using an application pool named 'complete_site_app_pool', with a virtual directory named 'vdir'. This example uses the `puppetlabs-acl module` to set permissions on directories. 82 | 83 | ```puppet 84 | # Create Directories 85 | 86 | file { 'c:\\inetpub\\complete': 87 | ensure => 'directory' 88 | } 89 | 90 | file { 'c:\\inetpub\\complete_vdir': 91 | ensure => 'directory' 92 | } 93 | 94 | # Set Permissions 95 | 96 | acl { 'c:\\inetpub\\complete': 97 | permissions => [ 98 | {'identity' => 'IISCompleteGroup', 'rights' => ['read', 'execute']}, 99 | ], 100 | } 101 | 102 | acl { 'c:\\inetpub\\complete_vdir': 103 | permissions => [ 104 | {'identity' => 'IISCompleteGroup', 'rights' => ['read', 'execute']}, 105 | ], 106 | } 107 | 108 | # Configure IIS 109 | 110 | iis_application_pool { 'complete_site_app_pool': 111 | ensure => 'present', 112 | state => 'started', 113 | managed_pipeline_mode => 'Integrated', 114 | managed_runtime_version => 'v4.0', 115 | } 116 | 117 | # Application Pool No Managed Code .Net CLR Version set up 118 | iis_application_pool {'test_app_pool': 119 | ensure => 'present', 120 | enable32_bit_app_on_win64 => true, 121 | managed_runtime_version => '', 122 | managed_pipeline_mode => 'Classic', 123 | start_mode => 'AlwaysRunning' 124 | } 125 | 126 | iis_site { 'complete': 127 | ensure => 'started', 128 | physicalpath => 'c:\\inetpub\\complete', 129 | applicationpool => 'complete_site_app_pool', 130 | enabledprotocols => 'https', 131 | bindings => [ 132 | { 133 | 'bindinginformation' => '*:443:', 134 | 'protocol' => 'https', 135 | 'certificatehash' => '3598FAE5ADDB8BA32A061C5579829B359409856F', 136 | 'certificatestorename' => 'MY', 137 | 'sslflags' => 1, 138 | }, 139 | ], 140 | require => File['c:\\inetpub\\complete'], 141 | } 142 | 143 | iis_virtual_directory { 'vdir': 144 | ensure => 'present', 145 | sitename => 'complete', 146 | physicalpath => 'c:\\inetpub\\complete_vdir', 147 | require => File['c:\\inetpub\\complete_vdir'], 148 | } 149 | ``` 150 | ### Note about physicalpaths 151 | This module does **not support** physicalpaths that end with a forwardslash (`/`). As such, the module: 152 | - Will remove any forwardslashes at the end of a physicalpath found in the manifest. 153 | - Will remove any forwardslashes at the end of a physicalpath found in any existing resource. 154 | 155 | 156 | ## Reference 157 | 158 | For information on the classes and types, see the [REFERENCE.md](https://github.com/puppetlabs/puppetlabs-iis/blob/main/REFERENCE.md). 159 | 160 | ## Limitations 161 | 162 | ### Compatibility 163 | 164 | #### OS Compatibility 165 | 166 | This module is compatible only with `Windows Server 2008R2`, `Windows Server 2012`, `Windows Server 2012R2`, `Windows Server 2016`,`Windows Server 2016 Core`, `Windows Server 2019` and `Windows Server 2022`. 167 | 168 | #### IIS Compatibility 169 | 170 | This module only supports `IIS 7.5`, `IIS 8`, `IIS 8.5` or `IIS 10.0`. 171 | 172 | #### PowerShell Compatibility 173 | 174 | This module requires PowerShell v2 or greater. Works best with PowerShell v3 or above. 175 | 176 | ### Known Issues 177 | 178 | N/A 179 | 180 | ## Development 181 | 182 | If you would like to contribute to this module, please follow the rules in the [CONTRIBUTING.md](https://github.com/puppetlabs/puppetlabs-iis/blob/main/CONTRIBUTING.md). For more information, see our [module contribution guide.](https://puppet.com/docs/puppet/latest/contributing.html) 183 | 184 | ## License 185 | 186 | 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. 187 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'puppet_litmus/rake_tasks' if Bundler.rubygems.find_name('puppet_litmus').any? 2 | require 'puppetlabs_spec_helper/rake_tasks' 3 | require 'puppet-lint/tasks/puppet-lint' 4 | require 'puppet_blacksmith/rake_tasks' if Bundler.rubygems.find_name('puppet-blacksmith').any? 5 | require 'github_changelog_generator/task' if Bundler.rubygems.find_name('github_changelog_generator').any? 6 | require 'puppet-strings/tasks' if Bundler.rubygems.find_name('puppet-strings').any? 7 | 8 | PuppetLint.configuration.fail_on_warnings = true 9 | PuppetLint.configuration.send('relative') 10 | PuppetLint.configuration.send('disable_80chars') 11 | 12 | def changelog_user 13 | return unless Rake.application.top_level_tasks.include? "changelog" 14 | returnVal = nil || JSON.load(File.read('metadata.json'))['author'] 15 | raise "unable to find the changelog_user in .sync.yml, or the author in metadata.json" if returnVal.nil? 16 | puts "GitHubChangelogGenerator user:#{returnVal}" 17 | returnVal 18 | end 19 | 20 | def changelog_project 21 | return unless Rake.application.top_level_tasks.include? "changelog" 22 | returnVal = nil || JSON.load(File.read('metadata.json'))['source'].match(%r{.*/([^/]*)})[1] 23 | raise "unable to find the changelog_project in .sync.yml or the name in metadata.json" if returnVal.nil? 24 | puts "GitHubChangelogGenerator project:#{returnVal}" 25 | returnVal 26 | end 27 | 28 | def changelog_future_release 29 | return unless Rake.application.top_level_tasks.include? "changelog" 30 | returnVal = "v%s" % JSON.load(File.read('metadata.json'))['version'] 31 | raise "unable to find the future_release (version) in metadata.json" if returnVal.nil? 32 | puts "GitHubChangelogGenerator future_release:#{returnVal}" 33 | returnVal 34 | end 35 | 36 | PuppetLint.configuration.send('disable_relative') 37 | 38 | if Bundler.rubygems.find_name('github_changelog_generator').any? 39 | GitHubChangelogGenerator::RakeTask.new :changelog do |config| 40 | raise "Set CHANGELOG_GITHUB_TOKEN environment variable eg 'export CHANGELOG_GITHUB_TOKEN=valid_token_here'" if Rake.application.top_level_tasks.include? "changelog" and ENV['CHANGELOG_GITHUB_TOKEN'].nil? 41 | config.user = "#{changelog_user}" 42 | config.project = "#{changelog_project}" 43 | config.future_release = "#{changelog_future_release}" 44 | config.exclude_labels = ['maintenance'] 45 | config.header = "# Change log\n\nAll notable changes to this project will be documented in this file. 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)." 46 | config.add_pr_wo_labels = true 47 | config.issues = false 48 | config.merge_prefix = "### UNCATEGORIZED PRS; GO LABEL THEM" 49 | config.configure_sections = { 50 | "Changed" => { 51 | "prefix" => "### Changed", 52 | "labels" => ["backwards-incompatible"], 53 | }, 54 | "Added" => { 55 | "prefix" => "### Added", 56 | "labels" => ["feature", "enhancement"], 57 | }, 58 | "Fixed" => { 59 | "prefix" => "### Fixed", 60 | "labels" => ["bugfix"], 61 | }, 62 | } 63 | end 64 | else 65 | desc 'Generate a Changelog from GitHub' 66 | task :changelog do 67 | raise <= Gem::Version.new('2.2.2')" 78 | EOM 79 | end 80 | end 81 | 82 | desc 'Run serverspec against localhost, USE WITH CAUTION, this action can be potentially dangerous.' 83 | RSpec::Core::RakeTask.new(:test_suite_a) do |t| 84 | t.pattern = 'spec/acceptance/**{,/*/**}/*_spec.rb' 85 | t.rspec_opts = "--tag suite_a" 86 | ENV['TARGET_HOST'] = 'localhost' 87 | end 88 | desc 'Run serverspec against localhost, USE WITH CAUTION, this action can be potentially dangerous.' 89 | RSpec::Core::RakeTask.new(:test_suite_b) do |t| 90 | t.pattern = 'spec/acceptance/**{,/*/**}/*_spec.rb' 91 | t.rspec_opts = "--tag suite_b" 92 | ENV['TARGET_HOST'] = 'localhost' 93 | end 94 | -------------------------------------------------------------------------------- /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/facter/iis_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Facter.add('iis_version') do 4 | confine kernel: :windows 5 | setcode do 6 | require_relative '../puppet_x/puppetlabs/iis/iis_version' 7 | PuppetX::PuppetLabs::IIS::IISVersion.installed_version 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/puppet/feature/iis_web_server.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/util/feature' 4 | require_relative '../../puppet_x/puppetlabs/iis/iis_version' 5 | 6 | Puppet.features.add(:iis_web_server) do 7 | PuppetX::PuppetLabs::IIS::IISVersion.supported_version_installed? 8 | end 9 | 10 | Puppet.features.send :meta_def, 'iis_web_server?' do 11 | name = :iis_web_server 12 | @results[name] = PuppetX::PuppetLabs::IIS::IISVersion.supported_version_installed? unless @results[name] 13 | @results[name] 14 | end 15 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_application/webadministration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.join(File.dirname(__FILE__), '../../../puppet/provider/iis_powershell') 4 | 5 | Puppet::Type.type(:iis_application).provide(:webadministration, parent: Puppet::Provider::IIS_PowerShell) do 6 | desc 'IIS Application provider using the PowerShell WebAdministration module' 7 | 8 | confine feature: :pwshlib 9 | confine feature: :iis_web_server 10 | confine operatingsystem: [:windows] 11 | defaultfor operatingsystem: :windows 12 | 13 | def self.powershell_path 14 | require 'ruby-pwsh' 15 | Pwsh::Manager.powershell_path 16 | rescue LoadError 17 | nil 18 | end 19 | 20 | commands powershell: powershell_path 21 | 22 | mk_resource_methods 23 | 24 | def initialize(value = {}) 25 | super(value) 26 | @property_flush = {} 27 | end 28 | 29 | def physicalpath=(value) 30 | @property_flush[:physicalpath] = value 31 | end 32 | 33 | def sslflags=(value) 34 | @property_flush[:sslflags] = value 35 | end 36 | 37 | def authenticationinfo=(value) 38 | # Using property flush to find just the changed values, for speed 39 | @property_flush[:authenticationinfo] = value.select do |k, v| 40 | auth_info = authenticationinfo.is_a?(Hash) ? authenticationinfo : @resource[:authenticationinfo] 41 | auth_info.key?(k) && auth_info[k] != v 42 | end 43 | end 44 | 45 | def enabledprotocols=(value) 46 | @property_flush[:enabledprotocols] = value 47 | end 48 | 49 | def applicationpool=(value) 50 | @property_flush[:applicationpool] = value 51 | end 52 | 53 | def create 54 | check_paths 55 | if @resource[:virtual_directory] 56 | args = [] 57 | args << (@resource[:virtual_directory]).to_s 58 | args << "-ApplicationPool #{@resource[:applicationpool].inspect}" if @resource[:applicationpool] 59 | inst_cmd = "ConvertTo-WebApplication #{args.join(' ')} -Force -ErrorAction Stop" 60 | else 61 | inst_cmd = self.class.ps_script_content('newapplication', @resource) 62 | end 63 | result = self.class.run(inst_cmd) 64 | raise "Error creating application: #{result[:errormessage]}" unless (result[:exitcode]).zero? 65 | raise "Error creating application: #{result[:errormessage]}" unless result[:errormessage].nil? 66 | 67 | @property_hash[:ensure] = :present 68 | end 69 | 70 | def destroy 71 | inst_cmd = "Remove-WebApplication -Site \"#{self.class.find_sitename(resource)}\" -Name \"#{app_name}\" -ErrorAction Stop" 72 | result = self.class.run(inst_cmd) 73 | @property_hash.clear 74 | raise "Error destroying application: #{result[:errormessage]}" unless (result[:exitcode]).zero? 75 | raise "Error destroying application: #{result[:errormessage]}" unless result[:errormessage].nil? 76 | end 77 | 78 | def update 79 | check_paths 80 | inst_cmd = [] 81 | 82 | inst_cmd << "$webApplication = Get-WebApplication -Site '#{self.class.find_sitename(resource)}' -Name '#{app_name}'" 83 | if @property_flush[:physicalpath] 84 | # XXX Under what conditions would we have other paths? 85 | inst_cmd << %{Set-WebConfigurationProperty -Filter "$($webApplication.ItemXPath)/virtualDirectory[@path='/']" -Name physicalPath -Value '#{@resource[:physicalpath]}' -ErrorAction Stop} 86 | end 87 | 88 | if @property_flush[:sslflags] 89 | flags = @property_flush[:sslflags].join(',') 90 | inst_cmd << "Set-WebConfigurationProperty -Location '#{self.class.find_sitename(resource)}/#{app_name}' " \ 91 | "-Filter 'system.webserver/security/access' -Name 'sslFlags' -Value '#{flags}' -ErrorAction Stop" 92 | end 93 | 94 | @property_flush[:authenticationinfo]&.each do |auth, enable| 95 | if auth == 'forms' 96 | # Handle formsAuthentication separately 97 | mode_value = enable ? 'Forms' : 'None' 98 | # For Forms authentication, we need to set the mode value in the system.web section, not the system.webserver section 99 | # This is a workaround for the fact that the WebAdministration module does not support setting the mode value for Forms authentication 100 | # at the site level 101 | inst_cmd << "Set-WebConfigurationProperty -PSPath 'IIS:/Sites/#{self.class.find_sitename(resource)}/#{app_name}' " \ 102 | "-Filter 'system.web/authentication' -Name 'mode' -Value '#{mode_value}' -ErrorAction Stop" 103 | else 104 | # Handle other authentication types 105 | inst_cmd << "Set-WebConfigurationProperty -Location '#{self.class.find_sitename(resource)}/#{app_name}' " \ 106 | "-Filter 'system.webserver/security/authentication/#{auth}Authentication' -Name enabled -Value #{enable} -ErrorAction Stop" 107 | end 108 | end 109 | 110 | if @property_flush[:enabledprotocols] 111 | inst_cmd << "Set-WebConfigurationProperty -Filter 'system.applicationHost/sites/site[@name=\"#{self.class.find_sitename(resource)}\"]/application[@path=\"/#{app_name}\"]' " \ 112 | "-Name enabledProtocols -Value '#{@property_flush[:enabledprotocols]}'" 113 | end 114 | 115 | if @property_flush[:applicationpool] 116 | inst_cmd << "Set-ItemProperty -Path 'IIS:/Sites/#{self.class.find_sitename(resource)}/#{app_name}' -Name applicationPool -Value '#{resource[:applicationpool]}'" 117 | end 118 | 119 | inst_cmd = inst_cmd.join("\n") 120 | result = self.class.run(inst_cmd) 121 | raise "Error updating application: #{result[:errormessage]}" unless (result[:exitcode]).zero? 122 | raise "Error updating application: #{result[:errormessage]}" unless result[:errormessage].nil? 123 | end 124 | 125 | def self.prefetch(resources) 126 | apps = instances 127 | resources.each do |name, resource| 128 | if (provider = apps.find { |app| compare_app_names(app, resource) && app.sitename == find_sitename(resource) }) 129 | resources[name].provider = provider 130 | end 131 | end 132 | end 133 | 134 | def self.instances 135 | inst_cmd = ps_script_content('getapps', @resource) 136 | result = run(inst_cmd) 137 | return [] if result.nil? 138 | 139 | app_json = parse_json_result(result[:stdout]) 140 | return [] if app_json.nil? 141 | 142 | app_json = [app_json] if app_json.is_a?(Hash) 143 | app_json.map do |app| 144 | app_hash = {} 145 | 146 | app_hash[:ensure] = :present 147 | app_hash[:name] = "#{app['site']}\\#{app['name']}" 148 | app_hash[:applicationname] = "#{app['site']}\\#{app['name']}" # app['name'] 149 | app_hash[:sitename] = app['site'] 150 | app_hash[:physicalpath] = app['physicalpath'] 151 | app_hash[:applicationpool] = app['applicationpool'] 152 | app_hash[:sslflags] = app['sslflags'] 153 | app_hash[:authenticationinfo] = app['authenticationinfo'] 154 | app_hash[:enabledprotocols] = app['enabledprotocols'] 155 | 156 | new(app_hash) 157 | end 158 | end 159 | 160 | def self.compare_app_names(app, resource) 161 | app_appname = app.applicationname.split(%r{[\\/]}) - Array(app.sitename) 162 | resource_appname = resource[:applicationname].split(%r{[\\/]}).reject(&:empty?) - Array(find_sitename(resource)) 163 | app_appname == resource_appname 164 | end 165 | 166 | def self.find_sitename(resource) 167 | if resource.parameters.key?(:virtual_directory) 168 | resource[:virtual_directory].gsub('IIS:\\Sites\\', '').split(%r{[\\/]})[0] 169 | elsif !resource.parameters.key?(:sitename) 170 | resource[:applicationname].split(%r{[\\/]})[0] 171 | else 172 | resource[:sitename] 173 | end 174 | end 175 | 176 | def app_name 177 | name_segments = @resource[:applicationname].split(%r{[\\/]}) 178 | if (@resource[:sitename] && name_segments.count > 1 && name_segments[0] == @resource[:sitename]) || @resource[:sitename].nil? 179 | name_segments[1..].join('/') 180 | else 181 | name_segments.join('/') 182 | end 183 | end 184 | 185 | private 186 | 187 | def check_paths 188 | return unless @resource[:physicalpath] && !File.exist?(@resource[:physicalpath]) 189 | 190 | raise "physicalpath doesn't exist: #{@resource[:physicalpath]}" 191 | 192 | # XXX How do I check for IIS:\ path existence without shelling out to PS? 193 | # if @resource[:virtual_directory] and ! File.exists?(@resource[:virtual_directory]) 194 | # fail "virtual_directory doesn't exist: #{@resource[:virtual_directory]}" 195 | # end 196 | end 197 | end 198 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_common.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # verify if the given path is a local one 4 | def local_path?(path) 5 | (path =~ %r{^.:(/|\\)}) 6 | end 7 | 8 | # verify if the given path is an UNC one 9 | def unc_path?(path) 10 | (path =~ %r{^\\\\[^\\]+\\[^\\]+}) 11 | end 12 | 13 | # verify if the given path is a physicalpath 14 | def verify_physicalpath 15 | raise('physicalpath is a required parameter') if @resource[:physicalpath].nil? || @resource[:physicalpath].empty? 16 | 17 | return unless local_path?(@resource[:physicalpath]) 18 | return if File.exist?(@resource[:physicalpath]) 19 | 20 | raise("physicalpath doesn't exist: #{@resource[:physicalpath]}") 21 | end 22 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_feature/default.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.join(File.dirname(__FILE__), '../../../puppet/provider/iis_powershell') 4 | 5 | Puppet::Type.type(:iis_feature).provide(:default, parent: Puppet::Provider::IIS_PowerShell) do 6 | desc 'IIS feature provider' 7 | 8 | require Pathname.new(__FILE__).dirname + '..' + '..' + '..' + 'puppet_x' + 'puppetlabs' + 'iis' + 'iis_features' # rubocop:disable Style/StringConcatenation 9 | include PuppetX::IIS::Features 10 | 11 | confine feature: :pwshlib 12 | confine operatingsystem: [:windows] 13 | defaultfor operatingsystem: :windows 14 | 15 | def self.powershell_path 16 | require 'ruby-pwsh' 17 | Pwsh::Manager.powershell_path 18 | rescue LoadError 19 | nil 20 | end 21 | 22 | commands powershell: powershell_path 23 | 24 | mk_resource_methods 25 | 26 | def exists? 27 | @property_hash[:ensure] == :present 28 | end 29 | 30 | def create 31 | raise Puppet::Error, "iis_feature can only be used to install IIS features. '#{resource[:name]}' is not an IIS feature" unless PuppetX::IIS::Features.iis_feature?(resource[:name]) 32 | 33 | raise Puppet::Error, 'include_management_tools can only be used with Windows 2012 and above' if @resource[:include_management_tools] == true && self.class.windows2008? == true 34 | 35 | cmd = [] 36 | cmd << "Import-Module ServerManager; Add-WindowsFeature -Name '#{resource[:name]}'" if self.class.windows2008? == true 37 | cmd << "Install-WindowsFeature -Name '#{resource[:name]}' " if self.class.windows2008? == false 38 | cmd << '-IncludeAllSubFeature ' if @resource[:include_all_subfeatures] == true 39 | cmd << '-Restart ' if @resource[:restart] == true 40 | cmd << "-Source '#{resource['source']}' " if @resource[:source] 41 | cmd << '-IncludeManagementTools' if @resource[:include_management_tools] == true && self.class.windows2008? == false 42 | 43 | Puppet.debug "Powershell create command is '#{cmd}'" 44 | result = self.class.run(cmd.join) 45 | Puppet.debug "Powershell create response was '#{result}'" 46 | end 47 | 48 | def update 49 | Puppet.debug "Updating #{@resource[:name]}" 50 | end 51 | 52 | def destroy 53 | raise Puppet::Error, "iis_feature can only be used to install IIS features. '#{resource[:name]}' is not an IIS feature" unless PuppetX::IIS::Features.iis_feature?(resource[:name]) 54 | 55 | cmd = [] 56 | cmd << "Import-Module ServerManager; Remove-WindowsFeature -Name '#{resource[:name]}'" if self.class.windows2008? == true 57 | cmd << "Uninstall-WindowsFeature '#{resource[:name]}'" if self.class.windows2008? == false 58 | cmd << ' -Restart' if @resource[:restart] == true 59 | 60 | Puppet.debug "Powershell destroy command is '#{cmd}'" 61 | result = self.class.run(cmd.join) 62 | Puppet.debug "Powershell destroy response was '#{result}'" 63 | end 64 | 65 | def self.instances 66 | cmd = [] 67 | cmd << 'Import-Module ServerManager; ' if windows2008? == true 68 | cmd << 'Get-WindowsFeature | Sort Name | Select Name,Installed | ConvertTo-Json -Depth 4' 69 | 70 | result = run(cmd.join) 71 | 72 | return [] if result.nil? 73 | 74 | json = parse_json_result(result[:stdout]) 75 | return [] if json.nil? 76 | 77 | json.select { |feature| PuppetX::IIS::Features.iis_feature?(feature['Name']) }.map do |feature| 78 | feature_hash = { 79 | name: feature['Name'], 80 | ensure: (feature['Installed'] == true) ? :present : :absent 81 | } 82 | 83 | new(feature_hash) 84 | end 85 | end 86 | 87 | def self.prefetch(resources) 88 | features = instances 89 | resources.each_key do |name| 90 | if (provider = features.find { |feature| name.casecmp(feature.name).zero? }) 91 | resources[name].provider = provider 92 | end 93 | end 94 | end 95 | 96 | def self.windows2008? 97 | os_major_version == '6.1' 98 | end 99 | 100 | def self.os_major_version 101 | if @os_major_version.nil? 102 | version = Facter.value(:kernelmajversion) 103 | @os_major_version = version.nil? ? nil : version 104 | end 105 | @os_major_version 106 | end 107 | end 108 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_powershell.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | require 'ruby-pwsh' 5 | 6 | # This is the base class on which other providers are based. 7 | class Puppet::Provider::IIS_PowerShell < Puppet::Provider # rubocop:disable all 8 | def initialize(value = {}) 9 | super(value) 10 | @original_values = if value.is_a? Hash 11 | value.clone 12 | else 13 | {} 14 | end 15 | end 16 | 17 | # Match resources with existing providers 18 | def self.prefetch(resources) 19 | nodes = instances 20 | resources.each_key do |name| 21 | if (provider = nodes.find { |node| node.name == name }) 22 | resources[name].provider = provider 23 | end 24 | end 25 | end 26 | 27 | def exists? 28 | @property_hash[:ensure] == :present 29 | end 30 | 31 | # update if exists 32 | def flush 33 | return unless exists? 34 | 35 | update 36 | end 37 | 38 | # run command 39 | def self.run(command, _check: false) 40 | Puppet.debug("COMMAND: #{command}") 41 | 42 | result = ps_manager.execute(command) 43 | stderr = result[:stderr] 44 | 45 | stderr&.each do |er| 46 | er.each { |e| Puppet.debug "STDERR: #{e.chop}" } unless er.empty? 47 | end 48 | 49 | Puppet.debug "STDOUT: #{result[:stdout]}" unless result[:stdout].nil? 50 | Puppet.debug "ERRMSG: #{result[:errormessage]}" unless result[:errormessage].nil? 51 | 52 | result 53 | end 54 | 55 | # PowerShellManager - Responsible for managing PowerShell 56 | def self.ps_manager 57 | Pwsh::Manager.instance(command(:powershell), Pwsh::Manager.powershell_args) 58 | end 59 | 60 | # do_not_use_cached_value is typically only used for testing. In normal usage 61 | # the PowerShell version does not suddenly change during a Puppet run. 62 | def self.ps_major_version(do_not_use_cached_value: false) 63 | if @powershell_major_version.nil? || do_not_use_cached_value 64 | version = Pwsh::WindowsPowerShell.version 65 | @powershell_major_version = version.nil? ? nil : version.split('.').first.to_i 66 | end 67 | @powershell_major_version 68 | end 69 | 70 | # parse json result 71 | def self.parse_json_result(raw) 72 | return nil if raw.nil? 73 | 74 | # Unfortunately PowerShell tends to automatically insert CRLF characters mid-string (Console Width) 75 | # However as we're using JSON which does not use Line Endings for termination, we can safely strip them 76 | raw = raw.delete("\n").delete("\r") 77 | 78 | result = JSON.parse(raw) 79 | return nil if result.nil? 80 | 81 | # The JSON conversion for PowerShell 2.0 always creates a root HashTable with a single key of 'Objects' 82 | # whereas under PowerShell 3.0+ this is not the case. Detect the PowerShell 2.0 style and render it back 83 | # into a PowerShell 3.0+ format. 84 | if result.is_a?(Hash) && result.keys[0] == 'Objects' 85 | return nil if result['Objects'].nil? 86 | 87 | # Due to Convert-XML in PowerShell 2.0 converting elements with empty elements () into nulls, 88 | # need to be careful how things are processed e.g. 89 | # - An empty array comes in as nil 90 | # - A blank string comes in as nil 91 | # Only the provider will be able to determine what a nil value really means 92 | 93 | # If only a single object is returned then the result is Hash with a single 'Object' key 94 | # if multiple objects are returned then the result is an array of Hashes 95 | if result['Objects'].is_a?(Hash) && result['Objects'].keys[0] == 'Object' 96 | return [result['Objects']['Object']] 97 | elsif result['Objects'].is_a?(Array) 98 | return result['Objects'] 99 | else 100 | raise 'Unable to determine the JSON encoding from PowerShell 2.0' 101 | end 102 | end 103 | 104 | # Always return an Array type 105 | result.is_a?(Array) ? result : [result] 106 | end 107 | 108 | # powershell script content 109 | def self.ps_script_content(template, resource) 110 | @param_hash = resource 111 | template_path = File.expand_path('templates', __dir__) 112 | template_file = File.new(template_path + "/webadministration/#{template}.ps1.erb").read 113 | template = ERB.new(template_file, trim_mode: '-') 114 | template.result(binding) 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_site/iisadministration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pathname' 4 | 5 | Puppet::Type.type(:iis_site).provide(:iisadministration) do 6 | desc 'IIS Provider using the PowerShell IISAdministration module' 7 | 8 | confine feature: :pwshlib 9 | confine iis_version: ['10'] 10 | confine operatingsystem: [:windows] 11 | confine kernelmajversion: 10.0 12 | defaultfor operatingsystem: :windows 13 | 14 | def self.powershell_path 15 | require 'ruby-pwsh' 16 | Pwsh::Manager.powershell_path 17 | rescue LoadError 18 | nil 19 | end 20 | 21 | commands powershell: powershell_path 22 | 23 | mk_resource_methods 24 | 25 | def initialize(value = {}) 26 | super(value) 27 | @property_flush = {} 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_site/webadministration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.join(File.dirname(__FILE__), '../../../puppet/provider/iis_powershell') 4 | 5 | # When writing IIS PowerShell code for any of the methods below 6 | # NEVER EVER use Get-Website without specifying -Name. As the number 7 | # of sites on a server increases, Get-Website will take longer and longer 8 | # to return. This will exponentially increase the total duration of your 9 | # puppet run 10 | 11 | SETTINGS = ['name', 'physicalpath', 'applicationpool', 'hostheader', 'state', 'serverautostart', 'enabledprotocols', 12 | 'logformat', 'logpath', 'logperiod', 'logtruncatesize', 'loglocaltimerollover', 'logextfileflags'].freeze 13 | 14 | Puppet::Type.type(:iis_site).provide(:webadministration, parent: Puppet::Provider::IIS_PowerShell) do 15 | desc 'IIS Provider using the PowerShell WebAdministration module' 16 | 17 | confine feature: :pwshlib 18 | confine feature: :iis_web_server 19 | confine operatingsystem: [:windows] 20 | defaultfor operatingsystem: :windows 21 | 22 | def self.powershell_path 23 | require 'ruby-pwsh' 24 | Pwsh::Manager.powershell_path 25 | rescue LoadError 26 | nil 27 | end 28 | 29 | commands powershell: powershell_path 30 | 31 | mk_resource_methods 32 | 33 | def create 34 | cmd = [] 35 | 36 | cmd << self.class.ps_script_content('_newwebsite', @resource) 37 | 38 | inst_cmd = cmd.join 39 | 40 | result = self.class.run(inst_cmd) 41 | 42 | Puppet.err "Error creating website: #{result[:errormessage]}" unless (result[:exitcode]).zero? 43 | Puppet.err "Error creating website: #{result[:errormessage]}" unless result[:errormessage].nil? 44 | 45 | exists? 46 | end 47 | 48 | def update 49 | cmd = [] 50 | 51 | cmd << self.class.ps_script_content('_setwebsite', @resource) 52 | 53 | cmd << self.class.ps_script_content('trysetitemproperty', @resource) 54 | 55 | cmd << self.class.ps_script_content('generalproperties', @resource) 56 | 57 | cmd << self.class.ps_script_content('bindingproperty', @resource) 58 | 59 | cmd << self.class.ps_script_content('logproperties', @resource) 60 | 61 | cmd << self.class.ps_script_content('limitsproperty', @resource) 62 | 63 | cmd << self.class.ps_script_content('serviceautostartprovider', @resource) 64 | 65 | @resource[:authenticationinfo]&.each do |auth, enable| 66 | args = [] 67 | if auth == 'forms' 68 | # Handle formsAuthentication separately 69 | mode_value = enable ? 'Forms' : 'None' 70 | # For Forms authentication, we need to set the mode value 71 | # in the system.web section, not the system.webserver section 72 | args << "-Filter 'system.web/authentication'" 73 | 74 | # This is a workaround for the fact that the WebAdministration module 75 | # does not support setting the mode value for Forms authentication 76 | # at the site level 77 | args << "-PSPath 'IIS:\\Sites\\#{@resource[:name]}'" 78 | args << "-Name 'mode'" 79 | args << "-Value '#{mode_value}'" 80 | else 81 | # Handle other authentication types 82 | args << "-Filter 'system.webserver/security/authentication/#{auth}Authentication'" 83 | args << "-PSPath 'IIS:\\'" 84 | args << "-Location '#{@resource[:name]}'" 85 | args << '-Name enabled' 86 | args << "-Value #{enable}" 87 | end 88 | cmd << "Set-WebConfigurationProperty #{args.join(' ')} -ErrorAction Stop\n" 89 | end 90 | 91 | inst_cmd = cmd.join 92 | 93 | result = self.class.run(inst_cmd) 94 | 95 | Puppet.err "Error updating website: #{result[:errormessage]}" unless (result[:exitcode]).zero? 96 | Puppet.err "Error updating website: #{result[:errormessage]}" unless result[:errormessage].nil? 97 | 98 | exists? 99 | end 100 | 101 | def destroy 102 | inst_cmd = "Remove-Website -Name \"#{@resource[:name]}\" -ErrorAction Stop" 103 | result = self.class.run(inst_cmd) 104 | Puppet.err "Error destroying website: #{result[:errormessage]}" unless (result[:exitcode]).zero? 105 | Puppet.err "Error destroying website: #{result[:errormessage]}" unless result[:errormessage].nil? 106 | exists? 107 | end 108 | 109 | def exists? 110 | inst_cmd = "If (Test-Path -Path 'IIS:\\sites\\#{@resource[:name]}') { exit 0 } else { exit 255 }" 111 | 112 | result = self.class.run(inst_cmd) 113 | 114 | (result[:exitcode]).zero? 115 | end 116 | 117 | def start 118 | create unless exists? 119 | 120 | inst_cmd = "Start-Website -Name \"#{@resource[:name]}\" -ErrorVariable errvar;if($errvar){ throw \"$($errvar). Perhaps there is another website with this port or configuration setting\" }" 121 | result = self.class.run(inst_cmd) 122 | 123 | raise "Error starting website: #{result[:errormessage]}" unless result[:errormessage].nil? || (result[:exitcode]).zero? 124 | 125 | true 126 | end 127 | 128 | def stop 129 | create unless exists? 130 | 131 | inst_cmd = "Stop-Website -Name \"#{@resource[:name]}\" -ErrorVariable errvar;if($errvar){ throw \"$($errvar).\" }" 132 | result = self.class.run(inst_cmd) 133 | 134 | raise "Error stopping website: #{result[:errormessage]}" unless result[:errormessage].nil? || (result[:exitcode]).zero? 135 | 136 | true 137 | end 138 | 139 | def initialize(value = {}) 140 | super(value) 141 | @property_flush = {} 142 | end 143 | 144 | def self.prefetch(resources) 145 | sites = instances 146 | resources.each_key do |site| 147 | next unless !sites.nil? && (provider = sites.find { |s| s.name == site }) 148 | 149 | resources[site]['authenticationinfo'] = provider.authenticationinfo.merge(resources[site]['authenticationinfo']) unless resources[site]['authenticationinfo'].nil? 150 | resources[site].provider = provider 151 | end 152 | end 153 | 154 | def self.instances 155 | inst_cmd = ps_script_content('_getwebsites', @resource) 156 | result = run(inst_cmd) 157 | return [] if result.nil? 158 | 159 | site_json = parse_json_result(result[:stdout]) 160 | return [] if site_json.nil? 161 | 162 | site_json.map do |site| 163 | site_hash = {} 164 | 165 | # Convert nil's to empty strings for all properties which we know are String types 166 | SETTINGS.each do |setting| 167 | site[setting] = '' if site[setting].nil? 168 | end 169 | site['bindings'] = [] if site['bindings'].nil? 170 | site['bindings'].each do |binding| 171 | # "The sslFlags attribute is only set when the protocol is https." 172 | binding.delete('sslflags') unless binding['protocol'] == 'https' 173 | # "The CertificateHash property is available only when the protocol 174 | # identifier defined by the Protocol property is "https". An attempt to 175 | # get or set the CertificateHash property for a binding with a protocol 176 | # of "http" will raise an error." 177 | binding.delete('certificatehash') unless binding['protocol'] == 'https' 178 | binding.delete('certificatestorename') unless binding['protocol'] == 'https' 179 | binding['certificatestorename'] = binding['certificatestorename'].upcase unless binding['certificatestorename'].nil? 180 | end 181 | site['limits'] = {} if site['limits'].nil? 182 | site['authenticationinfo'] = {} if site['authenticationinfo'].nil? 183 | 184 | site_hash[:ensure] = site['state'].downcase 185 | site_hash[:name] = site['name'] 186 | site_hash[:physicalpath] = site['physicalpath'] 187 | site_hash[:applicationpool] = site['applicationpool'] 188 | site_hash[:serverautostart] = to_bool(site['serverautostart']) 189 | site_hash[:enabledprotocols] = site['enabledprotocols'] 190 | site_hash[:bindings] = site['bindings'] 191 | site_hash[:limits] = site['limits'] 192 | site_hash[:logpath] = site['logpath'] 193 | site_hash[:logperiod] = site['logperiod'] 194 | site_hash[:logtruncatesize] = site['logtruncatesize'] 195 | site_hash[:loglocaltimerollover] = to_bool(site['loglocaltimerollover']) 196 | site_hash[:logformat] = site['logformat'] 197 | site_hash[:logflags] = site['logextfileflags'].split(%r{,\s*}).sort 198 | site_hash[:preloadenabled] = to_bool(site['preloadenabled']) unless site['preloadenabled'].nil? 199 | site_hash[:authenticationinfo] = site['authenticationinfo'] 200 | 201 | new(site_hash) 202 | end 203 | end 204 | 205 | def self.to_bool(value) 206 | return :true if value == true || value =~ %r{(true|t|yes|y|1)$}i 207 | return :false if value == false || value =~ %r{(^$|false|f|no|n|0)$}i 208 | 209 | raise ArgumentError, "invalid value for Boolean: \"#{value}\"" 210 | end 211 | 212 | def binding_information 213 | return unless @resource[:bindings] && ['http', 'https'].include?(@resource['bindings'].first['protocol']) 214 | 215 | binding = @resource[:bindings].first 216 | matches = binding['bindinginformation'].match(%r{^(?.+):(?\d*):(?(.*))}) 217 | [matches[:ip_dns], matches[:port], matches[:host_header]] 218 | end 219 | 220 | def ssl? 221 | bindings_ssl = !resource[:bindings].find { |x| x['protocol'] == 'https' }.nil? unless resource[:bindings].nil? 222 | port443 = binding_information[1] == '443' unless binding_information.nil? 223 | 224 | bindings_ssl || port443 225 | end 226 | end 227 | -------------------------------------------------------------------------------- /lib/puppet/provider/iis_virtual_directory/webadministration.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require File.join(File.dirname(__FILE__), '../../../puppet/provider/iis_common') 4 | require File.join(File.dirname(__FILE__), '../../../puppet/provider/iis_powershell') 5 | 6 | Puppet::Type.type(:iis_virtual_directory).provide(:webadministration, parent: Puppet::Provider::IIS_PowerShell) do 7 | desc 'IIS Virtual Directory provider using the PowerShell WebAdministration module' 8 | 9 | confine feature: :pwshlib 10 | confine feature: :iis_web_server 11 | confine operatingsystem: [:windows] 12 | defaultfor operatingsystem: :windows 13 | 14 | def self.powershell_path 15 | require 'ruby-pwsh' 16 | Pwsh::Manager.powershell_path 17 | rescue LoadError 18 | nil 19 | end 20 | 21 | commands powershell: powershell_path 22 | 23 | mk_resource_methods 24 | 25 | def exists? 26 | @property_hash[:ensure] == :present 27 | end 28 | 29 | def create 30 | Puppet.debug "Creating #{@resource[:name]}" 31 | 32 | verify_physicalpath 33 | 34 | cmd = [] 35 | if local_path?(@resource[:physicalpath]) 36 | cmd << "New-WebVirtualDirectory -Name \"#{@resource[:name]}\" " 37 | raise('sitename is a required parameter') unless @resource[:sitename] 38 | 39 | cmd << "-Site \"#{@resource[:sitename]}\" " 40 | else 41 | # New-WebVirtualDirectory fails when PhysicalPath is a UNC path that unavailable, 42 | # and UNC paths are inherently not necessarily always available. 43 | cmd << "New-Item -Type VirtualDirectory 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' " 44 | end 45 | cmd << "-Application \"#{@resource[:application]}\" " if @resource[:application] 46 | cmd << "-PhysicalPath \"#{@resource[:physicalpath]}\" " if @resource[:physicalpath] 47 | cmd << '-ErrorAction Stop;' 48 | if @resource[:user_name] 49 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' " \ 50 | "-Name 'userName' -Value '#{@resource[:user_name]}' -ErrorAction Stop;" 51 | end 52 | if @resource[:password] 53 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' " \ 54 | "-Name 'password' -Value '#{escape_string(@resource[:password])}' -ErrorAction Stop;" 55 | end 56 | cmd = cmd.join 57 | 58 | result = self.class.run(cmd) 59 | Puppet.err "Error creating virtual directory: #{result[:errormessage]}" unless (result[:exitcode]).zero? 60 | @resource[:ensure] = :present 61 | end 62 | 63 | def update 64 | Puppet.debug "Updating #{@resource[:name]}" 65 | 66 | verify_physicalpath 67 | 68 | cmd = [] 69 | 70 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' -Name 'physicalPath' -Value '#{@resource[:physicalpath]}';" if @resource[:physicalpath] 71 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' -Name 'application' -Value '#{@resource[:application]}';" if @resource[:application] 72 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' -Name 'userName' -Value '#{@resource[:user_name]}';" if @resource[:user_name] 73 | cmd << "Set-ItemProperty -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' -Name 'password' -Value '#{escape_string(@resource[:password])}';" if @resource[:password] 74 | 75 | cmd = cmd.join 76 | result = self.class.run(cmd) 77 | Puppet.err "Error updating virtual directory: #{result[:errormessage]}" unless (result[:exitcode]).zero? 78 | end 79 | 80 | def destroy 81 | Puppet.debug "Destroying #{@resource[:name]}" 82 | test = self.class.run("Test-Path -Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}'") 83 | if test[:stdout].strip.casecmp('true').zero? 84 | cmd = [] 85 | cmd << 'Remove-Item ' 86 | cmd << "-Path 'IIS:\\Sites\\#{virt_dir_path(@resource[:sitename], @resource[:name])}' " 87 | cmd << '-Recurse ' 88 | cmd << '-ErrorAction Stop ' 89 | cmd = cmd.join 90 | 91 | result = self.class.run(cmd) 92 | Puppet.err "Error destroying virtual directory: #{result[:errormessage]}" unless (result[:exitcode]).zero? 93 | end 94 | @property_hash[:ensure] = :absent 95 | end 96 | 97 | def initialize(value = {}) 98 | super(value) 99 | @property_flush = {} 100 | end 101 | 102 | def self.prefetch(resources) 103 | virt_dirs = instances 104 | resources.each_key do |virt_dir| 105 | if (provider = virt_dirs.find { |s| virt_dir.casecmp(s.name).zero? }) 106 | resources[virt_dir].provider = provider 107 | end 108 | end 109 | end 110 | 111 | def self.instances 112 | cmd = ps_script_content('_getvirtualdirectories', @resource) 113 | result = run(cmd) 114 | return [] if result.nil? 115 | 116 | virt_dir_json = parse_json_result(result[:stdout]) 117 | return [] if virt_dir_json.nil? 118 | 119 | virt_dir_json.map do |virt_dir| 120 | virt_dir_hash = {} 121 | 122 | virt_dir_hash[:ensure] = :present 123 | virt_dir_hash[:name] = virt_dir['name'] 124 | virt_dir_hash[:physicalpath] = virt_dir['physicalpath'] 125 | virt_dir_hash[:user_name] = virt_dir['user_name'] 126 | virt_dir_hash[:password] = virt_dir['password'] 127 | virt_dir_hash[:application] = virt_dir['application'] 128 | virt_dir_hash[:sitename] = virt_dir['sitename'] 129 | 130 | new(virt_dir_hash) 131 | end 132 | end 133 | 134 | def virt_dir_path(sitename, name) 135 | @cached_virt_dir_path ||= {} 136 | 137 | key = "#{sitename}/#{name}" 138 | @cached_virt_dir_path[key] ||= begin 139 | parts = name.tr('/', '\\').split('\\') 140 | parts.shift if parts.first.casecmp?(sitename) 141 | normalized_name = parts.join('\\') 142 | "#{sitename}\\#{normalized_name}" 143 | end 144 | end 145 | 146 | def escape_string(value) 147 | value.gsub("'", "''") 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/iisadministration/_getwebsites.ps1.erb: -------------------------------------------------------------------------------- 1 | Get-IISSite | %{ 2 | New-Object -TypeName PSObject -Property @{ 3 | name = [string]$_.Name 4 | path = [string]$_.PhysicalPath 5 | applicationpool = [string]$_.ApplicationPool 6 | hostheader = [string]$_.HostHeader 7 | state = [string]$_.State 8 | serverautostart = [string]$_.serverautostart 9 | enabledprotocols = [string]$_.enabledprotocols 10 | bindings = @($_.Bindings.Collection | %{ 11 | New-Object -TypeName PSObject -Property @{ 12 | protocol = [string]$_.protocol 13 | bindinginformation = [string]$_.bindingInformation 14 | sslflags = [string]$_.sslFlags 15 | certificatehash = [string]$_.certificateHash 16 | certificatestorename = [string]$_.certificateStoreName 17 | } 18 | }) 19 | limits = New-Object -TypeName PSObject -Property @{ 20 | maxbandwidth = [int64]$_.limits.maxbandwidth 21 | maxconnections = [int64]$_.limits.maxconnections 22 | connectiontimeout = [int]$_.limits.connectiontimeout.totalseconds 23 | } 24 | logformat = [string]$_.LogFile.logFormat 25 | logpath = [string]$_.LogFile.directory 26 | logperiod = [string]$_.LogFile.period 27 | logtruncatesize = [string]$_.LogFile.truncateSize 28 | loglocaltimerollover = [string]$_.LogFile.localTimeRollover 29 | logextfileflags = [string]$_.LogFile.logExtFileFlags 30 | } 31 | } | ConvertTo-Json -Depth 10 32 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/_getapppools.ps1.erb: -------------------------------------------------------------------------------- 1 | Get-WebConfiguration -Filter '/system.applicationHost/applicationPools/add' | % { 2 | New-Object -TypeName PSObject -Property @{ 3 | name = [string]$_.name 4 | state = [string]$_.state 5 | 6 | auto_start = [string]$_.autostart 7 | clr_config_file = [string]$_.clrconfigfile 8 | enable32_bit_app_on_win64 = [string]$_.enable32bitapponwin64 9 | enable_configuration_override = [string]$_.enableconfigurationoverride 10 | managed_pipeline_mode = [string]$_.managedpipelinemode 11 | managed_runtime_loader = [string]$_.managedruntimeloader 12 | managed_runtime_version = [string]$_.managedruntimeversion 13 | pass_anonymous_token = [string]$_.passanonymoustoken 14 | start_mode = [string]$_.startmode 15 | queue_length = [string]$_.queuelength 16 | 17 | cpu_action = [string]$_.cpu.action 18 | cpu_limit = [string]$_.cpu.limit 19 | cpu_reset_interval = [string]$_.cpu.resetinterval 20 | cpu_smp_affinitized = [string]$_.cpu.smpaffinitized 21 | cpu_smp_processor_affinity_mask = [string]$_.cpu.smpprocessoraffinitymask 22 | cpu_smp_processor_affinity_mask2 = [string]$_.cpu.smpprocessoraffinitymask2 23 | 24 | identity_type = [string]$_.processmodel.identityType 25 | idle_timeout = [string]$_.processmodel.idleTimeout 26 | idle_timeout_action = [string]$_.processmodel.idleTimeoutAction 27 | load_user_profile = [string]$_.processmodel.loadUserProfile 28 | log_event_on_process_model = [string]$_.processmodel.logEventOnProcessModel 29 | logon_type = [string]$_.processmodel.logonType 30 | manual_group_membership = [string]$_.processmodel.manualGroupMembership 31 | max_processes = [string]$_.processmodel.maxProcesses 32 | pinging_enabled = [string]$_.processmodel.pingingEnabled 33 | ping_interval = [string]$_.processmodel.pingInterval 34 | ping_response_time = [string]$_.processmodel.pingResponseTime 35 | set_profile_environment = [string]$_.processmodel.setProfileEnvironment 36 | shutdown_time_limit = [string]$_.processmodel.shutdownTimeLimit 37 | startup_time_limit = [string]$_.processmodel.startupTimeLimit 38 | user_name = [string]$_.processmodel.username 39 | password = [string]$_.processmodel.password 40 | 41 | orphan_action_exe = [string]$_.failure.orphanactionexe 42 | orphan_action_params = [string]$_.failure.orphanactionparams 43 | orphan_worker_process = [string]$_.failure.orphanworkerprocess 44 | load_balancer_capabilities = [string]$_.failure.loadbalancercapabilities 45 | rapid_fail_protection = [string]$_.failure.rapidfailprotection 46 | rapid_fail_protection_interval = [string]$_.failure.rapidfailprotectioninterval 47 | rapid_fail_protection_max_crashes = [string]$_.failure.rapidfailprotectionmaxcrashes 48 | auto_shutdown_exe = [string]$_.failure.autoshutdownexe 49 | auto_shutdown_params = [string]$_.failure.autoshutdownparams 50 | 51 | disallow_overlapping_rotation = [string]$_.recycling.disallowoverlappingrotation 52 | disallow_rotation_on_config_change = [string]$_.recycling.disallowrotationonconfigchange 53 | log_event_on_recycle = [string]$_.recycling.logeventonrecycle 54 | restart_memory_limit = [string]$_.recycling.periodicrestart.memory 55 | restart_private_memory_limit = [string]$_.recycling.periodicrestart.privatememory 56 | restart_requests_limit = [string]$_.recycling.periodicrestart.requests 57 | restart_time_limit = [string]$_.recycling.periodicrestart.time 58 | restart_schedule = [string]$_.recycling.periodicrestart.schedule.collection.value 59 | } 60 | } | ConvertTo-Json -Depth 10 61 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/_getvirtualdirectories.ps1.erb: -------------------------------------------------------------------------------- 1 | Get-WebVirtualDirectory | ForEach-Object { 2 | $physicalpath = [string]$_.PhysicalPath 3 | $user_name = [string]$_.userName 4 | $password = [string]$_.password 5 | 6 | $name = [string]$_.Path 7 | $name = $name -Replace "^/", '' 8 | $name = $name -Replace "/", '\' 9 | 10 | if ($_.ItemXPath -Match "application\[\@path\=(.*?)\]") { 11 | $application = $matches[1].Replace("'", '') 12 | } else { 13 | $application = '/' 14 | } 15 | 16 | if ($_.ItemXPath -Match "site\[\@name\=(.*?) and") { 17 | $sitename = $matches[1].Replace("'", '') 18 | } else { 19 | $sitename = '' 20 | } 21 | 22 | New-Object -TypeName PSObject -Property @{ 23 | name = $name 24 | physicalpath = $physicalpath 25 | user_name = $user_name 26 | password = $password 27 | application = $application 28 | sitename = $sitename 29 | } 30 | 31 | } | ConvertTo-Json -Depth 10 32 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/_getwebsites.ps1.erb: -------------------------------------------------------------------------------- 1 | $iis_version = [Double]'<%= Facter.value(:iis_version) %>' 2 | 3 | Get-WebSite | % { 4 | $name = $_.Name 5 | 6 | if ($iis_version -gt 7.5) { 7 | $preloadenabled = [string](Get-ItemProperty -Path "IIS:\Sites\$($name)" -Name 'applicationDefaults.preloadEnabled' -ErrorAction 'Continue').Value 8 | } 9 | 10 | $SiteEntityPath = "IIS:\\Sites\${name}" 11 | $SiteEntityPhysicalPath = Get-ItemProperty -Path $SiteEntityPath -Name physicalPath 12 | 13 | if ($SiteEntityPhysicalPath.EndsWith('/')){ 14 | $null = Set-ItemProperty -Path $SiteEntityPath -Name PhysicalPath -Value $SiteEntityPhysicalPath.TrimEnd('/') -Force 15 | } 16 | 17 | $authenticationTypes = @( 18 | 'anonymous', 19 | 'basic', 20 | 'clientCertificateMapping', 21 | 'digest', 22 | 'iisClientCertificateMapping', 23 | 'windows', 24 | 'forms' 25 | ) 26 | $authenticationTypes | Foreach-Object -Begin { $info = @{} } -Process { 27 | if ($_ -eq 'forms') { 28 | # Special handling for formsAuthentication 29 | $p = Get-WebConfigurationProperty -Filter "system.web/authentication" -Name "mode" -PSPath "IIS:\Sites\$($name)" -ErrorAction SilentlyContinue 30 | $info["$($_)"] = if ($p -eq 'Forms') { $true } else { $false } 31 | } else { 32 | # Handle other authentication types 33 | $p = Get-WebConfiguration -Filter "system.webserver/security/authentication/$($_)Authentication" -PSPath "IIS:\sites\$($name)" -ErrorAction SilentlyContinue 34 | $info["$($_)"] = $p.enabled 35 | } 36 | } 37 | $authenticationinfo = New-Object -TypeName PSObject -Property $info 38 | 39 | New-Object -TypeName PSObject -Property @{ 40 | name = [string]$_.Name 41 | physicalpath = [string]$_.PhysicalPath 42 | applicationpool = [string]$_.ApplicationPool 43 | hostheader = [string]$_.HostHeader 44 | state = [string]$_.State 45 | serverautostart = [string]$_.serverautostart 46 | enabledprotocols = [string]$_.enabledprotocols 47 | bindings = @($_.Bindings.Collection | %{ 48 | New-Object -TypeName PSObject -Property @{ 49 | protocol = [string]$_.protocol 50 | bindinginformation = [string]$_.bindingInformation 51 | sslflags = [int]$_.sslFlags 52 | certificatehash = [string]$_.certificateHash 53 | certificatestorename = [string]$_.certificateStoreName 54 | } 55 | }) 56 | limits = New-Object -TypeName PSObject -Property @{ 57 | maxbandwidth = [int64]$_.limits.maxbandwidth 58 | maxconnections = [int64]$_.limits.maxconnections 59 | connectiontimeout = [int]$_.limits.connectiontimeout.totalseconds 60 | } 61 | authenticationinfo = $authenticationinfo 62 | logformat = [string]$_.LogFile.logFormat 63 | logpath = [string]$_.LogFile.directory 64 | logperiod = [string]$_.LogFile.period 65 | logtruncatesize = [string]$_.LogFile.truncateSize 66 | loglocaltimerollover = [string]$_.LogFile.localTimeRollover 67 | logextfileflags = [string]$_.LogFile.logExtFileFlags 68 | preloadenabled = $preloadenabled 69 | } 70 | } | ConvertTo-Json -Depth 10 71 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/_newwebsite.ps1.erb: -------------------------------------------------------------------------------- 1 | <% ip_dns, port, host_header = resource.provider.binding_information %> 2 | 3 | $resource = @{ 4 | name = '<%= "#{resource[:name]}" %>' 5 | ensure = '<%= "#{resource[:ensure]}" %>' 6 | physicalpath = '<%= "#{resource[:physicalpath]}" %>' 7 | applicationpool = '<%= "#{resource[:applicationpool]}" %>' 8 | } 9 | 10 | $createParams = @{ 11 | Name = $resource.name 12 | PhysicalPath = $resource.physicalpath 13 | ApplicationPool = $resource.applicationpool 14 | Force = $true 15 | ErrorAction = 'Stop' 16 | <%= " port = #{port}\n" unless port.nil? -%> 17 | <%= " HostHeader = '#{host_header}'\n" unless host_header.nil? -%> 18 | <%= " Ssl = $true\n" if resource.provider.ssl? -%> 19 | } 20 | 21 | # If there are no other websites, specify the Id, otherwise an Index Out of Range error can be thrown 22 | If ((Get-ChildItem 'IIS:\sites' | Measure-Object).Count -eq 0) { 23 | $createParams['Id'] = 1 24 | } 25 | 26 | # create website 27 | # dont set applicationpool if it doesnt exist 28 | New-Website @createParams 29 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/_setwebsite.ps1.erb: -------------------------------------------------------------------------------- 1 | $resource = @{ 2 | name = '<%= "#{resource[:name]}" %>' 3 | ensure = '<%= "#{resource[:ensure]}" %>' 4 | } 5 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/bindingproperty.ps1.erb: -------------------------------------------------------------------------------- 1 | <%- if resource[:bindings] -%> 2 | $website = Get-WebConfiguration -Filter '/system.applicationHost/sites/site' | Where-Object -FilterScript {$_.Name -eq '<%= resource[:name] %>' } 3 | 4 | Clear-WebConfiguration -Filter "$($website.ItemXPath)/bindings" -Force -ErrorAction Stop 5 | 6 | <%- resource[:bindings].each do |bind| -%> 7 | Add-WebConfiguration -Filter "$($website.ItemXPath)/bindings" ` 8 | -Value @{ 9 | protocol = '<%= bind['protocol'] %>' 10 | bindingInformation = '<%= bind['bindinginformation'] %>' 11 | } ` 12 | -Force ` 13 | -ErrorAction Stop 14 | <%- if bind['protocol'] == 'https' and bind['sslflags'] -%> 15 | Set-WebConfigurationProperty -Filter "$($website.ItemXPath)/bindings/binding[last()]" ` 16 | -Name sslFlags ` 17 | -Value <%= bind['sslflags'] %> ` 18 | -Force ` 19 | -ErrorAction Stop 20 | 21 | $binding = Get-WebConfiguration -Filter "$($website.ItemXPath)/bindings/binding[last()]" -ErrorAction Stop 22 | $binding.AddSslCertificate('<%= bind['certificatehash'] %>', '<%= bind['certificatestorename'] %>') 23 | <%- end -%> 24 | <%- end -%> 25 | <%- end -%> 26 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/generalproperties.ps1.erb: -------------------------------------------------------------------------------- 1 | <% if !resource[:name].nil? -%> 2 | $nameParams = @{ 3 | Path = "IIS:\Sites\$($resource.name)" 4 | Name = 'name' 5 | Value = '<%= "#{resource[:name]}" %>' 6 | } 7 | Try-SetItemProperty @nameParams 8 | <% end -%> 9 | 10 | <% if !resource[:physicalpath].nil? -%> 11 | $pathParams = @{ 12 | Path = "IIS:\Sites\$($resource.name)" 13 | Name = 'physicalpath' 14 | Value = '<%= "#{resource[:physicalpath]}" %>' 15 | } 16 | Try-SetItemProperty @pathParams 17 | <% end -%> 18 | 19 | <% if !resource[:applicationpool].nil? -%> 20 | $poolParams = @{ 21 | Path = "IIS:\Sites\$($resource.name)" 22 | Name = 'applicationpool' 23 | Value = '<%= "#{resource[:applicationpool]}" %>' 24 | } 25 | Try-SetItemProperty @poolParams 26 | <% end -%> 27 | 28 | <% if !resource[:defaultpage].nil? -%> 29 | $resource.defaultpage = @('<%= "#{resource[:defaultpage].join("','")}" %>') 30 | $AllDefaultPages = @( 31 | Get-WebConfiguration -Filter '//defaultDocument/files/*' -PSPath "IIS:\Sites\$($resource.name)" | 32 | ForEach-Object { $_.value } 33 | ) 34 | $resource.defaultpage | %{ 35 | if ($AllDefaultPages -inotcontains $_){ 36 | Add-WebConfiguration -Filter '//defaultDocument/files' ` 37 | -PSPath "IIS:\Sites\$($resource.name)" ` 38 | -Value @{value = $_} 39 | } 40 | } 41 | <% end -%> 42 | 43 | <% if !resource[:enabledprotocols].nil? -%> 44 | $logParams = @{ 45 | Path = "IIS:\Sites\$($resource.name)" 46 | Name = 'enabledProtocols' 47 | Value = '<%= "#{resource[:enabledprotocols]}" %>' 48 | } 49 | Try-SetItemProperty @logParams 50 | <% end -%> 51 | 52 | <% if !resource[:serviceautostart].nil? -%> 53 | $logParams = @{ 54 | Path = "IIS:\Sites\$($resource.name)" 55 | Name = 'applicationDefaults.serviceAutoStartEnabled' 56 | Value = '<%= "#{resource[:serviceautostart]}" %>' 57 | } 58 | Try-SetItemProperty @logParams 59 | <% end -%> 60 | 61 | <% if !resource[:preloadenabled].nil? -%> 62 | $logParams = @{ 63 | Path = "IIS:\Sites\$($resource.name)" 64 | Name = 'applicationDefaults.preloadEnabled' 65 | Value = '<%= "#{resource[:preloadenabled]}" %>' 66 | } 67 | Try-SetItemProperty @logParams 68 | <% end -%> 69 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/getapps.ps1.erb: -------------------------------------------------------------------------------- 1 | [regex]$pattern = '/' 2 | 3 | Get-WebApplication | % { 4 | $name = [string]$pattern.replace($_.Path,'',1) 5 | $site = [string]$_.ItemXPath.split("'")[1] 6 | 7 | $AppEntityPath = "IIS:\\Sites\${site}\${name}" 8 | $AppEntityPhysicalPath = Get-ItemProperty -Path $AppEntityPath -Name physicalPath 9 | 10 | if ($AppEntityPhysicalPath.EndsWith('/')){ 11 | $null = Set-ItemProperty -Path $AppEntityPath -Name physicalPath -Value $AppEntityPhysicalPath.TrimEnd('/') -Force 12 | } 13 | 14 | $sslFlags = @() 15 | $sslFlags_raw = [String](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/access").sslFlags 16 | if ($sslFlags_raw -ne '') { $sslFlags = $sslFlags_raw -split ',' } 17 | New-Object -TypeName PSObject -Property @{ 18 | name = $name 19 | site = $site 20 | applicationpool = [string]$_.ApplicationPool 21 | physicalpath = [string]$_.PhysicalPath 22 | sslflags = $sslFlags 23 | authenticationinfo = New-Object -TypeName PSObject -Property @{ 24 | anonymous = [bool](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/authentication/anonymousAuthentication").enabled 25 | basic = [bool](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/authentication/basicAuthentication").enabled 26 | clientCertificateMapping = [bool](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/authentication/clientCertificateMappingAuthentication").enabled 27 | iisClientCertificateMapping = [bool](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/authentication/iisClientCertificateMappingAuthentication").enabled 28 | windows = [bool](Get-WebConfiguration -Location "${site}/${name}" -Filter "system.webserver/security/authentication/windowsAuthentication").enabled 29 | forms = [string](Get-WebConfigurationProperty -Location "${site}/${name}" -Filter "system.web/authentication" -Name "mode") -eq "Forms" 30 | } 31 | enabledprotocols = [string]$_.enabledProtocols 32 | } 33 | } | ConvertTo-Json -Depth 10 34 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/limitsproperty.ps1.erb: -------------------------------------------------------------------------------- 1 | <%- if resource[:limits] -%> 2 | $website = Get-WebConfiguration -Filter '/system.applicationHost/sites/site' | Where-Object -FilterScript {$_.Name -eq '<%= resource[:name] %>' } 3 | <%- resource[:limits].each do |limit_name,limit_setting| -%> 4 | <%- if limit_name == 'connectiontimeout' -%> 5 | Set-WebConfiguration -Filter "$($website.ItemXPath)/limits/@<%= limit_name %>" -Value (New-Timespan -Seconds <%= limit_setting %>) 6 | <%- else -%> 7 | Set-WebConfiguration -Filter "$($website.ItemXPath)/limits/@<%= limit_name %>" -Value <%= limit_setting %> 8 | <%- end -%> 9 | <%- end -%> 10 | <%- end -%> -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/logproperties.ps1.erb: -------------------------------------------------------------------------------- 1 | $iis_version = '<%= Facter.value(:iis_version) %>' 2 | 3 | # Convert the string representation of an Enum value into an Int32 safely 4 | Function ConvertTo-EnumInt32 { 5 | param( 6 | [Type]$EnumType, 7 | [String]$Name, 8 | [String]$Value 9 | ) 10 | 11 | $EnumValue = ([Enum]::GetNames($EnumType)) | ForEach-Object { 12 | if ($_.ToUpper() -eq $Value.ToUpper()) { 13 | return [Int32]($EnumType::$_) 14 | } 15 | } 16 | if ($EnumValue -eq $null) { 17 | Throw "$Value is not a valid value for $Name" 18 | } else { 19 | Return $EnumValue 20 | } 21 | } 22 | 23 | if ($iis_version -eq '7.5') { 24 | # In IIS 7.5 the Enums are saved using the Int32 value, whereas in later versions, the text value can be used 25 | # Load the WebAdministration DLL so we can convert string values into their Int32 enum equivalent 26 | 27 | # Microsoft.Web.Administration namespace - https://msdn.microsoft.com/en-us/library/microsoft.web.administration(v=vs.90).aspx 28 | # Log Format - https://msdn.microsoft.com/en-us/library/microsoft.web.administration.logformat(v=vs.90).aspx 29 | # LogExtFileFlags - https://msdn.microsoft.com/en-us/library/microsoft.web.administration.logextfileflags(v=vs.90).aspx 30 | # LogPeriod - https://msdn.microsoft.com/en-us/library/microsoft.web.administration.loggingrolloverperiod(v=vs.90).aspx 31 | 32 | [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.Web.Administration") | Out-Null 33 | } 34 | 35 | <% if !resource[:logformat].nil? -%> 36 | $logParams = @{ 37 | Path = "IIS:\Sites\$($resource.name)" 38 | Name = 'LogFile.logFormat' 39 | Value = '<%= "#{resource[:logformat]}" %>' 40 | } 41 | 42 | if ($iis_version -eq '7.5') { 43 | $logParams.Value = ConvertTo-EnumInt32 -EnumType ([Microsoft.Web.Administration.LogFormat]) -Value $logParams.Value -Name 'logformat' 44 | } 45 | 46 | Try-SetItemProperty @logParams 47 | <% end -%> 48 | 49 | <% if !resource[:logpath].nil? -%> 50 | $logParams = @{ 51 | Path = "IIS:\Sites\$($resource.name)" 52 | Name = 'LogFile.directory' 53 | value = '<%= "#{resource[:logpath]}" %>' 54 | } 55 | Try-SetItemProperty @logParams 56 | <% end -%> 57 | 58 | <% if !resource[:logflags].nil? -%> 59 | $W3CValue = 'W3C' 60 | if ($iis_version -eq '7.5') { 61 | $W3CValue = ConvertTo-EnumInt32 -EnumType ([Microsoft.Web.Administration.LogFormat]) -Value 'W3C' -Name 'logformat' 62 | } 63 | 64 | $logParams = @{ 65 | Path = "IIS:\Sites\$($resource.name)" 66 | Name = 'LogFile.logFormat' 67 | value = $W3CValue 68 | } 69 | Try-SetItemProperty @logParams 70 | 71 | $logParams.Name = 'LogFile.logExtFileFlags' 72 | $logParams.value = "<%= "#{resource[:logflags].is_a?(Array) ? resource[:logflags].join(',') : resource[:logflags]}" %>" 73 | 74 | if ($iis_version -eq '7.5') { 75 | # For each flag, convert the strings into Int32 bitwise mask 76 | $flags = 0 77 | $logParams.value -Split ',' | % { 78 | $flags = $flags -bor (ConvertTo-EnumInt32 -EnumType ([Microsoft.Web.Administration.LogExtFileFlags]) -Value $_ -Name 'logflags') 79 | } 80 | $logParams.Value = $flags 81 | } 82 | 83 | Try-SetItemProperty @logParams 84 | <% end -%> 85 | 86 | <% if !resource[:logperiod].nil? -%> 87 | $logParams = @{ 88 | Path = "IIS:\Sites\$($resource.name)" 89 | Name = 'LogFile.period' 90 | value = '<%= "#{resource[:logperiod]}" %>' 91 | } 92 | 93 | if ($iis_version -eq '7.5') { 94 | $logParams.Value = ConvertTo-EnumInt32 -EnumType ([Microsoft.Web.Administration.LoggingRolloverPeriod]) -Value $logParams.Value -Name 'logperiod' 95 | } 96 | 97 | Try-SetItemProperty @logParams 98 | <% end -%> 99 | 100 | <% if !resource[:logtruncatesize].nil? -%> 101 | $logParams = @{ 102 | Path = "IIS:\Sites\$($resource.name)" 103 | Name = 'LogFile.truncateSize' 104 | value = '<%= "#{resource[:logtruncatesize]}" %>' 105 | } 106 | Try-SetItemProperty @logParams 107 | 108 | $MaxSizeValue = 'MaxSize' 109 | if ($iis_version -eq '7.5') { 110 | $MaxSizeValue = ConvertTo-EnumInt32 -EnumType ([Microsoft.Web.Administration.LoggingRolloverPeriod]) -Value 'MaxSize' -Name 'logperiod' 111 | } 112 | $logParams.Name = 'LogFile.period' 113 | $logParams.value = $MaxSizeValue 114 | Try-SetItemProperty @logParams 115 | <% end -%> 116 | 117 | <% if !resource[:loglocaltimerollover].nil? -%> 118 | $logParams = @{ 119 | Path = "IIS:\Sites\$($resource.name)" 120 | Name = 'LogFile.localTimeRollover' 121 | value = '<%= "#{resource[:loglocaltimerollover]}" %>' 122 | } 123 | Try-SetItemProperty @logParams 124 | <% end -%> 125 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/newapplication.ps1.erb: -------------------------------------------------------------------------------- 1 | $resource = @{ 2 | name = '<%= "#{resource.provider.app_name}" %>' 3 | site = '<%= "#{resource.provider.class.find_sitename(resource)}" %>' 4 | physicalpath = '<%= "#{resource[:physicalpath]}" %>' 5 | applicationpool = '<%= "#{resource[:applicationpool]}" %>' 6 | } 7 | 8 | $createParams = @{ 9 | Name = $resource.name 10 | Site = $resource.site 11 | PhysicalPath = $resource.physicalpath 12 | ApplicationPool = $resource.applicationpool 13 | Force = $true 14 | ErrorAction = 'Stop' 15 | } 16 | 17 | # create application 18 | # dont set applicationpool if it doesnt exist 19 | New-WebApplication @createParams 20 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/serviceautostartprovider.ps1.erb: -------------------------------------------------------------------------------- 1 | <% if !resource[:serviceautostartprovidername].nil? -%> 2 | $resource.serviceautostartprovidername = '<%= "#{resource[:serviceautostartprovidername]}" %>' 3 | $resource.serviceautostartprovidertype = '<%= "#{resource[:serviceautostartprovidertype]}" %>' 4 | 5 | $provider = @(New-Object -TypeName PSObject -Property @{ 6 | Name = $resource.serviceautostartprovidername 7 | Type = $resource.serviceautostartprovidertype 8 | }) 9 | 10 | $website = $null 11 | try { 12 | $wesbite = Get-Item -Path 'IIS:\sites\<%= "#{resource[:name]}" %>' 13 | } catch { 14 | $website = $null 15 | } 16 | 17 | if($website){ 18 | $current = $website.applicationDefaults.ServiceAutoStartProvider 19 | 20 | if(($provider.name) -and ($current -ne $provider.name)){ 21 | $webConfig = (Get-WebConfiguration -filter '/system.applicationHost/serviceAutoStartProviders').Collection 22 | $existing = $webConfig | Where-Object { $_.Name -eq $provider.name } | Select-Object Name,Type 23 | 24 | if($existing){ 25 | if($existing.name -ne $provider.name){ 26 | if($existing.type -ne $provider.type){ 27 | throw 'The specified AutoStartProvider is the same as a Global Property. 28 | Ensure that the serviceAutoStartProvider is unique' 29 | } 30 | } 31 | }else{ 32 | Add-WebConfiguration -Filter "/system.applicationHost/serviceAutoStartProviders" ` 33 | -Value $provider ` 34 | -ErrorAction 'Stop' 35 | } 36 | 37 | Set-ItemProperty -Path "IIS:\Sites\<%= "#{resource[:name]}" %>" ` 38 | -Name 'applicationDefaults.serviceAutoStartProvider' ` 39 | -Value $provider.name ` 40 | -ErrorAction Stop 41 | } 42 | } 43 | <% end -%> 44 | -------------------------------------------------------------------------------- /lib/puppet/provider/templates/webadministration/trysetitemproperty.ps1.erb: -------------------------------------------------------------------------------- 1 | function Try-SetItemProperty 2 | { 3 | param($Path, $Name, $Value) 4 | 5 | $existing = Get-ItemProperty -Path $Path -Name $Name -ErrorAction 'Continue' 6 | 7 | if(($existing -eq $null) -or ($existing.Value -ne $Value)){ 8 | 9 | try { 10 | Set-ItemProperty -Path $Path -Name $Name -Value $Value 11 | }catch{ 12 | throw "Error setting $($Name) to $($Value): $($_)" 13 | } 14 | 15 | }else{ 16 | # NOOP 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/puppet/type/iis_application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/parameter/boolean' 4 | require_relative '../../puppet_x/puppetlabs/iis/property/name' 5 | require_relative '../../puppet_x/puppetlabs/iis/property/string' 6 | require_relative '../../puppet_x/puppetlabs/iis/property/hash' 7 | require_relative '../../puppet_x/puppetlabs/iis/property/path' 8 | require_relative '../../puppet_x/puppetlabs/iis/property/authenticationinfo' 9 | 10 | Puppet::Type.newtype(:iis_application) do 11 | desc <<-DOC 12 | @summary 13 | Allows creation of a new IIS Application and configuration of 14 | application parameters. 15 | 16 | The iis_application type uses an applicationname and a sitename to 17 | create an IIS Application. When specifying an application you must 18 | specify both. You can specify the sitename by putting it in the title 19 | as in "$site_name\\$application_name", or you can use the named 20 | parameters. If converting a virtual directory to an app, you can use 21 | the virtual_directory parameter to specify the site and omit the 22 | sitename parameter. To manage two applications of the same name within 23 | different websites on an IIS instance, you must ensure the resource 24 | title is unique. You can do this by entering both the sitename and 25 | applicationname in the title, or using a descriptive title for the 26 | resource and using the named parameters for sitename and 27 | applicationname 28 | DOC 29 | 30 | ensurable 31 | 32 | newparam(:applicationname, namevar: true) do 33 | desc "The name of the application. The virtual path of the application is 34 | '/'." 35 | end 36 | 37 | newproperty(:sitename, parent: PuppetX::PuppetLabs::IIS::Property::Name) do 38 | desc 'The name of the site for the application.' 39 | end 40 | 41 | newproperty(:physicalpath, parent: PuppetX::PuppetLabs::IIS::Property::Path) do 42 | desc 'The physical path to the application directory. This path must be 43 | fully qualified.' 44 | munge do |value| 45 | v = value.chomp('/') if value.match?(%r{^.:(/|\\)}) 46 | v = value.chomp('\\') if value.match?(%r{^(/|\\)(/|\\)[^(/|\\)]+(/|\\)[^(/|\\)]+}) 47 | 48 | raise ArgumentError, 'A non-empty physicalpath must be specified.' if v.nil? || v.empty? 49 | raise("File paths must be fully qualified, not '#{v}'") unless v =~ %r{^.:(/|\\)} || v =~ %r{^(/|\\)(/|\\)[^(/|\\)]+(/|\\)[^(/|\\)]+} 50 | 51 | v 52 | end 53 | end 54 | 55 | newproperty(:applicationpool, parent: PuppetX::PuppetLabs::IIS::Property::Name) do 56 | desc 'The name of the application pool for the application.' 57 | validate do |value| 58 | raise ArgumentError, 'A non-empty applicationpool name must be specified.' if value.nil? || value.empty? 59 | 60 | super value 61 | end 62 | end 63 | 64 | newparam(:virtual_directory) do 65 | desc "The IIS Virtual Directory to convert to an application on create. 66 | Similar to iis_application, iis_virtual_directory uses composite 67 | namevars." 68 | 69 | munge do |value| 70 | value.start_with?('IIS:') ? value : File.join('IIS:/Sites', value) 71 | end 72 | end 73 | 74 | newproperty(:sslflags, array_matching: :all) do 75 | desc 'The SSL settings for the application. Valid options are an array of 76 | flags, with the following names: \'Ssl\', \'SslRequireCert\', 77 | \'SslNegotiateCert\', \'Ssl128\'.' 78 | newvalues( 79 | 'Ssl', 80 | 'SslRequireCert', 81 | 'SslNegotiateCert', 82 | 'Ssl128', 83 | ) 84 | end 85 | 86 | newproperty(:authenticationinfo, parent: PuppetX::PuppetLabs::IIS::Property::AuthenticationInfo) 87 | 88 | newproperty(:enabledprotocols) do 89 | desc 'The comma-delimited list of enabled protocols for the application. 90 | Valid protocols are: \'http\', \'https\', \'net.pipe\', \'net.tcp\', \'net.msmq\', \'msmq.formatname\'.' 91 | validate do |value| 92 | return if value.nil? 93 | raise("Invalid value '#{value}'. Should be a string") unless value.is_a?(String) 94 | 95 | raise("Invalid value ''. Valid values are http, https, net.pipe, net.tcp, net.msmq, msmq.formatname") if value.empty? 96 | 97 | allowed_protocols = ['http', 'https', 'net.pipe', 'net.tcp', 'net.msmq', 'msmq.formatname'].freeze 98 | protocols = value.split(',') 99 | protocols.each do |protocol| 100 | raise("Invalid protocol '#{protocol}'. Valid values are http, https, net.pipe, net.tcp, net.msmq, msmq.formatname") unless allowed_protocols.include?(protocol) 101 | end 102 | end 103 | end 104 | 105 | autorequire(:iis_application_pool) { self[:applicationpool] } 106 | autorequire(:iis_site) { self[:sitename] } 107 | end 108 | -------------------------------------------------------------------------------- /lib/puppet/type/iis_feature.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/parameter/boolean' 4 | require_relative '../../puppet_x/puppetlabs/iis/property/string' 5 | 6 | Puppet::Type.newtype(:iis_feature) do 7 | @doc = 'Allows installation and removal of IIS Features.' 8 | 9 | ensurable do 10 | desc 'Manage the state of this rule.' 11 | defaultvalues 12 | defaultto :present 13 | end 14 | 15 | newparam(:name, namevar: true) do 16 | desc 'The unique name of the feature to manage.' 17 | end 18 | 19 | newparam(:include_all_subfeatures, boolean: true) do 20 | desc "Indicates whether to install all sub features of a parent IIS feature. 21 | For instance, ASP.NET as well as the IIS Web Server" 22 | end 23 | 24 | newparam(:restart, boolean: true) do 25 | desc "Indicates whether to allow a restart if the IIS feature installation 26 | requests one" 27 | end 28 | 29 | newparam(:include_management_tools, boolean: true) do 30 | desc "Indicates whether to automatically install all managment tools for a 31 | given IIS feature" 32 | end 33 | 34 | newparam(:source, parent: PuppetX::PuppetLabs::IIS::Property::String) do 35 | desc "Optionally include a source path for the installation media for an IIS 36 | feature" 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/puppet/type/iis_virtual_directory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet/parameter/boolean' 4 | require_relative '../../puppet_x/puppetlabs/iis/property/path' 5 | require_relative '../../puppet_x/puppetlabs/iis/property/string' 6 | 7 | Puppet::Type.newtype(:iis_virtual_directory) do 8 | @doc = 'Allows creation of a new IIS Virtual Directory and configuration of virtual directory parameters.' 9 | 10 | ensurable do 11 | desc 'Manage the state of this rule.' 12 | defaultvalues 13 | defaultto :present 14 | end 15 | 16 | newparam(:name, namevar: true) do 17 | desc 'The name of the virtual directory to manage' 18 | validate do |value| 19 | raise ArgumentError, 'A non-empty name must be specified.' if value.nil? || value.empty? 20 | end 21 | end 22 | 23 | newproperty(:sitename) do 24 | desc 'The site name under which the virtual directory is created' 25 | validate do |value| 26 | raise ArgumentError, 'A non-empty sitename must be specified.' if value.nil? || value.empty? 27 | end 28 | end 29 | 30 | newproperty(:application) do 31 | desc 'The application under which the virtual directory is created' 32 | validate do |value| 33 | raise ArgumentError, 'A non-empty application must be specified.' if value.nil? || value.empty? 34 | end 35 | end 36 | 37 | newproperty(:physicalpath, parent: PuppetX::PuppetLabs::IIS::Property::Path) do 38 | desc "The physical path to the virtual directory. This path must be fully 39 | qualified. Though not recommended, this can be a UNC style path. 40 | Supply credentials for access to the UNC path with the `user_name` and 41 | `password` properties." 42 | validate do |value| 43 | raise ArgumentError, 'A non-empty physicalpath must be specified.' if value.nil? || value.empty? 44 | 45 | super value 46 | end 47 | end 48 | 49 | newproperty(:user_name, parent: PuppetX::PuppetLabs::IIS::Property::String) do 50 | desc "Specifies the identity that should be impersonated when accessing the 51 | physical path." 52 | end 53 | 54 | newproperty(:password, parent: PuppetX::PuppetLabs::IIS::Property::String) do 55 | desc 'Specifies the password associated with the user_name property.' 56 | end 57 | 58 | autorequire(:iis_application) { self[:application] } 59 | autorequire(:iis_site) { self[:sitename] } 60 | 61 | validate do 62 | unless self[:user_name].to_s.empty? && self[:password].to_s.empty? 63 | raise ArgumentError, 'A user_name is required when specifying password.' if self[:user_name].to_s.empty? 64 | raise ArgumentError, 'A password is required when specifying user_name.' if self[:password].to_s.empty? 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/bindings.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX::PuppetLabs::IIS 5 | # Bindings class 6 | class Bindings 7 | def self.sort_bindings(binding_value) 8 | if binding_value.nil? 9 | [] 10 | else 11 | binding_value.sort_by { |a| ((a['protocol'] == 'https') ? '0' : '1') + a['bindinginformation'] } 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/iis_features.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # IIS 6 | module IIS 7 | # Features 8 | module Features 9 | def iis_feature?(feature_name) 10 | # Note this code uses an array of the latest IIS features available to 11 | # install, but does not keep track of which subset is available in a given 12 | # IIS distribution. We could have kept track but since there are only a 13 | # handful of added features from 7.5 to 8.5, it was thought it would be 14 | # easier to have one array to check rather than keep a seperate list per 15 | # IIS version. In short, we defer to the tooling to tell us what feature is 16 | # present in which IIS version and only keep track of the larger list. 17 | IIS_INSTALLABLE_FEATURES.include?(feature_name.downcase) 18 | end 19 | module_function :iis_feature? 20 | 21 | # Note - In order to make comparisions easier, all text should be lowercase. 22 | IIS_INSTALLABLE_FEATURES = [ 23 | 'web-app-dev', 24 | 'web-appinit', 25 | 'web-application-proxy', 26 | 'web-asp', 27 | 'web-asp-net', 28 | 'web-asp-net45', 29 | 'web-basic-auth', 30 | 'web-cert-auth', 31 | 'web-certprovider', 32 | 'web-cgi', 33 | 'web-client-auth', 34 | 'web-common-http', 35 | 'web-custom-logging', 36 | 'web-dav-publishing', 37 | 'web-default-doc', 38 | 'web-digest-auth', 39 | 'web-dir-browsing', 40 | 'web-dyn-compression', 41 | 'web-filtering', 42 | 'web-ftp-ext', 43 | 'web-ftp-server', 44 | 'web-ftp-service', 45 | 'web-health', 46 | 'web-http-errors', 47 | 'web-http-logging', 48 | 'web-http-redirect', 49 | 'web-http-tracing', 50 | 'web-includes', 51 | 'web-ip-security', 52 | 'web-isapi-ext', 53 | 'web-isapi-filter', 54 | 'web-lgcy-mgmt-console', 55 | 'web-lgcy-scripting', 56 | 'web-log-libraries', 57 | 'web-metabase', 58 | 'web-mgmt-compat', 59 | 'web-mgmt-console', 60 | 'web-mgmt-service', 61 | 'web-mgmt-tools', 62 | 'web-net-ext', 63 | 'web-net-ext45', 64 | 'web-odbc-logging', 65 | 'web-performance', 66 | 'web-request-monitor', 67 | 'web-scripting-tools', 68 | 'web-security', 69 | 'web-server', 70 | 'web-stat-compression', 71 | 'web-static-content', 72 | 'web-url-auth', 73 | 'web-webserver', 74 | 'web-websockets', 75 | 'web-whc', 76 | 'web-windows-auth', 77 | 'web-wmi', 78 | ].freeze 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/iis_version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # IISVersion 10 | class IISVersion 11 | def self.supported_version_installed? 12 | false 13 | end 14 | end 15 | end 16 | end 17 | end 18 | if Puppet::Util::Platform.windows? 19 | # util 20 | require 'win32/registry' 21 | # The Puppet Extensions Module 22 | module PuppetX 23 | # PuppetLabs 24 | module PuppetLabs 25 | # IIS 26 | module IIS 27 | # IISVersion 28 | class IISVersion 29 | # define iis supported_versions 30 | def self.supported_versions 31 | ['7.5', '8.0', '8.5', '10.0'] 32 | end 33 | 34 | # get iis installed_version 35 | def self.installed_version 36 | version = nil 37 | begin 38 | hklm = Win32::Registry::HKEY_LOCAL_MACHINE 39 | reg_path = 'SOFTWARE\Microsoft\InetStp' 40 | access_type = Win32::Registry::KEY_READ | 0x100 41 | 42 | major_version = '' 43 | minor_version = '' 44 | 45 | hklm.open(reg_path, access_type) do |reg| 46 | major_version = reg['MajorVersion'] 47 | minor_version = reg['MinorVersion'] 48 | end 49 | 50 | version = "#{major_version}.#{minor_version}" 51 | rescue StandardError 52 | version = nil 53 | end 54 | version 55 | end 56 | 57 | # verify if iis supported version is installed 58 | def self.supported_version_installed? 59 | supported_versions.include? installed_version 60 | end 61 | end 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/authenticationinfo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | class PuppetX::PuppetLabs::IIS::Property::AuthenticationInfo < Puppet::Property 5 | desc 'Enable and disable authentication schemas. Note: some schemas require 6 | additional Windows features to be installed, for example windows 7 | authentication. This type does not ensure a given feature is installed 8 | before attempting to configure it.' 9 | valid_schemas = ['anonymous', 'basic', 'clientCertificateMapping', 10 | 'digest', 'iisClientCertificateMapping', 'windows', 'forms'] 11 | def insync?(is) 12 | should.reject { |k, v| 13 | is[k] == v 14 | }.empty? 15 | end 16 | validate do |value| 17 | raise "#{name} should be a Hash" unless value.is_a? ::Hash 18 | unless (value.keys & valid_schemas) == value.keys 19 | raise('All schemas must specify any of the following: anonymous, basic, clientCertificateMapping, digest, iisClientCertificateMapping, windows, or forms') 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # hash Property 12 | class Hash < Puppet::Property 13 | validate do |value| 14 | raise "#{name} should be a Hash" unless value.is_a? ::Hash 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/name.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # name Property 12 | class Name < Puppet::Property 13 | validate do |value| 14 | raise("#{value} is not a valid #{name}") unless %r{^[a-zA-Z0-9.\-_'\s]+$}.match?(value) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/path.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # path Property 12 | class Path < Puppet::Property 13 | validate do |value| 14 | raise("#{name} should be a path (local or UNC) not '#{value}'") unless value =~ %r{^.:(/|\\)} || value =~ %r{^\\\\[^\\]+\\[^\\]+} 15 | end 16 | 17 | def property_matches?(current, desired) 18 | current.casecmp(desired.downcase).zero? 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/positive_integer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # PositiveInteger Property 12 | class PositiveInteger < Puppet::Property 13 | def insync?(is) 14 | is.to_i == should.to_i 15 | end 16 | validate do |value| 17 | raise "#{name} should be an Integer" unless value.to_i.to_s == value.to_s 18 | raise "#{name} should be greater than 0" unless value.to_i.positive? 19 | end 20 | end 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/read_only.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # readonly property 12 | class ReadOnly < Puppet::Property 13 | validate do |_value| 14 | raise "#{name} is read-only and is only available via puppet resource." 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/string.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # string 12 | class String < Puppet::Property 13 | validate do |value| 14 | raise "#{name} should be a String" unless value.is_a? ::String 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/timeformat.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX 5 | # PuppetLabs 6 | module PuppetLabs 7 | # IIS 8 | module IIS 9 | # Property 10 | module Property 11 | # time format 12 | class TimeFormat < Puppet::Property 13 | validate do |value| 14 | raise "#{name} should match datetime format 00:00:00 or 0.00:00:00" unless %r{^(\d+\.)?\d\d:\d\d:\d\d$}.match?(value) 15 | end 16 | end 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/puppet_x/puppetlabs/iis/property/whole_number.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The Puppet Extensions Module 4 | module PuppetX::PuppetLabs 5 | # IIS 6 | module IIS::Property 7 | # WholeNumber Property 8 | class WholeNumber < Puppet::Property 9 | def insync?(is) 10 | is.to_i == should.to_i 11 | end 12 | validate do |value| 13 | raise "#{name} should be an Integer" unless value.to_i.to_s == value.to_s 14 | raise "#{name} should be 0 or greater" unless value.to_i >= 0 15 | end 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "puppetlabs-iis", 3 | "version": "10.1.3", 4 | "author": "puppetlabs", 5 | "summary": "Manage IIS for Windows Server 2012, 2012R2, 2016, 2019 and 2022. Maintain application sites, pools, installation, and many other IIS settings.", 6 | "license": "Apache-2.0", 7 | "source": "git://github.com/puppetlabs/puppetlabs-iis", 8 | "project_page": "https://github.com/puppetlabs/puppetlabs-iis", 9 | "issues_url": "https://github.com/puppetlabs/puppetlabs-iis/issues", 10 | "dependencies": [ 11 | { 12 | "name": "puppetlabs/pwshlib", 13 | "version_requirement": ">= 0.4.0 < 2.1.0" 14 | } 15 | ], 16 | "operatingsystem_support": [ 17 | { 18 | "operatingsystem": "Windows", 19 | "operatingsystemrelease": [ 20 | "2012", 21 | "2012 R2", 22 | "2016", 23 | "2019", 24 | "2022" 25 | ] 26 | } 27 | ], 28 | "requirements": [ 29 | { 30 | "name": "puppet", 31 | "version_requirement": ">= 7.0.0 < 9.0.0" 32 | } 33 | ], 34 | "pdk-version": "3.2.0", 35 | "template-url": "https://github.com/puppetlabs/pdk-templates.git#main", 36 | "template-ref": "tags/3.2.0.4-0-g5d17ec1" 37 | } 38 | -------------------------------------------------------------------------------- /pdk.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | ignore: [] 3 | -------------------------------------------------------------------------------- /provision.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | default: 3 | provisioner: vagrant 4 | images: 5 | - gusztavvargadr/windows-server 6 | release_checks: 7 | provisioner: abs 8 | images: 9 | - win-2008r2-x86_64 10 | - win-2012r2-x86_64 11 | - win-2016-core-x86_64 12 | - win-2019-core-x86_64 13 | release_checks_7: 14 | provisioner: abs 15 | images: 16 | - win-2012r2-x86_64 17 | - win-2016-core-x86_64 18 | - win-2019-core-x86_64 19 | -------------------------------------------------------------------------------- /spec/README.md: -------------------------------------------------------------------------------- 1 | Test Levels 2 | =========================== 3 | 4 | This folder contains tests at the unit, acceptance, and integration level for the "puppetlabs-iis" module. The unit 5 | tests are kept in the "spec/unit" level folder of the repository. Acceptance in the "spce/acceptance" and Integration at 6 | the "spec/integration" level. 7 | 8 | ## Acceptance Folder 9 | 10 | At Puppet Inc. we define an "acceptance" test as: 11 | 12 | > Validating the system state and/or side effects while completing a stated piece of functionality within a tool. 13 | > This type of test is contained within the boundaries of a tool in order to test a defined functional area within 14 | > that tool. 15 | 16 | What this means for this project is that we will install and configure some infrastructure needed for a "puppetlabs-iis" 17 | environment. (Puppet agent only.) 18 | 19 | ### Nodesets Folder 20 | 21 | The "spec/acceptance/nodesets" folder contains Beaker host configuration files for the various test platforms used by the 22 | "acceptance" test suite. Note: these configs are by default dynamically generated using [Beaker-HostGenerator](https://github.com/puppetlabs/beaker-hostgenerator) 23 | The default config can be overridden by setting the PLATFORM environment variable. 24 | 25 | ## Integration Folder 26 | 27 | These tests were originally written by the QA team at Puppet Labs and is actively maintained by the QA team. 28 | Feel free to contribute tests to this folder as long as they are written with [beaker-rspec](https://github.com/puppetlabs/beaker-rspec) 29 | and match the style of the [examples given](https://github.com/puppetlabs/beaker-rspec#create-spec-tests-for-your-module). 30 | 31 | The "puppetlabs-iis" project already contains RSpec and acceptance tests and you might be wondering why there 32 | is a need to have a set of tests separate from those tests. At Puppet Labs we define an "integration" test as: 33 | 34 | > Validating the system state and/or side effects while completing a complete life cycle of user stories using a 35 | > system. This type of test crosses the boundary of a discrete tool in the process of testing a defined user 36 | > objective that utilizes a system composed of integrated components. 37 | 38 | What this means for this project is that we will install and configure all infrastructure needed in a real-world 39 | "puppetlabs-iis" environment. (Puppet Server and agent.) 40 | 41 | ## Running Tests 42 | 43 | ### General Setup Steps: 44 | Option 1 - using Bundler: 45 | 1. Install Bundler 46 | ``` 47 | gem install bundler 48 | ``` 49 | 2. Install dependent gems 50 | ``` 51 | bundle install --path .bundle/gems 52 | ``` 53 | 3. To see what tasks are available run 54 | ``` 55 | bundle exec rake -T 56 | ``` 57 | 58 | Option 2 - if not using Bundler, then install the dependent gems. 59 | 60 | Puppet's default is to use Bundler, as such the rest of this document will assume use thereof. 61 | 62 | ### Unit tests: 63 | To run tests using rspec-puppet and example: 64 | ``` 65 | bundle exec rake spec/classes/init.rb 66 | ``` 67 | 68 | ### To run integration and reference tests using Rototiller: 69 | Modules frequently have integration tests associated with them that can be run on Puppet's internal network using 70 | vmpooler with default test setup values set by [Rototiller](https://github.com/puppetlabs/rototiller). 71 | 72 | Acceptance and reference tests can typically be run with default values using vmpooler inside the network by typing: 73 | ``` 74 | bundle exec rake acceptance_tests 75 | ``` 76 | or 77 | ``` 78 | bundle exec rake integration_tests 79 | ``` 80 | respectively. 81 | 82 | **For vmpooler on the Puppet internal network (only needed for acceptance and integration tests): 83 | To start the the tests you'll need your keyfile setup in one of two ways: 84 | Option 1: Make sure the keyfile is in the default location of ~/.ssh/id_rsa-acceptance 85 | 86 | Option 2: If your keyfile is in a different directory then override the default by setting the BEAKER_keyfile 87 | environment variable to where your keyfile is located. 88 | 89 | **For Vagrant Cloud 90 | Use the [puppetlabs-packer](https://github.com/puppetlabs/puppetlabs-packer) repository to build images. 91 | -------------------------------------------------------------------------------- /spec/acceptance/iis_feature_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'when managing features iis_features', :suite_a do 6 | context 'with default parameters' do 7 | feature = 'Web-Scripting-Tools' 8 | manifest = <<-HERE 9 | iis_feature { '#{feature}': 10 | ensure => 'present' 11 | } 12 | HERE 13 | 14 | iis_idempotent_apply('create iis feature', manifest) 15 | 16 | it 'iis_feature is present' do 17 | result = resource('iis_feature', feature) 18 | puppet_resource_should_show('ensure', 'present', result) 19 | end 20 | end 21 | 22 | context 'with invalid feature name' do 23 | manifest = <<-HERE 24 | iis_feature { 'Foo': 25 | ensure => 'present' 26 | } 27 | HERE 28 | apply_failing_manifest('apply failed manifest', manifest) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/acceptance/iis_minimal_config_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'a minimal IIS config:', :suite_a do 6 | manifest = <<-EOF 7 | file {'c:\\inetpub\\minimal': 8 | ensure => directory, 9 | source => 'C:\\inetpub\\wwwroot', 10 | recurse => true 11 | } 12 | iis_site { 'minimal': 13 | ensure => 'stopped', 14 | physicalpath => 'c:\\inetpub\\minimal', 15 | applicationpool => 'DefaultAppPool', 16 | } 17 | EOF 18 | 19 | iis_idempotent_apply('create app', manifest) 20 | end 21 | -------------------------------------------------------------------------------- /spec/acceptance/iis_unicode_site_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'iis_site', :suite_b do 6 | before(:all) do 7 | # Remove 'Default Web Site' to start from a clean slate 8 | remove_all_sites 9 | end 10 | 11 | context 'when configuring a website' do 12 | context 'with required parameters and UTF-8 site name' do 13 | before(:all) do 14 | create_path('C:\inetpub\basic') 15 | end 16 | 17 | site_name = "\u4388\u542B\u3D3C\u7F4D\uF961\u4381\u53F4\u79C0\u3AB2\u8EDE" # 䎈含㴼罍率䎁叴秀㪲軞 18 | manifest = <<-HERE 19 | iis_site { '#{site_name}': 20 | ensure => 'started', 21 | physicalpath => 'C:\\inetpub\\basic', 22 | applicationpool => 'DefaultAppPool', 23 | } 24 | HERE 25 | 26 | after(:all) do 27 | remove_all_sites 28 | end 29 | 30 | it 'runs without errors' do 31 | # Expected to fail due to MODULES-6869 32 | expect { apply_manifest(manifest, catch_failures: true) }.to raise_exception 33 | end 34 | 35 | def verify_iis_site(iis_site_name) 36 | <<-POWERSHELL 37 | Import-Module Webadministration 38 | (Get-ChildItem -Path IIS:\Sites | Where-Object { $_.Name -match ([regex]::Unescape("#{iis_site_name}")) } | Measure-Object).Count 39 | POWERSHELL 40 | end 41 | 42 | it 'Verify that IIS site name is present' do 43 | result = run_shell(interpolate_powershell(verify_iis_site(site_name))) 44 | # Expected to fail due to MODULES-6869' 45 | expect { assert_match(%r{^1$}, result.stdout, 'Expected IIS site was not present!') }.to raise_exception 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/acceptance/iis_virtual_directory_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'iis_virtual_directory', :suite_b do 6 | site_name = SecureRandom.hex(10) 7 | before(:context) do 8 | # Remove 'Default Web Site' to start from a clean slate 9 | remove_all_sites 10 | create_site(site_name, true) 11 | end 12 | 13 | after(:context) do 14 | remove_all_sites 15 | end 16 | 17 | context 'when configuring a virtual directory' do 18 | context 'with default parameters' do 19 | before(:all) do 20 | create_path('C:\foo') 21 | end 22 | 23 | virt_dir_name = SecureRandom.hex(10).to_s 24 | # create_site(site_name, true) 25 | after(:all) do 26 | remove_vdir(virt_dir_name, site_name) 27 | end 28 | 29 | describe 'apply manifest twice' do 30 | manifest = <<-HERE 31 | file{ 'c:/foo': 32 | ensure => 'directory' 33 | }-> 34 | file{ 'c:/foo2': 35 | ensure => 'directory' 36 | }-> 37 | iis_virtual_directory { '#{virt_dir_name}': 38 | ensure => 'present', 39 | sitename => '#{site_name}', 40 | physicalpath => 'c:\\foo' 41 | } 42 | HERE 43 | 44 | iis_idempotent_apply('create iis virtual dir', manifest) 45 | end 46 | 47 | context 'when puppet resource is run' do 48 | it 'iis_virtual_directory should be present' do 49 | puppet_resource_should_show('ensure', 'present', resource('iis_virtual_directory', virt_dir_name)) 50 | end 51 | 52 | context 'when capitalization of paths change' do 53 | manifest = <<-HERE 54 | iis_virtual_directory { '#{virt_dir_name}': 55 | ensure => 'present', 56 | sitename => '#{site_name}', 57 | # Change capitalization to see if it breaks idempotency 58 | physicalpath => 'c:\\Foo' 59 | } 60 | HERE 61 | 62 | it 'runs with no changes' do 63 | apply_manifest(manifest, catch_changes: true) 64 | end 65 | end 66 | end 67 | 68 | context 'when physical path changes' do 69 | describe 'apply manifest twice' do 70 | manifest = <<-HERE 71 | iis_virtual_directory { '#{virt_dir_name}': 72 | ensure => 'present', 73 | sitename => '#{site_name}', 74 | physicalpath => 'c:\\foo2' 75 | } 76 | HERE 77 | 78 | iis_idempotent_apply('create iis virtual dir', manifest) 79 | end 80 | 81 | context 'when puppet resource is run' do 82 | it 'physicalpath to be configured' do 83 | puppet_resource_should_show('physicalpath', 'c:\\foo2', resource('iis_virtual_directory', virt_dir_name)) 84 | end 85 | end 86 | end 87 | end 88 | 89 | context 'with a password wrapped in Sensitive()' do 90 | virt_dir_name = SecureRandom.hex(10).to_s 91 | manifest = <<-HERE 92 | file{ 'c:/foo': 93 | ensure => 'directory' 94 | }-> 95 | iis_virtual_directory { '#{virt_dir_name}': 96 | ensure => 'present', 97 | sitename => '#{site_name}', 98 | physicalpath => 'c:\\foo', 99 | user_name => 'user', 100 | password => Sensitive('#@\\'454sdf'), 101 | } 102 | HERE 103 | 104 | iis_idempotent_apply('create iis virtual dir', manifest) 105 | 106 | it 'all parameters are configured' do 107 | resource_data = resource('iis_virtual_directory', virt_dir_name) 108 | [ 109 | 'ensure', 'present', 110 | 'user_name', 'user', 111 | 'password', '#@\\\'454sdf' 112 | ].each_slice(2) do |key, value| 113 | puppet_resource_should_show(key, value, resource_data) 114 | end 115 | end 116 | 117 | it 'remove virt dir name' do 118 | remove_vdir(virt_dir_name, site_name) 119 | end 120 | end 121 | 122 | context 'can remove virtual directory' do 123 | virt_dir_name = SecureRandom.hex(10).to_s 124 | before(:all) do 125 | create_path('c:/foo') 126 | create_vdir(virt_dir_name, site_name, 'c:/foo') 127 | end 128 | 129 | manifest = <<-HERE 130 | iis_virtual_directory { '#{virt_dir_name}': 131 | sitename => '#{site_name}', 132 | ensure => 'absent' 133 | } 134 | HERE 135 | iis_idempotent_apply('remove iis virtual dir', manifest) 136 | 137 | after(:all) do 138 | remove_vdir(virt_dir_name) 139 | end 140 | 141 | it 'iis_virtual_directory to be absent' do 142 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 143 | end 144 | end 145 | 146 | context 'name allows slashes' do 147 | context 'simple case' do 148 | virt_dir_name = SecureRandom.hex(10).to_s 149 | before(:all) do 150 | create_path('c:\inetpub\test_site') 151 | create_path('c:\inetpub\test_vdir') 152 | create_path('c:\inetpub\deeper') 153 | # create_site(site_name, true) 154 | end 155 | 156 | manifest = <<-HERE 157 | iis_virtual_directory{ "test_vdir": 158 | ensure => 'present', 159 | sitename => "#{site_name}", 160 | physicalpath => 'c:\\inetpub\\test_vdir', 161 | }-> 162 | iis_virtual_directory { 'test_vdir\deeper': 163 | name => 'test_vdir\deeper', 164 | ensure => 'present', 165 | sitename => '#{site_name}', 166 | physicalpath => 'c:\\inetpub\\deeper', 167 | } 168 | HERE 169 | iis_idempotent_apply('create iis virtual dir', manifest) 170 | 171 | after(:all) do 172 | remove_vdir(virt_dir_name) 173 | end 174 | end 175 | end 176 | 177 | context 'with invalid' do 178 | context 'physicalpath parameter defined' do 179 | virt_dir_name = SecureRandom.hex(10).to_s 180 | manifest = <<-HERE 181 | iis_virtual_directory { '#{virt_dir_name}': 182 | ensure => 'present', 183 | sitename => '#{site_name}', 184 | physicalpath => 'c:\\wakka' 185 | } 186 | HERE 187 | apply_failing_manifest('apply failing manifest', manifest) 188 | 189 | after(:all) do 190 | remove_vdir(virt_dir_name) 191 | end 192 | 193 | it 'iis_virtual_directory to be absent' do 194 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 195 | end 196 | end 197 | 198 | context 'physicalpath parameter not defined' do 199 | virt_dir_name = SecureRandom.hex(10).to_s 200 | manifest = <<-HERE 201 | iis_virtual_directory { '#{virt_dir_name}': 202 | ensure => 'present', 203 | sitename => '#{site_name}' 204 | } 205 | HERE 206 | apply_failing_manifest('apply failing manifest', manifest) 207 | 208 | after(:all) do 209 | remove_vdir(virt_dir_name) 210 | end 211 | 212 | it 'iis_virtual_directory to be absent' do 213 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 214 | end 215 | end 216 | end 217 | 218 | context 'with names prefixed with site name' do 219 | virt_dir_name = SecureRandom.hex(10).to_s 220 | name = "#{site_name}\\#{virt_dir_name}" 221 | manifest = <<-HERE 222 | file{ 'c:/foo': 223 | ensure => 'directory' 224 | }-> 225 | iis_virtual_directory { '#{name}': 226 | ensure => 'present', 227 | sitename => '#{site_name}', 228 | physicalpath => 'c:\\foo' 229 | } 230 | HERE 231 | 232 | iis_idempotent_apply('create iis virtual dir', manifest) 233 | 234 | it 'configures all expected parameters' do 235 | resource_data = resource('iis_virtual_directory', name) 236 | puppet_resource_should_show('ensure', 'present', resource_data) 237 | end 238 | 239 | it 'has the correct IIS provider path' do 240 | expected_path = "/#{site_name}/#{virt_dir_name}" 241 | expect(virtual_directory_path_exists?(site_name, expected_path)).to be(true) 242 | end 243 | 244 | it 'remove virt dir name' do 245 | remove_vdir(virt_dir_name, site_name) 246 | end 247 | end 248 | end 249 | end 250 | -------------------------------------------------------------------------------- /spec/acceptance/iis_virtual_directory_test_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper_acceptance' 4 | 5 | describe 'iis_virtual_directory', :suite_b do 6 | site_name = SecureRandom.hex(10) 7 | before(:context) do 8 | # Remove 'Default Web Site' to start from a clean slate 9 | remove_all_sites 10 | create_site(site_name, true) 11 | end 12 | 13 | after(:context) do 14 | remove_all_sites 15 | end 16 | 17 | context 'when configuring a virtual directory' do 18 | context 'with default parameters' do 19 | before(:all) do 20 | create_path('C:\foo') 21 | end 22 | 23 | virt_dir_name = SecureRandom.hex(10).to_s 24 | manifest = <<-HERE 25 | file{ 'c:/foo': 26 | ensure => 'directory' 27 | }-> 28 | file{ 'c:/foo2': 29 | ensure => 'directory' 30 | }-> 31 | iis_virtual_directory { '#{virt_dir_name}': 32 | ensure => 'present', 33 | sitename => '#{site_name}', 34 | physicalpath => 'c:\\foo' 35 | } 36 | HERE 37 | 38 | manifest2 = <<-HERE 39 | iis_virtual_directory { '#{virt_dir_name}': 40 | ensure => 'present', 41 | sitename => '#{site_name}', 42 | # Change capitalization to see if it breaks idempotency 43 | physicalpath => 'c:\\Foo' 44 | } 45 | HERE 46 | 47 | manifest3 = <<-HERE 48 | iis_virtual_directory { '#{virt_dir_name}': 49 | ensure => 'present', 50 | sitename => '#{site_name}', 51 | physicalpath => 'c:\\foo2' 52 | } 53 | HERE 54 | 55 | iis_idempotent_apply('create iis virtual dir', manifest) 56 | 57 | after(:all) do 58 | remove_vdir(virt_dir_name, site_name) 59 | end 60 | 61 | it 'iis_virtual_directory should be present' do 62 | puppet_resource_should_show('ensure', 'present', resource('iis_virtual_directory', virt_dir_name)) 63 | end 64 | 65 | it 'runs with no changes if capitolization changes' do 66 | apply_manifest(manifest2, catch_changes: true) 67 | end 68 | 69 | iis_idempotent_apply('change physical path', manifest3) 70 | 71 | it 'physicalpath to be configured' do 72 | puppet_resource_should_show('physicalpath', 'c:\\foo2', resource('iis_virtual_directory', virt_dir_name)) 73 | end 74 | end 75 | 76 | context 'with a password wrapped in Sensitive()' do 77 | virt_dir_name = SecureRandom.hex(10).to_s 78 | manifest = <<-HERE 79 | file{ 'c:/foo': 80 | ensure => 'directory' 81 | }-> 82 | iis_virtual_directory { '#{virt_dir_name}': 83 | ensure => 'present', 84 | sitename => '#{site_name}', 85 | physicalpath => 'c:\\foo', 86 | user_name => 'user', 87 | password => Sensitive('#@\\'454sdf'), 88 | } 89 | HERE 90 | 91 | iis_idempotent_apply('create iis virtual dir', manifest) 92 | 93 | it 'all parameters are configured' do 94 | resource_data = resource('iis_virtual_directory', virt_dir_name) 95 | [ 96 | 'ensure', 'present', 97 | 'user_name', 'user', 98 | 'password', '#@\\\'454sdf' 99 | ].each_slice(2) do |key, value| 100 | puppet_resource_should_show(key, value, resource_data) 101 | end 102 | end 103 | 104 | it 'remove virt dir name' do 105 | remove_vdir(virt_dir_name, site_name) 106 | end 107 | end 108 | 109 | context 'when virtual directory is removed' do 110 | virt_dir_name = SecureRandom.hex(10).to_s 111 | before(:all) do 112 | create_path('c:/foo') 113 | create_vdir(virt_dir_name, site_name, 'c:/foo') 114 | end 115 | 116 | manifest = <<-HERE 117 | iis_virtual_directory { '#{virt_dir_name}': 118 | sitename => '#{site_name}', 119 | ensure => 'absent' 120 | } 121 | HERE 122 | 123 | iis_idempotent_apply('remove iis virtual dir', manifest) 124 | 125 | after(:all) do 126 | remove_vdir(virt_dir_name) 127 | end 128 | 129 | it 'iis_virtual_directory to be absent' do 130 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 131 | end 132 | end 133 | 134 | context 'with invalid' do 135 | context 'when physicalpath parameter is defined' do 136 | virt_dir_name = SecureRandom.hex(10).to_s 137 | manifest = <<-HERE 138 | iis_virtual_directory { '#{virt_dir_name}': 139 | ensure => 'present', 140 | sitename => '#{site_name}', 141 | physicalpath => 'c:\\wakka' 142 | } 143 | HERE 144 | apply_failing_manifest('apply failing manifest', manifest) 145 | 146 | after(:all) do 147 | remove_vdir(virt_dir_name) 148 | end 149 | 150 | it 'iis_virtual_directory to be absent' do 151 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 152 | end 153 | end 154 | 155 | context 'when physicalpath parameter is not defined' do 156 | virt_dir_name = SecureRandom.hex(10).to_s 157 | manifest = <<-HERE 158 | iis_virtual_directory { '#{virt_dir_name}': 159 | ensure => 'present', 160 | sitename => '#{site_name}' 161 | } 162 | HERE 163 | apply_failing_manifest('apply failing manifest', manifest) 164 | 165 | after(:all) do 166 | remove_vdir(virt_dir_name) 167 | end 168 | 169 | it 'iis_virtual_directory to be absent' do 170 | puppet_resource_should_show('ensure', 'absent', resource('iis_virtual_directory', virt_dir_name)) 171 | end 172 | end 173 | end 174 | end 175 | end 176 | -------------------------------------------------------------------------------- /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/exit-27.ps1: -------------------------------------------------------------------------------- 1 | exit 27 2 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |c| 4 | c.mock_with :rspec 5 | end 6 | 7 | require 'puppetlabs_spec_helper/module_spec_helper' 8 | require 'rspec-puppet-facts' 9 | 10 | require 'spec_helper_local' if File.file?(File.join(File.dirname(__FILE__), 'spec_helper_local.rb')) 11 | 12 | include RspecPuppetFacts 13 | 14 | default_facts = { 15 | puppetversion: Puppet.version, 16 | facterversion: Facter.version, 17 | } 18 | 19 | default_fact_files = [ 20 | File.expand_path(File.join(File.dirname(__FILE__), 'default_facts.yml')), 21 | File.expand_path(File.join(File.dirname(__FILE__), 'default_module_facts.yml')), 22 | ] 23 | 24 | default_fact_files.each do |f| 25 | next unless File.exist?(f) && File.readable?(f) && File.size?(f) 26 | 27 | begin 28 | require 'deep_merge' 29 | default_facts.deep_merge!(YAML.safe_load(File.read(f), permitted_classes: [], permitted_symbols: [], aliases: true)) 30 | rescue StandardError => e 31 | RSpec.configuration.reporter.message "WARNING: Unable to load #{f}: #{e}" 32 | end 33 | end 34 | 35 | # read default_facts and merge them over what is provided by facterdb 36 | default_facts.each do |fact, value| 37 | add_custom_fact fact, value, merge_facts: true 38 | end 39 | 40 | RSpec.configure do |c| 41 | c.default_facts = default_facts 42 | c.before :each do 43 | # set to strictest setting for testing 44 | # by default Puppet runs at warning level 45 | Puppet.settings[:strict] = :warning 46 | Puppet.settings[:strict_variables] = true 47 | end 48 | c.filter_run_excluding(bolt: true) unless ENV['GEM_BOLT'] 49 | c.after(:suite) do 50 | RSpec::Puppet::Coverage.report!(0) 51 | end 52 | 53 | # Filter backtrace noise 54 | backtrace_exclusion_patterns = [ 55 | %r{spec_helper}, 56 | %r{gems}, 57 | ] 58 | 59 | if c.respond_to?(:backtrace_exclusion_patterns) 60 | c.backtrace_exclusion_patterns = backtrace_exclusion_patterns 61 | elsif c.respond_to?(:backtrace_clean_patterns) 62 | c.backtrace_clean_patterns = backtrace_exclusion_patterns 63 | end 64 | end 65 | 66 | # Ensures that a module is defined 67 | # @param module_name Name of the module 68 | def ensure_module_defined(module_name) 69 | module_name.split('::').reduce(Object) do |last_module, next_module| 70 | last_module.const_set(next_module, Module.new) unless last_module.const_defined?(next_module, false) 71 | last_module.const_get(next_module, false) 72 | end 73 | end 74 | 75 | # 'spec_overrides' from sync.yml will appear below this line 76 | -------------------------------------------------------------------------------- /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/spec_helper_acceptance_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'puppet_litmus' 4 | require 'singleton' 5 | 6 | # automatically load any shared examples or contexts 7 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 8 | 9 | class LitmusHelper 10 | include Singleton 11 | include PuppetLitmus 12 | end 13 | 14 | # This method allows a block to be passed in and if an exception is raised 15 | # that matches the 'error_matcher' matcher, the block will wait a set number 16 | # of seconds before retrying. 17 | # Params: 18 | # - max_retry_count - Max number of retries 19 | # - retry_wait_interval_secs - Number of seconds to wait before retry 20 | # - error_matcher - Matcher which the exception raised must match to allow retry 21 | # Example Usage: 22 | # retry_on_error_matching(3, 5, /OpenGPG Error/) do 23 | # apply_manifest(pp, :catch_failures => true) 24 | # end 25 | def retry_on_error_matching(max_retry_count = 3, retry_wait_interval_secs = 5, error_matcher = nil) 26 | try = 0 27 | begin 28 | try += 1 29 | yield 30 | rescue StandardError => e 31 | raise unless try < max_retry_count && (error_matcher.nil? || e.message =~ error_matcher) 32 | 33 | sleep retry_wait_interval_secs 34 | retry 35 | end 36 | end 37 | 38 | RSpec.configure do |c| 39 | # Configure all nodes in nodeset 40 | c.before :suite do 41 | # Install IIS and required features on the target host 42 | unless ENV['TARGET_HOST'].nil? || ENV['TARGET_HOST'] == 'localhost' 43 | LitmusHelper.instance.run_shell('Install-WindowsFeature -name Web-Server -IncludeManagementTools') 44 | LitmusHelper.instance.run_shell('Install-WindowsFeature -Name Web-HTTP-Errors') 45 | result = LitmusHelper.instance.run_shell('(Enable-WindowsOptionalFeature -Online -FeatureName IIS-WebServerRole -NoRestart).RestartNeeded') 46 | 47 | if result.stdout.split("\r\n").last == 'True' 48 | puts 'VM need reboot, hence doing force reboot' 49 | LitmusHelper.instance.run_shell('Restart-Computer -Force') 50 | end 51 | end 52 | 53 | # waiting for VM restart to complete and comes in running state 54 | retry_on_error_matching(120, 5, %r{.*}) do 55 | puts 'waiting for VM to restart..' 56 | LitmusHelper.instance.run_shell('ls') # random command to check connectivity to litmus host 57 | end 58 | end 59 | end 60 | 61 | def module_fixtures 62 | @module_fixtures ||= File.join(Dir.pwd, 'spec\fixtures\modules') 63 | end 64 | 65 | def resource(res, name) 66 | if ENV['TARGET_HOST'].nil? || ENV['TARGET_HOST'] == 'localhost' 67 | LitmusHelper.instance.run_shell("puppet resource #{res} #{name} --modulepath #{module_fixtures}") 68 | else 69 | LitmusHelper.instance.run_shell("puppet resource #{res} #{name}") 70 | end 71 | end 72 | 73 | def target_host_facts 74 | facter_version = run_shell('facter --version').stdout.strip.split('.')[0] 75 | @target_host_facts ||= if facter_version.include?('4') 76 | run_shell('facter --json --show-legacy').stdout.strip 77 | else 78 | run_shell('facter -p --json').stdout.strip 79 | end 80 | end 81 | 82 | def encode_command(command) 83 | Base64.strict_encode64(command.encode('UTF-16LE')) 84 | end 85 | 86 | def interpolate_powershell(command) 87 | "powershell.exe -EncodedCommand #{encode_command(command)}" 88 | end 89 | -------------------------------------------------------------------------------- /spec/spec_helper_local.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | dir = __dir__ 4 | $LOAD_PATH.unshift File.join(dir, 'lib') 5 | 6 | require 'puppet' 7 | require 'pathname' 8 | 9 | require 'tmpdir' 10 | require 'fileutils' 11 | 12 | # automatically load any shared examples or contexts 13 | Dir['./spec/support/**/*.rb'].sort.each { |f| require f } 14 | 15 | if Puppet.features.microsoft_windows? 16 | require 'puppet/util/windows/security' 17 | 18 | def take_ownership(path) 19 | path = path.tr('/', '\\') 20 | output = `takeown.exe /F #{path} /R /A /D Y 2>&1` 21 | return unless $CHILD_STATUS != 0 # check if the child process exited cleanly. 22 | 23 | puts "#{path} got error #{output}" 24 | end 25 | 26 | def installed_powershell_major_version 27 | provider = Puppet::Type.type(:iis_site).provider(:webadministration) 28 | 29 | begin 30 | psversion = provider.powershell_version.split('.').first 31 | # psversion = `#{powershell} -NoProfile -NonInteractive -NoLogo -ExecutionPolicy Bypass -Command \"$PSVersionTable.PSVersion.Major.ToString()\"`.chomp!.to_i 32 | puts "PowerShell major version number is #{psversion}" 33 | rescue StandardError 34 | puts 'Unable to determine PowerShell version' 35 | psversion = -1 36 | end 37 | psversion 38 | end 39 | end 40 | 41 | RSpec.configure do |config| 42 | tmpdir = Dir.mktmpdir('rspecrun_iis') 43 | oldtmpdir = Dir.tmpdir 44 | ENV['TMPDIR'] = tmpdir 45 | 46 | config.after :suite do 47 | # return to original tmpdir 48 | ENV['TMPDIR'] = oldtmpdir 49 | take_ownership(tmpdir) if Puppet::Util::Platform.windows? 50 | FileUtils.rm_rf(tmpdir) 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/support/files/www.puppet.local.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/puppetlabs-iis/6cecc8524e755e170e17968116bb1eb8998e9a14/spec/support/files/www.puppet.local.pfx -------------------------------------------------------------------------------- /spec/support/matchers/puppet_resource_should_show.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # The property named `property_name` should be shown in the output of puppet resource. If a non-nil value is specified, the matcher only 4 | # matches if the value is presented (quoted, or unquoted) 5 | def puppet_resource_should_show(property_name, value, result) 6 | # it "should report the correct #{property_name} value" do 7 | regex = if value.nil? 8 | %r{(#{property_name})(\s*)(=>)(\s*)} 9 | elsif value.is_a?(Array) 10 | %r{(#{property_name})(\s*)(=>)(\s*)(\[#{value.sort.map! { |v| "'#{v}'" }.join(', ')}\])} 11 | else 12 | %r{(#{property_name})(\s*)(=>)(\s*)('#{Regexp.escape(value)}'|#{Regexp.escape(value)})} 13 | end 14 | expect(result.stdout).to match(regex) 15 | # end 16 | end 17 | -------------------------------------------------------------------------------- /spec/support/matchers/type_provider_interface.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec::Matchers.define :require_string_for do |property| 4 | match do |type_class| 5 | config = { name: 'name' } 6 | config[property] = 2 7 | expect { 8 | type_class.new(config) 9 | }.to raise_error(Puppet::Error, %r{#{property} should be a String}) 10 | end 11 | failure_message do |type_class| 12 | "#{type_class} should require #{property} to be a String" 13 | end 14 | end 15 | 16 | RSpec::Matchers.define :require_hash_for do |property| 17 | match do |type_class| 18 | config = { name: 'name' } 19 | config[property] = 2 20 | expect { 21 | type_class.new(config) 22 | }.to raise_error(Puppet::Error, %r{#{property} should be a Hash}) 23 | end 24 | failure_message do |type_class| 25 | "#{type_class} should require #{property} to be a Hash" 26 | end 27 | end 28 | 29 | RSpec.shared_examples 'array properties' do |properties| 30 | properties.each do |property| 31 | it "requires #{property} to be an Array" do 32 | config = { name: 'name' } 33 | config[property] = 2 34 | expect { 35 | type_class.new(config) 36 | }.to raise_error(Puppet::Error, %r{#{property} should be an Array}) 37 | end 38 | end 39 | end 40 | 41 | RSpec::Matchers.define :require_integer_for do |property| 42 | match do |type_class| 43 | config = { name: 'name' } 44 | config[property] = 'string' 45 | expect { 46 | type_class.new(config) 47 | }.to raise_error(Puppet::Error, %r{#{property} should be an Integer}) 48 | end 49 | failure_message do |type_class| 50 | "#{type_class} should require #{property} to be a Integer" 51 | end 52 | end 53 | 54 | RSpec.shared_examples 'boolean properties' do |properties| 55 | properties.each do |property| 56 | it "requires #{property} to be boolean" do 57 | config = { name: 'name' } 58 | config[property] = 'string' 59 | expect { 60 | type_class.new(config) 61 | }.to raise_error(Puppet::Error, %r{Parameter #{property} failed on .*: Invalid value}) 62 | end 63 | end 64 | end 65 | 66 | RSpec::Matchers.define :be_read_only do |property| 67 | match do |type_class| 68 | config = { name: 'name' } 69 | config[property] = 'invalid' 70 | expect { 71 | type_class.new(config) 72 | }.to raise_error(Puppet::Error, %r{#{property} is read-only}) 73 | end 74 | failure_message do |type_class| 75 | "#{type_class} should require #{property} to be read-only" 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /spec/support/utilities/iis_application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def has_app(app_name) 4 | command = format_powershell_iis_command("Get-WebApplication -Name #{app_name}") 5 | !(run_shell(command).stdout =~ %r{Started}i).nil? 6 | end 7 | 8 | def create_app(site_name, app_name, directory = nil) 9 | physicalpath_dash = directory ? "-PhysicalPath #{directory}" : '' 10 | command = format_powershell_iis_command("New-WebApplication -Site #{site_name} -Name #{app_name} #{physicalpath_dash} -Force -ErrorAction Stop") 11 | run_shell(command) unless has_app(app_name) 12 | end 13 | 14 | def remove_app(app_name) 15 | command = format_powershell_iis_command("Remove-WebApplication -Name #{app_name}") 16 | run_shell(command) if has_app(app_name) 17 | end 18 | 19 | def create_virtual_directory(site, name, directory) 20 | command = format_powershell_iis_command("New-WebVirtualDirectory -Site #{site} -Name #{name} -physicalPath #{directory} -Force -ErrorAction Stop") 21 | run_shell(command) 22 | end 23 | -------------------------------------------------------------------------------- /spec/support/utilities/iis_application_pool.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def has_app_pool(pool_name) 4 | command = format_powershell_iis_command("Get-WebAppPoolState -Name #{pool_name}") 5 | result = run_shell(command, expect_failures: true) 6 | return false if result.exit_code != 0 7 | 8 | true 9 | end 10 | 11 | def app_pool_started(pool_name) 12 | command = format_powershell_iis_command("Get-WebAppPoolState -Name #{pool_name}") 13 | !(run_shell(command).stdout =~ %r{Started}i).nil? 14 | end 15 | 16 | def create_app_pool(pool_name) 17 | command = format_powershell_iis_command("New-WebAppPool -Name #{pool_name}") 18 | run_shell(command) unless has_app_pool(pool_name) 19 | end 20 | 21 | def remove_app_pool(pool_name) 22 | command = format_powershell_iis_command("Remove-WebAppPool -Name #{pool_name}") 23 | run_shell(command) if has_app_pool(pool_name) 24 | end 25 | 26 | def stop_app_pool(pool_name) 27 | command = format_powershell_iis_command("Stop-WebAppPool -Name #{pool_name}") 28 | run_shell(command) if app_pool_started(pool_name) 29 | end 30 | -------------------------------------------------------------------------------- /spec/support/utilities/iis_site.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def create_site(name, started, path = 'C:\inetpub\wwwroot') 4 | create_path(path) 5 | # These commands are executed in bash therefore things need to be escaped properly 6 | run_shell(format_powershell_iis_command("$params = @{Name = '#{name}'; PhysicalPath = '#{path}'}; " \ 7 | "If ((Get-ChildItem 'IIS:\\sites' | Measure-Object).Count -eq 0) { $params['Id'] = 1 }; New-Website @params")) 8 | command = if started == true 9 | format_powershell_iis_command("Start-Website -Name '#{name}'") 10 | else 11 | format_powershell_iis_command("Stop-Website -Name '#{name}'") 12 | end 13 | run_shell(command) 14 | end 15 | 16 | def create_path(path) 17 | run_shell(interpolate_powershell("New-Item -ItemType Directory -Force -Path '#{path}'")) 18 | end 19 | 20 | def remove_all_sites 21 | run_shell(format_powershell_iis_command('Get-Website | Remove-Website')) 22 | end 23 | 24 | def virtual_directory_path_exists?(site_name, path) 25 | ps_script = %( 26 | $vdir = Get-WebVirtualDirectory -Site "#{site_name}" | Where-Object { $_.Path -eq "#{path}" }; 27 | if ($vdir) { Write-Output "true" } else { Write-Output "false" } 28 | ).strip 29 | 30 | result = run_shell(format_powershell_iis_command(ps_script)) 31 | result[:stdout].strip.casecmp('true').zero? 32 | end 33 | -------------------------------------------------------------------------------- /spec/support/utilities/iis_virtual_directory.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def has_vdir(vdir_name) 4 | command = format_powershell_iis_command("Get-WebVirtualDirectory -Name #{vdir_name}") 5 | result = run_shell(command, expect_failure: true) 6 | if result.exit_code == 0 7 | !(result.stdout =~ %r{Name}i).nil? 8 | else 9 | false 10 | end 11 | end 12 | 13 | def create_vdir(vdir_name, site = 'foo', path = 'C:\inetpub\wwwroot') 14 | command = format_powershell_iis_command("New-WebVirtualDirectory -Name #{vdir_name} -Site #{site} -PhysicalPath #{path}") 15 | run_shell(command) unless has_vdir(vdir_name) 16 | end 17 | 18 | def remove_vdir(vdir_name, site = 'foo') 19 | command = format_powershell_iis_command("Remove-Item -Path 'IIS:\\Sites\\#{site}\\#{vdir_name}' -Recurse -ErrorAction Stop") 20 | run_shell(command) if has_vdir(vdir_name) 21 | end 22 | 23 | def stop_vdir(vdir_name) 24 | command = format_powershell_iis_command("Stop-WebVirtualDirectory -Name #{vdir_name}") 25 | run_shell(command) if has_vdir(vdir_name) 26 | end 27 | -------------------------------------------------------------------------------- /spec/support/utilities/utility_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | def format_powershell_iis_command(ps_command) 4 | command = [] 5 | command << 'Import-Module WebAdministration -ErrorAction Stop;' 6 | command << 'cd iis: ;' 7 | command << ps_command 8 | interpolate_powershell(command.join) 9 | end 10 | 11 | def create_selfsigned_cert(dnsname) 12 | # Due to most self-signed certificate methods don't work on 2008, instead use a test file with a fixed 13 | # hostname of www.puppet.local. The helper method will still keep the variable so that when 2008 is dropped 14 | # only this helper needs to be updated, not the tests. 15 | raise "Unable to create a self signed cert for DNS Name of #{dnsname}. Only www.puppet.local is allowed" unless dnsname == 'www.puppet.local' 16 | 17 | # Test Certificate fixture 18 | cert_filename_source = File.dirname(__FILE__) + "/../files/#{dnsname}.pfx" 19 | cert_filename_dest_windows = "C:/#{dnsname}.pfx" 20 | 21 | bolt_upload_file(cert_filename_source, cert_filename_dest_windows) 22 | 23 | # Defaults to personal machine store 24 | command = format_powershell_iis_command("& CERTUTIL -f -p puppet -importpfx '#{cert_filename_dest_windows}' NoRoot ") 25 | run_shell(command) 26 | 27 | # These commands are executed in bash therefore things need to be escaped properly 28 | command = format_powershell_iis_command("(Get-ChildItem -Path 'Cert:\\LocalMachine\\My' | Where-Object { $_.Subject -eq 'CN=#{dnsname}'} | Select-Object -First 1).Thumbprint") 29 | result = run_shell(command) 30 | result.stdout.chomp 31 | end 32 | 33 | def iis_idempotent_apply(work_description, manifest) 34 | it "#{work_description} runs without errors" do 35 | apply_manifest(manifest, catch_failures: true) 36 | end 37 | 38 | it "#{work_description} runs a second time without changes" do 39 | apply_manifest(manifest, catch_changes: true) 40 | end 41 | end 42 | 43 | def apply_failing_manifest(work_description, manifest) 44 | it "#{work_description} runs with errors" do 45 | apply_manifest(manifest, expect_failures: true) 46 | end 47 | end 48 | 49 | def puppet_resource_run(result) 50 | it 'returns successfully' do 51 | expect(result.exit_code).to eq 0 52 | end 53 | 54 | it 'does not return an error' do 55 | expect(result.stderr).not_to match(%r{\b}) 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/iis_application/webadministration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet/provider/iis_powershell' 5 | 6 | describe 'iis_application provider' do 7 | subject(:iis_application_provider) do 8 | resource = Puppet::Type.type(:iis_application).new(params) 9 | resource.provider = Puppet::Type.type(:iis_application).provider(:webadministration).new 10 | resource.provider 11 | end 12 | 13 | let(:facts) do 14 | { 15 | iis_version: '8.0', 16 | operatingsystem: 'Windows' 17 | } 18 | end 19 | 20 | describe 'creating from scratch' do 21 | context 'without physicalpath' do 22 | let(:params) do 23 | { title: 'foo\bar' } 24 | end 25 | 26 | before :each do 27 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run).with(%r{New-WebApplication}).and_return(exitcode: 0) 28 | end 29 | 30 | it { iis_application_provider.create } 31 | end 32 | 33 | context 'with nonexistent physicalpath' do 34 | let(:params) do 35 | { 36 | title: 'foo\bar', 37 | physicalpath: 'C:\noexist' 38 | } 39 | end 40 | 41 | before :each do 42 | allow(File).to receive(:exists?).with('C:\noexist').and_return(false) 43 | end 44 | 45 | it { expect { iis_application_provider.create }.to raise_error(RuntimeError, %r{doesn't exist}) } 46 | end 47 | 48 | context 'with existent physicalpath' do 49 | let(:params) do 50 | { 51 | title: 'foo\bar', 52 | physicalpath: 'C:\exist', 53 | sitename: 'foo' 54 | } 55 | end 56 | 57 | before :each do 58 | allow(File).to receive(:exist?).with('C:\exist').and_return(true) 59 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run).with(%r{New-WebApplication}).and_return(exitcode: 0) 60 | end 61 | 62 | it { iis_application_provider.create } 63 | end 64 | end 65 | 66 | describe 'converting virtual_directory' do 67 | let(:params) do 68 | { 69 | title: 'foo\bar', 70 | virtual_directory: 'IIS:\Sites\exists\vdir' 71 | } 72 | end 73 | 74 | before :each do 75 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run).with(%r{ConvertTo-WebApplication}).and_return(exitcode: 0) 76 | end 77 | 78 | it { iis_application_provider.create } 79 | end 80 | 81 | describe 'updating physicalpath' 82 | describe 'updating sslflags' 83 | describe 'updating authenticationinfo for IIS_Application' do 84 | let(:params) do 85 | { 86 | title: 'foo\bar', 87 | name: 'foo\bar', 88 | ensure: :present, 89 | sitename: 'foo', 90 | applicationname: 'bar', 91 | applicationpool: 'DefaultAppPool', 92 | enabledprotocols: 'http,https', 93 | authenticationinfo: { 94 | 'anonymous' => true, 95 | 'basic' => false, 96 | 'clientCertificateMapping' => false, 97 | 'digest' => false, 98 | 'iisClientCertificateMapping' => false, 99 | 'windows' => true, 100 | 'forms' => false 101 | }, 102 | } 103 | end 104 | let(:authenticationinfo) do 105 | { 106 | 'anonymous' => true, 107 | 'basic' => false, 108 | 'clientCertificateMapping' => false, 109 | 'digest' => false, 110 | 'iisClientCertificateMapping' => false, 111 | 'windows' => false, 112 | 'forms' => true 113 | } 114 | end 115 | 116 | before :each do 117 | cmdtext = "$webApplication = Get-WebApplication -Site 'foo' -Name 'bar'" 118 | cmdtext += "\n" 119 | authenticationinfo.each do |auth, enable| 120 | if auth == 'forms' # Forms authentication requires a different command 121 | mode_value = enable ? 'Forms' : 'None' 122 | cmdtext += "Set-WebConfigurationProperty -PSPath 'IIS:/Sites/foo/bar' " \ 123 | "-Filter 'system.web/authentication' -Name 'mode' -Value '#{mode_value}' -ErrorAction Stop\n" 124 | else 125 | cmdtext += "Set-WebConfigurationProperty -Location 'foo/bar' " \ 126 | "-Filter 'system.webserver/security/authentication/#{auth}Authentication' -Name enabled -Value #{enable} -ErrorAction Stop\n" 127 | end 128 | end 129 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run).and_return(exitcode: 0) 130 | end 131 | 132 | it 'updates value' do 133 | iis_application_provider.authenticationinfo = authenticationinfo 134 | iis_application_provider.update 135 | end 136 | end 137 | 138 | describe 'updating enabledprotocols' do 139 | let(:params) do 140 | { 141 | title: 'foo\bar' 142 | } 143 | end 144 | 145 | before :each do 146 | cmdtext = "$webApplication = Get-WebApplication -Site 'foo' -Name 'bar'" 147 | cmdtext += "\n" 148 | cmdtext += "Set-WebConfigurationProperty -Filter 'system.applicationHost/sites/site[@name=\"foo\"]/application[@path=\"/bar\"]' -Name enabledProtocols -Value 'http,https,net.tcp'" 149 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run) \ 150 | .with(cmdtext) \ 151 | .and_return(exitcode: 0) 152 | end 153 | 154 | it 'updates value' do 155 | iis_application_provider.enabledprotocols = 'http,https,net.tcp' 156 | iis_application_provider.update 157 | end 158 | end 159 | end 160 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/iis_application_pool/webadministration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | provider_class = Puppet::Type.type(:iis_application_pool).provider(:webadministration) 6 | 7 | describe provider_class do 8 | let(:facts) do 9 | { 10 | iis_version: '8.0', 11 | operatingsystem: 'Windows' 12 | } 13 | end 14 | 15 | let(:resource) do 16 | result = Puppet::Type.type(:iis_application_pool).new(name: 'iis_application_pool') 17 | result.provider = subject 18 | result 19 | end 20 | 21 | it 'is an instance of the correct provider' do 22 | expect(resource.provider).to be_an_instance_of Puppet::Type::Iis_application_pool::ProviderWebadministration 23 | end 24 | 25 | [:name].each do |method| 26 | it "responds to the class method #{method}" do 27 | expect(provider_class).to respond_to(method) 28 | end 29 | end 30 | 31 | [:exists?, :create, :destroy, :update].each do |method| 32 | it "responds to the instance method #{method}" do 33 | expect(provider_class.new).to respond_to(method) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/iis_site/webadministration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet/provider/iis_powershell' 5 | 6 | describe Puppet::Type.type(:iis_site).provider(:webadministration) do 7 | subject(:webadministration) { described_class.new } 8 | 9 | let(:resource) do 10 | result = Puppet::Type.type(:iis_site).new(name: 'iis_site') 11 | result.provider = webadministration 12 | result 13 | end 14 | 15 | context 'verify provider' do 16 | it { is_expected.to be_an_instance_of Puppet::Type::Iis_site::ProviderWebadministration } 17 | it { is_expected.to respond_to(:create) } 18 | it { is_expected.to respond_to(:exists?) } 19 | it { is_expected.to respond_to(:destroy) } 20 | it { is_expected.to respond_to(:start) } 21 | it { is_expected.to respond_to(:stop) } 22 | 23 | context 'verify ssl? function' do 24 | it { is_expected.to respond_to(:ssl?) } 25 | 26 | it 'returns true protocol == https' do 27 | resource[:bindings] = { 28 | 'protocol' => 'https', 29 | 'bindinginformation' => '*:443:', 30 | 'sslflags' => 0, 31 | 'certificatehash' => 'D69B5C3315FF0DA09AF640784622CF20DC51F03E', 32 | 'certificatestorename' => 'My' 33 | } 34 | expect(webadministration.ssl?).to be true 35 | end 36 | 37 | it 'returns true bindings is an array' do 38 | resource[:bindings] = [{ 39 | 'protocol' => 'https', 40 | 'bindinginformation' => '*:443:', 41 | 'sslflags' => 0, 42 | 'certificatehash' => 'D69B5C3315FF0DA09AF640784622CF20DC51F03E', 43 | 'certificatestorename' => 'My' 44 | }, 45 | { 46 | 'protocol' => 'http', 47 | 'bindinginformation' => '*:8080:' 48 | }] 49 | expect(webadministration.ssl?).to be true 50 | end 51 | 52 | it 'returns false if no https bindings are specified' do 53 | resource[:bindings] = { 54 | 'protocol' => 'http', 55 | 'bindinginformation' => '*:8080:' 56 | } 57 | expect(webadministration.ssl?).to be false 58 | end 59 | end 60 | end 61 | 62 | context 'updating authenticationinfo for IIS_Site' do 63 | let(:iis_site_resource) do 64 | result = Puppet::Type.type(:iis_site).new( 65 | name: 'foo', 66 | ensure: :present, 67 | physicalpath: 'C:\inetpub\wwwroot\foo', 68 | applicationpool: 'MyAppPool', 69 | enabledprotocols: 'http,https', 70 | authenticationinfo: { 71 | 'anonymous' => true, 72 | 'basic' => false, 73 | 'clientCertificateMapping' => false, 74 | 'digest' => false, 75 | 'iisClientCertificateMapping' => false, 76 | 'windows' => false, 77 | 'forms' => true 78 | }, 79 | ) 80 | result.provider = webadministration 81 | result 82 | end 83 | let(:authenticationinfo) do 84 | { 85 | 'anonymous' => true, 86 | 'basic' => false, 87 | 'clientCertificateMapping' => false, 88 | 'digest' => false, 89 | 'iisClientCertificateMapping' => false, 90 | 'windows' => false, 91 | 'forms' => true 92 | } 93 | end 94 | 95 | before :each do 96 | cmd = [] 97 | cmd << described_class.ps_script_content('_setwebsite', iis_site_resource) 98 | cmd << described_class.ps_script_content('trysetitemproperty', iis_site_resource) 99 | cmd << described_class.ps_script_content('generalproperties', iis_site_resource) 100 | cmd << described_class.ps_script_content('bindingproperty', iis_site_resource) 101 | cmd << described_class.ps_script_content('logproperties', iis_site_resource) 102 | cmd << described_class.ps_script_content('limitsproperty', iis_site_resource) 103 | cmd << described_class.ps_script_content('serviceautostartprovider', iis_site_resource) 104 | authenticationinfo.each do |auth, enable| 105 | args = [] 106 | if auth == 'forms' # Forms authentication requires a different command 107 | mode_value = enable ? 'Forms' : 'None' 108 | args << "-Filter 'system.web/authentication'" 109 | args << "-PSPath 'IIS:\\Sites\\foo'" 110 | args << "-Name 'mode'" 111 | args << "-Value '#{mode_value}'" 112 | else 113 | args << "-Filter 'system.webserver/security/authentication/#{auth}Authentication'" 114 | args << "-PSPath 'IIS:\\'" 115 | args << "-Location 'foo'" 116 | args << '-Name enabled' 117 | args << "-Value #{enable}" 118 | end 119 | cmd << "Set-WebConfigurationProperty #{args.join(' ')} -ErrorAction Stop\n" 120 | end 121 | allow(Puppet::Provider::IIS_PowerShell).to receive(:run).and_return(exitcode: 0) 122 | end 123 | 124 | it 'updates value' do 125 | webadministration.authenticationinfo = authenticationinfo 126 | webadministration.update 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /spec/unit/puppet/provider/iis_virtual_directory/webadministration_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet/type' 5 | require 'puppet/type/iis_virtual_directory' 6 | 7 | describe Puppet::Type.type(:iis_virtual_directory) do 8 | subject { resource } 9 | 10 | let(:resource) { described_class.new(name: 'iis_virtual_directory') } 11 | 12 | it { is_expected.to be_a Puppet::Type::Iis_virtual_directory } 13 | 14 | describe 'parameter :name' do 15 | subject { resource.parameters[:name] } 16 | 17 | it { is_expected.to be_isnamevar } 18 | 19 | ['value', 'value\with\slashes', '0123456789_-'].each do |value| 20 | it "accepts '#{value}'" do 21 | expect { resource[:name] = value }.not_to raise_error 22 | end 23 | end 24 | end 25 | 26 | context 'parameter :sitename' do 27 | it 'does not allow nil' do 28 | expect { 29 | resource[:sitename] = nil 30 | }.to raise_error(Puppet::Error, %r{Got nil value for sitename}) 31 | end 32 | 33 | it 'does not allow empty' do 34 | expect { 35 | resource[:sitename] = '' 36 | }.to raise_error(Puppet::Error, %r{A non-empty sitename must be specified}) 37 | end 38 | end 39 | 40 | context 'parameter :application' do 41 | it 'does not allow nil' do 42 | expect { 43 | resource[:application] = nil 44 | }.to raise_error(Puppet::Error, %r{Got nil value for application}) 45 | end 46 | 47 | it 'does not allow empty' do 48 | expect { 49 | resource[:application] = '' 50 | }.to raise_error(Puppet::Error, %r{A non-empty application must be specified}) 51 | end 52 | end 53 | 54 | context 'parameter :physicalpath' do 55 | it 'does not allow nil' do 56 | expect { 57 | resource[:physicalpath] = nil 58 | }.to raise_error(Puppet::Error, %r{Got nil value for physicalpath}) 59 | end 60 | 61 | it 'does not allow empty' do 62 | expect { 63 | resource[:physicalpath] = '' 64 | }.to raise_error(Puppet::Error, %r{A non-empty physicalpath must be specified}) 65 | end 66 | 67 | it 'accepts forward and back slashes' do 68 | resource[:physicalpath] = 'c:/thisstring-location/value/somefile.txt' 69 | resource[:physicalpath] = 'c:\\thisstring-location\\value\\somefile.txt' 70 | end 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/iis_application_pool_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'iis_application_pool' do 6 | let(:type_class) { Puppet::Type.type(:iis_application_pool) } 7 | 8 | let :params do 9 | [ 10 | :name, 11 | ] 12 | end 13 | 14 | let :properties do 15 | [ 16 | :ensure, 17 | :state, 18 | :managed_pipeline_mode, 19 | :managed_runtime_version, 20 | :auto_start, 21 | :clr_config_file, 22 | :enable32_bit_app_on_win64, 23 | :enable_configuration_override, 24 | :managed_pipeline_mode, 25 | :managed_runtime_loader, 26 | :managed_runtime_version, 27 | :pass_anonymous_token, 28 | :start_mode, 29 | :queue_length, 30 | :cpu_action, 31 | :cpu_limit, 32 | :cpu_reset_interval, 33 | :cpu_smp_affinitized, 34 | :cpu_smp_processor_affinity_mask, 35 | :cpu_smp_processor_affinity_mask2, 36 | :identity_type, 37 | :idle_timeout, 38 | :idle_timeout_action, 39 | :load_user_profile, 40 | :log_event_on_process_model, 41 | :logon_type, 42 | :manual_group_membership, 43 | :max_processes, 44 | :pinging_enabled, 45 | :ping_interval, 46 | :ping_response_time, 47 | :set_profile_environment, 48 | :shutdown_time_limit, 49 | :startup_time_limit, 50 | :orphan_action_exe, 51 | :orphan_action_params, 52 | :orphan_worker_process, 53 | :load_balancer_capabilities, 54 | :rapid_fail_protection, 55 | :rapid_fail_protection_interval, 56 | :rapid_fail_protection_max_crashes, 57 | :auto_shutdown_exe, 58 | :auto_shutdown_params, 59 | :disallow_overlapping_rotation, 60 | :disallow_rotation_on_config_change, 61 | :log_event_on_recycle, 62 | :restart_memory_limit, 63 | :restart_private_memory_limit, 64 | :restart_requests_limit, 65 | :restart_time_limit, 66 | :restart_schedule, 67 | :user_name, 68 | :password, 69 | ] 70 | end 71 | 72 | let :minimal_config do 73 | { 74 | name: 'Some App Pool' 75 | } 76 | end 77 | 78 | let :optional_config do 79 | {} 80 | end 81 | 82 | let :default_config do 83 | minimal_config.merge(optional_config) 84 | end 85 | 86 | it 'has expected properties' do 87 | expect(type_class.properties.map(&:name)).to include(*properties) 88 | end 89 | 90 | it 'has expected parameters' do 91 | expect(type_class.parameters).to include(*params) 92 | end 93 | 94 | it 'does not have unexpected properties' do 95 | expect(properties).to include(*type_class.properties.map(&:name)) 96 | end 97 | 98 | it 'does not have unexpected parameters' do 99 | expect(params + [:provider]).to include(*type_class.parameters) 100 | end 101 | 102 | [:state, 103 | :managed_pipeline_mode, 104 | :managed_runtime_version, 105 | :start_mode, 106 | :cpu_action, 107 | :idle_timeout_action, 108 | :logon_type, 109 | :load_balancer_capabilities, 110 | :identity_type].freeze 111 | 112 | [:name, 113 | :clr_config_file, 114 | :managed_runtime_loader, 115 | :log_event_on_process_model, 116 | :orphan_action_exe, 117 | :orphan_action_params, 118 | :rapid_fail_protection, 119 | :auto_shutdown_exe, 120 | :auto_shutdown_params, 121 | :log_event_on_recycle].each do |property| 122 | it "requires #{property} to be a string" do 123 | expect(type_class).to require_string_for(property) 124 | end 125 | end 126 | 127 | [ 128 | :auto_start, 129 | :enable32_bit_app_on_win64, 130 | :enable_configuration_override, 131 | :pass_anonymous_token, 132 | :cpu_smp_affinitized, 133 | :load_user_profile, 134 | :manual_group_membership, 135 | :pinging_enabled, 136 | :set_profile_environment, 137 | :orphan_worker_process, 138 | :disallow_overlapping_rotation, 139 | :disallow_rotation_on_config_change, 140 | ].each do |property| 141 | it "requires #{property} to be boolean" do 142 | config = { name: 'name' } 143 | config[property] = 'string' 144 | expect { 145 | type_class.new(config) 146 | }.to raise_error(Puppet::Error, %r{Parameter #{property} failed on .*: Invalid value}) 147 | end 148 | end 149 | 150 | [ 151 | { cpu_reset_interval: '00:00:00' }, 152 | { idle_timeout: '00:00:00' }, 153 | { ping_interval: '00:00:00' }, 154 | { ping_response_time: '00:00:00' }, 155 | { shutdown_time_limit: '00:00:00' }, 156 | { startup_time_limit: '00:00:00' }, 157 | { rapid_fail_protection_interval: '00:00:00' }, 158 | { restart_time_limit: '00:00:00' }, 159 | { restart_time_limit: '1.00:00:00' }, 160 | ].each do |property| 161 | prop = property.keys[0] 162 | upper_limit = property[property.keys[0]] 163 | 164 | it "supports #{prop} as a formatted time" do 165 | config = { name: 'name' } 166 | config[prop] = upper_limit 167 | expect { 168 | type_class.new(config) 169 | }.not_to raise_error 170 | end 171 | 172 | it "requires #{prop} to be a formatted time" do 173 | config = { name: 'name' } 174 | config[prop] = 'string' 175 | expect { 176 | type_class.new(config) 177 | }.to raise_error(Puppet::Error, %r{#{prop} should match datetime format 00:00:00 or 0.00:00:00}) 178 | end 179 | end 180 | 181 | [ 182 | { queue_length: 65_535 }, 183 | { cpu_limit: 100_000 }, 184 | { cpu_smp_processor_affinity_mask: nil }, 185 | { cpu_smp_processor_affinity_mask2: nil }, 186 | { max_processes: 2_147_483_647 }, 187 | { rapid_fail_protection_max_crashes: 2_147_483_647 }, 188 | { restart_memory_limit: nil }, 189 | { restart_private_memory_limit: nil }, 190 | { restart_requests_limit: nil }, 191 | ].each do |property| 192 | prop = property.keys[0] 193 | upper_limit = property[property.keys[0]] 194 | it "requires #{prop} to be a number" do 195 | expect(type_class).to require_integer_for(prop) 196 | end 197 | 198 | next unless upper_limit 199 | 200 | it "requires #{prop} to be less than #{upper_limit}" do 201 | expect { 202 | upper_limit += 1 203 | config = { name: 'sample' } 204 | config[prop] = upper_limit 205 | type_class.new(config) 206 | }.to raise_error(Puppet::Error, %r{#{prop} should be less than or equal to #{upper_limit}}) 207 | end 208 | end 209 | 210 | context 'parameter :name' do 211 | it 'does not allow nil' do 212 | expect { 213 | pool = type_class.new( 214 | name: 'foo', 215 | ) 216 | pool[:name] = nil 217 | }.to raise_error(Puppet::Error, %r{Got nil value for name}) 218 | end 219 | 220 | it 'does not allow empty' do 221 | expect { 222 | pool = type_class.new( 223 | name: 'foo', 224 | ) 225 | pool[:name] = '' 226 | }.to raise_error(Puppet::ResourceError, %r{A non-empty name must}) 227 | end 228 | 229 | ['value', 'value with spaces', 'UPPER CASE', '0123456789_-', 'With.Period'].each do |value| 230 | it "accepts '#{value}'" do 231 | expect { 232 | pool = type_class.new( 233 | name: 'foo', 234 | ) 235 | pool[:name] = value 236 | }.not_to raise_error 237 | end 238 | end 239 | 240 | ['*', '()', '[]', '!@'].each do |value| 241 | it "rejects '#{value}'" do 242 | expect { 243 | pool = type_class.new( 244 | name: 'foo', 245 | ) 246 | pool[:name] = value 247 | }.to raise_error(Puppet::ResourceError, %r{is not a valid name}) 248 | end 249 | end 250 | end 251 | 252 | context 'parameter :restart_schedule' do 253 | it 'accepts a formatted time' do 254 | pool = type_class.new( 255 | name: 'foo', 256 | restart_schedule: '00:00:00', 257 | ) 258 | expect(pool[:restart_schedule]).to eq(['00:00:00']) 259 | end 260 | 261 | it 'accepts an array of formatted times' do 262 | pool = type_class.new( 263 | name: 'foo', 264 | restart_schedule: ['00:00:00'], 265 | ) 266 | expect(pool[:restart_schedule]).to eq(['00:00:00']) 267 | end 268 | 269 | it 'rejects a value that is not a formatted time' do 270 | expect { 271 | config = { 272 | name: 'foo', 273 | restart_schedule: 'bottle' 274 | } 275 | type_class.new(config) 276 | }.to raise_error(Puppet::Error, %r{Parameter restart_schedule failed}) 277 | end 278 | 279 | it 'rejects a formatted time with a granularity of less than 60 seconds' do 280 | expect { 281 | config = { 282 | name: 'foo', 283 | restart_schedule: '00:00:45' 284 | } 285 | type_class.new(config) 286 | }.to raise_error(Puppet::Error, %r{Parameter restart_schedule failed}) 287 | end 288 | end 289 | 290 | it 'defaults ensure to present' do 291 | pool = type_class.new( 292 | name: 'foo', 293 | ) 294 | expect(pool[:ensure]).to eq(:present) 295 | end 296 | 297 | context 'with a minimal set of properties' do 298 | let :config do 299 | minimal_config 300 | end 301 | 302 | let :app_pool do 303 | type_class.new(config) 304 | end 305 | 306 | it 'is valid' do 307 | expect { app_pool }.not_to raise_error 308 | end 309 | end 310 | 311 | # See https://github.com/puppetlabs/puppetlabs-azure/blob/main/spec/unit/type/azure_vm_spec.rb for more examples 312 | end 313 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/iis_application_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe 'iis_application' do 6 | subject(:iis_application_type) do 7 | Puppet::Type.type(:iis_application).new(params) 8 | end 9 | 10 | context 'specifying title with sitename' do 11 | let(:params) do 12 | { 13 | title: 'bar', 14 | sitename: 'foo' 15 | } 16 | end 17 | 18 | it { expect(iis_application_type[:sitename]).to eq 'foo' } 19 | it { expect(iis_application_type[:applicationname]).to eq 'bar' } 20 | end 21 | 22 | context 'specifying sitename and applicationname' do 23 | let(:params) do 24 | { 25 | title: 'anything else', 26 | sitename: 'foo', 27 | applicationname: 'bar' 28 | } 29 | end 30 | 31 | it { expect(iis_application_type[:sitename]).to eq 'foo' } 32 | it { expect(iis_application_type[:applicationname]).to eq 'bar' } 33 | end 34 | 35 | context 'specifying virtual_directory' do 36 | let(:params) do 37 | { 38 | title: 'foo\bar', 39 | virtual_directory: 'IIS:\Sites\foo\bar' 40 | } 41 | end 42 | 43 | it { expect(iis_application_type[:virtual_directory]).to eq 'IIS:\Sites\foo\bar' } 44 | end 45 | 46 | context 'specifying virtual_directory with no provider path' do 47 | let(:params) do 48 | { 49 | title: 'foo\bar', 50 | virtual_directory: 'foo\bar' 51 | } 52 | end 53 | 54 | it { expect(iis_application_type[:virtual_directory]).to eq 'IIS:/Sites/foo\bar' } 55 | end 56 | 57 | context 'specifying authenticationinfo' do 58 | let(:params) do 59 | { 60 | title: 'foo\bar', 61 | authenticationinfo: { 62 | 'basic' => true, 63 | 'anonymous' => true 64 | } 65 | } 66 | end 67 | 68 | it { expect(iis_application_type[:authenticationinfo]).to eq('basic' => true, 'anonymous' => true) } 69 | end 70 | 71 | context 'specifying physicalpath' do 72 | let(:params) do 73 | { 74 | title: 'foo\bar', 75 | physicalpath: 'C:\test' 76 | } 77 | end 78 | 79 | it { expect(iis_application_type[:physicalpath]).to eq 'C:\test' } 80 | end 81 | 82 | describe 'parameter :applicationpool' do 83 | ['value', 'value with spaces', 'UPPER CASE', '0123456789_-', 'With.Period'].each do |value| 84 | context "when '#{value}'" do 85 | let(:params) do 86 | { 87 | title: 'foo\bar', 88 | applicationpool: value 89 | } 90 | end 91 | 92 | it { expect { iis_application_type }.not_to raise_error } 93 | end 94 | end 95 | ['*', '()', '[]', '!@'].each do |value| 96 | context "when '#{value}'" do 97 | let(:params) do 98 | { 99 | title: 'foo\bar', 100 | applicationpool: value 101 | } 102 | end 103 | 104 | it { expect { iis_application_type }.to raise_error(Puppet::ResourceError, %r{is not a valid applicationpool}) } 105 | end 106 | end 107 | end 108 | 109 | describe 'parameter :sitename' do 110 | ['value', 'value with spaces', 'UPPER CASE', '0123456789_-', 'With.Period'].each do |value| 111 | context "when '#{value}'" do 112 | let(:params) do 113 | { 114 | title: 'foo\bar', 115 | sitename: value 116 | } 117 | end 118 | 119 | it { expect { iis_application_type }.not_to raise_error } 120 | end 121 | end 122 | ['*', '()', '[]', '!@'].each do |value| 123 | context "when '#{value}'" do 124 | let(:params) do 125 | { 126 | title: 'foo\bar', 127 | sitename: value 128 | } 129 | end 130 | 131 | it { expect { iis_application_type }.to raise_error(Puppet::ResourceError, %r{is not a valid sitename}) } 132 | end 133 | end 134 | end 135 | 136 | describe 'applicationpool' do 137 | context 'when empty' do 138 | let(:params) do 139 | { 140 | title: 'foo\bar', 141 | applicationpool: '' 142 | } 143 | end 144 | 145 | it { expect { iis_application_type }.to raise_error(Puppet::Error, %r{applicationpool}) } 146 | end 147 | 148 | context 'when invalid' do 149 | let(:params) do 150 | { 151 | title: 'foo\bar', 152 | applicationpool: 'sweet!' 153 | } 154 | end 155 | 156 | it { expect { iis_application_type }.to raise_error(Puppet::Error, %r{applicationpool}) } 157 | end 158 | 159 | context 'when valid' do 160 | let(:params) do 161 | { 162 | title: 'foo\bar', 163 | applicationpool: 'OtherPool' 164 | } 165 | end 166 | 167 | it { expect(iis_application_type[:applicationpool]).to eq 'OtherPool' } 168 | end 169 | end 170 | 171 | describe 'sslflags' do 172 | context 'single item' do 173 | let(:params) do 174 | { 175 | title: 'foo\bar', 176 | sslflags: 'Ssl' 177 | } 178 | end 179 | 180 | it { expect(iis_application_type[:sslflags]).to eq([:Ssl]) } 181 | end 182 | 183 | context 'array of items' do 184 | let(:params) do 185 | { 186 | title: 'foo\bar', 187 | sslflags: [ 188 | 'Ssl', 189 | 'SslNegotiateCert', 190 | ] 191 | } 192 | end 193 | 194 | it { expect(iis_application_type[:sslflags]).to eq([:Ssl, :SslNegotiateCert]) } 195 | end 196 | 197 | context 'array with invalid items' do 198 | let(:params) do 199 | { 200 | title: 'foo\bar', 201 | sslflags: [ 202 | 'SslOn', 203 | 'SslNegotiateCert', 204 | ] 205 | } 206 | end 207 | 208 | it { expect { iis_application_type }.to raise_error(Puppet::Error, %r{sslflags}) } 209 | end 210 | end 211 | 212 | describe 'enabledprotocols' do 213 | context 'should accept valid string value' do 214 | let(:params) do 215 | { 216 | title: 'foo\bar', 217 | enabledprotocols: 'http,https,net.pipe,net.tcp,net.msmq,msmq.formatname' 218 | } 219 | end 220 | 221 | it { expect(iis_application_type[:enabledprotocols]).to eq('http,https,net.pipe,net.tcp,net.msmq,msmq.formatname') } 222 | end 223 | 224 | context 'should not allow nil' do 225 | let(:params) do 226 | { 227 | title: 'foo\bar', 228 | enabledprotocols: nil 229 | } 230 | end 231 | 232 | it { expect { iis_application_type }.to raise_error(Puppet::Error, %r{Got nil value for enabledprotocols}) } 233 | end 234 | 235 | context 'should not allow empty' do 236 | let(:params) do 237 | { 238 | title: 'foo\bar', 239 | enabledprotocols: '' 240 | } 241 | end 242 | 243 | it { expect { iis_application_type }.to raise_error(Puppet::ResourceError, %r{Invalid value ''. Valid values are http, https, net.pipe, net.tcp, net.msmq, msmq.formatname}) } 244 | end 245 | 246 | context 'should not accept invalid string value' do 247 | let(:params) do 248 | { 249 | title: 'foo\bar', 250 | enabledprotocols: 'woot' 251 | } 252 | end 253 | 254 | it { expect { iis_application_type }.to raise_error(Puppet::ResourceError, %r{Invalid protocol 'woot'. Valid values are http, https, net.pipe, net.tcp, net.msmq, msmq.formatname}) } 255 | end 256 | end 257 | end 258 | -------------------------------------------------------------------------------- /spec/unit/puppet/type/iis_virtual_directory_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | describe Puppet::Type.type(:iis_virtual_directory) do 6 | subject { resource } 7 | 8 | let(:type_class) { Puppet::Type.type(:iis_virtual_directory) } 9 | let(:resource) { described_class.new(name: 'iis_virtual_directory') } 10 | 11 | let :params do 12 | [ 13 | :name, 14 | ] 15 | end 16 | 17 | let :properties do 18 | [ 19 | :ensure, 20 | :sitename, 21 | :application, 22 | :physicalpath, 23 | :user_name, 24 | :password, 25 | ] 26 | end 27 | 28 | it 'has expected properties' do 29 | expect(type_class.properties.map(&:name)).to include(*properties) 30 | end 31 | 32 | it 'has expected parameters' do 33 | expect(type_class.parameters).to include(*params) 34 | end 35 | 36 | it 'does not have unexpected properties' do 37 | expect(properties).to include(*type_class.properties.map(&:name)) 38 | end 39 | 40 | it 'does not have unexpected parameters' do 41 | expect(params + [:provider]).to include(*type_class.parameters) 42 | end 43 | 44 | describe 'parameter :name' do 45 | subject { resource.parameters[:name] } 46 | 47 | it { is_expected.to be_isnamevar } 48 | 49 | it 'does not allow nil' do 50 | expect { 51 | resource[:name] = nil 52 | }.to raise_error(Puppet::Error, %r{Got nil value for name}) 53 | end 54 | 55 | it 'does not allow empty' do 56 | expect { 57 | resource[:name] = '' 58 | }.to raise_error(Puppet::ResourceError, %r{A non-empty name must be specified}) 59 | end 60 | end 61 | 62 | describe 'parameter :sitename' do 63 | subject { resource.parameters[:name] } 64 | 65 | it 'does not allow nil' do 66 | expect { 67 | resource[:sitename] = nil 68 | }.to raise_error(Puppet::Error, %r{Got nil value for sitename}) 69 | end 70 | 71 | it 'does not allow empty' do 72 | expect { 73 | resource[:sitename] = '' 74 | }.to raise_error(Puppet::ResourceError, %r{A non-empty sitename must be specified}) 75 | end 76 | end 77 | 78 | describe 'parameter :application' do 79 | subject { resource.parameters[:name] } 80 | 81 | it 'does not allow nil' do 82 | expect { 83 | resource[:application] = nil 84 | }.to raise_error(Puppet::Error, %r{Got nil value for application}) 85 | end 86 | 87 | it 'does not allow empty' do 88 | expect { 89 | resource[:application] = '' 90 | }.to raise_error(Puppet::ResourceError, %r{A non-empty application must be specified}) 91 | end 92 | end 93 | 94 | context 'parameter :physicalpath' do 95 | it 'does not allow nil' do 96 | expect { 97 | resource[:physicalpath] = nil 98 | }.to raise_error(Puppet::Error, %r{Got nil value for physicalpath}) 99 | end 100 | 101 | it 'does not allow empty' do 102 | expect { 103 | resource[:physicalpath] = '' 104 | }.to raise_error(Puppet::Error, %r{A non-empty physicalpath must be specified}) 105 | end 106 | 107 | it 'accepts forward-slash and backslash paths' do 108 | resource[:physicalpath] = 'c:/directory/subdirectory' 109 | resource[:physicalpath] = 'c:\\directory\\subdirectory' 110 | end 111 | end 112 | 113 | context 'parameter :user_name' do 114 | it 'requires it to be a string' do 115 | expect(type_class).to require_string_for(:user_name) 116 | end 117 | end 118 | 119 | context 'parameter :password' do 120 | it 'requires it to be a string' do 121 | expect(type_class).to require_string_for(:password) 122 | end 123 | end 124 | end 125 | -------------------------------------------------------------------------------- /spec/unit/puppet_x/puppetlabs/iis/iis_version_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | require 'puppet/type' 5 | require 'puppet_x/puppetlabs/iis/iis_version' 6 | 7 | describe 'iis_version' do 8 | before(:each) do 9 | skip 'Not on Windows platform' unless Puppet::Util::Platform.windows? 10 | end 11 | 12 | describe 'when iis is installed' do 13 | let(:ps) { PuppetX::PuppetLabs::IIS::IISVersion } 14 | 15 | it 'detects a iis version' do 16 | expect_any_instance_of(Win32::Registry).to receive(:open) 17 | .with('SOFTWARE\Microsoft\InetStp', Win32::Registry::KEY_READ | 0x100) 18 | .and_yield('MajorVersion' => 10, 'MinorVersion' => 0) 19 | version = ps.installed_version 20 | 21 | expect(version).not_to be_nil 22 | end 23 | 24 | it 'reports true if iis supported version installed' do 25 | expect_any_instance_of(Win32::Registry).to receive(:open) 26 | .with('SOFTWARE\Microsoft\InetStp', Win32::Registry::KEY_READ | 0x100) 27 | .and_yield('MajorVersion' => 10, 'MinorVersion' => 0) 28 | 29 | result = ps.supported_version_installed? 30 | 31 | expect(result).to be_truthy 32 | end 33 | 34 | it 'reports false if no iis supported version installed' do 35 | expect_any_instance_of(Win32::Registry).to receive(:open) 36 | .with('SOFTWARE\Microsoft\InetStp', Win32::Registry::KEY_READ | 0x100) 37 | .and_yield('MajorVersion' => 6, 'MinorVersion' => 0) 38 | 39 | result = ps.supported_version_installed? 40 | 41 | expect(result).to be_falsey 42 | end 43 | end 44 | 45 | describe 'when iis is not installed' do 46 | let(:ps) { PuppetX::PuppetLabs::IIS::IISVersion } 47 | 48 | it 'returns nil and not throw' do 49 | expect_any_instance_of(Win32::Registry).to receive(:open) 50 | .with('SOFTWARE\Microsoft\InetStp', Win32::Registry::KEY_READ | 0x100) 51 | .and_raise(Win32::Registry::Error.new(2), 'nope') 52 | 53 | version = ps.installed_version 54 | 55 | expect(version).to be_nil 56 | end 57 | end 58 | end 59 | --------------------------------------------------------------------------------