class Chef::Provider::WindowsTask

Constants

DAYS_OF_MONTH
DAYS_OF_WEEK
MONTHS
PRIORITY
WEEKS_OF_MONTH

Public Instance Methods

action_create() click to toggle source
# File lib/chef/provider/windows_task.rb, line 118
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
action_delete() click to toggle source
# File lib/chef/provider/windows_task.rb, line 170
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
action_disable() click to toggle source
# File lib/chef/provider/windows_task.rb, line 214
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
action_enable() click to toggle source
# File lib/chef/provider/windows_task.rb, line 197
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
action_end() click to toggle source
# File lib/chef/provider/windows_task.rb, line 182
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
action_run() click to toggle source
# File lib/chef/provider/windows_task.rb, line 155
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
basic_validation() click to toggle source

This method checks if task and command properties exist since those two are mandatory properties to create a schedules task.

# File lib/chef/provider/windows_task.rb, line 601
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
config_settings() click to toggle source

TODO: while creating the configuration settings win32-taskscheduler it accepts execution time limit values in ISO8601 formata

# File lib/chef/provider/windows_task.rb, line 555
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
convert_hours_in_minutes(hours) click to toggle source
# File lib/chef/provider/windows_task.rb, line 321
def convert_hours_in_minutes(hours)
  hours.to_i * 60 if hours
end
day_includes_last_or_lastday?(day) click to toggle source
# File lib/chef/provider/windows_task.rb, line 315
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
days_of_month() click to toggle source

Deleting the “LAST” and “LASTDAY” from days since last day is handled in :run_on_last_day_of_month parameter.

# File lib/chef/provider/windows_task.rb, line 446
def days_of_month
  days_of_month = []
  if new_resource.day
    days = new_resource.day.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
days_of_week() click to toggle source
# File lib/chef/provider/windows_task.rb, line 465
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.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
description_needs_update?(task) click to toggle source
# File lib/chef/provider/windows_task.rb, line 576
def description_needs_update?(task)
  task.description != new_resource.description unless new_resource.description.nil?
end
frequency_modifier_contains_last_week?(frequency_modifier) click to toggle source
# File lib/chef/provider/windows_task.rb, line 309
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
get_binary_values_from_constants(array_values, constant) click to toggle source
# File lib/chef/provider/windows_task.rb, line 536
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
get_day(date) click to toggle source

rubocop:enable Style/StringLiteralsInInterpolation

# File lib/chef/provider/windows_task.rb, line 628
def get_day(date)
  Date.strptime(date, "%m/%d/%Y").strftime("%a").upcase
end
load_current_resource() click to toggle source
# File lib/chef/provider/windows_task.rb, line 105
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
logon_type() click to toggle source
# File lib/chef/provider/windows_task.rb, line 580
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
months_of_year() click to toggle source
# File lib/chef/provider/windows_task.rb, line 483
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
principal_settings() click to toggle source
# File lib/chef/provider/windows_task.rb, line 569
def principal_settings
  settings = {}
  settings[:run_level] = run_level
  settings[:logon_type] = logon_type
  settings
end
run_level() click to toggle source
# File lib/chef/provider/windows_task.rb, line 545
def run_level
  case new_resource.run_level
  when :highest
    TaskScheduler::TASK_RUNLEVEL_HIGHEST
  when :limited
    TaskScheduler::TASK_RUNLEVEL_LUA
  end
end
run_schtasks(task_action, options = {}) click to toggle source

rubocop:disable Style/StringLiteralsInInterpolation

# File lib/chef/provider/windows_task.rb, line 610
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
set_command_and_arguments() click to toggle source

seprated command arguments from :command property

# File lib/chef/provider/windows_task.rb, line 235
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
set_months(frequency_modifier) click to toggle source

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 docs.microsoft.com/en-us/windows-server/administration/windows-commands/schtasks#d-dayday

# File lib/chef/provider/windows_task.rb, line 507
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
set_start_day_and_time() click to toggle source
# File lib/chef/provider/windows_task.rb, line 241
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
start_day_updated?(current_task_trigger, new_task_trigger) click to toggle source
# File lib/chef/provider/windows_task.rb, line 378
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
start_time_updated?(current_task_trigger, new_task_trigger) click to toggle source
# File lib/chef/provider/windows_task.rb, line 384
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
task_needs_update?(task) click to toggle source

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

# File lib/chef/provider/windows_task.rb, line 328
def task_needs_update?(task)
  flag = false
  if new_resource.frequency == :none
    flag = (task.account_information != 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.account_information.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
trigger() click to toggle source
# File lib/chef/provider/windows_task.rb, line 260
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
trigger_type() click to toggle source
# File lib/chef/provider/windows_task.rb, line 389
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
type() click to toggle source
# File lib/chef/provider/windows_task.rb, line 412
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
update_task(task) click to toggle source
# File lib/chef/provider/windows_task.rb, line 246
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
weeks_of_month() click to toggle source

Deleting last from the array of weeks of month since last week is handled in :run_on_last_week_of_month parameter.

# File lib/chef/provider/windows_task.rb, line 434
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