diff --git a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb index 82d795a1..16e78a88 100644 --- a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb +++ b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb @@ -1,5 +1,5 @@ require 'puppet/parameter' -require_relative '../../../puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task' +require_relative '../../../puppet_x/puppetlabs/scheduled_task/taskscheduler2_task' Puppet::Type.type(:scheduled_task).provide(:taskscheduler_api2) do @@ -13,7 +13,7 @@ has_feature :compatibility def self.instances - PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.tasks.collect do |job_file| + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') new( :provider => :taskscheduler_api2, @@ -23,13 +23,13 @@ def self.instances end def exists? - PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.exists? resource[:name] + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task.new.exists? resource[:name] end def task return @task if @task - @task ||= PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new + @task ||= PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task.new @task.activate(resource[:name] + '.job') if exists? @task @@ -41,7 +41,7 @@ def clear_task end def enabled - task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false + task.flags & PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED == 0 ? :true : :false end def command @@ -118,9 +118,9 @@ def working_dir=(value) def enabled=(value) if value == :true - task.flags = task.flags & ~Win32::TaskScheduler::DISABLED + task.flags = task.flags & ~PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED else - task.flags = task.flags | Win32::TaskScheduler::DISABLED + task.flags = task.flags | PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED end end @@ -176,19 +176,19 @@ def user=(value) def create clear_task - @task = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new( + @task = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task.new( resource[:name], PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.default_trigger_for('once') ) self.command = resource[:command] - [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| + [:arguments, :working_dir, :enabled, :trigger, :user, :compatibility].each do |prop| send("#{prop}=", resource[prop]) if resource[prop] end end def destroy - PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.delete(resource[:name] + '.job') + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task.new.delete(resource[:name] + '.job') end def flush diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb index 3c2f96b4..b411d860 100644 --- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb @@ -1,20 +1,17 @@ require 'puppet/parameter' +require_relative '../../../puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task' -if Puppet.features.microsoft_windows? - require 'puppet/util/windows/taskscheduler' -end Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do - desc %q{This provider manages scheduled tasks on Windows.} + desc "This provider manages scheduled tasks on Windows. + This is a technical preview using the newer V2 API interface but + still editing V1 compatbile scheduled tasks." confine :operatingsystem => :windows - MINUTES_IN_DAY = 1440 - def self.instances - Win32::TaskScheduler.new.tasks.collect do |job_file| + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.tasks.collect do |job_file| job_title = File.basename(job_file, '.job') - new( :provider => :win32_taskscheduler, :name => job_title @@ -23,13 +20,13 @@ def self.instances end def exists? - Win32::TaskScheduler.new.exists? resource[:name] + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.exists? resource[:name] end def task return @task if @task - @task ||= Win32::TaskScheduler.new + @task ||= PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new @task.activate(resource[:name] + '.job') if exists? @task @@ -41,7 +38,7 @@ def clear_task end def enabled - task.flags & Win32::TaskScheduler::DISABLED == 0 ? :true : :false + task.flags & PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED == 0 ? :true : :false end def command @@ -63,52 +60,13 @@ def user end def trigger - return @triggers if @triggers - - @triggers = [] - task.trigger_count.times do |i| - trigger = begin - task.trigger(i) - rescue Win32::TaskScheduler::Error - # Win32::TaskScheduler can't handle all of the - # trigger types Windows uses, so we need to skip the - # unhandled types to prevent "puppet resource" from - # blowing up. - nil - end - next unless trigger and scheduler_trigger_types.include?(trigger['trigger_type']) - puppet_trigger = {} - case trigger['trigger_type'] - when Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY - puppet_trigger['schedule'] = 'daily' - puppet_trigger['every'] = trigger['type']['days_interval'].to_s - when Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY - puppet_trigger['schedule'] = 'weekly' - puppet_trigger['every'] = trigger['type']['weeks_interval'].to_s - puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) - when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE - puppet_trigger['schedule'] = 'monthly' - puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) - puppet_trigger['on'] = days_from_bitfield(trigger['type']['days']) - when Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW - puppet_trigger['schedule'] = 'monthly' - puppet_trigger['months'] = months_from_bitfield(trigger['type']['months']) - puppet_trigger['which_occurrence'] = occurrence_constant_to_name(trigger['type']['weeks']) - puppet_trigger['day_of_week'] = days_of_week_from_bitfield(trigger['type']['days_of_week']) - when Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE - puppet_trigger['schedule'] = 'once' - end - puppet_trigger['start_date'] = self.class.normalized_date("#{trigger['start_year']}-#{trigger['start_month']}-#{trigger['start_day']}") - puppet_trigger['start_time'] = self.class.normalized_time("#{trigger['start_hour']}:#{trigger['start_minute']}") - puppet_trigger['enabled'] = trigger['flags'] & Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED == 0 - puppet_trigger['minutes_interval'] = trigger['minutes_interval'] ||= 0 - puppet_trigger['minutes_duration'] = trigger['minutes_duration'] ||= 0 - puppet_trigger['index'] = i - - @triggers << puppet_trigger - end - - @triggers + @triggers ||= task.trigger_count.times.map do |i| + v1trigger = task.trigger(i) + # nil trigger definitions are unsupported ITrigger types + next if v1trigger.nil? + index = { 'index' => i } + PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.to_manifest_hash(v1trigger).merge(index) + end.compact end def user_insync?(current, should) @@ -153,9 +111,9 @@ def working_dir=(value) def enabled=(value) if value == :true - task.flags = task.flags & ~Win32::TaskScheduler::DISABLED + task.flags = task.flags & ~PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED else - task.flags = task.flags | Win32::TaskScheduler::DISABLED + task.flags = task.flags | PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED end end @@ -188,11 +146,8 @@ def trigger=(value) end needed_triggers.each do |trigger_hash| - # Even though this is an assignment, the API for - # Win32::TaskScheduler ends up appending this trigger to the - # list of triggers for the task, while #add_trigger is only able - # to replace existing triggers. *shrug* - task.trigger = translate_hash_to_trigger(trigger_hash) + v1trigger = PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(trigger_hash) + task.append_trigger(v1trigger) end end @@ -210,7 +165,10 @@ def user=(value) def create clear_task - @task = Win32::TaskScheduler.new(resource[:name], dummy_time_trigger) + @task = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new( + resource[:name], + PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.default_trigger_for('once') + ) self.command = resource[:command] [:arguments, :working_dir, :enabled, :trigger, :user].each do |prop| @@ -219,7 +177,7 @@ def create end def destroy - Win32::TaskScheduler.new.delete(resource[:name] + '.job') + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task.new.delete(resource[:name] + '.job') end def flush @@ -240,7 +198,7 @@ def flush def triggers_same?(current_trigger, desired_trigger) return false unless current_trigger['schedule'] == desired_trigger['schedule'] return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] - return false if translate_hash_to_trigger(desired_trigger)['trigger_type'] != translate_hash_to_trigger(current_trigger)['trigger_type'] + return false if PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(desired_trigger)['trigger_type'] != PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(current_trigger)['trigger_type'] desired = desired_trigger.dup desired['start_date'] ||= current_trigger['start_date'] if current_trigger.has_key?('start_date') @@ -249,343 +207,19 @@ def triggers_same?(current_trigger, desired_trigger) desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') desired['day_of_week'] ||= current_trigger['day_of_week'] if current_trigger.has_key?('day_of_week') - translate_hash_to_trigger(current_trigger) == translate_hash_to_trigger(desired) - end - - def self.normalized_date(date_string) - date = Date.parse("#{date_string}") - "#{date.year}-#{date.month}-#{date.day}" - end - - def self.normalized_time(time_string) - Time.parse("#{time_string}").strftime('%H:%M') - end - - def dummy_time_trigger - now = Time.now - { - 'flags' => 0, - 'end_day' => 0, - 'end_year' => 0, - 'minutes_interval' => 0, - 'end_month' => 0, - 'minutes_duration' => 0, - 'start_year' => now.year, - 'start_month' => now.month, - 'start_day' => now.day, - 'start_hour' => now.hour, - 'start_minute' => now.min, - 'trigger_type' => Win32::TaskScheduler::ONCE, - } - end - - def translate_hash_to_trigger(puppet_trigger) - trigger = dummy_time_trigger - - if puppet_trigger['enabled'] == false - trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED - else - trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED - end - - extra_keys = puppet_trigger.keys.sort - ['index', 'enabled', 'schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week', 'minutes_interval', 'minutes_duration'] - self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? - self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] - - case puppet_trigger['schedule'] - when 'daily' - trigger['trigger_type'] = Win32::TaskScheduler::DAILY - trigger['type'] = { - 'days_interval' => Integer(puppet_trigger['every'] || 1) - } - when 'weekly' - trigger['trigger_type'] = Win32::TaskScheduler::WEEKLY - trigger['type'] = { - 'weeks_interval' => Integer(puppet_trigger['every'] || 1) - } - - trigger['type']['days_of_week'] = if puppet_trigger['day_of_week'] - bitfield_from_days_of_week(puppet_trigger['day_of_week']) - else - scheduler_days_of_week.inject(0) {|day_flags,day| day_flags |= day} - end - when 'monthly' - trigger['type'] = { - 'months' => bitfield_from_months(puppet_trigger['months'] || (1..12).to_a), - } - - if puppet_trigger.keys.include?('on') - if puppet_trigger.has_key?('day_of_week') or puppet_trigger.has_key?('which_occurrence') - self.fail "Neither 'day_of_week' nor 'which_occurrence' can be specified when creating a monthly date-based trigger" - end - - trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDATE - trigger['type']['days'] = bitfield_from_days(puppet_trigger['on']) - elsif puppet_trigger.keys.include?('which_occurrence') or puppet_trigger.keys.include?('day_of_week') - self.fail 'which_occurrence cannot be specified as an array' if puppet_trigger['which_occurrence'].is_a?(Array) - %w{day_of_week which_occurrence}.each do |field| - self.fail "#{field} must be specified when creating a monthly day-of-week based trigger" unless puppet_trigger.has_key?(field) - end - - trigger['trigger_type'] = Win32::TaskScheduler::MONTHLYDOW - trigger['type']['weeks'] = occurrence_name_to_constant(puppet_trigger['which_occurrence']) - trigger['type']['days_of_week'] = bitfield_from_days_of_week(puppet_trigger['day_of_week']) - else - self.fail "Don't know how to create a 'monthly' schedule with the options: #{puppet_trigger.keys.sort.join(', ')}" - end - when 'once' - self.fail "Must specify 'start_date' when defining a one-time trigger" unless puppet_trigger['start_date'] - - trigger['trigger_type'] = Win32::TaskScheduler::ONCE - else - self.fail "Unknown schedule type: #{puppet_trigger["schedule"].inspect}" - end - - integer_interval = -1 - if puppet_trigger['minutes_interval'] - integer_interval = Integer(puppet_trigger['minutes_interval']) - self.fail 'minutes_interval must be an integer greater or equal to 0' if integer_interval < 0 - trigger['minutes_interval'] = integer_interval - end - - integer_duration = -1 - if puppet_trigger['minutes_duration'] - integer_duration = Integer(puppet_trigger['minutes_duration']) - self.fail 'minutes_duration must be an integer greater than minutes_interval and equal to or greater than 0' if integer_duration <= integer_interval && integer_duration != 0 - trigger['minutes_duration'] = integer_duration - end - - if integer_interval > 0 && integer_duration == -1 - integer_duration = MINUTES_IN_DAY - trigger['minutes_duration'] = MINUTES_IN_DAY - end - - if integer_interval >= integer_duration && integer_interval > 0 - self.fail 'minutes_interval cannot be set without minutes_duration also being set to a number greater than 0' - end - - if start_date = puppet_trigger['start_date'] - start_date = Date.parse(start_date) - self.fail "start_date must be on or after 1753-01-01" unless start_date >= Date.new(1753, 1, 1) - - trigger['start_year'] = start_date.year - trigger['start_month'] = start_date.month - trigger['start_day'] = start_date.day - end - - start_time = Time.parse(puppet_trigger['start_time']) - trigger['start_hour'] = start_time.hour - trigger['start_minute'] = start_time.min - - trigger + PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(current_trigger) == PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(desired) end def validate_trigger(value) - value = [value] unless value.is_a?(Array) - - value.each do |t| - if t.has_key?('index') - self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." - end - - if t.has_key?('enabled') - self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." + [value].flatten.each do |t| + %w[index enabled].each do |key| + if t.key?(key) + self.fail "'#{key}' is read-only on scheduled_task triggers and should be removed ('#{key}' is usually provided in puppet resource scheduled_task)." + end end - - translate_hash_to_trigger(t) + PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(t) end true end - - private - - def bitfield_from_months(months) - bitfield = 0 - - months = [months] unless months.is_a?(Array) - months.each do |month| - integer_month = Integer(month) rescue nil - self.fail 'Month must be specified as an integer in the range 1-12' unless integer_month == month.to_f and integer_month.between?(1,12) - - bitfield |= scheduler_months[integer_month - 1] - end - - bitfield - end - - def bitfield_from_days(days) - bitfield = 0 - - days = [days] unless days.is_a?(Array) - days.each do |day| - # The special "day" of 'last' is represented by day "number" - # 32. 'last' has the special meaning of "the last day of the - # month", no matter how many days there are in the month. - day = 32 if day == 'last' - - integer_day = Integer(day) - self.fail "Day must be specified as an integer in the range 1-31, or as 'last'" unless integer_day = day.to_f and integer_day.between?(1,32) - - bitfield |= 1 << integer_day - 1 - end - - bitfield - end - - def bitfield_from_days_of_week(days_of_week) - bitfield = 0 - - days_of_week = [days_of_week] unless days_of_week.is_a?(Array) - days_of_week.each do |day_of_week| - bitmask = day_of_week_name_to_constant(day_of_week) - self.fail "Days_of_week value #{day_of_week} is invalid. Expected sun, mon, tue, wed, thu, fri or sat." if bitmask.nil? - bitfield |= bitmask - end - - bitfield - end - - def months_from_bitfield(bitfield) - months = [] - - scheduler_months.each do |month| - if bitfield & month != 0 - months << month_constant_to_number(month) - end - end - - months - end - - def days_from_bitfield(bitfield) - days = [] - - i = 0 - while bitfield > 0 - if bitfield & 1 > 0 - # Day 32 has the special meaning of "the last day of the - # month", no matter how many days there are in the month. - days << (i == 31 ? 'last' : i + 1) - end - - bitfield = bitfield >> 1 - i += 1 - end - - days - end - - def days_of_week_from_bitfield(bitfield) - days_of_week = [] - - scheduler_days_of_week.each do |day_of_week| - if bitfield & day_of_week != 0 - days_of_week << day_of_week_constant_to_name(day_of_week) - end - end - - days_of_week - end - - def scheduler_trigger_types - [ - Win32::TaskScheduler::TASK_TIME_TRIGGER_DAILY, - Win32::TaskScheduler::TASK_TIME_TRIGGER_WEEKLY, - Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDATE, - Win32::TaskScheduler::TASK_TIME_TRIGGER_MONTHLYDOW, - Win32::TaskScheduler::TASK_TIME_TRIGGER_ONCE - ] - end - - def scheduler_days_of_week - [ - Win32::TaskScheduler::SUNDAY, - Win32::TaskScheduler::MONDAY, - Win32::TaskScheduler::TUESDAY, - Win32::TaskScheduler::WEDNESDAY, - Win32::TaskScheduler::THURSDAY, - Win32::TaskScheduler::FRIDAY, - Win32::TaskScheduler::SATURDAY - ] - end - - def scheduler_months - [ - Win32::TaskScheduler::JANUARY, - Win32::TaskScheduler::FEBRUARY, - Win32::TaskScheduler::MARCH, - Win32::TaskScheduler::APRIL, - Win32::TaskScheduler::MAY, - Win32::TaskScheduler::JUNE, - Win32::TaskScheduler::JULY, - Win32::TaskScheduler::AUGUST, - Win32::TaskScheduler::SEPTEMBER, - Win32::TaskScheduler::OCTOBER, - Win32::TaskScheduler::NOVEMBER, - Win32::TaskScheduler::DECEMBER - ] - end - - def scheduler_occurrences - [ - Win32::TaskScheduler::FIRST_WEEK, - Win32::TaskScheduler::SECOND_WEEK, - Win32::TaskScheduler::THIRD_WEEK, - Win32::TaskScheduler::FOURTH_WEEK, - Win32::TaskScheduler::LAST_WEEK - ] - end - - def day_of_week_constant_to_name(constant) - case constant - when Win32::TaskScheduler::SUNDAY; 'sun' - when Win32::TaskScheduler::MONDAY; 'mon' - when Win32::TaskScheduler::TUESDAY; 'tues' - when Win32::TaskScheduler::WEDNESDAY; 'wed' - when Win32::TaskScheduler::THURSDAY; 'thurs' - when Win32::TaskScheduler::FRIDAY; 'fri' - when Win32::TaskScheduler::SATURDAY; 'sat' - end - end - - def day_of_week_name_to_constant(name) - case name - when 'sun'; Win32::TaskScheduler::SUNDAY - when 'mon'; Win32::TaskScheduler::MONDAY - when 'tues'; Win32::TaskScheduler::TUESDAY - when 'wed'; Win32::TaskScheduler::WEDNESDAY - when 'thurs'; Win32::TaskScheduler::THURSDAY - when 'fri'; Win32::TaskScheduler::FRIDAY - when 'sat'; Win32::TaskScheduler::SATURDAY - end - end - - def month_constant_to_number(constant) - month_num = 1 - while constant >> month_num - 1 > 1 - month_num += 1 - end - month_num - end - - def occurrence_constant_to_name(constant) - case constant - when Win32::TaskScheduler::FIRST_WEEK; 'first' - when Win32::TaskScheduler::SECOND_WEEK; 'second' - when Win32::TaskScheduler::THIRD_WEEK; 'third' - when Win32::TaskScheduler::FOURTH_WEEK; 'fourth' - when Win32::TaskScheduler::LAST_WEEK; 'last' - end - end - - def occurrence_name_to_constant(name) - case name - when 'first'; Win32::TaskScheduler::FIRST_WEEK - when 'second'; Win32::TaskScheduler::SECOND_WEEK - when 'third'; Win32::TaskScheduler::THIRD_WEEK - when 'fourth'; Win32::TaskScheduler::FOURTH_WEEK - when 'last'; Win32::TaskScheduler::LAST_WEEK - end - end end diff --git a/lib/puppet/type/scheduled_task.rb b/lib/puppet/type/scheduled_task.rb index d2685aec..4a1309ab 100644 --- a/lib/puppet/type/scheduled_task.rb +++ b/lib/puppet/type/scheduled_task.rb @@ -88,11 +88,19 @@ def insync?(current) end newproperty(:compatibility, :required_features=>:compatibility) do - desc "The compatibility level associated with the task. May currently only - be set to 1 for compatibility with tasks on a Windows XP or Windows Server - 2003 computer." + desc "The compatibility level associated with the task. May currently be set + to 1 for compatibility with tasks on a Windows XP or Windows Server + 2003 computer, 2 for compatibility with tasks on a Windows 2008 computer, + 3 for compatibility with new features for tasks introduced in Windows 7 + and 2008R2, 4 for compatibility with new features for tasks introduced in + Windows 8, Server 2012R2 and Server 2016, or 6 for compatibility with new + features for tasks introduced in Windows 10" newvalue(1) + newvalue(2) + newvalue(3) + newvalue(4) + newvalue(6) defaultto(1) validate do |value| diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb index 922974ff..c426d185 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb @@ -29,6 +29,9 @@ module TaskScheduler2 TASK_COMPATIBILITY_AT = 0 TASK_COMPATIBILITY_V1 = 1 TASK_COMPATIBILITY_V2 = 2 + TASK_COMPATIBILITY_V3 = 3 + TASK_COMPATIBILITY_V4 = 4 + TASK_COMPATIBILITY_V6 = 6 # https://msdn.microsoft.com/en-us/library/windows/desktop/aa382538%28v=vs.85%29.aspx TASK_VALIDATE_ONLY = 0x1 @@ -54,6 +57,21 @@ module TaskScheduler2 RESERVED_FOR_FUTURE_USE = 0 + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa381283(v=vs.85).aspx + TASK_FLAG_INTERACTIVE = 0x1 + TASK_FLAG_DELETE_WHEN_DONE = 0x2 + TASK_FLAG_DISABLED = 0x4 + TASK_FLAG_START_ONLY_IF_IDLE = 0x10 + TASK_FLAG_KILL_ON_IDLE_END = 0x20 + TASK_FLAG_DONT_START_IF_ON_BATTERIES = 0x40 + TASK_FLAG_KILL_IF_GOING_ON_BATTERIES = 0x80 + TASK_FLAG_RUN_ONLY_IF_DOCKED = 0x100 + TASK_FLAG_HIDDEN = 0x200 + TASK_FLAG_RUN_IF_CONNECTED_TO_INTERNET = 0x400 + TASK_FLAG_RESTART_ON_IDLE_RESUME = 0x800 + TASK_FLAG_SYSTEM_REQUIRED = 0x1000 + TASK_FLAG_RUN_ONLY_IF_LOGGED_ON = 0x2000 + def self.folder_path_from_task_path(task_path) path = task_path.rpartition('\\')[0] diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb index fbc3e45f..3b4777c5 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb @@ -28,7 +28,14 @@ def initialize(work_item = nil, trigger = nil) def enum @tasksched.enum_task_names(PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::ROOT_FOLDER, include_child_folders: false, - include_compatibility: [PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V2]).map do |item| + include_compatibility: [ + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V6, + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V4, + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V3, + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V2, + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_AT, + PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V1 + ]).map do |item| @tasksched.task_name_from_task_path(item) end end @@ -80,7 +87,7 @@ def save(file = nil) # # Calling task.set_account_information('SYSTEM', nil) will generally not # work, except for one special case where flags are also set like: - # task.flags = Win32::TaskScheduler::TASK_FLAG_RUN_ONLY_IF_LOGGED_ON + # task.flags = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_RUN_ONLY_IF_LOGGED_ON # # This must be done prior to the 1st save() call for the task to be # properly registered and visible through the MMC snap-in / schtasks.exe @@ -157,8 +164,6 @@ def new_work_item(task_name, task_trigger) @task = nil @task_password = nil - @tasksched.set_compatibility(@definition, PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_COMPATIBILITY_V2) - Trigger::V2.append_v1trigger(@definition, task_trigger) set_account_information('',nil) @@ -212,14 +217,14 @@ def append_trigger(v1trigger) # def flags flags = 0 - flags = flags | Win32::TaskScheduler::DISABLED if !@definition.Settings.Enabled + flags = flags | PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED if !@definition.Settings.Enabled flags end # Sets an OR'd value of flags that modify the behavior of the work item. # def flags=(flags) - @definition.Settings.Enabled = (flags & Win32::TaskScheduler::DISABLED == 0) + @definition.Settings.Enabled = (flags & PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED == 0) end # Returns whether or not the scheduled task exists. diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task.rb index 1fe2b7a0..05261881 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task.rb @@ -85,7 +85,7 @@ def save(file = nil) # # Calling task.set_account_information('SYSTEM', nil) will generally not # work, except for one special case where flags are also set like: - # task.flags = Win32::TaskScheduler::TASK_FLAG_RUN_ONLY_IF_LOGGED_ON + # task.flags = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_RUN_ONLY_IF_LOGGED_ON # # This must be done prior to the 1st save() call for the task to be # properly registered and visible through the MMC snap-in / schtasks.exe @@ -217,14 +217,14 @@ def append_trigger(v1trigger) # def flags flags = 0 - flags = flags | Win32::TaskScheduler::DISABLED if !@definition.Settings.Enabled + flags = flags | PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED if !@definition.Settings.Enabled flags end # Sets an OR'd value of flags that modify the behavior of the work item. # def flags=(flags) - @definition.Settings.Enabled = (flags & Win32::TaskScheduler::DISABLED == 0) + @definition.Settings.Enabled = (flags & PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED == 0) end # Returns whether or not the scheduled task exists. diff --git a/spec/integration/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task_spec.rb b/spec/integration/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task_spec.rb index 71547a73..feec7abf 100644 --- a/spec/integration/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task_spec.rb +++ b/spec/integration/puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task_spec.rb @@ -18,7 +18,7 @@ task = Win32::TaskScheduler.new(@task_name, PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.default_trigger_for('once')) task.application_name = 'cmd.exe' task.parameters = '/c exit 0' - task.flags = Win32::TaskScheduler::DISABLED + task.flags = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED task.save end @@ -50,7 +50,7 @@ task = Win32::TaskScheduler.new(@task_name, PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.default_trigger_for('once')) task.application_name = 'cmd.exe' task.parameters = '/c exit 0' - task.flags = Win32::TaskScheduler::DISABLED + task.flags = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED task.save end diff --git a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb index b5ffd747..28f81bf7 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -2,19 +2,12 @@ require 'spec_helper' require 'puppet/util/windows/taskscheduler' if Puppet.features.microsoft_windows? -require 'puppet_x/puppetlabs/scheduled_task/taskscheduler2_v1task' if Puppet.features.microsoft_windows? +require 'puppet_x/puppetlabs/scheduled_task/taskscheduler2_task' shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do - if described_class == Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler - described_class.new( - :name => 'Shared Test Task', - :command => 'C:\Windows\System32\notepad.exe' - ).translate_hash_to_trigger(trigger_hash) - else PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(trigger_hash) end - end before :each do Win32::TaskScheduler.any_instance.stubs(:save) @@ -120,17 +113,18 @@ def time_component end end -# The Win32::TaskScheduler and PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task classes should be -# API compatible and behave the same way. What differs is which Windows API is used to query -# and affect the system. This means for testing, any tests should be the same no matter what -# provider or concrete class (which the provider uses) is used. -klass_list = Puppet.features.microsoft_windows? ? [Win32::TaskScheduler, PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task] : [] -klass_list.each do |concrete_klass| - -if concrete_klass == PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task - task_provider = :taskscheduler_api2 -else - task_provider = :win32_taskscheduler +# The win32_taskscheduler and taskscheduler_api2 providers should be API compatible and behave +# the same way. What differs is which Windows API is used to query and affect the system. +# This means for testing, any tests should be the same no matter what provider or concrete class +# (which the provider uses) is used. +task_providers = Puppet.features.microsoft_windows? ? [:win32_taskscheduler, :taskscheduler_api2] : [] +task_providers.each do |task_provider| + +case task_provider +when :win32_taskscheduler + concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task +when :taskscheduler_api2 + concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task end describe Puppet::Type.type(:scheduled_task).provider(task_provider), :if => Puppet.features.microsoft_windows? do @@ -143,10 +137,10 @@ def time_component describe 'when retrieving' do before :each do @mock_task = stub - @mock_task.responds_like(Win32::TaskScheduler.new) + @mock_task.responds_like(concrete_klass.new) described_class.any_instance.stubs(:task).returns(@mock_task) - Win32::TaskScheduler.stubs(:new).returns(@mock_task) + concrete_klass.stubs(:new).returns(@mock_task) end let(:resource) { Puppet::Type.type(:scheduled_task).new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } @@ -474,13 +468,7 @@ def time_component 'start_minute' => 21, 'flags' => 0, }) - if task_provider == :win32_taskscheduler - @mock_task.expects(:trigger).with(1).raises( - Win32::TaskScheduler::Error.new('Unknown trigger type') - ) - else @mock_task.expects(:trigger).with(1).returns(nil) - end @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => :TASK_TIME_TRIGGER_ONCE, @@ -525,10 +513,7 @@ def time_component 'start_minute' => 21, 'flags' => 0, }) - @trigger1 = task_provider == :win32_taskscheduler ? - { 'trigger_type' => Win32::TaskScheduler::TASK_EVENT_TRIGGER_AT_LOGON, } : - nil - @mock_task.expects(:trigger).with(1).returns(@trigger1) + @mock_task.expects(:trigger).with(1).returns(nil) @mock_task.expects(:trigger).with(2).returns({ 'trigger_type' => :TASK_TIME_TRIGGER_ONCE, @@ -607,6 +592,13 @@ def time_component expect(resource.provider.arguments).to eq('these are my arguments') end + it 'should get the compatibility from the parameters on the task' do + skip("provider #{resource.provider} doesn't support compatibility") unless resource.provider.respond_to?(:compatibility) + @mock_task.expects(:compatibility).returns(3) + + expect(resource.provider.compatibility).to eq(3) + end + it 'should get the user from the account_information on the task' do @mock_task.expects(:account_information).returns('this is my user') @@ -615,19 +607,19 @@ def time_component describe 'whether the task is enabled' do it 'should report tasks with the disabled bit set as disabled' do - @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) + @mock_task.stubs(:flags).returns(PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED) expect(resource.provider.enabled).to eq(:false) end it 'should report tasks without the disabled bit set as enabled' do - @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) + @mock_task.stubs(:flags).returns(~PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED) expect(resource.provider.enabled).to eq(:true) end it 'should not consider triggers for determining if the task is enabled' do - @mock_task.stubs(:flags).returns(~Win32::TaskScheduler::DISABLED) + @mock_task.stubs(:flags).returns(~PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED) @mock_task.stubs(:trigger_count).returns(1) @mock_task.stubs(:trigger).with(0).returns({ 'trigger_type' => :TASK_TIME_TRIGGER_ONCE, @@ -1212,20 +1204,6 @@ def time_component end end - describe '#normalized_date', - :if => described_class == Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler do - it 'should format the date without leading zeros' do - expect(described_class.normalized_date('2011-01-01')).to eq('2011-1-1') - end - end - - describe '#normalized_time', - :if => described_class == Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler do - it 'should format the time as {24h}:{minutes}' do - expect(described_class.normalized_time('8:37 PM')).to eq('20:37') - end - end - describe '#from_manifest_hash' do before :each do @puppet_trigger = { @@ -1235,12 +1213,8 @@ def time_component end let(:provider) { described_class.new(:name => 'Test Task', :command => 'C:\Windows\System32\notepad.exe') } let(:trigger) do - if provider.is_a?(Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler) - provider.translate_hash_to_trigger(@puppet_trigger) - else PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(@puppet_trigger) end - end context "working with repeat every x triggers" do before :each do @@ -1732,13 +1706,13 @@ def time_component describe '#enabled=' do it 'should set the disabled flag if the task should be disabled' do @mock_task.stubs(:flags).returns(0) - @mock_task.expects(:flags=).with(Win32::TaskScheduler::DISABLED) + @mock_task.expects(:flags=).with(PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED) resource.provider.enabled = :false end it 'should clear the disabled flag if the task should be enabled' do - @mock_task.stubs(:flags).returns(Win32::TaskScheduler::DISABLED) + @mock_task.stubs(:flags).returns(PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2::TASK_FLAG_DISABLED) @mock_task.expects(:flags=).with(0) resource.provider.enabled = :true @@ -1800,15 +1774,8 @@ def time_component {'schedule' => 'once', 'start_date' => '2011-09-15', 'start_time' => '15:10', 'index' => 0}, ] resource.provider.stubs(:trigger).returns(current_triggers) - if resource.provider.is_a?(Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler) - translater = resource.provider.method(:translate_hash_to_trigger) - expected_method = :trigger= - else - translater = PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.method(:from_manifest_hash) - expected_method = :append_trigger - end - @mock_task.expects(expected_method).with(translater.call(@trigger[1])) - @mock_task.expects(expected_method).with(translater.call(@trigger[2])) + @mock_task.expects(:append_trigger).with(PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(@trigger[1])) + @mock_task.expects(:append_trigger).with(PuppetX::PuppetLabs::ScheduledTask::Trigger::V1.from_manifest_hash(@trigger[2])) resource.provider.trigger = @trigger end @@ -1852,28 +1819,39 @@ def time_component resource.provider.user = 'my_user_name' end end + + describe '#compatibility=' do + it 'should set the parameters on the task' do + skip("provider #{resource.provider} doesn't support compatibility") unless resource.provider.respond_to?(:compatibility) + @mock_task.expects(:compatibility=).with(1) + + resource.provider.compatibility = 1 + end + end end describe '#create' do let(:resource) do Puppet::Type.type(:scheduled_task).new( - :name => 'Test Task', - :enabled => @enabled, - :command => @command, - :arguments => @arguments, - :working_dir => @working_dir, - :trigger => { 'schedule' => 'once', 'start_date' => '2011-09-27', 'start_time' => '17:00' } + :name => 'Test Task', + :enabled => @enabled, + :command => @command, + :arguments => @arguments, + :compatibility => @compatibility, + :working_dir => @working_dir, + :trigger => { 'schedule' => 'once', 'start_date' => '2011-09-27', 'start_time' => '17:00' } ) end before :each do - @enabled = :true - @command = 'C:\Windows\System32\notepad.exe' - @arguments = '/a /list /of /arguments' - @working_dir = 'C:\Windows\Some\Directory' + @enabled = :true + @command = 'C:\Windows\System32\notepad.exe' + @arguments = '/a /list /of /arguments' + @compatibility = 1 + @working_dir = 'C:\Windows\Some\Directory' @mock_task = stub - @mock_task.responds_like(Win32::TaskScheduler.new) + @mock_task.responds_like(concrete_klass.new) @mock_task.stubs(:exists?).returns(true) @mock_task.stubs(:activate) @mock_task.stubs(:application_name=) @@ -1883,9 +1861,13 @@ def time_component @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) - @mock_task.stubs(:trigger=) + @mock_task.stubs(:append_trigger) + if resource.provider.is_a?(Puppet::Type::Scheduled_task::ProviderTaskscheduler_api2) + # allow compatibility to set given newer provider allows it + @mock_task.stubs(:compatibility=) + end @mock_task.stubs(:save) - Win32::TaskScheduler.stubs(:new).returns(@mock_task) + concrete_klass.stubs(:new).returns(@mock_task) described_class.any_instance.stubs(:sync_triggers) end @@ -1902,6 +1884,13 @@ def time_component resource.provider.create end + it 'should set the compatibility' do + skip("provider #{resource.provider} doesn't support compatibility") unless resource.provider.respond_to?(:compatibility) + resource.provider.expects(:compatibility=).with(@compatibility) + + resource.provider.create + end + it 'should set the working_dir' do resource.provider.expects(:working_dir=).with(@working_dir) diff --git a/spec/unit/puppet/type/scheduled_task_spec.rb b/spec/unit/puppet/type/scheduled_task_spec.rb index 90782dcd..73392971 100644 --- a/spec/unit/puppet/type/scheduled_task_spec.rb +++ b/spec/unit/puppet/type/scheduled_task_spec.rb @@ -109,12 +109,14 @@ end describe 'when setting the compatibility' do - it 'should allow 1' do - expect(described_class.new( - :title => 'Foo', - :command => 'C:\Windows\System32\notepad.exe', - :compatibility => 1, - )[:compatibility]).to eq(1) + [1, 2, 3, 4, 6].each do |compat| + it "should allow #{compat}" do + expect(described_class.new( + :title => 'Foo', + :command => 'C:\Windows\System32\notepad.exe', + :compatibility => compat + )[:compatibility]).to eq(compat) + end end it 'should not allow the string value "1"' do @@ -129,19 +131,6 @@ /Parameter compatibility failed on Scheduled_task\[Foo\]: must be a number/ ) end - - it 'should not allow 2' do - expect { - described_class.new( - :name => 'Foo', - :command => 'C:\Windows\System32\notepad.exe', - :compatibility => 2 - ) - }.to raise_error( - Puppet::ResourceError, - /Parameter compatibility failed on Scheduled_task\[Foo\]: Invalid value 2\. Valid values are 1\./ - ) - end end describe 'when setting the trigger' do