using JetBrains.Annotations; using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; using System.Runtime.InteropServices; namespace Microsoft.Win32.TaskScheduler { /// /// Quick simple trigger types for the /// method. /// public enum QuickTriggerType { /// At boot. Boot, /// On system idle. Idle, /// At logon of any user. Logon, /// When the task is registered. TaskRegistration, /// Hourly, starting now. Hourly, /// Daily, starting now. Daily, /// Weekly, starting now. Weekly, /// Monthly, starting now. Monthly } /// /// Known versions of the native Task Scheduler library. This can be used as a decoder for the /// and values. /// public static class TaskServiceVersion { /// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). [Description("Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).")] public static readonly Version V1_1 = new Version(1, 1); /// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). [Description("Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).")] public static readonly Version V1_2 = new Version(1, 2); /// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). [Description("Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).")] public static readonly Version V1_3 = new Version(1, 3); /// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). [Description("Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).")] public static readonly Version V1_4 = new Version(1, 4); /// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). [Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).")] public static readonly Version V1_5 = new Version(1, 5); /// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703). [Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703).")] public static readonly Version V1_6 = new Version(1, 6); } /// Provides access to the Task Scheduler service for managing registered tasks. [Description("Provides access to the Task Scheduler service.")] [ToolboxItem(true), Serializable] public sealed partial class TaskService : Component, ISupportInitialize, System.Runtime.Serialization.ISerializable { internal static readonly bool LibraryIsV2 = Environment.OSVersion.Version.Major >= 6; internal static readonly Guid PowerShellActionGuid = new Guid("dab4c1e3-cd12-46f1-96fc-3981143c9bab"); private static Guid CLSID_Ctask = typeof(V1Interop.CTask).GUID; private static Guid IID_ITask = typeof(V1Interop.ITask).GUID; [ThreadStatic] private static TaskService instance; private static Version osLibVer; internal V1Interop.ITaskScheduler v1TaskScheduler; internal V2Interop.ITaskService v2TaskService; private bool connecting; private bool forceV1; private bool initializing; private Version maxVer; private bool maxVerSet; private string targetServer; private bool targetServerSet; private string userDomain; private bool userDomainSet; private string userName; private bool userNameSet; private string userPassword; private bool userPasswordSet; private WindowsImpersonatedIdentity v1Impersonation; /// Creates a new instance of a TaskService connecting to the local machine as the current user. public TaskService() { ResetHighestSupportedVersion(); Connect(); } /// Initializes a new instance of the class. /// /// The name of the computer that you want to connect to. If the this parameter is empty, then this will connect to the local computer. /// /// /// The user name that is used during the connection to the computer. If the user is not specified, then the current token is used. /// /// The domain of the user specified in the parameter. /// /// The password that is used to connect to the computer. If the user name and password are not specified, then the current token is used. /// /// If set to true force Task Scheduler 1.0 compatibility. public TaskService(string targetServer, string userName = null, string accountDomain = null, string password = null, bool forceV1 = false) { BeginInit(); TargetServer = targetServer; UserName = userName; UserAccountDomain = accountDomain; UserPassword = password; this.forceV1 = forceV1; ResetHighestSupportedVersion(); EndInit(); } private TaskService([NotNull] System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { BeginInit(); TargetServer = (string)info.GetValue("TargetServer", typeof(string)); UserName = (string)info.GetValue("UserName", typeof(string)); UserAccountDomain = (string)info.GetValue("UserAccountDomain", typeof(string)); UserPassword = (string)info.GetValue("UserPassword", typeof(string)); forceV1 = (bool)info.GetValue("forceV1", typeof(bool)); ResetHighestSupportedVersion(); EndInit(); } /// Delegate for methods that support update calls during COM handler execution. /// The percentage of completion (0 to 100). /// An optional message. public delegate void ComHandlerUpdate(short percentage, string message); /// Occurs when the Task Scheduler is connected to the local or remote target. public event EventHandler ServiceConnected; /// Occurs when the Task Scheduler is disconnected from the local or remote target. public event EventHandler ServiceDisconnected; /// Gets a local instance of the using the current user's credentials. /// Local user instance. public static TaskService Instance { get { if (instance is null) { instance = new TaskService(); instance.ServiceDisconnected += Instance_ServiceDisconnected; } return instance; } } /// /// Gets the library version. This is the highest version supported by the local library. Tasks cannot be created using any /// compatibility level higher than this version. /// /// The library version. /// /// The following table list the various versions and their host operating system: /// /// /// Version /// Operating System /// /// /// 1.1 /// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). /// /// /// 1.2 /// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). /// /// /// 1.3 /// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). /// /// /// 1.4 /// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). /// /// /// 1.5 /// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). /// /// /// 1.6 /// Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703). /// /// /// [Browsable(false)] public static Version LibraryVersion { get; } = Instance.HighestSupportedVersion; /// /// Gets or sets a value indicating whether to allow tasks from later OS versions with new properties to be retrieved as read only tasks. /// /// true if allow read only tasks; otherwise, false. [DefaultValue(false), Category("Behavior"), Description("Allow tasks from later OS versions with new properties to be retrieved as read only tasks.")] public bool AllowReadOnlyTasks { get; set; } /// Gets the name of the domain to which the computer is connected. [Browsable(false)] [DefaultValue(null)] [Obsolete("This property has been superseded by the UserAccountDomin property and may not be available in future releases.")] public string ConnectedDomain { get { if (v2TaskService != null) return v2TaskService.ConnectedDomain; var parts = v1Impersonation.Name.Split('\\'); if (parts.Length == 2) return parts[0]; return string.Empty; } } /// Gets the name of the user that is connected to the Task Scheduler service. [Browsable(false)] [DefaultValue(null)] [Obsolete("This property has been superseded by the UserName property and may not be available in future releases.")] public string ConnectedUser { get { if (v2TaskService != null) return v2TaskService.ConnectedUser; var parts = v1Impersonation.Name.Split('\\'); if (parts.Length == 2) return parts[1]; return parts[0]; } } /// Gets the highest version of Task Scheduler that a computer supports. /// /// The following table list the various versions and their host operating system: /// /// /// Version /// Operating System /// /// /// 1.1 /// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). /// /// /// 1.2 /// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). /// /// /// 1.3 /// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). /// /// /// 1.4 /// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). /// /// /// 1.5 /// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). /// /// /// 1.6 /// Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703). /// /// /// [Category("Data"), TypeConverter(typeof(VersionConverter)), Description("Highest version of library that should be used.")] public Version HighestSupportedVersion { get => maxVer; set { if (value > GetLibraryVersionFromLocalOS()) throw new ArgumentOutOfRangeException(nameof(HighestSupportedVersion), @"The value of HighestSupportedVersion cannot exceed that of the underlying Windows version library."); maxVer = value; maxVerSet = true; var localForceV1 = value <= TaskServiceVersion.V1_1; if (localForceV1 == forceV1) return; forceV1 = localForceV1; Connect(); } } /// Gets the root ("\") folder. For Task Scheduler 1.0, this is the only folder. [Browsable(false)] public TaskFolder RootFolder => GetFolder(TaskFolder.rootString); /// Gets or sets the name of the computer that is running the Task Scheduler service that the user is connected to. [Category("Data"), DefaultValue(null), Description("The name of the computer to connect to.")] public string TargetServer { get => ShouldSerializeTargetServer() ? targetServer : null; set { if (value == null || value.Trim() == string.Empty) value = null; if (string.Compare(value, targetServer, StringComparison.OrdinalIgnoreCase) != 0) { targetServerSet = true; targetServer = value; Connect(); } } } /// Gets or sets the user account domain to be used when connecting to the . /// The user account domain. [Category("Data"), DefaultValue(null), Description("The user account domain to be used when connecting.")] public string UserAccountDomain { get => ShouldSerializeUserAccountDomain() ? userDomain : null; set { if (value == null || value.Trim() == string.Empty) value = null; if (string.Compare(value, userDomain, StringComparison.OrdinalIgnoreCase) != 0) { userDomainSet = true; userDomain = value; Connect(); } } } /// Gets or sets the user name to be used when connecting to the . /// The user name. [Category("Data"), DefaultValue(null), Description("The user name to be used when connecting.")] public string UserName { get => ShouldSerializeUserName() ? userName : null; set { if (value == null || value.Trim() == string.Empty) value = null; if (string.Compare(value, userName, StringComparison.OrdinalIgnoreCase) != 0) { userNameSet = true; userName = value; Connect(); } } } /// Gets or sets the user password to be used when connecting to the . /// The user password. [Category("Data"), DefaultValue(null), Description("The user password to be used when connecting.")] public string UserPassword { get => userPassword; set { if (value == null || value.Trim() == string.Empty) value = null; if (string.CompareOrdinal(value, userPassword) != 0) { userPasswordSet = true; userPassword = value; Connect(); } } } /// Gets a which enumerates all the tasks in all folders. /// A for all instances. [Browsable(false)] public System.Collections.Generic.IEnumerable AllTasks => RootFolder.AllTasks; /// Gets a Boolean value that indicates if you are connected to the Task Scheduler service. [Browsable(false)] public bool Connected => v2TaskService != null && v2TaskService.Connected || v1TaskScheduler != null; /// /// Gets the connection token for this instance. This token is thread safe and can be used to create new /// instances on other threads using the static method. /// /// The connection token. public ConnectionToken Token => ConnectionDataManager.TokenFromInstance(TargetServer, UserName, UserAccountDomain, UserPassword, forceV1); /// Gets a value indicating whether the component can raise an event. protected override bool CanRaiseEvents { get; } = false; /// /// Creates a new instance from a token. Given that a TaskService instance is thread specific, this is the /// preferred method for multi-thread creation or asynchronous method parameters. /// /// The token. /// A instance valid for the thread calling this method. public static TaskService CreateFromToken(ConnectionToken token) => ConnectionDataManager.InstanceFromToken(token); /// Gets a formatted string that tells the Task Scheduler to retrieve a string from a resource .dll file. /// The path to the .dll file that contains the resource. /// The identifier for the resource text (typically a negative number). /// A string in the format of $(@ [dllPath], [resourceId]). /// /// For example, the setting this property value to $(@ %SystemRoot%\System32\ResourceName.dll, -101) will set the property to the /// value of the resource text with an identifier equal to -101 in the %SystemRoot%\System32\ResourceName.dll file. /// public static string GetDllResourceString([NotNull] string dllPath, int resourceId) => $"$(@ {dllPath}, {resourceId})"; /// /// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the /// interface. /// /// The CLSID of the COM object. /// An optional string passed to the COM object at startup. /// The number of milliseconds to wait or -1 for indefinitely. /// /// An optional delegate that is called when the COM object calls the /// method. /// /// The value set by the COM object via a call to the method. public static int RunComHandlerAction(Guid clsid, string data = null, int millisecondsTimeout = -1, ComHandlerUpdate onUpdate = null) { var thread = new ComHandlerThread(clsid, data, millisecondsTimeout, onUpdate, null); thread.Start().Join(); return thread.ReturnCode; } /// /// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the /// interface. /// /// The CLSID of the COM object. /// The action to run on thread completion. /// An optional string passed to the COM object at startup. /// The number of milliseconds to wait or -1 for indefinitely. /// /// An optional delegate that is called when the COM object calls the /// method. /// public static void RunComHandlerActionAsync(Guid clsid, Action onComplete, string data = null, int millisecondsTimeout = -1, ComHandlerUpdate onUpdate = null) => new ComHandlerThread(clsid, data, millisecondsTimeout, onUpdate, onComplete).Start(); /// Adds or updates an Automatic Maintenance Task on the connected machine. /// Name of the task with full path. /// The amount of time the task needs once executed during regular Automatic maintenance. /// /// The amount of time after which the Task Scheduler attempts to run the task during emergency Automatic maintenance, if the task /// failed to complete during regular Automatic Maintenance. /// /// The path to an executable file. /// The arguments associated with the command-line operation. /// /// The directory that contains either the executable file or the files that are used by the executable file. /// /// A instance of the Automatic Maintenance Task. /// /// Automatic Maintenance tasks are only supported on Windows 8/Server 2012 and later. /// public Task AddAutomaticMaintenanceTask([NotNull] string taskPathAndName, TimeSpan period, TimeSpan deadline, string executablePath, string arguments = null, string workingDirectory = null) { if (HighestSupportedVersion.Minor < 4) throw new InvalidOperationException("Automatic Maintenance tasks are only supported on Windows 8/Server 2012 and later."); var td = NewTask(); td.Settings.UseUnifiedSchedulingEngine = true; td.Settings.MaintenanceSettings.Period = period; td.Settings.MaintenanceSettings.Deadline = deadline; td.Actions.Add(executablePath, arguments, workingDirectory); // The task needs to grant explicit FRFX to LOCAL SERVICE (A;;FRFX;;;LS) return RootFolder.RegisterTaskDefinition(taskPathAndName, td, TaskCreation.CreateOrUpdate, null, null, TaskLogonType.InteractiveToken, "D:P(A;;FA;;;BA)(A;;FA;;;SY)(A;;FRFX;;;LS)"); } /// Creates a new task, registers the task, and returns the instance. /// /// The task name. If this value is NULL, the task will be registered in the root task folder and the task name will be a GUID value /// that is created by the Task Scheduler service. A task name cannot begin or end with a space character. The '.' character cannot /// be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path. /// /// The to determine when to run the task. /// The to determine what happens when the task is triggered. /// The user credentials used to register the task. /// The password for the userId used to register the task. /// /// A value that defines what logon technique is used to run the registered task. /// /// The task description. /// A instance of the registered task. /// /// This method is shorthand for creating a new TaskDescription, adding a trigger and action, and then registering it in the root folder. /// /// /// /// /// /// public Task AddTask([NotNull] string path, [NotNull] Trigger trigger, [NotNull] Action action, string userId = null, string password = null, TaskLogonType logonType = TaskLogonType.InteractiveToken, string description = null) { var td = NewTask(); if (!string.IsNullOrEmpty(description)) td.RegistrationInfo.Description = description; // Create a trigger that will fire the task at a specific date and time td.Triggers.Add(trigger); // Create an action that will launch Notepad whenever the trigger fires td.Actions.Add(action); // Register the task in the root folder return RootFolder.RegisterTaskDefinition(path, td, TaskCreation.CreateOrUpdate, userId, password, logonType); } /// Creates a new task, registers the task, and returns the instance. /// /// The task name. If this value is NULL, the task will be registered in the root task folder and the task name will be a GUID value /// that is created by the Task Scheduler service. A task name cannot begin or end with a space character. The '.' character cannot /// be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path. /// /// The to determine when to run the task. /// The executable path. /// The arguments (optional). Value can be NULL. /// The user credentials used to register the task. /// The password for the userId used to register the task. /// /// A value that defines what logon technique is used to run the registered task. /// /// The task description. /// A instance of the registered task. /// /// /// /// /// public Task AddTask([NotNull] string path, QuickTriggerType trigger, [NotNull] string exePath, string arguments = null, string userId = null, string password = null, TaskLogonType logonType = TaskLogonType.InteractiveToken, string description = null) { // Create a trigger based on quick trigger Trigger newTrigger; switch (trigger) { case QuickTriggerType.Boot: newTrigger = new BootTrigger(); break; case QuickTriggerType.Idle: newTrigger = new IdleTrigger(); break; case QuickTriggerType.Logon: newTrigger = new LogonTrigger(); break; case QuickTriggerType.TaskRegistration: newTrigger = new RegistrationTrigger(); break; case QuickTriggerType.Hourly: newTrigger = new DailyTrigger { Repetition = new RepetitionPattern(TimeSpan.FromHours(1), TimeSpan.FromDays(1)) }; break; case QuickTriggerType.Daily: newTrigger = new DailyTrigger(); break; case QuickTriggerType.Weekly: newTrigger = new WeeklyTrigger(); break; case QuickTriggerType.Monthly: newTrigger = new MonthlyTrigger(); break; default: throw new ArgumentOutOfRangeException(nameof(trigger), trigger, null); } return AddTask(path, newTrigger, new ExecAction(exePath, arguments), userId, password, logonType, description); } /// Signals the object that initialization is starting. public void BeginInit() => initializing = true; /// Signals the object that initialization is complete. public void EndInit() { initializing = false; Connect(); } /// Determines whether the specified , is equal to this instance. /// The to compare with this instance. /// true if the specified is equal to this instance; otherwise, false. public override bool Equals(object obj) { var tsobj = obj as TaskService; if (tsobj != null) return tsobj.TargetServer == TargetServer && tsobj.UserAccountDomain == UserAccountDomain && tsobj.UserName == UserName && tsobj.UserPassword == UserPassword && tsobj.forceV1 == forceV1; return base.Equals(obj); } /// Finds all tasks matching a name or standard wildcards. /// Name of the task in regular expression form. /// if set to true search all sub folders. /// An array of containing all tasks matching . public Task[] FindAllTasks(System.Text.RegularExpressions.Regex name, bool searchAllFolders = true) { var results = new System.Collections.Generic.List(); FindTaskInFolder(RootFolder, name, ref results, searchAllFolders); return results.ToArray(); } /// Finds all tasks matching a name or standard wildcards. /// The filter used to determine tasks to select. /// if set to true search all sub folders. /// An array of containing all tasks matching . public Task[] FindAllTasks(Predicate filter, bool searchAllFolders = true) { if (filter == null) filter = t => true; var results = new System.Collections.Generic.List(); FindTaskInFolder(RootFolder, filter, ref results, searchAllFolders); return results.ToArray(); } /// Finds a task given a name and standard wildcards. /// The task name. This can include the wildcards * or ?. /// if set to true search all sub folders. /// A if one matches , otherwise NULL. public Task FindTask([NotNull] string name, bool searchAllFolders = true) { var results = FindAllTasks(new Wildcard(name), searchAllFolders); if (results.Length > 0) return results[0]; return null; } /// Gets the event log for this instance. /// (Optional) The task path if only the events for a single task are desired. /// A instance. public TaskEventLog GetEventLog(string taskPath = null) => new TaskEventLog(TargetServer, taskPath, UserAccountDomain, UserName, UserPassword); /// Gets the path to a folder of registered tasks. /// /// The path to the folder to retrieve. Do not use a backslash following the last folder name in the path. The root task folder is /// specified with a backslash (\). An example of a task folder path, under the root task folder, is \MyTaskFolder. The '.' character /// cannot be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path. /// /// instance for the requested folder or null if was unrecognized. /// /// Folder other than the root (\) was requested on a system not supporting Task Scheduler 2.0. /// public TaskFolder GetFolder(string folderName) { TaskFolder f = null; if (v2TaskService != null) { if (string.IsNullOrEmpty(folderName)) folderName = TaskFolder.rootString; try { var ifld = v2TaskService.GetFolder(folderName); if (ifld != null) f = new TaskFolder(this, ifld); } catch (System.IO.DirectoryNotFoundException) { } catch (System.IO.FileNotFoundException) { } } else if (folderName == TaskFolder.rootString || string.IsNullOrEmpty(folderName)) f = new TaskFolder(this); else throw new NotV1SupportedException("Folder other than the root (\\) was requested on a system only supporting Task Scheduler 1.0."); return f; } /// Returns a hash code for this instance. /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => new { A = TargetServer, B = UserAccountDomain, C = UserName, D = UserPassword, E = forceV1 }.GetHashCode(); /// Gets a collection of running tasks. /// True to include hidden tasks. /// instance with the list of running tasks. public RunningTaskCollection GetRunningTasks(bool includeHidden = true) { if (v2TaskService != null) try { return new RunningTaskCollection(this, v2TaskService.GetRunningTasks(includeHidden ? 1 : 0)); } catch { } return new RunningTaskCollection(this); } /// Gets the task with the specified path. /// The task path. /// /// The instance matching the , if found. If not found, this method returns null. /// public Task GetTask([NotNull] string taskPath) { Task t = null; if (v2TaskService != null) { var iTask = GetTask(v2TaskService, taskPath); if (iTask != null) t = Task.CreateTask(this, iTask); } else { taskPath = System.IO.Path.GetFileNameWithoutExtension(taskPath); var iTask = GetTask(v1TaskScheduler, taskPath); if (iTask != null) t = new Task(this, iTask); } return t; } /// /// Returns an empty task definition object to be filled in with settings and properties and then registered using the /// method. /// /// A instance for setting properties. public TaskDefinition NewTask() { if (v2TaskService != null) return new TaskDefinition(v2TaskService.NewTask(0)); var v1Name = "Temp" + Guid.NewGuid().ToString("B"); return new TaskDefinition(v1TaskScheduler.NewWorkItem(v1Name, CLSID_Ctask, IID_ITask), v1Name); } /// Returns a populated with the properties defined in an XML file. /// The XML file to use as input. /// A instance. /// Importing from an XML file is only supported under Task Scheduler 2.0. public TaskDefinition NewTaskFromFile([NotNull] string xmlFile) { var td = NewTask(); td.XmlText = System.IO.File.ReadAllText(xmlFile); return td; } /// Starts the Task Scheduler UI for the OS hosting the assembly if the session is running in interactive mode. public void StartSystemTaskSchedulerManager() { if (Environment.UserInteractive) System.Diagnostics.Process.Start("control.exe", "schedtasks"); } [System.Security.SecurityCritical] void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { info.AddValue("TargetServer", TargetServer, typeof(string)); info.AddValue("UserName", UserName, typeof(string)); info.AddValue("UserAccountDomain", UserAccountDomain, typeof(string)); info.AddValue("UserPassword", UserPassword, typeof(string)); info.AddValue("forceV1", forceV1, typeof(bool)); } internal static V2Interop.IRegisteredTask GetTask([NotNull] V2Interop.ITaskService iSvc, [NotNull] string name) { V2Interop.ITaskFolder fld = null; try { fld = iSvc.GetFolder("\\"); return fld.GetTask(name); } catch { return null; } finally { if (fld != null) Marshal.ReleaseComObject(fld); } } internal static V1Interop.ITask GetTask([NotNull] V1Interop.ITaskScheduler iSvc, [NotNull] string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); try { return iSvc.Activate(name, IID_ITask); } catch (UnauthorizedAccessException) { // TODO: Take ownership of the file and try again throw; } catch (ArgumentException) { return iSvc.Activate(name + ".job", IID_ITask); } catch (FileNotFoundException) { return null; } } /// /// Releases the unmanaged resources used by the and optionally releases the managed resources. /// /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected override void Dispose(bool disposing) { if (v2TaskService != null) { try { Marshal.ReleaseComObject(v2TaskService); } catch { } v2TaskService = null; } if (v1TaskScheduler != null) { try { Marshal.ReleaseComObject(v1TaskScheduler); } catch { } v1TaskScheduler = null; } if (v1Impersonation != null) { v1Impersonation.Dispose(); v1Impersonation = null; } if (!connecting) ServiceDisconnected?.Invoke(this, EventArgs.Empty); base.Dispose(disposing); } private static Version GetLibraryVersionFromLocalOS() { if (osLibVer == null) { if (Environment.OSVersion.Version.Major < 6) osLibVer = TaskServiceVersion.V1_1; else { if (Environment.OSVersion.Version.Minor == 0) osLibVer = TaskServiceVersion.V1_2; else if (Environment.OSVersion.Version.Minor == 1) osLibVer = TaskServiceVersion.V1_3; else { try { var fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(Path.Combine(Environment.SystemDirectory, "taskschd.dll")); if (fvi.FileBuildPart > 9600 && fvi.FileBuildPart <= 14393) osLibVer = TaskServiceVersion.V1_5; else if (fvi.FileBuildPart >= 15063) osLibVer = TaskServiceVersion.V1_6; else // fvi.FileBuildPart <= 9600 osLibVer = TaskServiceVersion.V1_4; } catch { /* ignored */ }; } } if (osLibVer == null) throw new NotSupportedException(@"The Task Scheduler library version for this system cannot be determined."); } return osLibVer; } private static void Instance_ServiceDisconnected(object sender, EventArgs e) => instance?.Connect(); /// Connects this instance of the class to a running Task Scheduler. private void Connect() { ResetUnsetProperties(); if (!initializing && !DesignMode) { if (!string.IsNullOrEmpty(userDomain) && !string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(userPassword) || string.IsNullOrEmpty(userDomain) && string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(userPassword)) { // Clear stuff if already connected connecting = true; Dispose(true); if (LibraryIsV2 && !forceV1) { v2TaskService = new V2Interop.ITaskService(); if (!string.IsNullOrEmpty(targetServer)) { // Check to ensure character only server name. (Suggested by bigsan) if (targetServer.StartsWith(@"\")) targetServer = targetServer.TrimStart('\\'); // Make sure null is provided for local machine to compensate for a native library oddity (Found by ctrollen) if (targetServer.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase)) targetServer = null; } else targetServer = null; v2TaskService.Connect(targetServer, userName, userDomain, userPassword); targetServer = v2TaskService.TargetServer; userName = v2TaskService.ConnectedUser; userDomain = v2TaskService.ConnectedDomain; maxVer = GetV2Version(); } else { v1Impersonation = new WindowsImpersonatedIdentity(userName, userDomain, userPassword); v1TaskScheduler = new V1Interop.ITaskScheduler(); if (!string.IsNullOrEmpty(targetServer)) { // Check to ensure UNC format for server name. (Suggested by bigsan) if (!targetServer.StartsWith(@"\\")) targetServer = @"\\" + targetServer; } else targetServer = null; v1TaskScheduler.SetTargetComputer(targetServer); targetServer = v1TaskScheduler.GetTargetComputer(); maxVer = TaskServiceVersion.V1_1; } ServiceConnected?.Invoke(this, EventArgs.Empty); connecting = false; } else { throw new ArgumentException("A username, password, and domain must be provided."); } } } /// Finds the task in folder. /// The folder. /// The wildcard expression to compare task names with. /// The results. /// if set to true recurse folders. /// True if any tasks are found, False if not. private bool FindTaskInFolder([NotNull] TaskFolder fld, System.Text.RegularExpressions.Regex taskName, ref System.Collections.Generic.List results, bool recurse = true) { results.AddRange(fld.GetTasks(taskName)); if (recurse) { foreach (var f in fld.SubFolders) { if (FindTaskInFolder(f, taskName, ref results)) return true; } } return false; } /// Finds the task in folder. /// The folder. /// The filter to use when looking for tasks. /// The results. /// if set to true recurse folders. /// True if any tasks are found, False if not. private bool FindTaskInFolder([NotNull] TaskFolder fld, Predicate filter, ref System.Collections.Generic.List results, bool recurse = true) { foreach (var t in fld.GetTasks()) try { if (filter(t)) results.Add(t); } catch { System.Diagnostics.Debug.WriteLine($"Unable to evaluate filter for task '{t.Path}'."); } if (recurse) { foreach (var f in fld.SubFolders) { if (FindTaskInFolder(f, filter, ref results)) return true; } } return false; } private Version GetV2Version() { var v = v2TaskService.HighestVersion; return new Version((int)(v >> 16), (int)(v & 0x0000FFFF)); } private void ResetHighestSupportedVersion() => maxVer = Connected ? (v2TaskService != null ? GetV2Version() : TaskServiceVersion.V1_1) : GetLibraryVersionFromLocalOS(); private void ResetUnsetProperties() { if (!maxVerSet) ResetHighestSupportedVersion(); if (!targetServerSet) targetServer = null; if (!userDomainSet) userDomain = null; if (!userNameSet) userName = null; if (!userPasswordSet) userPassword = null; } private bool ShouldSerializeHighestSupportedVersion() => LibraryIsV2 && maxVer <= TaskServiceVersion.V1_1; private bool ShouldSerializeTargetServer() => targetServer != null && !targetServer.Trim('\\').Equals(Environment.MachineName.Trim('\\'), StringComparison.InvariantCultureIgnoreCase); private bool ShouldSerializeUserAccountDomain() => userDomain != null && !userDomain.Equals(Environment.UserDomainName, StringComparison.InvariantCultureIgnoreCase); private bool ShouldSerializeUserName() => userName != null && !userName.Equals(Environment.UserName, StringComparison.InvariantCultureIgnoreCase); /// /// Represents a valid, connected session to a Task Scheduler instance. This token is thread-safe and should be the means of passing /// information about a between threads. /// public struct ConnectionToken { internal int token; internal ConnectionToken(int value) => token = value; } // Manages the list of tokens and associated data private static class ConnectionDataManager { public static List connections = new List() { new ConnectionData(null) }; public static TaskService InstanceFromToken(ConnectionToken token) { ConnectionData data; lock (connections) { data = connections[token.token < connections.Count ? token.token : 0]; } return new TaskService(data.TargetServer, data.UserName, data.UserAccountDomain, data.UserPassword, data.ForceV1); } public static ConnectionToken TokenFromInstance(string targetServer, string userName = null, string accountDomain = null, string password = null, bool forceV1 = false) { lock (connections) { var newData = new ConnectionData(targetServer, userName, accountDomain, password, forceV1); for (var i = 0; i < connections.Count; i++) { if (connections[i].Equals(newData)) return new ConnectionToken(i); } connections.Add(newData); return new ConnectionToken(connections.Count - 1); } } } private class ComHandlerThread { public int ReturnCode; private readonly System.Threading.AutoResetEvent completed = new System.Threading.AutoResetEvent(false); private readonly string Data; private readonly Type objType; private readonly TaskHandlerStatus status; private readonly int Timeout; public ComHandlerThread(Guid clsid, string data, int millisecondsTimeout, ComHandlerUpdate onUpdate, Action onComplete) { objType = Type.GetTypeFromCLSID(clsid, true); Data = data; Timeout = millisecondsTimeout; status = new TaskHandlerStatus(i => { completed.Set(); onComplete?.Invoke(i); }, onUpdate); } public System.Threading.Thread Start() { var t = new System.Threading.Thread(ThreadProc); t.Start(); return t; } private void ThreadProc() { completed.Reset(); object obj = null; try { obj = Activator.CreateInstance(objType); } catch { } if (obj == null) return; ITaskHandler taskHandler = null; try { taskHandler = (ITaskHandler)obj; } catch { } try { if (taskHandler != null) { taskHandler.Start(status, Data); completed.WaitOne(Timeout); taskHandler.Stop(out ReturnCode); } } finally { if (taskHandler != null) Marshal.ReleaseComObject(taskHandler); Marshal.ReleaseComObject(obj); } } private class TaskHandlerStatus : ITaskHandlerStatus { private readonly Action OnCompleted; private readonly ComHandlerUpdate OnUpdate; public TaskHandlerStatus(Action onCompleted, ComHandlerUpdate onUpdate) { OnCompleted = onCompleted; OnUpdate = onUpdate; } public void TaskCompleted([In, MarshalAs(UnmanagedType.Error)] int taskErrCode) => OnCompleted?.Invoke(taskErrCode); public void UpdateStatus([In] short percentComplete, [In, MarshalAs(UnmanagedType.BStr)] string statusMessage) => OnUpdate?.Invoke(percentComplete, statusMessage); } } // This private class holds information needed to create a new TaskService instance private class ConnectionData : IEquatable { public bool ForceV1; public string TargetServer, UserAccountDomain, UserName, UserPassword; public ConnectionData(string targetServer, string userName = null, string accountDomain = null, string password = null, bool forceV1 = false) { TargetServer = targetServer; UserAccountDomain = accountDomain; UserName = userName; UserPassword = password; ForceV1 = forceV1; } public bool Equals(ConnectionData other) => string.Equals(TargetServer, other.TargetServer, StringComparison.InvariantCultureIgnoreCase) && string.Equals(UserAccountDomain, other.UserAccountDomain, StringComparison.InvariantCultureIgnoreCase) && string.Equals(UserName, other.UserName, StringComparison.InvariantCultureIgnoreCase) && string.Equals(UserPassword, other.UserPassword, StringComparison.InvariantCultureIgnoreCase) && ForceV1 == other.ForceV1; } private class VersionConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) return true; return base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value) { var s = value as string; return s != null ? new Version(s) : base.ConvertFrom(context, culture, value); } } } }