/*
NOTICE OF CHANGES:
date of change: 8/3/2007
change: Exposed a property to express the Enabled/Disabled state of the job.
*/
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using TaskSchedulerInterop;
using SMStrings;
namespace TaskScheduler {
#region Enums
///
/// Options for a task, used for the Flags property of a Task. Uses the
/// "Flags" attribute, so these values are combined with |.
/// Some flags are documented as Windows 95 only, but they have a
/// user interface in Windows XP so that may not be true.
///
[Flags]
public enum TaskFlags {
///
/// The precise meaning of this flag is elusive. The MSDN documentation describes it
/// only for use in converting jobs from the Windows NT "AT" service to the newer
/// Task Scheduler. No other use for the flag is documented.
///
Interactive = 0x1,
///
/// The task will be deleted when there are no more scheduled run times.
///
DeleteWhenDone = 0x2,
///
/// The task is disabled. Used to temporarily prevent a task from being triggered normally.
///
Disabled = 0x4,
///
/// The task begins only if the computer is idle at the scheduled start time.
/// The computer is not considered idle until the task's time
/// elapses with no user input.
///
StartOnlyIfIdle = 0x10,
///
/// The task terminates if the computer makes an idle to non-idle transition while the task is running.
/// For information regarding idle triggers, see .
///
KillOnIdleEnd = 0x20,
///
/// The task does not start if the computer is running on battery power.
///
DontStartIfOnBatteries = 0x40,
///
/// The task ends, and the associated application quits if the computer switches
/// to battery power.
///
KillIfGoingOnBatteries = 0x80,
///
/// The task runs only if the system is docked.
/// (Not mentioned in current MSDN documentation; probably obsolete.)
///
RunOnlyIfDocked = 0x100,
///
/// The task item is hidden.
///
/// This is implemented by setting the job file's hidden attribute. Testing revealed that clearing
/// this flag doesn't clear the file attribute, so the library sets the file attribute directly. This
/// flag is kept in sync with the task's Hidden property, so they function equivalently.
///
Hidden = 0x200,
///
/// The task runs only if there is currently a valid Internet connection.
/// Not currently implemented. (Check current MSDN documentation for updates.)
///
RunIfConnectedToInternet = 0x400,
///
/// The task starts again if the computer makes a non-idle to idle transition before all the
/// task's task_triggers elapse. (Use this flag in conjunction with KillOnIdleEnd.)
///
RestartOnIdleResume = 0x800,
///
/// Wake the computer to run this task. Seems to be misnamed, but the name is taken from
/// the low-level interface.
///
///
SystemRequired = 0x1000,
///
/// The task runs only if the user specified in SetAccountInformation() is
/// logged on interactively. This flag has no effect on tasks set to run in
/// the local SYSTEM account.
///
RunOnlyIfLoggedOn = 0x2000
}
///
/// Status values returned for a task. Some values have been determined to occur although
/// they do no appear in the Task Scheduler system documentation.
///
public enum TaskStatus {
///
/// The task is ready to run at its next scheduled time.
///
Ready = HResult.SCHED_S_TASK_READY,
///
/// The task is currently running.
///
Running = HResult.SCHED_S_TASK_RUNNING,
///
/// One or more of the properties that are needed to run this task on a schedule have not been set.
///
NotScheduled = HResult.SCHED_S_TASK_NOT_SCHEDULED,
///
/// The task has not yet run.
///
NeverRun = HResult.SCHED_S_TASK_HAS_NOT_RUN,
///
/// The task will not run at the scheduled times because it has been disabled.
///
Disabled = HResult.SCHED_S_TASK_DISABLED,
///
/// There are no more runs scheduled for this task.
///
NoMoreRuns = HResult.SCHED_S_TASK_NO_MORE_RUNS,
///
/// The last run of the task was terminated by the user.
///
Terminated = HResult.SCHED_S_TASK_TERMINATED,
///
/// Either the task has no triggers or the existing triggers are disabled or not set.
///
NoTriggers = HResult.SCHED_S_TASK_NO_VALID_TRIGGERS,
///
/// Event triggers don't have set run times.
///
NoTriggerTime = HResult.SCHED_S_EVENT_TRIGGER
}
#endregion
///
/// Represents an item in the Scheduled Tasks folder. There are no public constructors for Task.
/// New instances are generated by a object using Open or Create methods.
/// A task object holds COM interfaces; call its method to release them.
///
public class Task : IDisposable {
#region Fields
///
/// Internal COM interface
///
private ITask iTask;
///
/// Name of this task (with no .job extension)
///
private string name;
///
/// List of triggers for this task
///
private TriggerList triggers;
#endregion
#region Constructors
///
/// Internal constructor for a task, used by .
///
/// Instance of an ITask.
/// Name of the task.
internal Task(ITask iTask, string taskName) {
this.iTask = iTask;
if (taskName.EndsWith(".job"))
name = taskName.Substring(0, taskName.Length-4);
else
name = taskName;
triggers = null;
this.Hidden = GetHiddenFileAttr();
}
#endregion
#region Properties
///
/// Gets the name of the task. The name is also the filename (plus a .job extension)
/// the Task Scheduler uses to store the task information. To change the name of a
/// task, use to save it as a new name and then delete
/// the old task.
///
public string Name {
get {
return name;
}
}
///
/// Gets the list of triggers associated with the task.
///
public TriggerList Triggers {
get {
if (triggers == null) {
// Trigger list has not been requested before; create it
triggers = new TriggerList(iTask);
}
return triggers;
}
}
///
/// Gets/sets the application filename that task is to run. Get returns
/// an absolute pathname. A name searched with the PATH environment variable can
/// be assigned, and the path search is done when the task is saved.
///
public string ApplicationName {
get {
IntPtr lpwstr;
iTask.GetApplicationName(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
set {
iTask.SetApplicationName(value);
}
}
///
/// Gets the name of the account under which the task process will run.
///
public string AccountName {
get {
IntPtr lpwstr = IntPtr.Zero;
iTask.GetAccountInformation(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
}
///
/// Gets/sets the comment associated with the task. The comment appears in the
/// Scheduled Tasks user interface.
///
public string Comment {
get {
IntPtr lpwstr;
iTask.GetComment(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
set {
iTask.SetComment(value);
}
}
///
/// Gets/sets the creator of the task. If no value is supplied, the system
/// fills in the account name of the caller when the task is saved.
///
public string Creator {
get {
IntPtr lpwstr;
iTask.GetCreator(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
set {
iTask.SetCreator(value);
}
}
///
/// Gets/sets the number of times to retry task execution after failure. (Not implemented.)
///
private short ErrorRetryCount {
get {
ushort ret;
iTask.GetErrorRetryCount(out ret);
return (short)ret;
}
set {
iTask.SetErrorRetryCount((ushort)value);
}
}
///
/// Gets/sets the time interval, in minutes, to delay between error retries. (Not implemented.)
///
private short ErrorRetryInterval {
get {
ushort ret;
iTask.GetErrorRetryInterval(out ret);
return (short)ret;
}
set {
iTask.SetErrorRetryInterval((ushort)value);
}
}
///
/// Gets the Win32 exit code from the last execution of the task. If the task failed
/// to start on its last run, the reason is returned as an exception. Not updated while
/// in an open task; the property does not change unless the task is closed and re-opened.
/// Various exceptions for a task that couldn't be run.
///
public int ExitCode {
get {
uint ret = 0;
iTask.GetExitCode(out ret);
return (int)ret;
}
}
///
/// Gets/sets the associated with the current task.
///
public TaskFlags Flags {
get {
uint ret;
iTask.GetFlags(out ret);
return (TaskFlags)ret;
}
set {
iTask.SetFlags((uint)value);
}
}
///
/// Gets/sets how long the system must remain idle, even after the trigger
/// would normally fire, before the task will run.
///
public short IdleWaitMinutes {
get {
ushort ret, nothing;
iTask.GetIdleWait(out ret, out nothing);
return (short)ret;
}
set {
ushort m = (ushort)IdleWaitDeadlineMinutes;
iTask.SetIdleWait((ushort)value, m);
}
}
///
/// Gets/sets the maximum number of minutes that Task Scheduler will wait for a
/// required idle period to occur.
///
public short IdleWaitDeadlineMinutes {
get {
ushort ret, nothing;
iTask.GetIdleWait(out nothing, out ret);
return (short)ret;
}
set {
ushort m = (ushort)IdleWaitMinutes;
iTask.SetIdleWait(m, (ushort)value);
}
}
///
/// Gets/sets the maximum length of time the task is permitted to run.
/// Setting MaxRunTime also affects the value of .
///
/// The longest MaxRunTime implemented is 0xFFFFFFFE milliseconds, or
/// about 50 days. If you set a TimeSpan longer than that, the
/// MaxRunTime will be unlimited.
///
///
///
public TimeSpan MaxRunTime {
get {
uint ret;
iTask.GetMaxRunTime(out ret);
return new TimeSpan((long)ret * TimeSpan.TicksPerMillisecond);
}
set {
double proposed = ((TimeSpan)value).TotalMilliseconds;
if (proposed >= uint.MaxValue) {
iTask.SetMaxRunTime(uint.MaxValue);
} else {
iTask.SetMaxRunTime((uint)proposed);
}
//iTask.SetMaxRunTime((uint)((TimeSpan)value).TotalMilliseconds);
}
}
///
/// If the maximum run time is limited, the task will be terminated after
/// expires. Setting the value to FALSE, i.e. unlimited,
/// invalidates MaxRunTime.
/// The Task Scheduler service will try to send a WM_CLOSE message when it needs to terminate
/// a task. If the message can't be sent, or the task does not respond with three minutes,
/// the task will be terminated using TerminateProcess.
///
public bool MaxRunTimeLimited {
get {
uint ret;
iTask.GetMaxRunTime(out ret);
return (ret == uint.MaxValue);
}
set {
if (value) {
uint ret;
iTask.GetMaxRunTime(out ret);
if (ret == uint.MaxValue) {
iTask.SetMaxRunTime(72*360*1000); //72 hours. Thats what Explorer sets.
}
} else {
iTask.SetMaxRunTime(uint.MaxValue);
}
}
}
///
/// Gets the most recent time the task began running.
/// returned if the task has not run.
///
public DateTime MostRecentRunTime {
get {
SystemTime st = new SystemTime();
iTask.GetMostRecentRunTime(ref st);
if (st.Year == 0)
return DateTime.MinValue;
return new DateTime((int)st.Year, (int)st.Month, (int)st.Day, (int)st.Hour, (int)st.Minute, (int)st.Second, (int)st.Milliseconds);
}
}
///
/// Gets the next time the task will run. Returns
/// if the task is not scheduled to run.
///
public DateTime NextRunTime {
get {
SystemTime st = new SystemTime();
iTask.GetNextRunTime(ref st);
if (st.Year == 0)
return DateTime.MinValue;
return new DateTime((int)st.Year, (int)st.Month, (int)st.Day, (int)st.Hour, (int)st.Minute, (int)st.Second, (int)st.Milliseconds);
}
}
///
/// Gets/sets the command-line parameters for the task.
///
public string Parameters {
get {
IntPtr lpwstr;
iTask.GetParameters(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
set {
iTask.SetParameters(value);
}
}
///
/// Gets/sets the priority for the task process.
/// Note: ProcessPriorityClass defines two levels (AboveNormal and BelowNormal) that are
/// not documented in the task scheduler interface and can't be use on Win 98 platforms.
///
public System.Diagnostics.ProcessPriorityClass Priority {
get {
uint ret;
iTask.GetPriority(out ret);
return (System.Diagnostics.ProcessPriorityClass)ret;
}
set {
if (value==System.Diagnostics.ProcessPriorityClass.AboveNormal ||
value==System.Diagnostics.ProcessPriorityClass.BelowNormal ) {
throw new ArgumentException(SMSharedStringTable.GetString("SMSCHEDULER_UNSUPPORTEDPRI_22"));
}
iTask.SetPriority((uint)value);
}
}
///
/// Gets the status of the task. Returns .
/// Not updated while a task is open.
///
public TaskStatus Status {
get {
int ret;
iTask.GetStatus(out ret);
return (TaskStatus)ret;
}
}
///
/// Extended Flags associated with a task. These are associated with the ITask com interface
/// and none are currently defined.
///
private int FlagsEx {
get {
uint ret;
iTask.GetTaskFlags(out ret);
return (int)ret;
}
set {
iTask.SetTaskFlags((uint)value);
}
}
///
/// Gets/sets the initial working directory for the task.
///
public string WorkingDirectory {
get {
IntPtr lpwstr;
iTask.GetWorkingDirectory(out lpwstr);
return CoTaskMem.LPWStrToString(lpwstr);
}
set {
iTask.SetWorkingDirectory(value);
}
}
///
/// Hidden tasks are stored in files with
/// the hidden file attribute so they don't appear in the Explorer user interface.
/// Because there is a special interface for Scheduled Tasks, they don't appear
/// even if Explorer is set to show hidden files.
/// Functionally equivalent to TaskFlags.Hidden.
///
public bool Hidden {
get {
return (this.Flags & TaskFlags.Hidden) != 0;
}
set {
if (value) {
this.Flags |= TaskFlags.Hidden;
} else {
this.Flags &= ~TaskFlags.Hidden;
}
}
}
///
/// Gets/sets arbitrary data associated with the task. The tag can be used for any purpose
/// by the client, and is not used by the Task Scheduler. Known as WorkItemData in the
/// IWorkItem com interface.
///
public object Tag {
get {
ushort DataLen;
IntPtr Data;
iTask.GetWorkItemData(out DataLen, out Data);
byte[] bytes = new byte[DataLen];
Marshal.Copy(Data, bytes, 0, DataLen);
MemoryStream stream = new MemoryStream(bytes, false);
BinaryFormatter b = new BinaryFormatter();
return b.Deserialize(stream);
}
set {
if (!value.GetType().IsSerializable)
throw new ArgumentException(SMSharedStringTable.GetString("SMSCHEDULER_OBJECTSSETASDA_19"), "value");
BinaryFormatter b = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
b.Serialize(stream, value);
iTask.SetWorkItemData((ushort)stream.Length, stream.GetBuffer());
}
}
#endregion
#region Methods
///
/// Set the hidden attribute on the file corresponding to this task.
///
/// Set the attribute accordingly.
private void SetHiddenFileAttr(bool set) {
UCOMIPersistFile iFile = (UCOMIPersistFile)iTask;
string fileName;
iFile.GetCurFile(out fileName);
System.IO.FileAttributes attr;
attr = System.IO.File.GetAttributes(fileName);
if (set)
attr |= System.IO.FileAttributes.Hidden;
else
attr &= ~System.IO.FileAttributes.Hidden;
System.IO.File.SetAttributes(fileName, attr);
}
///
/// Get the hidden attribute from the file corresponding to this task.
///
/// The value of the attribute.
private bool GetHiddenFileAttr() {
UCOMIPersistFile iFile = (UCOMIPersistFile)iTask;
string fileName;
iFile.GetCurFile(out fileName);
System.IO.FileAttributes attr;
try {
attr = System.IO.File.GetAttributes(fileName);
return (attr & System.IO.FileAttributes.Hidden) != 0;
} catch {
return false;
}
}
///
/// Calculate the next time the task would be scheduled
/// to run after a given arbitrary time. If the task will not run
/// (perhaps disabled) then returns .
///
/// The time to calculate from.
/// The next time the task would run.
public DateTime NextRunTimeAfter(DateTime after) {
//Add one second to get a run time strictly greater than the specified time.
after = after.AddSeconds(1);
//Convert to a valid SystemTime
SystemTime stAfter = new SystemTime();
stAfter.Year = (ushort)after.Year;
stAfter.Month = (ushort)after.Month;
stAfter.Day = (ushort)after.Day;
stAfter.DayOfWeek = (ushort)after.DayOfWeek;
stAfter.Hour = (ushort)after.Hour;
stAfter.Minute = (ushort)after.Minute;
stAfter.Second = (ushort)after.Second;
SystemTime stLimit = new SystemTime();
// Would like to pass null as the second parameter to GetRunTimes, indicating that
// the interval is unlimited. Can't figure out how to do that, so use a big time value.
stLimit = stAfter;
stLimit.Year = (ushort)DateTime.MaxValue.Year;
IntPtr pTimes;
ushort nFetch = 1;
iTask.GetRunTimes(ref stAfter, ref stLimit, ref nFetch, out pTimes);
if (nFetch == 1) {
SystemTime stNext = new SystemTime();
stNext = (SystemTime)Marshal.PtrToStructure(pTimes, typeof(SystemTime));
Marshal.FreeCoTaskMem(pTimes);
return new DateTime(stNext.Year, stNext.Month, stNext.Day, stNext.Hour, stNext.Minute, stNext.Second);
} else {
return DateTime.MinValue;
}
}
///
/// Schedules the task for immediate execution.
/// The system works from the saved version of the task, so call before running.
/// If the task has never been saved, it throws an argument exception. Problems starting
/// the task are reported by the property, not by exceptions on Run.
///
/// The system never updates an open task, so you don't get current results for
/// the or the properties until you close
/// and reopen the task.
///
///
public void Run() {
iTask.Run();
}
///
/// Saves changes to the established task name.
///
/// Saves changes that have been made to this Task.
/// The account name is checked for validity
/// when a Task is saved. The password is not checked, but the account name
/// must be valid (or empty).
///
/// Unable to establish existence of the account specified.
public void Save() {
UCOMIPersistFile iFile = (UCOMIPersistFile)iTask;
iFile.Save(null, false);
SetHiddenFileAttr(Hidden); //Do the Task Scheduler's work for it because it doesn't reset properly
}
///
/// Saves the Task with a new name. The task with the old name continues to
/// exist in whatever state it was last saved. It is no longer open, because.
/// the Task object is associated with the new name from now on.
/// If there is already a task using the new name, it is overwritten.
///
/// See the () overload.
/// The new name to be used for this task.
/// Unable to establish existence of the account specified.
public void Save(string name) {
UCOMIPersistFile iFile = (UCOMIPersistFile)iTask;
string path;
iFile.GetCurFile(out path);
string newPath;
newPath = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + name + Path.GetExtension(path);
iFile.Save(newPath, true);
iFile.SaveCompleted(newPath); /* probably unnecessary */
this.name = name;
SetHiddenFileAttr(Hidden); //Do the Task Scheduler's work for it because it doesn't reset properly
}
///
/// Release COM interfaces for this Task. After a Task is closed, accessing its
/// members throws a null reference exception.
///
public void Close() {
if (triggers != null) {
triggers.Dispose();
}
Marshal.ReleaseComObject(iTask);
iTask = null;
}
///
/// For compatibility with earlier versions. New clients should use .
///
///
/// Display the property pages of this task for user editing. If the user clicks OK, the
/// task's properties are updated and the task is also automatically saved.
///
public void DisplayForEdit() {
iTask.EditWorkItem(0, 0);
}
///
/// Argument for DisplayForEdit to determine which property pages to display.
///
[Flags]
public enum PropPages {
///
/// The task property page
///
Task = 0x01,
///
/// The schedule property page
///
Schedule = 0x02,
///
/// The setting property page
///
Settings = 0x04
}
///
///
/// Display all property pages.
///
///
/// The method does not return until the user has dismissed the dialog box.
/// If the dialog box is dismissed with the OK button, returns true and
/// updates properties in the task.
/// The changes are not made permanent, however, until the task is saved. (Save() method.)
///
/// true if dialog box was dismissed with OK, otherwise false.
/// Display the property pages of this task for user editing.
public bool DisplayPropertySheet() {
//iTask.EditWorkItem(0, 0); //This implementation saves automatically, so we don't use it.
return DisplayPropertySheet(PropPages.Task | PropPages.Schedule | PropPages.Settings);
}
///
/// Display only the specified property pages.
///
///
/// See the () overload.
///
/// Controls which pages are presented
/// true if dialog box was dismissed with OK, otherwise false.
public bool DisplayPropertySheet(PropPages pages) {
PropSheetHeader hdr = new PropSheetHeader();
IProvideTaskPage iProvideTaskPage = (IProvideTaskPage)iTask;
IntPtr[] hPages = new IntPtr[3];
IntPtr hPage;
int nPages = 0;
if ((pages & PropPages.Task) != 0) {
//get task page
iProvideTaskPage.GetPage(0, false, out hPage);
hPages[nPages++] = hPage;
}
if ((pages & PropPages.Schedule) != 0) {
//get task page
iProvideTaskPage.GetPage(1, false, out hPage);
hPages[nPages++] = hPage;
}
if ((pages & PropPages.Settings) != 0) {
//get task page
iProvideTaskPage.GetPage(2, false, out hPage);
hPages[nPages++] = hPage;
}
if (nPages == 0)
throw (new ArgumentException(SMSharedStringTable.GetString("SMSCHEDULER_NOPROPERTYPAGE_18")));
hdr.dwSize = (uint)Marshal.SizeOf(hdr);
hdr.dwFlags = (uint) (PropSheetFlags.PSH_DEFAULT | PropSheetFlags.PSH_NOAPPLYNOW);
hdr.pszCaption = this.Name;
hdr.nPages = (uint)nPages;
GCHandle gch = GCHandle.Alloc(hPages, GCHandleType.Pinned);
hdr.phpage = gch.AddrOfPinnedObject();
int res = PropertySheetDisplay.PropertySheet(ref hdr);
gch.Free();
if (res < 0)
throw (new Exception(SMSharedStringTable.GetString("SMSCHEDULER_PROPERTYSHEETF_20")));
return res>0;
}
///
/// Sets the account under which the task will run. Supply the account name and
/// password as parameters. For the localsystem account, pass an empty string for
/// the account name and null for the password. See Remarks.
///
/// Full account name.
/// Password for the account.
///
/// To have the task to run under the local system account, pass the empty string ("")
/// as accountName and null as the password. The caller must be running in
/// an administrator account or in the local system account.
///
///
/// You can also specify a null password if the task has the flag RunOnlyIfLoggedOn set.
/// This allows you to schedule a task for an account for which you don't know the password,
/// but the account must be logged on interactively at the time the task runs.
///
public void SetAccountInformation(string accountName, string password) {
iTask.SetAccountInformation(accountName, password);
}
///
/// Request that the task be terminated if it is currently running. The call returns
/// immediately, although the task may continue briefly. For Windows programs, a WM_CLOSE
/// message is sent first and the task is given three minutes to shut down voluntarily.
/// Should it not, or if the task is not a Windows program, TerminateProcess is used.
///
/// The task is not running.
public void Terminate() {
iTask.Terminate();
}
///
/// Overridden. Outputs the name of the task, the application and parameters.
///
/// String representing task.
public override string ToString() {
return string.Format("{0} (\"{1}\" {2})", name, ApplicationName, Parameters);
}
///
/// return true boolean value if scheduled task is enabled;
/// otherwise, return false.
///
public bool Enable
{
get
{
if ((this.Flags & TaskFlags.Disabled) == 0)
return true;
else
return false;
}
}
///
/// Enable scheduled task runs at specified time
///
public void EnableTask()
{
if ((this.Flags & TaskFlags.Disabled) > 0)
{
this.Flags = this.Flags ^ TaskFlags.Disabled;
this.Save();
}
}//EnableTask
///
/// Disable scheduled task runs at specified time
///
public void DisableTask()
{
if ((this.Flags & TaskFlags.Disabled) == 0)
{
this.Flags = this.Flags ^ TaskFlags.Disabled;
this.Save();
}
}//DisableTask
#endregion
#region Implementation of IDisposable
///
/// A synonym for Close.
///
public void Dispose() {
this.Close();
}
#endregion
}
}