summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2020-10-26 22:36:19 -0700
committerLamont Granquist <lamont@scriptkiddie.org>2020-10-26 23:07:32 -0700
commit45874fecc5010353a5aadf4a0893737ad9282f7b (patch)
treeacd5098251e4d845f5b7d3d1a3d79931f0c580b1
parentaccb875c6d01205fdbb4e2bcb190057763bf7669 (diff)
downloadchef-45874fecc5010353a5aadf4a0893737ad9282f7b.tar.gz
Final batch of unified_mode providers
Also converts some of them to custom resources Signed-off-by: Lamont Granquist <lamont@scriptkiddie.org>
-rw-r--r--lib/chef/provider/windows_env.rb210
-rw-r--r--lib/chef/provider/windows_path.rb61
-rw-r--r--lib/chef/provider/windows_task.rb631
-rw-r--r--lib/chef/providers.rb3
-rw-r--r--lib/chef/resource/windows_env.rb173
-rw-r--r--lib/chef/resource/windows_feature.rb2
-rw-r--r--lib/chef/resource/windows_package.rb1
-rw-r--r--lib/chef/resource/windows_path.rb38
-rw-r--r--lib/chef/resource/windows_service.rb1
-rw-r--r--lib/chef/resource/windows_task.rb661
-rw-r--r--lib/chef/resource/windows_workgroup.rb2
-rw-r--r--spec/functional/resource/windows_task_spec.rb5
-rw-r--r--spec/unit/provider/windows_env_spec.rb52
-rw-r--r--spec/unit/provider/windows_path_spec.rb13
-rw-r--r--spec/unit/provider/windows_task_spec.rb13
-rw-r--r--spec/unit/provider_resolver_spec.rb2
16 files changed, 884 insertions, 984 deletions
diff --git a/lib/chef/provider/windows_env.rb b/lib/chef/provider/windows_env.rb
deleted file mode 100644
index f60dc79c5b..0000000000
--- a/lib/chef/provider/windows_env.rb
+++ /dev/null
@@ -1,210 +0,0 @@
-#
-# Author:: Doug MacEachern (<dougm@vmware.com>)
-# Copyright:: Copyright 2010-2016, VMware, 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_relative "../provider"
-require_relative "../resource/windows_env"
-require_relative "../mixin/windows_env_helper"
-
-class Chef
- class Provider
- class WindowsEnv < Chef::Provider
- include Chef::Mixin::WindowsEnvHelper
- attr_accessor :key_exists
-
- provides :env
- provides :windows_env
-
- def whyrun_supported?
- false
- end
-
- def initialize(new_resource, run_context)
- super
- @key_exists = true
- end
-
- def load_current_resource
- @current_resource = Chef::Resource::WindowsEnv.new(new_resource.name)
- current_resource.key_name(new_resource.key_name)
-
- if env_key_exists(new_resource.key_name)
- current_resource.value(env_value(new_resource.key_name))
- else
- @key_exists = false
- logger.trace("#{new_resource} key does not exist")
- end
-
- current_resource
- end
-
- def env_key_exists(key_name)
- env_value(key_name) ? true : false
- end
-
- # Check to see if value needs any changes
- #
- # ==== Returns
- # <true>:: If a change is required
- # <false>:: If a change is not required
- def requires_modify_or_create?
- if new_resource.delim
- # e.g. check for existing value within PATH
- new_values.inject(0) do |index, val|
- next_index = current_values.find_index val
- return true if next_index.nil? || next_index < index
-
- next_index
- end
- false
- else
- new_resource.value != current_resource.value
- end
- end
-
- alias_method :compare_value, :requires_modify_or_create?
-
- action :create do
- if @key_exists
- if requires_modify_or_create?
- modify_env
- logger.info("#{new_resource} altered")
- new_resource.updated_by_last_action(true)
- end
- else
- create_env
- logger.info("#{new_resource} created")
- new_resource.updated_by_last_action(true)
- end
- end
-
- # e.g. delete a PATH element
- #
- # ==== Returns
- # <true>:: If we handled the element case and caller should not delete the key
- # <false>:: Caller should delete the key, either no :delim was specific or value was empty
- # after we removed the element.
- def delete_element
- return false unless new_resource.delim # no delim: delete the key
-
- needs_delete = new_values.any? { |v| current_values.include?(v) }
- if !needs_delete
- logger.trace("#{new_resource} element '#{new_resource.value}' does not exist")
- true # do not delete the key
- else
- new_value =
- current_values.select do |item|
- not new_values.include?(item)
- end.join(new_resource.delim)
-
- if new_value.empty?
- false # nothing left here, delete the key
- else
- old_value = new_resource.value(new_value)
- create_env
- logger.trace("#{new_resource} deleted #{old_value} element")
- new_resource.updated_by_last_action(true)
- true # we removed the element and updated; do not delete the key
- end
- end
- end
-
- action :delete do
- if ( ENV[new_resource.key_name] || @key_exists ) && !delete_element
- delete_env
- logger.info("#{new_resource} deleted")
- new_resource.updated_by_last_action(true)
- end
- end
-
- action :modify do
- if @key_exists
- if requires_modify_or_create?
- modify_env
- logger.info("#{new_resource} modified")
- new_resource.updated_by_last_action(true)
- end
- else
- raise Chef::Exceptions::WindowsEnv, "Cannot modify #{new_resource} - key does not exist!"
- end
- end
-
- def create_env
- obj = env_obj(@new_resource.key_name)
- unless obj
- obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
- obj.name = @new_resource.key_name
- obj.username = new_resource.user
- end
- obj.variablevalue = @new_resource.value
- obj.put_
- value = @new_resource.value
- value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0
- ENV[@new_resource.key_name] = value
- broadcast_env_change
- end
-
- def delete_env
- obj = env_obj(@new_resource.key_name)
- if obj
- obj.delete_
- broadcast_env_change
- end
- if ENV[@new_resource.key_name]
- ENV.delete(@new_resource.key_name)
- end
- end
-
- def modify_env
- if new_resource.delim
- new_resource.value((new_values + current_values).uniq.join(new_resource.delim))
- end
- create_env
- end
-
- # Returns the current values to split by delimiter
- def current_values
- @current_values ||= current_resource.value.split(new_resource.delim)
- end
-
- # Returns the new values to split by delimiter
- def new_values
- @new_values ||= new_resource.value.split(new_resource.delim)
- end
-
- def env_value(key_name)
- obj = env_obj(key_name)
- obj.variablevalue if obj
- end
-
- def env_obj(key_name)
- return @env_obj if @env_obj
-
- wmi = WmiLite::Wmi.new
- # Note that by design this query is case insensitive with regard to key_name
- environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
- if environment_variables && environment_variables.length > 0
- environment_variables.each do |env|
- @env_obj = env.wmi_ole_object
- return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0
- end
- end
- @env_obj = nil
- end
- end
- end
-end
diff --git a/lib/chef/provider/windows_path.rb b/lib/chef/provider/windows_path.rb
deleted file mode 100644
index 36d202ad49..0000000000
--- a/lib/chef/provider/windows_path.rb
+++ /dev/null
@@ -1,61 +0,0 @@
-#
-# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
-# Copyright:: Copyright (c) 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_relative "../mixin/windows_env_helper" if ChefUtils.windows?
-require_relative "../mixin/wide_string"
-require_relative "../exceptions"
-
-class Chef
- class Provider
- class WindowsPath < Chef::Provider
- include Chef::Mixin::WindowsEnvHelper if ChefUtils.windows?
-
- provides :windows_path
-
- def load_current_resource
- @current_resource = Chef::Resource::WindowsPath.new(new_resource.name)
- @current_resource.path(new_resource.path)
- @current_resource
- end
-
- action :add do
- # The windows Env provider does not correctly expand variables in
- # the PATH environment variable. Ruby expects these to be expanded.
- #
- path = expand_path(new_resource.path)
- declare_resource(:env, "path") do
- action :modify
- delim ::File::PATH_SEPARATOR
- value path.tr("/", '\\')
- end
- end
-
- action :remove do
- # The windows Env provider does not correctly expand variables in
- # the PATH environment variable. Ruby expects these to be expanded.
- #
- path = expand_path(new_resource.path)
- declare_resource(:env, "path") do
- action :delete
- delim ::File::PATH_SEPARATOR
- value path.tr("/", '\\')
- end
- end
- end
- end
-end
diff --git a/lib/chef/provider/windows_task.rb b/lib/chef/provider/windows_task.rb
deleted file mode 100644
index 963c52ef02..0000000000
--- a/lib/chef/provider/windows_task.rb
+++ /dev/null
@@ -1,631 +0,0 @@
-#
-# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
-# Copyright:: Copyright (c) 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.
-#
-
-autoload :ISO8601, "iso8601" if ChefUtils.windows?
-require_relative "../provider"
-require_relative "../util/path_helper"
-require "win32/taskscheduler" if ChefUtils.windows?
-
-class Chef
- class Provider
- class WindowsTask < Chef::Provider
- if ChefUtils.windows?
- include Win32
-
- provides :windows_task
-
- MONTHS = {
- JAN: TaskScheduler::JANUARY,
- FEB: TaskScheduler::FEBRUARY,
- MAR: TaskScheduler::MARCH,
- APR: TaskScheduler::APRIL,
- MAY: TaskScheduler::MAY,
- JUN: TaskScheduler::JUNE,
- JUL: TaskScheduler::JULY,
- AUG: TaskScheduler::AUGUST,
- SEP: TaskScheduler::SEPTEMBER,
- OCT: TaskScheduler::OCTOBER,
- NOV: TaskScheduler::NOVEMBER,
- DEC: TaskScheduler::DECEMBER,
- }.freeze
-
- DAYS_OF_WEEK = { MON: TaskScheduler::MONDAY,
- TUE: TaskScheduler::TUESDAY,
- WED: TaskScheduler::WEDNESDAY,
- THU: TaskScheduler::THURSDAY,
- FRI: TaskScheduler::FRIDAY,
- SAT: TaskScheduler::SATURDAY,
- SUN: TaskScheduler::SUNDAY }.freeze
-
- WEEKS_OF_MONTH = {
- FIRST: TaskScheduler::FIRST_WEEK,
- SECOND: TaskScheduler::SECOND_WEEK,
- THIRD: TaskScheduler::THIRD_WEEK,
- FOURTH: TaskScheduler::FOURTH_WEEK,
- }.freeze
-
- DAYS_OF_MONTH = {
- 1 => TaskScheduler::TASK_FIRST,
- 2 => TaskScheduler::TASK_SECOND,
- 3 => TaskScheduler::TASK_THIRD,
- 4 => TaskScheduler::TASK_FOURTH,
- 5 => TaskScheduler::TASK_FIFTH,
- 6 => TaskScheduler::TASK_SIXTH,
- 7 => TaskScheduler::TASK_SEVENTH,
- 8 => TaskScheduler::TASK_EIGHTH,
- # cspell:disable-next-line
- 9 => TaskScheduler::TASK_NINETH,
- 10 => TaskScheduler::TASK_TENTH,
- 11 => TaskScheduler::TASK_ELEVENTH,
- 12 => TaskScheduler::TASK_TWELFTH,
- 13 => TaskScheduler::TASK_THIRTEENTH,
- 14 => TaskScheduler::TASK_FOURTEENTH,
- 15 => TaskScheduler::TASK_FIFTEENTH,
- 16 => TaskScheduler::TASK_SIXTEENTH,
- 17 => TaskScheduler::TASK_SEVENTEENTH,
- 18 => TaskScheduler::TASK_EIGHTEENTH,
- 19 => TaskScheduler::TASK_NINETEENTH,
- 20 => TaskScheduler::TASK_TWENTIETH,
- 21 => TaskScheduler::TASK_TWENTY_FIRST,
- 22 => TaskScheduler::TASK_TWENTY_SECOND,
- 23 => TaskScheduler::TASK_TWENTY_THIRD,
- 24 => TaskScheduler::TASK_TWENTY_FOURTH,
- 25 => TaskScheduler::TASK_TWENTY_FIFTH,
- 26 => TaskScheduler::TASK_TWENTY_SIXTH,
- 27 => TaskScheduler::TASK_TWENTY_SEVENTH,
- 28 => TaskScheduler::TASK_TWENTY_EIGHTH,
- 29 => TaskScheduler::TASK_TWENTY_NINTH,
- # cspell:disable-next-line
- 30 => TaskScheduler::TASK_THIRTYETH,
- 31 => TaskScheduler::TASK_THIRTY_FIRST,
- }.freeze
-
- PRIORITY = { "critical" => 0, "highest" => 1, "above_normal_2" => 2 , "above_normal_3" => 3, "normal_4" => 4,
- "normal_5" => 5, "normal_6" => 6, "below_normal_7" => 7, "below_normal_8" => 8, "lowest" => 9, "idle" => 10 }.freeze
-
- def load_current_resource
- @current_resource = Chef::Resource::WindowsTask.new(new_resource.name)
- task = TaskScheduler.new(new_resource.task_name, nil, "\\", false)
- @current_resource.exists = task.exists?(new_resource.task_name)
- if @current_resource.exists
- task.get_task(new_resource.task_name)
- @current_resource.task = task
- pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
- @current_resource.task_name(pathed_task_name)
- end
- @current_resource
- end
-
- action :create do
- set_command_and_arguments if new_resource.command
-
- if current_resource.exists
- logger.trace "#{new_resource} task exist."
- unless (task_needs_update?(current_resource.task)) || (new_resource.force)
- logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do"
- return
- end
-
- # if start_day and start_time is not set by user current date and time will be set while updating any property
- set_start_day_and_time unless new_resource.frequency == :none
- update_task(current_resource.task)
- else
- basic_validation
- set_start_day_and_time
- converge_by("#{new_resource} task created") do
- task = TaskScheduler.new
- if new_resource.frequency == :none
- task.new_work_item(new_resource.task_name, {}, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
- task.activate(new_resource.task_name)
- else
- task.new_work_item(new_resource.task_name, trigger, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
- end
- task.application_name = new_resource.command
- task.parameters = new_resource.command_arguments if new_resource.command_arguments
- task.working_directory = new_resource.cwd if new_resource.cwd
- task.configure_settings(config_settings)
- task.configure_principals(principal_settings)
- task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
- task.creator = new_resource.user
- task.description = new_resource.description unless new_resource.description.nil?
- task.activate(new_resource.task_name)
- end
- end
- end
-
- action :run do
- if current_resource.exists
- logger.trace "#{new_resource} task exists"
- if current_resource.task.status == "running"
- logger.info "#{new_resource} task is currently running, skipping run"
- else
- converge_by("run scheduled task #{new_resource}") do
- current_resource.task.run
- end
- end
- else
- logger.warn "#{new_resource} task does not exist - nothing to do"
- end
- end
-
- action :delete do
- if current_resource.exists
- logger.trace "#{new_resource} task exists"
- converge_by("delete scheduled task #{new_resource}") do
- ts = TaskScheduler.new
- ts.delete(current_resource.task_name)
- end
- else
- logger.warn "#{new_resource} task does not exist - nothing to do"
- end
- end
-
- action :end do
- if current_resource.exists
- logger.trace "#{new_resource} task exists"
- if current_resource.task.status != "running"
- logger.trace "#{new_resource} is not running - nothing to do"
- else
- converge_by("#{new_resource} task ended") do
- current_resource.task.stop
- end
- end
- else
- logger.warn "#{new_resource} task does not exist - nothing to do"
- end
- end
-
- action :enable do
- if current_resource.exists
- logger.trace "#{new_resource} task exists"
- if current_resource.task.status == "not scheduled"
- converge_by("#{new_resource} task enabled") do
- # TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here
- run_schtasks "CHANGE", "ENABLE" => ""
- end
- else
- logger.trace "#{new_resource} already enabled - nothing to do"
- end
- else
- logger.fatal "#{new_resource} task does not exist - nothing to do"
- raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
- end
- end
-
- action :disable do
- if current_resource.exists
- logger.info "#{new_resource} task exists"
- if %w{ready running}.include?(current_resource.task.status)
- converge_by("#{new_resource} task disabled") do
- # TODO: in win32-taskscheduler there is no method which disables the task so currently calling disable with schtasks.exe
- run_schtasks "CHANGE", "DISABLE" => ""
- end
- else
- logger.warn "#{new_resource} already disabled - nothing to do"
- end
- else
- logger.warn "#{new_resource} task does not exist - nothing to do"
- end
- end
-
- alias_method :action_change, :action_create
-
- private
-
- # separated command arguments from :command property
- def set_command_and_arguments
- cmd, *args = Chef::Util::PathHelper.split_args(new_resource.command)
- new_resource.command = cmd
- new_resource.command_arguments = args.join(" ")
- end
-
- def set_start_day_and_time
- new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day
- new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time
- end
-
- def update_task(task)
- converge_by("#{new_resource} task updated") do
- task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
- task.application_name = new_resource.command if new_resource.command
- task.parameters = new_resource.command_arguments if new_resource.command_arguments
- task.working_directory = new_resource.cwd if new_resource.cwd
- task.trigger = trigger unless new_resource.frequency == :none
- task.configure_settings(config_settings)
- task.creator = new_resource.user
- task.description = new_resource.description unless new_resource.description.nil?
- task.configure_principals(principal_settings)
- end
- end
-
- def trigger
- start_month, start_day, start_year = new_resource.start_day.to_s.split("/")
- start_hour, start_minute = new_resource.start_time.to_s.split(":")
- # TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error.
- trigger_hash = {
- start_year: start_year.to_i,
- start_month: start_month.to_i,
- start_day: start_day.to_i,
- start_hour: start_hour.to_i,
- start_minute: start_minute.to_i,
- end_month: 0,
- end_day: 0,
- end_year: 0,
- trigger_type: trigger_type,
- type: type,
- random_minutes_interval: new_resource.random_delay,
- }
-
- if new_resource.frequency == :minute
- trigger_hash[:minutes_interval] = new_resource.frequency_modifier
- end
-
- if new_resource.frequency == :hourly
- minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i)
- trigger_hash[:minutes_interval] = minutes
- end
-
- if new_resource.minutes_interval
- trigger_hash[:minutes_interval] = new_resource.minutes_interval
- end
-
- if new_resource.minutes_duration
- trigger_hash[:minutes_duration] = new_resource.minutes_duration
- end
-
- if trigger_type == TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier)
- trigger_hash[:run_on_last_week_of_month] = true
- else
- trigger_hash[:run_on_last_week_of_month] = false
- end
-
- if trigger_type == TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day)
- trigger_hash[:run_on_last_day_of_month] = true
- else
- trigger_hash[:run_on_last_day_of_month] = false
- end
- trigger_hash
- end
-
- def frequency_modifier_contains_last_week?(frequency_modifier)
- frequency_modifier = frequency_modifier.to_s.split(",")
- frequency_modifier.map! { |value| value.strip.upcase }
- frequency_modifier.include?("LAST")
- end
-
- def day_includes_last_or_lastday?(day)
- day = day.to_s.split(",")
- day.map! { |value| value.strip.upcase }
- day.include?("LAST") || day.include?("LASTDAY")
- end
-
- def convert_hours_in_minutes(hours)
- hours.to_i * 60 if hours
- end
-
- # TODO : Try to optimize this method
- # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency
- # is not getting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task
- def task_needs_update?(task)
- flag = false
- if new_resource.frequency == :none
- flag = (task.author != new_resource.user ||
- task.application_name != new_resource.command ||
- description_needs_update?(task) ||
- task.parameters != new_resource.command_arguments.to_s ||
- task.principals[:run_level] != run_level ||
- task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
- task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
- task.settings[:start_when_available] != new_resource.start_when_available)
- else
- current_task_trigger = task.trigger(0)
- new_task_trigger = trigger
- flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle
- flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil?
-
- # if trigger not found updating the task to add the trigger
- if current_task_trigger.nil?
- flag = true
- else
- flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true ||
- start_time_updated?(current_task_trigger, new_task_trigger) == true ||
- current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] ||
- current_task_trigger[:type] != new_task_trigger[:type] ||
- current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
- current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
- task.author.to_s.casecmp(new_resource.user.to_s) != 0 ||
- task.application_name != new_resource.command ||
- description_needs_update?(task) ||
- task.parameters != new_resource.command_arguments.to_s ||
- task.working_directory != new_resource.cwd.to_s ||
- task.principals[:logon_type] != logon_type ||
- task.principals[:run_level] != run_level ||
- PRIORITY[task.priority] != new_resource.priority ||
- task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
- task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
- task.settings[:start_when_available] != new_resource.start_when_available
- if trigger_type == TaskScheduler::MONTHLYDATE
- flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month]
- end
-
- if trigger_type == TaskScheduler::MONTHLYDOW
- flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month]
- end
- end
- end
- flag
- end
-
- def start_day_updated?(current_task_trigger, new_task_trigger)
- ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] ||
- current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] ||
- current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) )
- end
-
- def start_time_updated?(current_task_trigger, new_task_trigger)
- ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] ||
- current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) )
- end
-
- def trigger_type
- case new_resource.frequency
- when :once, :minute, :hourly
- TaskScheduler::ONCE
- when :daily
- TaskScheduler::DAILY
- when :weekly
- TaskScheduler::WEEKLY
- when :monthly
- # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow
- # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx
- new_resource.frequency_modifier.to_i.between?(1, 12) ? TaskScheduler::MONTHLYDATE : TaskScheduler::MONTHLYDOW
- when :on_idle
- TaskScheduler::ON_IDLE
- when :onstart
- TaskScheduler::AT_SYSTEMSTART
- when :on_logon
- TaskScheduler::AT_LOGON
- else
- raise ArgumentError, "Please set frequency"
- end
- end
-
- def type
- case trigger_type
- when TaskScheduler::ONCE
- { once: nil }
- when TaskScheduler::DAILY
- { days_interval: new_resource.frequency_modifier.to_i }
- when TaskScheduler::WEEKLY
- { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i }
- when TaskScheduler::MONTHLYDATE
- { months: months_of_year.to_i, days: days_of_month.to_i }
- when TaskScheduler::MONTHLYDOW
- { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i }
- when TaskScheduler::ON_IDLE
- # TODO: handle option for this trigger
- when TaskScheduler::AT_LOGON
- # TODO: handle option for this trigger
- when TaskScheduler::AT_SYSTEMSTART
- # TODO: handle option for this trigger
- end
- end
-
- # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.
- def weeks_of_month
- weeks_of_month = []
- if new_resource.frequency_modifier
- weeks = new_resource.frequency_modifier.split(",")
- weeks.map! { |week| week.to_s.strip.upcase }
- weeks.delete("LAST") if weeks.include?("LAST")
- weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH)
- end
- weeks_of_month
- end
-
- # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter.
- def days_of_month
- days_of_month = []
- if new_resource.day
- days = new_resource.day.to_s.split(",")
- days.map! { |day| day.to_s.strip.upcase }
- days.delete("LAST") if days.include?("LAST")
- days.delete("LASTDAY") if days.include?("LASTDAY")
- if days - (1..31).to_a
- days.each do |day|
- days_of_month << DAYS_OF_MONTH[day.to_i]
- end
- days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0]
- end
- else
- days_of_month = DAYS_OF_MONTH[1]
- end
- days_of_month
- end
-
- def days_of_week
- if new_resource.day
- # this line of code is just to support backward compatibility of wild card *
- new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly
- days = new_resource.day.to_s.split(",")
- days.map! { |day| day.to_s.strip.upcase }
- weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK)
- else
- # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day
- if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?)
- weeks_days = current_resource.task.trigger(0)[:type][:days_of_week]
- else
- day = get_day(new_resource.start_day).to_sym if new_resource.start_day
- DAYS_OF_WEEK[day]
- end
- end
- end
-
- def months_of_year
- months_of_year = []
- if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months)
- new_resource.months = set_months(new_resource.frequency_modifier.to_i)
- end
-
- if new_resource.months
- # this line of code is just to support backward compatibility of wild card *
- new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly
- months = new_resource.months.split(",")
- months.map! { |month| month.to_s.strip.upcase }
- months_of_year = get_binary_values_from_constants(months, MONTHS)
- else
- MONTHS.each do |key, value|
- months_of_year << MONTHS[key]
- end
- months_of_year = months_of_year.inject(:|)
- end
- months_of_year
- end
-
- # This values are set for frequency_modifier set as 1-12
- # This is to give backward compatibility validated this values with earlier code and running schtask.exe
- # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
- def set_months(frequency_modifier)
- case frequency_modifier
- when 1
- "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec"
- when 2
- "feb, apr, jun, aug, oct, dec"
- when 3
- "mar, jun, sep, dec"
- when 4
- "apr, aug, dec"
- when 5
- "may, oct"
- when 6
- "jun, dec"
- when 7
- "jul"
- when 8
- "aug"
- when 9
- "sep"
- when 10
- "oct"
- when 11
- "nov"
- when 12
- "dec"
- end
- end
-
- def get_binary_values_from_constants(array_values, constant)
- data = []
- array_values.each do |value|
- value = value.to_sym
- data << constant[value]
- end
- data.size > 1 ? data.inject(:|) : data[0]
- end
-
- def run_level
- case new_resource.run_level
- when :highest
- TaskScheduler::TASK_RUNLEVEL_HIGHEST
- when :limited
- TaskScheduler::TASK_RUNLEVEL_LUA
- end
- end
-
- # TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 format
- def config_settings
- settings = {
- execution_time_limit: new_resource.execution_time_limit,
- enabled: true,
- }
- settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time
- settings[:run_only_if_idle] = true if new_resource.idle_time
- settings[:priority] = new_resource.priority
- settings[:disallow_start_if_on_batteries] = new_resource.disallow_start_if_on_batteries
- settings[:stop_if_going_on_batteries] = new_resource.stop_if_going_on_batteries
- settings[:start_when_available] = new_resource.start_when_available
- settings
- end
-
- def principal_settings
- settings = {}
- settings[:run_level] = run_level
- settings[:logon_type] = logon_type
- settings
- end
-
- def description_needs_update?(task)
- task.description != new_resource.description unless new_resource.description.nil?
- end
-
- def logon_type
- # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx
- # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparison.
- user_id = new_resource.user.to_s
- password = new_resource.password.to_s
- if Chef::ReservedNames::Win32::Security::SID.service_account_user?(user_id)
- TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT
- elsif Chef::ReservedNames::Win32::Security::SID.group_user?(user_id)
- TaskScheduler::TASK_LOGON_GROUP
- elsif !user_id.empty? && !password.empty?
- if new_resource.interactive_enabled
- TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
- else
- TaskScheduler::TASK_LOGON_PASSWORD
- end
- else
- TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
- end
- end
-
- # This method checks if task and command properties exist since those two are mandatory properties to create a schedules task.
- def basic_validation
- validate = []
- validate << "Command" if new_resource.command.nil? || new_resource.command.empty?
- validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty?
- return true if validate.empty?
-
- raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(", ")}' option cannot be empty"
- end
-
- # rubocop:disable Style/StringLiteralsInInterpolation
- def run_schtasks(task_action, options = {})
- cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
- options.each_key do |option|
- unless option == "TR"
- cmd += "/#{option} "
- cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ""
- end
- end
- # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value
- if options["TR"]
- cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE"
- end
- logger.trace("running: ")
- logger.trace(" #{cmd}")
- shell_out!(cmd, returns: [0])
- end
- # rubocop:enable Style/StringLiteralsInInterpolation
-
- def get_day(date)
- Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
- end
- end
- end
- end
-end
diff --git a/lib/chef/providers.rb b/lib/chef/providers.rb
index 6b099fc2b7..7652d60896 100644
--- a/lib/chef/providers.rb
+++ b/lib/chef/providers.rb
@@ -47,11 +47,8 @@ require_relative "provider/systemd_unit"
require_relative "provider/template"
require_relative "provider/user"
require_relative "provider/whyrun_safe_ruby_block"
-require_relative "provider/windows_env"
require_relative "provider/yum_repository"
-require_relative "provider/windows_task"
require_relative "provider/zypper_repository"
-require_relative "provider/windows_path"
require_relative "provider/package/apt"
require_relative "provider/package/chocolatey"
diff --git a/lib/chef/resource/windows_env.rb b/lib/chef/resource/windows_env.rb
index 35070d6623..ab65465ed6 100644
--- a/lib/chef/resource/windows_env.rb
+++ b/lib/chef/resource/windows_env.rb
@@ -18,10 +18,13 @@
#
require_relative "../resource"
+require_relative "../mixin/windows_env_helper"
class Chef
class Resource
class WindowsEnv < Chef::Resource
+ unified_mode true
+
provides :windows_env
provides :env # backwards compat with the pre-Chef 14 resource name
@@ -52,6 +55,176 @@ class Chef
desired_state: false
property :user, String, default: "<System>"
+
+ action_class do
+ include Chef::Mixin::WindowsEnvHelper
+
+ def whyrun_supported?
+ false
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsEnv.new(new_resource.name)
+ current_resource.key_name(new_resource.key_name)
+
+ if key_exists?
+ current_resource.value(env_value(new_resource.key_name))
+ else
+ logger.trace("#{new_resource} key does not exist")
+ end
+
+ current_resource
+ end
+
+ def key_exists?
+ @key_exists ||= !!env_value(new_resource.key_name)
+ end
+
+ def requires_modify_or_create?
+ if new_resource.delim
+ # e.g. check for existing value within PATH
+ new_values.inject(0) do |index, val|
+ next_index = current_values.find_index val
+ return true if next_index.nil? || next_index < index
+
+ next_index
+ end
+ false
+ else
+ new_resource.value != current_resource.value
+ end
+ end
+
+ alias_method :compare_value, :requires_modify_or_create?
+
+ # e.g. delete a PATH element
+ #
+ # ==== Returns
+ # <true>:: If we handled the element case and caller should not delete the key
+ # <false>:: Caller should delete the key, either no :delim was specific or value was empty
+ # after we removed the element.
+ def delete_element
+ return false unless new_resource.delim # no delim: delete the key
+
+ needs_delete = new_values.any? { |v| current_values.include?(v) }
+ if !needs_delete
+ logger.trace("#{new_resource} element '#{new_resource.value}' does not exist")
+ true # do not delete the key
+ else
+ new_value =
+ current_values.select do |item|
+ not new_values.include?(item)
+ end.join(new_resource.delim)
+
+ if new_value.empty?
+ false # nothing left here, delete the key
+ else
+ old_value = new_resource.value(new_value)
+ create_env
+ logger.trace("#{new_resource} deleted #{old_value} element")
+ new_resource.updated_by_last_action(true)
+ true # we removed the element and updated; do not delete the key
+ end
+ end
+ end
+
+ def create_env
+ obj = env_obj(@new_resource.key_name)
+ unless obj
+ obj = WIN32OLE.connect("winmgmts://").get("Win32_Environment").spawninstance_
+ obj.name = @new_resource.key_name
+ obj.username = new_resource.user
+ end
+ obj.variablevalue = @new_resource.value
+ obj.put_
+ value = @new_resource.value
+ value = expand_path(value) if @new_resource.key_name.casecmp("PATH") == 0
+ ENV[@new_resource.key_name] = value
+ broadcast_env_change
+ end
+
+ def delete_env
+ obj = env_obj(@new_resource.key_name)
+ if obj
+ obj.delete_
+ broadcast_env_change
+ end
+ if ENV[@new_resource.key_name]
+ ENV.delete(@new_resource.key_name)
+ end
+ end
+
+ def modify_env
+ if new_resource.delim
+ new_resource.value((new_values + current_values).uniq.join(new_resource.delim))
+ end
+ create_env
+ end
+
+ # Returns the current values to split by delimiter
+ def current_values
+ @current_values ||= current_resource.value.split(new_resource.delim)
+ end
+
+ # Returns the new values to split by delimiter
+ def new_values
+ @new_values ||= new_resource.value.split(new_resource.delim)
+ end
+
+ def env_value(key_name)
+ obj = env_obj(key_name)
+ obj.variablevalue if obj
+ end
+
+ def env_obj(key_name)
+ return @env_obj if @env_obj
+
+ wmi = WmiLite::Wmi.new
+ # Note that by design this query is case insensitive with regard to key_name
+ environment_variables = wmi.query("select * from Win32_Environment where name = '#{key_name}'")
+ if environment_variables && environment_variables.length > 0
+ environment_variables.each do |env|
+ @env_obj = env.wmi_ole_object
+ return @env_obj if @env_obj.username.split('\\').last.casecmp(new_resource.user) == 0
+ end
+ end
+ @env_obj = nil
+ end
+ end
+
+ action :create do
+ if key_exists?
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} altered")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ create_env
+ logger.info("#{new_resource} created")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ action :delete do
+ if ( ENV[new_resource.key_name] || key_exists? ) && !delete_element
+ delete_env
+ logger.info("#{new_resource} deleted")
+ new_resource.updated_by_last_action(true)
+ end
+ end
+
+ action :modify do
+ if key_exists?
+ if requires_modify_or_create?
+ modify_env
+ logger.info("#{new_resource} modified")
+ new_resource.updated_by_last_action(true)
+ end
+ else
+ raise Chef::Exceptions::WindowsEnv, "Cannot modify #{new_resource} - key does not exist!"
+ end
+ end
end
end
end
diff --git a/lib/chef/resource/windows_feature.rb b/lib/chef/resource/windows_feature.rb
index aa90f67a37..760a7fe3f1 100644
--- a/lib/chef/resource/windows_feature.rb
+++ b/lib/chef/resource/windows_feature.rb
@@ -21,6 +21,8 @@ require_relative "../resource"
class Chef
class Resource
class WindowsFeature < Chef::Resource
+ unified_mode true
+
provides(:windows_feature) { true }
description "Use the **windows_feature** resource to add, remove or entirely delete Windows features and roles. This resource calls the 'windows_feature_dism' or 'windows_feature_powershell' resources depending on the specified installation method, and defaults to DISM, which is available on both Workstation and Server editions of Windows."
diff --git a/lib/chef/resource/windows_package.rb b/lib/chef/resource/windows_package.rb
index 2e10dde43d..0072d70656 100644
--- a/lib/chef/resource/windows_package.rb
+++ b/lib/chef/resource/windows_package.rb
@@ -26,6 +26,7 @@ class Chef
class Resource
class WindowsPackage < Chef::Resource::Package
include Chef::Mixin::Uris
+ unified_mode true
provides(:windows_package) { true }
provides :package, os: "windows"
diff --git a/lib/chef/resource/windows_path.rb b/lib/chef/resource/windows_path.rb
index 850be66ba0..427360023c 100644
--- a/lib/chef/resource/windows_path.rb
+++ b/lib/chef/resource/windows_path.rb
@@ -16,11 +16,15 @@
# limitations under the License.
#
+require_relative "../mixin/windows_env_helper" if ChefUtils.windows?
+require_relative "../mixin/wide_string"
require_relative "../resource"
class Chef
class Resource
class WindowsPath < Chef::Resource
+ unified_mode true
+
provides(:windows_path) { true }
description "Use the **windows_path** resource to manage the path environment variable on Microsoft Windows."
@@ -49,6 +53,40 @@ class Chef
property :path, String,
description: "An optional property to set the path value if it differs from the resource block's name.",
name_property: true
+
+ action_class do
+ include Chef::Mixin::WindowsEnvHelper if ChefUtils.windows?
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsPath.new(new_resource.name)
+ @current_resource.path(new_resource.path)
+ @current_resource
+ end
+
+ action :add do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ env "path" do
+ action :modify
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
+
+ action :remove do
+ # The windows Env provider does not correctly expand variables in
+ # the PATH environment variable. Ruby expects these to be expanded.
+ #
+ path = expand_path(new_resource.path)
+ env "path" do
+ action :delete
+ delim ::File::PATH_SEPARATOR
+ value path.tr("/", '\\')
+ end
+ end
end
end
end
diff --git a/lib/chef/resource/windows_service.rb b/lib/chef/resource/windows_service.rb
index defdf9fe29..0fa007497b 100644
--- a/lib/chef/resource/windows_service.rb
+++ b/lib/chef/resource/windows_service.rb
@@ -23,6 +23,7 @@ class Chef
class Resource
class WindowsService < Chef::Resource::Service
include Chef::Win32ServiceConstants
+ unified_mode true
ALLOWED_START_TYPES = {
automatic: SERVICE_AUTO_START,
diff --git a/lib/chef/resource/windows_task.rb b/lib/chef/resource/windows_task.rb
index 4b6b9ba9cf..8a22ed321b 100644
--- a/lib/chef/resource/windows_task.rb
+++ b/lib/chef/resource/windows_task.rb
@@ -16,12 +16,18 @@
# limitations under the License.
#
+require "chef-utils" unless defined?(ChefUtils::CANARY)
require_relative "../resource"
-require_relative "../win32/security" if Chef::Platform.windows?
+require_relative "../win32/security" if ChefUtils.windows_ruby?
+autoload :ISO8601, "iso8601" if ChefUtils.windows_ruby?
+require_relative "../util/path_helper"
+require "win32/taskscheduler" if ChefUtils.windows_ruby?
class Chef
class Resource
class WindowsTask < Chef::Resource
+ unified_mode true
+
provides(:windows_task) { true }
description "Use the **windows_task** resource to create, delete or run a Windows scheduled task."
@@ -144,8 +150,8 @@ class Chef
default_action :create
property :task_name, String, regex: [%r{\A[^/\:\*\?\<\>\|]+\z}],
- description: "An optional property to set the task name if it differs from the resource block's name. Example: `Task Name` or `/Task Name`",
- name_property: true
+ description: "An optional property to set the task name if it differs from the resource block's name. Example: `Task Name` or `/Task Name`",
+ name_property: true
property :command, String,
description: "The command to be executed by the windows scheduled task."
@@ -155,15 +161,15 @@ class Chef
property :user, String,
description: "The user to run the task as.",
- default: lazy { Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name if Chef::Platform.windows? },
+ default: lazy { Chef::ReservedNames::Win32::Security::SID.LocalSystem.account_simple_name if ChefUtils.windows_ruby? },
default_description: "The localized SYSTEM user for the node."
property :password, String,
description: "The user's password. The user property must be set if using this property."
property :run_level, Symbol, equal_to: %i{highest limited},
- description: "Run with `:limited` or `:highest` privileges.",
- default: :limited
+ description: "Run with `:limited` or `:highest` privileges.",
+ default: :limited
property :force, [TrueClass, FalseClass],
description: "When used with create, will update the task.",
@@ -177,16 +183,16 @@ class Chef
default: 1
property :frequency, Symbol, equal_to: %i{minute
- hourly
- daily
- weekly
- monthly
- once
- on_logon
- onstart
- on_idle
- none},
- description: "The frequency with which to run the task."
+ hourly
+ daily
+ weekly
+ monthly
+ once
+ on_logon
+ onstart
+ on_idle
+ none},
+ description: "The frequency with which to run the task."
property :start_day, String,
description: "Specifies the first date on which the task runs in **MM/DD/YYYY** format.",
@@ -383,16 +389,16 @@ class Chef
frequency_modifier = frequency_modifier.to_i
min = 1
max = case frequency
- when :minute
- 1439
- when :hourly
- 23
- when :daily
- 365
- when :weekly
- 52
- else
- min
+ when :minute
+ 1439
+ when :hourly
+ 23
+ when :daily
+ 365
+ when :weekly
+ 52
+ else
+ min
end
unless frequency_modifier.between?(min, max)
raise ArgumentError, "frequency_modifier value #{frequency_modifier} is invalid. Valid values for :#{frequency} frequency are #{min} - #{max}."
@@ -464,6 +470,609 @@ class Chef
def sec_to_min(seconds)
seconds.to_i / 60
end
+
+ action_class do
+ if ChefUtils.windows_ruby?
+ include ::Win32
+
+ MONTHS = {
+ JAN: ::Win32::TaskScheduler::JANUARY,
+ FEB: ::Win32::TaskScheduler::FEBRUARY,
+ MAR: ::Win32::TaskScheduler::MARCH,
+ APR: ::Win32::TaskScheduler::APRIL,
+ MAY: ::Win32::TaskScheduler::MAY,
+ JUN: ::Win32::TaskScheduler::JUNE,
+ JUL: ::Win32::TaskScheduler::JULY,
+ AUG: ::Win32::TaskScheduler::AUGUST,
+ SEP: ::Win32::TaskScheduler::SEPTEMBER,
+ OCT: ::Win32::TaskScheduler::OCTOBER,
+ NOV: ::Win32::TaskScheduler::NOVEMBER,
+ DEC: ::Win32::TaskScheduler::DECEMBER,
+ }.freeze
+
+ DAYS_OF_WEEK = { MON: ::Win32::TaskScheduler::MONDAY,
+ TUE: ::Win32::TaskScheduler::TUESDAY,
+ WED: ::Win32::TaskScheduler::WEDNESDAY,
+ THU: ::Win32::TaskScheduler::THURSDAY,
+ FRI: ::Win32::TaskScheduler::FRIDAY,
+ SAT: ::Win32::TaskScheduler::SATURDAY,
+ SUN: ::Win32::TaskScheduler::SUNDAY }.freeze
+
+ WEEKS_OF_MONTH = {
+ FIRST: ::Win32::TaskScheduler::FIRST_WEEK,
+ SECOND: ::Win32::TaskScheduler::SECOND_WEEK,
+ THIRD: ::Win32::TaskScheduler::THIRD_WEEK,
+ FOURTH: ::Win32::TaskScheduler::FOURTH_WEEK,
+ }.freeze
+
+ DAYS_OF_MONTH = {
+ 1 => ::Win32::TaskScheduler::TASK_FIRST,
+ 2 => ::Win32::TaskScheduler::TASK_SECOND,
+ 3 => ::Win32::TaskScheduler::TASK_THIRD,
+ 4 => ::Win32::TaskScheduler::TASK_FOURTH,
+ 5 => ::Win32::TaskScheduler::TASK_FIFTH,
+ 6 => ::Win32::TaskScheduler::TASK_SIXTH,
+ 7 => ::Win32::TaskScheduler::TASK_SEVENTH,
+ 8 => ::Win32::TaskScheduler::TASK_EIGHTH,
+ # cspell:disable-next-line
+ 9 => ::Win32::TaskScheduler::TASK_NINETH,
+ 10 => ::Win32::TaskScheduler::TASK_TENTH,
+ 11 => ::Win32::TaskScheduler::TASK_ELEVENTH,
+ 12 => ::Win32::TaskScheduler::TASK_TWELFTH,
+ 13 => ::Win32::TaskScheduler::TASK_THIRTEENTH,
+ 14 => ::Win32::TaskScheduler::TASK_FOURTEENTH,
+ 15 => ::Win32::TaskScheduler::TASK_FIFTEENTH,
+ 16 => ::Win32::TaskScheduler::TASK_SIXTEENTH,
+ 17 => ::Win32::TaskScheduler::TASK_SEVENTEENTH,
+ 18 => ::Win32::TaskScheduler::TASK_EIGHTEENTH,
+ 19 => ::Win32::TaskScheduler::TASK_NINETEENTH,
+ 20 => ::Win32::TaskScheduler::TASK_TWENTIETH,
+ 21 => ::Win32::TaskScheduler::TASK_TWENTY_FIRST,
+ 22 => ::Win32::TaskScheduler::TASK_TWENTY_SECOND,
+ 23 => ::Win32::TaskScheduler::TASK_TWENTY_THIRD,
+ 24 => ::Win32::TaskScheduler::TASK_TWENTY_FOURTH,
+ 25 => ::Win32::TaskScheduler::TASK_TWENTY_FIFTH,
+ 26 => ::Win32::TaskScheduler::TASK_TWENTY_SIXTH,
+ 27 => ::Win32::TaskScheduler::TASK_TWENTY_SEVENTH,
+ 28 => ::Win32::TaskScheduler::TASK_TWENTY_EIGHTH,
+ 29 => ::Win32::TaskScheduler::TASK_TWENTY_NINTH,
+ # cspell:disable-next-line
+ 30 => ::Win32::TaskScheduler::TASK_THIRTYETH,
+ 31 => ::Win32::TaskScheduler::TASK_THIRTY_FIRST,
+ }.freeze
+
+ PRIORITY = { "critical" => 0, "highest" => 1, "above_normal_2" => 2 , "above_normal_3" => 3, "normal_4" => 4,
+ "normal_5" => 5, "normal_6" => 6, "below_normal_7" => 7, "below_normal_8" => 8, "lowest" => 9, "idle" => 10 }.freeze
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::WindowsTask.new(new_resource.name)
+ task = ::Win32::TaskScheduler.new(new_resource.task_name, nil, "\\", false)
+ @current_resource.exists = task.exists?(new_resource.task_name)
+ if @current_resource.exists
+ task.get_task(new_resource.task_name)
+ @current_resource.task = task
+ pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
+ @current_resource.task_name(pathed_task_name)
+ end
+ @current_resource
+ end
+
+ # separated command arguments from :command property
+ def set_command_and_arguments
+ cmd, *args = Chef::Util::PathHelper.split_args(new_resource.command)
+ new_resource.command = cmd
+ new_resource.command_arguments = args.join(" ")
+ end
+
+ def set_start_day_and_time
+ new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day
+ new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time
+ end
+
+ def update_task(task)
+ converge_by("#{new_resource} task updated") do
+ task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
+ task.application_name = new_resource.command if new_resource.command
+ task.parameters = new_resource.command_arguments if new_resource.command_arguments
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.trigger = trigger unless new_resource.frequency == :none
+ task.configure_settings(config_settings)
+ task.creator = new_resource.user
+ task.description = new_resource.description unless new_resource.description.nil?
+ task.configure_principals(principal_settings)
+ end
+ end
+
+ def trigger
+ start_month, start_day, start_year = new_resource.start_day.to_s.split("/")
+ start_hour, start_minute = new_resource.start_time.to_s.split(":")
+ # TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error.
+ trigger_hash = {
+ start_year: start_year.to_i,
+ start_month: start_month.to_i,
+ start_day: start_day.to_i,
+ start_hour: start_hour.to_i,
+ start_minute: start_minute.to_i,
+ end_month: 0,
+ end_day: 0,
+ end_year: 0,
+ trigger_type: trigger_type,
+ type: type,
+ random_minutes_interval: new_resource.random_delay,
+ }
+
+ if new_resource.frequency == :minute
+ trigger_hash[:minutes_interval] = new_resource.frequency_modifier
+ end
+
+ if new_resource.frequency == :hourly
+ minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i)
+ trigger_hash[:minutes_interval] = minutes
+ end
+
+ if new_resource.minutes_interval
+ trigger_hash[:minutes_interval] = new_resource.minutes_interval
+ end
+
+ if new_resource.minutes_duration
+ trigger_hash[:minutes_duration] = new_resource.minutes_duration
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier)
+ trigger_hash[:run_on_last_week_of_month] = true
+ else
+ trigger_hash[:run_on_last_week_of_month] = false
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day)
+ trigger_hash[:run_on_last_day_of_month] = true
+ else
+ trigger_hash[:run_on_last_day_of_month] = false
+ end
+ trigger_hash
+ end
+
+ def frequency_modifier_contains_last_week?(frequency_modifier)
+ frequency_modifier = frequency_modifier.to_s.split(",")
+ frequency_modifier.map! { |value| value.strip.upcase }
+ frequency_modifier.include?("LAST")
+ end
+
+ def day_includes_last_or_lastday?(day)
+ day = day.to_s.split(",")
+ day.map! { |value| value.strip.upcase }
+ day.include?("LAST") || day.include?("LASTDAY")
+ end
+
+ def convert_hours_in_minutes(hours)
+ hours.to_i * 60 if hours
+ end
+
+ # TODO : Try to optimize this method
+ # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency
+ # is not getting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task
+ def task_needs_update?(task)
+ flag = false
+ if new_resource.frequency == :none
+ flag = (task.author != new_resource.user ||
+ task.application_name != new_resource.command ||
+ description_needs_update?(task) ||
+ task.parameters != new_resource.command_arguments.to_s ||
+ task.principals[:run_level] != run_level ||
+ task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
+ task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
+ task.settings[:start_when_available] != new_resource.start_when_available)
+ else
+ current_task_trigger = task.trigger(0)
+ new_task_trigger = trigger
+ flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle
+ flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil?
+
+ # if trigger not found updating the task to add the trigger
+ if current_task_trigger.nil?
+ flag = true
+ else
+ flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true ||
+ start_time_updated?(current_task_trigger, new_task_trigger) == true ||
+ current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] ||
+ current_task_trigger[:type] != new_task_trigger[:type] ||
+ current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
+ current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
+ task.author.to_s.casecmp(new_resource.user.to_s) != 0 ||
+ task.application_name != new_resource.command ||
+ description_needs_update?(task) ||
+ task.parameters != new_resource.command_arguments.to_s ||
+ task.working_directory != new_resource.cwd.to_s ||
+ task.principals[:logon_type] != logon_type ||
+ task.principals[:run_level] != run_level ||
+ PRIORITY[task.priority] != new_resource.priority ||
+ task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
+ task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
+ task.settings[:start_when_available] != new_resource.start_when_available
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDATE
+ flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month]
+ end
+
+ if trigger_type == ::Win32::TaskScheduler::MONTHLYDOW
+ flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month]
+ end
+ end
+ end
+ flag
+ end
+
+ def start_day_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] ||
+ current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] ||
+ current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) )
+ end
+
+ def start_time_updated?(current_task_trigger, new_task_trigger)
+ ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] ||
+ current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) )
+ end
+
+ def trigger_type
+ case new_resource.frequency
+ when :once, :minute, :hourly
+ ::Win32::TaskScheduler::ONCE
+ when :daily
+ ::Win32::TaskScheduler::DAILY
+ when :weekly
+ ::Win32::TaskScheduler::WEEKLY
+ when :monthly
+ # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow
+ # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx
+ new_resource.frequency_modifier.to_i.between?(1, 12) ? ::Win32::TaskScheduler::MONTHLYDATE : ::Win32::TaskScheduler::MONTHLYDOW
+ when :on_idle
+ ::Win32::TaskScheduler::ON_IDLE
+ when :onstart
+ ::Win32::TaskScheduler::AT_SYSTEMSTART
+ when :on_logon
+ ::Win32::TaskScheduler::AT_LOGON
+ else
+ raise ArgumentError, "Please set frequency"
+ end
+ end
+
+ def type
+ case trigger_type
+ when ::Win32::TaskScheduler::ONCE
+ { once: nil }
+ when ::Win32::TaskScheduler::DAILY
+ { days_interval: new_resource.frequency_modifier.to_i }
+ when ::Win32::TaskScheduler::WEEKLY
+ { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i }
+ when ::Win32::TaskScheduler::MONTHLYDATE
+ { months: months_of_year.to_i, days: days_of_month.to_i }
+ when ::Win32::TaskScheduler::MONTHLYDOW
+ { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i }
+ when ::Win32::TaskScheduler::ON_IDLE
+ # TODO: handle option for this trigger
+ when ::Win32::TaskScheduler::AT_LOGON
+ # TODO: handle option for this trigger
+ when ::Win32::TaskScheduler::AT_SYSTEMSTART
+ # TODO: handle option for this trigger
+ end
+ end
+
+ # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.
+ def weeks_of_month
+ weeks_of_month = []
+ if new_resource.frequency_modifier
+ weeks = new_resource.frequency_modifier.split(",")
+ weeks.map! { |week| week.to_s.strip.upcase }
+ weeks.delete("LAST") if weeks.include?("LAST")
+ weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH)
+ end
+ weeks_of_month
+ end
+
+ # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter.
+ def days_of_month
+ days_of_month = []
+ if new_resource.day
+ days = new_resource.day.to_s.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ days.delete("LAST") if days.include?("LAST")
+ days.delete("LASTDAY") if days.include?("LASTDAY")
+ if days - (1..31).to_a
+ days.each do |day|
+ days_of_month << DAYS_OF_MONTH[day.to_i]
+ end
+ days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0]
+ end
+ else
+ days_of_month = DAYS_OF_MONTH[1]
+ end
+ days_of_month
+ end
+
+ def days_of_week
+ if new_resource.day
+ # this line of code is just to support backward compatibility of wild card *
+ new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly
+ days = new_resource.day.to_s.split(",")
+ days.map! { |day| day.to_s.strip.upcase }
+ weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK)
+ else
+ # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day
+ if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?)
+ weeks_days = current_resource.task.trigger(0)[:type][:days_of_week]
+ else
+ day = get_day(new_resource.start_day).to_sym if new_resource.start_day
+ DAYS_OF_WEEK[day]
+ end
+ end
+ end
+
+ def months_of_year
+ months_of_year = []
+ if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months)
+ new_resource.months = set_months(new_resource.frequency_modifier.to_i)
+ end
+
+ if new_resource.months
+ # this line of code is just to support backward compatibility of wild card *
+ new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly
+ months = new_resource.months.split(",")
+ months.map! { |month| month.to_s.strip.upcase }
+ months_of_year = get_binary_values_from_constants(months, MONTHS)
+ else
+ MONTHS.each do |key, value|
+ months_of_year << MONTHS[key]
+ end
+ months_of_year = months_of_year.inject(:|)
+ end
+ months_of_year
+ end
+
+ # This values are set for frequency_modifier set as 1-12
+ # This is to give backward compatibility validated this values with earlier code and running schtask.exe
+ # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
+ def set_months(frequency_modifier)
+ case frequency_modifier
+ when 1
+ "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec"
+ when 2
+ "feb, apr, jun, aug, oct, dec"
+ when 3
+ "mar, jun, sep, dec"
+ when 4
+ "apr, aug, dec"
+ when 5
+ "may, oct"
+ when 6
+ "jun, dec"
+ when 7
+ "jul"
+ when 8
+ "aug"
+ when 9
+ "sep"
+ when 10
+ "oct"
+ when 11
+ "nov"
+ when 12
+ "dec"
+ end
+ end
+
+ def get_binary_values_from_constants(array_values, constant)
+ data = []
+ array_values.each do |value|
+ value = value.to_sym
+ data << constant[value]
+ end
+ data.size > 1 ? data.inject(:|) : data[0]
+ end
+
+ def run_level
+ case new_resource.run_level
+ when :highest
+ ::Win32::TaskScheduler::TASK_RUNLEVEL_HIGHEST
+ when :limited
+ ::Win32::TaskScheduler::TASK_RUNLEVEL_LUA
+ end
+ end
+
+ # TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 format
+ def config_settings
+ settings = {
+ execution_time_limit: new_resource.execution_time_limit,
+ enabled: true,
+ }
+ settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time
+ settings[:run_only_if_idle] = true if new_resource.idle_time
+ settings[:priority] = new_resource.priority
+ settings[:disallow_start_if_on_batteries] = new_resource.disallow_start_if_on_batteries
+ settings[:stop_if_going_on_batteries] = new_resource.stop_if_going_on_batteries
+ settings[:start_when_available] = new_resource.start_when_available
+ settings
+ end
+
+ def principal_settings
+ settings = {}
+ settings[:run_level] = run_level
+ settings[:logon_type] = logon_type
+ settings
+ end
+
+ def description_needs_update?(task)
+ task.description != new_resource.description unless new_resource.description.nil?
+ end
+
+ def logon_type
+ # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx
+ # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparison.
+ user_id = new_resource.user.to_s
+ password = new_resource.password.to_s
+ if Chef::ReservedNames::Win32::Security::SID.service_account_user?(user_id)
+ ::Win32::TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT
+ elsif Chef::ReservedNames::Win32::Security::SID.group_user?(user_id)
+ ::Win32::TaskScheduler::TASK_LOGON_GROUP
+ elsif !user_id.empty? && !password.empty?
+ if new_resource.interactive_enabled
+ ::Win32::TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
+ else
+ ::Win32::TaskScheduler::TASK_LOGON_PASSWORD
+ end
+ else
+ ::Win32::TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
+ end
+ end
+
+ # This method checks if task and command properties exist since those two are mandatory properties to create a schedules task.
+ def basic_validation
+ validate = []
+ validate << "Command" if new_resource.command.nil? || new_resource.command.empty?
+ validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty?
+ return true if validate.empty?
+
+ raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(", ")}' option cannot be empty"
+ end
+
+ # rubocop:disable Style/StringLiteralsInInterpolation
+ def run_schtasks(task_action, options = {})
+ cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
+ options.each_key do |option|
+ unless option == "TR"
+ cmd += "/#{option} "
+ cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ""
+ end
+ end
+ # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value
+ if options["TR"]
+ cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE"
+ end
+ logger.trace("running: ")
+ logger.trace(" #{cmd}")
+ shell_out!(cmd, returns: [0])
+ end
+ # rubocop:enable Style/StringLiteralsInInterpolation
+
+ def get_day(date)
+ Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
+ end
+ end
+
+ action :create do
+ set_command_and_arguments if new_resource.command
+
+ if current_resource.exists
+ logger.trace "#{new_resource} task exist."
+ unless (task_needs_update?(current_resource.task)) || (new_resource.force)
+ logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do"
+ return
+ end
+
+ # if start_day and start_time is not set by user current date and time will be set while updating any property
+ set_start_day_and_time unless new_resource.frequency == :none
+ update_task(current_resource.task)
+ else
+ basic_validation
+ set_start_day_and_time
+ converge_by("#{new_resource} task created") do
+ task = ::Win32::TaskScheduler.new
+ if new_resource.frequency == :none
+ task.new_work_item(new_resource.task_name, {}, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
+ task.activate(new_resource.task_name)
+ else
+ task.new_work_item(new_resource.task_name, trigger, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
+ end
+ task.application_name = new_resource.command
+ task.parameters = new_resource.command_arguments if new_resource.command_arguments
+ task.working_directory = new_resource.cwd if new_resource.cwd
+ task.configure_settings(config_settings)
+ task.configure_principals(principal_settings)
+ task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
+ task.creator = new_resource.user
+ task.description = new_resource.description unless new_resource.description.nil?
+ task.activate(new_resource.task_name)
+ end
+ end
+ end
+
+ action :run do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "running"
+ logger.info "#{new_resource} task is currently running, skipping run"
+ else
+ converge_by("run scheduled task #{new_resource}") do
+ current_resource.task.run
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :delete do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ converge_by("delete scheduled task #{new_resource}") do
+ ts = ::Win32::TaskScheduler.new
+ ts.delete(current_resource.task_name)
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :end do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status != "running"
+ logger.trace "#{new_resource} is not running - nothing to do"
+ else
+ converge_by("#{new_resource} task ended") do
+ current_resource.task.stop
+ end
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action :enable do
+ if current_resource.exists
+ logger.trace "#{new_resource} task exists"
+ if current_resource.task.status == "not scheduled"
+ converge_by("#{new_resource} task enabled") do
+ # TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here
+ run_schtasks "CHANGE", "ENABLE" => ""
+ end
+ else
+ logger.trace "#{new_resource} already enabled - nothing to do"
+ end
+ else
+ logger.fatal "#{new_resource} task does not exist - nothing to do"
+ raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
+ end
+ end
+
+ action :disable do
+ if current_resource.exists
+ logger.info "#{new_resource} task exists"
+ if %w{ready running}.include?(current_resource.task.status)
+ converge_by("#{new_resource} task disabled") do
+ # TODO: in win32-taskscheduler there is no method which disables the task so currently calling disable with schtasks.exe
+ run_schtasks "CHANGE", "DISABLE" => ""
+ end
+ else
+ logger.warn "#{new_resource} already disabled - nothing to do"
+ end
+ else
+ logger.warn "#{new_resource} task does not exist - nothing to do"
+ end
+ end
+
+ action_class do
+ alias_method :action_change, :action_create
+ end
end
end
end
diff --git a/lib/chef/resource/windows_workgroup.rb b/lib/chef/resource/windows_workgroup.rb
index aa2af646dc..e506b51257 100644
--- a/lib/chef/resource/windows_workgroup.rb
+++ b/lib/chef/resource/windows_workgroup.rb
@@ -21,6 +21,8 @@ require "chef-utils/dist" unless defined?(ChefUtils::Dist)
class Chef
class Resource
class WindowsWorkgroup < Chef::Resource
+ unified_mode true
+
provides :windows_workgroup
description "Use the **windows_workgroup** resource to join or change the workgroup of a Windows host."
diff --git a/spec/functional/resource/windows_task_spec.rb b/spec/functional/resource/windows_task_spec.rb
index 97ab66c7f5..3affa625fd 100644
--- a/spec/functional/resource/windows_task_spec.rb
+++ b/spec/functional/resource/windows_task_spec.rb
@@ -17,15 +17,14 @@
#
require "spec_helper"
-require "chef/provider/windows_task"
require "chef-utils/dist"
describe Chef::Resource::WindowsTask, :windows_only do
# resource.task.application_name will default to task_name unless resource.command is set
let(:task_name) { "chef-client-functional-test" }
- let(:new_resource) { Chef::Resource::WindowsTask.new(task_name) }
+ let(:new_resource) { Chef::Resource::WindowsTask.new(task_name, run_context) }
let(:windows_task_provider) do
- Chef::Provider::WindowsTask.new(new_resource, run_context)
+ new_resource.provider_for_action(:create)
end
let(:run_context) do
diff --git a/spec/unit/provider/windows_env_spec.rb b/spec/unit/provider/windows_env_spec.rb
index 5d14128230..67c05bc095 100644
--- a/spec/unit/provider/windows_env_spec.rb
+++ b/spec/unit/provider/windows_env_spec.rb
@@ -18,21 +18,17 @@
require "spec_helper"
-describe Chef::Provider::WindowsEnv, :windows_only do
+describe "windows_env provider", :windows_only do
let(:run_context) do
Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
end
before do
- @new_resource = Chef::Resource::WindowsEnv.new("FOO")
+ @new_resource = Chef::Resource::WindowsEnv.new("FOO", run_context)
@new_resource.value("bar")
@new_resource.user("<System>")
- @provider = Chef::Provider::WindowsEnv.new(@new_resource, run_context)
- end
-
- it "assumes the key_name exists by default" do
- expect(@provider.key_exists).to be_truthy
+ @provider = @new_resource.provider_for_action(:create)
end
describe "when loading the current status" do
@@ -41,7 +37,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
# Chef::Resource::Env.stub(:new).and_return(@current_resource)
@provider.current_resource = @current_resource
allow(@provider).to receive(:env_value).with("FOO").and_return("bar")
- allow(@provider).to receive(:env_key_exists).and_return(true)
+ allow(@provider).to receive(:key_exists?).and_return(true)
end
it "should create a current resource with the same name as the new resource" do
@@ -59,18 +55,6 @@ describe Chef::Provider::WindowsEnv, :windows_only do
expect(@provider.current_resource.key_name).to eq("FOO")
end
- it "should check if the key_name and user exist" do
- expect(@provider).to receive(:env_key_exists).with("FOO").and_return(true)
- @provider.load_current_resource
- expect(@provider.key_exists).to be_truthy
- end
-
- it "should flip the value of exists if the key does not exist" do
- expect(@provider).to receive(:env_key_exists).with("FOO").and_return(false)
- @provider.load_current_resource
- expect(@provider.key_exists).to be_falsey
- end
-
it "should return the current resource" do
expect(@provider.load_current_resource).to be_a_kind_of(Chef::Resource::WindowsEnv)
end
@@ -78,7 +62,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
describe "action_create" do
before do
- @provider.key_exists = false
+ allow(@provider).to receive(:key_exists?).and_return(false)
allow(@provider).to receive(:create_env).and_return(true)
allow(@provider).to receive(:modify_env).and_return(true)
end
@@ -94,20 +78,20 @@ describe Chef::Provider::WindowsEnv, :windows_only do
end
it "should check to see if the values are the same if the key exists" do
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
expect(@provider).to receive(:requires_modify_or_create?).and_return(false)
@provider.action_create
end
it "should call modify_env if the key exists with provided user and values are not equal" do
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
allow(@provider).to receive(:requires_modify_or_create?).and_return(true)
expect(@provider).to receive(:modify_env).and_return(true)
@provider.action_create
end
it "should set the new_resources updated flag when it updates an existing value" do
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
allow(@provider).to receive(:requires_modify_or_create?).and_return(true)
allow(@provider).to receive(:modify_env).and_return(true)
@provider.action_create
@@ -118,7 +102,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
describe "action_delete" do
before(:each) do
@provider.current_resource = @current_resource
- @provider.key_exists = false
+ allow(@provider).to receive(:key_exists?).and_return(false)
allow(@provider).to receive(:delete_element).and_return(false)
allow(@provider).to receive(:delete_env).and_return(true)
end
@@ -134,13 +118,13 @@ describe Chef::Provider::WindowsEnv, :windows_only do
end
it "should call delete_env if the key exists" do
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
expect(@provider).to receive(:delete_env)
@provider.action_delete
end
it "should set the new_resources updated flag to true if the key is deleted" do
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
@provider.action_delete
expect(@new_resource).to be_updated
end
@@ -149,7 +133,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
describe "action_modify" do
before(:each) do
@provider.current_resource = @current_resource
- @provider.key_exists = true
+ allow(@provider).to receive(:key_exists?).and_return(true)
allow(@provider).to receive(:modify_env).and_return(true)
end
@@ -179,7 +163,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
end
it "should raise a Chef::Exceptions::WindowsEnv if the key doesn't exist" do
- @provider.key_exists = false
+ allow(@provider).to receive(:key_exists?).and_return(false)
expect { @provider.action_modify }.to raise_error(Chef::Exceptions::WindowsEnv)
end
end
@@ -324,12 +308,12 @@ describe Chef::Provider::WindowsEnv, :windows_only do
context "when environment variable is not PATH" do
let(:new_resource) do
- new_resource = Chef::Resource::WindowsEnv.new("CHEF_WINDOWS_ENV_TEST")
+ new_resource = Chef::Resource::WindowsEnv.new("CHEF_WINDOWS_ENV_TEST", run_context)
new_resource.value("foo")
new_resource
end
let(:provider) do
- provider = Chef::Provider::WindowsEnv.new(new_resource, run_context)
+ provider = new_resource.provider_for_action(:create)
allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object)
provider
end
@@ -337,7 +321,7 @@ describe Chef::Provider::WindowsEnv, :windows_only do
describe "action_create" do
before do
ENV.delete("CHEF_WINDOWS_ENV_TEST")
- provider.key_exists = false
+ allow(@provider).to receive(:key_exists?).and_return(false)
end
it "should update the ruby ENV object when it creates the key" do
@@ -376,12 +360,12 @@ describe Chef::Provider::WindowsEnv, :windows_only do
let(:system_root) { "%SystemRoot%" }
let(:system_root_value) { 'D:\Windows' }
let(:new_resource) do
- new_resource = Chef::Resource::WindowsEnv.new("PATH")
+ new_resource = Chef::Resource::WindowsEnv.new("PATH", run_context)
new_resource.value(system_root)
new_resource
end
let(:provider) do
- provider = Chef::Provider::WindowsEnv.new(new_resource, run_context)
+ provider = new_resource.provider_for_action(:create)
allow(provider).to receive(:env_obj).and_return(double("null object").as_null_object)
provider
end
diff --git a/spec/unit/provider/windows_path_spec.rb b/spec/unit/provider/windows_path_spec.rb
index 6036772af0..94c4b671bc 100644
--- a/spec/unit/provider/windows_path_spec.rb
+++ b/spec/unit/provider/windows_path_spec.rb
@@ -18,7 +18,7 @@
require "spec_helper"
-describe Chef::Provider::WindowsPath, :windows_only do
+describe "windows_path provider", :windows_only do
before(:all) do
@old_path = ENV["PATH"].dup
end
@@ -27,14 +27,9 @@ describe Chef::Provider::WindowsPath, :windows_only do
ENV["PATH"] = @old_path
end
- let(:new_resource) { Chef::Resource::WindowsPath.new("some_path") }
-
- let(:provider) do
- node = Chef::Node.new
- events = Chef::EventDispatch::Dispatcher.new
- run_context = Chef::RunContext.new(node, {}, events)
- Chef::Provider::WindowsPath.new(new_resource, run_context)
- end
+ let(:run_context) { Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new) }
+ let(:new_resource) { Chef::Resource::WindowsPath.new("some_path", run_context) }
+ let(:provider) { new_resource.provider_for_action(:add) }
describe "#load_current_resource" do
it "returns a current_resource" do
diff --git a/spec/unit/provider/windows_task_spec.rb b/spec/unit/provider/windows_task_spec.rb
index 62a3885faa..dce898154b 100644
--- a/spec/unit/provider/windows_task_spec.rb
+++ b/spec/unit/provider/windows_task_spec.rb
@@ -18,15 +18,16 @@
require "spec_helper"
-describe Chef::Provider::WindowsTask, :windows_only do
- let(:new_resource) { Chef::Resource::WindowsTask.new("sample_task") }
+describe "windows_task provider", :windows_only do
+ let(:new_resource) { Chef::Resource::WindowsTask.new("sample_task", run_context) }
let(:current_resource) { Chef::Resource::WindowsTask.new }
+ let(:run_context) do
+ Chef::RunContext.new(Chef::Node.new, {}, Chef::EventDispatch::Dispatcher.new)
+ end
+
let(:provider) do
- node = Chef::Node.new
- events = Chef::EventDispatch::Dispatcher.new
- run_context = Chef::RunContext.new(node, {}, events)
- Chef::Provider::WindowsTask.new(new_resource, run_context)
+ new_resource.provider_for_action(:create)
end
describe "#load_current_resource" do
diff --git a/spec/unit/provider_resolver_spec.rb b/spec/unit/provider_resolver_spec.rb
index 20c07a5c45..6d770c1935 100644
--- a/spec/unit/provider_resolver_spec.rb
+++ b/spec/unit/provider_resolver_spec.rb
@@ -735,7 +735,7 @@ describe Chef::ProviderResolver do
"windows" => {
batch: [ Chef::Resource::Batch, Chef::Provider::Batch ],
dsc_script: [ Chef::Resource::DscScript, Chef::Provider::DscScript ],
- windows_env: [ Chef::Resource::WindowsEnv, Chef::Provider::WindowsEnv ],
+ windows_env: [ Chef::Resource::WindowsEnv ],
group: [ Chef::Resource::Group, Chef::Provider::Group::Windows ],
mount: [ Chef::Resource::Mount, Chef::Provider::Mount::Windows ],
package: [ Chef::Resource::WindowsPackage, Chef::Provider::Package::Windows ],