class Win32::TaskScheduler

The TaskScheduler class encapsulates taskscheduler settings and behavior

Constants

ABOVE_NORMAL
APRIL
AT_LOGON
AT_SYSTEMSTART
AUGUST
BELOW_NORMAL
DAILY
DECEMBER
DELETE_WHEN_DONE
DISABLED
DONT_START_IF_ON_BATTERIES
FEBRUARY
FIRST_WEEK
FLAG_DISABLED
FLAG_HAS_END_DATE
FLAG_KILL_AT_DURATION_END
FOURTH_WEEK
FRIDAY
HIDDEN
HIGH
IDLE

Shorthand constants

INTERACTIVE
JANUARY
JULY
JUNE
KILL_IF_GOING_ON_BATTERIES
KILL_ON_IDLE_END
LAST_WEEK
MARCH
MAX_RUN_TIMES
MAY
MONDAY
MONTHLYDATE
MONTHLYDOW
NORMAL
NOVEMBER
OCTOBER
ONCE
ON_IDLE
REALTIME
RESTART_ON_IDLE_RESUME
RUN_IF_CONNECTED_TO_INTERNET
RUN_ONLY_IF_DOCKED
RUN_ONLY_IF_LOGGED_ON
SATURDAY
SECOND_WEEK
SEPTEMBER
START_ONLY_IF_IDLE
SUNDAY
SYSTEM_REQUIRED
THIRD_WEEK
THURSDAY
TUESDAY
WEDNESDAY
WEEKLY

Public Instance Methods

account_information() click to toggle source

Returns the user associated with the task or nil if no user has yet been associated with the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 342
def account_information
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  # default under certain failures
  user = nil

  begin
    FFI::MemoryPointer.new(:pointer) do |ptr|
      @pITask.GetAccountInformation(ptr)
      ptr.read_com_memory_pointer do |str_ptr|
        user = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
      end
    end
  rescue Puppet::Util::Windows::Error => e
    raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET ||
                   e.code == SCHED_E_NO_SECURITY_SERVICES ||
                   e.code == ERROR_NONE_MAPPED
  end

  user
end
activate(task) click to toggle source

Activate the specified task.

# File lib/puppet/util/windows/taskscheduler.rb, line 231
def activate(task)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise TypeError unless task.is_a?(String)

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITS.Activate(wide_string(task), IID_ITask, ptr)

    reset_current_task
    @pITask = COM::Task.new(ptr.read_pointer)
  end

  @pITask
end
add_trigger(index, trigger) click to toggle source

Adds a trigger at the specified index.

# File lib/puppet/util/windows/taskscheduler.rb, line 630
def add_trigger(index, trigger)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless trigger.is_a?(Hash)

  @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger|
    populate_trigger(pITaskTrigger, trigger)
  end
end
application_name() click to toggle source

Returns the name of the application associated with the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 367
def application_name
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  app = nil

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetApplicationName(ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      app = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
    end
  end

  app
end
application_name=(app) click to toggle source

Sets the application name associated with the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 386
def application_name=(app)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless app.is_a?(String)

  @pITask.SetApplicationName(wide_string(app))

  app
end
comment() click to toggle source

Returns the comment associated with the task, if any.

# File lib/puppet/util/windows/taskscheduler.rb, line 714
def comment
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  comment = nil

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetComment(ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      comment = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
    end
  end

  comment
end
comment=(comment) click to toggle source

Sets the comment for the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 732
def comment=(comment)
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless comment.is_a?(String)

  @pITask.SetComment(wide_string(comment))
  comment
end
creator() click to toggle source

Returns the name of the user who created the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 742
def creator
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  creator = nil

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetCreator(ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      creator = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
    end
  end

  creator
end
creator=(creator) click to toggle source

Sets the creator for the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 760
def creator=(creator)
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless creator.is_a?(String)

  @pITask.SetCreator(wide_string(creator))
  creator
end
delete(task) click to toggle source

Delete the specified task name.

# File lib/puppet/util/windows/taskscheduler.rb, line 247
def delete(task)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise TypeError unless task.is_a?(String)

  @pITS.Delete(wide_string(task))

  true
end
delete_trigger(index) click to toggle source

Deletes the trigger at the specified index.

# File lib/puppet/util/windows/taskscheduler.rb, line 581
def delete_trigger(index)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  @pITask.DeleteTrigger(index)
  index
end
enum() click to toggle source

Returns an array of scheduled task names.

# File lib/puppet/util/windows/taskscheduler.rb, line 198
def enum
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  array = []

  @pITS.UseInstance(COM::EnumWorkItems, :Enum) do |pIEnum|
    FFI::MemoryPointer.new(:pointer) do |names_array_ptr_ptr|
      FFI::MemoryPointer.new(:win32_ulong) do |fetched_count_ptr|
        # awkward usage, if number requested is available, returns S_OK (0), or if less were returned returns S_FALSE (1)
        while (pIEnum.Next(TASKS_TO_RETRIEVE, names_array_ptr_ptr, fetched_count_ptr) >= Puppet::Util::Windows::COM::S_OK)
          count = fetched_count_ptr.read_win32_ulong
          break if count == 0

          names_array_ptr_ptr.read_com_memory_pointer do |names_array_ptr|
            # iterate over the array of pointers
            name_ptr_ptr = FFI::Pointer.new(:pointer, names_array_ptr)
            for i in 0 ... count
              name_ptr_ptr[i].read_com_memory_pointer do |name_ptr|
                array << name_ptr.read_arbitrary_wide_string_up_to(256)
              end
            end
          end
        end
      end
    end
  end

  array
end
Also aliased as: tasks
exists?(job_name) click to toggle source

Returns whether or not the scheduled task exists.

# File lib/puppet/util/windows/taskscheduler.rb, line 831
def exists?(job_name)
  bool = false
  Dir.foreach('C:/Windows/Tasks'){ |file|
    if File.basename(file, '.job') == job_name
      bool = true
      break
    end
  }
  bool
end
exit_code() click to toggle source

Returns the exit code from the last scheduled run.

# File lib/puppet/util/windows/taskscheduler.rb, line 695
def exit_code
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  status = 0

  begin
    FFI::MemoryPointer.new(:dword, 1) do |ptr|
      @pITask.GetExitCode(ptr)
      status = ptr.read_dword
    end
  rescue Puppet::Util::Windows::Error => e
    raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN
  end

  status
end
flags() click to toggle source

Returns the flags (integer) that modify the behavior of the work item. You must OR the return value to determine the flags yourself.

# File lib/puppet/util/windows/taskscheduler.rb, line 643
def flags
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  flags = 0

  FFI::MemoryPointer.new(:dword, 1) do |ptr|
    @pITask.GetFlags(ptr)
    flags = ptr.read_dword
  end

  flags
end
flags=(flags) click to toggle source

Sets an OR’d value of flags that modify the behavior of the work item.

# File lib/puppet/util/windows/taskscheduler.rb, line 658
def flags=(flags)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  @pITask.SetFlags(flags)
  flags
end
host=(host) click to toggle source
Alias for: machine=
machine=(host) click to toggle source

Set the host on which the various TaskScheduler methods will execute.

# File lib/puppet/util/windows/taskscheduler.rb, line 296
def machine=(host)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise TypeError unless host.is_a?(String)

  @pITS.SetTargetComputer(wide_string(host))

  host
end
Also aliased as: host=
max_run_time() click to toggle source

Returns the maximum length of time, in milliseconds, that the task will run before terminating.

# File lib/puppet/util/windows/taskscheduler.rb, line 806
def max_run_time
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  max_run_time = nil

  FFI::MemoryPointer.new(:dword, 1) do |ptr|
    @pITask.GetMaxRunTime(ptr)
    max_run_time = ptr.read_dword
  end

  max_run_time
end
max_run_time=(max_run_time) click to toggle source

Sets the maximum length of time, in milliseconds, that the task can run before terminating. Returns the value you specified if successful.

# File lib/puppet/util/windows/taskscheduler.rb, line 822
def max_run_time=(max_run_time)
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless max_run_time.is_a?(Numeric)

  @pITask.SetMaxRunTime(max_run_time)
  max_run_time
end
most_recent_run_time() click to toggle source

Returns a Time object indicating the most recent time the task ran or nil if the task has never run.

# File lib/puppet/util/windows/taskscheduler.rb, line 786
def most_recent_run_time
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  time = nil

  begin
    FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr|
      @pITask.GetMostRecentRunTime(ptr)
      time = WIN32::SYSTEMTIME.new(ptr).to_local_time
    end
  rescue Puppet::Util::Windows::Error => e
    raise e unless e.code == SCHED_S_TASK_HAS_NOT_RUN
  end

  time
end
new_task(task, trigger) click to toggle source
Alias for: new_work_item
new_work_item(task, trigger) click to toggle source

Creates a new work item (scheduled job) with the given trigger. The trigger variable is a hash of options that define when the scheduled job should run.

# File lib/puppet/util/windows/taskscheduler.rb, line 509
def new_work_item(task, trigger)
  raise TypeError unless trigger.is_a?(Hash)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?

  # I'm working around github issue #1 here.
  enum.each{ |name|
    if name.downcase == task.downcase + '.job'
      raise Error.new("task '#{task}' already exists")
    end
  }

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITS.NewWorkItem(wide_string(task), CLSID_CTask, IID_ITask, ptr)

    reset_current_task
    @pITask = COM::Task.new(ptr.read_pointer)

    FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr|
      # Without the 'enum.include?' check above the code segfaults here if the
      # task already exists. This should probably be handled properly instead
      # of simply avoiding the issue.

      @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger|
        populate_trigger(pITaskTrigger, trigger)
      end
    end
  end

  @pITask
end
Also aliased as: new_task
next_run_time() click to toggle source

Returns a Time object that indicates the next time the task will run.

# File lib/puppet/util/windows/taskscheduler.rb, line 770
def next_run_time
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  time = nil

  FFI::MemoryPointer.new(WIN32::SYSTEMTIME.size) do |ptr|
    @pITask.GetNextRunTime(ptr)
    time = WIN32::SYSTEMTIME.new(ptr).to_local_time
  end

  time
end
parameters() click to toggle source

Returns the command line parameters for the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 398
def parameters
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  param = nil

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetParameters(ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      param = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
    end
  end

  param
end
parameters=(param) click to toggle source

Sets the parameters for the task. These parameters are passed as command line arguments to the application the task will run. To clear the command line parameters set it to an empty string.

# File lib/puppet/util/windows/taskscheduler.rb, line 419
def parameters=(param)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless param.is_a?(String)

  @pITask.SetParameters(wide_string(param))

  param
end
priority() click to toggle source

Returns the task’s priority level. Possible values are ‘idle’, ‘normal’, ‘high’, ‘realtime’, ‘below_normal’, ‘above_normal’, and ‘unknown’.

# File lib/puppet/util/windows/taskscheduler.rb, line 464
def priority
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  FFI::MemoryPointer.new(:dword, 1) do |ptr|
    @pITask.GetPriority(ptr)

    pri = ptr.read_dword
    if (pri & IDLE) != 0
      priority = 'idle'
    elsif (pri & NORMAL) != 0
      priority = 'normal'
    elsif (pri & HIGH) != 0
      priority = 'high'
    elsif (pri & REALTIME) != 0
      priority = 'realtime'
    elsif (pri & BELOW_NORMAL) != 0
      priority = 'below_normal'
    elsif (pri & ABOVE_NORMAL) != 0
      priority = 'above_normal'
    else
      priority = 'unknown'
    end
  end

  priority
end
priority=(priority) click to toggle source

Sets the priority of the task. The priority should be a numeric priority constant value.

# File lib/puppet/util/windows/taskscheduler.rb, line 495
def priority=(priority)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless priority.is_a?(Numeric)

  @pITask.SetPriority(priority)

  priority
end
run() click to toggle source

Execute the current task.

# File lib/puppet/util/windows/taskscheduler.rb, line 258
def run
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  @pITask.Run
end
save(file = nil) click to toggle source

Saves the current task. Tasks must be saved before they can be activated. The .job file itself is typically stored in the C:WINDOWSTasks folder.

If file (an absolute path) is specified then the job is saved to that file instead. A ‘.job’ extension is recommended but not enforced.

# File lib/puppet/util/windows/taskscheduler.rb, line 270
def save(file = nil)
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  reset = true

  begin
    @pITask.QueryInstance(COM::PersistFile) do |pIPersistFile|
      pIPersistFile.Save(wide_string(file), 1)
    end
  rescue
    reset = false
  ensure
    reset_current_task if reset
  end
end
set_account_information(user, password) click to toggle source

Sets the user and password for the given task. If the user and password are set properly then true is returned.

In some cases the job may be created, but the account information was bad. In this case the task is created but a warning is generated and false is returned.

# File lib/puppet/util/windows/taskscheduler.rb, line 314
def set_account_information(user, password)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  bool = false

  begin
    if (user.nil? || user=="") && (password.nil? || password=="")
      @pITask.SetAccountInformation(wide_string(""), FFI::Pointer::NULL)
    else
      user = wide_string(user)
      password = wide_string(password)
      @pITask.SetAccountInformation(user, password)
    end

    bool = true
  rescue Puppet::Util::Windows::Error => e
    raise e unless e.code == SCHED_E_ACCOUNT_INFORMATION_NOT_SET

    warn 'job created, but password was invalid'
  end

  bool
end
status() click to toggle source

Returns the status of the currently active task. Possible values are ‘ready’, ‘running’, ‘not scheduled’ or ‘unknown’.

# File lib/puppet/util/windows/taskscheduler.rb, line 669
def status
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  st = nil

  FFI::MemoryPointer.new(:hresult, 1) do |ptr|
    @pITask.GetStatus(ptr)
    st = ptr.read_hresult
  end

  case st
    when SCHED_S_TASK_READY
       status = 'ready'
    when SCHED_S_TASK_RUNNING
       status = 'running'
    when SCHED_S_TASK_NOT_SCHEDULED
       status = 'not scheduled'
    else
       status = 'unknown'
  end

  status
end
tasks() click to toggle source
Alias for: enum
terminate() click to toggle source

Terminate the current task.

# File lib/puppet/util/windows/taskscheduler.rb, line 288
def terminate
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  @pITask.Terminate
end
trigger(index) click to toggle source

Returns a hash that describes the trigger at the given index for the current task.

# File lib/puppet/util/windows/taskscheduler.rb, line 592
def trigger(index)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  trigger = {}

  @pITask.UseInstance(COM::TaskTrigger, :GetTrigger, index) do |pITaskTrigger|
    FFI::MemoryPointer.new(COM::TASK_TRIGGER.size) do |task_trigger_ptr|
      pITaskTrigger.GetTrigger(task_trigger_ptr)
      trigger = populate_hash_from_trigger(COM::TASK_TRIGGER.new(task_trigger_ptr))
    end
  end

  trigger
end
trigger=(trigger) click to toggle source

Sets the trigger for the currently active task.

# File lib/puppet/util/windows/taskscheduler.rb, line 610
def trigger=(trigger)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless trigger.is_a?(Hash)

  FFI::MemoryPointer.new(:word, 1) do |trigger_index_ptr|
    # Without the 'enum.include?' check above the code segfaults here if the
    # task already exists. This should probably be handled properly instead
    # of simply avoiding the issue.

    @pITask.UseInstance(COM::TaskTrigger, :CreateTrigger, trigger_index_ptr) do |pITaskTrigger|
      populate_trigger(pITaskTrigger, trigger)
    end
  end

  trigger
end
trigger_count() click to toggle source

Returns the number of triggers associated with the active task.

# File lib/puppet/util/windows/taskscheduler.rb, line 544
def trigger_count
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  count = 0

  FFI::MemoryPointer.new(:word, 1) do |ptr|
    @pITask.GetTriggerCount(ptr)
    count = ptr.read_word
  end

  count
end
trigger_string(index) click to toggle source

Returns a string that describes the current trigger at the specified index for the active task.

Example: “At 7:14 AM every day, starting 4/11/2009”

# File lib/puppet/util/windows/taskscheduler.rb, line 563
def trigger_string(index)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless index.is_a?(Numeric)

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetTriggerString(index, ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      trigger = str_ptr.read_arbitrary_wide_string_up_to(256)
    end
  end

  trigger
end
working_directory() click to toggle source

Returns the working directory for the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 431
def working_directory
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?

  dir = nil

  FFI::MemoryPointer.new(:pointer) do |ptr|
    @pITask.GetWorkingDirectory(ptr)

    ptr.read_com_memory_pointer do |str_ptr|
      dir = str_ptr.read_arbitrary_wide_string_up_to(256) if ! str_ptr.null?
    end
  end

  dir
end
working_directory=(dir) click to toggle source

Sets the working directory for the task.

# File lib/puppet/util/windows/taskscheduler.rb, line 450
def working_directory=(dir)
  raise Error.new('No current task scheduler. ITaskScheduler is NULL.') if @pITS.nil?
  raise Error.new('No currently active task. ITask is NULL.') if @pITask.nil?
  raise TypeError unless dir.is_a?(String)

  @pITask.SetWorkingDirectory(wide_string(dir))

  dir
end

Public Class Methods

new(work_item=nil, trigger=nil) click to toggle source

Returns a new TaskScheduler object. If a work_item (and possibly the the trigger) are passed as arguments then a new work item is created and associated with that trigger, although you can still activate other tasks with the same handle.

This is really just a bit of convenience. Passing arguments to the constructor is the same as calling ::new plus #new_work_item.

# File lib/puppet/util/windows/taskscheduler.rb, line 170
def initialize(work_item=nil, trigger=nil)
  @pITS   = nil
  @pITask = nil

  if ! self.class.com_initialized
    Puppet::Util::Windows::COM.InitializeCom()
    self.class.com_initialized = true
  end

  @pITS = COM::TaskScheduler.new
  at_exit do
    begin
      @pITS.Release if @pITS && !@pITS.null?
      @pITS = nil
    rescue
    end
  end

  if work_item
    if trigger
      raise TypeError unless trigger.is_a?(Hash)
      new_work_item(work_item, trigger)
    end
  end
end