diff --git a/cookbooks/windows/.delivery/project.toml b/cookbooks/windows/.delivery/project.toml new file mode 100644 index 00000000..6d5e3617 --- /dev/null +++ b/cookbooks/windows/.delivery/project.toml @@ -0,0 +1 @@ +remote_file = "https://raw.githubusercontent.com/chef-cookbooks/community_cookbook_tools/master/delivery/project.toml" diff --git a/cookbooks/windows/.github/CODEOWNERS b/cookbooks/windows/.github/CODEOWNERS new file mode 100644 index 00000000..8b0f7d00 --- /dev/null +++ b/cookbooks/windows/.github/CODEOWNERS @@ -0,0 +1 @@ +* @chef-cookbooks/windows-team diff --git a/cookbooks/windows/.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md b/cookbooks/windows/.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md new file mode 100644 index 00000000..e8bae1b8 --- /dev/null +++ b/cookbooks/windows/.github/ISSUE_TEMPLATE/BUG_TEMPLATE.md @@ -0,0 +1,26 @@ +--- +name: 🐛 Bug Report +about: If something isn't working as expected 🤔. + +--- + +### Cookbook version +[Version of the cookbook where you are encountering the issue] + +### Chef-client version +[Version of chef-client in your environment] + +### Platform Details +[Operating system distribution and release version. Cloud provider if running in the cloud] + +### Scenario: +[What you are trying to achieve and you can't?] + +### Steps to Reproduce: +[If you are filing an issue what are the things we need to do in order to repro your problem? How are you using this cookbook or any resources it includes?] + +### Expected Result: +[What are you expecting to happen as the consequence of above reproduction steps?] + +### Actual Result: +[What actually happens after the reproduction steps? Include the error output or a link to a gist if possible.] diff --git a/cookbooks/windows/.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md b/cookbooks/windows/.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..cbf9f091 --- /dev/null +++ b/cookbooks/windows/.github/ISSUE_TEMPLATE/ENHANCEMENT_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +--- +name: 🚀 Enhancement Request +about: I have a suggestion (and may want to implement it 🙂)! + +--- + +### Describe the Enhancement: +[What you are trying to achieve that you can't?] + +### Describe the Need: + +[What kind of user do you believe would utilize this enhancement, and how many users might want this functionality] + +### Current Alternative +[Is there a current alternative that you can utilize to workaround the lack of this enhancement] + +### Can We Help You Implement This?: +[The best way to ensure your enhancement is built is to help implement the enhancement yourself. If you're interested in helping out we'd love to give you a hand to make this possible. Let us know if there's something you need.] diff --git a/cookbooks/windows/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md b/cookbooks/windows/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md new file mode 100644 index 00000000..f5063188 --- /dev/null +++ b/cookbooks/windows/.github/ISSUE_TEMPLATE/SUPPORT_QUESTION.md @@ -0,0 +1,13 @@ +--- +name: 🤗 Support Question +about: If you have a question 💬, please check out our Slack! + +--- + +We use GitHub issues to track bugs and feature requests. If you need help please post to our Mailing List or join the Chef Community Slack. + + * Chef Community Slack at http://community-slack.chef.io/. + * Chef Mailing List https://discourse.chef.io/ + + + Support issues opened here will be closed and redirected to Slack or Discourse. diff --git a/cookbooks/windows/.github/PULL_REQUEST_TEMPLATE.md b/cookbooks/windows/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..b8c6d9f9 --- /dev/null +++ b/cookbooks/windows/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,14 @@ +### Description + +[Describe what this change achieves] + +### Issues Resolved + +[List any existing issues this PR resolves] + +### Check List + +- [ ] All tests pass. See +- [ ] New functionality includes testing. +- [ ] New functionality has been documented in the README if applicable +- [ ] All commits have been signed for the Developer Certificate of Origin. See diff --git a/cookbooks/windows/.gitignore b/cookbooks/windows/.gitignore new file mode 100644 index 00000000..8f93e577 --- /dev/null +++ b/cookbooks/windows/.gitignore @@ -0,0 +1,49 @@ +*.rbc +.config +coverage +InstalledFiles +lib/bundler/man +pkg +rdoc +spec/reports +test/tmp +test/version_tmp +tmp +_Store +*~ +*# +.#* +\#*# +.*.sw[a-z] +*.un~ +*.tmp +*.bk +*.bkup + +# ruby/bundler files +.ruby-version +.ruby-gemset +.rvmrc +Gemfile.lock +.bundle +*.gem + +# YARD artifacts +.yardoc +_yardoc +doc/ +.idea + +# chef stuff +Berksfile.lock +.kitchen +kitchen.local.yml +vendor/ +.coverage/ +.zero-knife.rb +Policyfile.lock.json + +# vagrant stuff +.vagrant/ +.vagrant.d/ +.kitchen/ diff --git a/cookbooks/windows/.rubocop.yml b/cookbooks/windows/.rubocop.yml new file mode 100644 index 00000000..b64b55d6 --- /dev/null +++ b/cookbooks/windows/.rubocop.yml @@ -0,0 +1,46 @@ +# rubocop todo + +Lint/HandleExceptions: + Exclude: + - 'libraries/registry_helper.rb' + - 'libraries/windows_helper.rb' + +Lint/ShadowingOuterLocalVariable: + Exclude: + - 'libraries/registry_helper.rb' + +Lint/UselessAssignment: + Exclude: + - 'libraries/registry_helper.rb' + +Metrics/BlockNesting: + Max: 5 + +Naming/AccessorMethodName: + Exclude: + - 'libraries/version.rb' + +Style/ClassVars: + Exclude: + - 'libraries/registry_helper.rb' + +Style/IfInsideElse: + Exclude: + - 'libraries/powershell_helper.rb' + +Style/IfUnlessModifier: + Exclude: + - 'libraries/registry_helper.rb' + - 'libraries/version.rb' + - 'libraries/windows_helper.rb' + - 'libraries/windows_privileged.rb' + - 'providers/dns.rb' + - 'resources/certificate_binding.rb' + - 'resources/pagefile.rb' + - 'resources/task.rb' + - 'resources/zipfile.rb' + +Style/RegexpLiteral: + Exclude: + - 'resources/task.rb' + - 'test/**/*.rb' diff --git a/cookbooks/windows/Berksfile b/cookbooks/windows/Berksfile new file mode 100644 index 00000000..7048569b --- /dev/null +++ b/cookbooks/windows/Berksfile @@ -0,0 +1,7 @@ +source 'https://supermarket.chef.io' + +metadata + +group :integration do + cookbook 'test', path: './test/cookbooks/test' +end diff --git a/cookbooks/windows/CHANGELOG.md b/cookbooks/windows/CHANGELOG.md index 244078ab..6e586b81 100644 --- a/cookbooks/windows/CHANGELOG.md +++ b/cookbooks/windows/CHANGELOG.md @@ -2,6 +2,296 @@ This file is used to list changes made in each version of the windows cookbook. +## 5.2.2 (2018-11-20) + +- windows_share: Accounts to be revoked should be provided as an individually quoted string array + +## 5.2.1 (2018-11-19) + +- windows_share: Fix idempotency by not adding everyone by default + +## 5.2.0 (2018-11-14) + +- Support installing deleted features in windows_feature_dism + +## 5.1.6 (2018-11-13) + +- Add a warning to the readme regarding windows_share and windows_certificate now being included in Chef 14.7 +- Deprecated win_friendly_path helper in favor of built-in helpers + +## 5.1.5 (2018-11-07) + +- Avoid deprecation warnings in windows_share and windows_certificate on Chef 14.7+ as these are now included in the chef-client itself. + +## 5.1.4 (2018-10-30) + +- Note the :verify action for windows_certificate in the readme +- certificate resource: auto set sensitive is passing password + +## 5.1.3 (2018-10-11) + +- Remove docs and test suite for windows tasks +- Changed variable name in log message for retrieving SMB share access +- Don't load the windows helper in windows_certificate + +## 5.1.2 (2018-10-08) + +- Fix typo in windows_feature_dism resource name + +## 5.1.1 (2018-09-06) + +- Require the win32-certstore gem and upgrade the gem as the resource runs so we get the most up to date version +- Remove redundant helper methods from the windows_certificate resource + +## 5.1.0 (2018-08-29) + +- Add an action to windows_user_privilege to remove a privilege +- Fix failing appveyor tests +- Require win32-certstore 0.1.8 which resolves several issues with the windows_certificate resource +- Avoid deprecation warnings with Chef 14.3+ by not loading resources that are now built into Chef + +## 5.0.0 (2018-07-24) + +### Breaking Changes + +This release removes the windows_task and windows_path resources from this cookbook. This resources shipped in Chef 13.0 and 13.4 This raises the required version of chef-client for this cookbook to 13.4 or later. + +## 4.3.4 (2018-07-18) + +- Fix error message typo in windows_feature_powershell +- Use win32-certstore 0.1.7 for bugfixes + +## 4.3.3 (2018-07-05) + +- Fix failures on PS 3.0 in windows_feature_powershell + +## 4.3.2 (2018-06-13) + +- Don't error in windows_feature_dism when providing a source + +## 4.3.1 (2018-06-11) + +- Make sure to quote each individual user to grant share access to + +## 4.3.0 (2018-06-11) + +- Add the windows_user_privilege resource which can grant privileges like Logon As a Service +- Add windows_feature_powershell support for Windows 2008 R2 by not downcasing the feature names there and modifying the shell_out commands to make older output look like the 2012+ output +- windows_certificate resource has been reworked to use the new win32-certstore gem. This gem abstracts away much of the logic and will allow us to better support certificates on Windows, especially on non-english systems. +- Convert pester tests to InSpec for easier testing with ChefDK out of the box +- Added additional tests for better testing in AppVeyor +- Stop importing the servermanager module in windows_feature_powershell since we require PowerShell 3.0 and we don't need to do this there +- Improve the error messages in Windows feature to get the Windows versions right +- Increase readability in version logic with helpers in windows_feature resources + +## 4.2.5 (2018-05-28) + +- Add quoting to Path when creating new Share + +## 4.2.4 (2018-05-14) + +- Fix the platform version check in windows_share + +## 4.2.3 (2018-05-07) + +- Include the helper in the action class to prevent failures with the zipfile resource + +## 4.2.2 (2018-04-24) + +- Properly fail in windows_share on Windows 2008 R2 since we lack the cmdlets to manipulates shares on those systems. + +## 4.2.1 (2018-04-17) + +- Make sure shares can have spaces in the share name + +## 4.2.0 (2018-04-16) + +- Initial rewrite of windows_share to use PowerShell for share creation. This introduces multiple new properties and resolves a good number of longstanding issues. Please be sure to report any issues you see with this so we can stabilize this resource and include it in Chef 15! +- Resolve failures in windows_certificate + +## 4.1.4 (2018-03-29) + +- Raise in windows_feature_powershell if we're on PS < 3.0 + +## 4.1.3 (2018-03-28) + +- Restore support for Windows 2008 R2 in windows_feature_dism + +## 4.1.2 (2018-03-27) + +- Improve creation messaging for shares +- Allow feature names to be case insensitive in windows_feature + +## 4.1.1 (2018-03-23) + +- Simplify delete action slightly in windows_pagefile +- Don't use win_friendly_path helper in windows_pagefile since we already coerce the path value + +## 4.1.0 (2018-03-21) + +- Adds Caching for WIndows Feature Powershell resource using the same sort of logic we use on windows_feature_dism. This gives us a 3.5X speedup when no features need to be changed (subsequent runs after the change) +- Warn if we're on w2k12 and trying to use source/management properties in windows_feature_powershell since that doesn't work. +- Properly parse features into arrays so installing an array of features works in dism/powershell. This is the preferred way to install a number of features and will be faster than a large number of feature resources +- Fix description of properties for pagefile in the readme + +## 4.0.2 (2018-03-20) + +- Enable FC016 testing +- Enable FC059 testing +- Properly calculate available packages if source is passed in windows_feature_dism resource + +## 4.0.1 (2018-03-07) + +Fix the previous update to windows_feature_dism to use 'override' level of attributes not the normal level which persists to the node. Thanks to @Annih for pointing out the mistake here. + +## 4.0.0 (2018-03-05) + +### WARNING + +This release contains a complete rewrite to windows_feature_dism resource and includes several behavior changes to windows_feature resource. Make sure to read the complete list of changes below before deploying this to production systems. + +#### DISM feature caching Ohai plugin replacement + +In the 3.X cookbook we installed an Ohai plugin that cached the state of features on the node, and we reloaded that plugin anytime we installed/removed a feature from the system. This greatly sped up Chef runs where no features were actually installed/removed (2nd run and later). Without the caching each resource would take about 1 second longer while it queried current feature state. Using Ohai to cache this data was problematic though due to incompatibilities with Chef Solo, the reliance on the ohai cookbook, and the addition of extra node data which had to be stored on the Chef Server. + +In the 4.0 release instead of caching data via an Ohai plugin we just write directly to the node within the resource. This avoids the need to load in the ohai plugin and the various issues that come with that. In the end it's basically the exact same thing, but less impacting on end users and faster when the data needs to be updated. + +#### Fail when feature is missing in windows_feature_dism + +The windows_feature_dism resource had a rather un-Chef behavior in which it just warned you if a feature wasn't available on your platform and then continued on silently. This isn't how we handle missing packages in any of our package resource and because of that it's not going to be what anyone expects out of the box. If someone really wants SNMP installed and we can't install it we should fail instead of continuing on as if we did install it. So we'll now do the following things: + +- When installing a feature that doesn't exist: fail +- When removing a feature that doesn't exist: continue since it is technically removed +- When deleting a feature that doesn't exist: continue since it is technically deleted + +For some users, particularly those writing community cookbooks, this is going to be a breaking change. I'd highly recommend putting logic within your cookbooks to only install features on supported releases of Windows. If you'd just like it to continue even with a failure you can also use `ignore_failure true` on your resource although this produces a lot of failure messaging in logs. + +#### Properly support features as an array in windows_feature_dism + +We claimed to support installing features as an array in the windows_feature_dism resource previously, but it didn't actually work. The actual result was a warning that the array of features wasn't available on your platform since we compared the array to available features as if it was a string. We now properly support installation as a array and we do validation on each feature in the array to make sure the features are available on your Windows release. + +#### Install as the default action in windows_feature_powershell + +Due to some previous refactoring the :install action was not the default action for windows_feature_powershell. For all other package resources in Chef install is the default so this would likely lead to some unexpected behavior in cookbooks. This is technically a breaking change, but I suspect everyone assumed :install was always the default. + +#### servermanagercmd.exe Support Removal + +This cookbook previously supported servermanagercmd.exe, which was necessary for feature installation on Windows 2003 / 2008 (not R2) systems. Windows 2003 went full EOL in 2015 and 2008 went into extended support in 2015\. Neither releases are supported platforms for Chef or this cookbook so we've chosen to simplify the code and remove support entirely. + +#### Remove the undocumented node['windows']['rubyzipversion'] attribute + +This attribute was a workaround for a bug in the rubyzip gem YEARS ago that's just not necessary anymore. We also never documented this attribute and a resource shouldn't change behavior based on attributes. + +## 3.5.2 (2018-03-01) + +- Remove value_for_feature_provider helper which wasn't being used and was using deprecated methods +- Add all the Windows Core editions to the version helper +- Simplify / speedup how we find the font directory in windows_font +- Don't bother enabling why-run mode in the resources since it's enabled by default +- Don't include mixlib-shellout in the resources since it's included by default +- Fix installation messaging for windows_feature_powershell to properly show all features being installed +- Use powershell for the share creation / deletion in windows_share. This speeds up the runs and fixes some of the failures. + +## 3.5.1 (2018-02-23) + +- Add a new `shortcut_name` property to `windows_shortcut` +- Use Chef's built in registry_key_exists helper in `windows_printer_port` +- Fix the `source` coerce in `windows_font` + +## 3.5.0 (2018-02-23) + +- Add Windows 2016 to the supported releases in the readme +- Add Windows 10 detection to the version helper +- Remove the Chefspec matchers. These are auto generated by ChefSpec now. If this causes your specs to fail upgrade ChefDK +- In `certificate_binding` support `hostnameport` option if address is a hostname +- Convert several tests to InSpec tests and add additional test scenarios +- Remove `required: true` on the name_properties, which serves no purpose and will be a Foodcritic rule in the next Foodcritic release +- Fix `windows_feature` logging to work when the user provides an array of features +- Don't both coercing a symbol into a symbol in the `windows_auto_run` resource. +- Switch `windows_font` over to the built in path helper in Chef, which a much more robust +- Don't coerce forward slashes to backslashes in the `windows_font` `source` property if the source is a URI +- Add a new `path` property to `windows_pagefile` for properly overriding the resource name +- Coerce backslashes to forward slashes in `windows_pagefile`'s `path` property so we do the right thing even if a user gives bad input +- Add a new `program_name` property in windows_auto_run for overriding the resource name +- Rename `program` property to `path` in windows_auto_run. The legacy name will continue to work, but cookbooks should be updated +- Coerce the `path` property to use backslashes in `windows_auto_run` so it works no matter what format of path the user provides +- Avoid writing out an extra space in `windows_auto_run`'s registry entry when the user doesn't specify an arg +- Added yard comments to many of the helper methods + +## 3.4.4 (2018-01-19) + +- Fix undefined method for 'ipv4_address' in windows_printer_port + +## 3.4.3 (2018-01-04) + +- Added missing parentheses around PersistKeySet flag that was preventing PowerShell from creating X509Certificate2 object + +## 3.4.2 (2018-01-02) + +- Add deprecation warnings for windows_path and windows_task which are now included in Chef 13\. These will be removed from this cookbook in Sept 2018. + +## 3.4.1 (2017-12-06) + +- Fix long-running filtering by replace LIKE with equality sign in the share resource +- Use logical OR instead of AND when trying to detect share permissions changing in the share resource +- Remove extra new_resource.updated_by_last_action in the windows_task resource that resulted in a Foodcritic warning + +## 3.4.0 (2017-11-14) + +- Add a root key property for the auto_run resource +- Fix a resource typo where a name_property was still written name_attribute +- Resolve FC108 warnings + +## 3.3.0 (2017-11-06) + +- Add new dns resource. See readme for examples +- Add BUILTIN\Users to SYSTEM_USERS for windows_task + +## 3.2.0 (2017-10-17) + +- Add management_tools property to windows_feature powershell provider which installs the various management tools +- Fix deprecations_namespace_collisions +- Add additional certificate store names +- Add the ability to define a timeout on windows_feature +- Multiple improvements to the font resource + + - Improved logging, particularly debug logging + - Allow pulling the font from a remote location using remote_file + - Fix some failures in fetching local fonts + - Added a font_name property that allows you specify the local name of the font, which can be different from the name of the chef resource. This allows you to create more friendly resource names for your converge. + - Handle font resources with backslashes in their source + +- Remove source property from servermanagercmd provider as it does not support it. + +- Remove converge_by around inner powershell_script resource to stop it always reporting as changed + +- Change install feature guards to work on Windows 2008r2 + +- Allow dism feature installs to work on non-English systems + +## 3.1.3 (2017-09-18) + +### windows_task and windows_path deprecation + +s of chef-client 13.0+ and 13.4+ windows_task and windows_path are now included in the Chef client. windows_task underwent a full rewrite that greatly improved the functionality and idempotency of the resource. We highly recommend using these new resources by upgrading to Chef 13.4 or later. If you are running these more recent Chef releases the windows_task and windows_path resources within chef-client will take precedence over those in this cookbook. In September 2018 we will release a new major version of this cookbook that removes windows_task and windows_path. + +## 3.1.2 (2017-08-14) + +- Revert "Require path in the share resource instead of raising if it's missing" which was causing failures due to a bug in the chef-client + +## 3.1.1 (2017-06-13) + +- Replace Windows 7 testing with Windows 10 testing +- Expand debug logging in the pagefile resource +- Require path in the share resource instead of raising if it's missing +- Make pagefile properly fail the run if the command fails to run + +## 3.1.0 (2017-05-30) + +- Updated resource documentation for windows_pagefile +- Declare windows_feature as why-runnable +- Remove action_class.class_eval usage and require 12.7+ as class_eval is causing issues with later versions of Chef + ## 3.0.5 (2017-04-07) - Add support for windows_task resource to run on non-English editions of Windows @@ -21,7 +311,7 @@ This file is used to list changes made in each version of the windows cookbook. ## 3.0.1 (2017-03-17) -- Fix `windows_share` to be fully idempotent. Fixes #447 +- Fix `windows_share` to be fully idempotent. Fixes #447 ## 3.0.0 (2017-03-15) diff --git a/cookbooks/windows/Gemfile b/cookbooks/windows/Gemfile index 01f50f84..49e0ea2b 100644 --- a/cookbooks/windows/Gemfile +++ b/cookbooks/windows/Gemfile @@ -6,7 +6,5 @@ source 'https://rubygems.org' -gem 'tomlrb' -gem 'stove' gem 'community_cookbook_releaser' -gem 'kitchen-pester' +gem 'win32-certstore', '>= 0.1.8' diff --git a/cookbooks/windows/MAINTAINERS.md b/cookbooks/windows/MAINTAINERS.md deleted file mode 100644 index 9f6bd2e4..00000000 --- a/cookbooks/windows/MAINTAINERS.md +++ /dev/null @@ -1,21 +0,0 @@ - - -# Maintainers - -This file lists how this cookbook project is maintained. When making changes to the system, this file tells you who needs to review your patch - you need a review from an existing maintainer for the cookbook to provide a :+1: on your pull request. Additionally, you need to not receive a veto from a Lieutenant or the Project Lead. - -Check out [How Cookbooks are Maintained](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD) for details on the process and how to become a maintainer or the project lead. - -# Project Maintainer -* [Adam Edwards](https://github.com/adamedx) - -# Maintainers -* [Adam Edwards](https://github.com/adamedx) -* [Kartik Null Cating-Subramanian](https://github.com/ksubrama) -* [Steven Murawski](https://github.com/smurawski) -* [Matt Wrock](https://github.com/mwrock) -* [Jay Mundrawala](https://github.com/jaym) -* [Claire McQuin](https://github.com/mcquin) -* [Salim Alam](https://github.com/chefsalim) -* [Tim Smith](https://github.com/tas50) -* [Jennifer Davis](https://github.com/sigje) diff --git a/cookbooks/windows/MAINTAINERS.toml b/cookbooks/windows/MAINTAINERS.toml deleted file mode 100644 index 0a7b2c3d..00000000 --- a/cookbooks/windows/MAINTAINERS.toml +++ /dev/null @@ -1,72 +0,0 @@ -# -# This file is structured to be consumed by both humans and computers. -# It is a TOML document containing Markdown -# -[Preamble] - title = "Maintainers" - text = """ - -This file lists how this cookbook project is maintained. When making changes to the system, this file tells you who needs to review your patch - you need a review from an existing maintainer for the cookbook to provide a :+1: on your pull request. Additionally, you need to not receive a veto from a Lieutenant or the Project Lead. - -Check out [How Cookbooks are Maintained](https://github.com/chef-cookbooks/community_cookbook_documentation/blob/master/CONTRIBUTING.MD) for details on the process and how to become a maintainer or the project lead. -""" - -[Org] - [Org.Components] - [Org.Components.Core] - title = "Project Maintainer" - - lieutenant = 'adamedx' - - maintainers = [ - 'adamedx', - 'ksubrama', - 'smurawski', - 'mwrock', - 'jaym', - 'claire', - 'chefsalim', - 'tas50', - 'sigje' - ] - -[people] - [people.adamedx] - name = "Adam Edwards" - github = "adamedx" - - [people.claire] - name = "Claire McQuin" - github = "mcquin" - - [people.chefsalim] - name = "Salim Alam" - github = "chefsalim" - - [people.mwrock] - name = "Matt Wrock" - github = "mwrock" - - [people.smurawski] - name = "Steven Murawski" - github = "smurawski" - - [people.ksubrama] - name = "Kartik Null Cating-Subramanian" - github = "ksubrama" - - [people.jaym] - name = "Jay Mundrawala" - github = "jaym" - - [people.sigje] - name = "Jennifer Davis" - github = "sigje" - - [people.tas50] - name = "Tim Smith" - github = "tas50" - - [people.thommay] - name = "Thom May" - github = "thommay" diff --git a/cookbooks/windows/README.md b/cookbooks/windows/README.md index 187f6825..a6ca85ef 100644 --- a/cookbooks/windows/README.md +++ b/cookbooks/windows/README.md @@ -12,15 +12,24 @@ Provides a set of Windows-specific resources to aid in the creation of cookbooks - Windows Server 2008 R2 - Windows 8, 8.1 - Windows Server 2012 (R1, R2) +- Windows Server 2016 ### Chef -- Chef 12.6+ +- Chef 13.4+ ## Resources +### Deprecated Resources Note + +As of Chef Client 14.0+ the auto_run, feature, feature_dism, feature_powershell, font, pagefile, printer_port, printer, and shortcut resources are now included in the Chef Client. If you are running Chef 14+ the resources in Chef client will take precedence over the resources in this cookbook. In April 2019 we will release a new major version of this cookbook that removes these resources. + +As of Chef 14.7+ the windows_share and windows_certificate resources are now included in the Chef Client. If you are running Chef 14.7+ the resources in Chef client will take precedence over the resources in this cookbook. In November 2019 we will release a new major version of this cookbook that removes these resources. + ### windows_auto_run +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + #### Actions - `:create` - Create an item to be run at login @@ -28,9 +37,10 @@ Provides a set of Windows-specific resources to aid in the creation of cookbooks #### Properties -- `name` - Name attribute. The name of the value to be stored in the registry -- `program` - The program to be run at login +- `program_name` - Name property. The name of the value to be stored in the registry +- `path` - The program to be run at login. This property was previous named `program`. Cookbooks using the `program` property will continue to function, but should be updated. - `args` - The arguments for the program +- `root` - The registry root key to put the entry under--`:machine` (default) or `:user` #### Examples @@ -46,6 +56,8 @@ end ### windows_certificate +`Note`: This resource is now included in Chef 14.7 and later. There is no need to depend on the Windows cookbook for this resource. + Installs a certificate into the Windows certificate store from a file, and grants read-only access to the private key for designated accounts. Due to current limitations in WinRM, installing certificated remotely may not work if the operation requires a user profile. Operations on the local machine store should still work. #### Actions @@ -53,13 +65,27 @@ Installs a certificate into the Windows certificate store from a file, and grant - `:create` - creates or updates a certificate. - `:delete` - deletes a certificate. - `:acl_add` - adds read-only entries to a certificate's private key ACL. +- `:verify` - logs whether or not a certificate is valid #### Properties - `source` - name attribute. The source file (for create and acl_add), thumbprint (for delete and acl_add) or subject (for delete). - `pfx_password` - the password to access the source if it is a pfx file. - `private_key_acl` - array of 'domain\account' entries to be granted read-only access to the certificate's private key. This is not idempotent. -- `store_name` - the certificate store to manipulate. One of MY (default : personal store), CA (trusted intermediate store) or ROOT (trusted root store). +- `store_name` - the certificate store to manipulate. One of: + - MY (Personal) + - CA (Intermediate Certification Authorities) + - ROOT (Trusted Root Certification Authorities) + - TRUSTEDPUBLISHER (Trusted Publishers) + - CLIENTAUTHISSUER (Client Authentication Issuers) + - REMOTE DESKTOP (Remote Desktop) + - TRUSTEDDEVICES (Trusted Devices) + - WEBHOSTING (Web Hosting) + - AUTHROOT (Third-Party Root Certification Authorities) + - TRUSTEDPEOPLE (Trusted People) + - SMARTCARDROOT (Smart Card Trusted Roots) + - TRUST (Enterprise Trust) + - DISALLOWED (Untrusted Certificates) - `user_store` - if false (default) then use the local machine store; if true then use the current user's store. #### Examples @@ -99,10 +125,25 @@ Binds a certificate to an HTTP port in order to enable TLS communication. - `cert_name` - name attribute. The thumbprint(hash) or subject that identifies the certificate to be bound. - `name_kind` - indicates the type of cert_name. One of :subject (default) or :hash. -- `address` - the address to bind against. Default is 0.0.0.0 (all IP addresses). +- `address` - the address to bind against. Default is 0.0.0.0 (all IP addresses). One of: + - IP v4 address `1.2.3.4` + - IP v6 address `[::1]` + - Host name `www.foo.com` - `port` - the port to bind against. Default is 443. - `app_id` - the GUID that defines the application that owns the binding. Default is the values used by IIS. -- `store_name` - the store to locate the certificate in. One of MY (default : personal store), CA (trusted intermediate store) or ROOT (trusted root store). +- `store_name` - the store to locate the certificate in. One of: + - MY (Personal) + - CA (Intermediate Certification Authorities) + - ROOT (Trusted Root Certification Authorities) + - TRUSTEDPUBLISHER (Trusted Publishers) + - CLIENTAUTHISSUER (Client Authentication Issuers) + - REMOTE DESKTOP (Remote Desktop) + - TRUSTEDDEVICES (Trusted Devices) + - WEBHOSTING (Web Hosting) + - AUTHROOT (Third-Party Root Certification Authorities) + - TRUSTEDPEOPLE (Trusted People) + - SMARTCARDROOT (Smart Card Trusted Roots) + - TRUST (Enterprise Trust) #### Examples @@ -122,8 +163,54 @@ windows_certificate_binding "me.acme.com" do end ``` +### windows_dns + +Configures A and CNAME records in Windows DNS. This requires the DNSCMD to be installed, which is done by adding the DNS role to the server or installing the Remote Server Admin Tools. + +#### Actions + +- :create: creates/updates the DNS entry +- :delete: deletes the DNS entry + +#### Properties + +- host_name: name attribute. FQDN of the entry to act on. +- dns_server: the DNS server to update. Default is local machine (.) +- record_type: the type of record to create. One of A (default) or CNAME +- target: for A records an array of IP addresses to associate with the host; for CNAME records the FQDN of the host to alias +- ttl: if > 0 then set the time to live of the record + +#### Examples + +```ruby +# Create A record linked to 2 addresses with a 10 minute ttl +windows_dns "m1.chef.test" do + target ['10.9.8.7', '1.2.3.4'] + ttl 600 +end +``` + +```ruby +# Delete records. target is mandatory although not used +windows_dns "m1.chef.test" do + action :delete + target [] +end +``` + +```ruby +# Set an alias against the node in a role +nodes = search( :node, "role:my_service" ) +windows_dns "myservice.chef.test" do + record_type 'CNAME' + target nodes[0]['fqdn'] +end +``` + ### windows_feature +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + **BREAKING CHANGE - Version 3.0.0** This resource has been moved from using LWRPs and multiple providers to using Custom Resources. To maintain functionality, you'll need to change `provider` to `install_method`. @@ -132,7 +219,7 @@ Windows Roles and Features can be thought of as built-in operating system packag This resource allows you to manage these 'features' in an unattended, idempotent way. -There are three methods for the `windows_feature` which map into Microsoft's three major tools for managing roles/features: [Deployment Image Servicing and Management (DISM)](http://msdn.microsoft.com/en-us/library/dd371719%28v=vs.85%29.aspx), [Servermanagercmd](http://technet.microsoft.com/en-us/library/ee344834%28WS.10%29.aspx) (The CLI for Server Manager), and [PowerShell](https://technet.microsoft.com/en-us/library/cc731774(v=ws.11).aspx). As Servermanagercmd is deprecated, Chef will set the default method to `:windows_feature_dism` if `dism.exe` is present on the system being configured. The default method will fall back to `:windows_feature_servermanagercmd`, and then `:windows_feature_powershell`. +There are two underlying resources that power `windows_feature` which map to the available installation systems on supported releases of Windows: [Deployment Image Servicing and Management (DISM)](http://msdn.microsoft.com/en-us/library/dd371719%28v=vs.85%29.aspx) and [PowerShell](https://technet.microsoft.com/en-us/library/cc731774(v=ws.11).aspx). Chef will set the default method to `:windows_feature_dism` if `dism.exe` is present on the system being configured and otherwise use `:windows_feature_powershell`. For more information on Roles, Role Services and Features see the [Microsoft TechNet article on the topic](http://technet.microsoft.com/en-us/library/cc754923.aspx). For a complete list of all features that are available on a node type either of the following commands at a command prompt: @@ -142,12 +229,6 @@ For Dism: dism /online /Get-Features ``` -For ServerManagerCmd: - -```text -servermanagercmd -query -``` - For PowerShell: ```text @@ -158,14 +239,16 @@ get-windowsfeature - `:install` - install a Windows role/feature - `:remove` - remove a Windows role/feature -- `:delete` - remove a Windows role/feature from the image (not supported by ServerManagerCmd) +- `:delete` - remove a Windows role/feature from the image #### Properties -- `feature_name` - name of the feature/role(s) to install. The same feature may have different names depending on the provider used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS). -- `all` - Boolean. Optional. Default: false. DISM and Powershell providers only. Forces all dependencies to be installed. -- `source` - String. Optional. DISM provider only. Uses local repository for feature install. -- `install_method` - Symbol. Optional. **REPLACEMENT FOR THE PREVIOUS PROVIDER OPTION** If not supplied, Chef will determine which method to use (in the order of `:windows_feature_dism`, `:windows_feature_servercmd`, `:windows_feature_powershell`) +- `feature_name` - name of the feature/role(s) to install. The same feature may have different names depending on the underlying resource being used (ie DHCPServer vs DHCP; DNS-Server-Full-Role vs DNS). +- `all` - Boolean. Optional. Default: false. For DISM this is the equivalent of specifying the /All switch to dism.exe, forcing all parent dependencies to be installed. With the PowerShell install method, the `-InstallAllSubFeatures` switch is applied. Note that these two methods may not produce identical results. +- `management_tools` - Boolean. Optional. Default: false. PowerShell only. Includes the `-IncludeManagementTools` switch. Installs all applicable management tools of the roles, role services, or features specified by the feature name. +- `source` - String. Optional. Uses local repository for feature install. +- `timeout` - Integer. Optional. Default: 600\. Specifies a timeout (in seconds) for feature install. +- `install_method` - Symbol. Optional. If not supplied, Chef will determine which method to use (in the order of `:windows_feature_dism`, `:windows_feature_servercmd`, `:windows_feature_powershell`) #### Examples @@ -177,13 +260,14 @@ windows_feature 'DHCPServer' do end ``` -Install the .Net 3.5.1 feature on Server 2012 using repository files on DVD and install all dependencies +Install the .Net 3.5.1 feature on Server 2012 using repository files on DVD and install all dependencies with a timeout of 900 seconds ```ruby windows_feature "NetFx3" do action :install all true source "d:\sources\sxs" + timeout 900 end ``` @@ -214,11 +298,21 @@ windows_feature ['Web-Asp-Net45', 'Web-Net-Ext45'] do end ``` +Install the Network Policy and Access Service feature, including the management tools. Which, for this example, will automatically install `RSAT-NPAS` as well. + +```ruby +windows_feature 'NPAS' do + action :install + management_tools true + install_method :windows_feature_powershell +end +``` + ### windows_font -Installs a font. +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. -Font files should be included in the cookbooks +Installs font files. Sources the font by default from the cookbook, but a URI source can be specified as well. #### Actions @@ -226,13 +320,17 @@ Font files should be included in the cookbooks #### Properties -- `name` - The file name of the font file name to install. The path defaults to the files/default directory of the cookbook you're calling windows_font from. Defaults to the resource name. -- `source` - Set an alternate path to the font file. +- `font_name` - The file name of the font file name to install. The path defaults to the files/default directory of the cookbook you're calling windows_font from. Defaults to the resource name. +- `source` - A local filesystem path or URI to source the font file from.. #### Examples ```ruby windows_font 'Code New Roman.otf' + +windows_font 'Custom.otf' do + source "https://example.com/Custom.otf" +end ``` ### windows_http_acl @@ -248,7 +346,7 @@ Sets the Access Control List for an http URL to grant non-admin accounts permiss - `url` - the name of the url to be created/deleted. - `sddl` - the DACL string configuring all permissions to URL. Mandatory for create if user is not provided. Can't be use with `user`. -- `user` - the name (domain\user) of the user or group to be granted permission to the URL. Mandatory for create if sddl is not provided. Can't be use with `sddl`. Only one user or group can be granted permission so this replaces any previously defined entry. +- `user` - the name (domain\user) of the user or group to be granted permission to the URL. Mandatory for create if sddl is not provided. Can't be use with `sddl`. Only one user or group can be granted permission so this replaces any previously defined entry. If you receive a parameter error your user may not exist. #### Examples @@ -271,8 +369,29 @@ windows_http_acl 'http://+:50051/' do end ``` +### windows_pagefile + +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + +Configures the file that provides virtual memory for applications requiring more memory than available RAM or that are paged out to free up memory in use. + +#### Actions + +- `:set` - configures the default pagefile, creating if it doesn't exist. +- `:delete` - deletes the specified pagefile. + +#### Properties + +- `path` - the path to the pagefile, String, name_property: true +- `system_managed` - configures whether the system manages the pagefile size. [true, false] +- `automatic_managed` - all of the settings are managed by the system. If this is set to true, other settings will be ignored. [true, false], default: false +- `initial_size` - initial size of the pagefile in megbytes. Integer +- `maximum_size` - maximum size of the pagefile in megbytes. Integer + ### windows_printer_port +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + Create and delete TCP/IPv4 printer ports. #### Actions @@ -328,25 +447,27 @@ end ### windows_printer +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + Create Windows printer. Note that this doesn't currently install a printer driver. You must already have the driver installed on the system. -The Windows Printer LWRP will automatically create a TCP/IP printer port for you using the `ipv4_address` property. If you want more granular control over the printer port, just create it using the `windows_printer_port` LWRP before creating the printer. +The Windows Printer resource will automatically create a TCP/IP printer port for you using the `ipv4_address` property. If you want more granular control over the printer port, just create it using the `windows_printer_port` resource before creating the printer. #### Actions - `:create` - Create a new printer -- `:delete` - Delete a new printer +- `:delete` - Delete an existing printer #### Properties -- `device_id` - Name attribute. Required. Printer queue name, e.g. 'HP LJ 5200 in fifth floor copy room' +- `device_id` - Printer queue name, e.g. 'HP LJ 5200 in fifth floor copy room'. Name property. - `comment` - Optional string describing the printer queue. - `default` - Boolean. Optional. Defaults to false. Note that Windows sets the first printer defined to the default printer regardless of this setting. - `driver_name` - String. Required. Exact name of printer driver. Note that the printer driver must already be installed on the node. - `location` - Printer location, e.g. 'Fifth floor copy room', or 'US/NYC/Floor42/Room4207' - `shared` - Boolean. Defaults to false. - `share_name` - Printer share name. -- `ipv4_address` - Printer IPv4 address, e.g. '10.4.64.23'. You don't have to be able to ping the IP address to set it. Required. +- `ipv4_address` - Printer's IPv4 address, e.g. '10.4.64.23'. You don't have to be able to ping the IP address to set it. Required. An error of "Set-WmiInstance : Generic failure" is most likely due to the printer driver name not matching or not being installed. @@ -371,21 +492,33 @@ end ### windows_share +`Note`: This resource is now included in Chef 14.7 and later. There is no need to depend on the Windows cookbook for this resource. + Creates, modifies and removes Windows shares. All properties are idempotent. +`Note`: This resource uses PowerShell cmdlets introduced in Windows 2012/8. + #### Actions -- :create: creates/modifies a share -- :delete: deletes a share +- `:create`: creates/modifies a share +- `:delete`: deletes a share #### Properties -- share_name: name attribute, the share name. -- path: path to the directory to be shared. Required when creating. If the share already exists on a different path then it is deleted and re-created. -- description: description to be applied to the share -- full_users: array of users which should have "Full control" permissions -- change_users: array of users which should have "Change" permissions -- read_users: array of users which should have "Read" permissions +property | type | default | description +------------------------ | ---------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- +`share_name` | String | resource name | the share to assign to the share +`path` | String | | The path of the location of the folder to share. Required when creating. If the share already exists on a different path then it is deleted and re-created. +`description` | String | | description to be applied to the share +`full_users` | Array | [] | users which should have "Full control" permissions +`change_users` | Array | [] | Users are granted modify permission to access the share. +`read_users` | Array | [] | users which should have "Read" permissions +`temporary` | True/False | false | The lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer +`scope_name` | String | '*' | The scope name of the share. +`ca_timeout` | Integer | 0 | The continuous availability time-out for the share. +`continuously_available` | True/False | false | Indicates that the share is continuously available. +`concurrent_user_limit` | Integer | 0 (unlimited) | The maximum number of concurrently connected users the share can accommodate +`encrypt_data` | True/False | false | Indicates that the share is encrypted. #### Examples @@ -406,6 +539,8 @@ end ### windows_shortcut +`Note`: This resource is now included in Chef 14 and later. There is no need to depend on the Windows cookbook for this resource. + Creates and modifies Windows shortcuts. #### Actions @@ -414,8 +549,8 @@ Creates and modifies Windows shortcuts. #### Properties -- `name` - name attribute. The shortcut to create/modify. -- `target` - what the shortcut links to +- `shortcut_name` - The name for the shortcut if it differs from the resource name. Name property +- `target` - Where the shortcut links to. - `arguments` - arguments to pass to the target when the shortcut is executed - `description` - description of the shortcut - `cwd` - Working directory to use when the target is executed @@ -423,27 +558,19 @@ Creates and modifies Windows shortcuts. #### Examples -Add a shortcut all users desktop: +Add a shortcut to all users desktop: ```ruby require 'win32ole' all_users_desktop = WIN32OLE.new("WScript.Shell").SpecialFolders("AllUsersDesktop") windows_shortcut "#{all_users_desktop}/Notepad.lnk" do - target "C:\\WINDOWS\\notepad.exe" + target "C:\\Windows\\notepad.exe" description "Launch Notepad" - iconlocation "C:\\windows\\notepad.exe, 0" + iconlocation "C:\\Windows\\notepad.exe,0" end ``` -#### Library Methods - -```ruby -Registry.value_exists?('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','BGINFO') -Registry.key_exists?('HKLM\SOFTWARE\Microsoft') -BgInfo = Registry.get_value('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run','BGINFO') -``` - ### windows_path #### Actions @@ -473,88 +600,87 @@ windows_path 'C:\7-Zip' do end ``` -### windows_task +### windows_user_privilege -Creates, deletes or runs a Windows scheduled task. Requires Windows Server 2008 due to API usage. +Adds the `principal` (User/Group) to the specified privileges (such as `Logon as a batch job` or `Logon as a Service`). #### Actions -- `:create` - creates a task (or updates existing if user or command has changed) -- `:delete` - deletes a task -- `:run` - runs a task -- `:end` - ends a task -- `:change` - changes the un/pw or command of a task -- `:enable` - enable a task -- `:disable` - disable a task +- `:add` - add the specified privileges to the `principal` +- `:remove` - remove the specified privilege of the `principal` #### Properties -- `task_name` - name attribute, The task name. ("Task Name" or "/Task Name") -- `force` - When used with create, will update the task. -- `command` - The command the task will run. -- `cwd` - The directory the task will be run from. -- `user` - The user to run the task as. (defaults to 'SYSTEM') -- `password` - The user's password. (requires user) -- `run_level` - Run with `:limited` or `:highest` privileges. -- `frequency` - Frequency with which to run the task. (default is :hourly. Other valid values include :minute, :hourly, :daily, :weekly, :monthly, :once, :on_logon, :onstart, :on_idle) :once requires start_time -- `frequency_modifier` - Multiple for frequency. (15 minutes, 2 days). Monthly tasks may also use these values": ('FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY') -- `start_day` - Specifies the first date on which the task runs. Optional string (MM/DD/YYYY) -- `start_time` - Specifies the start time to run the task. Optional string (HH:mm) -- `interactive_enabled` - (Allow task to run interactively or non-interactively. Requires user and password.) -- `day` - For monthly or weekly tasks, the day(s) on which the task runs. (MON - SUN, *, 1 - 31) -- `months` - The Months of the year on which the task runs. (JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC, *). Multiple months should be comma delimited. -- `idle_time` - For :on_idle frequency, the time (in minutes) without user activity that must pass to trigger the task. (1 - 999) +- `principal` - Name attribute, Required, String. The user or group to be granted privileges. +- `privilege` - Required, String/Array. The privilege(s) to be granted. #### Examples -Create a `chef-client` task with TaskPath `\` running every 15 minutes - -```ruby -windows_task 'chef-client' do - user 'Administrator' - password '$ecR3t' - cwd 'C:\\chef\\bin' - command 'chef-client -L C:\\tmp\\' - run_level :highest - frequency :minute - frequency_modifier 15 -end -``` - -Update `chef-client` task with new password and log location - -```ruby -windows_task 'chef-client' do - user 'Administrator' - password 'N3wPassW0Rd' - cwd 'C:\\chef\\bin' - command 'chef-client -L C:\\chef\\logs\\' - action :change -end -``` - -Delete a task named `old task` +Grant the Administrator user the `Logon as a batch job` and `Logon as a service` privilege. ```ruby -windows_task 'old task' do - action :delete +windows_user_privilege 'Administrator' do + privilege %w(SeBatchLogonRight SeServiceLogonRight) end ``` -Enable a task named `chef-client` +Remove `Logon as a batch job` privilege of Administrator. ```ruby -windows_task 'chef-client' do - action :enable +windows_user_privilege 'Administrator' do + privilege %w(SeBatchLogonRight) + action :remove end ``` -Disable a task named `ProgramDataUpdater` with TaskPath `\Microsoft\Windows\Application Experience\` - -```ruby -windows_task '\Microsoft\Windows\Application Experience\ProgramDataUpdater' do - action :disable -end +#### Available Privileges + +``` +SeTrustedCredManAccessPrivilege Access Credential Manager as a trusted caller +SeNetworkLogonRight Access this computer from the network +SeTcbPrivilege Act as part of the operating system +SeMachineAccountPrivilege Add workstations to domain +SeIncreaseQuotaPrivilege Adjust memory quotas for a process +SeInteractiveLogonRight Allow log on locally +SeRemoteInteractiveLogonRight Allow log on through Remote Desktop Services +SeBackupPrivilege Back up files and directories +SeChangeNotifyPrivilege Bypass traverse checking +SeSystemtimePrivilege Change the system time +SeTimeZonePrivilege Change the time zone +SeCreatePagefilePrivilege Create a pagefile +SeCreateTokenPrivilege Create a token object +SeCreateGlobalPrivilege Create global objects +SeCreatePermanentPrivilege Create permanent shared objects +SeCreateSymbolicLinkPrivilege Create symbolic links +SeDebugPrivilege Debug programs +SeDenyNetworkLogonRight Deny access this computer from the network +SeDenyBatchLogonRight Deny log on as a batch job +SeDenyServiceLogonRight Deny log on as a service +SeDenyInteractiveLogonRight Deny log on locally +SeDenyRemoteInteractiveLogonRight Deny log on through Remote Desktop Services +SeEnableDelegationPrivilege Enable computer and user accounts to be trusted for delegation +SeRemoteShutdownPrivilege Force shutdown from a remote system +SeAuditPrivilege Generate security audits +SeImpersonatePrivilege Impersonate a client after authentication +SeIncreaseWorkingSetPrivilege Increase a process working set +SeIncreaseBasePriorityPrivilege Increase scheduling priority +SeLoadDriverPrivilege Load and unload device drivers +SeLockMemoryPrivilege Lock pages in memory +SeBatchLogonRight Log on as a batch job +SeServiceLogonRight Log on as a service +SeSecurityPrivilege Manage auditing and security log +SeRelabelPrivilege Modify an object label +SeSystemEnvironmentPrivilege Modify firmware environment values +SeManageVolumePrivilege Perform volume maintenance tasks +SeProfileSingleProcessPrivilege Profile single process +SeSystemProfilePrivilege Profile system performance +SeUnsolicitedInputPrivilege "Read unsolicited input from a terminal device" +SeUndockPrivilege Remove computer from docking station +SeAssignPrimaryTokenPrivilege Replace a process level token +SeRestorePrivilege Restore files and directories +SeShutdownPrivilege Shut down the system +SeSyncAgentPrivilege Synchronize directory service data +SeTakeOwnershipPrivilege Take ownership of files or other objects ``` ### windows_zipfile @@ -696,60 +822,6 @@ case ::Windows::VersionHelper.nt_version node end ``` -## Windows ChefSpec Matchers - -The Windows cookbook includes custom [ChefSpec](https://github.com/sethvargo/chefspec) matchers you can use to test your own cookbooks that consume Windows cookbook LWRPs. - -### Example Matcher Usage - -```ruby -expect(chef_run).to install_windows_package('Node.js').with( - source: 'http://nodejs.org/dist/v0.10.26/x64/node-v0.10.26-x64.msi') -``` - -### Windows Cookbook Matchers - -- create_windows_auto_run -- remove_windows_auto_run -- create_windows_certificate -- delete_windows_certificate -- add_acl_to_windows_certificate -- create_windows_certificate_binding -- delete_windows_certificate_binding -- install_windows_feature -- install_windows_feature_dism -- install_windows_feature_servermanagercmd -- install_windows_feature_powershell -- remove_windows_feature -- remove_windows_feature_dism -- remove_windows_feature_servermanagercmd -- remove_windows_feature_powershell -- delete_windows_feature -- delete_windows_feature_dism -- delete_windows_feature_powershell -- install_windows_font -- create_windows_http_acl -- delete_windows_http_acl -- install_windows_package -- remove_windows_package -- set_windows_pagefile -- add_windows_path -- remove_windows_path -- create_windows_printer -- delete_windows_printer -- create_windows_printer_port -- delete_windows_printer_port -- create_windows_shortcut -- create_windows_shortcut -- create_windows_task -- disable_windows_task -- enable_windows_task -- delete_windows_task -- run_windows_task -- change_windows_task -- unzip_windows_zipfile_to -- zip_windows_zipfile_to - ## Usage Place an explicit dependency on this cookbook (using depends in the cookbook's metadata.rb) from any cookbook where you would like to use the Windows-specific resources/providers that ship with this cookbook. @@ -766,7 +838,7 @@ depends 'windows' - Author:: Doug Ireton ([doug.ireton@nordstrom.com](mailto:doug.ireton@nordstrom.com)) ```text -Copyright 2011-2016, Chef Software, Inc. +Copyright 2011-2018, Chef Software, Inc. Copyright 2010, VMware, Inc. Copyright 2011, Business Intelligence Associates, Inc Copyright 2012, Nordstrom, Inc. diff --git a/cookbooks/windows/appveyor.yml b/cookbooks/windows/appveyor.yml index 5d42890a..9c4a3d3b 100644 --- a/cookbooks/windows/appveyor.yml +++ b/cookbooks/windows/appveyor.yml @@ -1,7 +1,7 @@ environment: machine_user: vagrant machine_pass: vagrant - KITCHEN_YAML: .kitchen.appveyor.yml + KITCHEN_YAML: kitchen.appveyor.yml branches: only: @@ -15,11 +15,10 @@ clone_depth: 1 # Install the latest nightly of ChefDK install: - - ps: iex (irm https://omnitruck.chef.io/install.ps1); Install-Project -Project chefdk -channel current + - ps: (& cmd /c); iex (irm https://omnitruck.chef.io/install.ps1); Install-Project -Project chefdk -channel current - ps: 'Get-CimInstance win32_operatingsystem -Property Caption, OSArchitecture, Version | fl Caption, OSArchitecture, Version' - ps: $PSVersionTable - c:\opscode\chefdk\bin\chef.bat exec ruby --version - - c:\opscode\chefdk\bin\chef.bat gem install kitchen-pester - ps: secedit /export /cfg $env:temp/export.cfg - ps: ((get-content $env:temp/export.cfg) -replace ('PasswordComplexity = 1', 'PasswordComplexity = 0')) | Out-File $env:temp/export.cfg - ps: ((get-content $env:temp/export.cfg) -replace ('MinimumPasswordLength = 8', 'MinimumPasswordLength = 0')) | Out-File $env:temp/export.cfg diff --git a/cookbooks/windows/chefignore b/cookbooks/windows/chefignore index a9769175..b2065c33 100644 --- a/cookbooks/windows/chefignore +++ b/cookbooks/windows/chefignore @@ -45,22 +45,20 @@ a.out # Testing # ########### -.watchr .rspec spec/* spec/fixtures/* test/* features/* examples/* -Guardfile Procfile +kitchen* .kitchen* .rubocop.yml spec/* -Rakefile .travis.yml .foodcritic -.codeclimate.yml +appveyor.yml # SCM # ####### @@ -82,21 +80,14 @@ Berksfile.lock cookbooks/* tmp +# Policyfile # +############## +Policyfile.rb +Policyfile.lock.json + # Cookbooks # ############# CONTRIBUTING* CHANGELOG* TESTING* -MAINTAINERS.toml -# Strainer # -############ -Colanderfile -Strainerfile -.colander -.strainer - -# Vagrant # -########### -.vagrant -Vagrantfile diff --git a/cookbooks/windows/files/dism_features.rb b/cookbooks/windows/files/dism_features.rb deleted file mode 100644 index 804dcc05..00000000 --- a/cookbooks/windows/files/dism_features.rb +++ /dev/null @@ -1,45 +0,0 @@ -# -# Author:: Wade Peacock -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -## See the License for the specific language governing permissions and -# limitations under the License. -# - -Ohai.plugin(:DismFeatures) do - provides 'dism_features' - collect_data(:windows) do - dism_features Mash.new - # This is for 32-bit ruby/chef client on 64-bit Windows - # This emulates the locate_sysnative_cmd helper as it is not available - cmd = 'dism.exe' - dism = if ::File.exist?("#{ENV['WINDIR']}\\sysnative\\#{cmd}") - "#{ENV['WINDIR']}\\sysnative\\#{cmd}" - elsif ::File.exist?("#{ENV['WINDIR']}\\system32\\#{cmd}") - "#{ENV['WINDIR']}\\system32\\#{cmd}" - else - cmd - end - # Grab raw feature information from dism command line - raw_list_of_features = shell_out("#{dism} /Get-Features /Online /Format:Table").stdout - # Split stdout into an array by windows line ending - features_list = raw_list_of_features.split("\r\n") - features_list.each do |feature_details_raw| - # Skip lines that do not match Enable / Disable - next unless feature_details_raw =~ /(En|Dis)able/ - # Strip trailing whitespace characters then split on n number of spaces + | + n number of spaces - feature_details = feature_details_raw.strip.split(/\s+[|]\s+/) - # Add to Mash - dism_features[feature_details.first] = feature_details.last - end - end -end diff --git a/cookbooks/windows/kitchen.appveyor.yml b/cookbooks/windows/kitchen.appveyor.yml new file mode 100644 index 00000000..881efdaa --- /dev/null +++ b/cookbooks/windows/kitchen.appveyor.yml @@ -0,0 +1,38 @@ +--- +driver: + name: proxy + host: localhost + reset_command: "exit 0" + port: 5985 + username: <%= ENV["machine_user"] %> + password: <%= ENV["machine_pass"] %> + +transport: + name: winrm + elevated: true + +provisioner: + name: chef_zero + deprecations_as_errors: true + product_name: chef + product_version: 13 + +platforms: + - name: windows-2012R2 + +verifier: + name: inspec + +suites: + - name: http_acl + run_list: + - recipe[test::http_acl] + - name: user_privilege + run_list: + - recipe[test::user_privilege] + - name: certificate + run_list: + - recipe[test::certificate] + - name: share + run_list: + - recipe[test::share] diff --git a/cookbooks/windows/kitchen.yml b/cookbooks/windows/kitchen.yml new file mode 100644 index 00000000..2f798d1e --- /dev/null +++ b/cookbooks/windows/kitchen.yml @@ -0,0 +1,75 @@ +driver: + name: vagrant + customize: + cpus: 2 + memory: 4096 + +transport: + name: winrm + elevated: true + +provisioner: + name: chef_zero + deprecations_as_errors: true + product_name: chef + product_version: 13 + +verifier: + name: inspec + +platforms: + - name: windows-8.1 + driver: + box: chef/windows-8.1-enterprise + - name: windows-10 + driver: + box: chef/windows-10-enterprise + - name: windows-2008r2 + driver: + box: tas50/windows_2008r2_updated + - name: windows-2012r2 + driver: + box: tas50/windows_2012r2 + - name: windows-2016 + driver: + box: chef/windows-server-2016-standard + +suites: + - name: autorun + run_list: + - recipe[test::autorun] + - name: certificate + run_list: + - recipe[test::certificate] + - name: feature + run_list: + - recipe[test::feature] + excludes: ["windows-10", "windows-8.1"] + - name: font + run_list: + - recipe[test::font] + - name: http_acl + run_list: + - recipe[test::http_acl] + - name: pagefile + run_list: + - recipe[test::pagefile] + - name: path + run_list: + - recipe[test::path] + - name: share + run_list: + - recipe[test::share] + excludes: ["windows-2008r2"] + - name: shortcut + run_list: + - recipe[test::shortcut] + - name: zipfile + run_list: + - recipe[test::zipfile] + - name: user_privilege + run_list: + - recipe[test::user_privilege] + - name: everything + run_list: + - recipe[test::everything] diff --git a/cookbooks/windows/libraries/matchers.rb b/cookbooks/windows/libraries/matchers.rb deleted file mode 100644 index 8299d940..00000000 --- a/cookbooks/windows/libraries/matchers.rb +++ /dev/null @@ -1,586 +0,0 @@ -if defined?(ChefSpec) - - ChefSpec.define_matcher :windows_auto_run - ChefSpec.define_matcher :windows_certificate - ChefSpec.define_matcher :windows_certificate_binding - ChefSpec.define_matcher :windows_feature - ChefSpec.define_matcher :windows_feature_dism - ChefSpec.define_matcher :windows_feature_servermanagercmd - ChefSpec.define_matcher :windows_feature_powershell - ChefSpec.define_matcher :windows_font - ChefSpec.define_matcher :windows_http_acl - ChefSpec.define_matcher :windows_pagefile - ChefSpec.define_matcher :windows_path - ChefSpec.define_matcher :windows_printer - ChefSpec.define_matcher :windows_printer_port - ChefSpec.define_matcher :windows_share - ChefSpec.define_matcher :windows_shortcut - ChefSpec.define_matcher :windows_task - ChefSpec.define_matcher :windows_zipfile - - # - # Assert that a +windows_certificate+ resource exists in the Chef run with the - # action +:create+. Given a Chef Recipe that creates 'c:\test\mycert.pfx' as a - # +windows_certificate+: - # - # windows_certificate 'c:\test\mycert.pfx' do - # action :create - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_certificate+ resource with ChefSpec. - # - # @example Assert that a +windows_certificate+ was created - # expect(chef_run).to create_windows_certificate('c:\test\mycert.pfx') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def create_windows_certificate(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_certificate, :create, resource_name) - end - - # - # Assert that a +windows_certificate+ resource exists in the Chef run with the - # action +:delete+. Given a Chef Recipe that deletes "me.acme.com" as a - # +windows_certificate+: - # - # windows_certificate 'me.acme.com' do - # action :delete - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_certificate+ resource with ChefSpec. - # - # @example Assert that a +windows_certificate+ was _not_ deleted - # expect(chef_run).to_not delete_windows_certificate('me.acme.com') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def delete_windows_certificate(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_certificate, :delete, resource_name) - end - - # - # Assert that a +windows_certificate+ resource exists in the Chef run with the - # action +:acl_add+. Given a Chef Recipe that adds a private key acl to "me.acme.com" as a - # +windows_certificate+: - # - # windows_certificate 'me.acme.com' do - # private_key_acl ['acme\fred', 'pc\jane'] - # action :acl_add - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_certificate+ resource with ChefSpec. - # - # @example Assert that a +windows_certificate+ was _not_ removed - # expect(chef_run).to add_acl_to_windows_certificate('me.acme.com').with(private_key_acl: ['acme\fred', 'pc\jane']) - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def add_acl_to_windows_certificate(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_certificate, :acl_add, resource_name) - end - - # - # Assert that a +windows_feature+ resource exists in the Chef run with the - # action +:install+. Given a Chef Recipe that installs "NetFX3" as a - # +windows_feature+: - # - # windows_feature 'NetFX3' do - # action :install - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_feature+ resource with ChefSpec. - # - # @example Assert that a +windows_feature+ was installed - # expect(chef_run).to install_windows_feature('NetFX3') - # - # @example Assert that a +windows_feature+ was _not_ installed - # expect(chef_run).to_not install_windows_feature('NetFX3') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def install_windows_feature(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :install, resource_name) - end - - def install_windows_feature_servermanagercmd(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_servermanagercmd, :install, resource_name) - end - - def install_windows_feature_dism(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_dism, :install, resource_name) - end - - def install_windows_feature_powershell(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_powershell, :install, resource_name) - end - - # - # Assert that a +windows_feature+ resource exists in the Chef run with the - # action +:remove+. Given a Chef Recipe that removes "NetFX3" as a - # +windows_feature+: - # - # windows_feature 'NetFX3' do - # action :remove - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_feature+ resource with ChefSpec. - # - # @example Assert that a +windows_feature+ was removed - # expect(chef_run).to remove_windows_feature('NetFX3') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def remove_windows_feature(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :remove, resource_name) - end - - def remove_windows_feature_servermanagercmd(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_servermanagercmd, :remove, resource_name) - end - - def remove_windows_feature_dism(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_dism, :remove, resource_name) - end - - def remove_windows_feature_powershell(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_powershell, :remove, resource_name) - end - - # - # Assert that a +windows_feature+ resource exists in the Chef run with the - # action +:delete+. Given a Chef Recipe that deletes "NetFX3" as a - # +windows_feature+: - # - # windows_feature 'NetFX3' do - # action :delete - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_feature+ resource with ChefSpec. - # - # @example Assert that a +windows_feature+ was deleted - # expect(chef_run).to delete_windows_feature('NetFX3') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def delete_windows_feature(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature, :delete, resource_name) - end - - def delete_windows_feature_dism(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_dism, :delete, resource_name) - end - - def delete_windows_feature_powershell(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_feature_powershell, :delete, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:create+. Given a Chef Recipe that creates "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # command 'mybatch.bat' - # action :create - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was created - # expect(chef_run).to create_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def create_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :create, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:disable+. Given a Chef Recipe that creates "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # action :disable - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was disabled - # expect(chef_run).to disable_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def disable_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :disable, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:enable+. Given a Chef Recipe that creates "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # action :enable - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was enabled - # expect(chef_run).to enable_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def enable_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :enable, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:delete+. Given a Chef Recipe that deletes "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # action :delete - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was deleted - # expect(chef_run).to delete_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def delete_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :delete, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:run+. Given a Chef Recipe that runs "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # action :run - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was run - # expect(chef_run).to run_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def run_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :run, resource_name) - end - - # - # Assert that a +windows_task+ resource exists in the Chef run with the - # action +:change+. Given a Chef Recipe that changes "mytask" as a - # +windows_task+: - # - # windows_task 'mytask' do - # action :change - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_task+ resource with ChefSpec. - # - # @example Assert that a +windows_task+ was changed - # expect(chef_run).to change_windows_task('mytask') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def change_windows_task(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_task, :change, resource_name) - end - - # - # Assert that a +windows_path+ resource exists in the Chef run with the - # action +:add+. Given a Chef Recipe that adds "C:\7-Zip" to the Windows - # PATH env var - # - # windows_path 'C:\7-Zip' do - # action :add - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_path+ resource with ChefSpec. - # - # @example Assert that a +windows_path+ was added - # expect(chef_run).to add_windows_path('C:\7-Zip') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def add_windows_path(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_path, :add, resource_name) - end - - # - # Assert that a +windows_path+ resource exists in the Chef run with the - # action +:remove+. Given a Chef Recipe that removes "C:\7-Zip" from the - # Windows PATH env var - # - # windows_path 'C:\7-Zip' do - # action :remove - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_path+ resource with ChefSpec. - # - # @example Assert that a +windows_path+ was removed - # expect(chef_run).to remove_windows_path('C:\7-Zip') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def remove_windows_path(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_path, :remove, resource_name) - end - - # - # Assert that a +windows_pagefile+ resource exists in the Chef run with the - # action +:set+. Given a Chef Recipe that sets a pagefile - # - # windows_pagefile "pagefile" do - # system_managed true - # initial_size 1024 - # maximum_size 4096 - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_pagefile+ resource with ChefSpec. - # - # @example Assert that a +windows_pagefile+ was set - # expect(chef_run).to set_windows_pagefile('pagefile').with( - # initial_size: 1024) - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def set_windows_pagefile(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_pagefile, :set, resource_name) - end - - # - # Assert that a +windows_zipfile+ resource exists in the Chef run with the - # action +:unzip+. Given a Chef Recipe that extracts "SysinternalsSuite.zip" - # to c:/bin - # - # windows_zipfile "c:/bin" do - # source "http://download.sysinternals.com/Files/SysinternalsSuite.zip" - # action :unzip - # not_if {::File.exists?("c:/bin/PsExec.exe")} - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_zipfile+ resource with ChefSpec. - # - # @example Assert that a +windows_zipfile+ was unzipped - # expect(chef_run).to unzip_windows_zipfile_to('c:/bin') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def unzip_windows_zipfile_to(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_zipfile, :unzip, resource_name) - end - - # - # Assert that a +windows_zipfile+ resource exists in the Chef run with the - # action +:zip+. Given a Chef Recipe that zips "c:/src" - # to c:/code.zip - # - # windows_zipfile "c:/code.zip" do - # source "c:/src" - # action :zip - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_zipfile+ resource with ChefSpec. - # - # @example Assert that a +windows_zipfile+ was zipped - # expect(chef_run).to zip_windows_zipfile_to('c:/code.zip') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def zip_windows_zipfile_to(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_zipfile, :zip, resource_name) - end - - # - # Assert that a +windows_share+ resource exists in the Chef run with the - # action +:create+. Given a Chef Recipe that shares "c:/src" - # as Src - # - # windows_share "Src" do - # path "c:/src" - # action :create - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_share+ resource with ChefSpec. - # - # @example Assert that a +windows_share+ was created - # expect(chef_run).to create_windows_share('Src') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def create_windows_share(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_share, :create, resource_name) - end - - # - # Assert that a +windows_share+ resource exists in the Chef run with the - # action +:delete+. Given a Chef Recipe that deletes share "c:/src" - # - # windows_share "Src" do - # action :delete - # end - # - # The Examples section demonstrates the different ways to test a - # +windows_share+ resource with ChefSpec. - # - # @example Assert that a +windows_share+ was created - # expect(chef_run).to delete_windows_share('Src') - # - # - # @param [String, Regex] resource_name - # the name of the resource to match - # - # @return [ChefSpec::Matchers::ResourceMatcher] - # - def delete_windows_share(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_share, :delete, resource_name) - end - - # All the other less commonly used LWRPs - def create_windows_shortcut(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_shortcut, :create, resource_name) - end - - def create_windows_auto_run(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_auto_run, :create, resource_name) - end - - def remove_windows_auto_run(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_auto_run, :remove, resource_name) - end - - def create_windows_printer(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_printer, :create, resource_name) - end - - def delete_windows_printer(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_printer, :delete, resource_name) - end - - def create_windows_printer_port(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_printer_port, :create, resource_name) - end - - def delete_windows_printer_port(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_printer_port, :delete, resource_name) - end - - def install_windows_font(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_font, :install, resource_name) - end - - def create_windows_certificate_binding(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_certificate_binding, :create, resource_name) - end - - def delete_windows_certificate_binding(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_certificate_binding, :delete, resource_name) - end - - def create_windows_http_acl(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_http_acl, :create, resource_name) - end - - def delete_windows_http_acl(resource_name) - ChefSpec::Matchers::ResourceMatcher.new(:windows_http_acl, :delete, resource_name) - end -end diff --git a/cookbooks/windows/libraries/powershell_helper.rb b/cookbooks/windows/libraries/powershell_helper.rb index e9261b4c..be021a3c 100644 --- a/cookbooks/windows/libraries/powershell_helper.rb +++ b/cookbooks/windows/libraries/powershell_helper.rb @@ -1,9 +1,9 @@ # # Author:: Seth Chisamore () # Cookbook:: windows -# Library:: helper +# Library:: powershell_helper # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/cookbooks/windows/libraries/registry_helper.rb b/cookbooks/windows/libraries/registry_helper.rb index 91ae91a0..6b20a5a6 100644 --- a/cookbooks/windows/libraries/registry_helper.rb +++ b/cookbooks/windows/libraries/registry_helper.rb @@ -3,10 +3,10 @@ # Author:: Seth Chisamore () # Author:: Paul Morton () # Cookbook:: windows -# Provider:: registry +# Library:: registry_helper # # Copyright:: 2010-2017, VMware, Inc. -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # Copyright:: 2011-2017, Business Intelligence Associates, Inc # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -257,9 +257,9 @@ def resolve_user_to_sid(username) end Chef::Log.debug("Resolved user SID to #{sid}") - return sid + sid rescue - return nil + nil end def hive_loaded?(path) @@ -350,7 +350,7 @@ def ensure_hive_unloaded(hive_loaded = false) end module Registry - module_function + module_function # rubocop: disable Lint/UselessAccessModifier extend Windows::RegistryHelper end diff --git a/cookbooks/windows/libraries/version.rb b/cookbooks/windows/libraries/version.rb index 64f62304..a3534513 100644 --- a/cookbooks/windows/libraries/version.rb +++ b/cookbooks/windows/libraries/version.rb @@ -3,7 +3,7 @@ # Cookbook:: windows # Library:: version # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -139,6 +139,7 @@ def initialize end WIN_VERSIONS = { + 'Windows 10' => { major: 10, minor: 0, callable: -> { @product_type != VER_NT_WORKSTATION } }, 'Windows Server 2012 R2' => { major: 6, minor: 3, callable: -> { @product_type != VER_NT_WORKSTATION } }, 'Windows 8' => { major: 6, minor: 2, callable: -> { @product_type == VER_NT_WORKSTATION } }, 'Windows Server 2012' => { major: 6, minor: 2, callable: -> { @product_type != VER_NT_WORKSTATION } }, diff --git a/cookbooks/windows/libraries/version_helper.rb b/cookbooks/windows/libraries/version_helper.rb index 2b5d4b34..fcb0aa79 100644 --- a/cookbooks/windows/libraries/version_helper.rb +++ b/cookbooks/windows/libraries/version_helper.rb @@ -36,6 +36,20 @@ module CoreSKU STANDARD_SERVER = 0x0D unless constants.include?(:STANDARD_SERVER) # Server Standard without Hyper-V Core STANDARD_SERVER_V = 0x28 unless constants.include?(:STANDARD_SERVER_V) + # Small Business Server Premium Core + PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE = 0x3F unless constants.include?(:PRODUCT_SMALLBUSINESS_SERVER_PREMIUM_CORE) + # Server Solutions Premium Core + STANDARD_SERVER_SOLUTIONS = 0x35 unless constants.include?(:STANDARD_SERVER_SOLUTIONS) + # Storage Server Enterprise Core + STORAGE_ENTERPRISE_SERVER = 0x2E unless constants.include?(:STORAGE_ENTERPRISE_SERVER) + # Storage Server Express Core + STORAGE_EXPRESS_SERVER = 0x2B unless constants.include?(:STORAGE_EXPRESS_SERVER) + # Storage Server Standard Core + STORAGE_STANDARD_SERVER = 0x2C unless constants.include?(:STORAGE_STANDARD_SERVER) + # Storage Server Workgroup Core + STORAGE_WORKGROUP_SERVER = 0x2D unless constants.include?(:STORAGE_WORKGROUP_SERVER) + # Web Server Core + WEB_SERVER = 0x1D unless constants.include?(:WEB_SERVER) end # Module referencing product type contants @@ -73,7 +87,7 @@ def self.nt_version(node) end def self.validate_platform(node) - raise 'Windows helper are only supported on windows platform!' if node['platform'] != 'windows' + raise 'Windows helper are only supported on windows platform!' unless node['platform'] == 'windows' end end end diff --git a/cookbooks/windows/libraries/windows_helper.rb b/cookbooks/windows/libraries/windows_helper.rb index 4d102dae..edc2d427 100644 --- a/cookbooks/windows/libraries/windows_helper.rb +++ b/cookbooks/windows/libraries/windows_helper.rb @@ -1,9 +1,9 @@ # # Author:: Seth Chisamore () # Cookbook:: windows -# Library:: helper +# Library:: windows_helper # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,6 +20,9 @@ require 'uri' require 'Win32API' if Chef::Platform.windows? require 'chef/exceptions' +require 'openssl' +require 'chef/mixin/powershell_out' +require 'chef/util/path_helper' module Windows module Helper @@ -30,6 +33,7 @@ module Helper # returns windows friendly version of the provided path, # ensures backslashes are used everywhere def win_friendly_path(path) + Chef::Log.warn('The win_friendly_path helper has been deprecated and will be removed from the next major release of the windows cookbook. Please update any cookbooks using this helper to instead require `chef/util/path_helper` and then use `Chef::Util::PathHelper.cleanpath`.') path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR || '\\') if path end @@ -47,16 +51,6 @@ def locate_sysnative_cmd(cmd) end end - # Create a feature provider dependent value object. - # mainly created becasue Windows Feature names are - # different based on whether dism.exe or servicemanagercmd.exe - # is used for installation - def value_for_feature_provider(provider_hash) - p = Chef::Platform.find_provider_for_node(node, :windows_feature) - key = p.to_s.downcase.split('::').last - provider_hash[key] || provider_hash[key.to_sym] - end - # singleton instance of the Windows Version checker def win_version @win_version ||= Windows::Version.new @@ -88,7 +82,7 @@ def cached_file(source, checksum = nil, windows_path = true) cache_file_path = source end - windows_path ? win_friendly_path(cache_file_path) : cache_file_path + windows_path ? Chef::Util::PathHelper.cleanpath(cache_file_path) : cache_file_path end end @@ -103,7 +97,7 @@ def expand_env_vars(path) buf.strip end - def is_package_installed?(package_name) # rubocop:disable Style/PredicateName + def is_package_installed?(package_name) # rubocop:disable Naming/PredicateName installed_packages.include?(package_name) end diff --git a/cookbooks/windows/libraries/windows_privileged.rb b/cookbooks/windows/libraries/windows_privileged.rb index 5abf08b3..68d96c36 100644 --- a/cookbooks/windows/libraries/windows_privileged.rb +++ b/cookbooks/windows/libraries/windows_privileged.rb @@ -57,7 +57,7 @@ def run(*privileges) unless OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, token) raise get_last_error end - token = token.unpack('L')[0] + token = token.unpack1('L') privileges.each do |name| unless adjust_privilege(token, name, SE_PRIVILEGE_ENABLED) diff --git a/cookbooks/windows/libraries/wmi_helper.rb b/cookbooks/windows/libraries/wmi_helper.rb index 4d9c6094..2acdc383 100644 --- a/cookbooks/windows/libraries/wmi_helper.rb +++ b/cookbooks/windows/libraries/wmi_helper.rb @@ -1,7 +1,9 @@ # # Author:: Adam Edwards () +# Cookbook:: windows +# Library:: wmi_helper # -# Copyright:: 2014-2017, Chef Software, Inc. +# Copyright:: 2014-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/cookbooks/windows/metadata.rb b/cookbooks/windows/metadata.rb index 61831f17..6aa5de85 100644 --- a/cookbooks/windows/metadata.rb +++ b/cookbooks/windows/metadata.rb @@ -4,9 +4,8 @@ license 'Apache-2.0' description 'Provides a set of useful Windows-specific primitives.' long_description IO.read(File.join(File.dirname(__FILE__), 'README.md')) -version '3.0.5' +version '5.2.2' supports 'windows' -depends 'ohai', '>= 4.0.0' source_url 'https://github.com/chef-cookbooks/windows' issues_url 'https://github.com/chef-cookbooks/windows/issues' -chef_version '>= 12.6' if respond_to?(:chef_version) +chef_version '>= 13.4' diff --git a/cookbooks/windows/providers/dns.rb b/cookbooks/windows/providers/dns.rb new file mode 100644 index 00000000..f6a8b31f --- /dev/null +++ b/cookbooks/windows/providers/dns.rb @@ -0,0 +1,153 @@ +# +# Author:: Richard Lavey (richard.lavey@calastone.com) +# Cookbook:: windows +# Provider:: dns +# +# Copyright:: 2015, Calastone Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# See this for info on DNSCMD +# https://technet.microsoft.com/en-gb/library/cc772069.aspx#BKMK_10 + +include Windows::Helper + +# Support whyrun +def whyrun_supported? + true +end + +action :create do + if @current_resource.exists + needs_change = (@new_resource.record_type != @current_resource.record_type) || + (@new_resource.ttl > 0 && @new_resource.ttl != @current_resource.ttl) || + (@new_resource.target.is_a?(String) && @new_resource.target != @current_resource.target) || + (@new_resource.target.is_a?(Array) && !(@new_resource.target - @current_resource.target).empty?) + + if needs_change + converge_by("Changing #{@new_resource.host_name}") do + update_dns + end + else + Chef::Log.debug("#{@new_resource.host_name} already exists - nothing to do") + end + else + converge_by("Creating #{@new_resource.host_name}") do + update_dns + end + end +end + +action :delete do + if @current_resource.exists + converge_by("Deleting #{@current_resource.host_name}") do + execute_command! 'recorddelete', "#{@current_resource.record_type} /f" + end + else + Chef::Log.debug("#{@new_resource.host_name} does not exist - nothing to do") + end +end + +def load_current_resource + # validate the new resource params : A records should be an array + if @new_resource.record_type == 'A' && @new_resource.target.is_a?(String) + raise 'target property must be an array for record_type A' + end + + @current_resource = Chef::Resource::WindowsDns.new(@new_resource.name) + @current_resource.host_name(@new_resource.host_name) + @current_resource.dns_server(@new_resource.dns_server) + + parts = @current_resource.host_name.scan(/(\w+)\.(.*)/) + @host = parts[0][0] + @domain = parts[0][1] + + fetch_attributes +end + +private + +def fetch_attributes + @command = locate_sysnative_cmd('dnscmd.exe') + cmd = shell_out("#{@command} #{@current_resource.dns_server} /enumrecords #{@domain} #{@host}") + Chef::Log.debug "dnscmd reports: #{cmd.stdout}" + + # extract values from returned text + if cmd.stdout.include?('DNS_ERROR_NAME_DOES_NOT_EXIST') + @current_resource.exists = false + @current_resource.target([]) + elsif cmd.exitstatus == 0 + @current_resource.exists = true + + m = cmd.stdout.scan(/(\d+)\s(A)\s+(\d+\.\d+\.\d+\.\d+)/) + if m.empty? + m = cmd.stdout.scan(/(\d+)\s(CNAME)\s+((?:\w+\.)+)/) + if m.empty? + @current_resource.exists = false + @current_resource.target([]) + else + # We have a cname record + @current_resource.record_type('CNAME') + @current_resource.ttl(m[0][0].to_i) + @current_resource.target(m[0][2].chomp('.')) + end + else + # we have A entries + @current_resource.record_type('A') + @current_resource.ttl(m[0][0].to_i) + addresses = [] + m.each do |match| + addresses.push(match[2]) + end + @current_resource.target(addresses) + end + else + raise "dnscmd returned error #{cmd.exitstatus} : #{cmd.stderr} #{cmd.stdout}" + end +end + +def update_dns + ttl = @new_resource.ttl if @new_resource.ttl > 0 + + if @current_resource.record_type != @new_resource.record_type + # delete current record(s) as we're changing the type + execute_command! 'recorddelete', "#{@current_resource.record_type} /f" + end + + if @new_resource.record_type == 'A' + # delete existing records that are no longer defined + (@current_resource.target - @new_resource.target).each do |address| + Chef::Log.info "Deleting #{address}" + execute_command! 'recorddelete', "A #{address} /f" + end + + # add new records that don't exist + # if ttl has changed then update all records + addresses = if @current_resource.ttl == @new_resource.ttl + (@new_resource.target - @current_resource.target) + else + @new_resource.target + end + addresses.each do |address| + Chef::Log.info "Adding/Changing #{address}" + execute_command! 'recordadd', "#{ttl} A #{address}" + end + else + execute_command! 'recordadd', "#{ttl} CNAME #{@new_resource.target}" + end +end + +def execute_command!(mode, options) + shell_out!("#{@command} #{@current_resource.dns_server} /#{mode} #{@domain} #{@host} #{options}") +end diff --git a/cookbooks/windows/recipes/default.rb b/cookbooks/windows/recipes/default.rb index c3832610..73845f6c 100644 --- a/cookbooks/windows/recipes/default.rb +++ b/cookbooks/windows/recipes/default.rb @@ -3,7 +3,7 @@ # Cookbook:: windows # Recipe:: default # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/cookbooks/windows/resources/auto_run.rb b/cookbooks/windows/resources/auto_run.rb index 4330b995..3b8f993f 100644 --- a/cookbooks/windows/resources/auto_run.rb +++ b/cookbooks/windows/resources/auto_run.rb @@ -3,8 +3,8 @@ # Cookbook:: windows # Resource:: auto_run # -# Copyright:: 2011-2017, Business Intelligence Associates, Inc. -# Copyright:: 2017, Chef Software, Inc. +# Copyright:: 2011-2018, Business Intelligence Associates, Inc. +# Copyright:: 2017-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,28 +19,48 @@ # limitations under the License. # -property :program, String -property :name, String, name_property: true +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_auto_run + +property :program_name, String, name_property: true +property :path, String, coerce: proc { |x| x.tr('/', '\\') } property :args, String +property :root, Symbol, + equal_to: %i(machine user), + default: :machine + +alias_method :program, :path action :create do - registry_key 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do + data = "\"#{new_resource.path}\"" + data << " #{new_resource.args}" if new_resource.args + + registry_key registry_path do values [{ - name: new_resource.name, + name: new_resource.program_name, type: :string, - data: "\"#{new_resource.program}\" #{new_resource.args}", + data: data, }] action :create end end action :remove do - registry_key 'HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run' do + registry_key registry_path do values [{ - name: new_resource.name, + name: new_resource.program_name, type: :string, data: '', }] action :delete end end + +action_class do + # determine the full registry path based on the root property + # @return [String] + def registry_path + { machine: 'HKLM', user: 'HKCU' }[new_resource.root] + \ + '\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run' + end +end diff --git a/cookbooks/windows/resources/certificate.rb b/cookbooks/windows/resources/certificate.rb index cfcb4e1a..074deee7 100644 --- a/cookbooks/windows/resources/certificate.rb +++ b/cookbooks/windows/resources/certificate.rb @@ -4,6 +4,7 @@ # Resource:: certificate # # Copyright:: 2015-2017, Calastone Ltd. +# Copyright:: 2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,31 +19,23 @@ # limitations under the License. # -include Windows::Helper +require 'chef/util/path_helper' -property :source, String, name_property: true, required: true +chef_version_for_provides '< 14.7' if respond_to?(:chef_version_for_provides) +resource_name :windows_certificate + +property :source, String, name_property: true property :pfx_password, String property :private_key_acl, Array -property :store_name, String, default: 'MY', regex: /^(?:MY|CA|ROOT|TrustedPublisher|TRUSTEDPEOPLE)$/ +property :store_name, String, default: 'MY', equal_to: ['TRUSTEDPUBLISHER', 'TrustedPublisher', 'CLIENTAUTHISSUER', 'REMOTE DESKTOP', 'ROOT', 'TRUSTEDDEVICES', 'WEBHOSTING', 'CA', 'AUTHROOT', 'TRUSTEDPEOPLE', 'MY', 'SMARTCARDROOT', 'TRUST', 'DISALLOWED'] property :user_store, [true, false], default: false +property :cert_path, String +property :sensitive, [ TrueClass, FalseClass ], default: lazy { |r| r.pfx_password ? true : false } action :create do - hash = '$cert.GetCertHashString()' - code_script = cert_script(true) << - within_store_script { |store| store + '.Add($cert)' } << - acl_script(hash) - - guard_script = cert_script(false) << - cert_exists_script(hash) - - converge_by("adding certificate #{new_resource.source} into #{new_resource.store_name} to #{cert_location}\\#{new_resource.store_name}") do - powershell_script new_resource.name do - guard_interpreter :powershell_script - convert_boolean_return true - code code_script - not_if guard_script - end - end + load_gem + + add_cert(OpenSSL::X509::Certificate.new(raw_source)) end # acl_add is a modify-if-exists operation : not idempotent @@ -60,59 +53,134 @@ code_script << acl_script(hash) guard_script << cert_exists_script(hash) - converge_by("setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}") do - powershell_script new_resource.name do - guard_interpreter :powershell_script - convert_boolean_return true - code code_script - only_if guard_script - end + powershell_script "setting the acls on #{new_resource.source} in #{cert_location}\\#{new_resource.store_name}" do + guard_interpreter :powershell_script + convert_boolean_return true + code code_script + only_if guard_script + sensitive if new_resource.sensitive end end action :delete do - # do we have a hash or a subject? - # TODO: It's a bit annoying to know the thumbprint of a cert you want to remove when you already - # have the file. Support reading the hash directly from the file if provided. - search = if new_resource.source =~ /^[a-fA-F0-9]{40}$/ - "Thumbprint -eq '#{new_resource.source}'" - else - "Subject -like '*#{new_resource.source.sub(/\*/, '`*')}*'" # escape any * in the source - end - cert_command = "Get-ChildItem Cert:\\#{cert_location}\\#{new_resource.store_name} | where { $_.#{search} }" - - code_script = within_store_script do |store| - <<-EOH -foreach ($c in #{cert_command}) -{ - #{store}.Remove($c) -} -EOH - end - guard_script = "@(#{cert_command}).Count -gt 0\n" - converge_by("Removing certificate #{new_resource.source} from #{cert_location}\\#{new_resource.store_name}") do - powershell_script new_resource.name do - guard_interpreter :powershell_script - convert_boolean_return true - code code_script - only_if guard_script + load_gem + + cert_obj = fetch_cert + if cert_obj + converge_by("Deleting certificate #{new_resource.source} from Store #{new_resource.store_name}") do + delete_cert end end end -action_class.class_eval do +action :fetch do + load_gem + + cert_obj = fetch_cert + if cert_obj + show_or_store_cert(cert_obj) + else + Chef::Log.info('Certificate not found') + end +end + +action :verify do + load_gem + + out = verify_cert + if !!out == out + out = out ? 'Certificate is valid' : 'Certificate not valid' + end + Chef::Log.info(out.to_s) +end + +action_class do + require 'openssl' + + # load the gem and rescue a gem install if it fails to load + def load_gem + gem 'win32-certstore', '>= 0.1.8' + require 'win32-certstore' # until this is in core chef + rescue LoadError + Chef::Log.debug('Did not find win32-certstore >= 0.1.8 gem installed. Installing now') + chef_gem 'win32-certstore' do + compile_time true + action :upgrade + end + + require 'win32-certstore' + end + + def add_cert(cert_obj) + store = ::Win32::Certstore.open(new_resource.store_name) + store.add(cert_obj) + end + + def delete_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.delete(new_resource.source) + end + + def fetch_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.get(new_resource.source) + end + + def verify_cert + store = ::Win32::Certstore.open(new_resource.store_name) + store.valid?(new_resource.source) + end + + def show_or_store_cert(cert_obj) + if new_resource.cert_path + export_cert(cert_obj, new_resource.cert_path) + if ::File.size(new_resource.cert_path) > 0 + Chef::Log.info("Certificate export in #{new_resource.cert_path}") + else + ::File.delete(new_resource.cert_path) + end + else + Chef::Log.info(cert_obj.display) + end + end + + def export_cert(cert_obj, cert_path) + out_file = ::File.new(cert_path, 'w+') + case ::File.extname(cert_path) + when '.pem' + out_file.puts(cert_obj.to_pem) + when '.der' + out_file.puts(cert_obj.to_der) + when '.cer' + cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CER").stdout + out_file.puts(cert_out) + when '.crt' + cert_out = powershell_out("openssl x509 -text -inform DER -in #{cert_obj.to_pem} -outform CRT").stdout + out_file.puts(cert_out) + when '.pfx' + cert_out = powershell_out("openssl pkcs12 -export -nokeys -in #{cert_obj.to_pem} -outform PFX").stdout + out_file.puts(cert_out) + when '.p7b' + cert_out = powershell_out("openssl pkcs7 -export -nokeys -in #{cert_obj.to_pem} -outform P7B").stdout + out_file.puts(cert_out) + else + Chef::Log.info('Supported certificate format .pem, .der, .cer, .crt, .pfx and .p7b') + end + out_file.close + end + def cert_location @location ||= new_resource.user_store ? 'CurrentUser' : 'LocalMachine' end def cert_script(persist) cert_script = '$cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2' - file = win_friendly_path(new_resource.source) + file = Chef::Util::PathHelper.cleanpath(new_resource.source) cert_script << " \"#{file}\"" if ::File.extname(file.downcase) == '.pfx' cert_script << ", \"#{new_resource.pfx_password}\"" if persist && new_resource.user_store - cert_script << ', [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet' + cert_script << ', ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet)' elsif persist cert_script << ', ([System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeyset)' end @@ -163,4 +231,30 @@ def acl_script(hash) end set_acl_script end + + def raw_source + ext = ::File.extname(new_resource.source) + convert_pem(ext, new_resource.source) + end + + def convert_pem(ext, source) + out = case ext + when '.crt', '.der' + powershell_out("openssl x509 -text -inform DER -in #{source} -outform PEM").stdout + when '.cer' + powershell_out("openssl x509 -text -inform DER -in #{source} -outform PEM").stdout + when '.pfx' + powershell_out("openssl pkcs12 -in #{source} -nodes -passin pass:'#{new_resource.pfx_password}'").stdout + when '.p7b' + powershell_out("openssl pkcs7 -print_certs -in #{source} -outform PEM").stdout + end + out = ::File.read(source) if out.nil? || out.empty? + format_raw_out(out) + end + + def format_raw_out(out) + begin_cert = '-----BEGIN CERTIFICATE-----' + end_cert = '-----END CERTIFICATE-----' + begin_cert + out[/#{begin_cert}(.*?)#{end_cert}/m, 1] + end_cert + end end diff --git a/cookbooks/windows/resources/certificate_binding.rb b/cookbooks/windows/resources/certificate_binding.rb index ed494571..df0aa442 100644 --- a/cookbooks/windows/resources/certificate_binding.rb +++ b/cookbooks/windows/resources/certificate_binding.rb @@ -4,6 +4,7 @@ # Resource:: certificate_binding # # Copyright:: 2015-2017, Calastone Ltd. +# Copyright:: 2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,20 +19,20 @@ # limitations under the License. # -include Chef::Mixin::ShellOut include Chef::Mixin::PowershellOut include Windows::Helper -property :cert_name, String, name_property: true, required: true +property :cert_name, String, name_property: true property :name_kind, Symbol, equal_to: [:hash, :subject], default: :subject property :address, String, default: '0.0.0.0' property :port, Integer, default: 443 property :app_id, String, default: '{4dc3e181-e14b-4a21-b022-59fc669b0914}' -property :store_name, String, default: 'MY', regex: /^(?:MY|CA|ROOT)$/ +property :store_name, String, default: 'MY', equal_to: ['TRUSTEDPUBLISHER', 'CLIENTAUTHISSUER', 'REMOTE DESKTOP', 'ROOT', 'TRUSTEDDEVICES', 'WEBHOSTING', 'CA', 'AUTHROOT', 'TRUSTEDPEOPLE', 'MY', 'SMARTCARDROOT', 'TRUST'] property :exists, [true, false], desired_state: true load_current_value do |desired| - cmd = shell_out("#{locate_sysnative_cmd('netsh.exe')} http show sslcert ipport=#{desired.address}:#{desired.port}") + mode = desired.address.match(/(\d+\.){3}\d+|\[.+\]/).nil? ? 'hostnameport' : 'ipport' + cmd = shell_out("#{locate_sysnative_cmd('netsh.exe')} http show sslcert #{mode}=#{desired.address}:#{desired.port}") Chef::Log.debug "netsh reports: #{cmd.stdout}" address desired.address @@ -81,14 +82,15 @@ end end -action_class.class_eval do +action_class do def netsh_command locate_sysnative_cmd('netsh.exe') end def add_binding(hash) cmd = "#{netsh_command} http add sslcert" - cmd << " ipport=#{current_resource.address}:#{current_resource.port}" + mode = address_mode(current_resource.address) + cmd << " #{mode}=#{current_resource.address}:#{current_resource.port}" cmd << " certhash=#{hash}" cmd << " appid=#{current_resource.app_id}" cmd << " certstorename=#{current_resource.store_name}" @@ -98,7 +100,8 @@ def add_binding(hash) end def delete_binding - shell_out!("#{netsh_command} http delete sslcert ipport=#{current_resource.address}:#{current_resource.port}") + mode = address_mode(current_resource.address) + shell_out!("#{netsh_command} http delete sslcert #{mode}=#{current_resource.address}:#{current_resource.port}") end def check_hash(hash) @@ -125,4 +128,8 @@ def hash_from_subject hash = p.stdout.strip hash[0].ord == 239 ? hash.force_encoding('UTF-8').delete!("\xEF\xBB\xBF".force_encoding('UTF-8')) : hash end + + def address_mode(address) + address.match(/(\d+\.){3}\d+|\[.+\]/).nil? ? 'hostnameport' : 'ipport' + end end diff --git a/cookbooks/windows/attributes/default.rb b/cookbooks/windows/resources/dns.rb similarity index 50% rename from cookbooks/windows/attributes/default.rb rename to cookbooks/windows/resources/dns.rb index 7e638458..35b02740 100644 --- a/cookbooks/windows/attributes/default.rb +++ b/cookbooks/windows/resources/dns.rb @@ -1,9 +1,9 @@ # -# Author:: Seth Chisamore () -# Cookbook:: windows -# Attribute:: default +# Author:: Richard Lavey (richard.lavey@calastone.com) +# Cookbook Name:: windows +# Resource:: dns # -# Copyright:: 2011-2017, Chef Software, Inc +# Copyright:: 2015, Calastone Ltd. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,4 +18,13 @@ # limitations under the License. # -default['windows']['rubyzipversion'] = nil +actions :create, :delete +default_action :create + +attribute :host_name, kind_of: String, name_property: true, required: true +attribute :record_type, kind_of: String, default: 'A', regex: /^(?:A|CNAME)$/ +attribute :dns_server, kind_of: String, default: '.' +attribute :target, kind_of: [Array, String], required: true +attribute :ttl, kind_of: Integer, required: false, default: 0 + +attr_accessor :exists diff --git a/cookbooks/windows/resources/feature.rb b/cookbooks/windows/resources/feature.rb index f9d74286..e6c45427 100644 --- a/cookbooks/windows/resources/feature.rb +++ b/cookbooks/windows/resources/feature.rb @@ -3,7 +3,7 @@ # Cookbook:: windows # Resource:: feature # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,61 +18,42 @@ # limitations under the License. # +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_feature + property :feature_name, [Array, String], name_property: true property :source, String property :all, [true, false], default: false +property :management_tools, [true, false], default: false property :install_method, Symbol, equal_to: [:windows_feature_dism, :windows_feature_powershell, :windows_feature_servermanagercmd] - -include Windows::Helper +property :timeout, Integer, default: 600 action :install do - run_default_provider :install + run_default_subresource :install end action :remove do - run_default_provider :remove + run_default_subresource :remove end action :delete do - run_default_provider :delete + run_default_subresource :delete end -action_class.class_eval do - def locate_default_provider - if new_resource.install_method - new_resource.install_method - elsif ::File.exist?(locate_sysnative_cmd('dism.exe')) - :windows_feature_dism - elsif ::File.exist?(locate_sysnative_cmd('servermanagercmd.exe')) - :windows_feature_servermanagercmd - else - :windows_feature_powershell - end - end +action_class do + # call the appropriate windows_feature resource based on the specified subresource + # @return [void] + def run_default_subresource(desired_action) + raise 'Support for Windows feature installation via servermanagercmd.exe has been removed as this support is no longer needed in Windows 2008 R2 and above. You will need to update your cookbook to install either via dism or powershell (preferred).' if new_resource.install_method == :windows_feature_servermanagercmd - def run_default_provider(desired_action) - case locate_default_provider - when :windows_feature_dism - windows_feature_dism new_resource.name do - action desired_action - feature_name new_resource.feature_name - source new_resource.source if new_resource.source - all new_resource.all - end - when :windows_feature_servermanagercmd - windows_feature_servermanagercmd new_resource.name do - action desired_action - feature_name new_resource.feature_name - source new_resource.source if new_resource.source - all new_resource.all - end - when :windows_feature_powershell - windows_feature_powershell new_resource.name do - action desired_action - feature_name new_resource.feature_name - source new_resource.source if new_resource.source - all new_resource.all - end + subresource = new_resource.install_method || :windows_feature_dism + declare_resource(subresource, new_resource.name) do + action desired_action + feature_name new_resource.feature_name + source new_resource.source if new_resource.source + all new_resource.all + timeout new_resource.timeout + management_tools new_resource.management_tools if subresource == :windows_feature_powershell end end end diff --git a/cookbooks/windows/resources/feature_dism.rb b/cookbooks/windows/resources/feature_dism.rb index 6b53b0a9..a028ed46 100644 --- a/cookbooks/windows/resources/feature_dism.rb +++ b/cookbooks/windows/resources/feature_dism.rb @@ -1,9 +1,9 @@ # # Author:: Seth Chisamore () # Cookbook:: windows -# Provider:: feature_dism +# Resource:: feature_dism # -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,87 +18,191 @@ # limitations under the License. # -property :feature_name, [Array, String], name_property: true +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_feature_dism + +property :feature_name, [Array, String], coerce: proc { |x| to_formatted_array(x) }, name_property: true property :source, String property :all, [true, false], default: false +property :timeout, Integer, default: 600 + +# @return [Array] lowercase the array unless we're on < Windows 2012 +def to_formatted_array(x) + x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list + + # feature installs on windows < 2012 are case sensitive so only downcase when on 2012+ + # @todo when we're really ready to remove support for Windows 2008 R2 this check can go away + older_than_2012_or_8? ? x : x.map(&:downcase) +end + +# a simple helper to determine if we're on a windows release pre-2012 / 8 +# @return [Boolean] Is the system older than Windows 8 / 2012 +def older_than_2012_or_8? + node['platform_version'].to_f < 6.2 +end -include Chef::Mixin::ShellOut include Windows::Helper action :install do - Chef::Log.warn("Requested feature #{new_resource.feature_name} is not available on this system.") unless available? - unless !available? || installed? - converge_by("install Windows feature #{new_resource.feature_name}") do - addsource = new_resource.source ? "/LimitAccess /Source:\"#{new_resource.source}\"" : '' - addall = new_resource.all ? '/All' : '' - shell_out!("#{dism} /online /enable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /norestart #{addsource} #{addall}", returns: [0, 42, 127, 3010]) - # Reload ohai data - reload_ohai_features_plugin(new_resource.action, new_resource.feature_name) + reload_cached_dism_data unless node['dism_features_cache'] + fail_if_unavailable # fail if the features don't exist + + Chef::Log.debug("Windows features needing installation: #{features_to_install.empty? ? 'none' : features_to_install.join(',')}") + unless features_to_install.empty? + message = "install Windows feature#{'s' if features_to_install.count > 1} #{features_to_install.join(',')}" + converge_by(message) do + install_command = "#{dism} /online /enable-feature #{features_to_install.map { |f| "/featurename:#{f}" }.join(' ')} /norestart" + install_command << " /LimitAccess /Source:\"#{new_resource.source}\"" if new_resource.source + install_command << ' /All' if new_resource.all + + begin + shell_out!(install_command, returns: [0, 42, 127, 3010], timeout: new_resource.timeout) + rescue Mixlib::ShellOut::ShellCommandFailed => e + raise "Error 50 returned by DISM related to parent features, try setting the 'all' property to 'true' on the 'windows_feature_dism' resource." if required_parent_feature?(e.inspect) + raise e.message + end + reload_cached_dism_data # Reload cached dism feature state end end end action :remove do - if installed? - converge_by("removing Windows feature #{new_resource.feature_name}") do - shell_out!("#{dism} /online /disable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /norestart", returns: [0, 42, 127, 3010]) - # Reload ohai data - reload_ohai_features_plugin(new_resource.action, new_resource.feature_name) + reload_cached_dism_data unless node['dism_features_cache'] + + Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? 'none' : features_to_remove.join(',')}") + unless features_to_remove.empty? + message = "remove Windows feature#{'s' if features_to_remove.count > 1} #{features_to_remove.join(',')}" + + converge_by(message) do + shell_out!("#{dism} /online /disable-feature #{features_to_remove.map { |f| "/featurename:#{f}" }.join(' ')} /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout) + + reload_cached_dism_data # Reload cached dism feature state end end end action :delete do - raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not support on #{win_version.sku}" unless supports_feature_delete? - if available? - converge_by("deleting Windows feature #{new_resource.feature_name} from the image") do - shell_out!("#{dism} /online /disable-feature #{to_array(new_resource.feature_name).map { |feature| "/featurename:#{feature}" }.join(' ')} /Remove /norestart", returns: [0, 42, 127, 3010]) - # Reload ohai data - reload_ohai_features_plugin(new_resource.action, new_resource.feature_name) + raise_if_delete_unsupported + + reload_cached_dism_data unless node['dism_features_cache'] + + fail_if_unavailable # fail if the features don't exist + + Chef::Log.debug("Windows features needing deletion: #{features_to_delete.empty? ? 'none' : features_to_delete.join(',')}") + unless features_to_delete.empty? + message = "delete Windows feature#{'s' if features_to_delete.count > 1} #{features_to_delete.join(',')} from the image" + converge_by(message) do + shell_out!("#{dism} /online /disable-feature #{features_to_delete.map { |f| "/featurename:#{f}" }.join(' ')} /Remove /norestart", returns: [0, 42, 127, 3010], timeout: new_resource.timeout) + + reload_cached_dism_data # Reload cached dism feature state end end end -action_class.class_eval do - def installed? - @installed ||= begin - install_ohai_plugin unless node['dism_features'] +action_class do + # @return [Array] features the user has requested to install which need installation + def features_to_install + @install ||= begin + # disabled features are always available to install + available_for_install = node['dism_features_cache']['disabled'].dup + + # removed features are also available for installation + available_for_install.concat(node['dism_features_cache']['removed']) - # Compare against ohai plugin instead of costly dism run - node['dism_features'].key?(new_resource.feature_name) && node['dism_features'][new_resource.feature_name] =~ /Enable/ + # the intersection of the features to install & disabled/removed features are what needs installing + new_resource.feature_name & available_for_install end end - def available? - @available ||= begin - install_ohai_plugin unless node['dism_features'] + # @return [Array] features the user has requested to remove which need removing + def features_to_remove + # the intersection of the features to remove & enabled features are what needs removing + @remove ||= new_resource.feature_name & node['dism_features_cache']['enabled'] + end - # Compare against ohai plugin instead of costly dism run - node['dism_features'].key?(new_resource.feature_name) && node['dism_features'][new_resource.feature_name] !~ /with payload removed/ + # @return [Array] features the user has requested to delete which need deleting + def features_to_delete + # the intersection of the features to remove & enabled/disabled features are what needs removing + @remove ||= begin + all_available = node['dism_features_cache']['enabled'] + + node['dism_features_cache']['disabled'] + new_resource.feature_name & all_available end end - def reload_ohai_features_plugin(take_action, feature_name) - ohai "Reloading Dism_Features Plugin - Action #{take_action} of feature #{feature_name}" do - action :reload - plugin 'dism_features' - end + # if any features are not supported on this release of Windows or + # have been deleted raise with a friendly message. At one point in time + # we just warned, but this goes against the behavior of ever other package + # provider in Chef and it isn't clear what you'd want if you passed an array + # and some features were available and others were not. + # @return [void] + def fail_if_unavailable + all_available = node['dism_features_cache']['enabled'] + + node['dism_features_cache']['disabled'] + + node['dism_features_cache']['removed'] + + # the difference of desired features to install to all features is what's not available + unavailable = (new_resource.feature_name - all_available) + raise "The Windows feature#{'s' if unavailable.count > 1} #{unavailable.join(',')} #{unavailable.count > 1 ? 'are' : 'is'} not available on this version of Windows. Run 'dism /online /Get-Features' to see the list of available feature names." unless unavailable.empty? end - def install_ohai_plugin - Chef::Log.info("node['dism_features'] data missing. Installing the dism_features Ohai plugin") + # run dism.exe to get a list of all available features and their state + # and save that to the node at node.override level. + # We do this because getting a list of features in dism takes at least a second + # and this data will be persisted across multiple resource runs which gives us + # a much faster run when no features actually need to be installed / removed. + # @return [void] + def reload_cached_dism_data + Chef::Log.debug('Caching Windows features available via dism.exe.') + node.override['dism_features_cache'] = Mash.new + node.override['dism_features_cache']['enabled'] = [] + node.override['dism_features_cache']['disabled'] = [] + node.override['dism_features_cache']['removed'] = [] - ohai_plugin 'dism_features' do - compile_time true - cookbook 'windows' + # Grab raw feature information from dism command line + raw_list_of_features = shell_out("#{dism} /Get-Features /Online /Format:Table /English").stdout + + # Split stdout into an array by windows line ending + features_list = raw_list_of_features.split("\r\n") + features_list.each do |feature_details_raw| + case feature_details_raw + when /Payload Removed/ # matches 'Disabled with Payload Removed' + add_to_feature_mash('removed', feature_details_raw) + when /Enable/ # matches 'Enabled' and 'Enable Pending' aka after reboot + add_to_feature_mash('enabled', feature_details_raw) + when /Disable/ # matches 'Disabled' and 'Disable Pending' aka after reboot + add_to_feature_mash('disabled', feature_details_raw) + end end + Chef::Log.debug("The dism cache contains\n#{node['dism_features_cache']}") + end + + # parse the feature string and add the values to the appropriate array + # in the + # strips trailing whitespace characters then split on n number of spaces + # + | + n number of spaces + # @return [void] + def add_to_feature_mash(feature_type, feature_string) + feature_details = feature_string.strip.split(/\s+[|]\s+/).first + + # dism on windows 2012+ isn't case sensitive so it's best to compare + # lowercase lists so the user input doesn't need to be case sensitive + # @todo when we're ready to remove windows 2008R2 the gating here can go away + feature_details.downcase! unless older_than_2012_or_8? + node.override['dism_features_cache'][feature_type] << feature_details + end + + # Fail unless we're on windows 8+ / 2012+ where deleting a feature is supported + # @return [void] + def raise_if_delete_unsupported + raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not support on Windows releases before Windows 8/2012. Cannot continue!" if older_than_2012_or_8? end - def supports_feature_delete? - win_version.major_version >= 6 && win_version.minor_version >= 2 + def required_parent_feature?(error_message) + error_message.include?('Error: 50') && error_message.include?('required parent feature') end - # account for File System Redirector + # find dism accounting for File System Redirector # http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx def dism @dism ||= begin diff --git a/cookbooks/windows/resources/feature_powershell.rb b/cookbooks/windows/resources/feature_powershell.rb index 1cda4800..cfcaec53 100644 --- a/cookbooks/windows/resources/feature_powershell.rb +++ b/cookbooks/windows/resources/feature_powershell.rb @@ -1,70 +1,242 @@ # # Author:: Greg Zapp () # Cookbook:: windows -# Provider:: feature_powershell +# Resource:: feature_powershell +# +# Copyright:: 2015-2018, Chef Software, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # -property :feature_name, [Array, String], name_attribute: true +require 'chef/json_compat' + +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_feature_powershell + +property :feature_name, [Array, String], coerce: proc { |x| to_formatted_array(x) }, name_property: true property :source, String -property :all, [true, false], default: false +property :all, [TrueClass, FalseClass], default: false +property :timeout, Integer, default: 600 +property :management_tools, [TrueClass, FalseClass], default: false + +# a simple helper to determine if we're on a windows release pre-2012 / 8 +# @return [Boolean] Is the system older than Windows 8 / 2012 +def older_than_2012_or_8? + node['platform_version'].to_f < 6.2 +end + +def to_formatted_array(x) + x = x.split(/\s*,\s*/) if x.is_a?(String) # split multiple forms of a comma separated list + + # feature installs on windows < 8/2012 are case sensitive so only downcase when on 2012+ + older_than_2012_or_8? ? x : x.map(&:downcase) +end include Chef::Mixin::PowershellOut -include Windows::Helper + +action :install do + raise_on_old_powershell + + reload_cached_powershell_data unless node['powershell_features_cache'] + fail_if_unavailable # fail if the features don't exist + fail_if_removed # fail if the features are in removed state + + Chef::Log.debug("Windows features needing installation: #{features_to_install.empty? ? 'none' : features_to_install.join(',')}") + unless features_to_install.empty? + converge_by("install Windows feature#{'s' if features_to_install.count > 1} #{features_to_install.join(',')}") do + install_command = "#{install_feature_cmdlet} #{features_to_install.join(',')}" + install_command << ' -IncludeAllSubFeature' if new_resource.all + if older_than_2012_or_8? && (new_resource.source || new_resource.management_tools) + Chef::Log.warn("The 'source' and 'management_tools' properties are only available on Windows 8/2012 or greater. Skipping these properties!") + else + install_command << " -Source \"#{new_resource.source}\"" if new_resource.source + install_command << ' -IncludeManagementTools' if new_resource.management_tools + end + + cmd = powershell_out!(install_command, timeout: new_resource.timeout) + Chef::Log.info(cmd.stdout) + + reload_cached_powershell_data # Reload cached powershell feature state + end + end +end action :remove do - if installed? - converge_by("remove Windows feature #{new_resource.feature_name}") do - cmd = powershell_out!("#{remove_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')}") + raise_on_old_powershell + + reload_cached_powershell_data unless node['powershell_features_cache'] + + Chef::Log.debug("Windows features needing removal: #{features_to_remove.empty? ? 'none' : features_to_remove.join(',')}") + + unless features_to_remove.empty? + converge_by("remove Windows feature#{'s' if features_to_remove.count > 1} #{features_to_remove.join(',')}") do + cmd = powershell_out!("#{remove_feature_cmdlet} #{features_to_remove.join(',')}", timeout: new_resource.timeout) Chef::Log.info(cmd.stdout) + + reload_cached_powershell_data # Reload cached powershell feature state end end end action :delete do - if available? - converge_by("delete Windows feature #{new_resource.feature_name} from the image") do - cmd = powershell_out!("Uninstall-WindowsFeature #{to_array(new_resource.feature_name).join(',')} -Remove") + raise_on_old_powershell + raise_if_delete_unsupported + + reload_cached_powershell_data unless node['powershell_features_cache'] + + fail_if_unavailable # fail if the features don't exist + + Chef::Log.debug("Windows features needing deletion: #{features_to_delete.empty? ? 'none' : features_to_delete.join(',')}") + + unless features_to_delete.empty? + converge_by("delete Windows feature#{'s' if features_to_delete.count > 1} #{features_to_delete.join(',')} from the image") do + cmd = powershell_out!("Uninstall-WindowsFeature #{features_to_delete.join(',')} -Remove", timeout: new_resource.timeout) Chef::Log.info(cmd.stdout) + + reload_cached_powershell_data # Reload cached powershell feature state end end end -action_class.class_eval do +action_class do + # shellout to determine the actively installed version of powershell + # we have this same data in ohai, but it doesn't get updated if powershell is installed mid run + # @return [Integer] the powershell version or 0 for nothing + def powershell_version + cmd = powershell_out('$PSVersionTable.psversion.major') + return 1 if cmd.stdout.empty? # PowerShell 1.0 doesn't have a $PSVersionTable + Regexp.last_match(1).to_i if cmd.stdout =~ /^(\d+)/ + rescue Errno::ENOENT + 0 # zero as in nothing is installed + end + + # raise if we're running powershell less than 3.0 since we need convertto-json + # check the powershell version via ohai data and if we're < 3.0 also shellout to make sure as + # a newer version could be installed post ohai run. Yes we're double checking. It's fine. + # @todo this can go away when we fully remove support for Windows 2008 R2 + # @raise [RuntimeError] Raise if powershell is < 3.0 + def raise_on_old_powershell + # be super defensive about the powershell lang plugin not being there + return if node['languages'] && node['languages']['powershell'] && node['languages']['powershell']['version'].to_i >= 3 + raise 'The windows_feature_powershell resource requires PowerShell 3.0 or later. Please install PowerShell 3.0+ before running this resource.' if powershell_version < 3 + end + + # The appropirate cmdlet to install a windows feature based on windows release + # @return [String] def install_feature_cmdlet - node['os_version'].to_f < 6.2 ? 'Import-Module ServerManager; Add-WindowsFeature' : 'Install-WindowsFeature' + older_than_2012_or_8? ? 'Add-WindowsFeature' : 'Install-WindowsFeature' end + # The appropirate cmdlet to remove a windows feature based on windows release + # @return [String] def remove_feature_cmdlet - node['os_version'].to_f < 6.2 ? 'Import-Module ServerManager; Remove-WindowsFeature' : 'Uninstall-WindowsFeature' + older_than_2012_or_8? ? 'Remove-WindowsFeature' : 'Uninstall-WindowsFeature' + end + + # @return [Array] features the user has requested to install which need installation + def features_to_install + # the intersection of the features to install & disabled features are what needs installing + @install ||= new_resource.feature_name & node['powershell_features_cache']['disabled'] end - def installed? - @installed ||= begin - cmd = powershell_out("(Get-WindowsFeature #{to_array(new_resource.feature_name).join(',')} | ?{$_.InstallState -ne \'Installed\'}).count") - cmd.stderr.empty? && cmd.stdout.chomp.to_i == 0 + # @return [Array] features the user has requested to remove which need removing + def features_to_remove + # the intersection of the features to remove & enabled features are what needs removing + @remove ||= new_resource.feature_name & node['powershell_features_cache']['enabled'] + end + + # @return [Array] features the user has requested to delete which need deleting + def features_to_delete + # the intersection of the features to remove & enabled/disabled features are what needs removing + @remove ||= begin + all_available = node['powershell_features_cache']['enabled'] + + node['powershell_features_cache']['disabled'] + new_resource.feature_name & all_available end end - def available? - @available ||= begin - cmd = powershell_out("(Get-WindowsFeature #{to_array(new_resource.feature_name).join(',')} | ?{$_.InstallState -ne \'Removed\'}).count") - cmd.stderr.empty? && cmd.stdout.chomp.to_i > 0 + # if any features are not supported on this release of Windows or + # have been deleted raise with a friendly message. At one point in time + # we just warned, but this goes against the behavior of ever other package + # provider in Chef and it isn't clear what you'd want if you passed an array + # and some features were available and others were not. + # @return [void] + def fail_if_unavailable + all_available = node['powershell_features_cache']['enabled'] + + node['powershell_features_cache']['disabled'] + + node['powershell_features_cache']['removed'] + + # the difference of desired features to install to all features is what's not available + unavailable = (new_resource.feature_name - all_available) + raise "The Windows feature#{'s' if unavailable.count > 1} #{unavailable.join(',')} #{unavailable.count > 1 ? 'are' : 'is'} not available on this version of Windows. Run 'Get-WindowsFeature' to see the list of available feature names." unless unavailable.empty? + end + + # run Get-WindowsFeature to get a list of all available features and their state + # and save that to the node at node.override level. + # @return [void] + def reload_cached_powershell_data + Chef::Log.debug('Caching Windows features available via Get-WindowsFeature.') + node.override['powershell_features_cache'] = Mash.new + node.override['powershell_features_cache']['enabled'] = [] + node.override['powershell_features_cache']['disabled'] = [] + node.override['powershell_features_cache']['removed'] = [] + + parsed_feature_list.each do |feature_details_raw| + case feature_details_raw['InstallState'] + when 5 # matches 'Removed' InstallState + add_to_feature_mash('removed', feature_details_raw['Name']) + when 1, 3 # matches 'Installed' or 'InstallPending' states + add_to_feature_mash('enabled', feature_details_raw['Name']) + when 0, 2 # matches 'Available' or 'UninstallPending' states + add_to_feature_mash('disabled', feature_details_raw['Name']) + end end + Chef::Log.debug("The powershell cache contains\n#{node['powershell_features_cache']}") end -end -action :install do - Chef::Log.warn("Requested feature #{new_resource.feature_name} is not available on this system.") unless available? - unless !available? || installed? - converge_by("install Windows feature #{new_resource.feature_name}") do - addsource = new_resource.source ? "-Source \"#{new_resource.source}\"" : '' - addall = new_resource.all ? '-IncludeAllSubFeature' : '' - cmd = if node['os_version'].to_f < 6.2 - powershell_out!("#{install_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')} #{addall}") - else - powershell_out!("#{install_feature_cmdlet} #{to_array(new_resource.feature_name).join(',')} #{addsource} #{addall}") - end - Chef::Log.info(cmd.stdout) + # fetch the list of available feature names and state in JSON and parse the JSON + def parsed_feature_list + # Grab raw feature information from dism command line + # Windows < 2012 doesn't present a state value so we have to check if the feature is installed or not + raw_list_of_features = if older_than_2012_or_8? # make the older format look like the new format, warts and all + powershell_out!('Get-WindowsFeature | Select-Object -Property Name, @{Name=\"InstallState\"; Expression = {If ($_.Installed) { 1 } Else { 0 }}} | ConvertTo-Json -Compress', timeout: new_resource.timeout).stdout + else + powershell_out!('Get-WindowsFeature | Select-Object -Property Name,InstallState | ConvertTo-Json -Compress', timeout: new_resource.timeout).stdout + end + + Chef::JSONCompat.from_json(raw_list_of_features) + end + + # add the features values to the appropriate array + # @return [void] + def add_to_feature_mash(feature_type, feature_details) + # add the lowercase feature name to the mash unless we're on < 2012 where they're case sensitive + node.override['powershell_features_cache'][feature_type] << (older_than_2012_or_8? ? feature_details : feature_details.downcase) + end + + # Fail if any of the packages are in a removed state + # @return [void] + def fail_if_removed + return if new_resource.source # if someone provides a source then all is well + if node['platform_version'].to_f > 6.2 # 2012R2 or later + return if registry_key_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing') && registry_value_exists?('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Servicing', name: 'LocalSourcePath') # if source is defined in the registry, still fine end + removed = new_resource.feature_name & node['powershell_features_cache']['removed'] + raise "The Windows feature#{'s' if removed.count > 1} #{removed.join(',')} #{removed.count > 1 ? 'are' : 'is'} have been removed from the host and cannot be installed." unless removed.empty? + end + + # Fail unless we're on windows 8+ / 2012+ where deleting a feature is supported + def raise_if_delete_unsupported + raise Chef::Exceptions::UnsupportedAction, "#{self} :delete action not supported on Windows releases before Windows 8/2012. Cannot continue!" if older_than_2012_or_8? end end diff --git a/cookbooks/windows/resources/feature_servermanagercmd.rb b/cookbooks/windows/resources/feature_servermanagercmd.rb deleted file mode 100644 index 83f6be24..00000000 --- a/cookbooks/windows/resources/feature_servermanagercmd.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -# Author:: Seth Chisamore () -# Cookbook:: windows -# Provider:: feature_servermanagercmd -# -# Copyright:: 2011-2017, Chef Software, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -property :feature_name, [Array, String], name_attribute: true -property :source, String -property :all, [true, false], default: false - -include Chef::Mixin::ShellOut -include Windows::Helper - -action :install do - unless installed? - converge_by("install Windows feature #{new_resource.feature_name}") do - check_reboot(shell_out("#{servermanagercmd} -install #{to_array(new_resource.feature_name).join(' ')}", returns: [0, 42, 127, 1003, 3010]), new_resource.feature_name) - end - end -end - -action :remove do - if installed? - converge_by("removing Windows feature #{new_resource.feature_name}") do - check_reboot(shell_out("#{servermanagercmd} -remove #{to_array(new_resource.feature_name).join(' ')}", returns: [0, 42, 127, 1003, 3010]), new_resource.feature_name) - end - end -end - -action :delete do - Chef::Log.warn('servermanagercmd does not support removing a feature from the image.') -end - -# Exit codes are listed at http://technet.microsoft.com/en-us/library/cc749128(v=ws.10).aspx - -action_class.class_eval do - def check_reboot(result, feature) - if result.exitstatus == 3010 # successful, but needs reboot - node.run_state['reboot_requested'] = true - Chef::Log.warn("Require reboot to install #{feature}") - elsif result.exitstatus == 1001 # failure, but needs reboot before we can do anything else - node.run_state['reboot_requested'] = true - Chef::Log.warn("Failed installing #{feature} and need to reboot") - end - result.error! # throw for any other bad results. The above results will also get raised, and should cause a reboot via the handler. - end - - def installed? - @installed ||= begin - cmd = shell_out("#{servermanagercmd} -query", returns: [0, 42, 127, 1003]) - cmd.stderr.empty? && (cmd.stdout =~ /^\s*?\[X\]\s.+?\s\[#{new_resource.feature_name}\]\s*$/i) - end - end - - # account for File System Redirector - # http://msdn.microsoft.com/en-us/library/aa384187(v=vs.85).aspx - def servermanagercmd - @servermanagercmd ||= begin - locate_sysnative_cmd('servermanagercmd.exe') - end - end -end diff --git a/cookbooks/windows/resources/font.rb b/cookbooks/windows/resources/font.rb index 2ea9e799..76e7a460 100644 --- a/cookbooks/windows/resources/font.rb +++ b/cookbooks/windows/resources/font.rb @@ -3,8 +3,8 @@ # Cookbook:: windows # Resource:: font # -# Copyright:: 2014-2017, Schuberg Philis BV. -# Copyright:: 2017, Chef Software, Inc. +# Copyright:: 2014-2018, Schuberg Philis BV. +# Copyright:: 2017-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,14 +19,17 @@ # limitations under the License. # -property :name, String, name_property: true -property :source, String, required: false +require 'chef/util/path_helper' -include Windows::Helper +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_font + +property :font_name, String, name_property: true +property :source, String, required: false, coerce: proc { |x| x =~ /^.:.*/ ? x.tr('\\', '/').gsub('//', '/') : x } action :install do if font_exists? - Chef::Log.debug("Not installing font: #{new_resource.name}, font already installed.") + Chef::Log.debug("Not installing font: #{new_resource.font_name} as font already installed.") else retrieve_cookbook_font install_font @@ -34,47 +37,75 @@ end end -action_class.class_eval do +action_class do + # if a source is specified fetch using remote_file. If not use cookbook_file def retrieve_cookbook_font - font_file = new_resource.name + font_file = new_resource.font_name if new_resource.source remote_file font_file do - action :nothing - source "file://#{new_resource.source}" - path win_friendly_path(::File.join(ENV['TEMP'], font_file)) + action :nothing + source source_uri + path Chef::Util::PathHelper.join(ENV['TEMP'], font_file) end.run_action(:create) else cookbook_file font_file do action :nothing cookbook cookbook_name.to_s unless cookbook_name.nil? - path win_friendly_path(::File.join(ENV['TEMP'], font_file)) + path Chef::Util::PathHelper.join(ENV['TEMP'], font_file) end.run_action(:create) end end + # delete the temp cookbook file def del_cookbook_font - file ::File.join(ENV['TEMP'], new_resource.name) do + file Chef::Util::PathHelper.join(ENV['TEMP'], new_resource.font_name) do action :delete end end + # install the font into the appropriate fonts directory def install_font require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ fonts_dir = WIN32OLE.new('WScript.Shell').SpecialFolders('Fonts') folder = WIN32OLE.new('Shell.Application').Namespace(fonts_dir) - converge_by("install font #{new_resource.name}") do - folder.CopyHere(win_friendly_path(::File.join(ENV['TEMP'], new_resource.name))) + converge_by("install font #{new_resource.font_name} to #{fonts_dir}") do + folder.CopyHere(Chef::Util::PathHelper.join(ENV['TEMP'], new_resource.font_name)) end end - # Check to see if the font is installed + # Check to see if the font is installed in the fonts dir # - # === Returns - # :: If the font is installed - # :: If the font is not instaled + # @return [Boolean] Is the font is installed? def font_exists? require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ - fonts_dir = WIN32OLE.new('WScript.Shell').SpecialFolders('Fonts') - ::File.exist?(win_friendly_path(::File.join(fonts_dir, new_resource.name))) + fonts_dir = Chef::Util::PathHelper.join(ENV['windir'], 'fonts') + Chef::Log.debug("Seeing if the font at #{Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name)} exists") + ::File.exist?(Chef::Util::PathHelper.join(fonts_dir, new_resource.font_name)) + end + + # Parse out the schema provided to us to see if it's one we support via remote_file. + # We do this because URI will parse C:/foo as schema 'c', which won't work with remote_file + # + # @return [Boolean] + def remote_file_schema?(schema) + return true if %w(http https ftp).include?(schema) + end + + # return new_resource.source if we have a proper URI specified + # if it's a local file listed as a source return it in file:// format + # + # @return [String] path to the font + def source_uri + begin + require 'uri' + if remote_file_schema?(URI.parse(new_resource.source).scheme) + Chef::Log.debug('source property starts with ftp/http. Using source property unmodified') + return new_resource.source + end + rescue URI::InvalidURIError + Chef::Log.warn("source property of #{new_resource.source} could not be processed as a URI. Check the format you provided.") + end + Chef::Log.debug('source property does not start with ftp/http. Prepending with file:// as it appears to be a local file.') + "file://#{new_resource.source}" end end diff --git a/cookbooks/windows/resources/http_acl.rb b/cookbooks/windows/resources/http_acl.rb index 0f281f8b..11ac8a68 100644 --- a/cookbooks/windows/resources/http_acl.rb +++ b/cookbooks/windows/resources/http_acl.rb @@ -18,10 +18,9 @@ # limitations under the License. # -include Chef::Mixin::ShellOut include Windows::Helper -property :url, String, name_property: true, required: true +property :url, String, name_property: true property :user, String property :sddl, String property :exists, [true, false], desired_state: true @@ -91,7 +90,7 @@ end end -action_class.class_eval do +action_class do def netsh_command locate_sysnative_cmd('netsh.exe') end diff --git a/cookbooks/windows/resources/pagefile.rb b/cookbooks/windows/resources/pagefile.rb index 65be663c..c5f326e8 100644 --- a/cookbooks/windows/resources/pagefile.rb +++ b/cookbooks/windows/resources/pagefile.rb @@ -3,8 +3,8 @@ # Cookbook:: windows # Resource:: pagefile # -# Copyright:: 2012-2017, Nordstrom, Inc. -# Copyright:: 2017, Chef Software, Inc. +# Copyright:: 2012-2018, Nordstrom, Inc. +# Copyright:: 2017-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,17 +19,19 @@ # limitations under the License. # -property :name, String, name_property: true +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_pagefile + +property :path, String, coerce: proc { |x| x.tr('/', '\\') }, name_property: true property :system_managed, [true, false] property :automatic_managed, [true, false], default: false property :initial_size, Integer property :maximum_size, Integer -include Chef::Mixin::ShellOut include Windows::Helper action :set do - pagefile = new_resource.name + pagefile = new_resource.path initial_size = new_resource.initial_size maximum_size = new_resource.maximum_size system_managed = new_resource.system_managed @@ -58,24 +60,36 @@ action :delete do validate_name - pagefile = new_resource.name - delete(pagefile) if exists?(pagefile) + delete(new_resource.path) if exists?(new_resource.path) end -action_class.class_eval do +action_class do + # make sure the provided name property matches the appropriate format + # we do this here and not in the property itself because if automatic_managed + # is set then this validation is not necessary / doesn't make sense at all def validate_name - return if /^.:.*.sys/ =~ new_resource.name - raise "#{new_resource.name} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys" + return if /^.:.*.sys/ =~ new_resource.path + raise "#{new_resource.path} does not match the format DRIVE:\\path\\file.sys for pagefiles. Example: C:\\pagefile.sys" end + # See if the pagefile exists + # + # @param [String] pagefile path to the pagefile + # @return [Boolean] def exists?(pagefile) @exists ||= begin - Chef::Log.debug("Checking if #{pagefile} exists") + Chef::Log.debug("Checking if #{pagefile} exists by runing: #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list") cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" list /format:list", returns: [0]) cmd.stderr.empty? && (cmd.stdout =~ /SettingID=#{get_setting_id(pagefile)}/i) end end + # is the max/min pagefile size set? + # + # @param [String] pagefile path to the pagefile + # @param [String] min the minimum size of the pagefile + # @param [String] max the minimum size of the pagefile + # @return [Boolean] def max_and_min_set?(pagefile, min, max) @max_and_min_set ||= begin Chef::Log.debug("Checking if #{pagefile} min: #{min} and max #{max} are set") @@ -84,20 +98,31 @@ def max_and_min_set?(pagefile, min, max) end end + # create a pagefile + # + # @param [String] pagefile path to the pagefile def create(pagefile) converge_by("create pagefile #{pagefile}") do - cmd = shell_out("#{wmic} pagefileset create name=\"#{win_friendly_path(pagefile)}\"") + Chef::Log.debug("Running #{wmic} pagefileset create name=\"#{pagefile}\"") + cmd = shell_out("#{wmic} pagefileset create name=\"#{pagefile}\"") check_for_errors(cmd.stderr) end end + # delete a pagefile + # + # @param [String] pagefile path to the pagefile def delete(pagefile) converge_by("remove pagefile #{pagefile}") do + Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete") cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" delete") check_for_errors(cmd.stderr) end end + # see if the pagefile is automatically managed by Windows + # + # @return [Boolean] def automatic_managed? @automatic_managed ||= begin Chef::Log.debug('Checking if pagefiles are automatically managed') @@ -106,47 +131,59 @@ def automatic_managed? end end + # turn on automatic management of all pagefiles by Windows def set_automatic_managed converge_by('set pagefile to Automatic Managed') do + Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True") cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=True") check_for_errors(cmd.stderr) end end + # turn off automatic management of all pagefiles by Windows def unset_automatic_managed converge_by('set pagefile to User Managed') do + Chef::Log.debug("Running #{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False") cmd = shell_out("#{wmic} computersystem where name=\"%computername%\" set AutomaticManagedPagefile=False") check_for_errors(cmd.stderr) end end + # set a custom size for the pagefile (vs the defaults) + # + # @param [String] pagefile path to the pagefile + # @param [String] min the minimum size of the pagefile + # @param [String] max the minimum size of the pagefile def set_custom_size(pagefile, min, max) converge_by("set #{pagefile} to InitialSize=#{min} & MaximumSize=#{max}") do + Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}") cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=#{min},MaximumSize=#{max}", returns: [0]) check_for_errors(cmd.stderr) end end - def set_system_managed(pagefile) # rubocop: disable Style/AccessorMethodName + # set a pagefile size to be system managed + # + # @param [String] pagefile path to the pagefile + def set_system_managed(pagefile) # rubocop: disable Naming/AccessorMethodName converge_by("set #{pagefile} to System Managed") do + Chef::Log.debug("Running #{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0") cmd = shell_out("#{wmic} pagefileset where SettingID=\"#{get_setting_id(pagefile)}\" set InitialSize=0,MaximumSize=0", returns: [0]) check_for_errors(cmd.stderr) end end def get_setting_id(pagefile) - pagefile = win_friendly_path(pagefile) - pagefile = pagefile.split('\\') - "#{pagefile[1]} @ #{pagefile[0]}" + split_path = pagefile.split('\\') + "#{split_path[1]} @ #{split_path[0]}" end + # raise if there's an error on stderr on a shellout def check_for_errors(stderr) - Chef::Log.fatal(stderr) unless stderr.empty? + raise stderr.chomp unless stderr.empty? end def wmic - @wmic ||= begin - locate_sysnative_cmd('wmic.exe') - end + @wmic ||= locate_sysnative_cmd('wmic.exe') end end diff --git a/cookbooks/windows/resources/path.rb b/cookbooks/windows/resources/path.rb deleted file mode 100644 index 21a22a88..00000000 --- a/cookbooks/windows/resources/path.rb +++ /dev/null @@ -1,54 +0,0 @@ -# -# Author:: Paul Morton () -# Cookbook:: windows -# Resource:: path -# -# Copyright:: 2011-2017, Business Intelligence Associates, Inc -# Copyright:: 2017, Chef Software, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -property :path, String, name_property: true - -include Windows::Helper - -action :add do - env 'path' do - action :modify - delim ::File::PATH_SEPARATOR - value new_resource.path.tr('/', '\\') - notifies :run, "ruby_block[fix ruby ENV['PATH']]", :immediately - end - - # The windows Env provider does not correctly expand variables in - # the PATH environment variable. Ruby expects these to be expanded. - # This is a temporary fix for that. - # - # Follow at https://github.com/chef/chef/pull/1876 - # - ruby_block "fix ruby ENV['PATH']" do - block do - ENV['PATH'] = expand_env_vars(ENV['PATH']) - end - action :nothing - end -end - -action :remove do - env 'path' do - action :delete - delim ::File::PATH_SEPARATOR - value new_resource.path.tr('/', '\\') - end -end diff --git a/cookbooks/windows/resources/printer.rb b/cookbooks/windows/resources/printer.rb index fe68b029..ed9a2a6a 100644 --- a/cookbooks/windows/resources/printer.rb +++ b/cookbooks/windows/resources/printer.rb @@ -22,7 +22,10 @@ require 'resolv' -property :device_id, String, name_property: true, required: true +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_printer + +property :device_id, String, name_property: true property :comment, String property :default, [true, false], default: false property :driver_name, String, required: true @@ -66,7 +69,7 @@ def printer_exists?(name) end end -action_class.class_eval do +action_class do def create_printer # Create the printer port first windows_printer_port new_resource.ipv4_address do diff --git a/cookbooks/windows/resources/printer_port.rb b/cookbooks/windows/resources/printer_port.rb index 60bd6c97..51d2d438 100644 --- a/cookbooks/windows/resources/printer_port.rb +++ b/cookbooks/windows/resources/printer_port.rb @@ -3,7 +3,7 @@ # Cookbook:: windows # Resource:: printer_port # -# Copyright:: 2012-2017, Nordstrom, Inc. +# Copyright:: 2012-2018, Nordstrom, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -22,7 +22,10 @@ require 'resolv' -property :ipv4_address, String, name_attribute: true, required: true, regex: Resolv::IPv4::Regex +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_printer_port + +property :ipv4_address, String, name_property: true, regex: Resolv::IPv4::Regex property :port_name, String property :port_number, Integer, default: 9100 property :port_description, String @@ -36,15 +39,15 @@ def port_exists?(name) port_reg_key = PORTS_REG_KEY + name Chef::Log.debug "Checking to see if this reg key exists: '#{port_reg_key}'" - Registry.key_exists?(port_reg_key) + registry_key_exists?(port_reg_key) end +# @todo Set @current_resource port properties from registry load_current_value do |desired| name desired.name ipv4_address desired.ipv4_address - port_name desired.port_name || "IP_#{@new_resource.ipv4_address}" - exists port_exists?(desired.port_name) - # TODO: Set @current_resource port properties from registry + port_name desired.port_name || "IP_#{desired.ipv4_address}" + exists port_exists?(desired.port_name || "IP_#{desired.ipv4_address}") end action :create do @@ -67,7 +70,7 @@ def port_exists?(name) end end -action_class.class_eval do +action_class do def create_printer_port port_name = new_resource.port_name || "IP_#{new_resource.ipv4_address}" diff --git a/cookbooks/windows/resources/share.rb b/cookbooks/windows/resources/share.rb index 5526e82e..cffa0f27 100644 --- a/cookbooks/windows/resources/share.rb +++ b/cookbooks/windows/resources/share.rb @@ -1,10 +1,12 @@ -# -*- coding: utf-8 -*- # -# Author:: Sölvi Páll Ásgeirsson (), Richard Lavey (richard.lavey@calastone.com) +# Author:: Sölvi Páll Ásgeirsson () +# Author:: Richard Lavey (richard.lavey@calastone.com) +# Author:: Tim Smith (tsmith@chef.io) # Cookbook:: windows # Resource:: share # # Copyright:: 2014-2017, Sölvi Páll Ásgeirsson. +# Copyright:: 2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -19,273 +21,265 @@ # limitations under the License. # +chef_version_for_provides '< 14.7' if respond_to?(:chef_version_for_provides) +resource_name :windows_share + +require 'chef/json_compat' + +# Specifies a name for the SMB share. The name may be composed of any valid file name characters, but must be less than 80 characters long. The names pipe and mailslot are reserved for use by the computer. property :share_name, String, name_property: true + +# Specifies the path of the location of the folder to share. The path must be fully qualified. Relative paths or paths that contain wildcard characters are not permitted. property :path, String + +# Specifies an optional description of the SMB share. A description of the share is displayed by running the Get-SmbShare cmdlet. The description may not contain more than 256 characters. property :description, String, default: '' -property :full_users, Array, default: [] -property :change_users, Array, default: [] -property :read_users, Array, default: [] -include Windows::Helper +# Specifies which accounts are granted full permission to access the share. Use a comma-separated list to specify multiple accounts. An account may not be specified more than once in the FullAccess, ChangeAccess, or ReadAccess parameter lists, but may be specified once in the FullAccess, ChangeAccess, or ReadAccess parameter list and once in the NoAccess parameter list. +property :full_users, Array, default: [], coerce: proc { |u| u.sort } + +# Specifies which users are granted modify permission to access the share +property :change_users, Array, default: [], coerce: proc { |u| u.sort } + +# Specifies which users are granted read permission to access the share. Multiple users can be specified by supplying a comma-separated list. +property :read_users, Array, default: [], coerce: proc { |u| u.sort } + +# Specifies the lifetime of the new SMB share. A temporary share does not persist beyond the next restart of the computer. By default, new SMB shares are persistent, and non-temporary. +property :temporary, [true, false], default: false + +# Specifies the scope name of the share. +property :scope_name, String, default: '*' + +# Specifies the continuous availability time-out for the share. +property :ca_timeout, Integer, default: 0 + +# Indicates that the share is continuously available. +property :continuously_available, [true, false], default: false + +# Specifies the caching mode of the offline files for the SMB share. +# property :caching_mode, String, equal_to: %w(None Manual Documents Programs BranchCache) + +# Specifies the maximum number of concurrently connected users that the new SMB share may accommodate. If this parameter is set to zero (0), then the number of users is unlimited. +property :concurrent_user_limit, Integer, default: 0 + +# Indicates that the share is encrypted. +property :encrypt_data, [true, false], default: false + +# Specifies which files and folders in the SMB share are visible to users. AccessBased: SMB does not the display the files and folders for a share to a user unless that user has rights to access the files and folders. By default, access-based enumeration is disabled for new SMB shares. Unrestricted: SMB displays files and folders to a user even when the user does not have permission to access the items. +# property :folder_enumeration_mode, String, equal_to: %(AccessBased Unrestricted) + include Chef::Mixin::PowershellOut -require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ +load_current_value do |desired| + # this command selects individual objects because EncryptData & CachingMode have underlying + # types that get converted to their Integer values by ConvertTo-Json & we need to make sure + # those get written out as strings + share_state_cmd = "Get-SmbShare -Name '#{desired.share_name}' | Select-Object Name,Path, Description, Temporary, CATimeout, ContinuouslyAvailable, ConcurrentUserLimit, EncryptData | ConvertTo-Json" + + Chef::Log.debug("Running '#{share_state_cmd}' to determine share state'") + ps_results = powershell_out(share_state_cmd) + + # detect a failure without raising and then set current_resource to nil + if ps_results.error? + Chef::Log.debug("Error fetching share state: #{ps_results.stderr}") + current_value_does_not_exist! + end + + Chef::Log.debug("The Get-SmbShare results were #{ps_results.stdout}") + results = Chef::JSONCompat.from_json(ps_results.stdout) + + path results['Path'] + description results['Description'] + temporary results['Temporary'] + ca_timeout results['CATimeout'] + continuously_available results['ContinuouslyAvailable'] + # caching_mode results['CachingMode'] + concurrent_user_limit results['ConcurrentUserLimit'] + encrypt_data results['EncryptData'] + # folder_enumeration_mode results['FolderEnumerationMode'] + + perm_state_cmd = %(Get-SmbShareAccess -Name "#{desired.share_name}" | Select-Object AccountName,AccessControlType,AccessRight | ConvertTo-Json) + + Chef::Log.debug("Running '#{perm_state_cmd}' to determine share permissions state'") + ps_perm_results = powershell_out(perm_state_cmd) + + # we raise here instead of warning like above because we'd only get here if the above Get-SmbShare + # command was successful and that continuing would leave us with 1/2 known state + raise "Could not determine #{desired.share_name} share permissions by running '#{perm_state_cmd}'" if ps_perm_results.error? -ACCESS_FULL = 2_032_127 -ACCESS_CHANGE = 1_245_631 -ACCESS_READ = 1_179_817 + Chef::Log.debug("The Get-SmbShareAccess results were #{ps_perm_results.stdout}") + + f_users, c_users, r_users = parse_permissions(ps_perm_results.stdout) + + full_users f_users + change_users c_users + read_users r_users +end + +def after_created + raise 'The windows_share resource relies on PowerShell cmdlets not present in Windows releases prior to 8/2012. Cannot continue!' if node['platform_version'].to_f < 6.3 +end + +# given the string output of Get-SmbShareAccess parse out +# arrays of full access users, change users, and read only users +def parse_permissions(results_string) + json_results = Chef::JSONCompat.from_json(results_string) + json_results = [json_results] unless json_results.is_a?(Array) # single result is not an array + + f_users = [] + c_users = [] + r_users = [] + + json_results.each do |perm| + next unless perm['AccessControlType'] == 0 # allow + case perm['AccessRight'] + when 0 then f_users << stripped_account(perm['AccountName']) # 0 full control + when 1 then c_users << stripped_account(perm['AccountName']) # 1 == change + when 2 then r_users << stripped_account(perm['AccountName']) # 2 == read + end + end + [f_users, c_users, r_users] +end + +# local names are returned from Get-SmbShareAccess in the full format MACHINE\\NAME +# but users of this resource would simply say NAME so we need to strip the values for comparison +def stripped_account(name) + name.slice!("#{node['hostname']}\\") + name +end action :create do + # we do this here instead of requiring the property because :delete doesn't need path set raise 'No path property set' unless new_resource.path - if different_path? - unless current_resource.path.nil? || current_resource.path.empty? - converge_by('Removing previous share') do - delete_share - end - end - converge_by("Creating share #{current_resource.share_name}") do + converge_if_changed do + # you can't actually change the path so you have to delete the old share first + delete_share if different_path? + + # powershell cmdlet for create is different than updates + if current_resource.nil? + Chef::Log.debug('The current resource is nil so we will create a new share') create_share + else + Chef::Log.debug('The current resource was not nil so we will update an existing share') + update_share end - end - if different_members?(:full_users) || - different_members?(:change_users) || - different_members?(:read_users) || - different_description? - converge_by("Setting permissions and description for #{new_resource.share_name}") do - set_share_permissions - end + # creating the share does not set permissions so we need to update + update_permissions end end action :delete do - if !current_resource.path.nil? && !current_resource.path.empty? - converge_by("Deleting #{current_resource.share_name}") do + if current_resource.nil? + Chef::Log.debug("#{new_resource.share_name} does not exist - nothing to do") + else + converge_by("delete #{new_resource.share_name}") do delete_share end - else - Chef::Log.debug("#{current_resource.share_name} does not exist - nothing to do") end end -load_current_value do |desired| - wmi = WIN32OLE.connect('winmgmts://') - shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{desired.share_name}'") - existing_share = shares.Count == 0 ? nil : shares.ItemIndex(0) - - description '' - unless existing_share.nil? - path existing_share.Path - description existing_share.Description +action_class do + def different_path? + return false if current_resource.nil? # going from nil to something isn't different for our concerns + return false if current_resource.path == new_resource.path + true end - perms = share_permissions name - unless perms.nil? - full_users perms[:full_users] - change_users perms[:change_users] - read_users perms[:read_users] - end -end + def delete_share + delete_command = "Remove-SmbShare -Name '#{new_resource.share_name}' -Force" -def share_permissions(name) - wmi = WIN32OLE.connect('winmgmts://') - shares = wmi.ExecQuery("SELECT * FROM Win32_LogicalShareSecuritySetting WHERE name = '#{name}'") - - # The security descriptor is an output parameter - sd = nil - begin - shares.ItemIndex(0).GetSecurityDescriptor(sd) - sd = WIN32OLE::ARGV[0] - rescue WIN32OLERuntimeError - Chef::Log.warn('Failed to retrieve any security information about the share.') + Chef::Log.debug("Running '#{delete_command}' to remove the share") + powershell_out!(delete_command) end - read = [] - change = [] - full = [] - - unless sd.nil? - sd.DACL.each do |dacl| - trustee = "#{dacl.Trustee.Domain}\\#{dacl.Trustee.Name}".downcase - case dacl.AccessMask - when ACCESS_FULL - full.push(trustee) - when ACCESS_CHANGE - change.push(trustee) - when ACCESS_READ - read.push(trustee) - else - Chef::Log.warn "Unknown access mask #{dacl.AccessMask} for user #{trustee}. This will be lost if permissions are updated" - end - end + def update_share + update_command = "Set-SmbShare -Name '#{new_resource.share_name}' -Description '#{new_resource.description}' -Force" + + Chef::Log.debug("Running '#{update_command}' to update the share") + powershell_out!(update_command) end - { - full_users: full, - change_users: change, - read_users: read, - } -end + def create_share + raise "#{new_resource.path} is missing or not a directory. Shares cannot be created if the path doesn't first exist." unless ::File.directory? new_resource.path -action_class.class_eval do - def description_exists?(resource) - !resource.description.nil? - end + share_cmd = "New-SmbShare -Name '#{new_resource.share_name}' -Path '#{new_resource.path}' -Description '#{new_resource.description}' -ConcurrentUserLimit #{new_resource.concurrent_user_limit} -CATimeout #{new_resource.ca_timeout} -EncryptData:#{bool_string(new_resource.encrypt_data)} -ContinuouslyAvailable:#{bool_string(new_resource.continuously_available)}" + share_cmd << " -ScopeName #{new_resource.scope_name}" unless new_resource.scope_name == '*' # passing * causes the command to fail + share_cmd << " -Temporary:#{bool_string(new_resource.temporary)}" if new_resource.temporary # only set true - def different_description? - if description_exists?(new_resource) && description_exists?(current_resource) - new_resource.description.casecmp(current_resource.description) != 0 - else - description_exists?(new_resource) || description_exists?(current_resource) - end - end + Chef::Log.debug("Running '#{share_cmd}' to create the share") + powershell_out!(share_cmd) - def different_path? - return true if current_resource.path.nil? - win_friendly_path(new_resource.path).casecmp(win_friendly_path(current_resource.path)) != 0 + # New-SmbShare adds the "Everyone" user with read access no matter what so we need to remove it + # before we add our permissions + revoke_user_permissions(['Everyone']) end - def different_members?(permission_type) - !(current_resource.send(permission_type.to_sym) - new_resource.send(permission_type.to_sym).map(&:downcase)).empty? && - !(new_resource.send(permission_type.to_sym).map(&:downcase) - current_resource.send(permission_type.to_sym)).empty? + # determine what users in the current state don't exist in the desired state + # users/groups will have their permissions updated with the same command that + # sets it, but removes must be performed with Revoke-SmbShareAccess + def users_to_revoke + @users_to_revoke ||= begin + # if the resource doesn't exist then nothing needs to be revoked + if current_resource.nil? + [] + else # if it exists then calculate the current to new resource diffs + (current_resource.full_users + current_resource.change_users + current_resource.read_users) - (new_resource.full_users + new_resource.change_users + new_resource.read_users) + end + end end - def find_share_by_name(name) - wmi = WIN32OLE.connect('winmgmts://') - shares = wmi.ExecQuery("SELECT * FROM Win32_Share WHERE name = '#{name}'") - shares.Count == 0 ? nil : shares.ItemIndex(0) - end + # update existing permissions on a share + def update_permissions + # revoke any users that had something, but now has nothing + revoke_user_permissions(users_to_revoke) unless users_to_revoke.empty? - def delete_share - find_share_by_name(new_resource.share_name).delete - end + # set permissions for each of the permission types + %w(full read change).each do |perm_type| + # set permissions for a brand new share OR + # update permissions if the current state and desired state differ + next unless permissions_need_update?(perm_type) + grant_command = "Grant-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{new_resource.send("#{perm_type}_users").join('","')}\" -Force -AccessRight #{perm_type}" - def create_share - raise "#{new_resource.path} is missing or not a directory" unless ::File.directory? new_resource.path - new_share_script = <<-EOH - $share = [wmiclass]"\\\\#{ENV['COMPUTERNAME']}\\root\\CimV2:Win32_Share" - $result=$share.Create('#{new_resource.path}', - '#{new_resource.share_name}', - 0, - 16777216, - '#{new_resource.description}', - $null, - $null) - exit $result.returnValue - EOH - r = powershell_out new_share_script - message = case r.exitstatus - when 2 - '2 : Access Denied' - when 8 - '8 : Unknown Failure' - when 9 - '9 : Invalid Name' - when 10 - '10 : Invalid Level' - when 21 - '21 : Invalid Parameter' - when 22 - '22 : Duplicate Share' - when 23 - '23 : Redirected Path' - when 24 - '24 : Unknown Device or Directory' - when 25 - '25 : Net Name Not Found' - else - r.exitstatus.to_s - end - - raise "Could not create share. Win32_Share.create returned #{message}" if r.error? + Chef::Log.debug("Running '#{grant_command}' to update the share permissions") + powershell_out!(grant_command) + end end - # set_share_permissions - Enforce the share permissions as dictated by the resource attributes - def set_share_permissions - share_permissions_script = <<-EOH - Function New-SecurityDescriptor - { - param ( - [array]$ACEs - ) - #Create SeCDesc object - $SecDesc = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_SecurityDescriptor").CreateInstance() - - foreach ($ACE in $ACEs ) - { - $SecDesc.DACL += $ACE.psobject.baseobject - } - - #Return the security Descriptor - return $SecDesc - } - - Function New-ACE - { - param ( - [string] $Name, - [string] $Domain, - [string] $Permission = "Read" - ) - #Create the Trusteee Object - $Trustee = ([WMIClass] "\\\\$env:computername\\root\\cimv2:Win32_Trustee").CreateInstance() - $account = get-wmiobject Win32_Account -filter "Name like '$Name' and Domain like '$Domain'" - $accountSID = [WMI] "\\\\$env:ComputerName\\root\\cimv2:Win32_SID.SID='$($account.sid)'" - - $Trustee.Domain = $Domain - $Trustee.Name = $Name - $Trustee.SID = $accountSID.BinaryRepresentation - - #Create ACE (Access Control List) object. - $ACE = ([WMIClass] "\\\\$env:ComputerName\\root\\cimv2:Win32_ACE").CreateInstance() - switch ($Permission) - { - "Read" { $ACE.AccessMask = 1179817 } - "Change" { $ACE.AccessMask = 1245631 } - "Full" { $ACE.AccessMask = 2032127 } - default { throw "$Permission is not a supported permission value. Possible values are 'Read','Change','Full'" } - } - - $ACE.AceFlags = 3 - $ACE.AceType = 0 - $ACE.Trustee = $Trustee - - $ACE - } - - $dacl_array = @() - - EOH - new_resource.full_users.each do |user| - share_permissions_script += user_to_ace(user, 'Full') - end + # determine if permissions need to be updated. + # Brand new share with no permissions defined: no + # Brand new share with permissions defined: yes + # Existing share with differing permissions: yes + # + # @param [String] type the permissions type (Full, Read, or Change) + def permissions_need_update?(type) + property_name = "#{type}_users" - new_resource.change_users.each do |user| - share_permissions_script += user_to_ace(user, 'Change') - end + # brand new share, but nothing to set + return false if current_resource.nil? && new_resource.send(property_name).empty? - new_resource.read_users.each do |user| - share_permissions_script += user_to_ace(user, 'Read') - end + # brand new share with new permissions to set + return true if current_resource.nil? && !new_resource.send(property_name).empty? - share_permissions_script += <<-EOH + # there's a difference between the current and desired state + return true unless (new_resource.send(property_name) - current_resource.send(property_name)).empty? - $dacl = New-SecurityDescriptor -Aces $dacl_array + # anything else + false + end - $share = get-wmiobject win32_share -filter 'Name like "#{new_resource.share_name}"' - $return = $share.SetShareInfo($null, '#{new_resource.description}', $dacl) - exit $return.returnValue - EOH - r = powershell_out(share_permissions_script) - raise "Could not set share permissions. Win32_Share.SedtShareInfo returned #{r.exitstatus}" if r.error? + # revoke user permissions from a share + # @param [Array] users + def revoke_user_permissions(users) + revoke_command = "Revoke-SmbShareAccess -Name '#{new_resource.share_name}' -AccountName \"#{users.join('","')}\" -Force" + Chef::Log.debug("Running '#{revoke_command}' to revoke share permissions") + powershell_out!(revoke_command) end - def user_to_ace(fully_qualified_user_name, access) - domain, user = fully_qualified_user_name.split('\\') - unless domain && user - raise "Invalid user entry #{fully_qualified_user_name}. The user names must be specified as 'DOMAIN\\user'" - end - "\n$dacl_array += new-ace -Name '#{user}' -domain '#{domain}' -permission '#{access}'" + # convert True/False into "$True" & "$False" + def bool_string(bool) + # bool ? 1 : 0 + bool ? '$true' : '$false' end end diff --git a/cookbooks/windows/resources/shortcut.rb b/cookbooks/windows/resources/shortcut.rb index ab35f172..d1a40e11 100644 --- a/cookbooks/windows/resources/shortcut.rb +++ b/cookbooks/windows/resources/shortcut.rb @@ -3,7 +3,8 @@ # Cookbook:: windows # Resource:: shortcut # -# Copyright:: 2010-2017, VMware, Inc. +# Copyright:: 2010-2018, VMware, Inc. +# Copyright:: 2017-2018, Chef Software Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +19,10 @@ # limitations under the License. # -property :name, String +chef_version_for_provides '< 14.0' if respond_to?(:chef_version_for_provides) +resource_name :windows_shortcut + +property :shortcut_name, String, name_property: true property :target, String property :arguments, String property :description, String @@ -28,8 +32,8 @@ load_current_value do |desired| require 'win32ole' if RUBY_PLATFORM =~ /mswin|mingw32|windows/ - link = WIN32OLE.new('WScript.Shell').CreateShortcut(desired.name) - name desired.name + link = WIN32OLE.new('WScript.Shell').CreateShortcut(desired.shortcut_name) + name desired.shortcut_name target(link.TargetPath) arguments(link.Arguments) description(link.Description) @@ -39,8 +43,8 @@ action :create do converge_if_changed do - converge_by "creating shortcut #{new_resource.name}" do - link = WIN32OLE.new('WScript.Shell').CreateShortcut(new_resource.name) + converge_by "creating shortcut #{new_resource.shortcut_name}" do + link = WIN32OLE.new('WScript.Shell').CreateShortcut(new_resource.shortcut_name) link.TargetPath = new_resource.target unless new_resource.target.nil? link.Arguments = new_resource.arguments unless new_resource.arguments.nil? link.Description = new_resource.description unless new_resource.description.nil? diff --git a/cookbooks/windows/resources/task.rb b/cookbooks/windows/resources/task.rb deleted file mode 100644 index fab7afe5..00000000 --- a/cookbooks/windows/resources/task.rb +++ /dev/null @@ -1,384 +0,0 @@ -# -# Author:: Paul Mooring () -# Cookbook:: windows -# Resource:: task -# -# Copyright:: 2012-2017, Chef Software, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -# Passwords can't be loaded for existing tasks, making :modify both confusing -# and not very useful - -require 'chef/mixin/shell_out' -require 'rexml/document' - -include Chef::Mixin::ShellOut -include Chef::Mixin::PowershellOut - -property :task_name, String, name_property: true, regex: [/\A[^\/\:\*\?\<\>\|]+\z/] -property :command, String -property :cwd, String -property :user, String, default: 'SYSTEM' -property :password, String -property :run_level, equal_to: [:highest, :limited], default: :limited -property :force, [true, false], default: false -property :interactive_enabled, [true, false], default: false -property :frequency_modifier, [Integer, String], default: 1 -property :frequency, equal_to: [:minute, - :hourly, - :daily, - :weekly, - :monthly, - :once, - :on_logon, - :onstart, - :on_idle], default: :hourly -property :start_day, String -property :start_time, String -property :day, [String, Integer] -property :months, String -property :idle_time, Integer -property :exists, [true, false], desired_state: true -property :status, Symbol, desired_state: true -property :enabled, [true, false], desired_state: true - -def load_task_hash(task_name) - Chef::Log.debug 'Looking for existing tasks' - - # we use powershell_out here instead of powershell_out! because a failure implies that the task does not exist - task_script = <<-EOH - [Console]::OutputEncoding = [Text.UTF8Encoding]::UTF8 - schtasks /Query /FO LIST /V /TN \"#{task_name}\" - EOH - output = powershell_out(task_script).stdout.force_encoding('UTF-8') - if output.empty? - task = false - else - task = {} - - output.split("\n").map! { |line| line.split(':', 2).map!(&:strip) }.each do |field| - if field.is_a?(Array) && field[0].respond_to?(:to_sym) - task[field[0].gsub(/\s+/, '').to_sym] = field[1] - end - end - end - - task -end - -load_current_value do |desired| - pathed_task_name = desired.task_name.start_with?('\\') ? desired.task_name : "\\#{desired.task_name}" - - task_hash = load_task_hash pathed_task_name - - task_name pathed_task_name - if task_hash.respond_to?(:[]) && task_hash[:TaskName] == pathed_task_name - exists true - status :running if task_hash[:Status] == 'Running' - enabled task_hash[:ScheduledTaskState] == 'Enabled' ? true : false - cwd task_hash[:StartIn] unless task_hash[:StartIn] == 'N/A' - command task_hash[:TaskToRun] - user task_hash[:RunAsUser] - else - exists false - end -end - -action :create do - if current_resource.exists && !(task_need_update? || new_resource.force) - Chef::Log.info "#{new_resource} task already exists - nothing to do" - else - converge_by("creating a new scheduled task #{new_resource.task_name}") do - validate_user_and_password - validate_interactive_setting - validate_create_frequency_modifier - validate_create_day - validate_create_months - validate_idle_time - - options = {} - options['F'] = '' if new_resource.force || task_need_update? - options['SC'] = schedule - options['MO'] = new_resource.frequency_modifier if frequency_modifier_allowed - options['I'] = new_resource.idle_time unless new_resource.idle_time.nil? - options['SD'] = new_resource.start_day unless new_resource.start_day.nil? - options['ST'] = new_resource.start_time unless new_resource.start_time.nil? - options['TR'] = new_resource.command - options['RU'] = new_resource.user - options['RP'] = new_resource.password if use_password? - options['RL'] = 'HIGHEST' if new_resource.run_level == :highest - options['IT'] = '' if new_resource.interactive_enabled - options['D'] = new_resource.day if new_resource.day - options['M'] = new_resource.months unless new_resource.months.nil? - - run_schtasks 'CREATE', options - cwd(new_resource.cwd) if new_resource.cwd - end - end -end - -action :run do - if current_resource.exists - if current_resource.status == :running - Chef::Log.info "#{new_resource} task is currently running, skipping run" - else - converge_by("running scheduled task #{new_resource.task_name}") do - run_schtasks 'RUN' - new_resource.updated_by_last_action true - end - end - else - Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do" - end -end - -action :change do - if current_resource.exists - converge_by("changing scheduled task #{new_resource.task_name}") do - validate_user_and_password - validate_interactive_setting - - options = {} - options['TR'] = new_resource.command if new_resource.command - options['RU'] = new_resource.user if new_resource.user - options['RP'] = new_resource.password if new_resource.password - options['SD'] = new_resource.start_day unless new_resource.start_day.nil? - options['ST'] = new_resource.start_time unless new_resource.start_time.nil? - options['IT'] = '' if new_resource.interactive_enabled - - run_schtasks 'CHANGE', options - cwd(new_resource.cwd) if new_resource.cwd != current_resource.cwd - end - else - Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do" - end -end - -action :delete do - if current_resource.exists - converge_by("deleting scheduled task #{new_resource.task_name}") do - # always need to force deletion - run_schtasks 'DELETE', 'F' => '' - end - else - Chef::Log.debug "#{new_resource} task doesn't exists - nothing to do" - end -end - -action :end do - if current_resource.exists - if current_resource.status != :running - Chef::Log.debug "#{new_resource} is not running - nothing to do" - else - converge_by("stopping scheduled task #{new_resource.task_name}") do - run_schtasks 'END' - end - end - else - Chef::Log.fatal "#{new_resource} task doesn't exist - nothing to do" - raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot end" - end -end - -action :enable do - if current_resource.exists - if current_resource.enabled - Chef::Log.debug "#{new_resource} already enabled - nothing to do" - else - converge_by("enabling scheduled task #{new_resource.task_name}") do - run_schtasks 'CHANGE', 'ENABLE' => '' - end - end - else - Chef::Log.fatal "#{new_resource} task doesn't exist - nothing to do" - raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable" - end -end - -action :disable do - if current_resource.exists - if current_resource.enabled - converge_by("disabling scheduled task #{new_resource.task_name}") do - run_schtasks 'CHANGE', 'DISABLE' => '' - end - else - Chef::Log.debug "#{new_resource} already disabled - nothing to do" - end - else - Chef::Log.debug "#{new_resource} task doesn't exist - nothing to do" - end -end - -action_class.class_eval do - # rubocop:disable Style/StringLiteralsInInterpolation - def run_schtasks(task_action, options = {}) - cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" " - options.keys.each do |option| - cmd += "/#{option} " - cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == '' - end - Chef::Log.debug('running: ') - Chef::Log.debug(" #{cmd}") - shell_out!(cmd, returns: [0]) - end - # rubocop:enable Style/StringLiteralsInInterpolation - - def task_need_update? - # gsub needed as schtasks converts single quotes to double quotes on creation - current_resource.command != new_resource.command.tr("'", '"') || - current_resource.user != new_resource.user - end - - def cwd(folder) - Chef::Log.debug 'looking for existing tasks' - - # we use shell_out here instead of shell_out! because a failure implies that the task does not exist - xml_cmd = shell_out("schtasks /Query /TN \"#{new_resource.task_name}\" /XML") - - return if xml_cmd.exitstatus != 0 - - doc = REXML::Document.new(xml_cmd.stdout) - - Chef::Log.debug 'Removing former CWD if any' - doc.root.elements.delete('Actions/Exec/WorkingDirectory') - - unless folder.nil? - Chef::Log.debug 'Setting CWD as #folder' - cwd_element = REXML::Element.new('WorkingDirectory') - cwd_element.add_text(folder) - exec_element = doc.root.elements['Actions/Exec'] - exec_element.add_element(cwd_element) - end - - temp_task_file = ::File.join(ENV['TEMP'], 'windows_task.xml') - begin - ::File.open(temp_task_file, 'w:UTF-16LE') do |f| - doc.write(f) - end - - options = {} - options['RU'] = new_resource.user if new_resource.user - options['RP'] = new_resource.password if new_resource.password - options['IT'] = '' if new_resource.interactive_enabled - options['XML'] = temp_task_file - - run_schtasks('DELETE', 'F' => '') - run_schtasks('CREATE', options) - ensure - ::File.delete(temp_task_file) - end - end - - SYSTEM_USERS = ['NT AUTHORITY\SYSTEM', 'SYSTEM', 'NT AUTHORITY\LOCALSERVICE', 'NT AUTHORITY\NETWORKSERVICE'].freeze - - def validate_user_and_password - return unless new_resource.user && use_password? - return unless new_resource.password.nil? - Chef::Log.fatal "#{new_resource.task_name}: Can't specify a non-system user without a password!" - end - - def validate_interactive_setting - return unless new_resource.interactive_enabled && new_resource.password.nil? - Chef::Log.fatal "#{new_resource} did not provide a password when attempting to set interactive/non-interactive." - end - - def validate_create_day - return unless new_resource.day - unless [:weekly, :monthly].include?(new_resource.frequency) - raise 'day attribute is only valid for tasks that run weekly or monthly' - end - return unless new_resource.day.is_a?(String) && new_resource.day.to_i.to_s != new_resource.day - days = new_resource.day.split(',') - days.each do |day| - unless ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', '*'].include?(day.strip.downcase) - raise 'day attribute invalid. Only valid values are: MON, TUE, WED, THU, FRI, SAT, SUN and *. Multiple values must be separated by a comma.' - end - end - end - - def validate_create_months - return unless new_resource.months - unless [:monthly].include?(new_resource.frequency) - raise 'months attribute is only valid for tasks that run monthly' - end - return unless new_resource.months.is_a? String - months = new_resource.months.split(',') - months.each do |month| - unless ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC', '*'].include?(month.strip.upcase) - raise 'months attribute invalid. Only valid values are: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC and *. Multiple values must be separated by a comma.' - end - end - end - - def validate_idle_time - return unless new_resource.frequency == :on_idle - return if new_resource.idle_time.to_i > 0 && new_resource.idle_time.to_i <= 999 - raise "idle_time value #{new_resource.idle_time} is invalid. Valid values for :on_idle frequency are 1 - 999." - end - - def validate_create_frequency_modifier - # Currently is handled in create action 'frequency_modifier_allowed' line. Does not allow for frequency_modifier for once,onstart,onlogon,onidle - # Note that 'OnEvent' is not a supported frequency. - return if new_resource.frequency.nil? || new_resource.frequency_modifier.nil? - case new_resource.frequency - when :minute - unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 1439 - raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :minute frequency are 1 - 1439." - end - when :hourly - unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 23 - raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :hourly frequency are 1 - 23." - end - when :daily - unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 365 - raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :daily frequency are 1 - 365." - end - when :weekly - unless new_resource.frequency_modifier.to_i > 0 && new_resource.frequency_modifier.to_i <= 52 - raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :weekly frequency are 1 - 52." - end - when :monthly - unless ('1'..'12').to_a.push('FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY').include?(new_resource.frequency_modifier.to_s.upcase) - raise "frequency_modifier value #{new_resource.frequency_modifier} is invalid. Valid values for :monthly frequency are 1 - 12, 'FIRST', 'SECOND', 'THIRD', 'FOURTH', 'LAST', 'LASTDAY'." - end - end - end - - def use_password? - @use_password ||= !SYSTEM_USERS.include?(new_resource.user.upcase) - end - - def schedule - case new_resource.frequency - when :on_logon - 'ONLOGON' - when :on_idle - 'ONIDLE' - else - new_resource.frequency - end - end - - def frequency_modifier_allowed - case new_resource.frequency - when :minute, :hourly, :daily, :weekly - true - when :monthly - new_resource.months.nil? || %w(FIRST SECOND THIRD FOURTH LAST LASTDAY).include?(new_resource.frequency_modifier) - else - false - end - end -end diff --git a/cookbooks/windows/resources/user_privilege.rb b/cookbooks/windows/resources/user_privilege.rb new file mode 100644 index 00000000..2264ded4 --- /dev/null +++ b/cookbooks/windows/resources/user_privilege.rb @@ -0,0 +1,40 @@ +# +# Author:: Jared Kauppila () +# Cookbook:: windows +# Resource:: user_privilege +# + +property :principal, String, name_property: true +property :privilege, [Array, String], required: true, coerce: proc { |v| [*v].sort } + +action :add do + ([*new_resource.privilege] - [*current_resource.privilege]).each do |user_right| + converge_by("adding user privilege #{user_right}") do + Chef::ReservedNames::Win32::Security.add_account_right(new_resource.principal, user_right) + end + end +end + +action :remove do + if Gem::Version.new(Chef::VERSION) < Gem::Version.new('14.4.10') + Chef::Log.warn('Chef 14.4.10 is required to use windows_privilege remove action') + else + curr_res_privilege = current_resource.privilege + new_res_privilege = new_resource.privilege + missing_res_privileges = (new_res_privilege - curr_res_privilege) + + if missing_res_privileges + Chef::Log.info("Privilege: #{missing_res_privileges.join(', ')} not present. Unable to delete") + end + + (new_res_privilege - missing_res_privileges).each do |user_right| + converge_by("removing user privilege #{user_right}") do + Chef::ReservedNames::Win32::Security.remove_account_right(new_resource.principal, user_right) + end + end + end +end + +load_current_value do |desired| + privilege Chef::ReservedNames::Win32::Security.get_account_right(desired.principal) +end diff --git a/cookbooks/windows/resources/zipfile.rb b/cookbooks/windows/resources/zipfile.rb index dc685331..424717b7 100644 --- a/cookbooks/windows/resources/zipfile.rb +++ b/cookbooks/windows/resources/zipfile.rb @@ -1,11 +1,12 @@ # # Author:: Doug MacEachern () # Author:: Seth Chisamore () +# Author:: Wade Peacock () # Cookbook:: windows # Resource:: zipfile # # Copyright:: 2010-2017, VMware, Inc. -# Copyright:: 2011-2017, Chef Software, Inc. +# Copyright:: 2011-2018, Chef Software, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -20,20 +21,19 @@ # limitations under the License. # +require 'chef/util/path_helper' + property :path, String, name_property: true property :source, String property :overwrite, [true, false], default: false property :checksum, String -include Windows::Helper -require 'find' - action :unzip do ensure_rubyzip_gem_installed Chef::Log.debug("unzip #{new_resource.source} => #{new_resource.path} (overwrite=#{new_resource.overwrite})") cache_file_path = if new_resource.source =~ %r{^(file|ftp|http|https):\/\/} # http://rubular.com/r/DGoIWjLfGI - uri = as_uri(source) + uri = as_uri(new_resource.source) local_cache_path = "#{Chef::Config[:file_cache_path]}/#{::File.basename(::URI.unescape(uri.path))}" Chef::Log.debug("Caching a copy of file #{new_resource.source} at #{cache_file_path}") @@ -48,7 +48,7 @@ new_resource.source end - cache_file_path = win_friendly_path(cache_file_path) + cache_file_path = Chef::Util::PathHelper.cleanpath(cache_file_path) converge_by("unzip #{new_resource.source}") do ruby_block 'Unzipping' do @@ -110,13 +110,15 @@ end end -action_class.class_eval do +action_class do + include Windows::Helper + require 'find' + def ensure_rubyzip_gem_installed require 'zip' rescue LoadError Chef::Log.info("Missing gem 'rubyzip'...installing now.") chef_gem 'rubyzip' do - version node['windows']['rubyzipversion'] action :install compile_time true end diff --git a/cookbooks/windows/spec/spec_helper.rb b/cookbooks/windows/spec/spec_helper.rb index 773d5579..6af50dd4 100644 --- a/cookbooks/windows/spec/spec_helper.rb +++ b/cookbooks/windows/spec/spec_helper.rb @@ -1,5 +1,6 @@ require 'chefspec' require 'chefspec/berkshelf' +require 'win32-certstore' RSpec.configure do |config| config.color = true # Use color in STDOUT diff --git a/cookbooks/windows/tasks/maintainers.rb b/cookbooks/windows/tasks/maintainers.rb deleted file mode 100644 index aa8e2540..00000000 --- a/cookbooks/windows/tasks/maintainers.rb +++ /dev/null @@ -1,76 +0,0 @@ -# -# Copyright:: 2015-2017, Chef Software, Inc. -# License:: Apache License, Version 2.0 -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -require 'rake' - -SOURCE = File.join(File.dirname(__FILE__), '..', 'MAINTAINERS.toml') -TARGET = File.join(File.dirname(__FILE__), '..', 'MAINTAINERS.md') - -begin - require 'tomlrb' - task default: 'maintainers:generate' - - namespace :maintainers do - desc 'Generate MarkDown version of MAINTAINERS file' - task :generate do - @toml = Tomlrb.load_file SOURCE - out = "\n\n" - - out << preamble - out << project_lieutenant - out << all_maintainers - - File.open(TARGET, 'w') do |fn| - fn.write out - end - end - end - -rescue LoadError - STDERR.puts "\n*** TomlRb not available. Skipping the Maintainers Rake task\n\n" -end - -private - -def preamble - <<-EOL -# #{@toml['Preamble']['title']} -#{@toml['Preamble']['text']} -EOL -end - -def project_lieutenant - <<-EOL -# #{@toml['Org']['Components']['Core']['title']} -#{github_link(@toml['Org']['Components']['Core']['lieutenant'])} - -EOL -end - -def all_maintainers - text = "# Maintainers\n" - @toml['Org']['Components']['Core']['maintainers'].each do |m| - text << "#{github_link(m)}\n" - end - text -end - -def github_link(person) - name = @toml['people'][person]['name'] - github = @toml['people'][person]['github'] - "* [#{name}](https://github.com/#{github})" -end diff --git a/cookbooks/windows/test/cookbooks/minimal/recipes/http_acl.rb b/cookbooks/windows/test/cookbooks/minimal/recipes/http_acl.rb deleted file mode 100644 index 5b873a5b..00000000 --- a/cookbooks/windows/test/cookbooks/minimal/recipes/http_acl.rb +++ /dev/null @@ -1,17 +0,0 @@ -windows_http_acl 'http://+:50051/' do - user 'pc\\fred' -end - -windows_http_acl 'http://+:5986/' do - user 'NT SERVICE\Winrm' -end - -# Grant access to users "NT SERVICE\WinRM" and "NT SERVICE\Wecsvc" via sddl -windows_http_acl 'http://+:5985/' do - sddl 'D:(A;;GX;;;S-1-5-80-569256582-2953403351-2909559716-1301513147-412116970)' \ - + '(A;;GX;;;S-1-5-80-4059739203-877974739-1245631912-527174227-2996563517)' -end - -windows_http_acl 'http://+:50051/' do - action :delete -end diff --git a/cookbooks/windows/test/cookbooks/test/files/default/Asimov.otf b/cookbooks/windows/test/cookbooks/test/files/Asimov.otf old mode 100755 new mode 100644 similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/Asimov.otf rename to cookbooks/windows/test/cookbooks/test/files/Asimov.otf diff --git a/cookbooks/windows/test/cookbooks/test/files/default/CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/CodeNewRoman.otf similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/CodeNewRoman.otf rename to cookbooks/windows/test/cookbooks/test/files/CodeNewRoman.otf diff --git a/cookbooks/windows/test/cookbooks/test/files/GlobalSignRootCA.pem b/cookbooks/windows/test/cookbooks/test/files/GlobalSignRootCA.pem new file mode 100644 index 00000000..58537e9a --- /dev/null +++ b/cookbooks/windows/test/cookbooks/test/files/GlobalSignRootCA.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG +A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv +b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw +MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i +YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT +aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ +jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp +xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp +1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG +snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ +U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 +9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B +AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz +yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE +38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP +AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad +DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME +HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/cookbooks/windows/test/cookbooks/test/files/default/base64-cert2.cer b/cookbooks/windows/test/cookbooks/test/files/base64-cert2.cer similarity index 98% rename from cookbooks/windows/test/cookbooks/test/files/default/base64-cert2.cer rename to cookbooks/windows/test/cookbooks/test/files/base64-cert2.cer index 42e8d1ee..44d4e392 100644 --- a/cookbooks/windows/test/cookbooks/test/files/default/base64-cert2.cer +++ b/cookbooks/windows/test/cookbooks/test/files/base64-cert2.cer @@ -1,24 +1,24 @@ ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh +MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE +YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 +MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo +ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg +MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN +ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA +PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w +wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi +EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY +avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ +YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE +sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h +/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 +IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD +ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy +OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P +TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ +HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER +dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf +ReYNnyicsbkqWletNw+vHX/bvZ8= +-----END CERTIFICATE----- diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!31272!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!31272!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!31945!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!31945!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!32892!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!32892!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!34116!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!34116!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!35617!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!35617!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/.!62030!CodeNewRoman.otf b/cookbooks/windows/test/cookbooks/test/files/default/.!62030!CodeNewRoman.otf deleted file mode 100644 index e69de29b..00000000 diff --git a/cookbooks/windows/test/cookbooks/test/files/default/der-cert1.cer b/cookbooks/windows/test/cookbooks/test/files/der-cert1.cer similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/der-cert1.cer rename to cookbooks/windows/test/cookbooks/test/files/der-cert1.cer diff --git a/cookbooks/windows/test/cookbooks/test/files/default/test-cert.cer b/cookbooks/windows/test/cookbooks/test/files/test-cert.cer similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/test-cert.cer rename to cookbooks/windows/test/cookbooks/test/files/test-cert.cer diff --git a/cookbooks/windows/test/cookbooks/test/files/default/test-cert.pfx b/cookbooks/windows/test/cookbooks/test/files/test-cert.pfx similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/test-cert.pfx rename to cookbooks/windows/test/cookbooks/test/files/test-cert.pfx diff --git a/cookbooks/windows/test/cookbooks/test/files/default/test-cert.pvk b/cookbooks/windows/test/cookbooks/test/files/test-cert.pvk similarity index 100% rename from cookbooks/windows/test/cookbooks/test/files/default/test-cert.pvk rename to cookbooks/windows/test/cookbooks/test/files/test-cert.pvk diff --git a/cookbooks/windows/test/cookbooks/test/files/test-pfx-certificate.pfx b/cookbooks/windows/test/cookbooks/test/files/test-pfx-certificate.pfx new file mode 100644 index 00000000..51c78458 Binary files /dev/null and b/cookbooks/windows/test/cookbooks/test/files/test-pfx-certificate.pfx differ diff --git a/cookbooks/windows/test/cookbooks/test/files/test_cert.crt b/cookbooks/windows/test/cookbooks/test/files/test_cert.crt new file mode 100644 index 00000000..1e6967fe Binary files /dev/null and b/cookbooks/windows/test/cookbooks/test/files/test_cert.crt differ diff --git a/cookbooks/windows/test/cookbooks/test/files/test_der.der b/cookbooks/windows/test/cookbooks/test/files/test_der.der new file mode 100644 index 00000000..1e6967fe Binary files /dev/null and b/cookbooks/windows/test/cookbooks/test/files/test_der.der differ diff --git a/cookbooks/windows/test/cookbooks/test/files/test_p7b.p7b b/cookbooks/windows/test/cookbooks/test/files/test_p7b.p7b new file mode 100644 index 00000000..c380514b --- /dev/null +++ b/cookbooks/windows/test/cookbooks/test/files/test_p7b.p7b @@ -0,0 +1,22 @@ +-----BEGIN PKCS7----- +MIIDpgYJKoZIhvcNAQcCoIIDlzCCA5MCAQExADALBgkqhkiG9w0BBwGgggN5MIID +dTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkGA1UE +BhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jvb3Qg +Q0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAwMDBa +Fw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxT +aWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxTaWdu +IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZjc6j +40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavpxy0S +y6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp1Wrj +sok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdGsnUO +hugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJU26Q +zns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N89iFo +7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0BAQUF +AAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOzyj1h +TdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE38Nf +lNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymPAbEV +tQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUadDKqC +5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbMEHMUf +pIBvFSDJ3gyICh3WZlXi/EjJKSZp4KEAMQA= +-----END PKCS7----- diff --git a/cookbooks/windows/test/cookbooks/test/recipes/autorun.rb b/cookbooks/windows/test/cookbooks/test/recipes/autorun.rb index b7433791..dd252e5e 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/autorun.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/autorun.rb @@ -1,11 +1,18 @@ windows_auto_run 'add notepad' do - name 'notepad' - program 'C:/windows/system32/notepad.exe' - action :create + program_name 'notepad' + path 'C:\windows\system32\notepad.exe' + action :create end windows_auto_run 'remove notepad' do - name 'notepad' - program 'C:/windows/system32/notepad.exe' - action :remove + program_name 'notepad' + root :machine + action :remove +end + +windows_auto_run 'add wordpad' do + program_name 'wordpad' + program 'C:/Windows/System32/write.exe' # the legacy name for the path property + root :user + action :create end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/certificate.rb b/cookbooks/windows/test/cookbooks/test/recipes/certificate.rb index d59f993c..6202c2e8 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/certificate.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/certificate.rb @@ -1,35 +1,195 @@ # We don't support reading the source from the cookbook yet. So manually point us to # the correct place in the chef file cache. -windows_certificate "#{Chef::Config[:file_cache_path]}/cookbooks/test/files/default/der-cert1.cer" do +directory 'C:/certs' + +cookbook_file 'C:/certs/GlobalSignRootCA.pem' do + source 'GlobalSignRootCA.pem' +end + +cookbook_file 'C:/certs/test_der.der' do + source 'test_der.der' +end + +cookbook_file 'C:/certs/der-cert1.cer' do + source 'der-cert1.cer' +end + +cookbook_file 'C:/certs/base64-cert2.cer' do + source 'base64-cert2.cer' +end + +cookbook_file 'C:/certs/test_cert.crt' do + source 'test_cert.crt' +end + +cookbook_file 'C:/certs/test-cert.pfx' do + source 'test-cert.pfx' +end + +cookbook_file 'C:/certs/test-pfx-certificate.pfx' do + source 'test-pfx-certificate.pfx' +end + +cookbook_file 'C:/certs/test_p7b.p7b' do + source 'test_p7b.p7b' +end + +# Add (.PEM) format certificate in MY certificate store +windows_certificate 'C:/certs/GlobalSignRootCA.pem' do + action :create +end + +# delete certificate by thumbprint from MY certificate store +windows_certificate 'b1bc968bd4f49d622aa89a81f2150152a41d829c' do + action :delete +end + +# Add (.DER) format certificate in MY certificate store +windows_certificate 'add DER format certificate' do + source 'C:/certs/test_der.der' + action :create +end + +# delete certificate by thumbprint from MY certificate store +windows_certificate 'delete certificate by thumbprint with space' do + source 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' + action :delete +end + +# Add (.CER) DER encoded binary X.509 certificate in MY certificate store +windows_certificate 'C:/certs/der-cert1.cer' do action :create end -windows_certificate "#{Chef::Config[:file_cache_path]}/cookbooks/test/files/default/base64-cert2.cer" do +# Add (.CER) Base64 encoded X.509 certificate in ROOT certificate store +windows_certificate 'C:/certs/base64-cert2.cer' do action :create + store_name 'CA' end -windows_certificate '2796bae63f1801e277261ba0d77770028f20eee4' do +# Add (.CRT) format certificate in MY certificate store +windows_certificate 'add .crt certificate' do + action :create + source 'C:/certs/test_cert.crt' +end + +# delete certificate by thumbprint with colon from MY certificate store +windows_certificate 'delete certificate by thumbprint with colon' do + source 'b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c' action :delete end -# Generate using: -# makecert -r -n "CN=ChefDummyCertForTest" -pe -ss My -sv test-cert.pvk test-cert.cer -# pvk2pfx -pvk test-cert.pvk -spc test-cert.cer -pfx test-cert.pfx -po chef123 -windows_certificate "#{Chef::Config[:file_cache_path]}/cookbooks/test/files/default/test-cert.pfx" do +# Add (.PFX) format certificate with password in CA certificate store +windows_certificate 'C:/certs/test-cert.pfx' do action :create pfx_password 'chef123' store_name 'CA' end -windows_certificate_binding 'ChefDummyCertForTest' do +# Add (.PFX) format certificate with password including special character( e.g. @, $) +windows_certificate 'C:/certs/test-pfx-certificate.pfx' do + action :create + pfx_password 'chef$123' + store_name 'MY' +end + +# delete certificate by thumbprint from CA certificate store +windows_certificate '50 81 f6 67 f1 ef 00 5d 0e c3 9f a3 e3 0a a7 1b 4f d8 4e b6' do + action :delete store_name 'CA' - port 443 end -windows_certificate_binding '444-appid' do - cert_name 'ChefDummyCertForTest' +# (.P7B) format certificate in MY certificate store +windows_certificate 'add .p7b certificate' do + action :create + source 'C:/certs/test_p7b.p7b' + store_name 'MY' +end + +# delete certificate by thumbprint from MY certificate store +windows_certificate 'delete certificate by thumbprint with space' do + source 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' + action :delete +end + +windows_certificate 'C:/certs/test-cert.pfx' do + action :create + pfx_password 'chef123' store_name 'CA' - port 444 - app_id '{00000000-0000-0000-0000-000000000000}' +end + +# Add (.PEM) format certificate in MY certificate store +windows_certificate 'C:/certs/GlobalSignRootCA.pem' do + action :create +end + +# Validate certificate by thumbprint +windows_certificate 'b1bc968bd4f49d622aa89a81f2150152a41d829c' do + action :verify +end + +# Validate certificate by thumbprint with space +windows_certificate 'b1bc968bd4f49d622aa89a81f2150152a41d829c' do + action :verify +end + +# Validate certificate by thumbprint with colon +windows_certificate 'validate certificate' do + action :verify + source 'b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c' +end + +# Validate certificate by invalid thumbprint +windows_certificate 'validate certificate' do + action :verify + source 'b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c:1' +end + +# Fetch certificate and display on console in PEM format +windows_certificate 'b1bc968bd4f49d622aa89a81f2150152a41d829c' do + action :fetch +end + +# Export certificate in PEM +windows_certificate 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' do + action :fetch + cert_path 'C:\certs\demo.pem' +end + +# Export certificate in DER +windows_certificate 'b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c' do + action :fetch + cert_path 'C:\certs\demo.der' +end + +# Export certificate in CER +windows_certificate 'Export certificate in cer' do + action :fetch + source 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' + cert_path 'C:\certs\demo.cer' +end + +# Export certificate in CRT +windows_certificate 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' do + action :fetch + cert_path 'C:\certs\demo.crt' +end + +# Export certificate in PFX with no keys +windows_certificate 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' do + action :fetch + cert_path 'C:\certs\demo.pfx' +end + +# Export certificate in P7B +windows_certificate 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' do + action :fetch + cert_path 'C:\certs\demo.p7b' +end + +# Export certificate in invalid format return error +windows_certificate 'b1 bc 96 8b d4 f4 9d 62 2a a8 9a 81 f2 15 01 52 a4 1d 82 9c' do + action :fetch + cert_path 'C:\certs\demo.mp3' end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/everything.rb b/cookbooks/windows/test/cookbooks/test/recipes/everything.rb new file mode 100644 index 00000000..9f8d5dd5 --- /dev/null +++ b/cookbooks/windows/test/cookbooks/test/recipes/everything.rb @@ -0,0 +1,10 @@ +include_recipe '::autorun' +include_recipe '::certificate' +include_recipe '::feature' +include_recipe '::font' +include_recipe '::http_acl' +include_recipe '::pagefile' +include_recipe '::share' +include_recipe '::shortcut' +include_recipe '::user_privilege' +include_recipe '::zipfile' diff --git a/cookbooks/windows/test/cookbooks/test/recipes/feature.rb b/cookbooks/windows/test/cookbooks/test/recipes/feature.rb index 277fd633..20497ddc 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/feature.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/feature.rb @@ -1,29 +1,56 @@ -windows_feature 'TelnetClient' do - action :install +# pass feature name as a string +windows_feature 'install SNMP' do + feature_name 'SNMP' end -windows_feature 'TFTP-Client' do - action :install - install_method :windows_feature_powershell +# pass feature name as an array +windows_feature 'remove SNMP' do + feature_name ['SNMP'] + action :remove +end + +# pass feature name as a lowercase value on 2012+ +windows_feature 'Install SNMP again' do + feature_name node['platform_version'].to_f < 6.2 ? 'SNMP' : 'snmp' # lowercase on purpose end +# array of feature names +windows_feature_dism %w(TelnetClient TFTP) + # This is for appveyor, which already seems to have FTP installed -# which causes a short circuit of the "all" behavior and breaks the test. +# which causes a short circuit of the "all" behavior and-breaks the test. # TODO: Make :windows_feature_powershell look at all the sub-features and validate # that they are installed when "all is specified" windows_feature 'Remove FTP for Appveyor' do feature_name 'Web-Ftp-Server' action :remove install_method :windows_feature_powershell + not_if { node['platform_version'].to_f < 6.2 } end -windows_feature 'Web-Ftp-Server' do - action :install - all true +# lowercase install for windows 2012+, but proper case for 2k8r2 because...it's old +windows_feature 'install Web-Ftp-Server' do + feature_name 'Web-Ftp-Server' + feature_name node['platform_version'].to_f < 6.2 ? 'Web-Ftp-Server' : 'web-ftp-server' + all true install_method :windows_feature_powershell end -windows_feature ['Web-Asp-Net45', 'Web-Net-Ext45'] do - action :install - install_method :windows_feature_powershell +# These aren't available on ancient windows so don't test there +unless node['platform_version'].to_f < 6.2 + # commas separated list of features (not an array) + windows_feature_powershell 'Web-Asp-Net45, Web-Net-Ext45' + + windows_feature ['NPAS'] do + management_tools true + install_method :windows_feature_powershell + end +end + +windows_feature 'NetFx3' do + action :delete +end + +windows_feature 'NetFx3' do + all true end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/font.rb b/cookbooks/windows/test/cookbooks/test/recipes/font.rb index 3bfe8059..c9ffab8f 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/font.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/font.rb @@ -7,3 +7,13 @@ windows_font 'Asimov.otf' do source 'C:/Asimov.otf' end + +windows_font 'Local Asimov with forward slashes' do + font_name 'Asimov.otf' + source 'C:\Asimov.otf' +end + +windows_font 'Local Asimov with double forward slashes' do + font_name 'Asimov.otf' + source 'C:\\Asimov.otf' +end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/http_acl.rb b/cookbooks/windows/test/cookbooks/test/recipes/http_acl.rb index 8ae432dc..70ade5a4 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/http_acl.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/http_acl.rb @@ -5,3 +5,17 @@ windows_http_acl 'http://google.com:80/' do user "#{ENV['COMPUTERNAME']}\\space user" end + +windows_http_acl 'http://+:50051/' do + user "#{ENV['COMPUTERNAME']}\\space user" +end + +# Grant access to users "NT SERVICE\WinRM" and "NT SERVICE\Wecsvc" via sddl +windows_http_acl 'http://+:5985/' do + sddl 'D:(A;;GX;;;S-1-5-80-569256582-2953403351-2909559716-1301513147-412116970)' \ + + '(A;;GX;;;S-1-5-80-4059739203-877974739-1245631912-527174227-2996563517)' +end + +windows_http_acl 'http://+:50051/' do + action :delete +end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/pagefile.rb b/cookbooks/windows/test/cookbooks/test/recipes/pagefile.rb index 62ceedd5..d5f1f9e3 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/pagefile.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/pagefile.rb @@ -1,10 +1,14 @@ -windows_pagefile 'create the pagefile' do - name 'C:\pagefile.sys' - initial_size 100 - maximum_size 200 +windows_pagefile 'have the system manage pagefiles' do + automatic_managed true end windows_pagefile 'delete the pagefile' do - name 'C:\pagefile.sys' + path 'C:\pagefile.sys' action :delete end + +windows_pagefile 'create the pagefile' do + path 'C:/pagefile.sys' + initial_size 100 + maximum_size 200 +end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/path.rb b/cookbooks/windows/test/cookbooks/test/recipes/path.rb index 5adb4d41..74ce851b 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/path.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/path.rb @@ -21,14 +21,3 @@ $env:path -split ';' | out-file c:\\paths.txt EOH end - -windows_task 'testpath' do - action [:create, :run] - command 'powershell.exe -command $env:path > c:\\external_paths.txt' -end - -ruby_block 'wait for task' do - block do - sleep 2 - end -end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/share.rb b/cookbooks/windows/test/cookbooks/test/recipes/share.rb index 2022ccd4..29d599d7 100644 --- a/cookbooks/windows/test/cookbooks/test/recipes/share.rb +++ b/cookbooks/windows/test/cookbooks/test/recipes/share.rb @@ -1,42 +1,56 @@ +user 'bob' +user 'jane' # create directory for sharing with full access for all users directory 'c:/test_share' do rights :full_control, 'BUILTIN\\Users' end +directory 'c:/test_share2' do + rights :full_control, 'BUILTIN\\Users' +end + windows_share 'read_only' do - path 'C:/test_share' - description 'a test share' - read_users ['BUILTIN\\Users'] + path 'C:/test_share' + description 'a test share' + read_users ['BUILTIN\\Users', 'bob', 'jane'] +end + +windows_share 'create read_only again (should not update)' do + share_name 'read_only' + path 'C:/test_share' + description 'a test share' + read_users ['BUILTIN\\Users', 'bob', 'jane'] end windows_share 'change' do - path 'C:/test_share' - change_users ['BUILTIN\\Users'] + path 'C:/test_share' + change_users ['BUILTIN\\Users'] end windows_share 'full' do - path 'C:/test_share' - full_users ['BUILTIN\\Users'] + path 'C:/test_share' + full_users ['BUILTIN\\Users', 'jane'] end # create then delete the share windows_share 'create no_share' do - share_name 'no_share' - path 'C:/test_share' + share_name 'no share' + path 'C:/test_share' end -windows_share 'no_share' do - action :delete +windows_share 'no share' do + action :delete end # create share then change path windows_share 'create changed_dir' do - share_name 'changed_dir' - path 'C:/' + share_name 'changed_dir' + path 'C:/test_share' end -windows_share 'alter changed_dir' do - share_name 'changed_dir' - path 'C:/test_share' -end +# windows_share 'alter changed_dir' do +# share_name 'changed_dir' +# path 'C:/test_share2' +# description 'this has been changed' +# end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/tasks.rb b/cookbooks/windows/test/cookbooks/test/recipes/tasks.rb deleted file mode 100644 index 187980bd..00000000 --- a/cookbooks/windows/test/cookbooks/test/recipes/tasks.rb +++ /dev/null @@ -1,83 +0,0 @@ -windows_task 'task_from_name' do - command 'dir' -end - -windows_task 'disable task_from_name' do - task_name 'task_from_name' - action :disable -end - -windows_task 'create chef\nested task' do - name 'chef\nested task' - action :create - command 'dir' -end - -windows_task 'disable chef\nested task' do - name 'chef\nested task' - command 'dir /s' - action :change -end - -windows_task 'create long running task loop' do - task_name '\chef\longtask' - action :create - command 'powershell.exe -command while ($true) {start-sleep -seconds 5}' -end - -windows_task 'run long running task' do - task_name '\chef\longtask' - action :run -end - -windows_task 'stop long running task' do - task_name '\chef\longtask' - action :end -end - -windows_task 'create task to change via create' do - task_name 'chef\change_me' - command 'dir' -end - -windows_task 'change task change_me via create' do - task_name 'chef\change_me' - command 'dir /s' -end - -windows_task 'create task delete_me' do - name 'delete_me' - user 'NT AUTHORITY\SYSTEM' - run_level :highest - password 'ignored' - action :create - command 'dir' - notifies :create, 'file[c:/notifytest.txt]', :immediately -end - -windows_task 'delete task delete_me' do - name 'delete_me' - action :delete -end - -# not everyone tests in vagrant -if ENV['USERNAME'] == 'vagrant' || ENV['machine_user'] == 'vagrant' - windows_task 'task_for_system' do - command 'dir' - run_level :highest - user 'vagrant' - password 'vagrant' - cwd ENV['TEMP'] - end -end - -windows_task 'task_on_idle' do - frequency :on_idle - command 'dir' - idle_time 30 -end - -file 'c:/notifytest.txt' do - content 'blah' - action :nothing -end diff --git a/cookbooks/windows/test/cookbooks/test/recipes/user_privilege.rb b/cookbooks/windows/test/cookbooks/test/recipes/user_privilege.rb new file mode 100644 index 00000000..98532999 --- /dev/null +++ b/cookbooks/windows/test/cookbooks/test/recipes/user_privilege.rb @@ -0,0 +1,23 @@ +# Adding 5 Privileges +windows_user_privilege 'vagrant' do + privilege %w(SeIncreaseQuotaPrivilege SeServiceLogonRight SeTimeZonePrivilege SeCreateTokenPrivilege SeBackupPrivilege) + action :add +end + +# Removing 3 of them +windows_user_privilege 'vagrant' do + privilege %w(SeIncreaseQuotaPrivilege SeServiceLogonRight SeTimeZonePrivilege) + action :remove +end + +# Removing 1 from already removed +windows_user_privilege 'vagrant' do + privilege %w(SeIncreaseQuotaPrivilege) + action :remove +end + +# Removing few present & few already removed +windows_user_privilege 'vagrant' do + privilege %w(SeServiceLogonRight SeTimeZonePrivilege SeCreateTokenPrivilege SeBackupPrivilege) + action :remove +end diff --git a/cookbooks/windows/test/integration/autorun/autorun_spec.rb b/cookbooks/windows/test/integration/autorun/autorun_spec.rb new file mode 100644 index 00000000..ff413940 --- /dev/null +++ b/cookbooks/windows/test/integration/autorun/autorun_spec.rb @@ -0,0 +1,7 @@ +describe registry_key('HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run') do + its('wordpad') { should eq '"C:\\Windows\\System32\\write.exe"' } +end + +describe registry_key('HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run\notepad') do + it { should_not exist } +end diff --git a/cookbooks/windows/test/integration/certificate/pester/minimal.certificate.Tests.ps1 b/cookbooks/windows/test/integration/certificate/pester/minimal.certificate.Tests.ps1 index 1ef4624e..58a62b55 100644 --- a/cookbooks/windows/test/integration/certificate/pester/minimal.certificate.Tests.ps1 +++ b/cookbooks/windows/test/integration/certificate/pester/minimal.certificate.Tests.ps1 @@ -1,38 +1,44 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::certificate' { - context 'windows_certificate' { - - it "installs der-cert1" { - "Cert:\LocalMachine\My\47BEABC922EAE80E78783462A79F45C254FDE68B" | Should Exist - } - - it "installs and removes base64-cert2" { - "Cert:\LocalMachine\My\2796BAE63F1801E277261BA0D77770028F20EEE4" | Should Not Exist - } - - it "installs test-cert" { - "Cert:\LocalMachine\CA\5081F667F1EF005D0EC39FA3E30AA71B4FD84EB6" | Should Exist - } - - it "puts persists the private key for test-cert" { - $cert = Get-ChildItem "Cert:\LocalMachine\CA\5081F667F1EF005D0EC39FA3E30AA71B4FD84EB6" - $cert.PrivateKey.CspKeyContainerInfo.MachineKeyStore | Should Be True - $uniqueName = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName - "$Env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$uniqueName" | Should Exist - } - - it "binds test-cert to port 443" { - $binding = netsh http show sslcert ipport=0.0.0.0:443 - $binding[4] | Should Match ' : 0.0.0.0:443\s*$' - $binding[5] | Should Match ' : 5081f667f1ef005d0ec39fa3e30aa71b4fd84eb6$' - } - - it "binds test-cert to port 444 with custom app-id" { - $binding = netsh http show sslcert ipport=0.0.0.0:444 - $binding[4] | Should Match ' : 0.0.0.0:444\s*$' - $binding[5] | Should Match ' : 5081f667f1ef005d0ec39fa3e30aa71b4fd84eb6$' - $binding[6] | Should Match ' : {00000000-0000-0000-0000-000000000000}\s*$' - } - } -} +$global:progressPreference = 'SilentlyContinue' + +describe 'test::certificate' { + context 'windows_certificate' { + + it "installs der-cert1" { + "Cert:\LocalMachine\My\47BEABC922EAE80E78783462A79F45C254FDE68B" | Should Exist + } + + it "installs and removes base64-cert2" { + "Cert:\LocalMachine\My\2796BAE63F1801E277261BA0D77770028F20EEE4" | Should Not Exist + } + + it "installs test-cert" { + "Cert:\LocalMachine\CA\5081F667F1EF005D0EC39FA3E30AA71B4FD84EB6" | Should Exist + } + + it "puts persists the private key for test-cert" { + $cert = Get-ChildItem "Cert:\LocalMachine\CA\5081F667F1EF005D0EC39FA3E30AA71B4FD84EB6" + $cert.PrivateKey.CspKeyContainerInfo.MachineKeyStore | Should Be True + $uniqueName = $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + "$Env:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$uniqueName" | Should Exist + } + + it "binds test-cert to port 443" { + $binding = netsh http show sslcert ipport=0.0.0.0:443 + $binding[4] | Should Match ' : 0.0.0.0:443\s*$' + $binding[5] | Should Match ' : 5081f667f1ef005d0ec39fa3e30aa71b4fd84eb6$' + } + + it "binds test-cert to port 444 with custom app-id" { + $binding = netsh http show sslcert ipport=0.0.0.0:444 + $binding[4] | Should Match ' : 0.0.0.0:444\s*$' + $binding[5] | Should Match ' : 5081f667f1ef005d0ec39fa3e30aa71b4fd84eb6$' + $binding[6] | Should Match ' : {00000000-0000-0000-0000-000000000000}\s*$' + } + + it "binds test-cert to port 443 with host www.chef.io" { + $binding = netsh http show sslcert hostnameport=www.chef.io:443 + $binding[4] | Should Match ' : www.chef.io:443\s*$' + $binding[5] | Should Match ' : 5081f667f1ef005d0ec39fa3e30aa71b4fd84eb6$' + } + } +} diff --git a/cookbooks/windows/test/integration/feature/feature_spec.rb b/cookbooks/windows/test/integration/feature/feature_spec.rb new file mode 100644 index 00000000..c2bf8db4 --- /dev/null +++ b/cookbooks/windows/test/integration/feature/feature_spec.rb @@ -0,0 +1,19 @@ +describe windows_feature('SNMP Service') do + it { should be_installed } +end + +describe windows_feature('Web-Ftp-Server') do + it { should be_installed } +end + +describe windows_feature('Web-Asp-Net45') do + it { should be_installed } +end + +describe windows_feature('Web-Net-Ext45') do + it { should be_installed } +end + +describe windows_feature('NPAS') do + it { should be_installed } +end diff --git a/cookbooks/windows/test/integration/feature/pester/minimal.package.Tests.ps1 b/cookbooks/windows/test/integration/feature/pester/minimal.package.Tests.ps1 deleted file mode 100644 index a2ccadc1..00000000 --- a/cookbooks/windows/test/integration/feature/pester/minimal.package.Tests.ps1 +++ /dev/null @@ -1,22 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::feature' { - context 'minimal_feature' { - - it "feature TelnetClient installed using dism" { - Get-Command Telnet -ErrorAction SilentlyContinue | Should Not Be $Null - } - - it "feature TFTP Client installed using powershell" { - Get-Command tftp -ErrorAction SilentlyContinue | Should Not Be $Null - } - - it "feature Web-Ftp-Server and sub features installed using powershell" { - (Get-WindowsFeature Web-Ftp-* | ?{$_.InstallState -eq "Installed"}).count | Should Be 3 - } - - it "feature Web-Asp-Net45 and Web-Net-Ext45 installed using powershell" { - (Get-WindowsFeature Web-Asp-Net45,Web-Net-Ext45 | ?{$_.InstallState -eq "Installed"}).count | Should Be 2 - } - } -} diff --git a/cookbooks/windows/test/integration/font/font_spec.rb b/cookbooks/windows/test/integration/font/font_spec.rb new file mode 100644 index 00000000..6ced85e0 --- /dev/null +++ b/cookbooks/windows/test/integration/font/font_spec.rb @@ -0,0 +1,7 @@ +describe file('c:/windows/fonts/CodeNewRoman.otf') do + it { should exist } +end + +describe file('c:/windows/fonts/Asimov.otf') do + it { should exist } +end diff --git a/cookbooks/windows/test/integration/font/pester/minimal.font.Tests.ps1 b/cookbooks/windows/test/integration/font/pester/minimal.font.Tests.ps1 deleted file mode 100644 index 4e28beee..00000000 --- a/cookbooks/windows/test/integration/font/pester/minimal.font.Tests.ps1 +++ /dev/null @@ -1,13 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::font' { - context 'windows_font' { - - it "installs CodeNewRoman" { - "c:/windows/fonts/CodeNewRoman.otf" | Should Exist - } - it "installs Asimov" { - "c:/windows/fonts/Asimov.otf" | Should Exist - } - } -} diff --git a/cookbooks/windows/test/integration/http_acl/http_acl_spec.rb b/cookbooks/windows/test/integration/http_acl/http_acl_spec.rb new file mode 100644 index 00000000..276d9098 --- /dev/null +++ b/cookbooks/windows/test/integration/http_acl/http_acl_spec.rb @@ -0,0 +1,4 @@ +describe command('netsh http show urlacl url=http://google.com:80/') do + its('exit_status') { should eq 0 } + its('stdout') { should_not match /^space user/ } +end diff --git a/cookbooks/windows/test/integration/http_acl/pester/minimal.http_acl.Tests.ps1 b/cookbooks/windows/test/integration/http_acl/pester/minimal.http_acl.Tests.ps1 deleted file mode 100644 index 615e5739..00000000 --- a/cookbooks/windows/test/integration/http_acl/pester/minimal.http_acl.Tests.ps1 +++ /dev/null @@ -1,10 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::http_acl' { - context 'minimal_http_acl' { - - it "http_acl added for some user to access google.com" { - netsh http show urlacl url=http://google.com:80/ | Out-String | Should match "space user" - } - } -} diff --git a/cookbooks/windows/test/integration/pagefile/pagefile_spec.rb b/cookbooks/windows/test/integration/pagefile/pagefile_spec.rb new file mode 100644 index 00000000..76e77b82 --- /dev/null +++ b/cookbooks/windows/test/integration/pagefile/pagefile_spec.rb @@ -0,0 +1,8 @@ +describe file('c:/pagefile.sys') do + it { should exist } +end + +describe command('wmic pagefileset') do + its('exit_status') { should eq 0 } # if it was system managed it would be 1 + its('stderr') { should_not match /No Instance\(s\) Available/ } # not system managed +end diff --git a/cookbooks/windows/test/integration/path/path_spec.rb b/cookbooks/windows/test/integration/path/path_spec.rb new file mode 100644 index 00000000..7b36b930 --- /dev/null +++ b/cookbooks/windows/test/integration/path/path_spec.rb @@ -0,0 +1,6 @@ +describe file('c:/paths.txt') do + it { should exist } + its('content') { should match(/C:\\path_test_path/) } + its('content') { should match(/c:\\path_test_with_forward_slashes/) } + its('content') { should match(/C:\\path_test_another_path/) } +end diff --git a/cookbooks/windows/test/integration/path/pester/minimal.path.Tests.ps1 b/cookbooks/windows/test/integration/path/pester/minimal.path.Tests.ps1 deleted file mode 100644 index 01006123..00000000 --- a/cookbooks/windows/test/integration/path/pester/minimal.path.Tests.ps1 +++ /dev/null @@ -1,32 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::path' { - context 'windows_path' { - $SystemVariables = get-itemproperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment' - $UserVariables = get-itemproperty 'HKCU:\Environment' - - $Paths = ($SystemVariables.path -split ';') - - it "'C:\path_test_path' was added to the path" { - ($Paths -contains 'C:\path_test_path') | should be $true - } - - it "'c:\path_test_with_forward_slashes' was added to the path" { - ($Paths -contains 'C:\path_test_with_forward_slashes') | should be $true - } - - it "'C:\path_test_path' was added to the path" { - ($Paths -contains 'C:\path_test_path') | should be $true - } - - it 'Child processes and shellouts have an updated path' { - 'c:\paths.txt' | should contain 'C:\\path_test_another_path' - 'c:\paths.txt' | should contain 'C:\\path_test_path' - } - - it 'Updates the path for new external processes' { - 'c:\external_paths.txt' | should contain 'C:\\path_test_another_path' - 'c:\external_paths.txt' | should contain 'C:\\path_test_path' - } - } -} diff --git a/cookbooks/windows/test/integration/share/pester/minimal.share.Tests.ps1 b/cookbooks/windows/test/integration/share/pester/minimal.share.Tests.ps1 deleted file mode 100644 index 820af750..00000000 --- a/cookbooks/windows/test/integration/share/pester/minimal.share.Tests.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'minimal::share' { - context 'windows_share' { - it "creates share read_only" { - "\\localhost\read_only" | Should Exist - } - - it "sets description" { - $s = Get-WmiObject win32_share -filter 'Name like "read_only"' - $s.description | Should Match "a test share" - } - - it "permissions read_only to prevent write" { - $f = New-Item "\\localhost\read_only\bang" -Type File -ErrorAction SilentlyContinue - $f | Should BeNullOrEmpty - } - - it "creates share change" { - "\\localhost\change" | Should Exist - } - - $fileName = "\\localhost\change\change_file" - it "permissions change to allow create" { - $f = New-Item $fileName -Type File - $f.Name | Should Be "change_file" - } - - it "permissions change to allow delete" { - Remove-Item $fileName - } - - it "creates share full" { - "\\localhost\full" | Should Exist - } - - $fileName = "\\localhost\full\change_file" - it "permissions full to allow create" { - $f = New-Item $fileName -Type File - $f.Name | Should Be "change_file" - } - - it "permissions full to allow delete" { - Remove-Item $fileName - } - - it "removes share no_share" { - "\\localhost\no_share" | Should Not Exist - } - - it "can change the directory associated with a share" { - $s = Get-WmiObject win32_share -filter 'Name like "changed_dir"' - $s.path | Should Match "c:/test_share" - } - - } -} diff --git a/cookbooks/windows/test/integration/share/share_spec.rb b/cookbooks/windows/test/integration/share/share_spec.rb new file mode 100644 index 00000000..901aec6d --- /dev/null +++ b/cookbooks/windows/test/integration/share/share_spec.rb @@ -0,0 +1,8 @@ +describe command('net share') do + its('exit_status') { should eq 0 } + its('stdout') { should_not match /^no share/ } + its('stdout') { should match /^change\s*C:\/test_share*/ } + its('stdout') { should match /^changed_dir\s*C:\/test_share*/ } + its('stdout') { should match /^read_only\s*C:\/test_share*/ } + its('stdout') { should match /^full\s*C:\/test_share*/ } +end diff --git a/cookbooks/windows/test/integration/tasks/pester/minimal.tasks.Tests.ps1 b/cookbooks/windows/test/integration/tasks/pester/minimal.tasks.Tests.ps1 deleted file mode 100644 index 9aa8b865..00000000 --- a/cookbooks/windows/test/integration/tasks/pester/minimal.tasks.Tests.ps1 +++ /dev/null @@ -1,86 +0,0 @@ -$global:progressPreference = 'SilentlyContinue' - -describe 'test::tasks' { - context 'windows_task' { - [xml]$top_level_task = schtasks /query /tn 'task_from_name' /XML 2> $null - [xml]$second_level_task_no_leading_slash = schtasks /query /tn '\chef\nested task' /XML 2> $null - [xml]$second_level_task_leading_slash = schtasks /query /tn '\chef\longtask' /XML 2> $null - $second_level_task = schtasks /query /tn '\chef\longtask' /FO csv /v | convertfrom-csv 2> $null - [xml]$missing_task = schtasks /query /tn 'delete_me' /XML 2> $null - [xml]$task_changed_by_create = schtasks /query /tn '\chef\change_me' /XML 2> $null - [xml]$system_task = schtasks /query /tn 'task_for_system' /XML 2> $null - [xml]$task_on_idle = schtasks /query /tn 'task_on_idle' /XML 2> $null - - it "task 'task_from_name' was created" { - $top_level_task | Should Not BeNullOrEmpty - } - - it "task 'task_from_name' was disabled" { - [bool]::parse($top_level_task.task.Settings.Enabled) | - Should Be $false - } - - it "task 'task_from_name' has command set to dir" { - $top_level_task.Task.Actions.Exec.Command | Should Be 'dir' - } - - it "task 'chef\nested task' was created (no leading \)" { - $second_level_task_no_leading_slash | Should Not BeNullOrEmpty - } - - it "task 'chef\nested task' was changed to command 'dir /s" { - $Command = $second_level_task_no_leading_slash.Task.Actions.Exec.Command - $second_level_task_no_leading_slash.Task.Actions.Exec.Arguments | - foreach {$Command += " $_"} - $Command | Should Be 'dir /s' - } - - it 'task \chef\longtask was created (with leading \)' { - $second_level_task_leading_slash | Should Not BeNullOrEmpty - } - - it 'task \chef\longtask was started and stopped' { - $second_level_task.'Last Run Time' | Should Not Match 'N/A' - } - - it 'task \chef\longtask was started and stopped and is ready' { - $second_level_task.'Status' | Should Be 'Ready' - } - - it 'task delete_me should not exist' { - $missing_task | Should BeNullOrEmpty - } - - it "task 'chef\change_me' was changed via create to command 'dir /s" { - $Command = $task_changed_by_create.Task.Actions.Exec.Command - $task_changed_by_create.Task.Actions.Exec.Arguments | - foreach {$Command += " $_"} - $Command | Should Be 'dir /s' - } - - it "task 'task_for_system' was created" { - $system_task | Should Not BeNullOrEmpty - } - - it "task 'task_for_system' runs in temp" { - $system_task.Task.Actions.Exec.WorkingDirectory | Should Be $env:temp - } - - it "task 'task_on_idle' was created" { - $task_on_idle | Should Not BeNullOrEmpty - } - - it "task 'task_on_idle' has IdleTrigger" { - $task_on_idle.Task.Triggers.IdleTrigger | Should Not BeNullOrEmpty - } - - it "task 'task_on_idle' has IdleSetting duration 30 minutes" { - $task_on_idle.Task.Settings.IdleSettings.Duration | Should be 'PT30M' - } - - it 'notifies other resources' { - test-path 'c:/notifytest.txt' | should be $true - } - } -} -