using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using System.Security; using System.Security.AccessControl; using System.Text.RegularExpressions; using JetBrains.Annotations; using Microsoft.Win32.TaskScheduler.V1Interop; using Microsoft.Win32.TaskScheduler.V2Interop; namespace Microsoft.Win32.TaskScheduler { /// /// Provides the methods that are used to register (create) tasks in the folder, remove tasks from the folder, and create or remove subfolders from the folder. /// [PublicAPI] public sealed class TaskFolder : IDisposable, IComparable { private ITaskScheduler v1List; private readonly ITaskFolder v2Folder; internal const string rootString = @"\"; internal TaskFolder([NotNull] TaskService svc) { TaskService = svc; v1List = svc.v1TaskScheduler; } internal TaskFolder([NotNull] TaskService svc, [NotNull] ITaskFolder iFldr) { TaskService = svc; v2Folder = iFldr; } /// /// Releases all resources used by this class. /// public void Dispose() { if (v2Folder != null) Marshal.ReleaseComObject(v2Folder); v1List = null; } /// /// Gets a which enumerates all the tasks in this and all subfolders. /// /// /// A for all instances. /// [NotNull, ItemNotNull] public IEnumerable AllTasks => EnumerateFolderTasks(this); /// /// Gets the name that is used to identify the folder that contains a task. /// [NotNull] public string Name => (v2Folder == null) ? rootString : v2Folder.Name; /// /// Gets the parent folder of this folder. /// /// /// The parent folder, or null if this folder is the root folder. /// public TaskFolder Parent { get { // V1 only has the root folder if (v2Folder == null) return null; string path = v2Folder.Path; string parentPath = System.IO.Path.GetDirectoryName(path); if (string.IsNullOrEmpty(parentPath)) return null; return TaskService.GetFolder(parentPath); } } /// /// Gets the path to where the folder is stored. /// [NotNull] public string Path => (v2Folder == null) ? rootString : v2Folder.Path; [NotNull] internal TaskFolder GetFolder([NotNull] string path) { if (v2Folder != null) return new TaskFolder(TaskService, v2Folder.GetFolder(path)); throw new NotV1SupportedException(); } /// /// Gets or sets the security descriptor of the task. /// /// The security descriptor. [Obsolete("This property will be removed in deference to the GetAccessControl, GetSecurityDescriptorSddlForm, SetAccessControl and SetSecurityDescriptorSddlForm methods.")] public GenericSecurityDescriptor SecurityDescriptor { #pragma warning disable 0618 get { return GetSecurityDescriptor(); } set { SetSecurityDescriptor(value); } #pragma warning restore 0618 } /// /// Gets all the subfolders in the folder. /// [NotNull, ItemNotNull] public TaskFolderCollection SubFolders { get { try { if (v2Folder != null) return new TaskFolderCollection(this, v2Folder.GetFolders(0)); } catch { } return new TaskFolderCollection(); } } /// /// Gets a collection of all the tasks in the folder. /// [NotNull, ItemNotNull] public TaskCollection Tasks => GetTasks(); /// /// Gets or sets the that manages this task. /// /// The task service. public TaskService TaskService { get; } /// /// Compares the current object with another object of the same type. /// /// An object to compare with this object. /// /// A value that indicates the relative order of the objects being compared. The return value has the following meanings: Value Meaning Less than zero This object is less than the parameter.Zero This object is equal to . Greater than zero This object is greater than . /// int IComparable.CompareTo(TaskFolder other) => string.Compare(Path, other.Path, true); /// /// Creates a folder for related tasks. Not available to Task Scheduler 1.0. /// /// The name used to identify the folder. If "FolderName\SubFolder1\SubFolder2" is specified, the entire folder tree will be created if the folders do not exist. This parameter can be a relative path to the current instance. 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. /// The security descriptor associated with the folder. /// A instance that represents the new subfolder. [Obsolete("This method will be removed in deference to the CreateFolder(string, TaskSecurity) method.")] public TaskFolder CreateFolder([NotNull] string subFolderName, GenericSecurityDescriptor sd) => CreateFolder(subFolderName, sd == null ? null : sd.GetSddlForm(Task.defaultAccessControlSections)); /// /// Creates a folder for related tasks. Not available to Task Scheduler 1.0. /// /// The name used to identify the folder. If "FolderName\SubFolder1\SubFolder2" is specified, the entire folder tree will be created if the folders do not exist. This parameter can be a relative path to the current instance. 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. /// The task security associated with the folder. /// A instance that represents the new subfolder. public TaskFolder CreateFolder([NotNull] string subFolderName, [NotNull] TaskSecurity folderSecurity) { if (folderSecurity == null) throw new ArgumentNullException(nameof(folderSecurity)); return CreateFolder(subFolderName, folderSecurity.GetSecurityDescriptorSddlForm(Task.defaultAccessControlSections)); } /// /// Creates a folder for related tasks. Not available to Task Scheduler 1.0. /// /// The name used to identify the folder. If "FolderName\SubFolder1\SubFolder2" is specified, the entire folder tree will be created if the folders do not exist. This parameter can be a relative path to the current instance. 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. /// The security descriptor associated with the folder. /// Set this value to false to avoid having an exception called if the folder already exists. /// A instance that represents the new subfolder. /// Security descriptor mismatch between specified credentials and credentials on existing folder by same name. /// Invalid SDDL form. /// Not supported under Task Scheduler 1.0. public TaskFolder CreateFolder([NotNull] string subFolderName, string sddlForm = null, bool exceptionOnExists = true) { if (v2Folder == null) throw new NotV1SupportedException(); ITaskFolder ifld = null; try { ifld = v2Folder.CreateFolder(subFolderName, sddlForm); } catch (COMException ce) { int serr = ce.ErrorCode & 0x0000FFFF; if (serr == 0xb7) // ERROR_ALREADY_EXISTS { if (exceptionOnExists) throw; try { ifld = v2Folder.GetFolder(subFolderName); if (ifld != null && sddlForm != null && sddlForm.Trim().Length > 0) { string sd = ifld.GetSecurityDescriptor((int)Task.defaultSecurityInfosSections); if (string.Compare(sddlForm, sd, StringComparison.OrdinalIgnoreCase) != 0) throw new SecurityException("Security descriptor mismatch between specified credentials and credentials on existing folder by same name."); } } catch { if (ifld != null) Marshal.ReleaseComObject(ifld); throw; } } else if (serr == 0x534 || serr == 0x538 || serr == 0x539 || serr == 0x53A || serr == 0x519 || serr == 0x57) throw new ArgumentException(@"Invalid SDDL form", nameof(sddlForm), ce); else throw; } return new TaskFolder(TaskService, ifld); } /// /// Deletes a subfolder from the parent folder. Not available to Task Scheduler 1.0. /// /// The name of the subfolder to be removed. The root task folder is specified with a backslash (\). This parameter can be a relative path to the folder you want to delete. 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. /// Set this value to false to avoid having an exception called if the folder does not exist. /// Not supported under Task Scheduler 1.0. public void DeleteFolder([NotNull] string subFolderName, bool exceptionOnNotExists = true) { if (v2Folder != null) { try { v2Folder.DeleteFolder(subFolderName, 0); } catch (Exception e) { if (!(e is FileNotFoundException || e is DirectoryNotFoundException) || exceptionOnNotExists) throw; } } else throw new NotV1SupportedException(); } /// Deletes a task from the folder. /// /// The name of the task that is specified when the task was registered. 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. /// /// Set this value to false to avoid having an exception called if the task does not exist. public void DeleteTask([NotNull] string name, bool exceptionOnNotExists = true) { try { if (v2Folder != null) v2Folder.DeleteTask(name, 0); else { if (!name.EndsWith(".job", StringComparison.CurrentCultureIgnoreCase)) name += ".job"; v1List.Delete(name); } } catch (FileNotFoundException) { if (exceptionOnNotExists) throw; } } /// Returns an enumerable collection of folders that matches a specified filter and recursion option. /// An optional predicate used to filter the returned instances. /// An enumerable collection of folders that matches . [NotNull, ItemNotNull] public IEnumerable EnumerateFolders(Predicate filter = null) { foreach (var fld in SubFolders) { if (filter == null || filter(fld)) yield return fld; } } /// Returns an enumerable collection of tasks that matches a specified filter and recursion option. /// An optional predicate used to filter the returned instances. /// Specifies whether the enumeration should include tasks in any subfolders. /// An enumerable collection of directories that matches and . [NotNull, ItemNotNull] public IEnumerable EnumerateTasks(Predicate filter = null, bool recurse = false) => EnumerateFolderTasks(this, filter, recurse); /// 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 folder = obj as TaskFolder; if (folder != null) return Path == folder.Path && TaskService.TargetServer == folder.TaskService.TargetServer && GetSecurityDescriptorSddlForm() == folder.GetSecurityDescriptorSddlForm(); return false; } /// /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task described by the /// current object. /// /// A object that encapsulates the access control rules for the current folder. [NotNull] public TaskSecurity GetAccessControl() => GetAccessControl(Task.defaultAccessControlSections); /// /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task folder described by /// the current object. /// /// /// One of the values that specifies which group of access control entries to retrieve. /// /// A object that encapsulates the access control rules for the current folder. [NotNull] public TaskSecurity GetAccessControl(AccessControlSections includeSections) => new TaskSecurity(this, includeSections); /// 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 = Path, B = TaskService.TargetServer, C = GetSecurityDescriptorSddlForm() }.GetHashCode(); /// Gets the security descriptor for the folder. Not available to Task Scheduler 1.0. /// Section(s) of the security descriptor to return. /// The security descriptor for the folder. [Obsolete("This method will be removed in deference to the GetAccessControl and GetSecurityDescriptorSddlForm methods.")] public GenericSecurityDescriptor GetSecurityDescriptor(SecurityInfos includeSections = Task.defaultSecurityInfosSections) => new RawSecurityDescriptor(GetSecurityDescriptorSddlForm(includeSections)); /// /// Gets the security descriptor for the folder. Not available to Task Scheduler 1.0. /// /// Section(s) of the security descriptor to return. /// The security descriptor for the folder. /// Not supported under Task Scheduler 1.0. public string GetSecurityDescriptorSddlForm(SecurityInfos includeSections = Task.defaultSecurityInfosSections) { if (v2Folder != null) return v2Folder.GetSecurityDescriptor((int)includeSections); throw new NotV1SupportedException(); } /// /// Gets a collection of all the tasks in the folder whose name matches the optional . /// /// The optional name filter expression. /// Collection of all matching tasks. [NotNull, ItemNotNull] public TaskCollection GetTasks(Regex filter = null) { if (v2Folder != null) return new TaskCollection(this, v2Folder.GetTasks(1), filter); return new TaskCollection(TaskService, filter); } /// Imports a from an XML file. /// 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 file containing the XML-formatted definition of the task. /// If set to , overwrites any existing task with the same name. /// A instance that represents the new task. /// Importing from an XML file is only supported under Task Scheduler 2.0. public Task ImportTask(string path, [NotNull] string xmlFile, bool overwriteExisting = true) => RegisterTask(path, File.ReadAllText(xmlFile), overwriteExisting ? TaskCreation.CreateOrUpdate : TaskCreation.Create); /// /// Registers (creates) a new task in the folder using XML to define the task. /// /// 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. /// An XML-formatted definition of the task. /// A union of flags. /// 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 security descriptor associated with the registered task. You can specify the access control list (ACL) in the security descriptor for a task in order to allow or deny certain users and groups access to a task. /// A instance that represents the new task. /// " + /// "" + /// " " + /// " " + /// " S-1-5-18" + /// " " + /// " " + /// " " + /// " " + /// " 2017-09-04T14:04:03" + /// " " + /// " " + /// " " + /// " " + /// " " + /// " cmd" + /// " " + /// " " + /// ""; /// // Register the task in the root folder of the local machine using the SYSTEM account defined in XML /// TaskService.Instance.RootFolder.RegisterTaskDefinition("Test", xml); /// ]]> public Task RegisterTask(string path, [NotNull] string xmlText, TaskCreation createType = TaskCreation.CreateOrUpdate, string userId = null, string password = null, TaskLogonType logonType = TaskLogonType.S4U, string sddl = null) { if (v2Folder != null) return Task.CreateTask(TaskService, v2Folder.RegisterTask(path, xmlText, (int)createType, userId, password, logonType, sddl)); TaskDefinition td = TaskService.NewTask(); XmlSerializationHelper.ReadObjectFromXmlText(xmlText, td); return RegisterTaskDefinition(path, td, createType, userId ?? td.Principal.ToString(), password, logonType == TaskLogonType.S4U ? td.Principal.LogonType : logonType, sddl); } /// /// Registers (creates) a task in a specified location using a instance to define a task. /// /// 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 of the registered task. /// A instance that represents the new task. /// /// public Task RegisterTaskDefinition(string path, [NotNull] TaskDefinition definition) => RegisterTaskDefinition(path, definition, TaskCreation.CreateOrUpdate, definition.Principal.ToString(), null, definition.Principal.LogonType); /// /// Registers (creates) a task in a specified location using a instance to define a task. /// /// 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 of the registered task. /// A union of flags. /// 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 security descriptor associated with the registered task. You can specify the access control list (ACL) in the security descriptor for a task in order to allow or deny certain users and groups access to a task. /// /// A instance that represents the new task. This will return null if is set to ValidateOnly and there are no validation errors. /// /// /// Task names may not include any characters which are invalid for file names. /// or /// Task names ending with a period followed by three or fewer characters cannot be retrieved due to a bug in the native library. /// /// This LogonType is not supported on Task Scheduler 1.0. /// or /// Security settings are not available on Task Scheduler 1.0. /// or /// Registration triggers are not available on Task Scheduler 1.0. /// or /// XML validation not available on Task Scheduler 1.0. /// This method is effectively the "Save" method for tasks. It takes a modified TaskDefinition instance and registers it in the folder defined by this TaskFolder instance. Optionally, you can use this method to override the user, password and logon type defined in the definition and supply security against the task. /// /// This first example registers a simple task with a single trigger and action using the default security. /// /// This example registers that same task using the SYSTEM account. /// /// This example registers that same task using a specific username and password along with a security definition. /// public Task RegisterTaskDefinition([NotNull] string path, [NotNull] TaskDefinition definition, TaskCreation createType, string userId, string password = null, TaskLogonType logonType = TaskLogonType.S4U, string sddl = null) { if (definition.Actions.Count < 1 || definition.Actions.Count > 32) throw new ArgumentOutOfRangeException(nameof(definition.Actions), @"A task must be registered with at least one action and no more than 32 actions."); userId ??= definition.Principal.Account; if (userId == string.Empty) userId = null; User user = new User(userId); if (v2Folder != null) { definition.Actions.ConvertUnsupportedActions(); if (logonType == TaskLogonType.ServiceAccount) { if (string.IsNullOrEmpty(userId) || !user.IsServiceAccount) throw new ArgumentException(@"A valid system account name must be supplied for TaskLogonType.ServiceAccount. Valid entries are ""NT AUTHORITY\SYSTEM"", ""SYSTEM"", ""NT AUTHORITY\LOCALSERVICE"", or ""NT AUTHORITY\NETWORKSERVICE"".", nameof(userId)); if (password != null) throw new ArgumentException(@"A password cannot be supplied when specifying TaskLogonType.ServiceAccount.", nameof(password)); } /*else if ((LogonType == TaskLogonType.Password || LogonType == TaskLogonType.InteractiveTokenOrPassword || (LogonType == TaskLogonType.S4U && UserId != null && !user.IsCurrent)) && password == null) { throw new ArgumentException("A password must be supplied when specifying TaskLogonType.Password or TaskLogonType.InteractiveTokenOrPassword or TaskLogonType.S4U from another account.", nameof(password)); }*/ else if (logonType == TaskLogonType.Group && password != null) { throw new ArgumentException(@"A password cannot be supplied when specifying TaskLogonType.Group.", nameof(password)); } // The following line compensates for an omission in the native library that never actually sets the registration date (thanks ixm7). if (definition.RegistrationInfo.Date == DateTime.MinValue) definition.RegistrationInfo.Date = DateTime.Now; var iRegTask = v2Folder.RegisterTaskDefinition(path, definition.v2Def, (int)createType, userId ?? user.Name, password, logonType, sddl); if (createType == TaskCreation.ValidateOnly && iRegTask == null) return null; return Task.CreateTask(TaskService, iRegTask); } // Check for V1 invalid task names string invChars = Regex.Escape(new string(System.IO.Path.GetInvalidFileNameChars())); if (Regex.IsMatch(path, @"[" + invChars + @"]")) throw new ArgumentOutOfRangeException(nameof(path), @"Task names may not include any characters which are invalid for file names."); if (Regex.IsMatch(path, @"\.[^" + invChars + @"]{0,3}\z")) throw new ArgumentOutOfRangeException(nameof(path), @"Task names ending with a period followed by three or fewer characters cannot be retrieved due to a bug in the native library."); // Adds ability to set a password for a V1 task. Provided by Arcao. TaskFlags flags = definition.v1Task.GetFlags(); if (logonType == TaskLogonType.InteractiveTokenOrPassword && string.IsNullOrEmpty(password)) logonType = TaskLogonType.InteractiveToken; switch (logonType) { case TaskLogonType.Group: case TaskLogonType.S4U: case TaskLogonType.None: throw new NotV1SupportedException("This LogonType is not supported on Task Scheduler 1.0."); case TaskLogonType.InteractiveToken: flags |= (TaskFlags.RunOnlyIfLoggedOn | TaskFlags.Interactive); definition.v1Task.SetAccountInformation(user.Name, IntPtr.Zero); break; case TaskLogonType.ServiceAccount: flags &= ~(TaskFlags.Interactive | TaskFlags.RunOnlyIfLoggedOn); definition.v1Task.SetAccountInformation((String.IsNullOrEmpty(userId) || user.IsSystem) ? String.Empty : user.Name, IntPtr.Zero); break; case TaskLogonType.InteractiveTokenOrPassword: flags |= TaskFlags.Interactive; using (CoTaskMemString cpwd = new CoTaskMemString(password)) definition.v1Task.SetAccountInformation(user.Name, cpwd.DangerousGetHandle()); break; case TaskLogonType.Password: using (CoTaskMemString cpwd = new CoTaskMemString(password)) definition.v1Task.SetAccountInformation(user.Name, cpwd.DangerousGetHandle()); break; default: throw new ArgumentOutOfRangeException(nameof(logonType), logonType, null); } definition.v1Task.SetFlags(flags); switch (createType) { case TaskCreation.Create: case TaskCreation.CreateOrUpdate: case TaskCreation.Disable: case TaskCreation.Update: if (createType == TaskCreation.Disable) definition.Settings.Enabled = false; definition.V1Save(path); break; case TaskCreation.DontAddPrincipalAce: throw new NotV1SupportedException("Security settings are not available on Task Scheduler 1.0."); case TaskCreation.IgnoreRegistrationTriggers: throw new NotV1SupportedException("Registration triggers are not available on Task Scheduler 1.0."); case TaskCreation.ValidateOnly: throw new NotV1SupportedException("XML validation not available on Task Scheduler 1.0."); default: throw new ArgumentOutOfRangeException(nameof(createType), createType, null); } return new Task(TaskService, definition.v1Task); } /// /// Applies access control list (ACL) entries described by a object to the file described by the current object. /// /// A object that describes an access control list (ACL) entry to apply to the current folder. public void SetAccessControl([NotNull] TaskSecurity taskSecurity) { taskSecurity.Persist(this); } /// /// Sets the security descriptor for the folder. Not available to Task Scheduler 1.0. /// /// The security descriptor for the folder. /// Section(s) of the security descriptor to set. [Obsolete("This method will be removed in deference to the SetAccessControl and SetSecurityDescriptorSddlForm methods.")] public void SetSecurityDescriptor([NotNull] GenericSecurityDescriptor sd, SecurityInfos includeSections = Task.defaultSecurityInfosSections) { SetSecurityDescriptorSddlForm(sd.GetSddlForm((AccessControlSections)includeSections)); } /// /// Sets the security descriptor for the folder. Not available to Task Scheduler 1.0. /// /// The security descriptor for the folder. /// Flags that specify how to set the security descriptor. /// Not supported under Task Scheduler 1.0. public void SetSecurityDescriptorSddlForm([NotNull] string sddlForm, TaskSetSecurityOptions options = TaskSetSecurityOptions.None) { if (v2Folder != null) v2Folder.SetSecurityDescriptor(sddlForm, (int)options); else throw new NotV1SupportedException(); } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() => Path; /// /// Enumerates the tasks in the specified folder and its child folders. /// /// The folder in which to start enumeration. /// An optional filter to apply to the task list. /// true if subfolders are to be queried recursively. /// A that can be used to iterate through the tasks. internal static IEnumerable EnumerateFolderTasks(TaskFolder folder, Predicate filter = null, bool recurse = true) { foreach (var task in folder.Tasks) if (filter == null || filter(task)) yield return task; if (!recurse) yield break; foreach (var sfld in folder.SubFolders) foreach (var task in EnumerateFolderTasks(sfld, filter)) yield return task; } } }