summaryrefslogtreecommitdiff
path: root/lib/chef/provider/windows_task.rb
blob: 90e0f3e9de039d1556da0f92643f3f833612831b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
#
# Author:: Nimisha Sharad (<nimisha.sharad@msystechnologies.com>)
# Copyright:: Copyright 2008-2019, Chef Software Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require_relative "../mixin/shell_out"
require "rexml/document" unless defined?(REXML::Document)
require "iso8601" if ChefUtils.windows?
require_relative "../mixin/powershell_out"
require_relative "../provider"
require_relative "../util/path_helper"
require "win32/taskscheduler" if ChefUtils.windows?

class Chef
  class Provider
    class WindowsTask < Chef::Provider
      include Chef::Mixin::ShellOut
      include Chef::Mixin::PowershellOut

      if ChefUtils.windows?
        include Win32

        provides :windows_task

        MONTHS = {
          JAN: TaskScheduler::JANUARY,
          FEB: TaskScheduler::FEBRUARY,
          MAR: TaskScheduler::MARCH,
          APR: TaskScheduler::APRIL,
          MAY: TaskScheduler::MAY,
          JUN: TaskScheduler::JUNE,
          JUL: TaskScheduler::JULY,
          AUG: TaskScheduler::AUGUST,
          SEP: TaskScheduler::SEPTEMBER,
          OCT: TaskScheduler::OCTOBER,
          NOV: TaskScheduler::NOVEMBER,
          DEC: TaskScheduler::DECEMBER,
        }.freeze

        DAYS_OF_WEEK = { MON: TaskScheduler::MONDAY,
                         TUE: TaskScheduler::TUESDAY,
                         WED: TaskScheduler::WEDNESDAY,
                         THU: TaskScheduler::THURSDAY,
                         FRI: TaskScheduler::FRIDAY,
                         SAT: TaskScheduler::SATURDAY,
                         SUN: TaskScheduler::SUNDAY }.freeze

        WEEKS_OF_MONTH = {
          FIRST: TaskScheduler::FIRST_WEEK,
          SECOND: TaskScheduler::SECOND_WEEK,
          THIRD: TaskScheduler::THIRD_WEEK,
          FOURTH: TaskScheduler::FOURTH_WEEK,
        }.freeze

        DAYS_OF_MONTH = {
          1 => TaskScheduler::TASK_FIRST,
          2 => TaskScheduler::TASK_SECOND,
          3 => TaskScheduler::TASK_THIRD,
          4 => TaskScheduler::TASK_FOURTH,
          5 => TaskScheduler::TASK_FIFTH,
          6 => TaskScheduler::TASK_SIXTH,
          7 => TaskScheduler::TASK_SEVENTH,
          8 => TaskScheduler::TASK_EIGHTH,
          9 => TaskScheduler::TASK_NINETH,
          10 => TaskScheduler::TASK_TENTH,
          11 => TaskScheduler::TASK_ELEVENTH,
          12 => TaskScheduler::TASK_TWELFTH,
          13 => TaskScheduler::TASK_THIRTEENTH,
          14 => TaskScheduler::TASK_FOURTEENTH,
          15 => TaskScheduler::TASK_FIFTEENTH,
          16 => TaskScheduler::TASK_SIXTEENTH,
          17 => TaskScheduler::TASK_SEVENTEENTH,
          18 => TaskScheduler::TASK_EIGHTEENTH,
          19 => TaskScheduler::TASK_NINETEENTH,
          20 => TaskScheduler::TASK_TWENTIETH,
          21 => TaskScheduler::TASK_TWENTY_FIRST,
          22 => TaskScheduler::TASK_TWENTY_SECOND,
          23 => TaskScheduler::TASK_TWENTY_THIRD,
          24 => TaskScheduler::TASK_TWENTY_FOURTH,
          25 => TaskScheduler::TASK_TWENTY_FIFTH,
          26 => TaskScheduler::TASK_TWENTY_SIXTH,
          27 => TaskScheduler::TASK_TWENTY_SEVENTH,
          28 => TaskScheduler::TASK_TWENTY_EIGHTH,
          29 => TaskScheduler::TASK_TWENTY_NINTH,
          30 => TaskScheduler::TASK_THIRTYETH,
          31 => TaskScheduler::TASK_THIRTY_FIRST,
        }.freeze

        PRIORITY = { "critical" => 0, "highest" => 1,  "above_normal_2" => 2 , "above_normal_3" => 3, "normal_4" => 4,
                     "normal_5" => 5, "normal_6" => 6, "below_normal_7" => 7, "below_normal_8" => 8, "lowest" => 9, "idle" => 10 }.freeze

        def load_current_resource
          @current_resource = Chef::Resource::WindowsTask.new(new_resource.name)
          task = TaskScheduler.new(new_resource.task_name, nil, "\\", false)
          @current_resource.exists = task.exists?(new_resource.task_name)
          if @current_resource.exists
            task.get_task(new_resource.task_name)
            @current_resource.task = task
            pathed_task_name = new_resource.task_name.start_with?('\\') ? new_resource.task_name : "\\#{new_resource.task_name}"
            @current_resource.task_name(pathed_task_name)
          end
          @current_resource
        end

        def action_create
          set_command_and_arguments if new_resource.command

          if current_resource.exists
            logger.trace "#{new_resource} task exist."
            unless (task_needs_update?(current_resource.task)) || (new_resource.force)
              logger.info "#{new_resource} task does not need updating and force is not specified - nothing to do"
              return
            end

            # if start_day and start_time is not set by user current date and time will be set while updating any property
            set_start_day_and_time unless new_resource.frequency == :none
            update_task(current_resource.task)
          else
            basic_validation
            set_start_day_and_time
            converge_by("#{new_resource} task created") do
              task = TaskScheduler.new
              if new_resource.frequency == :none
                task.new_work_item(new_resource.task_name, {}, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
                task.activate(new_resource.task_name)
              else
                task.new_work_item(new_resource.task_name, trigger, { user: new_resource.user, password: new_resource.password, interactive: new_resource.interactive_enabled })
              end
              task.application_name = new_resource.command
              task.parameters = new_resource.command_arguments if new_resource.command_arguments
              task.working_directory = new_resource.cwd if new_resource.cwd
              task.configure_settings(config_settings)
              task.configure_principals(principal_settings)
              task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
              task.creator = new_resource.user
              task.description = new_resource.description unless new_resource.description.nil?
              task.activate(new_resource.task_name)
            end
          end
        end

        def action_run
          if current_resource.exists
            logger.trace "#{new_resource} task exists"
            if current_resource.task.status == "running"
              logger.info "#{new_resource} task is currently running, skipping run"
            else
              converge_by("run scheduled task #{new_resource}") do
                current_resource.task.run
              end
            end
          else
            logger.warn "#{new_resource} task does not exist - nothing to do"
          end
        end

        def action_delete
          if current_resource.exists
            logger.trace "#{new_resource} task exists"
            converge_by("delete scheduled task #{new_resource}") do
              ts = TaskScheduler.new
              ts.delete(current_resource.task_name)
            end
          else
            logger.warn "#{new_resource} task does not exist - nothing to do"
          end
        end

        def action_end
          if current_resource.exists
            logger.trace "#{new_resource} task exists"
            if current_resource.task.status != "running"
              logger.trace "#{new_resource} is not running - nothing to do"
            else
              converge_by("#{new_resource} task ended") do
                current_resource.task.stop
              end
            end
          else
            logger.warn "#{new_resource} task does not exist - nothing to do"
          end
        end

        def action_enable
          if current_resource.exists
            logger.trace "#{new_resource} task exists"
            if current_resource.task.status == "not scheduled"
              converge_by("#{new_resource} task enabled") do
                # TODO wind32-taskscheduler currently not having any method to handle this so using schtasks.exe here
                run_schtasks "CHANGE", "ENABLE" => ""
              end
            else
              logger.trace "#{new_resource} already enabled - nothing to do"
            end
          else
            logger.fatal "#{new_resource} task does not exist - nothing to do"
            raise Errno::ENOENT, "#{new_resource}: task does not exist, cannot enable"
          end
        end

        def action_disable
          if current_resource.exists
            logger.info "#{new_resource} task exists"
            if %w{ready running}.include?(current_resource.task.status)
              converge_by("#{new_resource} task disabled") do
                # TODO: in win32-taskscheduler there is no method whcih disbales the task so currently calling disable with schtasks.exe
                run_schtasks "CHANGE", "DISABLE" => ""
              end
            else
              logger.warn "#{new_resource} already disabled - nothing to do"
            end
          else
            logger.warn "#{new_resource} task does not exist - nothing to do"
          end
        end

        alias_method :action_change, :action_create

        private

        # seprated command arguments from :command property
        def set_command_and_arguments
          cmd, *args = Chef::Util::PathHelper.split_args(new_resource.command)
          new_resource.command = cmd
          new_resource.command_arguments = args.join(" ")
        end

        def set_start_day_and_time
          new_resource.start_day = Time.now.strftime("%m/%d/%Y") unless new_resource.start_day
          new_resource.start_time = Time.now.strftime("%H:%M") unless new_resource.start_time
        end

        def update_task(task)
          converge_by("#{new_resource} task updated") do
            task.set_account_information(new_resource.user, new_resource.password, new_resource.interactive_enabled)
            task.application_name = new_resource.command if new_resource.command
            task.parameters = new_resource.command_arguments if new_resource.command_arguments
            task.working_directory = new_resource.cwd if new_resource.cwd
            task.trigger = trigger unless new_resource.frequency == :none
            task.configure_settings(config_settings)
            task.creator = new_resource.user
            task.description = new_resource.description unless new_resource.description.nil?
            task.configure_principals(principal_settings)
          end
        end

        def trigger
          start_month, start_day, start_year = new_resource.start_day.to_s.split("/")
          start_hour, start_minute = new_resource.start_time.to_s.split(":")
          # TODO currently end_month, end_year and end_year needs to be set to 0. If not set win32-taskscheduler throwing nil into integer error.
          trigger_hash = {
            start_year: start_year.to_i,
            start_month: start_month.to_i,
            start_day: start_day.to_i,
            start_hour: start_hour.to_i,
            start_minute: start_minute.to_i,
            end_month: 0,
            end_day: 0,
            end_year: 0,
            trigger_type: trigger_type,
            type: type,
            random_minutes_interval: new_resource.random_delay,
          }

          if new_resource.frequency == :minute
            trigger_hash[:minutes_interval] = new_resource.frequency_modifier
          end

          if new_resource.frequency == :hourly
            minutes = convert_hours_in_minutes(new_resource.frequency_modifier.to_i)
            trigger_hash[:minutes_interval] = minutes
          end

          if new_resource.minutes_interval
            trigger_hash[:minutes_interval] = new_resource.minutes_interval
          end

          if new_resource.minutes_duration
            trigger_hash[:minutes_duration] = new_resource.minutes_duration
          end

          if trigger_type == TaskScheduler::MONTHLYDOW && frequency_modifier_contains_last_week?(new_resource.frequency_modifier)
            trigger_hash[:run_on_last_week_of_month] = true
          else
            trigger_hash[:run_on_last_week_of_month] = false
          end

          if trigger_type == TaskScheduler::MONTHLYDATE && day_includes_last_or_lastday?(new_resource.day)
            trigger_hash[:run_on_last_day_of_month] = true
          else
            trigger_hash[:run_on_last_day_of_month] = false
          end
          trigger_hash
        end

        def frequency_modifier_contains_last_week?(frequency_modifier)
          frequency_modifier = frequency_modifier.to_s.split(",")
          frequency_modifier.map! { |value| value.strip.upcase }
          frequency_modifier.include?("LAST")
        end

        def day_includes_last_or_lastday?(day)
          day = day.to_s.split(",")
          day.map! { |value| value.strip.upcase }
          day.include?("LAST") || day.include?("LASTDAY")
        end

        def convert_hours_in_minutes(hours)
          hours.to_i * 60 if hours
        end

        # TODO : Try to optimize this method
        # known issue : Since start_day and time is not mandatory while updating weekly frequency for which start_day is not mentioned by user idempotency
        # is not gettting maintained as new_resource.start_day is nil and we fetch the day of week from start_day to set and its currently coming as nil and don't match with current_task
        def task_needs_update?(task)
          flag = false
          if new_resource.frequency == :none
            flag = (task.author != new_resource.user ||
            task.application_name != new_resource.command ||
            description_needs_update?(task) ||
            task.parameters != new_resource.command_arguments.to_s ||
            task.principals[:run_level] != run_level ||
            task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
            task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
            task.settings[:start_when_available] != new_resource.start_when_available)
          else
            current_task_trigger = task.trigger(0)
            new_task_trigger = trigger
            flag = (ISO8601::Duration.new(task.idle_settings[:idle_duration])) != (ISO8601::Duration.new(new_resource.idle_time * 60)) if new_resource.frequency == :on_idle
            flag = (ISO8601::Duration.new(task.execution_time_limit)) != (ISO8601::Duration.new(new_resource.execution_time_limit * 60)) unless new_resource.execution_time_limit.nil?

            # if trigger not found updating the task to add the trigger
            if current_task_trigger.nil?
              flag = true
            else
              flag = true if start_day_updated?(current_task_trigger, new_task_trigger) == true ||
                start_time_updated?(current_task_trigger, new_task_trigger) == true ||
                current_task_trigger[:trigger_type] != new_task_trigger[:trigger_type] ||
                current_task_trigger[:type] != new_task_trigger[:type] ||
                current_task_trigger[:random_minutes_interval].to_i != new_task_trigger[:random_minutes_interval].to_i ||
                current_task_trigger[:minutes_interval].to_i != new_task_trigger[:minutes_interval].to_i ||
                task.author.to_s.casecmp(new_resource.user.to_s) != 0 ||
                task.application_name != new_resource.command ||
                description_needs_update?(task) ||
                task.parameters != new_resource.command_arguments.to_s ||
                task.working_directory != new_resource.cwd.to_s ||
                task.principals[:logon_type] != logon_type ||
                task.principals[:run_level] != run_level ||
                PRIORITY[task.priority] != new_resource.priority ||
                task.settings[:disallow_start_if_on_batteries] != new_resource.disallow_start_if_on_batteries ||
                task.settings[:stop_if_going_on_batteries] != new_resource.stop_if_going_on_batteries ||
                task.settings[:start_when_available] != new_resource.start_when_available
              if trigger_type == TaskScheduler::MONTHLYDATE
                flag = true if current_task_trigger[:run_on_last_day_of_month] != new_task_trigger[:run_on_last_day_of_month]
              end

              if trigger_type == TaskScheduler::MONTHLYDOW
                flag = true if current_task_trigger[:run_on_last_week_of_month] != new_task_trigger[:run_on_last_week_of_month]
              end
            end
          end
          flag
        end

        def start_day_updated?(current_task_trigger, new_task_trigger)
          ( new_resource.start_day && (current_task_trigger[:start_year].to_i != new_task_trigger[:start_year] ||
                    current_task_trigger[:start_month].to_i != new_task_trigger[:start_month] ||
                    current_task_trigger[:start_day].to_i != new_task_trigger[:start_day]) )
        end

        def start_time_updated?(current_task_trigger, new_task_trigger)
          ( new_resource.start_time && ( current_task_trigger[:start_hour].to_i != new_task_trigger[:start_hour] ||
            current_task_trigger[:start_minute].to_i != new_task_trigger[:start_minute] ) )
        end

        def trigger_type
          case new_resource.frequency
            when :once, :minute, :hourly
              TaskScheduler::ONCE
            when :daily
              TaskScheduler::DAILY
            when :weekly
              TaskScheduler::WEEKLY
            when :monthly
              # If frequency modifier is set with frequency :monthly we are setting taskscheduler as monthlydow
              # Ref https://msdn.microsoft.com/en-us/library/windows/desktop/aa382061(v=vs.85).aspx
              new_resource.frequency_modifier.to_i.between?(1, 12) ? TaskScheduler::MONTHLYDATE : TaskScheduler::MONTHLYDOW
            when :on_idle
              TaskScheduler::ON_IDLE
            when :onstart
              TaskScheduler::AT_SYSTEMSTART
            when :on_logon
              TaskScheduler::AT_LOGON
            else
              raise ArgumentError, "Please set frequency"
          end
        end

        def type
          case trigger_type
            when TaskScheduler::ONCE
              { once: nil }
            when TaskScheduler::DAILY
              { days_interval: new_resource.frequency_modifier.to_i }
            when TaskScheduler::WEEKLY
              { weeks_interval: new_resource.frequency_modifier.to_i, days_of_week: days_of_week.to_i }
            when TaskScheduler::MONTHLYDATE
              { months: months_of_year.to_i, days: days_of_month.to_i }
            when TaskScheduler::MONTHLYDOW
              { months: months_of_year.to_i, days_of_week: days_of_week.to_i, weeks_of_month: weeks_of_month.to_i }
            when TaskScheduler::ON_IDLE
              # TODO: handle option for this trigger
            when TaskScheduler::AT_LOGON
              # TODO: handle option for this trigger
            when TaskScheduler::AT_SYSTEMSTART
              # TODO: handle option for this trigger
          end
        end

        # Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.
        def weeks_of_month
          weeks_of_month = []
          if new_resource.frequency_modifier
            weeks = new_resource.frequency_modifier.split(",")
            weeks.map! { |week| week.to_s.strip.upcase }
            weeks.delete("LAST") if weeks.include?("LAST")
            weeks_of_month = get_binary_values_from_constants(weeks, WEEKS_OF_MONTH)
          end
          weeks_of_month
        end

        # Deleting the "LAST" and "LASTDAY" from days since last day is handled in :run_on_last_day_of_month parameter.
        def days_of_month
          days_of_month = []
          if new_resource.day
            days = new_resource.day.to_s.split(",")
            days.map! { |day| day.to_s.strip.upcase }
            days.delete("LAST") if days.include?("LAST")
            days.delete("LASTDAY") if days.include?("LASTDAY")
            if days - (1..31).to_a
              days.each do |day|
                days_of_month << DAYS_OF_MONTH[day.to_i]
              end
              days_of_month = days_of_month.size > 1 ? days_of_month.inject(:|) : days_of_month[0]
            end
          else
            days_of_month = DAYS_OF_MONTH[1]
          end
          days_of_month
        end

        def days_of_week
          if new_resource.day
            # this line of code is just to support backward compatibility of wild card *
            new_resource.day = "mon, tue, wed, thu, fri, sat, sun" if new_resource.day == "*" && new_resource.frequency == :weekly
            days = new_resource.day.to_s.split(",")
            days.map! { |day| day.to_s.strip.upcase }
            weeks_days = get_binary_values_from_constants(days, DAYS_OF_WEEK)
          else
            # following condition will make the frequency :weekly idempotent if start_day is not provided by user setting day as the current_resource day
            if (current_resource) && (current_resource.task) && (current_resource.task.trigger(0)[:type][:days_of_week]) && (new_resource.start_day.nil?)
              weeks_days = current_resource.task.trigger(0)[:type][:days_of_week]
            else
              day = get_day(new_resource.start_day).to_sym if new_resource.start_day
              DAYS_OF_WEEK[day]
            end
          end
        end

        def months_of_year
          months_of_year = []
          if new_resource.frequency_modifier.to_i.between?(1, 12) && !(new_resource.months)
            new_resource.months = set_months(new_resource.frequency_modifier.to_i)
          end

          if new_resource.months
            # this line of code is just to support backward compatibility of wild card *
            new_resource.months = "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec" if new_resource.months == "*" && new_resource.frequency == :monthly
            months = new_resource.months.split(",")
            months.map! { |month| month.to_s.strip.upcase }
            months_of_year = get_binary_values_from_constants(months, MONTHS)
          else
            MONTHS.each do |key, value|
              months_of_year << MONTHS[key]
            end
            months_of_year = months_of_year.inject(:|)
          end
          months_of_year
        end

        # This values are set for frequency_modifier set as 1-12
        # This is to give backward compatibility validated this values with earlier code and running schtask.exe
        # Used this as reference https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday--
        def set_months(frequency_modifier)
          case frequency_modifier
            when 1
              "jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec"
            when 2
              "feb, apr, jun, aug, oct, dec"
            when 3
              "mar, jun, sep, dec"
            when 4
              "apr, aug, dec"
            when 5
              "may, oct"
            when 6
              "jun, dec"
            when 7
              "jul"
            when 8
              "aug"
            when 9
              "sep"
            when 10
              "oct"
            when 11
              "nov"
            when 12
              "dec"
          end
        end

        def get_binary_values_from_constants(array_values, constant)
          data = []
          array_values.each do |value|
            value = value.to_sym
            data << constant[value]
          end
          data.size > 1 ? data.inject(:|) : data[0]
        end

        def run_level
          case new_resource.run_level
          when :highest
            TaskScheduler::TASK_RUNLEVEL_HIGHEST
          when :limited
            TaskScheduler::TASK_RUNLEVEL_LUA
          end
        end

        # TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 formata
        def config_settings
          settings = {
            execution_time_limit: new_resource.execution_time_limit,
            enabled: true,
          }
          settings[:idle_duration] = new_resource.idle_time if new_resource.idle_time
          settings[:run_only_if_idle] = true if new_resource.idle_time
          settings[:priority] = new_resource.priority
          settings[:disallow_start_if_on_batteries] = new_resource.disallow_start_if_on_batteries
          settings[:stop_if_going_on_batteries] = new_resource.stop_if_going_on_batteries
          settings[:start_when_available] = new_resource.start_when_available
          settings
        end

        def principal_settings
          settings = {}
          settings[:run_level] = run_level
          settings[:logon_type] = logon_type
          settings
        end

        def description_needs_update?(task)
          task.description != new_resource.description unless new_resource.description.nil?
        end

        def logon_type
          # Ref: https://msdn.microsoft.com/en-us/library/windows/desktop/aa383566(v=vs.85).aspx
          # if nothing is passed as logon_type the TASK_LOGON_SERVICE_ACCOUNT is getting set as default so using that for comparision.
          user_id = new_resource.user.to_s
          password = new_resource.password.to_s
          if Chef::ReservedNames::Win32::Security::SID.service_account_user?(user_id)
            TaskScheduler::TASK_LOGON_SERVICE_ACCOUNT
          elsif Chef::ReservedNames::Win32::Security::SID.group_user?(user_id)
            TaskScheduler::TASK_LOGON_GROUP
          elsif !user_id.empty? && !password.empty?
            if new_resource.interactive_enabled
              TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
            else
              TaskScheduler::TASK_LOGON_PASSWORD
            end
          else
            TaskScheduler::TASK_LOGON_INTERACTIVE_TOKEN
          end
        end

        # This method checks if task and command properties exist since those two are mandatory properties to create a schedules task.
        def basic_validation
          validate = []
          validate << "Command" if new_resource.command.nil? || new_resource.command.empty?
          validate << "Task Name" if new_resource.task_name.nil? || new_resource.task_name.empty?
          return true if validate.empty?

          raise Chef::Exceptions::ValidationFailed.new "Value for '#{validate.join(", ")}' option cannot be empty"
        end

        # rubocop:disable Style/StringLiteralsInInterpolation
        def run_schtasks(task_action, options = {})
          cmd = "schtasks /#{task_action} /TN \"#{new_resource.task_name}\" "
          options.each_key do |option|
            unless option == "TR"
              cmd += "/#{option} "
              cmd += "\"#{options[option].to_s.gsub('"', "\\\"")}\" " unless options[option] == ""
            end
          end
          # Appending Task Run [TR] option at the end since appending causing sometimes to append other options in option["TR"] value
          if options["TR"]
            cmd += "/TR \"#{options["TR"]} \" " unless task_action == "DELETE"
          end
          logger.trace("running: ")
          logger.trace("    #{cmd}")
          shell_out!(cmd, returns: [0])
        end
        # rubocop:enable Style/StringLiteralsInInterpolation

        def get_day(date)
          Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
        end
      end
    end
  end
end