/* 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 } }