From 344626e51c1f92a8a91633815e4320bff0e579b2 Mon Sep 17 00:00:00 2001 From: Michael T Lombardi Date: Wed, 2 May 2018 14:13:36 -0500 Subject: [PATCH 1/8] (MAINT) Refactor spec to iterate over providers Prior to this commit the spec for the providers iterated over the helpers used. This causes some problems with plans to copy the new provider into the old as both providers will temporarily use the same provider. This commit changes the spec logic to iterate over the providers and map the appropriate helpers to them. No tests are changed or affected. --- .../win32_taskscheduler_spec.rb | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) 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..ca8718b9 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -120,17 +120,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 = Win32::TaskScheduler +when :taskscheduler_api2 + concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task end describe Puppet::Type.type(:scheduled_task).provider(task_provider), :if => Puppet.features.microsoft_windows? do From ac31c0d6d4cf4040bddcddaa8525bc1c254b7545 Mon Sep 17 00:00:00 2001 From: Michael T Lombardi Date: Wed, 2 May 2018 17:57:57 -0500 Subject: [PATCH 2/8] (MAINT) Replace Win32::TaskScheduler constant Prior to this commit the code relied on the Win32::TaskScheduler class to provide the DISABLED constant, indicating a task is disabled. This required loading that class and caused puppet resource calls to fail. This commit adds the task flag constants to the `TaskScheduler2` class and bypasses the need to load the Win32::TaskScheduler at all. It then replaces all calls for that constant to the appropriate call to those defined in `TaskScheduler2`. --- .../provider/scheduled_task/taskscheduler_api2.rb | 6 +++--- .../puppetlabs/scheduled_task/taskscheduler2.rb | 15 +++++++++++++++ .../scheduled_task/taskscheduler2_task.rb | 6 +++--- .../scheduled_task/taskscheduler2_v1task.rb | 6 +++--- .../scheduled_task/taskscheduler2_v1task_spec.rb | 4 ++-- .../scheduled_task/win32_taskscheduler_spec.rb | 10 +++++----- 6 files changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb index 82d795a1..afdc687d 100644 --- a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb +++ b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb @@ -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 diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb index 922974ff..5542f04e 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb @@ -54,6 +54,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..2609aa4b 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb @@ -80,7 +80,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 @@ -212,14 +212,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 ca8718b9..200deab3 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -616,19 +616,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, @@ -1733,13 +1733,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 From ceaa0bccfb6be3bd3fe9d6ab61fc474c53fd2406 Mon Sep 17 00:00:00 2001 From: "Ethan J. Brown" Date: Wed, 2 May 2018 16:26:42 -0700 Subject: [PATCH 3/8] (maint) Remove unnecessary Windows guard in specs --- .../puppet/provider/scheduled_task/win32_taskscheduler_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 200deab3..4f2d7d76 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -2,7 +2,7 @@ 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_v1task' shared_examples_for "a trigger that handles start_date and start_time" do let(:trigger) do From 9d0258258e8677fac35c7707da201695e88307cc Mon Sep 17 00:00:00 2001 From: "Ethan J. Brown" Date: Fri, 4 May 2018 11:50:42 -0700 Subject: [PATCH 4/8] (maint) Update specs to use correct helper classes - Previously, specs were hardcoded to use the Win32::TaskScheduler helper during some retrieval and creation style tests, when they should be switching the class based on the appropriate helper, either PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task OR Win32::TaskScheduler. This creates a minor discrepancy in a subsequent commit when additional testing is added for compatibility, which is implemented only in TaskScheduler2V1Task and *not* Win32::TaskScheduler --- .../scheduled_task/win32_taskscheduler_spec.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) 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 4f2d7d76..a35bc89f 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -144,10 +144,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') } @@ -1874,7 +1874,7 @@ def time_component @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=) @@ -1884,9 +1884,13 @@ def time_component @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) - @mock_task.stubs(:trigger=) + if resource.provider.is_a?(Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler) + @mock_task.stubs(:trigger=) + else + @mock_task.stubs(:append_trigger) + 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 From 73f8b5c5584aabcd4843165eb4a6c3ccac61302c Mon Sep 17 00:00:00 2001 From: Michael T Lombardi Date: Wed, 2 May 2018 16:49:40 -0500 Subject: [PATCH 5/8] (MODULES-6526) Ensure provider sets compatibility - The initial V2 implementation of compatbility in the V2 provider did not properly use the attribute to set the compatibility level when constructing new task instances. Remove the hardcoded setter that pushes through V2 and instead call the property setter for `compatibility` the same way as is done for other providers. - Add additional validations through tests to demonstrate this behavior, noting the old Win32_TaskScheduler provider does not follow this same behavior. --- .../scheduled_task/taskscheduler_api2.rb | 2 +- .../scheduled_task/taskscheduler2_task.rb | 2 - .../win32_taskscheduler_spec.rb | 47 +++++++++++++++---- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb index afdc687d..93a95295 100644 --- a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb +++ b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb @@ -182,7 +182,7 @@ def create ) 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 diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb index 2609aa4b..1ebc0c55 100644 --- a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb +++ b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb @@ -157,8 +157,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) 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 a35bc89f..db62e8df 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -608,6 +608,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') @@ -1853,25 +1860,36 @@ 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(concrete_klass.new) @@ -1888,6 +1906,8 @@ def time_component @mock_task.stubs(:trigger=) else @mock_task.stubs(:append_trigger) + # also allow compatibility to set given newer provider allows it + @mock_task.stubs(:compatibility=) end @mock_task.stubs(:save) concrete_klass.stubs(:new).returns(@mock_task) @@ -1907,6 +1927,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) From 44fba46cc23d10618840c4c37f34fdbafdb02f9d Mon Sep 17 00:00:00 2001 From: Michael T Lombardi Date: Tue, 1 May 2018 17:57:42 -0500 Subject: [PATCH 6/8] (MODULES-6845) Copy V2 Provider into Win32 Prior to this commit the Win32 provider in this module was a near mirror of the existing provider in core Puppet. In order to more easily maintain the new module, to debug it, and to onboard team members to maintaining it, this commit copies over the code from the new provider into the old one. As the new provider was made to keep in parity with the old one and the tests already existed to verify this, the change is determined to be low risk. In a future commit the V2 provider will be updated to manage scheduled tasks of all compatibility modes and will be the code which gets all new features and improvements. The legacy provider will be frozen at a point in the near future for all changes except for security and critical bug fixes. This commit also updates the test suite to verify the changes and removes unneccessary logic from the tests which were needed when the providers did not share helper code. However, in this iteration, the tests only run over the legacy provider and not the V2 provider, though they are using the same helper at this time. In a future commit, when the V2 provider is updated to use the new V2 helper, the test suite will again run for both. Note also that references to the compatibility property were removed from the win32_taskscheduler code because only the taskscheduler_api2 supports it. --- .../scheduled_task/win32_taskscheduler.rb | 430 ++---------------- .../win32_taskscheduler_spec.rb | 59 +-- 2 files changed, 40 insertions(+), 449 deletions(-) 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/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb index db62e8df..fb607114 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' +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) @@ -129,7 +122,7 @@ def time_component case task_provider when :win32_taskscheduler - concrete_klass = Win32::TaskScheduler + concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task when :taskscheduler_api2 concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task end @@ -475,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, @@ -526,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, @@ -1220,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 = { @@ -1243,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 @@ -1808,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 @@ -1902,11 +1861,9 @@ def time_component @mock_task.stubs(:flags) @mock_task.stubs(:flags=) @mock_task.stubs(:trigger_count).returns(0) - if resource.provider.is_a?(Puppet::Type::Scheduled_task::ProviderWin32_taskscheduler) - @mock_task.stubs(:trigger=) - else - @mock_task.stubs(:append_trigger) - # also allow compatibility to set given newer provider allows it + @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) From 82b84b1c30ceb2dc647c4afd5d74816badfec8e4 Mon Sep 17 00:00:00 2001 From: Michael T Lombardi Date: Wed, 2 May 2018 16:49:40 -0500 Subject: [PATCH 7/8] (MODULES-6845) TaskScheduler2Task in API2 Provider - Prior to this commit both the win32 (legacy) and api2 (new) providers leveraged the adapter code in `TaskScheduler2V1Task` to manage scheduled tasks. This commit updates the API2 provider to use the TaskScheduler2Task helper, allowing us to freeze work on the adapter code and begin to add new features to the API2 helper. At this point in time, only the helper has been swapped out, the provider itself has not been meaningfully altered. - Make sure the helper queries for all task compatibility levels including 3 (2008R2), 4 (2012R2 / 2016), 6 (Windows 10) in addition to just V1 and AT as the old provider did. This ensures that `puppet resource` returns all tasks on the system even if they cannot be configured yet. --- .../provider/scheduled_task/taskscheduler_api2.rb | 12 ++++++------ .../puppetlabs/scheduled_task/taskscheduler2.rb | 3 +++ .../puppetlabs/scheduled_task/taskscheduler2_task.rb | 9 ++++++++- .../scheduled_task/win32_taskscheduler_spec.rb | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb b/lib/puppet/provider/scheduled_task/taskscheduler_api2.rb index 93a95295..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 @@ -176,7 +176,7 @@ 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') ) @@ -188,7 +188,7 @@ def create 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_x/puppetlabs/scheduled_task/taskscheduler2.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2.rb index 5542f04e..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 diff --git a/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb b/lib/puppet_x/puppetlabs/scheduled_task/taskscheduler2_task.rb index 1ebc0c55..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 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 fb607114..28f81bf7 100644 --- a/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/puppet/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -124,7 +124,7 @@ def time_component when :win32_taskscheduler concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task when :taskscheduler_api2 - concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2V1Task + concrete_klass = PuppetX::PuppetLabs::ScheduledTask::TaskScheduler2Task end describe Puppet::Type.type(:scheduled_task).provider(task_provider), :if => Puppet.features.microsoft_windows? do From 51902a6512a6f5ba1415327da928703c7ae40414 Mon Sep 17 00:00:00 2001 From: "Ethan J. Brown" Date: Mon, 23 Apr 2018 15:11:24 -0700 Subject: [PATCH 8/8] (MODULES-6845) Allow more compatibility levels - To fully support reading / writing v2 tasks requires allowing the compatibility value in the scheduled_task type to be anything valid, including 1, 2, 3, 4 and 6. 1 - 2003, XP, 2000 2 - Vista / 2008 3 - 7, 2008R2 4 - 8, 2012R2, 2016 6 - 10 --- lib/puppet/type/scheduled_task.rb | 14 +++++++--- spec/unit/puppet/type/scheduled_task_spec.rb | 27 ++++++-------------- 2 files changed, 19 insertions(+), 22 deletions(-) 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/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