diff --git a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs index 1d24f46..e0574c4 100644 --- a/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs +++ b/winPEAS/winPEASexe/winPEAS/Checks/Checks.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Management; using System.Security.Principal; @@ -36,6 +35,9 @@ namespace winPEAS.Checks //static string paint_lockoutUsers = ""; public static string PaintAdminUsers = ""; + private static List _systemChecks; + private static HashSet _systemCheckSelectedKeysHashSet = new HashSet(); + class SystemCheck { public string Key { get; } @@ -51,10 +53,10 @@ namespace winPEAS.Checks internal static void Run(string[] args) { //Check parameters - bool isAllChecks = true; + bool isAllChecks = true; bool wait = false; - var systemChecks = new List + _systemChecks = new List { new SystemCheck("systeminfo", new SystemInfo()), new SystemCheck("userinfo", new UserInfo()), @@ -67,8 +69,7 @@ namespace winPEAS.Checks new SystemCheck("filesinfo", new FilesInfo()), }; - var systemCheckAllKeys = new HashSet(systemChecks.Select(i => i.Key)); - var systemCheckSelectedKeysHashSet = new HashSet(); + var systemCheckAllKeys = new HashSet(_systemChecks.Select(i => i.Key)); foreach (string arg in args) { @@ -114,40 +115,48 @@ namespace winPEAS.Checks string argToLower = arg.ToLower(); if (systemCheckAllKeys.Contains(argToLower)) { - systemCheckSelectedKeysHashSet.Add(argToLower); + _systemCheckSelectedKeysHashSet.Add(argToLower); isAllChecks = false; - } + } } - //Start execution - if (IsNoColor) + CheckRunner.Run(() => { - Beaprint.DeleteColors(); - } - else + //Start execution + if (IsNoColor) + { + Beaprint.DeleteColors(); + } + else + { + CheckRegANSI(); + } + + CheckRunner.Run(CreateDynamicLists, IsDebug); + + Beaprint.PrintInit(IsDebug); + + RunChecks(isAllChecks, wait); + }, IsDebug, "Total time"); + } + + private static void RunChecks(bool isAllChecks, bool wait) + { + for (int i = 0; i < _systemChecks.Count; i++) { - CheckRegANSI(); - } + var systemCheck = _systemChecks[i]; - CheckRunner.Run(CreateDynamicLists, IsDebug); - - Beaprint.PrintInit(IsDebug); - - for (int i = 0; i < systemChecks.Count; i++) - { - var systemCheck = systemChecks[i]; - - if (systemCheckSelectedKeysHashSet.Contains(systemCheck.Key) || isAllChecks) + if (_systemCheckSelectedKeysHashSet.Contains(systemCheck.Key) || isAllChecks) { systemCheck.Check.PrintInfo(IsDebug); - if ((i < systemCheckSelectedKeysHashSet.Count - 1) && wait) + if ((i < _systemCheckSelectedKeysHashSet.Count - 1) && wait) { WaitInput(); } - } - } - } + } + } + } private static void CreateDynamicLists() { @@ -236,7 +245,7 @@ namespace winPEAS.Checks Beaprint.GrayPrint("Error while creating admin users groups list: " + ex); } - // create the file lists + //create the file lists try { Beaprint.GrayPrint(" - Creating files/directories list for search..."); @@ -245,7 +254,7 @@ namespace winPEAS.Checks catch (Exception ex) { Beaprint.GrayPrint("Error while creating directory list: " + ex); - } + } } private static void CheckRegANSI() diff --git a/winPEAS/winPEASexe/winPEAS/Helpers/CheckRunner.cs b/winPEAS/winPEASexe/winPEAS/Helpers/CheckRunner.cs index f34f32b..07f2d17 100644 --- a/winPEAS/winPEASexe/winPEAS/Helpers/CheckRunner.cs +++ b/winPEAS/winPEASexe/winPEAS/Helpers/CheckRunner.cs @@ -5,7 +5,7 @@ namespace winPEAS.Helpers { internal static class CheckRunner { - public static void Run(Action action, bool isDebug) + public static void Run(Action action, bool isDebug, string description = null) { var timer = new Stopwatch(); @@ -20,11 +20,12 @@ namespace winPEAS.Helpers { timer.Stop(); - TimeSpan timeTaken = timer.Elapsed; - string log = $"Execution took : {timeTaken.Minutes:00}m:{timeTaken.Seconds:00}s:{timeTaken.Milliseconds:000}"; + TimeSpan timeTaken = timer.Elapsed; + string descriptionText = string.IsNullOrEmpty(description) ? string.Empty : $"[{description}] "; + string log = $"{descriptionText}Execution took : {timeTaken.Minutes:00}m:{timeTaken.Seconds:00}s:{timeTaken.Milliseconds:000}"; Beaprint.PrintDebugLine(log); - } + } } } } diff --git a/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/ApplicationInfoHelper.cs b/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/ApplicationInfoHelper.cs index 135ddfa..e82faa7 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/ApplicationInfoHelper.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/ApplicationInfo/ApplicationInfoHelper.cs @@ -33,28 +33,28 @@ namespace winPEAS.Info.ApplicationInfo public static List> GetScheduledAppsNoMicrosoft() { var results = new List>(); - - try + + void ProcessTaskFolder(TaskFolder taskFolder) { - void EnumFolderTasks(TaskFolder fld) + foreach (var runTask in taskFolder.GetTasks()) // browse all tasks in folder { - foreach (Task task in fld.Tasks) - { - ActOnTask(task); - } - //task.Name - //task.Enabled - //task.Definition.Actions - //task.Definition - foreach (TaskFolder sfld in fld.SubFolders) - { - EnumFolderTasks(sfld); - } + ActOnTask(runTask); } - void ActOnTask(Task t) + foreach (var taskFolderSub in taskFolder.SubFolders) // recursively browse subfolders { - if (t.Enabled && (!string.IsNullOrEmpty(t.Definition.RegistrationInfo.Author) && !t.Definition.RegistrationInfo.Author.Contains("Microsoft"))) + ProcessTaskFolder(taskFolderSub); + } + } + + void ActOnTask(Task t) + { + try + { + if (t.Enabled && + !string.IsNullOrEmpty(t.Path) && !t.Path.Contains("Microsoft") && + !string.IsNullOrEmpty(t.Definition.RegistrationInfo.Author) && + !t.Definition.RegistrationInfo.Author.Contains("Microsoft")) { List f_trigger = new List(); foreach (Trigger trigger in t.Definition.Triggers) @@ -72,13 +72,17 @@ namespace winPEAS.Info.ApplicationInfo }); } } - EnumFolderTasks(TaskService.Instance.RootFolder); - } - catch (Exception ex) - { - Beaprint.GrayPrint("Error: " + ex); + catch (Exception ex) + { + Beaprint.PrintException($"failed to process scheduled task: '{t.Name}': {ex.Message}"); + } } + + TaskFolder folder = TaskService.Instance.GetFolder("\\"); + + ProcessTaskFolder(folder); + return results; - } + } } } diff --git a/winPEAS/winPEASexe/winPEAS/Info/ServicesInfo/ServicesInfoHelper.cs b/winPEAS/winPEASexe/winPEAS/Info/ServicesInfo/ServicesInfoHelper.cs index 73e56d2..be1ca4b 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/ServicesInfo/ServicesInfoHelper.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/ServicesInfo/ServicesInfoHelper.cs @@ -48,18 +48,18 @@ namespace winPEAS.Info.ServicesInfo if (string.IsNullOrEmpty(companyName) || (!Regex.IsMatch(companyName, @"^Microsoft.*", RegexOptions.IgnoreCase))) { - Dictionary toadd = new Dictionary - { - ["Name"] = string.Format("{0}", result["Name"]), - ["DisplayName"] = string.Format("{0}", result["DisplayName"]), - ["CompanyName"] = companyName, - ["State"] = string.Format("{0}", result["State"]), - ["StartMode"] = string.Format("{0}", result["StartMode"]), - ["PathName"] = string.Format("{0}", result["PathName"]), - ["FilteredPath"] = binaryPath, - ["isDotNet"] = isDotNet, - ["Description"] = result["Description"].ToString() - }; + Dictionary toadd = new Dictionary(); + + toadd["Name"] = GetStringOrEmpty(result["Name"]); + toadd["DisplayName"] = GetStringOrEmpty(result["DisplayName"]); + toadd["CompanyName"] = companyName; + toadd["State"] = GetStringOrEmpty(result["State"]); + toadd["StartMode"] = GetStringOrEmpty(result["StartMode"]); + toadd["PathName"] = GetStringOrEmpty(result["PathName"]); + toadd["FilteredPath"] = binaryPath; + toadd["isDotNet"] = isDotNet; + toadd["Description"] = GetStringOrEmpty(result["Description"]); + results.Add(toadd); } } @@ -72,6 +72,11 @@ namespace winPEAS.Info.ServicesInfo return results; } + private static string GetStringOrEmpty(object obj) + { + return obj == null ? string.Empty : obj.ToString(); + } + public static List> GetNonstandardServicesFromReg() { List> results = new List>(); @@ -234,7 +239,7 @@ namespace winPEAS.Info.ServicesInfo } } - catch (Exception) + catch (Exception ex) { //Beaprint.PrintException(ex.Message) } diff --git a/winPEAS/winPEASexe/winPEAS/Info/UserInfo/User.cs b/winPEAS/winPEASexe/winPEAS/Info/UserInfo/User.cs index 17a0f45..056e3a2 100644 --- a/winPEAS/winPEASexe/winPEAS/Info/UserInfo/User.cs +++ b/winPEAS/winPEASexe/winPEAS/Info/UserInfo/User.cs @@ -159,10 +159,17 @@ namespace winPEAS.Info.UserInfo ManagementObjectSearcher searcher = new ManagementObjectSearcher(query); foreach (ManagementObject user in searcher.Get()) { - string username = new SecurityIdentifier(user["SID"].ToString()).Translate(typeof(NTAccount)).ToString(); - if (!username.Contains("NT AUTHORITY")) + try + { + string username = new SecurityIdentifier(user["SID"].ToString()).Translate(typeof(NTAccount)).ToString(); + if (!username.Contains("NT AUTHORITY")) + { + retList.Add(username); + } + } + // user SID could not be translated, ignore + catch (Exception) { - retList.Add(username); } } } diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/AccessControlExtension.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/AccessControlExtension.cs similarity index 96% rename from winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/AccessControlExtension.cs rename to winPEAS/winPEASexe/winPEAS/TaskScheduler/AccessControlExtension.cs index 344ff14..a398073 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/AccessControlExtension.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/AccessControlExtension.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Security.AccessControl; +using System.Text; +using System.Threading.Tasks; -namespace winPEAS.TaskScheduler.Native +namespace winPEAS.TaskScheduler { /// Extensions for classes in the System.Security.AccessControl namespace. public static class AccessControlExtension @@ -88,4 +91,4 @@ namespace winPEAS.TaskScheduler.Native } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Action.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Action.cs index c79b8bf..8d6877c 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Action.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Action.cs @@ -1,12 +1,14 @@ -using System; +using Microsoft.Win32; +using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; -using Microsoft.Win32; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using winPEAS.TaskScheduler.V2Interop; namespace winPEAS.TaskScheduler { @@ -15,8 +17,8 @@ namespace winPEAS.TaskScheduler public enum TaskActionType { /// - /// This action performs a command-line operation. For example, the action can run a script, launch an executable, or, if the name of a document is - /// provided, find its associated application and launch the application with the document. + /// This action performs a command-line operation. For example, the action can run a script, launch an executable, or, if the name + /// of a document is provided, find its associated application and launch the application with the document. /// Execute = 0, @@ -30,104 +32,83 @@ namespace winPEAS.TaskScheduler ShowMessage = 7 } - /// - /// An interface that exposes the ability to convert an actions functionality to a PowerShell script. - /// + /// An interface that exposes the ability to convert an actions functionality to a PowerShell script. internal interface IBindAsExecAction { } /// - /// Abstract base class that provides the common properties that are inherited by all action - /// objects. An action object is created by the method. + /// Abstract base class that provides the common properties that are inherited by all action objects. An action object is created by the + /// method. /// [PublicAPI] public abstract class Action : IDisposable, ICloneable, IEquatable, INotifyPropertyChanged, IComparable, IComparable { internal IAction iAction; - internal ITask v1Task; + internal V1Interop.ITask v1Task; /// List of unbound values when working with Actions not associated with a registered task. protected readonly Dictionary unboundValues = new Dictionary(); - internal Action() { } - - internal Action([NotNull] IAction action) + internal Action() { - iAction = action; } - internal Action([NotNull] ITask iTask) - { - v1Task = iTask; - } + internal Action([NotNull] IAction action) => iAction = action; - /// - /// Occurs when a property value changes. - /// + internal Action([NotNull] V1Interop.ITask iTask) => v1Task = iTask; + + /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; - /// - /// Gets the type of the action. - /// + /// Gets the type of the action. /// The type of the action. [XmlIgnore] public TaskActionType ActionType => iAction?.Type ?? InternalActionType; - /// - /// Gets or sets the identifier of the action. - /// + /// Gets or sets the identifier of the action. [DefaultValue(null)] [XmlAttribute(AttributeName = "id")] public virtual string Id { - get { return GetProperty(nameof(Id)); } - set { SetProperty(nameof(Id), value); } + get => GetProperty(nameof(Id)); + set => SetProperty(nameof(Id), value); } internal abstract TaskActionType InternalActionType { get; } - /// - /// Creates the specified action. - /// + /// Creates the specified action. /// Type of the action to instantiate. /// of specified type. public static Action CreateAction(TaskActionType actionType) => Activator.CreateInstance(GetObjectType(actionType)) as Action; - /// - /// Creates a new object that is a copy of the current instance. - /// - /// - /// A new object that is a copy of this instance. - /// + /// Creates a new object that is a copy of the current instance. + /// A new object that is a copy of this instance. public object Clone() { - Action ret = CreateAction(ActionType); + var ret = CreateAction(ActionType); ret.CopyProperties(this); return ret; } - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current + /// instance precedes, follows, or occurs in the same position in the sort order as the other object. + /// /// An object to compare with this instance. /// A value that indicates the relative order of the objects being compared. public int CompareTo(Action obj) => string.Compare(Id, obj?.Id, StringComparison.InvariantCulture); - /// - /// Releases all resources used by this class. - /// + /// Releases all resources used by this class. public virtual void Dispose() { if (iAction != null) Marshal.ReleaseComObject(iAction); } - /// - /// 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. - /// + /// 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([CanBeNull] object obj) { if (obj is Action) @@ -135,33 +116,34 @@ namespace winPEAS.TaskScheduler return base.Equals(obj); } - /// - /// Indicates whether the current object is equal to another object of the same type. - /// + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// + /// true if the current object is equal to the parameter; otherwise, false. public virtual bool Equals([NotNull] Action other) => ActionType == other.ActionType && Id == other.Id; - /// - /// 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. - /// + /// 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 = ActionType, B = Id }.GetHashCode(); - /// - /// Returns the action Id. - /// + /// Returns the action Id. /// String representation of action. public override string ToString() => Id; + /// Returns a that represents this action. + /// The culture. + /// String representation of action. + public virtual string ToString([NotNull] System.Globalization.CultureInfo culture) + { + using (new CultureSwitcher(culture)) + return ToString(); + } + + int IComparable.CompareTo(object obj) => CompareTo(obj as Action); + internal static Action ActionFromScript(string actionType, string script) { - TaskActionType tat = TryParse(actionType, TaskActionType.Execute); - Type t = GetObjectType(tat); + var tat = TryParse(actionType, TaskActionType.Execute); + var t = GetObjectType(tat); return (Action)t.InvokeMember("FromPowerShellCommand", BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, new object[] { script }); } @@ -181,82 +163,55 @@ namespace winPEAS.TaskScheduler return null; } - /// - /// Creates a specialized class from a defined interface. - /// + /// Creates a specialized class from a defined interface. /// Version 1.0 interface. /// Specialized action class - internal static Action CreateAction(ITask iTask) + internal static Action CreateAction(V1Interop.ITask iTask) { - ExecAction tempAction = new ExecAction(iTask); - Action a = ConvertFromPowerShellAction(tempAction); - return a ?? tempAction; + var tempAction = new ExecAction(iTask); + return ConvertFromPowerShellAction(tempAction) ?? tempAction; } - /// - /// Creates a specialized class from a defined interface. - /// + /// Creates a specialized class from a defined interface. /// Version 2.0 Action interface. /// Specialized action class internal static Action CreateAction(IAction iAction) { - Type t = GetObjectType(iAction.Type); + var t = GetObjectType(iAction.Type); return Activator.CreateInstance(t, BindingFlags.CreateInstance | BindingFlags.NonPublic | BindingFlags.Instance, null, new object[] { iAction }, null) as Action; } - [NotNull] - private static Type GetObjectType(TaskActionType actionType) - { - switch (actionType) - { - case TaskActionType.ComHandler: - return typeof(ComHandlerAction); - - case TaskActionType.SendEmail: - return typeof(EmailAction); - - case TaskActionType.ShowMessage: - return typeof(ShowMessageAction); - - default: - return typeof(ExecAction); - } - } - internal static T TryParse(string val, T defaultVal) { - T ret = defaultVal; + var ret = defaultVal; if (val != null) try { ret = (T)Enum.Parse(typeof(T), val); } catch { } return ret; } - internal virtual void Bind(ITask iTask) + internal virtual void Bind(V1Interop.ITask iTask) { if (Id != null) iTask.SetDataItem("ActionId", Id); - IBindAsExecAction bindable = this as IBindAsExecAction; + var bindable = this as IBindAsExecAction; if (bindable != null) iTask.SetDataItem("ActionType", InternalActionType.ToString()); - object o = null; - unboundValues.TryGetValue("Path", out o); + unboundValues.TryGetValue("Path", out var o); iTask.SetApplicationName(bindable != null ? ExecAction.PowerShellPath : o?.ToString() ?? string.Empty); - o = null; unboundValues.TryGetValue("Arguments", out o); iTask.SetParameters(bindable != null ? ExecAction.BuildPowerShellCmd(ActionType.ToString(), GetPowerShellCommand()) : o?.ToString() ?? string.Empty); - o = null; unboundValues.TryGetValue("WorkingDirectory", out o); iTask.SetWorkingDirectory(o?.ToString() ?? string.Empty); } internal virtual void Bind(ITaskDefinition iTaskDef) { - IActionCollection iActions = iTaskDef.Actions; + var iActions = iTaskDef.Actions; if (iActions.Count >= ActionCollection.MaxActions) throw new ArgumentOutOfRangeException(nameof(iTaskDef), @"A maximum of 32 actions is allowed within a single task."); CreateV2Action(iActions); Marshal.ReleaseComObject(iActions); - foreach (string key in unboundValues.Keys) + foreach (var key in unboundValues.Keys) { try { ReflectionHelper.SetProperty(iAction, key, unboundValues[key]); } catch (TargetInvocationException tie) { throw tie.InnerException; } @@ -265,21 +220,22 @@ namespace winPEAS.TaskScheduler unboundValues.Clear(); } + /// Copies the properties from another the current instance. + /// The source . + internal virtual void CopyProperties([NotNull] Action sourceAction) => Id = sourceAction.Id; + internal abstract void CreateV2Action(IActionCollection iActions); internal abstract string GetPowerShellCommand(); - internal T GetProperty(string propName, T defaultValue = default(T)) + internal T GetProperty(string propName, T defaultValue = default) { if (iAction == null) - return (unboundValues.TryGetValue(propName, out var value)) ? (T)value : defaultValue; + return unboundValues.TryGetValue(propName, out var value) ? (T)value : defaultValue; return ReflectionHelper.GetProperty((TB)iAction, propName, defaultValue); } - internal void OnPropertyChanged(string propName) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); - } + internal void OnPropertyChanged(string propName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); internal void SetProperty(string propName, T value) { @@ -295,39 +251,40 @@ namespace winPEAS.TaskScheduler OnPropertyChanged(propName); } - /// - /// Copies the properties from another the current instance. - /// - /// The source . - internal virtual void CopyProperties([NotNull] Action sourceAction) + [NotNull] + private static Type GetObjectType(TaskActionType actionType) => actionType switch { - Id = sourceAction.Id; - } - - int IComparable.CompareTo(object obj) => CompareTo(obj as Action); + TaskActionType.ComHandler => typeof(ComHandlerAction), + TaskActionType.SendEmail => typeof(EmailAction), + TaskActionType.ShowMessage => typeof(ShowMessageAction), + _ => typeof(ExecAction), + }; } /// - /// Represents an action that fires a handler. Only available on Task Scheduler 2.0. - /// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. + /// Represents an action that fires a handler. Only available on Task Scheduler 2.0. Only available for Task Scheduler 2.0 on + /// Windows Vista or Windows Server 2003 and later. /// - /// This action is the most complex. It allows the task to execute and In-Proc COM server object that implements the ITaskHandler interface. There is a sample project that shows how to do this in the Downloads section. - /// + /// + /// This action is the most complex. It allows the task to execute and In-Proc COM server object that implements the ITaskHandler + /// interface. There is a sample project that shows how to do this in the Downloads section. + /// + /// + /// + /// + /// + /// [XmlType(IncludeInSchema = true)] [XmlRoot("ComHandler", Namespace = TaskDefinition.tns, IsNullable = false)] public class ComHandlerAction : Action, IBindAsExecAction { - /// - /// Creates an unbound instance of . - /// + /// Creates an unbound instance of . public ComHandlerAction() { } - /// - /// Creates an unbound instance of . - /// + /// Creates an unbound instance of . /// Identifier of the handler class. /// Addition data associated with the handler. public ComHandlerAction(Guid classId, [CanBeNull] string data) @@ -336,87 +293,51 @@ namespace winPEAS.TaskScheduler Data = data; } - internal ComHandlerAction([NotNull] ITask task) : base(task) { } - - internal ComHandlerAction([NotNull] IAction action) : base(action) { } - - /// - /// Gets or sets the identifier of the handler class. - /// - public Guid ClassId + internal ComHandlerAction([NotNull] V1Interop.ITask task) : base(task) { - get { return new Guid(GetProperty(nameof(ClassId), Guid.Empty.ToString())); } - set { SetProperty(nameof(ClassId), value.ToString()); } } - /// - /// Gets the name of the object referred to by . - /// + internal ComHandlerAction([NotNull] IAction action) : base(action) + { + } + + /// Gets or sets the identifier of the handler class. + public Guid ClassId + { + get => new Guid(GetProperty(nameof(ClassId), Guid.Empty.ToString())); + set => SetProperty(nameof(ClassId), value.ToString()); + } + + /// Gets the name of the object referred to by . public string ClassName => GetNameForCLSID(ClassId); - /// - /// Gets or sets additional data that is associated with the handler. - /// + /// Gets or sets additional data that is associated with the handler. [DefaultValue(null)] [CanBeNull] public string Data { - get { return GetProperty(nameof(Data)); } - set { SetProperty(nameof(Data), value); } + get => GetProperty(nameof(Data)); + set => SetProperty(nameof(Data), value); } internal override TaskActionType InternalActionType => TaskActionType.ComHandler; - /// - /// Indicates whether the current object is equal to another object of the same type. - /// + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// + /// true if the current object is equal to the parameter; otherwise, false. public override bool Equals(Action other) => base.Equals(other) && ClassId == ((ComHandlerAction)other).ClassId && Data == ((ComHandlerAction)other).Data; - /// - /// Gets a string representation of the . - /// + /// Gets a string representation of the . /// String representation of this action. - public override string ToString() => string.Format(winPEAS.Properties.Resources.ComHandlerAction, ClassId, Data, Id, ClassName); + public override string ToString() => string.Format(Properties.Resources.ComHandlerAction, ClassId, Data, Id, ClassName); - /// - /// Gets the name for CLSID. - /// - /// The unique identifier. - /// - [CanBeNull] - private static string GetNameForCLSID(Guid guid) + internal static Action FromPowerShellCommand(string p) { - using (RegistryKey k = Registry.ClassesRoot.OpenSubKey("CLSID", false)) - { - if (k != null) - { - using (RegistryKey k2 = k.OpenSubKey(guid.ToString("B"), false)) - return k2?.GetValue(null) as string; - } - } - return null; + var match = System.Text.RegularExpressions.Regex.Match(p, @"^\[Reflection.Assembly\]::LoadFile\('(?:[^']*)'\); \[Microsoft.Win32.TaskScheduler.TaskService\]::RunComHandlerAction\(\[GUID\]\('(?[^']*)'\), '(?[^']*)'\);?\s*$"); + return match.Success ? new ComHandlerAction(new Guid(match.Groups["g"].Value), match.Groups["d"].Value.Replace("''", "'")) : null; } - internal override void CreateV2Action([NotNull] IActionCollection iActions) - { - iAction = iActions.Create(TaskActionType.ComHandler); - } - - internal override string GetPowerShellCommand() - { - var sb = new System.Text.StringBuilder(); - sb.Append($"[Reflection.Assembly]::LoadFile('{Assembly.GetExecutingAssembly().Location}'); "); - sb.Append($"[Microsoft.Win32.TaskScheduler.TaskService]::RunComHandlerAction([GUID]('{ClassId.ToString("D")}'), '{Data?.Replace("'", "''") ?? string.Empty}'); "); - return sb.ToString(); - } - - /// - /// Copies the properties from another the current instance. - /// + /// Copies the properties from another the current instance. /// The source . internal override void CopyProperties(Action sourceAction) { @@ -427,22 +348,54 @@ namespace winPEAS.TaskScheduler Data = ((ComHandlerAction)sourceAction).Data; } } + + internal override void CreateV2Action([NotNull] IActionCollection iActions) => iAction = iActions.Create(TaskActionType.ComHandler); + + internal override string GetPowerShellCommand() + { + var sb = new System.Text.StringBuilder(); + sb.Append($"[Reflection.Assembly]::LoadFile('{Assembly.GetExecutingAssembly().Location}'); "); + sb.Append($"[Microsoft.Win32.TaskScheduler.TaskService]::RunComHandlerAction([GUID]('{ClassId:D}'), '{Data?.Replace("'", "''") ?? string.Empty}'); "); + return sb.ToString(); + } + + /// Gets the name for CLSID. + /// The unique identifier. + /// + [CanBeNull] + private static string GetNameForCLSID(Guid guid) + { + using (var k = Registry.ClassesRoot.OpenSubKey("CLSID", false)) + { + if (k != null) + { + using var k2 = k.OpenSubKey(guid.ToString("B"), false); + return k2?.GetValue(null) as string; + } + } + return null; + } } /// - /// Represents an action that sends an e-mail. - /// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. - /// This action has been deprecated in Windows 8 and later. However, this library is able to mimic its functionality using PowerShell if the property is set to . To disable this conversion, set the value to . + /// Represents an action that sends an e-mail. Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and + /// later.This action has been deprecated in Windows 8 and later. However, this library is able to mimic its + /// functionality using PowerShell if the property is set to . To disable this conversion, set the value to . /// /// The EmailAction allows for an email to be sent when the task is triggered. - /// + /// + /// + ///ea.Attachments = new object[] { "localpath\\ondiskfile.txt" }; + ///]]> + /// + /// [XmlType(IncludeInSchema = true)] [XmlRoot("SendEmail", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class EmailAction : Action, IBindAsExecAction @@ -452,14 +405,10 @@ namespace winPEAS.TaskScheduler private NamedValueCollection nvc; private bool validateAttachments = true; - /// - /// Creates an unbound instance of . - /// + /// Creates an unbound instance of . public EmailAction() { } - /// - /// Creates an unbound instance of . - /// + /// Creates an unbound instance of . /// Subject of the e-mail. /// E-mail address that you want to send the e-mail from. /// E-mail address or addresses that you want to send the e-mail to. @@ -474,19 +423,24 @@ namespace winPEAS.TaskScheduler Server = mailServer; } - internal EmailAction([NotNull] ITask task) : base(task) { } + internal EmailAction([NotNull] V1Interop.ITask task) : base(task) + { + } - internal EmailAction([NotNull] IAction action) : base(action) { } + internal EmailAction([NotNull] IAction action) : base(action) + { + } /// - /// Gets or sets an array of file paths to be sent as attachments with the e-mail. Each item must be a value containing a path to file. + /// Gets or sets an array of file paths to be sent as attachments with the e-mail. Each item must be a value + /// containing a path to file. /// [XmlArray("Attachments", IsNullable = true)] [XmlArrayItem("File", typeof(string))] [DefaultValue(null)] public object[] Attachments { - get { return GetProperty(nameof(Attachments)); } + get => GetProperty(nameof(Attachments)); set { if (value != null) @@ -510,49 +464,39 @@ namespace winPEAS.TaskScheduler } } - /// - /// Gets or sets the e-mail address or addresses that you want to Bcc in the e-mail. - /// + /// Gets or sets the e-mail address or addresses that you want to Bcc in the e-mail. [DefaultValue(null)] public string Bcc { - get { return GetProperty(nameof(Bcc)); } - set { SetProperty(nameof(Bcc), value); } + get => GetProperty(nameof(Bcc)); + set => SetProperty(nameof(Bcc), value); } - /// - /// Gets or sets the body of the e-mail that contains the e-mail message. - /// + /// Gets or sets the body of the e-mail that contains the e-mail message. [DefaultValue(null)] public string Body { - get { return GetProperty(nameof(Body)); } - set { SetProperty(nameof(Body), value); } + get => GetProperty(nameof(Body)); + set => SetProperty(nameof(Body), value); } - /// - /// Gets or sets the e-mail address or addresses that you want to Cc in the e-mail. - /// + /// Gets or sets the e-mail address or addresses that you want to Cc in the e-mail. [DefaultValue(null)] public string Cc { - get { return GetProperty(nameof(Cc)); } - set { SetProperty(nameof(Cc), value); } + get => GetProperty(nameof(Cc)); + set => SetProperty(nameof(Cc), value); } - /// - /// Gets or sets the e-mail address that you want to send the e-mail from. - /// + /// Gets or sets the e-mail address that you want to send the e-mail from. [DefaultValue(null)] public string From { - get { return GetProperty(nameof(From)); } - set { SetProperty(nameof(From), value); } + get => GetProperty(nameof(From)); + set => SetProperty(nameof(From), value); } - /// - /// Gets or sets the header information in the e-mail message to send. - /// + /// Gets or sets the header information in the e-mail message to send. [XmlArray] [XmlArrayItem("HeaderField", typeof(NameValuePair))] [NotNull] @@ -570,85 +514,81 @@ namespace winPEAS.TaskScheduler } } - /// - /// Gets or sets the priority of the e-mail message. - /// - /// - /// A that contains the priority of this message. - /// + /// Gets or sets the priority of the e-mail message. + /// A that contains the priority of this message. [XmlIgnore] [DefaultValue(typeof(System.Net.Mail.MailPriority), "Normal")] public System.Net.Mail.MailPriority Priority { get { - string s; - if (nvc != null && HeaderFields.TryGetValue(ImportanceHeader, out s)) + if (nvc != null && HeaderFields.TryGetValue(ImportanceHeader, out var s)) return TryParse(s, System.Net.Mail.MailPriority.Normal); return System.Net.Mail.MailPriority.Normal; } - set - { - HeaderFields[ImportanceHeader] = value.ToString(); - } + set => HeaderFields[ImportanceHeader] = value.ToString(); } - /// - /// Gets or sets the e-mail address that you want to reply to. - /// + /// Gets or sets the e-mail address that you want to reply to. [DefaultValue(null)] public string ReplyTo { - get { return GetProperty(nameof(ReplyTo)); } - set { SetProperty(nameof(ReplyTo), value); } + get => GetProperty(nameof(ReplyTo)); + set => SetProperty(nameof(ReplyTo), value); } - /// - /// Gets or sets the name of the server that you use to send e-mail from. - /// + /// Gets or sets the name of the server that you use to send e-mail from. [DefaultValue(null)] public string Server { - get { return GetProperty(nameof(Server)); } - set { SetProperty(nameof(Server), value); } + get => GetProperty(nameof(Server)); + set => SetProperty(nameof(Server), value); } - /// - /// Gets or sets the subject of the e-mail. - /// + /// Gets or sets the subject of the e-mail. [DefaultValue(null)] public string Subject { - get { return GetProperty(nameof(Subject)); } - set { SetProperty(nameof(Subject), value); } + get => GetProperty(nameof(Subject)); + set => SetProperty(nameof(Subject), value); } - /// - /// Gets or sets the e-mail address or addresses that you want to send the e-mail to. - /// + /// Gets or sets the e-mail address or addresses that you want to send the e-mail to. [DefaultValue(null)] public string To { - get { return GetProperty(nameof(To)); } - set { SetProperty(nameof(To), value); } + get => GetProperty(nameof(To)); + set => SetProperty(nameof(To), value); } internal override TaskActionType InternalActionType => TaskActionType.SendEmail; - /// - /// Indicates whether the current object is equal to another object of the same type. - /// + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// + /// true if the current object is equal to the parameter; otherwise, false. public override bool Equals(Action other) => base.Equals(other) && GetPowerShellCommand() == other.GetPowerShellCommand(); - /// - /// Gets a string representation of the . - /// + /// Gets a string representation of the . /// String representation of this action. - public override string ToString() => string.Format(winPEAS.Properties.Resources.EmailAction, Subject, To, Cc, Bcc, From, ReplyTo, Body, Server, Id); + public override string ToString() => string.Format(Properties.Resources.EmailAction, Subject, To, Cc, Bcc, From, ReplyTo, Body, Server, Id); + + internal static Action FromPowerShellCommand(string p) + { + var match = System.Text.RegularExpressions.Regex.Match(p, @"^Send-MailMessage -From '(?(?:[^']|'')*)' -Subject '(?(?:[^']|'')*)' -SmtpServer '(?(?:[^']|'')*)'(?: -Encoding UTF8)?(?: -To (?'(?:(?:[^']|'')*)'(?:, '(?:(?:[^']|'')*)')*))?(?: -Cc (?'(?:(?:[^']|'')*)'(?:, '(?:(?:[^']|'')*)')*))?(?: -Bcc (?'(?:(?:[^']|'')*)'(?:, '(?:(?:[^']|'')*)')*))?(?:(?: -BodyAsHtml)? -Body '(?(?:[^']|'')*)')?(?: -Attachments (?'(?:(?:[^']|'')*)'(?:, '(?:(?:[^']|'')*)')*))?(?: -Priority (?High|Normal|Low))?;?\s*$"); + if (match.Success) + { + var action = new EmailAction(UnPrep(FromUTF8(match.Groups["subject"].Value)), UnPrep(match.Groups["from"].Value), FromPS(match.Groups["to"]), UnPrep(FromUTF8(match.Groups["body"].Value)), UnPrep(match.Groups["server"].Value)) + { Cc = FromPS(match.Groups["cc"]), Bcc = FromPS(match.Groups["bcc"]) }; + action.validateAttachments = false; + if (match.Groups["att"].Success) + action.Attachments = Array.ConvertAll(FromPS(match.Groups["att"].Value), s => s); + action.validateAttachments = true; + if (match.Groups["imp"].Success) + action.HeaderFields[ImportanceHeader] = match.Groups["imp"].Value; + return action; + } + return null; + } internal override void Bind(ITaskDefinition iTaskDef) { @@ -656,48 +596,7 @@ namespace winPEAS.TaskScheduler nvc?.Bind(((IEmailAction)iAction).HeaderFields); } - internal override void CreateV2Action(IActionCollection iActions) - { - iAction = iActions.Create(TaskActionType.SendEmail); - } - - internal override string GetPowerShellCommand() - { - // Send-MailMessage [-To] [-Subject] [[-Body] ] [[-SmtpServer] ] -From [-Attachments ] - // [-Bcc ] [-BodyAsHtml] [-Cc ] [-Credential ] [-DeliveryNotificationOption ] - // [-Encoding ] [-Port ] [-Priority ] [-UseSsl] [ ] - bool bodyIsHtml = Body != null && Body.Trim().StartsWith("<") && Body.Trim().EndsWith(">"); - var sb = new System.Text.StringBuilder(); - sb.AppendFormat("Send-MailMessage -From '{0}' -Subject '{1}' -SmtpServer '{2}' -Encoding UTF8", Prep(From), ToUTF8(Prep(Subject)), Prep(Server)); - if (!string.IsNullOrEmpty(To)) - sb.AppendFormat(" -To {0}", ToPS(To)); - if (!string.IsNullOrEmpty(Cc)) - sb.AppendFormat(" -Cc {0}", ToPS(Cc)); - if (!string.IsNullOrEmpty(Bcc)) - sb.AppendFormat(" -Bcc {0}", ToPS(Bcc)); - if (bodyIsHtml) - sb.Append(" -BodyAsHtml"); - if (!string.IsNullOrEmpty(Body)) - sb.AppendFormat(" -Body '{0}'", ToUTF8(Prep(Body))); - if (Attachments != null && Attachments.Length > 0) - sb.AppendFormat(" -Attachments {0}", ToPS(Array.ConvertAll(Attachments, o => Prep(o.ToString())))); - var hdr = new List(HeaderFields.Names); - if (hdr.Contains(ImportanceHeader)) - { - var p = Priority; - if (p != System.Net.Mail.MailPriority.Normal) - sb.Append($" -Priority {p.ToString()}"); - hdr.Remove(ImportanceHeader); - } - if (hdr.Count > 0) - throw new InvalidOperationException("Under Windows 8 and later, EmailAction objects are converted to PowerShell. This action contains headers that are not supported."); - sb.Append("; "); - return sb.ToString(); - } - - /// - /// Copies the properties from another the current instance. - /// + /// Copies the properties from another the current instance. /// The source . internal override void CopyProperties(Action sourceAction) { @@ -719,6 +618,73 @@ namespace winPEAS.TaskScheduler } } + internal override void CreateV2Action(IActionCollection iActions) => iAction = iActions.Create(TaskActionType.SendEmail); + + internal override string GetPowerShellCommand() + { + // Send-MailMessage [-To] [-Subject] [[-Body] ] [[-SmtpServer] ] -From + // [-Attachments ] [-Bcc ] [-BodyAsHtml] [-Cc ] [-Credential ] + // [-DeliveryNotificationOption ] [-Encoding ] [-Port ] [-Priority + // ] [-UseSsl] [ ] + var bodyIsHtml = Body != null && Body.Trim().StartsWith("<") && Body.Trim().EndsWith(">"); + var sb = new System.Text.StringBuilder(); + sb.AppendFormat("Send-MailMessage -From '{0}' -Subject '{1}' -SmtpServer '{2}' -Encoding UTF8", Prep(From), ToUTF8(Prep(Subject)), Prep(Server)); + if (!string.IsNullOrEmpty(To)) + sb.AppendFormat(" -To {0}", ToPS(To)); + if (!string.IsNullOrEmpty(Cc)) + sb.AppendFormat(" -Cc {0}", ToPS(Cc)); + if (!string.IsNullOrEmpty(Bcc)) + sb.AppendFormat(" -Bcc {0}", ToPS(Bcc)); + if (bodyIsHtml) + sb.Append(" -BodyAsHtml"); + if (!string.IsNullOrEmpty(Body)) + sb.AppendFormat(" -Body '{0}'", ToUTF8(Prep(Body))); + if (Attachments != null && Attachments.Length > 0) + sb.AppendFormat(" -Attachments {0}", ToPS(Array.ConvertAll(Attachments, o => Prep(o.ToString())))); + var hdr = new List(HeaderFields.Names); + if (hdr.Contains(ImportanceHeader)) + { + var p = Priority; + if (p != System.Net.Mail.MailPriority.Normal) + sb.Append($" -Priority {p}"); + hdr.Remove(ImportanceHeader); + } + if (hdr.Count > 0) + throw new InvalidOperationException("Under Windows 8 and later, EmailAction objects are converted to PowerShell. This action contains headers that are not supported."); + sb.Append("; "); + return sb.ToString(); + + /*var msg = new System.Net.Mail.MailMessage(this.From, this.To, this.Subject, this.Body); + if (!string.IsNullOrEmpty(this.Bcc)) + msg.Bcc.Add(this.Bcc); + if (!string.IsNullOrEmpty(this.Cc)) + msg.CC.Add(this.Cc); + if (!string.IsNullOrEmpty(this.ReplyTo)) + msg.ReplyTo = new System.Net.Mail.MailAddress(this.ReplyTo); + if (this.Attachments != null && this.Attachments.Length > 0) + foreach (string s in this.Attachments) + msg.Attachments.Add(new System.Net.Mail.Attachment(s)); + if (this.nvc != null) + foreach (var ha in this.HeaderFields) + msg.Headers.Add(ha.Name, ha.Value); + var client = new System.Net.Mail.SmtpClient(this.Server); + client.Send(msg);*/ + } + + private static string[] FromPS(string p) + { + var list = p.Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries); + return Array.ConvertAll(list, i => UnPrep(i).Trim('\'')); + } + + private static string FromPS(System.Text.RegularExpressions.Group g, string delimeter = ";") => g.Success ? string.Join(delimeter, FromPS(g.Value)) : null; + + private static string FromUTF8(string s) + { + var bytes = System.Text.Encoding.UTF8.GetBytes(s); + return System.Text.Encoding.Default.GetString(bytes); + } + private static string Prep(string s) => s?.Replace("'", "''"); private static string ToPS(string input, char[] delimeters = null) @@ -733,21 +699,27 @@ namespace winPEAS.TaskScheduler private static string ToUTF8(string s) { if (s == null) return null; - byte[] bytes = System.Text.Encoding.Default.GetBytes(s); + var bytes = System.Text.Encoding.Default.GetBytes(s); return System.Text.Encoding.UTF8.GetString(bytes); } - } - /// - /// Represents an action that executes a command-line operation. - /// - /// All versions of the base library support the ExecAction. It only has three properties that allow it to run an executable with parameters. - /// + private static string UnPrep(string s) => s?.Replace("''", "'"); + } + + /// Represents an action that executes a command-line operation. + /// + /// All versions of the base library support the ExecAction. It only has three properties that allow it to run an executable with parameters. + /// + /// + /// + /// + /// + /// [XmlRoot("Exec", Namespace = TaskDefinition.tns, IsNullable = false)] public class ExecAction : Action { @@ -759,17 +731,15 @@ namespace winPEAS.TaskScheduler internal const string PowerShellPath = "powershell"; internal const string ScriptIdentifer = "TSML_20140424"; - /// - /// Creates a new instance of an that can be added to . - /// + /// Creates a new instance of an that can be added to . public ExecAction() { } - /// - /// Creates a new instance of an that can be added to . - /// + /// Creates a new instance of an that can be added to . /// Path to an executable file. /// Arguments associated with the command-line operation. This value can be null. - /// Directory that contains either the executable file or the files that are used by the executable file. This value can be null. + /// + /// Directory that contains either the executable file or the files that are used by the executable file. This value can be null. + /// public ExecAction([NotNull] string path, string arguments = null, string workingDirectory = null) { Path = path; @@ -777,13 +747,15 @@ namespace winPEAS.TaskScheduler WorkingDirectory = workingDirectory; } - internal ExecAction([NotNull] ITask task) : base(task) { } + internal ExecAction([NotNull] V1Interop.ITask task) : base(task) + { + } - internal ExecAction([NotNull] IAction action) : base(action) { } + internal ExecAction([NotNull] IAction action) : base(action) + { + } - /// - /// Gets or sets the arguments associated with the command-line operation. - /// + /// Gets or sets the arguments associated with the command-line operation. [DefaultValue("")] public string Arguments { @@ -802,9 +774,7 @@ namespace winPEAS.TaskScheduler } } - /// - /// Gets or sets the path to an executable file. - /// + /// Gets or sets the path to an executable file. [XmlElement("Command")] [DefaultValue("")] public string Path @@ -847,18 +817,55 @@ namespace winPEAS.TaskScheduler internal override TaskActionType InternalActionType => TaskActionType.Execute; - /// - /// Indicates whether the current object is equal to another object of the same type. - /// + /// Determines whether the specified path is a valid filename and, optionally, if it exists. + /// The path. + /// if set to true check if file exists. + /// if set to true throw exception on error. + /// true if the specified path is a valid filename; otherwise, false. + public static bool IsValidPath(string path, bool checkIfExists = true, bool throwOnException = false) + { + try + { + if (path == null) throw new ArgumentNullException(nameof(path)); + /*if (path.StartsWith("\"") && path.EndsWith("\"") && path.Length > 1) + path = path.Substring(1, path.Length - 2);*/ + var fn = System.IO.Path.GetFileName(path); + System.Diagnostics.Debug.WriteLine($"IsValidPath fn={fn}"); + if (fn == string.Empty) + return false; + var dn = System.IO.Path.GetDirectoryName(path); + System.Diagnostics.Debug.WriteLine($"IsValidPath dir={dn ?? "null"}"); + System.IO.Path.GetFullPath(path); + return true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"IsValidPath exc={ex}"); + if (throwOnException) throw; + } + return false; + } + + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. - /// true if the current object is equal to the parameter; otherwise, false. + /// true if the current object is equal to the parameter; otherwise, false. public override bool Equals(Action other) => base.Equals(other) && Path == ((ExecAction)other).Path && Arguments == ((ExecAction)other).Arguments && WorkingDirectory == ((ExecAction)other).WorkingDirectory; /// - /// Gets a string representation of the . + /// Validates the input as a valid filename and optionally checks for its existence. If valid, the property is + /// set to the validated absolute file path. /// + /// The file path to validate. + /// if set to true check if the file exists. + public void SetValidatedPath([NotNull] string path, bool checkIfExists = true) + { + if (IsValidPath(path, checkIfExists, true)) + Path = path; + } + + /// Gets a string representation of the . /// String representation of this action. - public override string ToString() => string.Format(winPEAS.Properties.Resources.ExecAction, Path, Arguments, WorkingDirectory, Id); + public override string ToString() => string.Format(Properties.Resources.ExecAction, Path, Arguments, WorkingDirectory, Id); internal static string BuildPowerShellCmd(string actionType, string cmd) => string.Format(PowerShellArgFormat, ScriptIdentifer, actionType, cmd); @@ -866,11 +873,27 @@ namespace winPEAS.TaskScheduler internal static ExecAction CreatePowerShellAction(string actionType, string cmd) => new ExecAction(PowerShellPath, BuildPowerShellCmd(actionType, cmd)); - internal override void CreateV2Action(IActionCollection iActions) + internal static Action FromPowerShellCommand(string p) { - iAction = iActions.Create(TaskActionType.Execute); + var match = System.Text.RegularExpressions.Regex.Match(p, "^Start-Process -FilePath '(?

[^']*)'(?: -ArgumentList '(?[^']*)')?(?: -WorkingDirectory '(?[^']*)')?;?\\s*$"); + return match.Success ? new ExecAction(match.Groups["p"].Value, match.Groups["a"].Success ? match.Groups["a"].Value.Replace("''", "'") : null, match.Groups["d"].Success ? match.Groups["d"].Value : null) : null; } + ///

Copies the properties from another the current instance. + /// The source . + internal override void CopyProperties(Action sourceAction) + { + if (sourceAction.GetType() == GetType()) + { + base.CopyProperties(sourceAction); + Path = ((ExecAction)sourceAction).Path; + Arguments = ((ExecAction)sourceAction).Arguments; + WorkingDirectory = ((ExecAction)sourceAction).WorkingDirectory; + } + } + + internal override void CreateV2Action(IActionCollection iActions) => iAction = iActions.Create(TaskActionType.Execute); + internal override string GetPowerShellCommand() { var sb = new System.Text.StringBuilder($"Start-Process -FilePath '{Path}'"); @@ -892,44 +915,30 @@ namespace winPEAS.TaskScheduler } return null; } - - /// - /// Copies the properties from another the current instance. - /// - /// The source . - internal override void CopyProperties(Action sourceAction) - { - if (sourceAction.GetType() == GetType()) - { - base.CopyProperties(sourceAction); - Path = ((ExecAction)sourceAction).Path; - Arguments = ((ExecAction)sourceAction).Arguments; - WorkingDirectory = ((ExecAction)sourceAction).WorkingDirectory; - } - } } /// - /// Represents an action that shows a message box when a task is activated. - /// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. - /// This action has been deprecated in Windows 8 and later. However, this library is able to mimic its functionality using PowerShell if the property is set to . To disable this conversion, set the value to . + /// Represents an action that shows a message box when a task is activated. Only available for Task Scheduler 2.0 on Windows Vista + /// or Windows Server 2003 and later.This action has been deprecated in Windows 8 and later. However, this + /// library is able to mimic its functionality using PowerShell if the property is + /// set to . To disable this conversion, set the value to . /// /// Display a message when the trigger fires using the ShowMessageAction. - /// + /// + /// + /// + /// + /// [XmlType(IncludeInSchema = true)] [XmlRoot("ShowMessage", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class ShowMessageAction : Action, IBindAsExecAction { - /// - /// Creates a new unbound instance of . - /// + /// Creates a new unbound instance of . public ShowMessageAction() { } - /// - /// Creates a new unbound instance of . - /// + /// Creates a new unbound instance of . /// Message text that is displayed in the body of the message box. /// Title of the message box. public ShowMessageAction([CanBeNull] string messageBody, [CanBeNull] string title) @@ -938,53 +947,62 @@ namespace winPEAS.TaskScheduler Title = title; } - internal ShowMessageAction([NotNull] ITask task) : base(task) { } + internal ShowMessageAction([NotNull] V1Interop.ITask task) : base(task) + { + } - internal ShowMessageAction([NotNull] IAction action) : base(action) { } + internal ShowMessageAction([NotNull] IAction action) : base(action) + { + } - /// - /// Gets or sets the message text that is displayed in the body of the message box. - /// + /// Gets or sets the message text that is displayed in the body of the message box. [XmlElement("Body")] [DefaultValue(null)] public string MessageBody { - get { return GetProperty(nameof(MessageBody)); } - set { SetProperty(nameof(MessageBody), value); } + get => GetProperty(nameof(MessageBody)); + set => SetProperty(nameof(MessageBody), value); } - /// - /// Gets or sets the title of the message box. - /// + /// Gets or sets the title of the message box. [DefaultValue(null)] public string Title { - get { return GetProperty(nameof(Title)); } - set { SetProperty(nameof(Title), value); } + get => GetProperty(nameof(Title)); + set => SetProperty(nameof(Title), value); } internal override TaskActionType InternalActionType => TaskActionType.ShowMessage; - /// - /// Indicates whether the current object is equal to another object of the same type. - /// + /// Indicates whether the current object is equal to another object of the same type. /// An object to compare with this object. - /// - /// true if the current object is equal to the parameter; otherwise, false. - /// + /// true if the current object is equal to the parameter; otherwise, false. public override bool Equals(Action other) => base.Equals(other) && string.Equals(Title, (other as ShowMessageAction)?.Title) && string.Equals(MessageBody, (other as ShowMessageAction)?.MessageBody); - /// - /// Gets a string representation of the . - /// + /// Gets a string representation of the . /// String representation of this action. - public override string ToString() => string.Format(winPEAS.Properties.Resources.ShowMessageAction, Title, MessageBody, Id); + public override string ToString() => string.Format(Properties.Resources.ShowMessageAction, Title, MessageBody, Id); - internal override void CreateV2Action(IActionCollection iActions) + internal static Action FromPowerShellCommand(string p) { - iAction = iActions.Create(TaskActionType.ShowMessage); + var match = System.Text.RegularExpressions.Regex.Match(p, @"^\[System.Reflection.Assembly\]::LoadWithPartialName\('System.Windows.Forms'\); \[System.Windows.Forms.MessageBox\]::Show\('(?(?:[^']|'')*)'(?:,'(?(?:[^']|'')*)')?\);?\s*$"); + return match.Success ? new ShowMessageAction(match.Groups["msg"].Value.Replace("''", "'"), match.Groups["t"].Success ? match.Groups["t"].Value.Replace("''", "'") : null) : null; } + /// Copies the properties from another the current instance. + /// The source . + internal override void CopyProperties(Action sourceAction) + { + if (sourceAction.GetType() == GetType()) + { + base.CopyProperties(sourceAction); + Title = ((ShowMessageAction)sourceAction).Title; + MessageBody = ((ShowMessageAction)sourceAction).MessageBody; + } + } + + internal override void CreateV2Action(IActionCollection iActions) => iAction = iActions.Create(TaskActionType.ShowMessage); + internal override string GetPowerShellCommand() { // [System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Your_Desired_Message','Your_Desired_Title'); @@ -998,19 +1016,5 @@ namespace winPEAS.TaskScheduler sb.Append("'); "); return sb.ToString(); } - - /// - /// Copies the properties from another the current instance. - /// - /// The source . - internal override void CopyProperties(Action sourceAction) - { - if (sourceAction.GetType() == GetType()) - { - base.CopyProperties(sourceAction); - Title = ((ShowMessageAction)sourceAction).Title; - MessageBody = ((ShowMessageAction)sourceAction).MessageBody; - } - } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ActionCollection.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/ActionCollection.cs index 9341fa9..78f73a9 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ActionCollection.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/ActionCollection.cs @@ -1,64 +1,68 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using winPEAS.TaskScheduler.TaskEditor.Native; namespace winPEAS.TaskScheduler { - /// - /// Options for when to convert actions to PowerShell equivalents. - /// + /// Options for when to convert actions to PowerShell equivalents. [Flags] public enum PowerShellActionPlatformOption { /// - /// Never convert any actions to PowerShell. This will force exceptions to be thrown when unsupported actions our action quantities are found. + /// Never convert any actions to PowerShell. This will force exceptions to be thrown when unsupported actions our action quantities + /// are found. /// Never = 0, + /// - /// Convert actions under Version 1 of the library (Windows XP or Windows Server 2003 and earlier). This option supports multiple actions of all types. - /// If not specified, only a single is supported. Developer must ensure that PowerShell v2 or higher is installed on the target computer. + /// Convert actions under Version 1 of the library (Windows XP or Windows Server 2003 and earlier). This option supports multiple + /// actions of all types. If not specified, only a single is supported. Developer must ensure that + /// PowerShell v2 or higher is installed on the target computer. /// Version1 = 1, + /// - /// Convert all and references to their PowerShell equivalents on systems on or after Windows 8 / Server 2012. + /// Convert all and references to their PowerShell equivalents on systems + /// on or after Windows 8 / Server 2012. /// Version2 = 2, - /// - /// Convert all actions regardless of version or operating system. - /// + + /// Convert all actions regardless of version or operating system. All = 3 } - /// - /// Collection that contains the actions that are performed by the task. - /// + /// Collection that contains the actions that are performed by the task. [XmlRoot("Actions", Namespace = TaskDefinition.tns, IsNullable = false)] [PublicAPI] - public sealed class ActionCollection : IList, IDisposable, IXmlSerializable, IList + public sealed class ActionCollection : IList, IDisposable, IXmlSerializable, IList, INotifyCollectionChanged, INotifyPropertyChanged { internal const int MaxActions = 32; - - private List v1Actions; - private ITask v1Task; - private IActionCollection v2Coll; - private ITaskDefinition v2Def; - private PowerShellActionPlatformOption psConvert = PowerShellActionPlatformOption.Version2; + private const string IndexerName = "Item[]"; private static readonly string psV2IdRegex = $"(?:; )?{nameof(PowerShellConversion)}=(?0|1)"; + private bool inV2set; + private PowerShellActionPlatformOption psConvert = PowerShellActionPlatformOption.Version2; + private readonly List v1Actions; + private V1Interop.ITask v1Task; + private readonly V2Interop.IActionCollection v2Coll; + private V2Interop.ITaskDefinition v2Def; - internal ActionCollection([NotNull] ITask task) + internal ActionCollection([NotNull] V1Interop.ITask task) { v1Task = task; v1Actions = GetV1Actions(); PowerShellConversion = Action.TryParse(v1Task.GetDataItem(nameof(PowerShellConversion)), psConvert | PowerShellActionPlatformOption.Version2); } - internal ActionCollection([NotNull] ITaskDefinition iTaskDef) + internal ActionCollection([NotNull] V2Interop.ITaskDefinition iTaskDef) { v2Def = iTaskDef; v2Coll = iTaskDef.Actions; @@ -75,9 +79,13 @@ namespace winPEAS.TaskScheduler UnconvertUnsupportedActions(); } - /// - /// Gets or sets the identifier of the principal for the task. - /// + /// Occurs when a collection changes. + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// Gets or sets the identifier of the principal for the task. [XmlAttribute] [DefaultValue(null)] [CanBeNull] @@ -95,36 +103,40 @@ namespace winPEAS.TaskScheduler v2Coll.Context = value; else v1Task.SetDataItem("ActionCollectionContext", value); + OnNotifyPropertyChanged(); } } /// - /// Gets the number of actions in the collection. + /// Gets or sets the systems under which unsupported actions will be converted to PowerShell instances. /// - public int Count => (v2Coll != null) ? v2Coll.Count : v1Actions.Count; - - bool ICollection.IsSynchronized => false; - - object ICollection.SyncRoot => this; - - bool ICollection.IsReadOnly => false; - - bool IList.IsFixedSize => false; - - bool IList.IsReadOnly => false; - - /// Gets or sets the systems under which unsupported actions will be converted to PowerShell instances. /// The PowerShell platform options. - /// This property will affect how many actions are physically stored in the system and is tied to the version of Task Scheduler. - /// If set to , then no actions will ever be converted to PowerShell. This will force exceptions to be thrown when unsupported actions our action quantities are found. - /// If set to , then actions will be converted only under Version 1 of the library (Windows XP or Windows Server 2003 and earlier). This option supports multiple actions of all types. If not specified, only a single is supported. Developer must ensure that PowerShell v2 or higher is installed on the target computer. - /// If set to (which is the default value), then and references will be converted to their PowerShell equivalents on systems on or after Windows 8 / Server 2012. - /// If set to , then any actions not supported by the Task Scheduler version will be converted to PowerShell. + /// + /// This property will affect how many actions are physically stored in the system and is tied to the version of Task Scheduler. + /// + /// If set to , then no actions will ever be converted to PowerShell. This will + /// force exceptions to be thrown when unsupported actions our action quantities are found. + /// + /// + /// If set to , then actions will be converted only under Version 1 of the + /// library (Windows XP or Windows Server 2003 and earlier). This option supports multiple actions of all types. If not specified, + /// only a single is supported. Developer must ensure that PowerShell v2 or higher is installed on the + /// target computer. + /// + /// + /// If set to (which is the default value), then and references will be converted to their PowerShell equivalents on systems + /// on or after Windows 8 / Server 2012. + /// + /// + /// If set to , then any actions not supported by the Task Scheduler version will be + /// converted to PowerShell. + /// /// [DefaultValue(typeof(PowerShellActionPlatformOption), "Version2")] public PowerShellActionPlatformOption PowerShellConversion { - get { return psConvert; } + get => psConvert; set { if (psConvert != value) @@ -139,23 +151,79 @@ namespace winPEAS.TaskScheduler if (!SupportV2Conversion) v2Def.Data = string.Format("{0}; {1}=0", v2Def.Data, nameof(PowerShellConversion)); } + OnNotifyPropertyChanged(); } } } + /// Gets or sets an XML-formatted version of the collection. + public string XmlText + { + get + { + if (v2Coll != null) + return v2Coll.XmlText; + return XmlSerializationHelper.WriteObjectToXmlText(this); + } + set + { + if (v2Coll != null) + v2Coll.XmlText = value; + else + XmlSerializationHelper.ReadObjectFromXmlText(value, this); + OnNotifyPropertyChanged(); + } + } + + /// Gets the number of actions in the collection. + public int Count => (v2Coll != null) ? v2Coll.Count : v1Actions.Count; + + bool IList.IsFixedSize => false; + bool ICollection.IsReadOnly => false; + bool IList.IsReadOnly => false; + bool ICollection.IsSynchronized => false; + + object ICollection.SyncRoot => this; private bool SupportV1Conversion => (PowerShellConversion & PowerShellActionPlatformOption.Version1) != 0; private bool SupportV2Conversion => (PowerShellConversion & PowerShellActionPlatformOption.Version2) != 0; - object IList.this[int index] + /// Gets or sets a specified action from the collection. + /// The . + /// The id ( ) of the action to be retrieved. + /// Specialized instance. + /// + /// + /// + /// Mismatching Id for action and lookup. + [NotNull] + public Action this[string actionId] { - get { return this[index]; } - set { this[index] = (Action)value; } + get + { + if (string.IsNullOrEmpty(actionId)) + throw new ArgumentNullException(nameof(actionId)); + var t = Find(a => string.Equals(a.Id, actionId)); + if (t != null) + return t; + throw new ArgumentOutOfRangeException(nameof(actionId)); + } + set + { + if (value == null) + throw new NullReferenceException(); + if (string.IsNullOrEmpty(actionId)) + throw new ArgumentNullException(nameof(actionId)); + var index = IndexOf(actionId); + value.Id = actionId; + if (index >= 0) + this[index] = value; + else + Add(value); + } } - /// - /// Gets or sets a an action at the specified index. - /// + /// Gets or sets a an action at the specified index. /// The zero-based index of the action to get or set. [NotNull] public Action this[int index] @@ -178,24 +246,39 @@ namespace winPEAS.TaskScheduler } set { - if (Count <= index) + if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(nameof(index), index, "Index is not a valid index in the ActionCollection"); + var orig = this[index].Clone(); if (v2Coll != null) { - Insert(index, value); - RemoveAt(index + 1); + inV2set = true; + try + { + Insert(index, value); + RemoveAt(index + 1); + } + finally + { + inV2set = false; + } } else { v1Actions[index] = value; SaveV1Actions(); } + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index)); } } - /// - /// Adds an action to the task. - /// + object IList.this[int index] + { + get => this[index]; + set => this[index] = (Action)value; + } + + /// Adds an action to the task. /// A type derived from . /// A derived class. /// The bound that was added to the collection. @@ -213,12 +296,24 @@ namespace winPEAS.TaskScheduler v1Actions.Add(action); SaveV1Actions(); } + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, action)); return action; } - /// - /// Adds a new instance to the task. - /// + /// Adds an to the task. + /// Path to an executable file. + /// Arguments associated with the command-line operation. This value can be null. + /// + /// Directory that contains either the executable file or the files that are used by the executable file. This value can be null. + /// + /// The bound that was added to the collection. + [NotNull] + public ExecAction Add([NotNull] string path, [CanBeNull] string arguments = null, [CanBeNull] string workingDirectory = null) => + Add(new ExecAction(path, arguments, workingDirectory)); + + /// Adds a new instance to the task. /// Type of task to be created /// Specialized instance. [NotNull] @@ -235,9 +330,33 @@ namespace winPEAS.TaskScheduler return Action.CreateAction(v2Coll.Create(actionType)); } - /// - /// Clears all actions from the task. - /// + /// Adds a collection of actions to the end of the . + /// + /// The actions to be added to the end of the . The collection itself cannot be null and cannot + /// contain null elements. + /// + /// is null. + public void AddRange([ItemNotNull, NotNull] IEnumerable actions) + { + if (actions == null) + throw new ArgumentNullException(nameof(actions)); + if (v1Task != null) + { + var list = new List(actions); + var at = list.Count == 1 && list[0].ActionType == TaskActionType.Execute; + if (!SupportV1Conversion && ((v1Actions.Count + list.Count) > 1 || !at)) + throw new NotV1SupportedException($"Only a single {nameof(ExecAction)} is supported unless the {nameof(PowerShellConversion)} property includes the {nameof(PowerShellActionPlatformOption.Version1)} value."); + v1Actions.AddRange(actions); + SaveV1Actions(); + } + else + { + foreach (var item in actions) + Add(item); + } + } + + /// Clears all actions from the task. public void Clear() { if (v2Coll != null) @@ -247,37 +366,48 @@ namespace winPEAS.TaskScheduler v1Actions.Clear(); SaveV1Actions(); } + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - /// - /// Determines whether the contains a specific value. - /// - /// The object to locate in the . - /// - /// true if is found in the ; otherwise, false. - /// + /// Determines whether the contains a specific value. + /// The object to locate in the . + /// true if is found in the ; otherwise, false. public bool Contains([NotNull] Action item) => Find(a => a.Equals(item)) != null; - /// - /// Copies the elements of the to an array of , starting at a particular index. - /// - /// The array that is the destination of the elements copied from . The array must have zero-based indexing. - /// The zero-based index in array at which copying begins. - public void CopyTo(Action[] array, int arrayIndex) - { - CopyTo(0, array, arrayIndex, Count); - } + /// Determines whether the specified action type is contained in this collection. + /// Type of the action. + /// true if the specified action type is contained in this collection; otherwise, false. + public bool ContainsType(Type actionType) => Find(a => a.GetType() == actionType) != null; /// - /// Copies the elements of the to an array, starting at a particular array index. + /// Copies the elements of the to an array of , starting at a particular index. + /// + /// + /// The array that is the destination of the elements copied from . The array must have zero-based indexing. + /// + /// The zero-based index in array at which copying begins. + public void CopyTo(Action[] array, int arrayIndex) => CopyTo(0, array, arrayIndex, Count); + + /// + /// Copies the elements of the to an array, starting at a particular array index. /// /// The zero-based index in the source at which copying begins. - /// The array that is the destination of the elements copied from . The array must have zero-based indexing. - /// The zero-based index in array at which copying begins. + /// + /// The array that is the destination of the elements copied from . The array must have zero-based indexing. + /// + /// The zero-based index in array at which copying begins. /// The number of elements to copy. - /// is null. - /// is less than 0. - /// The number of elements in the source is greater than the available space from to the end of the destination . + /// is null. + /// is less than 0. + /// + /// The number of elements in the source is greater than the available space from to the end of the destination . + /// public void CopyTo(int index, [NotNull] Action[] array, int arrayIndex, int count) { if (array == null) @@ -290,13 +420,11 @@ namespace winPEAS.TaskScheduler throw new ArgumentOutOfRangeException(nameof(count)); if ((Count - index) > (array.Length - arrayIndex)) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - for (int i = 0; i < count; i++) + for (var i = 0; i < count; i++) array[arrayIndex + i] = (Action)this[index + i].Clone(); } - /// - /// Releases all resources used by this class. - /// + /// Releases all resources used by this class. public void Dispose() { v1Task = null; @@ -305,10 +433,15 @@ namespace winPEAS.TaskScheduler } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire collection. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the first + /// occurrence within the entire collection. /// - /// The delegate that defines the conditions of the to search for. - /// The first that matches the conditions defined by the specified predicate, if found; otherwise, null. + /// + /// The delegate that defines the conditions of the to search for. + /// + /// + /// The first that matches the conditions defined by the specified predicate, if found; otherwise, null. + /// public Action Find(Predicate match) { if (match == null) @@ -319,12 +452,15 @@ namespace winPEAS.TaskScheduler } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based + /// index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements. /// /// The zero-based starting index of the search. /// The number of elements in the collection to search. /// The delegate that defines the conditions of the element to search for. - /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// + /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// public int FindIndexOf(int startIndex, int count, [NotNull] Predicate match) { if (startIndex < 0 || startIndex >= Count) @@ -333,77 +469,49 @@ namespace winPEAS.TaskScheduler throw new ArgumentOutOfRangeException(nameof(count)); if (match == null) throw new ArgumentNullException(nameof(match)); - for (int i = startIndex; i < startIndex + count; i++) + for (var i = startIndex; i < startIndex + count; i++) if (match(this[i])) return i; return -1; } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the collection. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based + /// index of the first occurrence within the collection. /// /// The delegate that defines the conditions of the element to search for. - /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// + /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// public int FindIndexOf([NotNull] Predicate match) => FindIndexOf(0, Count, match); - /// - /// Retrieves an enumeration of each of the actions. - /// - /// Returns an object that implements the interface and that can iterate through the objects within the . + /// Retrieves an enumeration of each of the actions. + /// + /// Returns an object that implements the interface and that can iterate through the + /// objects within the . + /// public IEnumerator GetEnumerator() { if (v2Coll != null) - return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], Action.CreateAction); + return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], Action.CreateAction); return v1Actions.GetEnumerator(); } - void ICollection.CopyTo(Array array, int index) - { - if (array != null && array.Rank != 1) - throw new RankException("Multi-dimensional arrays are not supported."); - Action[] src = new Action[Count]; - CopyTo(src, 0); - Array.Copy(src, 0, array, index, Count); - } - - void ICollection.Add(Action item) - { - Add(item); - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - int IList.Add(object value) - { - Add((Action)value); - return Count - 1; - } - - bool IList.Contains(object value) => Contains((Action)value); - - int IList.IndexOf(object value) => IndexOf((Action)value); - - void IList.Insert(int index, object value) - { - Insert(index, (Action)value); - } - - void IList.Remove(object value) - { - Remove((Action)value); - } - - /// - /// Determines the index of a specific item in the . - /// - /// The object to locate in the . - /// - /// The index of if found in the list; otherwise, -1. - /// + /// Determines the index of a specific item in the . + /// The object to locate in the . + /// The index of if found in the list; otherwise, -1. public int IndexOf(Action item) => FindIndexOf(a => a.Equals(item)); - /// - /// Inserts an action at the specified index. - /// + /// Determines the index of a specific item in the . + /// The id ( ) of the action to be retrieved. + /// The index of if found in the list; otherwise, -1. + public int IndexOf(string actionId) + { + if (string.IsNullOrEmpty(actionId)) + throw new ArgumentNullException(nameof(actionId)); + return FindIndexOf(a => string.Equals(a.Id, actionId)); + } + + /// Inserts an action at the specified index. /// The zero-based index at which action should be inserted. /// The action to insert into the list. public void Insert(int index, [NotNull] Action action) @@ -415,16 +523,16 @@ namespace winPEAS.TaskScheduler if (v2Coll != null) { - Action[] pushItems = new Action[Count - index]; + var pushItems = new Action[Count - index]; if (Count != index) { CopyTo(index, pushItems, 0, Count - index); - for (int j = Count - 1; j >= index; j--) + for (var j = Count - 1; j >= index; j--) RemoveAt(j); } Add(action); if (Count != index) - for (int k = 0; k < pushItems.Length; k++) + for (var k = 0; k < pushItems.Length; k++) Add(pushItems[k]); } else @@ -434,40 +542,24 @@ namespace winPEAS.TaskScheduler v1Actions.Insert(index, action); SaveV1Actions(); } - } - System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - - void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) - { - reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns); - Context = reader.GetAttribute("Context"); - while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) + if (!inV2set) { - Action a = Action.CreateAction(Action.TryParse(reader.LocalName == "Exec" ? "Execute" : reader.LocalName, TaskActionType.Execute)); - XmlSerializationHelper.ReadObject(reader, a); - this.Add(a); + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, action, index)); } - reader.ReadEndElement(); } - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) - { - // TODO:FIX if (!string.IsNullOrEmpty(Context)) writer.WriteAttributeString("Context", Context); - foreach (var item in this) - XmlSerializationHelper.WriteObject(writer, item); - } - - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . + /// Removes the first occurrence of a specific object from the . + /// The object to remove from the . /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// true if was successfully removed from the ; otherwise, false. This method + /// also returns false if is not found in the original . /// public bool Remove([NotNull] Action item) { - int idx = IndexOf(item); + var idx = IndexOf(item); if (idx != -1) { try @@ -480,15 +572,14 @@ namespace winPEAS.TaskScheduler return false; } - /// - /// Removes the action at a specified index. - /// + /// Removes the action at a specified index. /// Index of action to remove. /// Index out of range. public void RemoveAt(int index) { - if (index >= Count) + if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index), index, "Failed to remove action. Index out of range."); + var item = this[index].Clone(); if (v2Coll != null) v2Coll.Remove(++index); else @@ -496,30 +587,91 @@ namespace winPEAS.TaskScheduler v1Actions.RemoveAt(index); SaveV1Actions(); } + if (!inV2set) + { + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + } } - /// - /// Returns a that represents the actions in this collection. - /// - /// - /// A that represents the actions in this collection. - /// + /// Copies the elements of the to a new array. + /// An array containing copies of the elements of the . + [NotNull, ItemNotNull] + public Action[] ToArray() + { + var ret = new Action[Count]; + CopyTo(ret, 0); + return ret; + } + + /// Returns a that represents the actions in this collection. + /// A that represents the actions in this collection. public override string ToString() { if (Count == 1) return this[0].ToString(); if (Count > 1) - return winPEAS.Properties.Resources.MultipleActions; + return Properties.Resources.MultipleActions; return string.Empty; } + void ICollection.Add(Action item) => Add(item); + + int IList.Add(object value) + { + Add((Action)value); + return Count - 1; + } + + bool IList.Contains(object value) => Contains((Action)value); + + void ICollection.CopyTo(Array array, int index) + { + if (array != null && array.Rank != 1) + throw new RankException("Multi-dimensional arrays are not supported."); + var src = new Action[Count]; + CopyTo(src, 0); + Array.Copy(src, 0, array, index, Count); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; + + int IList.IndexOf(object value) => IndexOf((Action)value); + + void IList.Insert(int index, object value) => Insert(index, (Action)value); + + void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) + { + reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns); + Context = reader.GetAttribute("Context"); + while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) + { + var a = Action.CreateAction(Action.TryParse(reader.LocalName == "Exec" ? "Execute" : reader.LocalName, TaskActionType.Execute)); + XmlSerializationHelper.ReadObject(reader, a); + Add(a); + } + reader.ReadEndElement(); + } + + void IList.Remove(object value) => Remove((Action)value); + + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) + { + // TODO:FIX if (!string.IsNullOrEmpty(Context)) writer.WriteAttributeString("Context", Context); + foreach (var item in this) + XmlSerializationHelper.WriteObject(writer, item); + } + internal void ConvertUnsupportedActions() { if (TaskService.LibraryVersion.Minor > 3 && SupportV2Conversion) { - for (int i = 0; i < Count; i++) + for (var i = 0; i < Count; i++) { - Action action = this[i]; + var action = this[i]; var bindable = action as IBindAsExecAction; if (bindable != null && !(action is ComHandlerAction)) this[i] = ExecAction.ConvertToPowerShellAction(action); @@ -527,26 +679,9 @@ namespace winPEAS.TaskScheduler } } - private void UnconvertUnsupportedActions() - { - if (TaskService.LibraryVersion.Minor > 3) - { - for (int i = 0; i < Count; i++) - { - ExecAction action = this[i] as ExecAction; - if (action != null) - { - Action newAction = Action.ConvertFromPowerShellAction(action); - if (newAction != null) - this[i] = newAction; - } - } - } - } - private List GetV1Actions() { - List ret = new List(); + var ret = new List(); if (v1Task != null && v1Task.GetDataItem("ActionType") != "EMPTY") { var exec = new ExecAction(v1Task); @@ -579,6 +714,10 @@ namespace winPEAS.TaskScheduler return ret; } + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + private void SaveV1Actions() { if (v1Task == null) @@ -614,5 +753,22 @@ namespace winPEAS.TaskScheduler v1Task.SetDataItem("ActionType", "MULTIPLE"); } } + + private void UnconvertUnsupportedActions() + { + if (TaskService.LibraryVersion.Minor > 3) + { + for (var i = 0; i < Count; i++) + { + var action = this[i] as ExecAction; + if (action != null) + { + var newAction = Action.ConvertFromPowerShellAction(action); + if (newAction != null) + this[i] = newAction; + } + } + } + } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/CultureSwitcher.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/CultureSwitcher.cs index 1b25bdb..ea58460 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/CultureSwitcher.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/CultureSwitcher.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; using System.Threading; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { @@ -7,6 +11,13 @@ namespace winPEAS.TaskScheduler { private readonly System.Globalization.CultureInfo cur, curUI; + public CultureSwitcher([NotNull] System.Globalization.CultureInfo culture) + { + cur = Thread.CurrentThread.CurrentCulture; + curUI = Thread.CurrentThread.CurrentUICulture; + Thread.CurrentThread.CurrentCulture = Thread.CurrentThread.CurrentUICulture = culture; + } + public void Dispose() { Thread.CurrentThread.CurrentCulture = cur; diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumGlobalizer.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumGlobalizer.cs index 25c2cac..edfac2a 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumGlobalizer.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumGlobalizer.cs @@ -4,10 +4,10 @@ using System.Globalization; namespace winPEAS.TaskScheduler { - /// - /// Functions to provide localized strings for enumerated types and values. - /// - public static class TaskEnumGlobalizer + /// + /// Functions to provide localized strings for enumerated types and values. + /// + public static class TaskEnumGlobalizer { /// /// Gets a string representing the localized value of the provided enum. @@ -37,7 +37,7 @@ namespace winPEAS.TaskScheduler private static string GetCultureEquivalentString(DaysOfTheWeek val) { if (val == DaysOfTheWeek.AllDays) - return winPEAS.Properties.Resources.DOWAllDays; + return Properties.Resources.DOWAllDays; var s = new List(7); var vals = Enum.GetValues(val.GetType()); @@ -47,23 +47,23 @@ namespace winPEAS.TaskScheduler s.Add(DateTimeFormatInfo.CurrentInfo.GetDayName((DayOfWeek)i)); } - return string.Join(winPEAS.Properties.Resources.ListSeparator, s.ToArray()); + return string.Join(Properties.Resources.ListSeparator, s.ToArray()); } private static string GetCultureEquivalentString(MonthsOfTheYear val) { if (val == MonthsOfTheYear.AllMonths) - return winPEAS.Properties.Resources.MOYAllMonths; + return Properties.Resources.MOYAllMonths; var s = new List(12); var vals = Enum.GetValues(val.GetType()); for (var i = 0; i < vals.Length - 1; i++) { if ((val & (MonthsOfTheYear)vals.GetValue(i)) > 0) - s.Add(DateTimeFormatInfo.CurrentInfo.GetMonthName(i+1)); + s.Add(DateTimeFormatInfo.CurrentInfo.GetMonthName(i + 1)); } - return string.Join(winPEAS.Properties.Resources.ListSeparator, s.ToArray()); + return string.Join(Properties.Resources.ListSeparator, s.ToArray()); } private static string BuildEnumString(string preface, object enumValue) @@ -72,8 +72,8 @@ namespace winPEAS.TaskScheduler if (vals.Length == 0) return string.Empty; for (var i = 0; i < vals.Length; i++) - vals[i] = winPEAS.Properties.Resources.ResourceManager.GetString(preface + vals[i]); - return string.Join(winPEAS.Properties.Resources.ListSeparator, vals); + vals[i] = Properties.Resources.ResourceManager.GetString(preface + vals[i]); + return string.Join(Properties.Resources.ListSeparator, vals); } } } diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumUtil.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumUtil.cs new file mode 100644 index 0000000..a30ca43 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/EnumUtil.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace winPEAS.TaskScheduler +{ + internal static class EnumUtil + { + public static void CheckIsEnum(bool checkHasFlags = false) + { + if (!typeof(T).IsEnum) + throw new ArgumentException($"Type '{typeof(T).FullName}' is not an enum"); + if (checkHasFlags && !IsFlags()) + throw new ArgumentException($"Type '{typeof(T).FullName}' doesn't have the 'Flags' attribute"); + } + + public static bool IsFlags() => Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)); + + public static void CheckHasValue(T value, string argName = null) + { + CheckIsEnum(); + if (IsFlags()) + { + var allFlags = 0L; + foreach (T flag in Enum.GetValues(typeof(T))) + allFlags |= Convert.ToInt64(flag); + if ((allFlags & Convert.ToInt64(value)) != 0L) + return; + } + else if (Enum.IsDefined(typeof(T), value)) + return; + throw new InvalidEnumArgumentException(argName ?? "value", Convert.ToInt32(value), typeof(T)); + } + + public static byte BitPosition(this T flags) where T : struct, IConvertible + { + CheckIsEnum(true); + var flagValue = Convert.ToInt64(flags); + if (flagValue == 0) throw new ArgumentException("The flag value is zero and has no bit position."); + var r = Math.Log(flagValue, 2); + if (r % 1 > 0) throw new ArithmeticException("The flag value has more than a single bit set."); + return Convert.ToByte(r); + } + + public static bool IsFlagSet(this T flags, T flag) where T : struct, IConvertible + { + CheckIsEnum(true); + var flagValue = Convert.ToInt64(flag); + return (Convert.ToInt64(flags) & flagValue) == flagValue; + } + + public static bool IsValidFlagValue(this T flags) where T : struct, IConvertible + { + CheckIsEnum(true); + var found = 0L; + foreach (T flag in Enum.GetValues(typeof(T))) + { + if (flags.IsFlagSet(flag)) + found |= Convert.ToInt64(flag); + } + return found == Convert.ToInt64(flags); + } + + public static void SetFlags(ref T flags, T flag, bool set = true) where T : struct, IConvertible + { + CheckIsEnum(true); + var flagsValue = Convert.ToInt64(flags); + var flagValue = Convert.ToInt64(flag); + if (set) + flagsValue |= flagValue; + else + flagsValue &= (~flagValue); + flags = (T)Enum.ToObject(typeof(T), flagsValue); + } + + public static T SetFlags(this T flags, T flag, bool set = true) where T : struct, IConvertible + { + var ret = flags; + SetFlags(ref ret, flag, set); + return ret; + } + + public static T ClearFlags(this T flags, T flag) where T : struct, IConvertible => flags.SetFlags(flag, false); + + public static IEnumerable GetFlags(this T value) where T : struct, IConvertible + { + CheckIsEnum(true); + foreach (T flag in Enum.GetValues(typeof(T))) + { + if (value.IsFlagSet(flag)) + yield return flag; + } + } + + public static T CombineFlags(this IEnumerable flags) where T : struct, IConvertible + { + CheckIsEnum(true); + long lValue = 0; + foreach (var flag in flags) + { + var lFlag = Convert.ToInt64(flag); + lValue |= lFlag; + } + return (T)Enum.ToObject(typeof(T), lValue); + } + + public static string GetDescription(this T value) where T : struct, IConvertible + { + CheckIsEnum(); + var name = Enum.GetName(typeof(T), value); + if (name != null) + { + var field = typeof(T).GetField(name); + if (field != null) + { + var attr = Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) as DescriptionAttribute; + if (attr != null) + { + return attr.Description; + } + } + } + return null; + } + + /// + /// Converts the string representation of the name or numeric value of one or more enumerated constants to an equivalent enumerated object or returns the value of . If is undefined, it returns the first declared item in the enumerated type. + /// + /// The enumeration type to which to convert . + /// The string representation of the enumeration name or underlying value to convert. + /// true to ignore case; false to consider case. + /// The default value. + /// An object of type whose value is represented by value. + public static TEnum TryParse(string value, bool ignoreCase = false, TEnum defaultVal = default(TEnum)) where TEnum : struct, IConvertible + { + CheckIsEnum(); + try { return (TEnum)Enum.Parse(typeof(TEnum), value, ignoreCase); } catch { } + if (!Enum.IsDefined(typeof(TEnum), defaultVal)) + { + var v = Enum.GetValues(typeof(TEnum)); + if (v != null && v.Length > 0) + return (TEnum)v.GetValue(0); + } + return defaultVal; + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/JetBrains.Annotations.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/JetBrains.Annotations.cs index 18c75f9..1d7ef3a 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/JetBrains.Annotations.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/JetBrains.Annotations.cs @@ -1,34 +1,8 @@ -/* MIT License - -Copyright (c) 2016 JetBrains http://www.jetbrains.com - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. */ - -using System; - -#pragma warning disable 1591 -// ReSharper disable UnusedMember.Global -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable UnusedAutoPropertyAccessor.Global -// ReSharper disable IntroduceOptionalParameters.Global -// ReSharper disable MemberCanBeProtected.Global -// ReSharper disable InconsistentNaming +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { @@ -74,6 +48,231 @@ namespace winPEAS.TaskScheduler AttributeTargets.Delegate | AttributeTargets.Field)] internal sealed class ItemNotNullAttribute : Attribute { } + /// + /// Can be appplied to symbols of types derived from IEnumerable as well as to symbols of Task + /// and Lazy classes to indicate that the value of a collection item, of the Task.Result property + /// or of the Lazy.Value property can be null. + /// + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | AttributeTargets.Property | + AttributeTargets.Delegate | AttributeTargets.Field)] + internal sealed class ItemCanBeNullAttribute : Attribute { } + + /// + /// Indicates that the marked method builds string by format pattern and (optional) arguments. + /// Parameter, which contains format string, should be given in constructor. The format string + /// should be in -like form. + /// + /// + /// [StringFormatMethod("message")] + /// void ShowError(string message, params object[] args) { /* do something */ } + /// + /// void Foo() { + /// ShowError("Failed: {0}"); // Warning: Non-existing argument in format string + /// } + /// + [AttributeUsage( + AttributeTargets.Constructor | AttributeTargets.Method | + AttributeTargets.Property | AttributeTargets.Delegate)] + internal sealed class StringFormatMethodAttribute : Attribute + { + /// + /// Specifies which parameter of an annotated method should be treated as format-string + /// + public StringFormatMethodAttribute([NotNull] string formatParameterName) + { + FormatParameterName = formatParameterName; + } + + [NotNull] public string FormatParameterName { get; private set; } + } + + /// + /// For a parameter that is expected to be one of the limited set of values. + /// Specify fields of which type should be used as values for this parameter. + /// + [AttributeUsage( + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field, + AllowMultiple = true)] + internal sealed class ValueProviderAttribute : Attribute + { + public ValueProviderAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Indicates that the function argument should be string literal and match one + /// of the parameters of the caller function. For example, ReSharper annotates + /// the parameter of . + /// + /// + /// void Foo(string param) { + /// if (param == null) + /// throw new ArgumentNullException("par"); // Warning: Cannot resolve symbol + /// } + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InvokerParameterNameAttribute : Attribute { } + + /// + /// Indicates that the method is contained in a type that implements + /// System.ComponentModel.INotifyPropertyChanged interface and this method + /// is used to notify that some property value changed. + /// + /// + /// The method should be non-static and conform to one of the supported signatures: + /// + /// NotifyChanged(string) + /// NotifyChanged(params string[]) + /// NotifyChanged{T}(Expression{Func{T}}) + /// NotifyChanged{T,U}(Expression{Func{T,U}}) + /// SetProperty{T}(ref T, T, string) + /// + /// + /// + /// public class Foo : INotifyPropertyChanged { + /// public event PropertyChangedEventHandler PropertyChanged; + /// + /// [NotifyPropertyChangedInvocator] + /// protected virtual void NotifyChanged(string propertyName) { ... } + /// + /// string _name; + /// + /// public string Name { + /// get { return _name; } + /// set { _name = value; NotifyChanged("LastName"); /* Warning */ } + /// } + /// } + /// + /// Examples of generated notifications: + /// + /// NotifyChanged("Property") + /// NotifyChanged(() => Property) + /// NotifyChanged((VM x) => x.Property) + /// SetProperty(ref myField, value, "Property") + /// + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class NotifyPropertyChangedInvocatorAttribute : Attribute + { + public NotifyPropertyChangedInvocatorAttribute() { } + public NotifyPropertyChangedInvocatorAttribute([NotNull] string parameterName) + { + ParameterName = parameterName; + } + + [CanBeNull] public string ParameterName { get; private set; } + } + + /// + /// Describes dependency between method input and output. + /// + /// + ///

Function Definition Table syntax:

+ /// + /// FDT ::= FDTRow [;FDTRow]* + /// FDTRow ::= Input => Output | Output <= Input + /// Input ::= ParameterName: Value [, Input]* + /// Output ::= [ParameterName: Value]* {halt|stop|void|nothing|Value} + /// Value ::= true | false | null | notnull | canbenull + /// + /// If method has single input parameter, it's name could be omitted.
+ /// Using halt (or void/nothing, which is the same) for method output + /// means that the methos doesn't return normally (throws or terminates the process).
+ /// Value canbenull is only applicable for output parameters.
+ /// You can use multiple [ContractAnnotation] for each FDT row, or use single attribute + /// with rows separated by semicolon. There is no notion of order rows, all rows are checked + /// for applicability and applied per each program state tracked by R# analysis.
+ ///
+ /// + /// + /// [ContractAnnotation("=> halt")] + /// public void TerminationMethod() + /// + /// + /// [ContractAnnotation("halt <= condition: false")] + /// public void Assert(bool condition, string text) // regular assertion method + /// + /// + /// [ContractAnnotation("s:null => true")] + /// public bool IsNullOrEmpty(string s) // string.IsNullOrEmpty() + /// + /// + /// // A method that returns null if the parameter is null, + /// // and not null if the parameter is not null + /// [ContractAnnotation("null => null; notnull => notnull")] + /// public object Transform(object data) + /// + /// + /// [ContractAnnotation("=> true, result: notnull; => false, result: null")] + /// public bool TryParse(string s, out Person result) + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + internal sealed class ContractAnnotationAttribute : Attribute + { + public ContractAnnotationAttribute([NotNull] string contract) + : this(contract, false) { } + + public ContractAnnotationAttribute([NotNull] string contract, bool forceFullStates) + { + Contract = contract; + ForceFullStates = forceFullStates; + } + + [NotNull] public string Contract { get; private set; } + + public bool ForceFullStates { get; private set; } + } + + /// + /// Indicates that marked element should be localized or not. + /// + /// + /// [LocalizationRequiredAttribute(true)] + /// class Foo { + /// string str = "my string"; // Warning: Localizable string + /// } + /// + [AttributeUsage(AttributeTargets.All)] + internal sealed class LocalizationRequiredAttribute : Attribute + { + public LocalizationRequiredAttribute() : this(true) { } + + public LocalizationRequiredAttribute(bool required) + { + Required = required; + } + + public bool Required { get; private set; } + } + + /// + /// Indicates that the value of the marked type (or its derivatives) + /// cannot be compared using '==' or '!=' operators and Equals() + /// should be used instead. However, using '==' or '!=' for comparison + /// with null is always permitted. + /// + /// + /// [CannotApplyEqualityOperator] + /// class NoEquality { } + /// + /// class UsesNoEquality { + /// void Test() { + /// var ca1 = new NoEquality(); + /// var ca2 = new NoEquality(); + /// if (ca1 != null) { // OK + /// bool condition = ca1 == ca2; // Warning + /// } + /// } + /// } + /// + [AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct)] + internal sealed class CannotApplyEqualityOperatorAttribute : Attribute { } ///
/// When applied to a target attribute, specifies a requirement for any type marked @@ -108,6 +307,12 @@ namespace winPEAS.TaskScheduler public UsedImplicitlyAttribute() : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + + public UsedImplicitlyAttribute(ImplicitUseTargetFlags targetFlags) + : this(ImplicitUseKindFlags.Default, targetFlags) { } + public UsedImplicitlyAttribute(ImplicitUseKindFlags useKindFlags, ImplicitUseTargetFlags targetFlags) { UseKindFlags = useKindFlags; @@ -126,6 +331,12 @@ namespace winPEAS.TaskScheduler [AttributeUsage(AttributeTargets.Class | AttributeTargets.GenericParameter)] internal sealed class MeansImplicitUseAttribute : Attribute { + public MeansImplicitUseAttribute() + : this(ImplicitUseKindFlags.Default, ImplicitUseTargetFlags.Default) { } + + public MeansImplicitUseAttribute(ImplicitUseKindFlags useKindFlags) + : this(useKindFlags, ImplicitUseTargetFlags.Default) { } + public MeansImplicitUseAttribute(ImplicitUseTargetFlags targetFlags) : this(ImplicitUseKindFlags.Default, targetFlags) { } @@ -180,8 +391,90 @@ namespace winPEAS.TaskScheduler internal sealed class PublicAPIAttribute : Attribute { public PublicAPIAttribute() { } - } - + + public PublicAPIAttribute([NotNull] string comment) + { + Comment = comment; + } + + [CanBeNull] public string Comment { get; private set; } + } + + /// + /// Tells code analysis engine if the parameter is completely handled when the invoked method is on stack. + /// If the parameter is a delegate, indicates that delegate is executed while the method is executed. + /// If the parameter is an enumerable, indicates that it is enumerated while the method is executed. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class InstantHandleAttribute : Attribute { } + + /// + /// Indicates that a method does not make any observable state changes. + /// The same as System.Diagnostics.Contracts.PureAttribute. + /// + /// + /// [Pure] int Multiply(int x, int y) => x * y; + /// + /// void M() { + /// Multiply(123, 42); // Waring: Return value of pure method is not used + /// } + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class PureAttribute : Attribute { } + + /// + /// Indicates that the return value of method invocation must be used. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class MustUseReturnValueAttribute : Attribute + { + public MustUseReturnValueAttribute() { } + + public MustUseReturnValueAttribute([NotNull] string justification) + { + Justification = justification; + } + + [CanBeNull] public string Justification { get; private set; } + } + + /// + /// Indicates the type member or parameter of some type, that should be used instead of all other ways + /// to get the value that type. This annotation is useful when you have some "context" value evaluated + /// and stored somewhere, meaning that all other ways to get this value must be consolidated with existing one. + /// + /// + /// class Foo { + /// [ProvidesContext] IBarService _barService = ...; + /// + /// void ProcessNode(INode node) { + /// DoSomething(node, node.GetGlobalServices().Bar); + /// // ^ Warning: use value of '_barService' field + /// } + /// } + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.GenericParameter)] + internal sealed class ProvidesContextAttribute : Attribute { } + + /// + /// Indicates that a parameter is a path to a file or a folder within a web project. + /// Path can be relative or absolute, starting from web root (~). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class PathReferenceAttribute : Attribute + { + public PathReferenceAttribute() { } + + public PathReferenceAttribute([NotNull, PathReference] string basePath) + { + BasePath = basePath; + } + + [CanBeNull] public string BasePath { get; private set; } + } + /// /// An extension method marked with this attribute is processed by ReSharper code completion /// as a 'Source Template'. When extension method is completed over some expression, it's source code @@ -262,6 +555,278 @@ namespace winPEAS.TaskScheduler [CanBeNull] public string Target { get; set; } } + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaMasterLocationFormatAttribute : Attribute + { + public AspMvcAreaMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaPartialViewLocationFormatAttribute : Attribute + { + public AspMvcAreaPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcAreaViewLocationFormatAttribute : Attribute + { + public AspMvcAreaViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcMasterLocationFormatAttribute : Attribute + { + public AspMvcMasterLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcPartialViewLocationFormatAttribute : Attribute + { + public AspMvcPartialViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = true)] + internal sealed class AspMvcViewLocationFormatAttribute : Attribute + { + public AspMvcViewLocationFormatAttribute([NotNull] string format) + { + Format = format; + } + + [NotNull] public string Format { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC action. If applied to a method, the MVC action name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcActionAttribute : Attribute + { + public AspMvcActionAttribute() { } + + public AspMvcActionAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC area. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcAreaAttribute : Attribute + { + public AspMvcAreaAttribute() { } + + public AspMvcAreaAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is + /// an MVC controller. If applied to a method, the MVC controller name is calculated + /// implicitly from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.ChildActionExtensions.RenderAction(HtmlHelper, String, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcControllerAttribute : Attribute + { + public AspMvcControllerAttribute() { } + + public AspMvcControllerAttribute([NotNull] string anonymousProperty) + { + AnonymousProperty = anonymousProperty; + } + + [CanBeNull] public string AnonymousProperty { get; private set; } + } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC Master. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcMasterAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC model type. Use this attribute + /// for custom wrappers similar to System.Web.Mvc.Controller.View(String, Object). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcModelTypeAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter is an MVC + /// partial view. If applied to a method, the MVC partial view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.RenderPartialExtensions.RenderPartial(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcPartialViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Allows disabling inspections for MVC views within a class or a method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] + internal sealed class AspMvcSuppressViewErrorAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC display template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.DisplayExtensions.DisplayForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcDisplayTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC editor template. + /// Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Html.EditorExtensions.EditorForModel(HtmlHelper, String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcEditorTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. Indicates that a parameter is an MVC template. + /// Use this attribute for custom wrappers similar to + /// System.ComponentModel.DataAnnotations.UIHintAttribute(System.String). + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcTemplateAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component. If applied to a method, the MVC view name is calculated implicitly + /// from the context. Use this attribute for custom wrappers similar to + /// System.Web.Mvc.Controller.View(Object). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component name. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class AspMvcViewComponentAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. If applied to a parameter, indicates that the parameter + /// is an MVC view component view. If applied to a method, the MVC view component view name is default. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class AspMvcViewComponentViewAttribute : Attribute { } + + /// + /// ASP.NET MVC attribute. When applied to a parameter of an attribute, + /// indicates that this parameter is an MVC action name. + /// + /// + /// [ActionName("Foo")] + /// public ActionResult Login(string returnUrl) { + /// ViewBag.ReturnUrl = Url.Action("Foo"); // OK + /// return RedirectToAction("Bar"); // Error: Cannot resolve action + /// } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)] + internal sealed class AspMvcActionSelectorAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] + internal sealed class HtmlElementAttributesAttribute : Attribute + { + public HtmlElementAttributesAttribute() { } + + public HtmlElementAttributesAttribute([NotNull] string name) + { + Name = name; + } + + [CanBeNull] public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Field | AttributeTargets.Property)] + internal sealed class HtmlAttributeValueAttribute : Attribute + { + public HtmlAttributeValueAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + /// + /// Razor attribute. Indicates that a parameter or a method is a Razor section. + /// Use this attribute for custom wrappers similar to + /// System.Web.WebPages.WebPageBase.RenderSection(String). + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method)] + internal sealed class RazorSectionAttribute : Attribute { } + + /// + /// Indicates how method, constructor invocation or property access + /// over collection type affects content of the collection. + /// + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor | AttributeTargets.Property)] + internal sealed class CollectionAccessAttribute : Attribute + { + public CollectionAccessAttribute(CollectionAccessType collectionAccessType) + { + CollectionAccessType = collectionAccessType; + } + + public CollectionAccessType CollectionAccessType { get; private set; } + } + + [Flags] + internal enum CollectionAccessType + { + /// Method does not use or modify content of the collection. + None = 0, + /// Method only reads content of the collection but does not modify it. + Read = 1, + /// Method can change content of the collection but does not add new elements. + ModifyExistingContent = 2, + /// Method can add new elements to the collection. + UpdatedContent = ModifyExistingContent | 4 + } + /// /// Indicates that the marked method is assertion method, i.e. it halts control flow if /// one of the conditions is satisfied. To set the condition, mark one of the parameters with @@ -276,5 +841,199 @@ namespace winPEAS.TaskScheduler /// the attribute is the assertion type. /// [AttributeUsage(AttributeTargets.Parameter)] - internal sealed class AssertionConditionAttribute : Attribute { } -} \ No newline at end of file + internal sealed class AssertionConditionAttribute : Attribute + { + public AssertionConditionAttribute(AssertionConditionType conditionType) + { + ConditionType = conditionType; + } + + public AssertionConditionType ConditionType { get; private set; } + } + + /// + /// Specifies assertion type. If the assertion method argument satisfies the condition, + /// then the execution continues. Otherwise, execution is assumed to be halted. + /// + internal enum AssertionConditionType + { + /// Marked parameter should be evaluated to true. + IS_TRUE = 0, + /// Marked parameter should be evaluated to false. + IS_FALSE = 1, + /// Marked parameter should be evaluated to null value. + IS_NULL = 2, + /// Marked parameter should be evaluated to not null value. + IS_NOT_NULL = 3, + } + + /// + /// Indicates that the marked method unconditionally terminates control flow execution. + /// For example, it could unconditionally throw exception. + /// + [Obsolete("Use [ContractAnnotation('=> halt')] instead")] + [AttributeUsage(AttributeTargets.Method)] + internal sealed class TerminatesProgramAttribute : Attribute { } + + /// + /// Indicates that method is pure LINQ method, with postponed enumeration (like Enumerable.Select, + /// .Where). This annotation allows inference of [InstantHandle] annotation for parameters + /// of delegate type by analyzing LINQ method chains. + /// + [AttributeUsage(AttributeTargets.Method)] + internal sealed class LinqTunnelAttribute : Attribute { } + + /// + /// Indicates that IEnumerable, passed as parameter, is not enumerated. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class NoEnumerationAttribute : Attribute { } + + /// + /// Indicates that parameter is regular expression pattern. + /// + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RegexPatternAttribute : Attribute { } + + /// + /// Prevents the Member Reordering feature from tossing members of the marked class. + /// + /// + /// The attribute must be mentioned in your member reordering patterns + /// + [AttributeUsage( + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct | AttributeTargets.Enum)] + internal sealed class NoReorderAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the type that has ItemsSource property and should be treated + /// as ItemsControl-derived type, to enable inner items DataContext type resolve. + /// + [AttributeUsage(AttributeTargets.Class)] + internal sealed class XamlItemsControlAttribute : Attribute { } + + /// + /// XAML attribute. Indicates the property of some BindingBase-derived type, that + /// is used to bind some item of ItemsControl-derived type. This annotation will + /// enable the DataContext type resolve for XAML bindings for such properties. + /// + /// + /// Property should have the tree ancestor of the ItemsControl type or + /// marked with the attribute. + /// + [AttributeUsage(AttributeTargets.Property)] + internal sealed class XamlItemBindingOfItemsControlAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspChildControlTypeAttribute : Attribute + { + public AspChildControlTypeAttribute([NotNull] string tagName, [NotNull] Type controlType) + { + TagName = tagName; + ControlType = controlType; + } + + [NotNull] public string TagName { get; private set; } + + [NotNull] public Type ControlType { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)] + internal sealed class AspDataFieldsAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspMethodPropertyAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + internal sealed class AspRequiredAttributeAttribute : Attribute + { + public AspRequiredAttributeAttribute([NotNull] string attribute) + { + Attribute = attribute; + } + + [NotNull] public string Attribute { get; private set; } + } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class AspTypePropertyAttribute : Attribute + { + public bool CreateConstructorReferences { get; private set; } + + public AspTypePropertyAttribute(bool createConstructorReferences) + { + CreateConstructorReferences = createConstructorReferences; + } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorImportNamespaceAttribute : Attribute + { + public RazorImportNamespaceAttribute([NotNull] string name) + { + Name = name; + } + + [NotNull] public string Name { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorInjectionAttribute : Attribute + { + public RazorInjectionAttribute([NotNull] string type, [NotNull] string fieldName) + { + Type = type; + FieldName = fieldName; + } + + [NotNull] public string Type { get; private set; } + + [NotNull] public string FieldName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorDirectiveAttribute : Attribute + { + public RazorDirectiveAttribute([NotNull] string directive) + { + Directive = directive; + } + + [NotNull] public string Directive { get; private set; } + } + + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + internal sealed class RazorPageBaseTypeAttribute : Attribute + { + public RazorPageBaseTypeAttribute([NotNull] string baseType) + { + BaseType = baseType; + } + public RazorPageBaseTypeAttribute([NotNull] string baseType, string pageName) + { + BaseType = baseType; + PageName = pageName; + } + + [NotNull] public string BaseType { get; private set; } + [CanBeNull] public string PageName { get; private set; } + } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorHelperCommonAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Property)] + internal sealed class RazorLayoutAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteLiteralMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Method)] + internal sealed class RazorWriteMethodAttribute : Attribute { } + + [AttributeUsage(AttributeTargets.Parameter)] + internal sealed class RazorWriteMethodParameterAttribute : Attribute { } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/NamedValueCollection.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/NamedValueCollection.cs index 851dd95..d64d87e 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/NamedValueCollection.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/NamedValueCollection.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V2; +using winPEAS.TaskScheduler.TaskEditor.Native; +using winPEAS.TaskScheduler.V2Interop; namespace winPEAS.TaskScheduler { @@ -273,6 +276,21 @@ namespace winPEAS.TaskScheduler } } + /// + /// Gets the value of the item at the specified index. + /// + /// The index of the item being requested. + /// The value of the name-value pair at the specified index. + [NotNull] + public string this[int index] + { + get + { + if (v2Coll != null) + return v2Coll[++index].Value; + return unboundDict[index].Value; + } + } /// /// Gets the value of the item with the specified name. @@ -351,6 +369,22 @@ namespace winPEAS.TaskScheduler Add(new NameValuePair(name, value)); } + /// + /// Adds the elements of the specified collection to the end of . + /// + /// The collection of whose elements should be added to the end of . + public void AddRange([ItemNotNull, NotNull] IEnumerable items) + { + if (v2Coll != null) + { + foreach (var item in items) + v2Coll.Create(item.Name, item.Value); + } + else + unboundDict.AddRange(items); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items)); + } + /// /// Clears the entire collection of name-value pairs. /// @@ -432,6 +466,29 @@ namespace winPEAS.TaskScheduler return false; } + /// + /// Removes a selected name-value pair from the collection. + /// + /// Index of the pair to remove. + public void RemoveAt(int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + NameValuePair nvp; + if (v2Coll != null) + { + nvp = new NameValuePair(v2Coll[index]).Clone(); + v2Coll.Remove(index); + } + else + { + nvp = unboundDict[index]; + unboundDict.RemoveAt(index); + } + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Item[]")); + OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, nvp, index)); + } /// /// Gets the value associated with the specified name. diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/ADVAPI32.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/ADVAPI32.cs deleted file mode 100644 index 1341f8a..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/ADVAPI32.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace winPEAS.TaskScheduler.Native -{ - internal static partial class NativeMethods - { - const string ADVAPI32 = "advapi32.dll"; - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - - public static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken); - - - public partial class SafeTokenHandle - { - private const Int32 ERROR_NO_TOKEN = 0x000003F0; - private const Int32 ERROR_INSUFFICIENT_BUFFER = 122; - private static SafeTokenHandle currentProcessToken = null; - } - } -} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/EnumUtil.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/EnumUtil.cs deleted file mode 100644 index 8d209c9..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/EnumUtil.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; - -namespace winPEAS.TaskScheduler.Native -{ - internal static class EnumUtil - { - public static void CheckIsEnum(bool checkHasFlags = false) - { - if (!typeof(T).IsEnum) - throw new ArgumentException($"Type '{typeof(T).FullName}' is not an enum"); - if (checkHasFlags && !IsFlags()) - throw new ArgumentException($"Type '{typeof(T).FullName}' doesn't have the 'Flags' attribute"); - } - - public static bool IsFlags() => Attribute.IsDefined(typeof(T), typeof(FlagsAttribute)); - - public static bool IsFlagSet(this T flags, T flag) where T : struct, IConvertible - { - CheckIsEnum(true); - var flagValue = Convert.ToInt64(flag); - return (Convert.ToInt64(flags) & flagValue) == flagValue; - } - - public static bool IsValidFlagValue(this T flags) where T : struct, IConvertible - { - CheckIsEnum(true); - var found = 0L; - foreach (T flag in Enum.GetValues(typeof(T))) - { - if (flags.IsFlagSet(flag)) - found |= Convert.ToInt64(flag); - } - return found == Convert.ToInt64(flags); - } - - public static void SetFlags(ref T flags, T flag, bool set = true) where T : struct, IConvertible - { - CheckIsEnum(true); - var flagsValue = Convert.ToInt64(flags); - var flagValue = Convert.ToInt64(flag); - if (set) - flagsValue |= flagValue; - else - flagsValue &= (~flagValue); - flags = (T)Enum.ToObject(typeof(T), flagsValue); - } - - public static T SetFlags(this T flags, T flag, bool set = true) where T : struct, IConvertible - { - var ret = flags; - SetFlags(ref ret, flag, set); - return ret; - } - } -} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/InteropUtil.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/InteropUtil.cs deleted file mode 100644 index 6733ff7..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/InteropUtil.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; - -// ReSharper disable once CheckNamespace -namespace winPEAS.TaskScheduler.Native -{ - internal class ComEnumerator : IEnumerator where T : class where TIn : class - { - protected readonly Func converter; - protected IEnumerator iEnum; - - public ComEnumerator(Func getCount, Func indexer, Func converter) - { - IEnumerator Enumerate() - { - for (var x = 1; x <= getCount(); x++) - yield return indexer(x); - } - - this.converter = converter; - iEnum = Enumerate(); - } - - public ComEnumerator(Func getCount, Func indexer, Func converter) - { - IEnumerator Enumerate() - { - for (var x = 1; x <= getCount(); x++) - yield return indexer(x); - } - - this.converter = converter; - iEnum = Enumerate(); - } - - object IEnumerator.Current => Current; - - public virtual T Current => converter(iEnum?.Current); - - public virtual void Dispose() - { - iEnum?.Dispose(); - iEnum = null; - } - - public virtual bool MoveNext() => iEnum?.MoveNext() ?? false; - - public virtual void Reset() - { - iEnum?.Reset(); - } - } -} \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/KERNEL32.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/KERNEL32.cs deleted file mode 100644 index 8c1013c..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/KERNEL32.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Runtime.ConstrainedExecution; -using System.Runtime.InteropServices; - -namespace winPEAS.TaskScheduler.Native -{ - internal static partial class NativeMethods - { - const string KERNEL32 = "Kernel32.dll"; - - [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport(KERNEL32, SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool CloseHandle(IntPtr handle); - - public partial class SafeTokenHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid - { - - internal SafeTokenHandle(IntPtr handle, bool own = true) : base(own) - { - SetHandle(handle); - } - - protected override bool ReleaseHandle() => CloseHandle(handle); - } - } -} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NetServerEnum.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NetServerEnum.cs deleted file mode 100644 index ecbb183..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NetServerEnum.cs +++ /dev/null @@ -1,8 +0,0 @@ - -namespace winPEAS.TaskScheduler.Native -{ - internal static partial class NativeMethods - { - - } -} \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/NotV1SupportedException.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/NotV1SupportedException.cs index 00c105e..0821531 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/NotV1SupportedException.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/NotV1SupportedException.cs @@ -1,8 +1,12 @@ using System; +using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Runtime.Serialization; using System.Security; using System.Security.Permissions; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { @@ -44,6 +48,16 @@ namespace winPEAS.TaskScheduler min = minComp; } + /// + /// Gets a message that describes the current exception. + /// + public override string Message => myMessage; + + /// + /// Gets the minimum supported TaskScheduler version required for this method or property. + /// + public TaskCompatibility MinimumSupportedVersion => min; + internal abstract string LibName { get; } /// @@ -93,6 +107,7 @@ namespace winPEAS.TaskScheduler /// /// The serialization information. /// The streaming context. + protected NotV2SupportedException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } internal NotV2SupportedException() : base(TaskCompatibility.V1) { } internal NotV2SupportedException(string message) : base(message, TaskCompatibility.V1) { } internal override string LibName => "Task Scheduler 2.0 (1.2)"; @@ -109,6 +124,7 @@ namespace winPEAS.TaskScheduler /// /// The serialization information. /// The streaming context. + protected NotSupportedPriorToException(SerializationInfo serializationInfo, StreamingContext streamingContext) : base(serializationInfo, streamingContext) { } internal NotSupportedPriorToException(TaskCompatibility supportedVersion) : base(supportedVersion) { } internal override string LibName => $"Task Scheduler versions prior to 2.{((int)min) - 2} (1.{(int)min})"; } diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ReflectionHelper.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/ReflectionHelper.cs index baabb3d..065ca8e 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ReflectionHelper.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/ReflectionHelper.cs @@ -1,5 +1,9 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { @@ -55,6 +59,18 @@ namespace winPEAS.TaskScheduler return false; } + /// Invokes a named method on a created instance of a type with parameters. + /// The expected type of the method's return value. + /// The type to be instantiated and then used to invoke the method. This method assumes the type has a default public constructor. + /// Name of the method. + /// The arguments to provide to the method invocation. + /// The value returned from the method. + public static T InvokeMethod(Type type, string methodName, params object[] args) + { + object o = Activator.CreateInstance(type); + return InvokeMethod(o, methodName, args); + } + /// Invokes a named method on a created instance of a type with parameters. /// The expected type of the method's return value. /// The type to be instantiated and then used to invoke the method. @@ -74,7 +90,7 @@ namespace winPEAS.TaskScheduler /// The arguments to provide to the method invocation. public static T InvokeMethod(object obj, string methodName, params object[] args) { - Type[] argTypes = (args == null || args.Length == 0) ? Type.EmptyTypes : Array.ConvertAll(args, delegate(object o) { return o == null ? typeof(object) : o.GetType(); }); + Type[] argTypes = (args == null || args.Length == 0) ? Type.EmptyTypes : Array.ConvertAll(args, delegate (object o) { return o == null ? typeof(object) : o.GetType(); }); return InvokeMethod(obj, methodName, argTypes, args); } @@ -120,4 +136,4 @@ namespace winPEAS.TaskScheduler catch { } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ResourceReferenceValue.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/ResourceReferenceValue.cs deleted file mode 100644 index 0d1de00..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/ResourceReferenceValue.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace winPEAS.TaskScheduler -{ - /// - /// Some string values of properties can be set to retrieve their value from existing DLLs as a resource. This class facilitates creating those reference strings. - /// - [PublicAPI] - public class ResourceReferenceValue - { - /// - /// Initializes a new instance of the class. - /// - /// The DLL path. - /// The resource identifier. - public ResourceReferenceValue([NotNull] string dllPath, int resourceId) - { - ResourceFilePath = dllPath; - ResourceIdentifier = resourceId; - } - - /// - /// Gets or sets the resource file path. This can be a relative path, full path or lookup path (e.g. %SystemRoot%\System32\ResourceName.dll). - /// - /// - /// The resource file path. - /// - public string ResourceFilePath { get; set; } - - /// - /// Gets or sets the resource identifier. - /// - /// The resource identifier. - public int ResourceIdentifier { get; set; } - - /// - /// Performs an implicit conversion from to . - /// - /// The value. - /// The result of the conversion. - public static implicit operator string(ResourceReferenceValue value) => value.ToString(); - - /// - /// Returns a in the format required by the Task Scheduler to reference a string in a DLL. - /// - /// A formatted in the format $(@ [Dll], [ResourceID]). - public override string ToString() => $"$(@ {ResourceFilePath}, {ResourceIdentifier})"; - } -} \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Task.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Task.cs index 753f977..089866e 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Task.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Task.cs @@ -5,90 +5,164 @@ using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; +using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Runtime.Serialization.Formatters.Binary; +using System.Security; using System.Security.AccessControl; +using System.Security.Principal; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Schema; using System.Xml.Serialization; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; -using IPrincipal = winPEAS.TaskScheduler.V2.IPrincipal; -// ReSharper disable UnusedMember.Global - -// ReSharper disable InconsistentNaming ReSharper disable SuspiciousTypeConversion.Global +using winPEAS.TaskScheduler.TaskEditor.Native; +using winPEAS.TaskScheduler.V1Interop; +using winPEAS.TaskScheduler.V2Interop; +using TaskStatus = winPEAS.TaskScheduler.V1Interop.TaskStatus; namespace winPEAS.TaskScheduler { - /// Defines what versions of Task Scheduler or the AT command that the task is compatible with. - public enum TaskCompatibility + /// Defines what versions of Task Scheduler or the AT command that the task is compatible with. + public enum TaskCompatibility { /// The task is compatible with the AT command. AT, - /// The task is compatible with Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). + /// + /// The task is compatible with Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). /// Items not available when compared to V2: /// - /// TaskDefinition.Principal.GroupId - All account information can be retrieved via the UserId property. - /// TaskLogonType values Group, None and S4U are not supported. - /// TaskDefinition.Principal.RunLevel == TaskRunLevel.Highest is not supported. - /// Assigning access security to a task is not supported using TaskDefinition.RegistrationInfo.SecurityDescriptorSddlForm or in RegisterTaskDefinition. - /// TaskDefinition.RegistrationInfo.Documentation, Source, URI and Version properties are only supported using this library. See details in the remarks for . - /// TaskDefinition.Settings.AllowDemandStart cannot be false. - /// TaskDefinition.Settings.AllowHardTerminate cannot be false. - /// TaskDefinition.Settings.MultipleInstances can only be IgnoreNew. - /// TaskDefinition.Settings.NetworkSettings cannot have any values. - /// TaskDefinition.Settings.RestartCount can only be 0. - /// TaskDefinition.Settings.StartWhenAvailable can only be false. - /// TaskDefinition.Actions can only contain ExecAction instances unless the TaskDefinition.Actions.PowerShellConversion property has the Version1 flag set. - /// TaskDefinition.Triggers cannot contain CustomTrigger, EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger instances. - /// TaskDefinition.Triggers cannot contain instances with delays set. - /// TaskDefinition.Triggers cannot contain instances with ExecutionTimeLimit or Id properties set. - /// TaskDefinition.Triggers cannot contain LogonTriggers instances with the UserId property set. - /// TaskDefinition.Triggers cannot contain MonthlyDOWTrigger instances with the RunOnLastWeekOfMonth property set to true. - /// TaskDefinition.Triggers cannot contain MonthlyTrigger instances with the RunOnDayWeekOfMonth property set to true. + /// + /// TaskDefinition.Principal.GroupId - All account information can be retrieved via the UserId property. + /// + /// + /// TaskLogonType values Group, None and S4U are not supported. + /// + /// + /// TaskDefinition.Principal.RunLevel == TaskRunLevel.Highest is not supported. + /// + /// + /// + /// Assigning access security to a task is not supported using TaskDefinition.RegistrationInfo.SecurityDescriptorSddlForm or in RegisterTaskDefinition. + /// + /// + /// + /// + /// TaskDefinition.RegistrationInfo.Documentation, Source, URI and Version properties are only supported using this library. See + /// details in the remarks for . + /// + /// + /// + /// TaskDefinition.Settings.AllowDemandStart cannot be false. + /// + /// + /// TaskDefinition.Settings.AllowHardTerminate cannot be false. + /// + /// + /// TaskDefinition.Settings.MultipleInstances can only be IgnoreNew. + /// + /// + /// TaskDefinition.Settings.NetworkSettings cannot have any values. + /// + /// + /// TaskDefinition.Settings.RestartCount can only be 0. + /// + /// + /// TaskDefinition.Settings.StartWhenAvailable can only be false. + /// + /// + /// + /// TaskDefinition.Actions can only contain ExecAction instances unless the TaskDefinition.Actions.PowerShellConversion property has + /// the Version1 flag set. + /// + /// + /// + /// + /// TaskDefinition.Triggers cannot contain CustomTrigger, EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger instances. + /// + /// + /// + /// TaskDefinition.Triggers cannot contain instances with delays set. + /// + /// + /// TaskDefinition.Triggers cannot contain instances with ExecutionTimeLimit or Id properties set. + /// + /// + /// TaskDefinition.Triggers cannot contain LogonTriggers instances with the UserId property set. + /// + /// + /// TaskDefinition.Triggers cannot contain MonthlyDOWTrigger instances with the RunOnLastWeekOfMonth property set to true. + /// + /// + /// TaskDefinition.Triggers cannot contain MonthlyTrigger instances with the RunOnDayWeekOfMonth property set to true. + /// /// /// V1, - /// The task is compatible with Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). + /// + /// The task is compatible with Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). /// - /// This version is the baseline for the new, non-file based Task Scheduler. See remarks for functionality that was - /// not forward-compatible. - /// + /// This version is the baseline for the new, non-file based Task Scheduler. See remarks for + /// functionality that was not forward-compatible. + /// + /// V2, - /// The task is compatible with Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). + /// + /// The task is compatible with Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). /// Changes from V2: /// - /// TaskDefinition.Principal.ProcessTokenSidType can be defined as a value other than Default. - /// TaskDefinition.Actions may not contain EmailAction or ShowMessageAction instances unless the TaskDefinition.Actions.PowerShellConversion property has - /// the Version2 flag set. - /// TaskDefinition.Principal.RequiredPrivileges can have privilege values assigned. - /// TaskDefinition.Settings.DisallowStartOnRemoteAppSession can be set to true. - /// TaskDefinition.UseUnifiedSchedulingEngine can be set to true. + /// + /// TaskDefinition.Principal.ProcessTokenSidType can be defined as a value other than Default. + /// + /// + /// + /// TaskDefinition.Actions may not contain EmailAction or ShowMessageAction instances unless the + /// TaskDefinition.Actions.PowerShellConversion property has the Version2 flag set. + /// + /// + /// + /// TaskDefinition.Principal.RequiredPrivileges can have privilege values assigned. + /// + /// + /// TaskDefinition.Settings.DisallowStartOnRemoteAppSession can be set to true. + /// + /// + /// TaskDefinition.UseUnifiedSchedulingEngine can be set to true. + /// /// /// V2_1, - /// The task is compatible with Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). + /// + /// The task is compatible with Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). /// Changes from V2_1: /// - /// TaskDefinition.Settings.MaintenanceSettings can have Period or Deadline be values other than TimeSpan.Zero or the Exclusive property set to true. - /// TaskDefinition.Settings.Volatile can be set to true. + /// + /// + /// TaskDefinition.Settings.MaintenanceSettings can have Period or Deadline be values other than TimeSpan.Zero or the Exclusive + /// property set to true. + /// + /// + /// + /// TaskDefinition.Settings.Volatile can be set to true. + /// /// /// V2_2, - /// The task is compatible with Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). + /// + /// The task is compatible with Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). /// Changes from V2_2: /// - /// None published. + /// + /// None published. + /// /// /// V2_3 @@ -102,38 +176,39 @@ namespace winPEAS.TaskScheduler Create = 2, /// - /// The Task Scheduler service either registers the task as a new task or as an updated version if the task already exists. Equivalent to Create | Update. + /// The Task Scheduler service either registers the task as a new task or as an updated version if the task already exists. + /// Equivalent to Create | Update. /// CreateOrUpdate = 6, /// - /// The Task Scheduler service registers the disabled task. A disabled task cannot run until it is enabled. For more information, see Enabled Property of - /// TaskSettings and Enabled Property of RegisteredTask. + /// The Task Scheduler service registers the disabled task. A disabled task cannot run until it is enabled. For more information, + /// see Enabled Property of TaskSettings and Enabled Property of RegisteredTask. /// Disable = 8, /// /// The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal. When the - /// TaskFolder.RegisterTaskDefinition or TaskFolder.RegisterTask functions are called with this flag to update a task, the Task Scheduler service does - /// not add the ACE for the new context principal and does not remove the ACE from the old context principal. + /// TaskFolder.RegisterTaskDefinition or TaskFolder.RegisterTask functions are called with this flag to update a task, the Task + /// Scheduler service does not add the ACE for the new context principal and does not remove the ACE from the old context principal. /// DontAddPrincipalAce = 0x10, /// - /// The Task Scheduler service creates the task, but ignores the registration triggers in the task. By ignoring the registration triggers, the task will - /// not execute when it is registered unless a time-based trigger causes it to execute on registration. + /// The Task Scheduler service creates the task, but ignores the registration triggers in the task. By ignoring the registration + /// triggers, the task will not execute when it is registered unless a time-based trigger causes it to execute on registration. /// IgnoreRegistrationTriggers = 0x20, /// - /// The Task Scheduler service registers the task as an updated version of an existing task. When a task with a registration trigger is updated, the task - /// will execute after the update occurs. + /// The Task Scheduler service registers the task as an updated version of an existing task. When a task with a registration trigger + /// is updated, the task will execute after the update occurs. /// Update = 4, /// - /// The Task Scheduler service checks the syntax of the XML that describes the task but does not register the task. This constant cannot be combined with - /// the Create, Update, or CreateOrUpdate values. + /// The Task Scheduler service checks the syntax of the XML that describes the task but does not register the task. This constant + /// cannot be combined with the Create, Update, or CreateOrUpdate values. /// ValidateOnly = 1 } @@ -166,8 +241,8 @@ namespace winPEAS.TaskScheduler Password, /// - /// Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is used, no password is - /// stored by the system and there is no access to either the network or to encrypted files. + /// Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is + /// used, no password is stored by the system and there is no access to either the network or to encrypted files. /// S4U, @@ -177,12 +252,15 @@ namespace winPEAS.TaskScheduler /// Group activation. The groupId field specifies the group. Group, - /// Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task. + /// + /// Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task. + /// ServiceAccount, /// - /// First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used. The password must be - /// specified when a task is registered. This flag is not recommended for new tasks because it is less reliable than Password. + /// First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used. + /// The password must be specified when a task is registered. This flag is not recommended for new tasks because it is less reliable + /// than Password. /// InteractiveTokenOrPassword } @@ -209,20 +287,21 @@ namespace winPEAS.TaskScheduler SeMachineAccountPrivilege, /// - /// This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this privilege. User Right: - /// Act as part of the operating system. + /// This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this + /// privilege. User Right: Act as part of the operating system. /// SeTcbPrivilege, /// - /// Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege identifies its holder as a - /// security operator. User Right: Manage auditing and the security log. + /// Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege + /// identifies its holder as a security operator. User Right: Manage auditing and the security log. /// SeSecurityPrivilege, /// - /// Required to take ownership of an object without being granted discretionary access. This privilege allows the owner value to be set only to those - /// values that the holder may legitimately assign as the owner of an object. User Right: Take ownership of files or other objects. + /// Required to take ownership of an object without being granted discretionary access. This privilege allows the owner value to be + /// set only to those values that the holder may legitimately assign as the owner of an object. User Right: Take ownership of files + /// or other objects. /// SeTakeOwnershipPrivilege, @@ -248,19 +327,19 @@ namespace winPEAS.TaskScheduler SeCreatePermanentPrivilege, /// - /// Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless of the access - /// control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. This privilege is required by the - /// RegSaveKey and RegSaveKeyExfunctions. The following access rights are granted if this privilege is held: READ_CONTROL, ACCESS_SYSTEM_SECURITY, - /// FILE_GENERIC_READ, FILE_TRAVERSE. User Right: Back up files and directories. + /// Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless + /// of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. + /// This privilege is required by the RegSaveKey and RegSaveKeyExfunctions. The following access rights are granted if this + /// privilege is held: READ_CONTROL, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_READ, FILE_TRAVERSE. User Right: Back up files and directories. /// SeBackupPrivilege, /// - /// Required to perform restore operations. This privilege causes the system to grant all write access control to any file, regardless of the ACL - /// specified for the file. Any access request other than write is still evaluated with the ACL. Additionally, this privilege enables you to set any - /// valid user or group security identifier (SID) as the owner of a file. This privilege is required by the RegLoadKey function. The following access - /// rights are granted if this privilege is held: WRITE_DAC, WRITE_OWNER, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_WRITE, FILE_ADD_FILE, - /// FILE_ADD_SUBDIRECTORY, DELETE. User Right: Restore files and directories. + /// Required to perform restore operations. This privilege causes the system to grant all write access control to any file, + /// regardless of the ACL specified for the file. Any access request other than write is still evaluated with the ACL. Additionally, + /// this privilege enables you to set any valid user or group security identifier (SID) as the owner of a file. This privilege is + /// required by the RegLoadKey function. The following access rights are granted if this privilege is held: WRITE_DAC, WRITE_OWNER, + /// ACCESS_SYSTEM_SECURITY, FILE_GENERIC_WRITE, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, DELETE. User Right: Restore files and directories. /// SeRestorePrivilege, @@ -274,14 +353,14 @@ namespace winPEAS.TaskScheduler SeAuditPrivilege, /// - /// Required to modify the nonvolatile RAM of systems that use this type of memory to store configuration information. User Right: Modify firmware - /// environment values. + /// Required to modify the nonvolatile RAM of systems that use this type of memory to store configuration information. User Right: + /// Modify firmware environment values. /// SeSystemEnvironmentPrivilege, /// - /// Required to receive notifications of changes to files or directories. This privilege also causes the system to skip all traversal access checks. It - /// is enabled by default for all users. User Right: Bypass traverse checking. + /// Required to receive notifications of changes to files or directories. This privilege also causes the system to skip all + /// traversal access checks. It is enabled by default for all users. User Right: Bypass traverse checking. /// SeChangeNotifyPrivilege, @@ -292,14 +371,15 @@ namespace winPEAS.TaskScheduler SeUndockPrivilege, /// - /// Required for a domain controller to use the LDAP directory synchronization services. This privilege allows the holder to read all objects and - /// properties in the directory, regardless of the protection on the objects and properties. By default, it is assigned to the Administrator and - /// LocalSystem accounts on domain controllers. User Right: Synchronize directory service data. + /// Required for a domain controller to use the LDAP directory synchronization services. This privilege allows the holder to read + /// all objects and properties in the directory, regardless of the protection on the objects and properties. By default, it is + /// assigned to the Administrator and LocalSystem accounts on domain controllers. User Right: Synchronize directory service data. /// SeSyncAgentPrivilege, /// - /// Required to mark user and computer accounts as trusted for delegation. User Right: Enable computer and user accounts to be trusted for delegation. + /// Required to mark user and computer accounts as trusted for delegation. User Right: Enable computer and user accounts to be + /// trusted for delegation. /// SeEnableDelegationPrivilege, @@ -307,15 +387,16 @@ namespace winPEAS.TaskScheduler SeManageVolumePrivilege, /// - /// Required to impersonate. User Right: Impersonate a client after authentication. Windows XP/2000: This privilege is not supported. Note that this - /// value is supported starting with Windows Server 2003, Windows XP with SP2, and Windows 2000 with SP4. + /// Required to impersonate. User Right: Impersonate a client after authentication. Windows XP/2000: This privilege is not + /// supported. Note that this value is supported starting with Windows Server 2003, Windows XP with SP2, and Windows 2000 with SP4. /// SeImpersonatePrivilege, /// - /// Required to create named file mapping objects in the global namespace during Terminal Services sessions. This privilege is enabled by default for - /// administrators, services, and the local system account. User Right: Create global objects. Windows XP/2000: This privilege is not supported. Note - /// that this value is supported starting with Windows Server 2003, Windows XP with SP2, and Windows 2000 with SP4. + /// Required to create named file mapping objects in the global namespace during Terminal Services sessions. This privilege is + /// enabled by default for administrators, services, and the local system account. User Right: Create global objects. Windows + /// XP/2000: This privilege is not supported. Note that this value is supported starting with Windows Server 2003, Windows XP with + /// SP2, and Windows 2000 with SP4. /// SeCreateGlobalPrivilege, @@ -325,7 +406,9 @@ namespace winPEAS.TaskScheduler /// Required to modify the mandatory integrity level of an object. User Right: Modify an object label. SeRelabelPrivilege, - /// Required to allocate more memory for applications that run in the context of users. User Right: Increase a process working set. + /// + /// Required to allocate more memory for applications that run in the context of users. User Right: Increase a process working set. + /// SeIncreaseWorkingSetPrivilege, /// Required to adjust the time zone associated with the computer's internal clock. User Right: Change the time zone. @@ -336,8 +419,8 @@ namespace winPEAS.TaskScheduler } /// - /// Defines the types of process security identifier (SID) that can be used by tasks. These changes are used to specify the type of process SID in the - /// IPrincipal2 interface. + /// Defines the types of process security identifier (SID) that can be used by tasks. These changes are used to specify the type of + /// process SID in the IPrincipal2 interface. /// public enum TaskProcessTokenSidType { @@ -345,8 +428,9 @@ namespace winPEAS.TaskScheduler None = 0, /// - /// A task SID that is derived from the task name will be added to the process token groups list, and the token default discretionary access control list - /// (DACL) will be modified to allow only the task SID and local system full control and the account SID read control. + /// A task SID that is derived from the task name will be added to the process token groups list, and the token default + /// discretionary access control list (DACL) will be modified to allow only the task SID and local system full control and the + /// account SID read control. /// Unrestricted = 1, @@ -354,6 +438,26 @@ namespace winPEAS.TaskScheduler Default = 2 } + /// Defines how a task is run. + [Flags] + public enum TaskRunFlags + { + /// The task is run with all flags ignored. + NoFlags = 0, + + /// The task is run as the user who is calling the Run method. + AsSelf = 1, + + /// The task is run regardless of constraints such as "do not run on batteries" or "run only if idle". + IgnoreConstraints = 2, + + /// The task is run using a terminal server session identifier. + UseSessionId = 4, + + /// The task is run using a security identifier. + UserSID = 8 + } + /// Defines LUA elevation flags that specify with what privilege level the task will be run. public enum TaskRunLevel { @@ -367,38 +471,43 @@ namespace winPEAS.TaskScheduler } /// - /// Defines what kind of Terminal Server session state change you can use to trigger a task to start. These changes are used to specify the type of state - /// change in the SessionStateChangeTrigger. + /// Defines what kind of Terminal Server session state change you can use to trigger a task to start. These changes are used to specify + /// the type of state change in the SessionStateChangeTrigger. /// public enum TaskSessionStateChangeType { /// - /// Terminal Server console connection state change. For example, when you connect to a user session on the local computer by switching users on the computer. + /// Terminal Server console connection state change. For example, when you connect to a user session on the local computer by + /// switching users on the computer. /// ConsoleConnect = 1, /// - /// Terminal Server console disconnection state change. For example, when you disconnect to a user session on the local computer by switching users on - /// the computer. + /// Terminal Server console disconnection state change. For example, when you disconnect to a user session on the local computer by + /// switching users on the computer. /// ConsoleDisconnect = 2, /// - /// Terminal Server remote connection state change. For example, when a user connects to a user session by using the Remote Desktop Connection program - /// from a remote computer. + /// Terminal Server remote connection state change. For example, when a user connects to a user session by using the Remote Desktop + /// Connection program from a remote computer. /// RemoteConnect = 3, /// - /// Terminal Server remote disconnection state change. For example, when a user disconnects from a user session while using the Remote Desktop Connection - /// program from a remote computer. + /// Terminal Server remote disconnection state change. For example, when a user disconnects from a user session while using the + /// Remote Desktop Connection program from a remote computer. /// RemoteDisconnect = 4, - /// Terminal Server session locked state change. For example, this state change causes the task to run when the computer is locked. + /// + /// Terminal Server session locked state change. For example, this state change causes the task to run when the computer is locked. + /// SessionLock = 7, - /// Terminal Server session unlocked state change. For example, this state change causes the task to run when the computer is unlocked. + /// + /// Terminal Server session unlocked state change. For example, this state change causes the task to run when the computer is unlocked. + /// SessionUnlock = 8 } @@ -413,13 +522,106 @@ namespace winPEAS.TaskScheduler DontAddPrincipalAce = 0x10 } + /***** WAITING TO DETERMINE USE CASE ***** + /// Success and error codes that some methods will expose through . + public enum TaskResultCode + { + /// The task is ready to run at its next scheduled time. + TaskReady = 0x00041300, + /// The task is currently running. + TaskRunning = 0x00041301, + /// The task will not run at the scheduled times because it has been disabled. + TaskDisabled = 0x00041302, + /// The task has not yet run. + TaskHasNotRun = 0x00041303, + /// There are no more runs scheduled for this task. + TaskNoMoreRuns = 0x00041304, + /// One or more of the properties that are needed to run this task on a schedule have not been set. + TaskNotScheduled = 0x00041305, + /// The last run of the task was terminated by the user. + TaskTerminated = 0x00041306, + /// Either the task has no triggers or the existing triggers are disabled or not set. + TaskNoValidTriggers = 0x00041307, + /// Event triggers do not have set run times. + EventTrigger = 0x00041308, + /// A task's trigger is not found. + TriggerNotFound = 0x80041309, + /// One or more of the properties required to run this task have not been set. + TaskNotReady = 0x8004130A, + /// There is no running instance of the task. + TaskNotRunning = 0x8004130B, + /// The Task Scheduler service is not installed on this computer. + ServiceNotInstalled = 0x8004130C, + /// The task object could not be opened. + CannotOpenTask = 0x8004130D, + /// The object is either an invalid task object or is not a task object. + InvalidTask = 0x8004130E, + /// No account information could be found in the Task Scheduler security database for the task indicated. + AccountInformationNotSet = 0x8004130F, + /// Unable to establish existence of the account specified. + AccountNameNotFound = 0x80041310, + /// Corruption was detected in the Task Scheduler security database; the database has been reset. + AccountDbaseCorrupt = 0x80041311, + /// Task Scheduler security services are available only on Windows NT. + NoSecurityServices = 0x80041312, + /// The task object version is either unsupported or invalid. + UnknownObjectVersion = 0x80041313, + /// The task has been configured with an unsupported combination of account settings and run time options. + UnsupportedAccountOption = 0x80041314, + /// The Task Scheduler Service is not running. + ServiceNotRunning = 0x80041315, + /// The task XML contains an unexpected node. + UnexpectedNode = 0x80041316, + /// The task XML contains an element or attribute from an unexpected namespace. + Namespace = 0x80041317, + /// The task XML contains a value which is incorrectly formatted or out of range. + InvalidValue = 0x80041318, + /// The task XML is missing a required element or attribute. + MissingNode = 0x80041319, + /// The task XML is malformed. + MalformedXml = 0x8004131A, + /// The task is registered, but not all specified triggers will start the task. + SomeTriggersFailed = 0x0004131B, + /// The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal. + BatchLogonProblem = 0x0004131C, + /// The task XML contains too many nodes of the same type. + TooManyNodes = 0x8004131D, + /// The task cannot be started after the trigger end boundary. + PastEndBoundary = 0x8004131E, + /// An instance of this task is already running. + AlreadyRunning = 0x8004131F, + /// The task will not run because the user is not logged on. + UserNotLoggedOn = 0x80041320, + /// The task image is corrupt or has been tampered with. + InvalidTaskHash = 0x80041321, + /// The Task Scheduler service is not available. + ServiceNotAvailable = 0x80041322, + /// The Task Scheduler service is too busy to handle your request. Please try again later. + ServiceTooBusy = 0x80041323, + /// + /// The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition. + /// + TaskAttempted = 0x80041324, + /// The Task Scheduler service has asked the task to run. + TaskQueued = 0x00041325, + /// The task is disabled. + TaskDisabled = 0x80041326, + /// The task has properties that are not compatible with earlier versions of Windows. + TaskNotV1Compatible = 0x80041327, + /// The task settings do not allow the task to start on demand. + StartOnDemand = 0x80041328, + } + */ + /// Defines the different states that a registered task can be in. public enum TaskState { /// The state of the task is unknown. Unknown, - /// The task is registered but is disabled and no instances of the task are queued or running. The task cannot be run until it is enabled. + /// + /// The task is registered but is disabled and no instances of the task are queued or running. The task cannot be run until it is enabled. + /// Disabled, /// Instances of the task are queued. @@ -433,28 +635,28 @@ namespace winPEAS.TaskScheduler } /// - /// Specifies how the Task Scheduler performs tasks when the computer is in an idle condition. For information about idle conditions, see Task Idle Conditions. + /// Specifies how the Task Scheduler performs tasks when the computer is in an idle condition. For information about idle conditions, + /// see Task Idle Conditions. /// [PublicAPI] - public sealed class IdleSettings : IDisposable + public sealed class IdleSettings : IDisposable, INotifyPropertyChanged { + private readonly IIdleSettings v2Settings; private ITask v1Task; - private IIdleSettings v2Settings; - internal IdleSettings([NotNull] IIdleSettings iSettings) - { - v2Settings = iSettings; - } + internal IdleSettings([NotNull] IIdleSettings iSettings) => v2Settings = iSettings; - internal IdleSettings([NotNull] ITask iTask) - { - v1Task = iTask; - } + internal IdleSettings([NotNull] ITask iTask) => v1Task = iTask; - /// Gets or sets a value that indicates the amount of time that the computer must be in an idle state before the task is run. + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets or sets a value that indicates the amount of time that the computer must be in an idle state before the task is run. + /// /// - /// A value that indicates the amount of time that the computer must be in an idle state before the task is run. The minimum value is one minute. If this - /// value is TimeSpan.Zero, then the delay will be set to the default of 10 minutes. + /// A value that indicates the amount of time that the computer must be in an idle state before the task is run. The minimum value + /// is one minute. If this value is TimeSpan.Zero, then the delay will be set to the default of 10 minutes. /// [DefaultValue(typeof(TimeSpan), "00:10:00")] [XmlElement("Duration")] @@ -464,7 +666,7 @@ namespace winPEAS.TaskScheduler { if (v2Settings != null) return Task.StringToTimeSpan(v2Settings.IdleDuration); - v1Task.GetIdleWait(out ushort _, out var deadMin); + v1Task.GetIdleWait(out var _, out var deadMin); return TimeSpan.FromMinutes(deadMin); } set @@ -479,16 +681,53 @@ namespace winPEAS.TaskScheduler { v1Task.SetIdleWait((ushort)WaitTimeout.TotalMinutes, (ushort)value.TotalMinutes); } + OnNotifyPropertyChanged(); } } /// - /// Gets or sets a value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. If no value is specified for - /// this property, then the Task Scheduler service will wait indefinitely for an idle condition to occur. + /// Gets or sets a Boolean value that indicates whether the task is restarted when the computer cycles into an idle condition more + /// than once. + /// + [DefaultValue(false)] + public bool RestartOnIdle + { + get => v2Settings?.RestartOnIdle ?? v1Task.HasFlags(TaskFlags.RestartOnIdleResume); + set + { + if (v2Settings != null) + v2Settings.RestartOnIdle = value; + else + v1Task.SetFlags(TaskFlags.RestartOnIdleResume, value); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the Task Scheduler will terminate the task if the idle condition ends before + /// the task is completed. + /// + [DefaultValue(true)] + public bool StopOnIdleEnd + { + get => v2Settings?.StopOnIdleEnd ?? v1Task.HasFlags(TaskFlags.KillOnIdleEnd); + set + { + if (v2Settings != null) + v2Settings.StopOnIdleEnd = value; + else + v1Task.SetFlags(TaskFlags.KillOnIdleEnd, value); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. If no + /// value is specified for this property, then the Task Scheduler service will wait indefinitely for an idle condition to occur. /// /// - /// A value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. The minimum time allowed is 1 minute. If - /// this value is TimeSpan.Zero, then the delay will be set to the default of 1 hour. + /// A value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. The minimum time + /// allowed is 1 minute. If this value is TimeSpan.Zero, then the delay will be set to the default of 1 hour. /// [DefaultValue(typeof(TimeSpan), "01:00:00")] public TimeSpan WaitTimeout @@ -512,6 +751,7 @@ namespace winPEAS.TaskScheduler { v1Task.SetIdleWait((ushort)value.TotalMinutes, (ushort)IdleDuration.TotalMinutes); } + OnNotifyPropertyChanged(); } } @@ -523,23 +763,27 @@ namespace winPEAS.TaskScheduler v1Task = null; } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() { if (v2Settings != null || v1Task != null) return DebugHelper.GetDebugString(this); return base.ToString(); } + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// Specifies the task settings the Task scheduler will use to start task during Automatic maintenance. [XmlType(IncludeInSchema = false)] [PublicAPI] - public sealed class MaintenanceSettings : IDisposable + public sealed class MaintenanceSettings : IDisposable, INotifyPropertyChanged { + private readonly ITaskSettings3 iSettings; private IMaintenanceSettings iMaintSettings; - private ITaskSettings3 iSettings; internal MaintenanceSettings([CanBeNull] ITaskSettings3 iSettings3) { @@ -548,10 +792,14 @@ namespace winPEAS.TaskScheduler iMaintSettings = iSettings.MaintenanceSettings; } + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + /// - /// Gets or sets 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 minimum value is one day. The value of the property should be greater than - /// the value of the property. If the deadline is not specified the task will not be started during emergency Automatic maintenance. + /// Gets or sets 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 minimum value is one day. The value of the property should be greater than the value of the property. If the deadline is not + /// specified the task will not be started during emergency Automatic maintenance. /// /// Property set for a task on a Task Scheduler version prior to 2.2. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -569,10 +817,38 @@ namespace winPEAS.TaskScheduler } else throw new NotSupportedPriorToException(TaskCompatibility.V2_2); + OnNotifyPropertyChanged(); } } - /// Gets or sets the amount of time the task needs to be started during Automatic maintenance. The minimum value is one minute. + /// + /// Gets or sets a value indicating whether the Task Scheduler must start the task during the Automatic maintenance in exclusive + /// mode. The exclusivity is guaranteed only between other maintenance tasks and doesn't grant any ordering priority of the task. If + /// exclusivity is not specified, the task is started in parallel with other maintenance tasks. + /// + /// Property set for a task on a Task Scheduler version prior to 2.2. + [DefaultValue(false)] + public bool Exclusive + { + get => iMaintSettings != null && iMaintSettings.Exclusive; + set + { + if (iSettings != null) + { + if (iMaintSettings == null && value) + iMaintSettings = iSettings.CreateMaintenanceSettings(); + if (iMaintSettings != null) + iMaintSettings.Exclusive = value; + } + else + throw new NotSupportedPriorToException(TaskCompatibility.V2_2); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets the amount of time the task needs to be started during Automatic maintenance. The minimum value is one minute. + /// /// Property set for a task on a Task Scheduler version prior to 2.2. [DefaultValue(typeof(TimeSpan), "00:00:00")] public TimeSpan Period @@ -589,6 +865,7 @@ namespace winPEAS.TaskScheduler } else throw new NotSupportedPriorToException(TaskCompatibility.V2_2); + OnNotifyPropertyChanged(); } } @@ -599,22 +876,28 @@ namespace winPEAS.TaskScheduler Marshal.ReleaseComObject(iMaintSettings); } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() => iMaintSettings != null ? DebugHelper.GetDebugString(this) : base.ToString(); - } + + internal bool IsSet() => iMaintSettings != null && (iMaintSettings.Period != null || iMaintSettings.Deadline != null || iMaintSettings.Exclusive); + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } /// Provides the settings that the Task Scheduler service uses to obtain a network profile. [XmlType(IncludeInSchema = false)] [PublicAPI] - public sealed class NetworkSettings : IDisposable + public sealed class NetworkSettings : IDisposable, INotifyPropertyChanged { - private INetworkSettings v2Settings; + private readonly INetworkSettings v2Settings; - internal NetworkSettings([CanBeNull] INetworkSettings iSettings) - { - v2Settings = iSettings; - } + internal NetworkSettings([CanBeNull] INetworkSettings iSettings) => v2Settings = iSettings; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; /// Gets or sets a GUID value that identifies a network profile. /// Not supported under Task Scheduler 1.0. @@ -634,6 +917,23 @@ namespace winPEAS.TaskScheduler v2Settings.Id = value == Guid.Empty ? null : value.ToString(); else throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets the name of a network profile. The name is used for display purposes. + /// Not supported under Task Scheduler 1.0. + [DefaultValue(null)] + public string Name + { + get => v2Settings?.Name; + set + { + if (v2Settings != null) + v2Settings.Name = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); } } @@ -644,44 +944,84 @@ namespace winPEAS.TaskScheduler Marshal.ReleaseComObject(v2Settings); } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() { if (v2Settings != null) return DebugHelper.GetDebugString(this); return base.ToString(); } - } + + internal bool IsSet() => v2Settings != null && (!string.IsNullOrEmpty(v2Settings.Id) || !string.IsNullOrEmpty(v2Settings.Name)); + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } /// Provides the methods to get information from and control a running task. [XmlType(IncludeInSchema = false)] [PublicAPI] public sealed class RunningTask : Task { - private IRunningTask v2RunningTask; + private readonly IRunningTask v2RunningTask; internal RunningTask([NotNull] TaskService svc, [NotNull] IRegisteredTask iTask, [NotNull] IRunningTask iRunningTask) - : base(svc, iTask) - { - v2RunningTask = iRunningTask; - } + : base(svc, iTask) => v2RunningTask = iRunningTask; internal RunningTask([NotNull] TaskService svc, [NotNull] ITask iTask) : base(svc, iTask) { } + /// Gets the process ID for the engine (process) which is running the task. + /// Not supported under Task Scheduler 1.0. + public uint EnginePID + { + get + { + if (v2RunningTask != null) + return v2RunningTask.EnginePID; + throw new NotV1SupportedException(); + } + } + + /// Gets the name of the current action that the running task is performing. + public string CurrentAction => v2RunningTask != null ? v2RunningTask.CurrentAction : v1Task.GetApplicationName(); + + /// Gets the GUID identifier for this instance of the task. + public Guid InstanceGuid => v2RunningTask != null ? new Guid(v2RunningTask.InstanceGuid) : Guid.Empty; + /// Gets the operational state of the running task. - public override TaskState State => v2RunningTask?.State ?? base.State; } + public override TaskState State => v2RunningTask?.State ?? base.State; + + /// Releases all resources used by this class. + public new void Dispose() + { + base.Dispose(); + if (v2RunningTask != null) Marshal.ReleaseComObject(v2RunningTask); + } + + /// Refreshes all of the local instance variables of the task. + /// Thrown if task is no longer running. + public void Refresh() + { + try { v2RunningTask?.Refresh(); } + catch (COMException ce) when ((uint)ce.ErrorCode == 0x8004130B) + { + throw new InvalidOperationException("The current task is no longer running.", ce); + } + } + } /// - /// Provides the methods that are used to run the task immediately, get any running instances of the task, get or set the credentials that are used to - /// register the task, and the properties that describe the task. + /// Provides the methods that are used to run the task immediately, get any running instances of the task, get or set the credentials + /// that are used to register the task, and the properties that describe the task. /// [XmlType(IncludeInSchema = false)] [PublicAPI] - public class Task : IDisposable, IComparable, IComparable + public class Task : IDisposable, IComparable, IComparable, INotifyPropertyChanged { internal const AccessControlSections defaultAccessControlSections = AccessControlSections.Owner | AccessControlSections.Group | AccessControlSections.Access; internal const SecurityInfos defaultSecurityInfosSections = SecurityInfos.Owner | SecurityInfos.Group | SecurityInfos.DiscretionaryAcl; @@ -689,15 +1029,15 @@ namespace winPEAS.TaskScheduler private static readonly int osLibMinorVer = GetOSLibraryMinorVersion(); private static readonly DateTime v2InvalidDate = new DateTime(1899, 12, 30); + private readonly IRegisteredTask v2Task; private TaskDefinition myTD; - private IRegisteredTask v2Task; internal Task([NotNull] TaskService svc, [NotNull] ITask iTask) { TaskService = svc; v1Task = iTask; ReadOnly = false; - } + } internal Task([NotNull] TaskService svc, [NotNull] IRegisteredTask iTask, ITaskDefinition iDef = null) { @@ -708,14 +1048,17 @@ namespace winPEAS.TaskScheduler ReadOnly = false; } + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + /// Gets the definition of the task. [NotNull] - public TaskDefinition Definition => myTD ?? (myTD = v2Task != null ? new TaskDefinition(GetV2Definition(TaskService, v2Task, true)) : new TaskDefinition(v1Task, Name)); + public TaskDefinition Definition => myTD ??= v2Task != null ? new TaskDefinition(GetV2Definition(TaskService, v2Task, true)) : new TaskDefinition(v1Task, Name); /// Gets or sets a Boolean value that indicates if the registered task is enabled. /// - /// As of version 1.8.1, under V1 systems (prior to Vista), this property will immediately update the Disabled state and re-save the current task. If - /// changes have been made to the , then those changes will be saved. + /// As of version 1.8.1, under V1 systems (prior to Vista), this property will immediately update the Disabled state and re-save the + /// current task. If changes have been made to the , then those changes will be saved. /// public bool Enabled { @@ -729,20 +1072,120 @@ namespace winPEAS.TaskScheduler Definition.Settings.Enabled = value; Definition.V1Save(null); } + OnNotifyPropertyChanged(); } } - /// Gets the path to where the registered task is stored. + /// Gets an instance of the parent folder. + /// A object representing the parent folder of this task. [NotNull] - public string Path => v2Task != null ? v2Task.Path : "\\" + Name; + public TaskFolder Folder + { + get + { + if (v2Task == null) + return TaskService.RootFolder; + + var path = v2Task.Path; + var parentPath = System.IO.Path.GetDirectoryName(path); + if (string.IsNullOrEmpty(parentPath) || parentPath == TaskFolder.rootString) + return TaskService.RootFolder; + return TaskService.GetFolder(parentPath); + } + } + + /// Gets a value indicating whether this task instance is active. + /// true if this task instance is active; otherwise, false. + public bool IsActive + { + get + { + var now = DateTime.Now; + if (!Definition.Settings.Enabled) return false; + foreach (var trigger in Definition.Triggers) + { + if (!trigger.Enabled || now < trigger.StartBoundary || now > trigger.EndBoundary) continue; + if (!(trigger is ICalendarTrigger) || DateTime.MinValue != NextRunTime || trigger is TimeTrigger) + return true; + } + return false; + } + } + + /// Gets the time the registered task was last run. + /// Returns if there are no prior run times. + public DateTime LastRunTime + { + get + { + if (v2Task == null) return v1Task.GetMostRecentRunTime(); + var dt = v2Task.LastRunTime; + return dt == v2InvalidDate ? DateTime.MinValue : dt; + } + } + + /// Gets the results that were returned the last time the registered task was run. + /// The value returned is the last exit code of the last program run via an . + /// + /// + /// + /// + /// + public int LastTaskResult + { + get + { + if (v2Task != null) + return v2Task.LastTaskResult; + return (int)v1Task.GetExitCode(); + } + } + + /// Gets the time when the registered task is next scheduled to run. + /// Returns if there are no future run times. + /// + /// Potentially breaking change in release 1.8.2. For Task Scheduler 2.0, the return value prior to 1.8.2 would be Dec 30, 1899 if + /// there were no future run times. For 1.0, that value would have been DateTime.MinValue. In release 1.8.2 and later, all + /// versions will return DateTime.MinValue if there are no future run times. While this is different from the native 2.0 + /// library, it was deemed more appropriate to have consistency between the two libraries and with other .NET libraries. + /// + public DateTime NextRunTime + { + get + { + if (v2Task == null) return v1Task.GetNextRunTime(); + var ret = v2Task.NextRunTime; + if (ret != DateTime.MinValue && ret != v2InvalidDate) return ret == v2InvalidDate ? DateTime.MinValue : ret; + var nrts = GetRunTimes(DateTime.Now, DateTime.MaxValue, 1); + ret = nrts.Length > 0 ? nrts[0] : DateTime.MinValue; + return ret == v2InvalidDate ? DateTime.MinValue : ret; + } + } /// - /// Gets a value indicating whether this task is read only. Only available if - /// is true. + /// Gets a value indicating whether this task is read only. Only available if is true. /// /// true if read only; otherwise, false. public bool ReadOnly { get; internal set; } + /// Gets or sets the security descriptor for the task. + /// The security descriptor. + [Obsolete("This property will be removed in deference to the GetAccessControl, GetSecurityDescriptorSddlForm, SetAccessControl and SetSecurityDescriptorSddlForm methods.")] + public GenericSecurityDescriptor SecurityDescriptor + { + get + { + var sddl = GetSecurityDescriptorSddlForm(); + return new RawSecurityDescriptor(sddl); + } + set => SetSecurityDescriptorSddlForm(value.GetSddlForm(defaultAccessControlSections)); + } + /// Gets the operational state of the registered task. public virtual TaskState State { @@ -782,9 +1225,20 @@ namespace winPEAS.TaskScheduler [NotNull] public string Name => v2Task != null ? v2Task.Name : System.IO.Path.GetFileNameWithoutExtension(GetV1Path(v1Task)); + /// Gets the number of times the registered task has missed a scheduled run. + /// Not supported under Task Scheduler 1.0. + public int NumberOfMissedRuns => v2Task?.NumberOfMissedRuns ?? throw new NotV1SupportedException(); + + /// Gets the path to where the registered task is stored. + [NotNull] + public string Path => v2Task != null ? v2Task.Path : "\\" + Name; + + /// Gets the XML-formatted registration information for the registered task. + public string Xml => v2Task != null ? v2Task.Xml : Definition.XmlText; + /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, - /// follows, or occurs in the same position in the sort order as the other object. + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current + /// instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// A value that indicates the relative order of the objects being compared. @@ -798,49 +1252,255 @@ namespace winPEAS.TaskScheduler v1Task = null; } + /// Exports the task to the specified file in XML. + /// Name of the output file. + public void Export([NotNull] string outputFileName) => File.WriteAllText(outputFileName, Xml, Encoding.Unicode); + /// - /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task described by the - /// current object. + /// 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 task. public TaskSecurity GetAccessControl() => GetAccessControl(defaultAccessControlSections); /// - /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task described by the - /// current object. + /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task + /// described by the current object. /// /// - /// One of the values that specifies which group of access control entries to retrieve. + /// 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 task. public TaskSecurity GetAccessControl(AccessControlSections includeSections) => new TaskSecurity(this, includeSections); - + /// Gets all instances of the currently running registered task. + /// A with all instances of current task. + /// Not supported under Task Scheduler 1.0. + [NotNull, ItemNotNull] + public RunningTaskCollection GetInstances() => v2Task != null + ? new RunningTaskCollection(TaskService, v2Task.GetInstances(0)) + : throw new NotV1SupportedException(); + + /// + /// Gets the last registration time, looking first at the value and then looking for the + /// most recent registration event in the Event Log. + /// + /// of the last registration or if no value can be found. + public DateTime GetLastRegistrationTime() + { + var ret = Definition.RegistrationInfo.Date; + if (ret != DateTime.MinValue) return ret; + var log = new TaskEventLog(Path, new[] { (int)StandardTaskEventId.JobRegistered }, null, TaskService.TargetServer, TaskService.UserAccountDomain, TaskService.UserName, TaskService.UserPassword); + if (!log.Enabled) return ret; + foreach (var item in log) + { + if (item.TimeCreated.HasValue) + return item.TimeCreated.Value; + } + return ret; + } + + /// Gets the times that the registered task is scheduled to run during a specified time. + /// The starting time for the query. + /// The ending time for the query. + /// The requested number of runs. A value of 0 will return all times requested. + /// The scheduled times that the task will run. + [NotNull] + public DateTime[] GetRunTimes(DateTime start, DateTime end, uint count = 0) + { + const ushort TASK_MAX_RUN_TIMES = 1440; + + NativeMethods.SYSTEMTIME stStart = start; + NativeMethods.SYSTEMTIME stEnd = end; + var runTimes = IntPtr.Zero; + var ret = new DateTime[0]; + try + { + if (v2Task != null) + v2Task.GetRunTimes(ref stStart, ref stEnd, ref count, ref runTimes); + else + { + var count1 = count > 0 && count <= TASK_MAX_RUN_TIMES ? (ushort)count : TASK_MAX_RUN_TIMES; + v1Task.GetRunTimes(ref stStart, ref stEnd, ref count1, ref runTimes); + count = count1; + } + ret = InteropUtil.ToArray(runTimes, (int)count); + } + catch (Exception ex) + { + Debug.WriteLine($"Task.GetRunTimes failed: Error {ex}."); + } + finally + { + Marshal.FreeCoTaskMem(runTimes); + } + Debug.WriteLine($"Task.GetRunTimes ({(v2Task != null ? "V2" : "V1")}): Returned {count} items from {stStart} to {stEnd}."); + return ret; + } + /// Gets the security descriptor for the task. Not available to Task Scheduler 1.0. /// Section(s) of the security descriptor to return. /// The security descriptor for the task. /// Not supported under Task Scheduler 1.0. - public string GetSecurityDescriptorSddlForm(SecurityInfos includeSections = defaultSecurityInfosSections) => v2Task != null ? v2Task.GetSecurityDescriptor((int) includeSections) : throw new NotV1SupportedException(); + public string GetSecurityDescriptorSddlForm(SecurityInfos includeSections = defaultSecurityInfosSections) => v2Task != null ? v2Task.GetSecurityDescriptor((int)includeSections) : throw new NotV1SupportedException(); /// - /// Applies access control list (ACL) entries described by a object to the file described by the current object. + /// Updates the task with any changes made to the by calling from the currently registered folder using the currently + /// registered name. /// - /// A object that describes an access control list (ACL) entry to apply to the current task. + /// Thrown if task was previously registered with a password. + public void RegisterChanges() + { + if (Definition.Principal.RequiresPassword()) + throw new SecurityException("Tasks which have been registered previously with stored passwords must use the TaskFolder.RegisterTaskDefinition method for updates."); + if (v2Task != null) + TaskService.GetFolder(System.IO.Path.GetDirectoryName(Path)).RegisterTaskDefinition(Name, Definition, TaskCreation.Update, null, null, Definition.Principal.LogonType); + else + TaskService.RootFolder.RegisterTaskDefinition(Name, Definition); + } + + /// Runs the registered task immediately. + /// + /// + /// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters, + /// call this method without any values (e.g. + /// Run() + /// ). + /// + /// + /// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value, + /// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in + /// the action properties. + /// + /// + /// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables, + /// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties. + /// + /// + /// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see + /// Task Actions. + /// + /// + /// A instance that defines the new instance of the task. + /// + /// + /// + /// + /// + public RunningTask Run(params string[] parameters) + { + if (v2Task != null) + { + if (parameters.Length > 32) + throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 values is allowed."); + if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260)) + throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, all individual parameters must be less than 260 characters."); + var irt = v2Task.Run(parameters.Length == 0 ? null : parameters); + return irt != null ? new RunningTask(TaskService, v2Task, irt) : null; + } + + v1Task.Run(); + return new RunningTask(TaskService, v1Task); + } + + /// Runs the registered task immediately using specified flags and a session identifier. + /// Defines how the task is run. + /// + /// The terminal server session in which you want to start the task. + /// + /// If the value is not passed into the parameter, then the value + /// specified in this parameter is ignored.If the value is passed into the flags parameter + /// and the sessionID value is less than or equal to 0, then an invalid argument error will be returned. + /// + /// + /// If the value is passed into the parameter and the sessionID + /// value is a valid session ID greater than 0 and if no value is specified for the user parameter, then the Task Scheduler service + /// will try to start the task interactively as the user who is logged on to the specified session. + /// + /// + /// If the value is passed into the parameter and the sessionID + /// value is a valid session ID greater than 0 and if a user is specified in the user parameter, then the Task Scheduler service + /// will try to start the task interactively as the user who is specified in the user parameter. + /// + /// + /// The user for which the task runs. + /// + /// + /// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters, + /// call this method without any values (e.g. + /// RunEx(0, 0, "MyUserName") + /// ). + /// + /// + /// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value, + /// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in + /// the action properties. + /// + /// + /// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables, + /// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties. + /// + /// + /// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see + /// Task Actions. + /// + /// + /// A instance that defines the new instance of the task. + /// + /// + /// This method will return without error, but the task will not run if the AllowDemandStart property of ITaskSettings is set to + /// false for the task. + /// + /// If RunEx is invoked from a disabled task, it will return null and the task will not be run. + /// + /// Not supported under Task Scheduler 1.0. + /// + /// + /// + /// + /// + public RunningTask RunEx(TaskRunFlags flags, int sessionID, string user, params string[] parameters) + { + if (v2Task == null) throw new NotV1SupportedException(); + if (parameters == null || parameters.Any(s => s == null)) + throw new ArgumentNullException(nameof(parameters), "The array and none of the values passed as parameters may be `null`."); + if (parameters.Length > 32) + throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 parameters can be supplied to RunEx."); + if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260)) + throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, no individual parameter may be more than 260 characters."); + var irt = v2Task.RunEx(parameters.Length == 0 ? null : parameters, (int)flags, sessionID, user); + return irt != null ? new RunningTask(TaskService, v2Task, irt) : null; + } + + /// + /// 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 task. + /// /// /// Give read access to all authenticated users for a task. /// - /// + ///]]> /// /// - public void SetAccessControl([NotNull] TaskSecurity taskSecurity) - { - taskSecurity.Persist(this); - } + public void SetAccessControl([NotNull] TaskSecurity taskSecurity) => taskSecurity.Persist(this); /// Sets the security descriptor for the task. Not available to Task Scheduler 1.0. /// The security descriptor for the task. @@ -854,8 +1514,52 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Dynamically tries to load the assembly for the editor and displays it as editable for this task. + /// true if editor returns with OK response; false otherwise. + /// + /// The Microsoft.Win32.TaskSchedulerEditor.dll assembly must reside in the same directory as the Microsoft.Win32.TaskScheduler.dll + /// or in the GAC. + /// + public bool ShowEditor() + { + try + { + var t = ReflectionHelper.LoadType("Microsoft.Win32.TaskScheduler.TaskEditDialog", "Microsoft.Win32.TaskSchedulerEditor.dll"); + if (t != null) + return ReflectionHelper.InvokeMethod(t, new object[] { this, true, true }, "ShowDialog") == 1; + } + catch { } + return false; + } + + /// Shows the property page for the task (v1.0 only). + public void ShowPropertyPage() + { + if (v1Task != null) + v1Task.EditWorkItem(IntPtr.Zero, 0); + else + throw new NotV2SupportedException(); + } + + /// Stops the registered task immediately. + /// + /// The Stop method stops all instances of the task. + /// + /// System account users can stop a task, users with Administrator group privileges can stop a task, and if a user has rights to + /// execute and read a task, then the user can stop the task. A user can stop the task instances that are running under the same + /// credentials as the user account. In all other cases, the user is denied access to stop the task. + /// + /// + public void Stop() + { + if (v2Task != null) + v2Task.Stop(0); + else + v1Task.Terminate(); + } + + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() => Name; int IComparable.CompareTo(object other) => CompareTo(other as Task); @@ -878,7 +1582,10 @@ namespace winPEAS.TaskScheduler return fileName ?? string.Empty; } - /// Gets the ITaskDefinition for a V2 task and prevents the errors that come when connecting remotely to a higher version of the Task Scheduler. + /// + /// Gets the ITaskDefinition for a V2 task and prevents the errors that come when connecting remotely to a higher version of the + /// Task Scheduler. + /// /// The local task service. /// The task instance. /// if set to true this method will throw an exception if unable to get the task definition. @@ -1038,6 +1745,10 @@ namespace winPEAS.TaskScheduler v1Task = iTask; } + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + private class DefDoc { private readonly XmlDocument doc; @@ -1125,7 +1836,7 @@ namespace winPEAS.TaskScheduler [XmlRoot("Task", Namespace = tns, IsNullable = false)] [XmlSchemaProvider("GetV1SchemaFile")] [PublicAPI, Serializable] - public sealed class TaskDefinition : IDisposable, IXmlSerializable + public sealed class TaskDefinition : IDisposable, IXmlSerializable, INotifyPropertyChanged { internal const string tns = "http://schemas.microsoft.com/windows/2004/02/mit/task"; @@ -1145,10 +1856,10 @@ namespace winPEAS.TaskScheduler v1Name = name; } - internal TaskDefinition([NotNull] ITaskDefinition iDef) - { - v2Def = iDef; - } + internal TaskDefinition([NotNull] ITaskDefinition iDef) => v2Def = iDef; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; /// Gets a collection of actions that are performed by the task. [XmlArrayItem(ElementName = "Exec", IsNullable = true, Type = typeof(ExecAction))] @@ -1157,19 +1868,20 @@ namespace winPEAS.TaskScheduler [XmlArrayItem(ElementName = "SendEmail", IsNullable = true, Type = typeof(EmailAction))] [XmlArray] [NotNull, ItemNotNull] - public ActionCollection Actions => actions ?? (actions = v2Def != null ? new ActionCollection(v2Def) : new ActionCollection(v1Task)); + public ActionCollection Actions => actions ??= v2Def != null ? new ActionCollection(v2Def) : new ActionCollection(v1Task); /// - /// Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by third-parties who wish to - /// extend the task format. + /// Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by + /// third-parties who wish to extend the task format. /// /// - /// For V1 tasks, this library makes special use of the SetWorkItemData and GetWorkItemData methods and does not expose that data stream directly. - /// Instead, it uses that data stream to hold a dictionary of properties that are not supported under V1, but can have values under V2. An example of - /// this is the value which is stored in the data stream. + /// For V1 tasks, this library makes special use of the SetWorkItemData and GetWorkItemData methods and does not expose that data + /// stream directly. Instead, it uses that data stream to hold a dictionary of properties that are not supported under V1, but can + /// have values under V2. An example of this is the value which is stored in the data stream. /// - /// The library does not provide direct access to the V1 work item data. If using V2 properties with a V1 task, programmatic access to the task using the - /// native API will retrieve unreadable results from GetWorkItemData and will eliminate those property values if SetWorkItemData is used. + /// The library does not provide direct access to the V1 work item data. If using V2 properties with a V1 task, programmatic access + /// to the task using the native API will retrieve unreadable results from GetWorkItemData and will eliminate those property values + /// if SetWorkItemData is used. /// /// [CanBeNull] @@ -1182,9 +1894,14 @@ namespace winPEAS.TaskScheduler v2Def.Data = value; else v1Task.SetDataItem(nameof(Data), value); + OnNotifyPropertyChanged(); } } + /// Gets the lowest supported version that supports the settings for this . + [XmlIgnore] + public TaskCompatibility LowestSupportedVersion => GetLowestSupportedVersion(); + /// Gets a collection of triggers that are used to start a task. [XmlArrayItem(ElementName = "BootTrigger", IsNullable = true, Type = typeof(BootTrigger))] [XmlArrayItem(ElementName = "CalendarTrigger", IsNullable = true, Type = typeof(CalendarTrigger))] @@ -1193,7 +1910,7 @@ namespace winPEAS.TaskScheduler [XmlArrayItem(ElementName = "TimeTrigger", IsNullable = true, Type = typeof(TimeTrigger))] [XmlArray] [NotNull, ItemNotNull] - public TriggerCollection Triggers => triggers ?? (triggers = v2Def != null ? new TriggerCollection(v2Def) : new TriggerCollection(v1Task)); + public TriggerCollection Triggers => triggers ??= v2Def != null ? new TriggerCollection(v2Def) : new TriggerCollection(v1Task); /// Gets or sets the XML-formatted definition of the task. [XmlIgnore] @@ -1206,22 +1923,149 @@ namespace winPEAS.TaskScheduler v2Def.XmlText = value; else XmlSerializationHelper.ReadObjectFromXmlText(value, this); + OnNotifyPropertyChanged(); } } /// Gets the principal for the task that provides the security credentials for the task. [NotNull] - public TaskPrincipal Principal => principal ?? (principal = v2Def != null ? new TaskPrincipal(v2Def.Principal, () => XmlText) : new TaskPrincipal(v1Task)); + public TaskPrincipal Principal => principal ??= v2Def != null ? new TaskPrincipal(v2Def.Principal, () => XmlText) : new TaskPrincipal(v1Task); /// - /// Gets a class instance of registration information that is used to describe a task, such as the description of the task, the author of the task, and - /// the date the task is registered. + /// Gets a class instance of registration information that is used to describe a task, such as the description of the task, the + /// author of the task, and the date the task is registered. /// - public TaskRegistrationInfo RegistrationInfo => regInfo ?? (regInfo = v2Def != null ? new TaskRegistrationInfo(v2Def.RegistrationInfo) : new TaskRegistrationInfo(v1Task)); + public TaskRegistrationInfo RegistrationInfo => regInfo ??= v2Def != null ? new TaskRegistrationInfo(v2Def.RegistrationInfo) : new TaskRegistrationInfo(v1Task); /// Gets the settings that define how the Task Scheduler service performs the task. [NotNull] - public TaskSettings Settings => settings ?? (settings = v2Def != null ? new TaskSettings(v2Def.Settings) : new TaskSettings(v1Task)); + public TaskSettings Settings => settings ??= v2Def != null ? new TaskSettings(v2Def.Settings) : new TaskSettings(v1Task); + + /// Gets the XML Schema file for V1 tasks. + /// The for V1 tasks. + /// An object containing the XML Schema for V1 tasks. + public static XmlSchemaComplexType GetV1SchemaFile([NotNull] XmlSchemaSet xs) + { + XmlSchema schema; + using (var xsdFile = Assembly.GetAssembly(typeof(TaskDefinition)).GetManifestResourceStream("Microsoft.Win32.TaskScheduler.V1.TaskSchedulerV1Schema.xsd")) + { + var schemaSerializer = new XmlSerializer(typeof(XmlSchema)); + schema = (XmlSchema)schemaSerializer.Deserialize(XmlReader.Create(xsdFile)); + xs.Add(schema); + } + + // target namespace + var name = new XmlQualifiedName("taskType", tns); + var productType = (XmlSchemaComplexType)schema.SchemaTypes[name]; + + return productType; + } + + /// + /// Determines whether this can use the Unified Scheduling Engine or if it contains unsupported properties. + /// + /// + /// if set to true throws an with details about unsupported properties in the Data + /// property of the exception. + /// + /// + /// true if this can use the Unified Scheduling Engine; otherwise, false. + public bool CanUseUnifiedSchedulingEngine(bool throwExceptionWithDetails = false, Version taskSchedulerVersion = null) + { + var tsVer = taskSchedulerVersion ?? TaskService.LibraryVersion; + if (tsVer < TaskServiceVersion.V1_3) return false; + var ex = new InvalidOperationException { HelpLink = "http://msdn.microsoft.com/en-us/library/windows/desktop/aa384138(v=vs.85).aspx" }; + var bad = false; + /*if (Principal.LogonType == TaskLogonType.InteractiveTokenOrPassword) + { + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, "Principal.LogonType", "== TaskLogonType.InteractiveTokenOrPassword"); + } + if (Settings.MultipleInstances == TaskInstancesPolicy.StopExisting) + { + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, "Settings.MultipleInstances", "== TaskInstancesPolicy.StopExisting"); + }*/ + if (Settings.NetworkSettings.Id != Guid.Empty && tsVer >= TaskServiceVersion.V1_5) + { + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, "Settings.NetworkSettings.Id", "!= Guid.Empty"); + } + /*if (!Settings.AllowHardTerminate) + { + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, "Settings.AllowHardTerminate", "== false"); + }*/ + if (!Actions.PowerShellConversion.IsFlagSet(PowerShellActionPlatformOption.Version2)) + for (var i = 0; i < Actions.Count; i++) + { + var a = Actions[i]; + switch (a) + { + case EmailAction _: + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Actions[{i}]", "== typeof(EmailAction)"); + break; + + case ShowMessageAction _: + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Actions[{i}]", "== typeof(ShowMessageAction)"); + break; + } + } + if (tsVer == TaskServiceVersion.V1_3) + for (var i = 0; i < Triggers.Count; i++) + { + Trigger t; + try { t = Triggers[i]; } + catch + { + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable."); + continue; + } + switch (t) + { + case MonthlyTrigger _: + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyTrigger)"); + break; + + case MonthlyDOWTrigger _: + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyDOWTrigger)"); + break; + /*case ICalendarTrigger _ when t.Repetition.IsSet(): + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}].Repetition", ""); + break; + + case EventTrigger _ when ((EventTrigger)t).ValueQueries.Count > 0: + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}].ValueQueries.Count", "!= 0"); + break;*/ + } + if (t.ExecutionTimeLimit != TimeSpan.Zero) + { + bad = true; + if (!throwExceptionWithDetails) return false; + TryAdd(ex.Data, $"Triggers[{i}].ExecutionTimeLimit", "!= TimeSpan.Zero"); + } + } + if (bad && throwExceptionWithDetails) + throw ex; + return !bad; + } /// Releases all resources used by this class. public void Dispose() @@ -1235,6 +2079,66 @@ namespace winPEAS.TaskScheduler v1Task = null; } + /// Validates the current . + /// + /// if set to true throw a with details about invalid properties. + /// + /// true if current is valid; false if not. + public bool Validate(bool throwException = false) + { + var ex = new InvalidOperationException(); + if (Settings.UseUnifiedSchedulingEngine) + { + try { CanUseUnifiedSchedulingEngine(throwException); } + catch (InvalidOperationException iox) + { + foreach (DictionaryEntry kvp in iox.Data) + TryAdd(ex.Data, (kvp.Key as ICloneable)?.Clone() ?? kvp.Key, (kvp.Value as ICloneable)?.Clone() ?? kvp.Value); + } + } + + if (Settings.Compatibility >= TaskCompatibility.V2_2) + { + var PT1D = TimeSpan.FromDays(1); + if (Settings.MaintenanceSettings.IsSet() && (Settings.MaintenanceSettings.Period < PT1D || Settings.MaintenanceSettings.Deadline < PT1D || Settings.MaintenanceSettings.Deadline <= Settings.MaintenanceSettings.Period)) + TryAdd(ex.Data, "Settings.MaintenanceSettings", "Period or Deadline must be at least 1 day and Deadline must be greater than Period."); + } + + var list = new List(); + if (GetLowestSupportedVersion(list) > Settings.Compatibility) + foreach (var item in list) + TryAdd(ex.Data, item.Property, item.Reason); + + var startWhenAvailable = Settings.StartWhenAvailable; + var delOldTask = Settings.DeleteExpiredTaskAfter != TimeSpan.Zero; + var v1 = Settings.Compatibility < TaskCompatibility.V2; + var hasEndBound = false; + for (var i = 0; i < Triggers.Count; i++) + { + Trigger trigger; + try { trigger = Triggers[i]; } + catch + { + TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable."); + continue; + } + if (startWhenAvailable && trigger.Repetition.Duration != TimeSpan.Zero && trigger.EndBoundary == DateTime.MaxValue) + TryAdd(ex.Data, "Settings.StartWhenAvailable", "== true requires time-based tasks with an end boundary or time-based tasks that are set to repeat infinitely."); + if (v1 && trigger.Repetition.Interval != TimeSpan.Zero && trigger.Repetition.Interval >= trigger.Repetition.Duration) + TryAdd(ex.Data, "Trigger.Repetition.Interval", ">= Trigger.Repetition.Duration under Task Scheduler 1.0."); + if (trigger.EndBoundary <= trigger.StartBoundary) + TryAdd(ex.Data, "Trigger.StartBoundary", ">= Trigger.EndBoundary is not allowed."); + if (delOldTask && trigger.EndBoundary != DateTime.MaxValue) + hasEndBound = true; + } + if (delOldTask && !hasEndBound) + TryAdd(ex.Data, "Settings.DeleteExpiredTaskAfter", "!= TimeSpan.Zero requires at least one trigger with an end boundary."); + + if (throwException && ex.Data.Count > 0) + throw ex; + return ex.Data.Count == 0; + } + XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(XmlReader reader) @@ -1244,11 +2148,9 @@ namespace winPEAS.TaskScheduler reader.ReadEndElement(); } - void IXmlSerializable.WriteXml(XmlWriter writer) - { + void IXmlSerializable.WriteXml(XmlWriter writer) => // TODO:FIX writer.WriteAttributeString("version", "1.1"); XmlSerializationHelper.WriteObjectProperties(writer, this); - } internal static Dictionary GetV1TaskDataDictionary(ITask v1Task) { @@ -1322,24 +2224,122 @@ namespace winPEAS.TaskScheduler } return null; } - } + + private static void TryAdd(IDictionary d, object k, object v) + { + if (!d.Contains(k)) + d.Add(k, v); + } + + /// Gets the lowest supported version. + /// The output list. + /// + private TaskCompatibility GetLowestSupportedVersion(IList outputList = null) + { + var res = TaskCompatibility.V1; + var list = new List(); + + //if (Principal.DisplayName != null) + // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.DisplayName", "cannot have a value.")); } + if (Principal.GroupId != null) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.GroupId", "cannot have a value.")); } + //this.Principal.Id != null || + if (Principal.LogonType == TaskLogonType.Group || Principal.LogonType == TaskLogonType.None || Principal.LogonType == TaskLogonType.S4U) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.LogonType", "cannot be Group, None or S4U.")); } + if (Principal.RunLevel == TaskRunLevel.Highest) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.RunLevel", "cannot be set to Highest.")); } + if (RegistrationInfo.SecurityDescriptorSddlForm != null) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.SecurityDescriptorSddlForm", "cannot have a value.")); } + //if (RegistrationInfo.Source != null) + // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Source", "cannot have a value.")); } + //if (RegistrationInfo.URI != null) + // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.URI", "cannot have a value.")); } + //if (RegistrationInfo.Version != new Version(1, 0)) + // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Version", "cannot be set or equal 1.0.")); } + if (Settings.AllowDemandStart == false) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowDemandStart", "must be true.")); } + if (Settings.AllowHardTerminate == false) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowHardTerminate", "must be true.")); } + if (Settings.MultipleInstances != TaskInstancesPolicy.IgnoreNew) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.MultipleInstances", "must be set to IgnoreNew.")); } + if (Settings.NetworkSettings.IsSet()) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.NetworkSetting", "cannot have a value.")); } + if (Settings.RestartCount != 0) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartCount", "must be 0.")); } + if (Settings.RestartInterval != TimeSpan.Zero) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartInterval", "must be 0 (TimeSpan.Zero).")); } + if (Settings.StartWhenAvailable) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.StartWhenAvailable", "must be false.")); } + + if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version1) != PowerShellActionPlatformOption.Version1 && (Actions.ContainsType(typeof(EmailAction)) || Actions.ContainsType(typeof(ShowMessageAction)) || Actions.ContainsType(typeof(ComHandlerAction)))) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Actions", "may only contain ExecAction types unless Actions.PowerShellConversion includes Version1.")); } + if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version2) != PowerShellActionPlatformOption.Version2 && (Actions.ContainsType(typeof(EmailAction)) || Actions.ContainsType(typeof(ShowMessageAction)))) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Actions", "may only contain ExecAction and ComHanlderAction types unless Actions.PowerShellConversion includes Version2.")); } + + try + { + if (null != Triggers.Find(t => t is ITriggerDelay && ((ITriggerDelay)t).Delay != TimeSpan.Zero)) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain delays.")); } + if (null != Triggers.Find(t => t.ExecutionTimeLimit != TimeSpan.Zero || t.Id != null)) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain an ExecutionTimeLimit or Id.")); } + if (null != Triggers.Find(t => (t as LogonTrigger)?.UserId != null)) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a LogonTrigger with a UserId.")); } + if (null != Triggers.Find(t => t is MonthlyDOWTrigger && ((MonthlyDOWTrigger)t).RunOnLastWeekOfMonth || t is MonthlyDOWTrigger && (((MonthlyDOWTrigger)t).WeeksOfMonth & (((MonthlyDOWTrigger)t).WeeksOfMonth - 1)) != 0)) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyDOWTrigger with RunOnLastWeekOfMonth set or multiple WeeksOfMonth.")); } + if (null != Triggers.Find(t => t is MonthlyTrigger && ((MonthlyTrigger)t).RunOnLastDayOfMonth)) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyTrigger with RunOnLastDayOfMonth set.")); } + if (Triggers.ContainsType(typeof(EventTrigger)) || Triggers.ContainsType(typeof(SessionStateChangeTrigger)) || Triggers.ContainsType(typeof(RegistrationTrigger))) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger types.")); } + } + catch + { + list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain Custom triggers.")); + } + + if (Principal.ProcessTokenSidType != TaskProcessTokenSidType.Default) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.ProcessTokenSidType", "must be Default.")); } + if (Principal.RequiredPrivileges.Count > 0) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.RequiredPrivileges", "must be empty.")); } + if (Settings.DisallowStartOnRemoteAppSession) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.DisallowStartOnRemoteAppSession", "must be false.")); } + if (Settings.UseUnifiedSchedulingEngine) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.UseUnifiedSchedulingEngine", "must be false.")); } + + if (Settings.MaintenanceSettings.IsSet()) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, "this.Settings.MaintenanceSettings", "must have no values set.")); } + if (Settings.Volatile) + { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, " this.Settings.Volatile", "must be false.")); } + + foreach (var item in list) + { + if (res < item.CompatibilityLevel) res = item.CompatibilityLevel; + outputList?.Add(item); + } + return res; + } + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } /// - /// Provides the security credentials for a principal. These security credentials define the security context for the tasks that are associated with the principal. + /// Provides the security credentials for a principal. These security credentials define the security context for the tasks that are + /// associated with the principal. /// [XmlRoot("Principals", Namespace = TaskDefinition.tns, IsNullable = true)] [PublicAPI] - public sealed class TaskPrincipal : IDisposable, IXmlSerializable + public sealed class TaskPrincipal : IDisposable, IXmlSerializable, INotifyPropertyChanged { private const string localSystemAcct = "SYSTEM"; + private readonly V2Interop.IPrincipal v2Principal; + private readonly IPrincipal2 v2Principal2; + private readonly Func xmlFunc; private TaskPrincipalPrivileges reqPriv; private string v1CachedAcctInfo; private ITask v1Task; - private readonly IPrincipal v2Principal; - private readonly IPrincipal2 v2Principal2; - private readonly Func xmlFunc; - internal TaskPrincipal([NotNull] IPrincipal iPrincipal, Func defXml) + internal TaskPrincipal([NotNull] V2Interop.IPrincipal iPrincipal, Func defXml) { xmlFunc = defXml; v2Principal = iPrincipal; @@ -1347,12 +2347,14 @@ namespace winPEAS.TaskScheduler catch { } } - internal TaskPrincipal([NotNull] ITask iTask) - { - v1Task = iTask; - } + internal TaskPrincipal([NotNull] ITask iTask) => v1Task = iTask; - /// Gets the account associated with this principal. This value is pulled from the TaskDefinition's XMLText property if set. + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets the account associated with this principal. This value is pulled from the TaskDefinition's XMLText property if set. + /// /// The account. [DefaultValue(null), Browsable(false)] public string Account @@ -1366,7 +2368,7 @@ namespace winPEAS.TaskScheduler { var doc = new XmlDocument(); doc.LoadXml(xml); - var pn = doc.DocumentElement?["Principals"]?["Principal"]; + var pn = doc.DocumentElement?["Principals"]?["Principal"]; if (pn != null) { var un = pn["UserId"] ?? pn["GroupId"]; @@ -1388,9 +2390,26 @@ namespace winPEAS.TaskScheduler } } + /// Gets or sets the name of the principal that is displayed in the Task Scheduler UI. + /// Not supported under Task Scheduler 1.0. + [DefaultValue(null)] + public string DisplayName + { + get => v2Principal != null ? v2Principal.DisplayName : v1Task.GetDataItem("PrincipalDisplayName"); + set + { + if (v2Principal != null) + v2Principal.DisplayName = value; + else + v1Task.SetDataItem("PrincipalDisplayName", value); + OnNotifyPropertyChanged(); + } + } + /// - /// Gets or sets the identifier of the user group that is required to run the tasks that are associated with the principal. Setting this property to - /// something other than a null or empty string, will set the property to NULL and will set the property to TaskLogonType.Group; + /// Gets or sets the identifier of the user group that is required to run the tasks that are associated with the principal. Setting + /// this property to something other than a null or empty string, will set the property to NULL and will set + /// the property to TaskLogonType.Group; /// /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] @@ -1413,6 +2432,7 @@ namespace winPEAS.TaskScheduler } else throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); } } @@ -1429,11 +2449,14 @@ namespace winPEAS.TaskScheduler v2Principal.Id = value; else v1Task.SetDataItem("PrincipalId", value); + OnNotifyPropertyChanged(); } } /// Gets or sets the security logon method that is required to run the tasks that are associated with the principal. - /// TaskLogonType values of Group, None, or S4UNot are not supported under Task Scheduler 1.0. + /// + /// TaskLogonType values of Group, None, or S4UNot are not supported under Task Scheduler 1.0. + /// [DefaultValue(typeof(TaskLogonType), "None")] public TaskLogonType LogonType { @@ -1457,13 +2480,59 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); v1Task.SetFlags(TaskFlags.RunOnlyIfLoggedOn, value == TaskLogonType.InteractiveToken); } + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets the task process security identifier (SID) type. + /// One of the enumeration constants. + /// Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems. + /// Not supported under Task Scheduler versions prior to 2.1. + [XmlIgnore, DefaultValue(typeof(TaskProcessTokenSidType), "Default")] + public TaskProcessTokenSidType ProcessTokenSidType + { + get => v2Principal2?.ProcessTokenSidType ?? TaskProcessTokenSidType.Default; + set + { + if (v2Principal2 != null) + v2Principal2.ProcessTokenSidType = value; + else + throw new NotSupportedPriorToException(TaskCompatibility.V2_1); + OnNotifyPropertyChanged(); } } - /// - /// Gets or sets the user identifier that is required to run the tasks that are associated with the principal. Setting this property to something other - /// than a null or empty string, will set the property to NULL; + /// Gets the security credentials for a principal. These security credentials define the security context for the tasks that are + /// associated with the principal. + /// + /// Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems. + [XmlIgnore] + public TaskPrincipalPrivileges RequiredPrivileges => reqPriv ??= new TaskPrincipalPrivileges(v2Principal2); + + /// + /// Gets or sets the identifier that is used to specify the privilege level that is required to run the tasks that are associated + /// with the principal. + /// + /// Not supported under Task Scheduler 1.0. + [DefaultValue(typeof(TaskRunLevel), "LUA")] + [XmlIgnore] + public TaskRunLevel RunLevel + { + get => v2Principal?.RunLevel ?? TaskRunLevel.LUA; + set + { + if (v2Principal != null) + v2Principal.RunLevel = value; + else if (value != TaskRunLevel.LUA) + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets the user identifier that is required to run the tasks that are associated with the principal. Setting this property + /// to something other than a null or empty string, will set the property to NULL; /// [DefaultValue(null)] public string UserId @@ -1504,9 +2573,20 @@ namespace winPEAS.TaskScheduler v1Task.SetAccountInformation(value, IntPtr.Zero); v1CachedAcctInfo = null; } + OnNotifyPropertyChanged(); } } + /// Validates the supplied account against the supplied . + /// The user or group account name. + /// The SID type for the process. + /// true if supplied account can be used for the supplied SID type. + public static bool ValidateAccountForSidType(string acct, TaskProcessTokenSidType sidType) + { + string[] validUserIds = { "NETWORK SERVICE", "LOCAL SERVICE", "S-1-5-19", "S-1-5-20" }; + return sidType == TaskProcessTokenSidType.Default || Array.Find(validUserIds, id => id.Equals(acct, StringComparison.InvariantCultureIgnoreCase)) != null; + } + /// Releases all resources used by this class. public void Dispose() { @@ -1515,8 +2595,13 @@ namespace winPEAS.TaskScheduler v1Task = null; } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Gets a value indicating whether current Principal settings require a password to be provided. + /// true if settings requires a password to be provided; otherwise, false. + public bool RequiresPassword() => LogonType == TaskLogonType.InteractiveTokenOrPassword || + LogonType == TaskLogonType.Password || LogonType == TaskLogonType.S4U && UserId != null && string.Compare(UserId, WindowsIdentity.GetCurrent().Name, StringComparison.OrdinalIgnoreCase) != 0; + + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() => LogonType == TaskLogonType.Group ? GroupId : UserId; XmlSchema IXmlSerializable.GetSchema() => null; @@ -1554,16 +2639,22 @@ namespace winPEAS.TaskScheduler XmlSerializationHelper.WriteObjectProperties(writer, this); writer.WriteEndElement(); } + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// - /// List of security credentials for a principal under version 1.3 of the Task Scheduler. These security credentials define the security context for the - /// tasks that are associated with the principal. + /// List of security credentials for a principal under version 1.3 of the Task Scheduler. These security credentials define the security + /// context for the tasks that are associated with the principal. /// [PublicAPI] public sealed class TaskPrincipalPrivileges : IList { - private IPrincipal2 v2Principal2; + private readonly IPrincipal2 v2Principal2; + + internal TaskPrincipalPrivileges(IPrincipal2 iPrincipal2 = null) => v2Principal2 = iPrincipal2; /// Gets the number of elements contained in the . /// The number of elements contained in the . @@ -1576,7 +2667,9 @@ namespace winPEAS.TaskScheduler /// Gets or sets the element at the specified index. /// The element at the specified index. /// is not a valid index in the . - /// The property is set and the is read-only. + /// + /// The property is set and the is read-only. + /// public TaskPrincipalPrivilege this[int index] { get @@ -1601,7 +2694,9 @@ namespace winPEAS.TaskScheduler /// Determines whether the contains a specific value. /// The object to locate in the . - /// true if is found in the ; otherwise, false. + /// + /// true if is found in the ; otherwise, false. + /// public bool Contains(TaskPrincipalPrivilege item) => IndexOf(item) != -1; /// Copies to. @@ -1637,10 +2732,7 @@ namespace winPEAS.TaskScheduler /// Removes all items from the . /// The is read-only. - void ICollection.Clear() - { - throw new NotImplementedException(); - } + void ICollection.Clear() => throw new NotImplementedException(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); @@ -1649,31 +2741,22 @@ namespace winPEAS.TaskScheduler /// The object to insert into the . /// is not a valid index in the . /// The is read-only. - void IList.Insert(int index, TaskPrincipalPrivilege item) - { - throw new NotImplementedException(); - } + void IList.Insert(int index, TaskPrincipalPrivilege item) => throw new NotImplementedException(); /// Removes the first occurrence of a specific object from the . /// The object to remove from the . /// - /// true if was successfully removed from the ; otherwise, false. This - /// method also returns false if is not found in the original . + /// true if was successfully removed from the ; + /// otherwise, false. This method also returns false if is not found in the original . /// /// The is read-only. - bool ICollection.Remove(TaskPrincipalPrivilege item) - { - throw new NotImplementedException(); - } + bool ICollection.Remove(TaskPrincipalPrivilege item) => throw new NotImplementedException(); /// Removes the item at the specified index. /// The zero-based index of the item to remove. /// is not a valid index in the . /// The is read-only. - void IList.RemoveAt(int index) - { - throw new NotImplementedException(); - } + void IList.RemoveAt(int index) => throw new NotImplementedException(); /// Enumerates the privileges set for a principal under version 1.3 of the Task Scheduler. public sealed class TaskPrincipalPrivilegesEnumerator : IEnumerator @@ -1697,7 +2780,9 @@ namespace winPEAS.TaskScheduler public void Dispose() { } /// Advances the enumerator to the next element of the collection. - /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// /// The collection was modified after the enumerator was created. public bool MoveNext() { @@ -1722,25 +2807,22 @@ namespace winPEAS.TaskScheduler } /// - /// Provides the administrative information that can be used to describe the task. This information includes details such as a description of the task, the - /// author of the task, the date the task is registered, and the security descriptor of the task. + /// Provides the administrative information that can be used to describe the task. This information includes details such as a + /// description of the task, the author of the task, the date the task is registered, and the security descriptor of the task. /// [XmlRoot("RegistrationInfo", Namespace = TaskDefinition.tns, IsNullable = true)] [PublicAPI] - public sealed class TaskRegistrationInfo : IDisposable, IXmlSerializable + public sealed class TaskRegistrationInfo : IDisposable, IXmlSerializable, INotifyPropertyChanged { + private readonly IRegistrationInfo v2RegInfo; private ITask v1Task; - private IRegistrationInfo v2RegInfo; - internal TaskRegistrationInfo([NotNull] IRegistrationInfo iRegInfo) - { - v2RegInfo = iRegInfo; - } + internal TaskRegistrationInfo([NotNull] IRegistrationInfo iRegInfo) => v2RegInfo = iRegInfo; - internal TaskRegistrationInfo([NotNull] ITask iTask) - { - v1Task = iTask; - } + internal TaskRegistrationInfo([NotNull] ITask iTask) => v1Task = iTask; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; /// Gets or sets the author of the task. [DefaultValue(null)] @@ -1753,6 +2835,7 @@ namespace winPEAS.TaskScheduler v2RegInfo.Author = value; else v1Task.SetCreator(value); + OnNotifyPropertyChanged(); } } @@ -1785,6 +2868,7 @@ namespace winPEAS.TaskScheduler if (!string.IsNullOrEmpty(v1Path) && File.Exists(v1Path)) File.SetLastWriteTime(v1Path, value); } + OnNotifyPropertyChanged(); } } @@ -1799,6 +2883,7 @@ namespace winPEAS.TaskScheduler v2RegInfo.Description = value; else v1Task.SetComment(value); + OnNotifyPropertyChanged(); } } @@ -1813,10 +2898,45 @@ namespace winPEAS.TaskScheduler v2RegInfo.Documentation = value; else v1Task.SetDataItem(nameof(Documentation), value); + OnNotifyPropertyChanged(); } } - /// Gets or sets where the task originated from. For example, a task may originate from a component, service, application, or user. + /// Gets or sets the security descriptor of the task. + /// The security descriptor. + [XmlIgnore] + public GenericSecurityDescriptor SecurityDescriptor + { + get => new RawSecurityDescriptor(SecurityDescriptorSddlForm); + set => SecurityDescriptorSddlForm = value?.GetSddlForm(Task.defaultAccessControlSections); + } + + /// Gets or sets the security descriptor of the task. + /// Not supported under Task Scheduler 1.0. + [DefaultValue(null)] + [XmlIgnore] + public string SecurityDescriptorSddlForm + { + get + { + object sddl = null; + if (v2RegInfo != null) + sddl = v2RegInfo.SecurityDescriptor; + return sddl?.ToString(); + } + set + { + if (v2RegInfo != null) + v2RegInfo.SecurityDescriptor = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets where the task originated from. For example, a task may originate from a component, service, application, or user. + /// [DefaultValue(null)] public string Source { @@ -1827,13 +2947,14 @@ namespace winPEAS.TaskScheduler v2RegInfo.Source = value; else v1Task.SetDataItem(nameof(Source), value); + OnNotifyPropertyChanged(); } } /// Gets or sets the URI of the task. /// - /// Note: Breaking change in version 2.0. This property was previously of type . It was found that in Windows 8, many of the - /// native tasks use this property in a string format rather than in a URI format. + /// Note: Breaking change in version 2.0. This property was previously of type . It was found that in + /// Windows 8, many of the native tasks use this property in a string format rather than in a URI format. /// [DefaultValue(null)] public string URI @@ -1849,6 +2970,7 @@ namespace winPEAS.TaskScheduler v2RegInfo.URI = value; else v1Task.SetDataItem(nameof(URI), value); + OnNotifyPropertyChanged(); } } @@ -1868,6 +2990,22 @@ namespace winPEAS.TaskScheduler v2RegInfo.Version = value?.ToString(); else v1Task.SetDataItem(nameof(Version), value.ToString()); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets an XML-formatted version of the registration information for the task. + [XmlIgnore] + public string XmlText + { + get => v2RegInfo != null ? v2RegInfo.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this); + set + { + if (v2RegInfo != null) + v2RegInfo.XmlText = value; + else + XmlSerializationHelper.ReadObjectFromXmlText(value, this); + OnNotifyPropertyChanged(); } } @@ -1879,8 +3017,8 @@ namespace winPEAS.TaskScheduler Marshal.ReleaseComObject(v2RegInfo); } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() { if (v2RegInfo != null || v1Task != null) @@ -1902,13 +3040,14 @@ namespace winPEAS.TaskScheduler reader.Skip(); } - void IXmlSerializable.WriteXml(XmlWriter writer) - { - XmlSerializationHelper.WriteObjectProperties(writer, this, ProcessVersionXml); - } + void IXmlSerializable.WriteXml(XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this, ProcessVersionXml); internal static string FixCrLf(string text) => text == null ? null : Regex.Replace(text, "(?Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + private bool ProcessVersionXml(PropertyInfo pi, object obj, ref object value) { if (pi.Name != "Version" || value == null) return false; @@ -1923,17 +3062,17 @@ namespace winPEAS.TaskScheduler /// Provides the settings that the Task Scheduler service uses to perform the task. [XmlRoot("Settings", Namespace = TaskDefinition.tns, IsNullable = true)] [PublicAPI] - public sealed class TaskSettings : IDisposable, IXmlSerializable + public sealed class TaskSettings : IDisposable, IXmlSerializable, INotifyPropertyChanged { private const uint InfiniteRunTimeV1 = 0xFFFFFFFF; + private readonly ITaskSettings v2Settings; + private readonly ITaskSettings2 v2Settings2; + private readonly ITaskSettings3 v2Settings3; private IdleSettings idleSettings; private MaintenanceSettings maintenanceSettings; private NetworkSettings networkSettings; private ITask v1Task; - private ITaskSettings v2Settings; - private ITaskSettings2 v2Settings2; - private ITaskSettings3 v2Settings3; internal TaskSettings([NotNull] ITaskSettings iSettings) { @@ -1944,13 +3083,145 @@ namespace winPEAS.TaskScheduler catch { } } - internal TaskSettings([NotNull] ITask iTask) + internal TaskSettings([NotNull] ITask iTask) => v1Task = iTask; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// + /// Gets or sets a Boolean value that indicates that the task can be started by using either the Run command or the Context menu. + /// + /// Not supported under Task Scheduler 1.0. + [DefaultValue(true)] + [XmlElement("AllowStartOnDemand")] + [XmlIgnore] + public bool AllowDemandStart { - v1Task = iTask; + get => v2Settings == null || v2Settings.AllowDemandStart; + set + { + if (v2Settings != null) + v2Settings.AllowDemandStart = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } } + /// Gets or sets a Boolean value that indicates that the task may be terminated by using TerminateProcess. + /// Not supported under Task Scheduler 1.0. + [DefaultValue(true)] + [XmlIgnore] + public bool AllowHardTerminate + { + get => v2Settings == null || v2Settings.AllowHardTerminate; + set + { + if (v2Settings != null) + v2Settings.AllowHardTerminate = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } - /// Gets or sets a Boolean value that indicates that the task is enabled. The task can be performed only when this setting is TRUE. + /// Gets or sets an integer value that indicates which version of Task Scheduler a task is compatible with. + /// Not supported under Task Scheduler 1.0. + [XmlIgnore] + public TaskCompatibility Compatibility + { + get => v2Settings?.Compatibility ?? TaskCompatibility.V1; + set + { + if (v2Settings != null) + v2Settings.Compatibility = value; + else + if (value != TaskCompatibility.V1) + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. If no value is + /// specified for this property, then the Task Scheduler service will not delete the task. + /// + /// + /// Gets and sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. A TimeSpan value + /// of 1 second indicates the task is set to delete when done. A value of TimeSpan.Zero indicates that the task should not be deleted. + /// + /// + /// A task expires after the end boundary has been exceeded for all triggers associated with the task. The end boundary for a + /// trigger is specified by the EndBoundary property of all trigger types. + /// + [DefaultValue(typeof(TimeSpan), "12:00:00")] + public TimeSpan DeleteExpiredTaskAfter + { + get + { + if (v2Settings != null) + return v2Settings.DeleteExpiredTaskAfter == "PT0S" ? TimeSpan.FromSeconds(1) : Task.StringToTimeSpan(v2Settings.DeleteExpiredTaskAfter); + return v1Task.HasFlags(TaskFlags.DeleteWhenDone) ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; + } + set + { + if (v2Settings != null) + v2Settings.DeleteExpiredTaskAfter = value == TimeSpan.FromSeconds(1) ? "PT0S" : Task.TimeSpanToString(value); + else + v1Task.SetFlags(TaskFlags.DeleteWhenDone, value >= TimeSpan.FromSeconds(1)); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the task will not be started if the computer is running on battery power. + /// + [DefaultValue(true)] + public bool DisallowStartIfOnBatteries + { + get => v2Settings?.DisallowStartIfOnBatteries ?? v1Task.HasFlags(TaskFlags.DontStartIfOnBatteries); + set + { + if (v2Settings != null) + v2Settings.DisallowStartIfOnBatteries = value; + else + v1Task.SetFlags(TaskFlags.DontStartIfOnBatteries, value); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the task will not be started if the task is triggered to run in a Remote + /// Applications Integrated Locally (RAIL) session. + /// + /// Property set for a task on a Task Scheduler version prior to 2.1. + [DefaultValue(false)] + [XmlIgnore] + public bool DisallowStartOnRemoteAppSession + { + get + { + if (v2Settings2 != null) + return v2Settings2.DisallowStartOnRemoteAppSession; + if (v2Settings3 != null) + return v2Settings3.DisallowStartOnRemoteAppSession; + return false; + } + set + { + if (v2Settings2 != null) + v2Settings2.DisallowStartOnRemoteAppSession = value; + else if (v2Settings3 != null) + v2Settings3.DisallowStartOnRemoteAppSession = value; + else + throw new NotSupportedPriorToException(TaskCompatibility.V2_1); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the task is enabled. The task can be performed only when this setting is TRUE. + /// [DefaultValue(true)] public bool Enabled { @@ -1961,14 +3232,114 @@ namespace winPEAS.TaskScheduler v2Settings.Enabled = value; else v1Task.SetFlags(TaskFlags.Disabled, !value); + OnNotifyPropertyChanged(); } } + /// + /// Gets or sets the amount of time that is allowed to complete the task. By default, a task will be stopped 72 hours after it + /// starts to run. + /// + /// + /// The amount of time that is allowed to complete the task. When this parameter is set to , the + /// execution time limit is infinite. + /// + /// + /// If a task is started on demand, the ExecutionTimeLimit setting is bypassed. Therefore, a task that is started on demand will not + /// be terminated if it exceeds the ExecutionTimeLimit. + /// + [DefaultValue(typeof(TimeSpan), "3")] + public TimeSpan ExecutionTimeLimit + { + get + { + if (v2Settings != null) + return Task.StringToTimeSpan(v2Settings.ExecutionTimeLimit); + var ms = v1Task.GetMaxRunTime(); + return ms == InfiniteRunTimeV1 ? TimeSpan.Zero : TimeSpan.FromMilliseconds(ms); + } + set + { + if (v2Settings != null) + v2Settings.ExecutionTimeLimit = value == TimeSpan.Zero ? "PT0S" : Task.TimeSpanToString(value); + else + { + // Due to an issue introduced in Vista, and propagated to Windows 7, setting the MaxRunTime to INFINITE results in the + // task only running for 72 hours. For these operating systems, setting the RunTime to "INFINITE - 1" gets the desired + // behavior of allowing an "infinite" run of the task. + var ms = value == TimeSpan.Zero ? InfiniteRunTimeV1 : Convert.ToUInt32(value.TotalMilliseconds); + v1Task.SetMaxRunTime(ms); + if (value == TimeSpan.Zero && v1Task.GetMaxRunTime() != InfiniteRunTimeV1) + v1Task.SetMaxRunTime(InfiniteRunTimeV1 - 1); + } + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets a Boolean value that indicates that the task will not be visible in the UI by default. + [DefaultValue(false)] + public bool Hidden + { + get => v2Settings?.Hidden ?? v1Task.HasFlags(TaskFlags.Hidden); + set + { + if (v2Settings != null) + v2Settings.Hidden = value; + else + v1Task.SetFlags(TaskFlags.Hidden, value); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets the information that the Task Scheduler uses during Automatic maintenance. + [XmlIgnore] + [NotNull] + public MaintenanceSettings MaintenanceSettings => maintenanceSettings ??= new MaintenanceSettings(v2Settings3); + + /// Gets or sets the policy that defines how the Task Scheduler handles multiple instances of the task. + /// Not supported under Task Scheduler 1.0. + [DefaultValue(typeof(TaskInstancesPolicy), "IgnoreNew")] + [XmlIgnore] + public TaskInstancesPolicy MultipleInstances + { + get => v2Settings?.MultipleInstances ?? TaskInstancesPolicy.IgnoreNew; + set + { + if (v2Settings != null) + v2Settings.MultipleInstances = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets the priority level of the task. + /// The priority. + /// Value set to AboveNormal or BelowNormal on Task Scheduler 1.0. + [DefaultValue(typeof(ProcessPriorityClass), "Normal")] + public ProcessPriorityClass Priority + { + get => v2Settings != null ? GetPriorityFromInt(v2Settings.Priority) : (ProcessPriorityClass)v1Task.GetPriority(); + set + { + if (v2Settings != null) + { + v2Settings.Priority = GetPriorityAsInt(value); + } + else + { + if (value == ProcessPriorityClass.AboveNormal || value == ProcessPriorityClass.BelowNormal) + throw new NotV1SupportedException("Unsupported priority level on Task Scheduler 1.0."); + v1Task.SetPriority((uint)value); + } + OnNotifyPropertyChanged(); + } + } /// Gets or sets the number of times that the Task Scheduler will attempt to restart the task. /// - /// The number of times that the Task Scheduler will attempt to restart the task. If this property is set, the property - /// must also be set. + /// The number of times that the Task Scheduler will attempt to restart the task. If this property is set, the property must also be set. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(0)] @@ -1982,13 +3353,14 @@ namespace winPEAS.TaskScheduler v2Settings.RestartCount = value; else throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); } } /// Gets or sets a value that specifies how long the Task Scheduler will attempt to restart the task. /// - /// A value that specifies how long the Task Scheduler will attempt to restart the task. If this property is set, the property - /// must also be set. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// A value that specifies how long the Task Scheduler will attempt to restart the task. If this property is set, the property must also be set. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -2002,9 +3374,184 @@ namespace winPEAS.TaskScheduler v2Settings.RestartInterval = Task.TimeSpanToString(value); else throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); } } + /// + /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the computer is in an idle condition. + /// + [DefaultValue(false)] + public bool RunOnlyIfIdle + { + get => v2Settings?.RunOnlyIfIdle ?? v1Task.HasFlags(TaskFlags.StartOnlyIfIdle); + set + { + if (v2Settings != null) + v2Settings.RunOnlyIfIdle = value; + else + v1Task.SetFlags(TaskFlags.StartOnlyIfIdle, value); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the user is logged on (v1.0 only) + /// + /// Property set for a task on a Task Scheduler version other than 1.0. + [XmlIgnore] + public bool RunOnlyIfLoggedOn + { + get => v2Settings != null || v1Task.HasFlags(TaskFlags.RunOnlyIfLoggedOn); + set + { + if (v1Task != null) + v1Task.SetFlags(TaskFlags.RunOnlyIfLoggedOn, value); + else if (v2Settings != null) + throw new NotV2SupportedException("Task Scheduler 2.0 (1.2) does not support setting this property. You must use an InteractiveToken in order to have the task run in the current user session."); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only when a network is available. + [DefaultValue(false)] + public bool RunOnlyIfNetworkAvailable + { + get => v2Settings?.RunOnlyIfNetworkAvailable ?? v1Task.HasFlags(TaskFlags.RunIfConnectedToInternet); + set + { + if (v2Settings != null) + v2Settings.RunOnlyIfNetworkAvailable = value; + else + v1Task.SetFlags(TaskFlags.RunIfConnectedToInternet, value); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the Task Scheduler can start the task at any time after its scheduled time has passed. + /// + /// Not supported under Task Scheduler 1.0. + [DefaultValue(false)] + [XmlIgnore] + public bool StartWhenAvailable + { + get => v2Settings != null && v2Settings.StartWhenAvailable; + set + { + if (v2Settings != null) + v2Settings.StartWhenAvailable = value; + else + throw new NotV1SupportedException(); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets a Boolean value that indicates that the task will be stopped if the computer switches to battery power. + [DefaultValue(true)] + public bool StopIfGoingOnBatteries + { + get => v2Settings?.StopIfGoingOnBatteries ?? v1Task.HasFlags(TaskFlags.KillIfGoingOnBatteries); + set + { + if (v2Settings != null) + v2Settings.StopIfGoingOnBatteries = value; + else + v1Task.SetFlags(TaskFlags.KillIfGoingOnBatteries, value); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets a Boolean value that indicates that the Unified Scheduling Engine will be utilized to run this task. + /// Property set for a task on a Task Scheduler version prior to 2.1. + [DefaultValue(false)] + [XmlIgnore] + public bool UseUnifiedSchedulingEngine + { + get + { + if (v2Settings2 != null) + return v2Settings2.UseUnifiedSchedulingEngine; + if (v2Settings3 != null) + return v2Settings3.UseUnifiedSchedulingEngine; + return false; + } + set + { + if (v2Settings2 != null) + v2Settings2.UseUnifiedSchedulingEngine = value; + else if (v2Settings3 != null) + v2Settings3.UseUnifiedSchedulingEngine = value; + else + throw new NotSupportedPriorToException(TaskCompatibility.V2_1); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets a boolean value that indicates whether the task is automatically disabled every time Windows starts. + /// Property set for a task on a Task Scheduler version prior to 2.2. + [DefaultValue(false)] + [XmlIgnore] + public bool Volatile + { + get => v2Settings3 != null && v2Settings3.Volatile; + set + { + if (v2Settings3 != null) + v2Settings3.Volatile = value; + else + throw new NotSupportedPriorToException(TaskCompatibility.V2_2); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets a Boolean value that indicates that the Task Scheduler will wake the computer when it is time to run the task. + /// + [DefaultValue(false)] + public bool WakeToRun + { + get => v2Settings?.WakeToRun ?? v1Task.HasFlags(TaskFlags.SystemRequired); + set + { + if (v2Settings != null) + v2Settings.WakeToRun = value; + else + v1Task.SetFlags(TaskFlags.SystemRequired, value); + OnNotifyPropertyChanged(); + } + } + + /// Gets or sets an XML-formatted definition of the task settings. + [XmlIgnore] + public string XmlText + { + get => v2Settings != null ? v2Settings.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this); + set + { + if (v2Settings != null) + v2Settings.XmlText = value; + else + XmlSerializationHelper.ReadObjectFromXmlText(value, this); + OnNotifyPropertyChanged(); + } + } + + /// + /// Gets or sets the information that specifies how the Task Scheduler performs tasks when the computer is in an idle state. + /// + [NotNull] + public IdleSettings IdleSettings => idleSettings ??= v2Settings != null ? new IdleSettings(v2Settings.IdleSettings) : new IdleSettings(v1Task); + + /// + /// Gets or sets the network settings object that contains a network profile identifier and name. If the RunOnlyIfNetworkAvailable + /// property of ITaskSettings is true and a network profile is specified in the NetworkSettings property, then the task will run + /// only if the specified network profile is available. + /// + [XmlIgnore] + [NotNull] + public NetworkSettings NetworkSettings => networkSettings ??= new NetworkSettings(v2Settings?.NetworkSettings); + /// Releases all resources used by this class. public void Dispose() { @@ -2015,8 +3562,8 @@ namespace winPEAS.TaskScheduler v1Task = null; } - /// Returns a that represents this instance. - /// A that represents this instance. + /// Returns a that represents this instance. + /// A that represents this instance. public override string ToString() { if (v2Settings != null || v1Task != null) @@ -2038,10 +3585,7 @@ namespace winPEAS.TaskScheduler reader.Skip(); } - void IXmlSerializable.WriteXml(XmlWriter writer) - { - XmlSerializationHelper.WriteObjectProperties(writer, this, ConvertXmlProperty); - } + void IXmlSerializable.WriteXml(XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this, ConvertXmlProperty); private bool ConvertXmlProperty(PropertyInfo pi, object obj, ref object value) { @@ -2060,7 +3604,7 @@ namespace winPEAS.TaskScheduler { // Check for back-door case where exact value is being passed and cast to ProcessPriorityClass if ((int)value <= 10 && value >= 0) return (int)value; - int p = 7; + var p = 7; switch (value) { case ProcessPriorityClass.AboveNormal: @@ -2114,6 +3658,10 @@ namespace winPEAS.TaskScheduler return ProcessPriorityClass.Idle; } } + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } internal static class DebugHelper @@ -2163,10 +3711,7 @@ namespace winPEAS.TaskScheduler TaskDefinition.SetV1TaskData(v1Task, d); } - public static void SetFlags(this ITask v1Task, TaskFlags flags, bool value = true) - { - v1Task.SetFlags(v1Task.GetFlags().SetFlags(flags, value)); - } + public static void SetFlags(this ITask v1Task, TaskFlags flags, bool value = true) => v1Task.SetFlags(v1Task.GetFlags().SetFlags(flags, value)); } internal class DefaultValueExAttribute : DefaultValueAttribute @@ -2188,4 +3733,4 @@ namespace winPEAS.TaskScheduler } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskCollection.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskCollection.cs index d8c0463..38ea994 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskCollection.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskCollection.cs @@ -2,9 +2,8 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text.RegularExpressions; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; + +using winPEAS.TaskScheduler.TaskEditor.Native; namespace winPEAS.TaskScheduler { @@ -15,11 +14,11 @@ namespace winPEAS.TaskScheduler public sealed class RunningTaskCollection : IReadOnlyList, IDisposable { private readonly TaskService svc; - private readonly IRunningTaskCollection v2Coll; + private readonly V2Interop.IRunningTaskCollection v2Coll; internal RunningTaskCollection([NotNull] TaskService svc) => this.svc = svc; - internal RunningTaskCollection([NotNull] TaskService svc, [NotNull] IRunningTaskCollection iTaskColl) + internal RunningTaskCollection([NotNull] TaskService svc, [NotNull] V2Interop.IRunningTaskCollection iTaskColl) { this.svc = svc; v2Coll = iTaskColl; @@ -74,9 +73,9 @@ namespace winPEAS.TaskScheduler public IEnumerator GetEnumerator() { if (v2Coll != null) - return new ComEnumerator(() => v2Coll.Count, (object o) => v2Coll[o], o => + return new ComEnumerator(() => v2Coll.Count, (object o) => v2Coll[o], o => { - IRegisteredTask task = null; + V2Interop.IRegisteredTask task = null; try { task = TaskService.GetTask(svc.v2TaskService, o.Path); } catch { } return task == null ? null : new RunningTask(svc, task, o); }); @@ -152,9 +151,9 @@ namespace winPEAS.TaskScheduler { private readonly TaskFolder fld; private readonly TaskService svc; - private readonly IRegisteredTaskCollection v2Coll; + private readonly V2Interop.IRegisteredTaskCollection v2Coll; private Regex filter; - private ITaskScheduler v1TS; + private V1Interop.ITaskScheduler v1TS; internal TaskCollection([NotNull] TaskService svc, Regex filter = null) { @@ -163,7 +162,7 @@ namespace winPEAS.TaskScheduler v1TS = svc.v1TaskScheduler; } - internal TaskCollection([NotNull] TaskFolder folder, [NotNull] IRegisteredTaskCollection iTaskColl, Regex filter = null) + internal TaskCollection([NotNull] TaskFolder folder, [NotNull] V2Interop.IRegisteredTaskCollection iTaskColl, Regex filter = null) { svc = folder.TaskService; Filter = filter; @@ -228,6 +227,24 @@ namespace winPEAS.TaskScheduler } } + /// Gets the named registered task from the collection. + /// The name of the registered task to be retrieved. + /// A instance that contains the requested context. + public Task this[string name] + { + get + { + if (v2Coll != null) + return Task.CreateTask(svc, v2Coll[name]); + + var v1Task = svc.GetTask(name); + if (v1Task != null) + return v1Task; + + throw new ArgumentOutOfRangeException(nameof(name)); + } + } + /// Releases all resources used by this class. public void Dispose() { @@ -236,6 +253,22 @@ namespace winPEAS.TaskScheduler Marshal.ReleaseComObject(v2Coll); } + /// Determines whether the specified task exists. + /// The name of the task. + /// true if task exists; otherwise, false. + public bool Exists([NotNull] string taskName) + { + try + { + if (v2Coll != null) + return v2Coll[taskName] != null; + + return svc.GetTask(taskName) != null; + } + catch { } + return false; + } + /// Gets the collection enumerator for the register task collection. /// An for this collection. public IEnumerator GetEnumerator() @@ -255,9 +288,9 @@ namespace winPEAS.TaskScheduler { private readonly Regex filter; private readonly TaskService svc; - private readonly IEnumWorkItems wienum; + private readonly V1Interop.IEnumWorkItems wienum; private string curItem; - private ITaskScheduler ts; + private V1Interop.ITaskScheduler ts; /// Internal constructor /// TaskService instance @@ -289,7 +322,7 @@ namespace winPEAS.TaskScheduler } } - internal ITask ICurrent => TaskService.GetTask(ts, curItem); + internal V1Interop.ITask ICurrent => TaskService.GetTask(ts, curItem); /// Releases all resources used by this class. public void Dispose() @@ -313,7 +346,7 @@ namespace winPEAS.TaskScheduler wienum?.Next(1, out names, out uFetched); if (uFetched != 1) break; - using (var name = new CoTaskMemString(Marshal.ReadIntPtr(names))) + using (var name = new V1Interop.CoTaskMemString(Marshal.ReadIntPtr(names))) curItem = name.ToString(); if (curItem != null && curItem.EndsWith(".job", StringComparison.InvariantCultureIgnoreCase)) curItem = curItem.Remove(curItem.Length - 4); @@ -328,7 +361,7 @@ namespace winPEAS.TaskScheduler continue; } - ITask itask = null; + V1Interop.ITask itask = null; try { itask = ICurrent; valid = true; } catch { valid = false; } finally { Marshal.ReleaseComObject(itask); } @@ -345,11 +378,11 @@ namespace winPEAS.TaskScheduler } } - private class V2TaskEnumerator : ComEnumerator + private class V2TaskEnumerator : ComEnumerator { private readonly Regex filter; - internal V2TaskEnumerator(TaskFolder folder, IRegisteredTaskCollection iTaskColl, Regex filter = null) : + internal V2TaskEnumerator(TaskFolder folder, V2Interop.IRegisteredTaskCollection iTaskColl, Regex filter = null) : base(() => iTaskColl.Count, (object o) => iTaskColl[o], o => Task.CreateTask(folder.TaskService, o)) => this.filter = filter; public override bool MoveNext() @@ -365,4 +398,4 @@ namespace winPEAS.TaskScheduler } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/ADVAPI32.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/ADVAPI32.cs new file mode 100644 index 0000000..be460ad --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/ADVAPI32.cs @@ -0,0 +1,366 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace winPEAS.TaskScheduler.TaskEditor.Native +{ + internal static partial class NativeMethods + { + const string ADVAPI32 = "advapi32.dll"; + + [Flags] + public enum AccessTypes : uint + { + TokenAssignPrimary = 0x0001, + TokenDuplicate = 0x0002, + TokenImpersonate = 0x0004, + TokenQuery = 0x0008, + TokenQuerySource = 0x0010, + TokenAdjustPrivileges = 0x0020, + TokenAdjustGroups = 0x0040, + TokenAdjustDefault = 0x0080, + TokenAdjustSessionID = 0x0100, + TokenAllAccessP = 0x000F00FF, + TokenAllAccess = 0x000F01FF, + TokenRead = 0x00020008, + TokenWrite = 0x000200E0, + TokenExecute = 0x00020000, + + Delete = 0x00010000, + ReadControl = 0x00020000, + WriteDac = 0x00040000, + WriteOwner = 0x00080000, + Synchronize = 0x00100000, + StandardRightsRequired = 0x000F0000, + StandardRightsRead = 0x00020000, + StandardRightsWrite = 0x00020000, + StandardRightsExecute = 0x00020000, + StandardRightsAll = 0x001F0000, + SpecificRightsAll = 0x0000FFFF, + AccessSystemSecurity = 0x01000000, + MaximumAllowed = 0x02000000, + GenericRead = 0x80000000, + GenericWrite = 0x40000000, + GenericExecute = 0x20000000, + GenericAll = 0x10000000, + } + + [Flags] + public enum PrivilegeAttributes : uint + { + Disabled = 0x00000000, + EnabledByDefault = 0x00000001, + Enabled = 0x00000002, + UsedForAccess = 0x80000000, + } + + public enum SECURITY_IMPERSONATION_LEVEL + { + Anonymous, + Identification, + Impersonation, + Delegation + } + + public enum TOKEN_ELEVATION_TYPE + { + Default = 1, + Full, + Limited + } + + public enum TOKEN_INFORMATION_CLASS + { + TokenUser = 1, + TokenGroups, + TokenPrivileges, + TokenOwner, + TokenPrimaryGroup, + TokenDefaultDacl, + TokenSource, + TokenType, + TokenImpersonationLevel, + TokenStatistics, + TokenRestrictedSids, + TokenSessionId, + TokenGroupsAndPrivileges, + TokenSessionReference, + TokenSandBoxInert, + TokenAuditPolicy, + TokenOrigin, + TokenElevationType, + TokenLinkedToken, + TokenElevation, + TokenHasRestrictions, + TokenAccessInformation, + TokenVirtualizationAllowed, + TokenVirtualizationEnabled, + TokenIntegrityLevel, + TokenUIAccess, + TokenMandatoryPolicy, + TokenLogonSid, + MaxTokenInfoClass + } + + [Serializable] + public enum TokenType + { + TokenImpersonation = 2, + TokenPrimary = 1 + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool AdjustTokenPrivileges([In] SafeTokenHandle TokenHandle, [In] bool DisableAllPrivileges, [In] ref TOKEN_PRIVILEGES NewState, [In] uint BufferLength, [In, Out] ref TOKEN_PRIVILEGES PreviousState, [In, Out] ref uint ReturnLength); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool AdjustTokenPrivileges([In] SafeTokenHandle TokenHandle, [In] bool DisableAllPrivileges, [In] ref TOKEN_PRIVILEGES NewState, [In] uint BufferLength, [In] IntPtr PreviousState, [In] IntPtr ReturnLength); + + [DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern bool ConvertStringSidToSid([In, MarshalAs(UnmanagedType.LPTStr)] string pStringSid, ref IntPtr sid); + + [DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public extern static bool DuplicateToken(SafeTokenHandle ExistingTokenHandle, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, out SafeTokenHandle DuplicateTokenHandle); + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail), DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DuplicateTokenEx([In] SafeTokenHandle ExistingTokenHandle, [In] AccessTypes DesiredAccess, [In] IntPtr TokenAttributes, [In] SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, [In] TokenType TokenType, [In, Out] ref SafeTokenHandle DuplicateTokenHandle); + + [DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetSidSubAuthority(IntPtr pSid, UInt32 nSubAuthority); + + [DllImport(ADVAPI32, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GetTokenInformation(SafeTokenHandle hToken, TOKEN_INFORMATION_CLASS tokenInfoClass, IntPtr pTokenInfo, Int32 tokenInfoLength, out Int32 returnLength); + + [DllImport(ADVAPI32, ExactSpelling = true, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool ImpersonateLoggedOnUser(IntPtr hToken); + + [DllImport(ADVAPI32, SetLastError = true, CharSet = CharSet.Unicode)] + public static extern int LogonUser(string lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, out SafeTokenHandle phToken); + + [DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool LookupAccountSid(string systemName, byte[] accountSid, StringBuilder accountName, ref int nameLength, StringBuilder domainName, ref int domainLength, out int accountType); + + [DllImport(ADVAPI32, CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool LookupAccountSid([In, MarshalAs(UnmanagedType.LPTStr)] string systemName, IntPtr sid, StringBuilder name, ref int cchName, StringBuilder referencedDomainName, ref int cchReferencedDomainName, out int use); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool LookupPrivilegeValue(string systemName, string name, out LUID luid); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenProcessToken(IntPtr ProcessHandle, AccessTypes DesiredAccess, out SafeTokenHandle TokenHandle); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool OpenThreadToken(IntPtr ThreadHandle, AccessTypes DesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool OpenAsSelf, out SafeTokenHandle TokenHandle); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool PrivilegeCheck(IntPtr ClientToken, ref PRIVILEGE_SET RequiredPrivileges, out int result); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool RevertToSelf(); + + [DllImport(ADVAPI32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool SetThreadToken(IntPtr ThreadHandle, SafeTokenHandle TokenHandle); + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LUID + { + public uint LowPart; + public int HighPart; + + public static LUID FromName(string name, string systemName = null) + { + LUID val; + if (!NativeMethods.LookupPrivilegeValue(systemName, name, out val)) + throw new System.ComponentModel.Win32Exception(); + return val; + } + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct LUID_AND_ATTRIBUTES + { + public LUID Luid; + public PrivilegeAttributes Attributes; + + public LUID_AND_ATTRIBUTES(LUID luid, PrivilegeAttributes attr) + { + Luid = luid; + Attributes = attr; + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct PRIVILEGE_SET : IDisposable + { + public uint PrivilegeCount; + public uint Control; + public IntPtr Privilege; + + public PRIVILEGE_SET(uint control, params LUID_AND_ATTRIBUTES[] privileges) + { + PrivilegeCount = (uint)privileges.Length; + Control = control; + Privilege = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES)) * (int)PrivilegeCount); + for (int i = 0; i < PrivilegeCount; i++) + Marshal.StructureToPtr(privileges[i], (IntPtr)((int)Privilege + (Marshal.SizeOf(typeof(LUID_AND_ATTRIBUTES)) * i)), false); + } + + public void Dispose() + { + Marshal.FreeHGlobal(Privilege); + } + } + + [StructLayout(LayoutKind.Sequential)] + public struct SID_AND_ATTRIBUTES + { + public IntPtr Sid; + public UInt32 Attributes; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_ELEVATION + { + public Int32 TokenIsElevated; + } + + [StructLayout(LayoutKind.Sequential)] + public struct TOKEN_MANDATORY_LABEL + { + public SID_AND_ATTRIBUTES Label; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct TOKEN_PRIVILEGES + { + public uint PrivilegeCount; + public LUID_AND_ATTRIBUTES Privileges; + + public TOKEN_PRIVILEGES(LUID luid, PrivilegeAttributes attribute) + { + PrivilegeCount = 1; + Privileges.Luid = luid; + Privileges.Attributes = attribute; + } + + public static uint SizeInBytes => (uint)Marshal.SizeOf(typeof(TOKEN_PRIVILEGES)); + } + + public partial class SafeTokenHandle + { + private const Int32 ERROR_NO_TOKEN = 0x000003F0; + private const Int32 ERROR_INSUFFICIENT_BUFFER = 122; + private static SafeTokenHandle currentProcessToken = null; + + public T GetInfo(TOKEN_INFORMATION_CLASS type) + { + int cbSize = Marshal.SizeOf(typeof(T)); + IntPtr pType = Marshal.AllocHGlobal(cbSize); + + try + { + // Retrieve token information. + if (!NativeMethods.GetTokenInformation(this, type, pType, cbSize, out cbSize)) + throw new System.ComponentModel.Win32Exception(); + + // Marshal from native to .NET. + switch (type) + { + case TOKEN_INFORMATION_CLASS.TokenType: + case TOKEN_INFORMATION_CLASS.TokenImpersonationLevel: + case TOKEN_INFORMATION_CLASS.TokenSessionId: + case TOKEN_INFORMATION_CLASS.TokenSandBoxInert: + case TOKEN_INFORMATION_CLASS.TokenOrigin: + case TOKEN_INFORMATION_CLASS.TokenElevationType: + case TOKEN_INFORMATION_CLASS.TokenHasRestrictions: + case TOKEN_INFORMATION_CLASS.TokenUIAccess: + case TOKEN_INFORMATION_CLASS.TokenVirtualizationAllowed: + case TOKEN_INFORMATION_CLASS.TokenVirtualizationEnabled: + return (T)Convert.ChangeType(Marshal.ReadInt32(pType), typeof(T)); + + case TOKEN_INFORMATION_CLASS.TokenLinkedToken: + return (T)Convert.ChangeType(Marshal.ReadIntPtr(pType), typeof(T)); + + case TOKEN_INFORMATION_CLASS.TokenUser: + case TOKEN_INFORMATION_CLASS.TokenGroups: + case TOKEN_INFORMATION_CLASS.TokenPrivileges: + case TOKEN_INFORMATION_CLASS.TokenOwner: + case TOKEN_INFORMATION_CLASS.TokenPrimaryGroup: + case TOKEN_INFORMATION_CLASS.TokenDefaultDacl: + case TOKEN_INFORMATION_CLASS.TokenSource: + case TOKEN_INFORMATION_CLASS.TokenStatistics: + case TOKEN_INFORMATION_CLASS.TokenRestrictedSids: + case TOKEN_INFORMATION_CLASS.TokenGroupsAndPrivileges: + case TOKEN_INFORMATION_CLASS.TokenElevation: + case TOKEN_INFORMATION_CLASS.TokenAccessInformation: + case TOKEN_INFORMATION_CLASS.TokenIntegrityLevel: + case TOKEN_INFORMATION_CLASS.TokenMandatoryPolicy: + case TOKEN_INFORMATION_CLASS.TokenLogonSid: + return (T)Marshal.PtrToStructure(pType, typeof(T)); + + case TOKEN_INFORMATION_CLASS.TokenSessionReference: + case TOKEN_INFORMATION_CLASS.TokenAuditPolicy: + default: + return default(T); + } + } + finally + { + Marshal.FreeHGlobal(pType); + } + } + + public static SafeTokenHandle FromCurrentProcess(AccessTypes desiredAccess = AccessTypes.TokenDuplicate) + { + lock (currentProcessToken) + { + if (currentProcessToken == null) + currentProcessToken = FromProcess(NativeMethods.GetCurrentProcess(), desiredAccess); + return currentProcessToken; + } + } + + public static SafeTokenHandle FromCurrentThread(AccessTypes desiredAccess = AccessTypes.TokenDuplicate, bool openAsSelf = true) => FromThread(NativeMethods.GetCurrentThread(), desiredAccess, openAsSelf); + + public static SafeTokenHandle FromProcess(IntPtr hProcess, AccessTypes desiredAccess = AccessTypes.TokenDuplicate) + { + SafeTokenHandle val; + if (!NativeMethods.OpenProcessToken(hProcess, desiredAccess, out val)) + throw new System.ComponentModel.Win32Exception(); + return val; + } + + public static SafeTokenHandle FromThread(IntPtr hThread, AccessTypes desiredAccess = AccessTypes.TokenDuplicate, bool openAsSelf = true) + { + SafeTokenHandle val; + if (!NativeMethods.OpenThreadToken(hThread, desiredAccess, openAsSelf, out val)) + { + if (Marshal.GetLastWin32Error() == ERROR_NO_TOKEN) + { + SafeTokenHandle pval = FromCurrentProcess(); + if (!NativeMethods.DuplicateTokenEx(pval, AccessTypes.TokenImpersonate | desiredAccess, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.Impersonation, TokenType.TokenImpersonation, ref val)) + throw new System.ComponentModel.Win32Exception(); + if (!NativeMethods.SetThreadToken(IntPtr.Zero, val)) + throw new System.ComponentModel.Win32Exception(); + } + else + throw new System.ComponentModel.Win32Exception(); + } + return val; + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/InteropUtil.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/InteropUtil.cs new file mode 100644 index 0000000..a9869ab --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/InteropUtil.cs @@ -0,0 +1,147 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace winPEAS.TaskScheduler.TaskEditor.Native +{ + internal static class InteropUtil + { + private const int cbBuffer = 256; + + public static T ToStructure(IntPtr ptr) => (T)Marshal.PtrToStructure(ptr, typeof(T)); + + public static IntPtr StructureToPtr(object value) + { + IntPtr ret = Marshal.AllocHGlobal(Marshal.SizeOf(value)); + Marshal.StructureToPtr(value, ret, false); + return ret; + } + + public static void AllocString(ref IntPtr ptr, ref uint size) + { + FreeString(ref ptr, ref size); + if (size == 0) size = cbBuffer; + ptr = Marshal.AllocHGlobal(cbBuffer); + } + + public static void FreeString(ref IntPtr ptr, ref uint size) + { + if (ptr != IntPtr.Zero) + { + Marshal.FreeHGlobal(ptr); + ptr = IntPtr.Zero; + size = 0; + } + } + + public static string GetString(IntPtr pString) => Marshal.PtrToStringUni(pString); + + public static bool SetString(ref IntPtr ptr, ref uint size, string value = null) + { + string s = GetString(ptr); + if (value == string.Empty) value = null; + if (string.CompareOrdinal(s, value) != 0) + { + FreeString(ref ptr, ref size); + if (value != null) + { + ptr = Marshal.StringToHGlobalUni(value); + size = (uint)value.Length + 1; + } + return true; + } + return false; + } + + /// + /// Converts an that points to a C-style array into a CLI array. + /// + /// Type of native structure used by the C-style array. + /// Output type for the CLI array. must be able to convert to . + /// The pointing to the native array. + /// The number of items in the native array. + /// An array of type containing the converted elements of the native array. + public static T[] ToArray(IntPtr ptr, int count) where TS : IConvertible + { + var ret = new T[count]; + var stSize = Marshal.SizeOf(typeof(TS)); + for (var i = 0; i < count; i++) + { + var tempPtr = new IntPtr(ptr.ToInt64() + (i * stSize)); + var val = ToStructure(tempPtr); + ret[i] = (T)Convert.ChangeType(val, typeof(T)); + } + return ret; + } + + /// + /// Converts an that points to a C-style array into a CLI array. + /// + /// Type of native structure used by the C-style array. + /// The pointing to the native array. + /// The number of items in the native array. + /// An array of type containing the elements of the native array. + public static T[] ToArray(IntPtr ptr, int count) + { + var ret = new T[count]; + var stSize = Marshal.SizeOf(typeof(T)); + for (var i = 0; i < count; i++) + { + var tempPtr = new IntPtr(ptr.ToInt64() + (i * stSize)); + ret[i] = ToStructure(tempPtr); + } + return ret; + } + } + + internal class ComEnumerator : IEnumerator where T : class where TIn : class + { + protected readonly Func converter; + protected IEnumerator iEnum; + + public ComEnumerator(Func getCount, Func indexer, Func converter) + { + IEnumerator Enumerate() + { + for (var x = 1; x <= getCount(); x++) + yield return indexer(x); + } + + this.converter = converter; + iEnum = Enumerate(); + } + + public ComEnumerator(Func getCount, Func indexer, Func converter) + { + IEnumerator Enumerate() + { + for (var x = 1; x <= getCount(); x++) + yield return indexer(x); + } + + this.converter = converter; + iEnum = Enumerate(); + } + + object IEnumerator.Current => Current; + + public virtual T Current => converter(iEnum?.Current); + + public virtual void Dispose() + { + iEnum?.Dispose(); + iEnum = null; + } + + public virtual bool MoveNext() => iEnum?.MoveNext() ?? false; + + public virtual void Reset() + { + iEnum?.Reset(); + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/KERNEL32.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/KERNEL32.cs new file mode 100644 index 0000000..9f022c4 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/KERNEL32.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace winPEAS.TaskScheduler.TaskEditor.Native +{ + internal static partial class NativeMethods + { + const string KERNEL32 = "Kernel32.dll"; + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport(KERNEL32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool CloseHandle(IntPtr handle); + + [DllImport(KERNEL32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport(KERNEL32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr GetCurrentThread(); + + /// + /// The GlobalLock function locks a global memory object and returns a pointer to the first byte of the object's memory block. + /// GlobalLock function increments the lock count by one. + /// Needed for the clipboard functions when getting the data from IDataObject + /// + /// + /// + [DllImport(KERNEL32, SetLastError = true)] + public static extern IntPtr GlobalLock(IntPtr hMem); + + /// + /// The GlobalUnlock function decrements the lock count associated with a memory object. + /// + /// + /// + [DllImport(KERNEL32, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool GlobalUnlock(IntPtr hMem); + + [DllImport(KERNEL32, CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr LoadLibrary(string filename); + + [DllImport(KERNEL32, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool FreeLibrary(IntPtr lib); + + public partial class SafeTokenHandle : Microsoft.Win32.SafeHandles.SafeHandleZeroOrMinusOneIsInvalid + { + private SafeTokenHandle() : base(true) { } + + internal SafeTokenHandle(IntPtr handle, bool own = true) : base(own) + { + SetHandle(handle); + } + + protected override bool ReleaseHandle() => CloseHandle(handle); + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NTDSAPI.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NTDSAPI.cs similarity index 98% rename from winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NTDSAPI.cs rename to winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NTDSAPI.cs index 0fecab3..5721830 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/NTDSAPI.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NTDSAPI.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Security; +using System.Text; +using System.Threading.Tasks; -namespace winPEAS.TaskScheduler.Native +namespace winPEAS.TaskScheduler.TaskEditor.Native { internal static partial class NativeMethods { @@ -227,4 +231,4 @@ namespace winPEAS.TaskScheduler.Native } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NetServerEnum.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NetServerEnum.cs new file mode 100644 index 0000000..45cace7 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/NetServerEnum.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; +using System.Threading.Tasks; + +namespace winPEAS.TaskScheduler.TaskEditor.Native +{ + internal static partial class NativeMethods + { + const int MAX_PREFERRED_LENGTH = -1; + + [Flags] + public enum ServerTypes : uint + { + Workstation = 0x00000001, + Server = 0x00000002, + SqlServer = 0x00000004, + DomainCtrl = 0x00000008, + BackupDomainCtrl = 0x00000010, + TimeSource = 0x00000020, + AppleFilingProtocol = 0x00000040, + Novell = 0x00000080, + DomainMember = 0x00000100, + PrintQueueServer = 0x00000200, + DialinServer = 0x00000400, + XenixServer = 0x00000800, + UnixServer = 0x00000800, + NT = 0x00001000, + WindowsForWorkgroups = 0x00002000, + MicrosoftFileAndPrintServer = 0x00004000, + NTServer = 0x00008000, + BrowserService = 0x00010000, + BackupBrowserService = 0x00020000, + MasterBrowserService = 0x00040000, + DomainMaster = 0x00080000, + OSF1Server = 0x00100000, + VMSServer = 0x00200000, + Windows = 0x00400000, + DFS = 0x00800000, + NTCluster = 0x01000000, + TerminalServer = 0x02000000, + VirtualNTCluster = 0x04000000, + DCE = 0x10000000, + AlternateTransport = 0x20000000, + LocalListOnly = 0x40000000, + PrimaryDomain = 0x80000000, + All = 0xFFFFFFFF + }; + + public enum ServerPlatform + { + DOS = 300, + OS2 = 400, + NT = 500, + OSF = 600, + VMS = 700 + } + + [DllImport("Netapi32", CharSet = CharSet.Auto, SetLastError = true)] + private static extern int NetServerGetInfo(string serverName, int level, out IntPtr pSERVER_INFO_XXX); + + [DllImport("Netapi32", CharSet = CharSet.Auto, SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + private static extern int NetServerEnum( + [MarshalAs(UnmanagedType.LPWStr)] string servernane, // must be null + int level, + out IntPtr bufptr, + int prefmaxlen, + out int entriesread, + out int totalentries, + ServerTypes servertype, + [MarshalAs(UnmanagedType.LPWStr)] string domain, // null for login domain + IntPtr resume_handle // Must be IntPtr.Zero + ); + + [DllImport("Netapi32", SetLastError = true), SuppressUnmanagedCodeSecurityAttribute] + private static extern int NetApiBufferFree(IntPtr pBuf); + + [StructLayout(LayoutKind.Sequential)] + public struct SERVER_INFO_100 + { + public ServerPlatform PlatformId; + [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] + public string Name; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVER_INFO_101 + { + public ServerPlatform PlatformId; + [MarshalAs(UnmanagedType.LPWStr)] + public string Name; + public int VersionMajor; + public int VersionMinor; + public ServerTypes Type; + [MarshalAs(UnmanagedType.LPWStr)] + public string Comment; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SERVER_INFO_102 + { + public ServerPlatform PlatformId; + [MarshalAs(UnmanagedType.LPWStr)] + public string Name; + public int VersionMajor; + public int VersionMinor; + public ServerTypes Type; + [MarshalAs(UnmanagedType.LPWStr)] + public string Comment; + public int MaxUsers; + public int AutoDisconnectMinutes; + [MarshalAs(UnmanagedType.Bool)] + public bool Hidden; + public int NetworkAnnounceRate; + public int NetworkAnnounceRateDelta; + public int UsersPerLicense; + [MarshalAs(UnmanagedType.LPWStr)] + public string UserDirectoryPath; + } + + [StructLayout(LayoutKind.Sequential)] + public struct NetworkComputerInfo // SERVER_INFO_101 + { + ServerPlatform sv101_platform_id; + [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] + string sv101_name; + int sv101_version_major; + int sv101_version_minor; + ServerTypes sv101_type; + [MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPWStr)] + string sv101_comment; + + public ServerPlatform Platform => sv101_platform_id; + public string Name => sv101_name; + public string Comment => sv101_comment; + public ServerTypes ServerTypes => sv101_type; + public Version Version => new Version(sv101_version_major, sv101_version_minor); + }; + + public static IEnumerable GetNetworkComputerNames(ServerTypes serverTypes = ServerTypes.Workstation | ServerTypes.Server, string domain = null) => + Array.ConvertAll(NetServerEnum(serverTypes, domain), si => si.Name); + + public static IEnumerable GetNetworkComputerInfo(ServerTypes serverTypes = ServerTypes.Workstation | ServerTypes.Server, string domain = null) => + NetServerEnum(serverTypes, domain, 101); + + public static T[] NetServerEnum(ServerTypes serverTypes = ServerTypes.Workstation | ServerTypes.Server, string domain = null, int level = 0) where T : struct + { + if (level == 0) + level = int.Parse(System.Text.RegularExpressions.Regex.Replace(typeof(T).Name, @"[^\d]", "")); + + IntPtr bufptr = IntPtr.Zero; + try + { + int entriesRead, totalEntries; + IntPtr resumeHandle = IntPtr.Zero; + + int ret = NetServerEnum(null, level, out bufptr, MAX_PREFERRED_LENGTH, out entriesRead, out totalEntries, serverTypes, domain, resumeHandle); + if (ret == 0) + return InteropUtil.ToArray(bufptr, entriesRead); + throw new System.ComponentModel.Win32Exception(ret); + } + finally + { + NetApiBufferFree(bufptr); + } + } + + public static T NetServerGetInfo(string serverName, int level = 0) where T : struct + { + if (level == 0) + level = int.Parse(System.Text.RegularExpressions.Regex.Replace(typeof(T).Name, @"[^\d]", "")); + + IntPtr ptr = IntPtr.Zero; + try + { + int ret = NetServerGetInfo(serverName, level, out ptr); + if (ret != 0) + throw new System.ComponentModel.Win32Exception(ret); + return (T)Marshal.PtrToStructure(ptr, typeof(T)); + } + finally + { + if (ptr != IntPtr.Zero) + NetApiBufferFree(ptr); + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/SYSTEMTIME.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/SYSTEMTIME.cs similarity index 86% rename from winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/SYSTEMTIME.cs rename to winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/SYSTEMTIME.cs index 633bc9f..29f8a4f 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/SYSTEMTIME.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEditor/Native/SYSTEMTIME.cs @@ -1,7 +1,11 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; -namespace winPEAS.TaskScheduler.Native +namespace winPEAS.TaskScheduler.TaskEditor.Native { internal static partial class NativeMethods { @@ -30,6 +34,18 @@ namespace winPEAS.TaskScheduler.Native Milliseconds = Convert.ToUInt16(dt.Millisecond); } + public SYSTEMTIME(ushort year, ushort month, ushort day, ushort hour = 0, ushort minute = 0, ushort second = 0, ushort millisecond = 0) + { + Year = year; + Month = month; + Day = day; + Hour = hour; + Minute = minute; + Second = second; + Milliseconds = millisecond; + DayOfWeek = 0; + } + public static implicit operator DateTime(SYSTEMTIME st) { if (st.Year == 0 || st == MinValue) @@ -47,6 +63,11 @@ namespace winPEAS.TaskScheduler.Native public static readonly SYSTEMTIME MinValue, MaxValue; + static SYSTEMTIME() + { + MinValue = new SYSTEMTIME(1601, 1, 1); + MaxValue = new SYSTEMTIME(30827, 12, 31, 23, 59, 59, 999); + } public override bool Equals(object obj) { diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEvent.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEvent.cs index 6c03694..499c71a 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEvent.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEvent.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; using System.Diagnostics.Eventing.Reader; - -// ReSharper disable MemberCanBePrivate.Global -// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global +using System.Linq; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { @@ -396,6 +396,22 @@ namespace winPEAS.TaskScheduler /// public EventRecord EventRecord { get; internal set; } + /// + /// Gets the from the . + /// + /// + /// The . If not found, returns . + /// + public StandardTaskEventId StandardEventId + { + get + { + if (Enum.IsDefined(typeof(StandardTaskEventId), EventId)) + return (StandardTaskEventId)EventId; + return StandardTaskEventId.Unknown; + } + } + /// /// Gets the level. This value is null for V1 events. /// @@ -441,6 +457,14 @@ namespace winPEAS.TaskScheduler /// public byte? Version { get; internal set; } + /// + /// Gets the data value from the task specific event data item list. + /// + /// The name of the data element. + /// Contents of the requested data element if found. null if no value found. + [Obsolete("Use the DataVales property instead.")] + public string GetDataValue(string name) => DataValues?[name]; + /// /// Returns a that represents this instance. /// @@ -479,7 +503,30 @@ namespace winPEAS.TaskScheduler { rec = eventRec; } - } + + /// + /// Gets the value of the specified property name. + /// + /// + /// The value. + /// + /// Name of the property. + /// Value of the specified property name. null if property does not exist. + public string this[string propertyName] + { + get + { + var propsel = new EventLogPropertySelector(new[] { $"Event/EventData/Data[@Name='{propertyName}']" }); + try + { + var logEventProps = rec.GetPropertyValues(propsel); + return logEventProps[0].ToString(); + } + catch { } + return null; + } + } + } } /// @@ -542,7 +589,27 @@ namespace winPEAS.TaskScheduler { log.Seek(System.IO.SeekOrigin.Begin, 0L); } - } + + /// + /// Seeks the specified bookmark. + /// + /// The bookmark. + /// The offset. + public void Seek(EventBookmark bookmark, long offset = 0L) + { + log.Seek(bookmark, offset); + } + + /// + /// Seeks the specified origin. + /// + /// The origin. + /// The offset. + public void Seek(System.IO.SeekOrigin origin, long offset) + { + log.Seek(origin, offset); + } + } /// /// Historical event log for a task. Only available for Windows Vista and Windows Server 2008 and later systems. @@ -577,6 +644,16 @@ namespace winPEAS.TaskScheduler private const string TSEventLogPath = "Microsoft-Windows-TaskScheduler/Operational"; private static readonly bool IsVistaOrLater = Environment.OSVersion.Version.Major >= 6; + /// + /// Initializes a new instance of the class. + /// + /// The task path. This can be retrieved using the property. + /// Thrown when instantiated on an OS prior to Windows Vista. + public TaskEventLog([CanBeNull] string taskPath) : this(".", taskPath) + { + Initialize(".", BuildQuery(taskPath), true); + } + /// /// Initializes a new instance of the class. /// @@ -591,6 +668,36 @@ namespace winPEAS.TaskScheduler Initialize(machineName, BuildQuery(taskPath), true, domain, user, password); } + /// + /// Initializes a new instance of the class that looks at all task events from a specified time. + /// + /// The start time. + /// Name of the task. + /// Name of the machine (optional). + /// The domain. + /// The user. + /// The password. + public TaskEventLog(DateTime startTime, string taskName = null, string machineName = null, string domain = null, string user = null, string password = null) + { + int[] numArray = new int[] { 100, 102, 103, 107, 108, 109, 111, 117, 118, 119, 120, 121, 122, 123, 124, 125 }; + Initialize(machineName, BuildQuery(taskName, numArray, startTime), false, domain, user, password); + } + + /// + /// Initializes a new instance of the class. + /// + /// Name of the task. + /// The event ids. + /// The start time. + /// Name of the machine (optional). + /// The domain. + /// The user. + /// The password. + public TaskEventLog(string taskName = null, int[] eventIDs = null, DateTime? startTime = null, string machineName = null, string domain = null, string user = null, string password = null) + { + Initialize(machineName, BuildQuery(taskName, eventIDs, startTime), true, domain, user, password); + } + /// /// Initializes a new instance of the class. /// @@ -670,6 +777,63 @@ namespace winPEAS.TaskScheduler Query.Session = new EventLogSession(machineName, domain, user, spwd, SessionAuthentication.Default); } + /// + /// Gets the total number of events for this task. + /// + public long Count + { + get + { + using (EventLogReader log = new EventLogReader(Query)) + { + long seed = 64L, l = 0L, h = seed; + while (log.ReadEvent() != null) + log.Seek(System.IO.SeekOrigin.Begin, l += seed); + bool foundLast = false; + while (l > 0L && h >= 1L) + { + if (foundLast) + l += (h /= 2L); + else + l -= (h /= 2L); + log.Seek(System.IO.SeekOrigin.Begin, l); + foundLast = (log.ReadEvent() != null); + } + return foundLast ? l + 1L : l; + } + } + } + + /// + /// Gets or sets a value indicating whether this is enabled. + /// + /// + /// true if enabled; otherwise, false. + /// + public bool Enabled + { + get + { + if (!IsVistaOrLater) + return false; + using (var cfg = new EventLogConfiguration(TSEventLogPath, Query.Session)) + return cfg.IsEnabled; + } + set + { + if (!IsVistaOrLater) + throw new NotSupportedException("Task history not available on systems prior to Windows Vista and Windows Server 2008."); + using (var cfg = new EventLogConfiguration(TSEventLogPath, Query.Session)) + { + if (cfg.IsEnabled != value) + { + cfg.IsEnabled = value; + cfg.SaveChanges(); + } + } + } + } + /// /// Gets or sets a value indicating whether to enumerate in reverse when calling the default enumerator (typically with foreach statement). /// @@ -711,4 +875,4 @@ namespace winPEAS.TaskScheduler internal EventLogQuery Query { get; private set; } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEventWatcher.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEventWatcher.cs deleted file mode 100644 index cf0dfc5..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskEventWatcher.cs +++ /dev/null @@ -1,698 +0,0 @@ -using System; -using System.ComponentModel; -using System.ComponentModel.Design; -using System.Diagnostics.Eventing.Reader; -using System.IO; - -namespace winPEAS.TaskScheduler -{ - /// - /// Information about the task event. - /// - [PublicAPI] - public class TaskEventArgs : EventArgs - { - private readonly TaskService taskService; - - internal TaskEventArgs([NotNull] TaskEvent evt, TaskService ts = null) - { - TaskEvent = evt; - TaskPath = evt.TaskPath; - taskService = ts; - } - - /// - /// Gets the . - /// - /// - /// The TaskEvent. - /// - [NotNull] - public TaskEvent TaskEvent { get; } - - /// - /// Gets the task path. - /// - /// - /// The task path. - /// - public string TaskPath { get; } - } - - /// - /// Watches system events related to tasks and issues a event when the filtered conditions are met. - /// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. - /// - /// Sometimes, a developer will need to know about events as they occur. In this case, they can use the TaskEventWatcher component that enables the developer to watch a task, a folder, or the entire system for filtered events. - /// - /// Below is information on how to watch a folder for all task events. For a complete example, look at this sample project: TestTaskWatcher.zip - /// - [DefaultEvent(nameof(EventRecorded)), DefaultProperty(nameof(Folder))] -#if DESIGNER - [Designer(typeof(Design.TaskEventWatcherDesigner))] -#endif - [ToolboxItem(true), Serializable] - [PublicAPI] - public class TaskEventWatcher : Component, ISupportInitialize - { - private const string root = "\\"; - private const string star = "*"; - - private static readonly TimeSpan MaxV1EventLapse = TimeSpan.FromSeconds(1); - - private bool disposed; - private bool enabled; - private string folder = root; - private bool includeSubfolders; - private bool initializing; - private StandardTaskEventId lastId = 0; - private DateTime lastIdTime = DateTime.MinValue; - private TaskService ts; - private FileSystemWatcher v1Watcher; - private EventLogWatcher watcher; - private ISynchronizeInvoke synchronizingObject; - - /// - /// Initializes a new instance of the class. If other - /// properties are not set, this will watch for all events for all tasks on the local machine. - /// - public TaskEventWatcher() : this(TaskService.Instance) - { - } - - /// - /// Initializes a new instance of the class watching only - /// those events for the task with the provided path on the local machine. - /// - /// The full path (folders and name) of the task to watch. - /// The task service. - /// $Invalid task name: {taskPath} - public TaskEventWatcher(string taskPath, TaskService taskService = null) : this(taskService ?? TaskService.Instance) - { - InitTask(taskPath); - } - - private TaskEventWatcher(TaskService ts) - { - TaskService = ts; - Filter = new EventFilter(this); - } - - /// - /// Occurs when a task or the task engine records an event. - /// - [Category("Action"), Description("Event recorded by a task or the task engine.")] - public event EventHandler EventRecorded; - - /// - /// Gets or sets a value indicating whether the component is enabled. - /// - /// - /// true if enabled; otherwise, false. - /// - [DefaultValue(false), Category("Behavior"), Description("Indicates whether the component is enabled.")] - public bool Enabled - { - get { return enabled; } - set - { - if (enabled != value) - { - System.Diagnostics.Debug.WriteLine($"TaskEventWather: Set {nameof(Enabled)} = {value}"); - enabled = value; - if (!IsSuspended()) - { - if (enabled) - StartRaisingEvents(); - else - StopRaisingEvents(); - } - } - } - } - - /// - /// Gets the filter for this . - /// - /// - /// The filter. - /// - [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Behavior"), Description("Indicates the filter for the watcher.")] - public EventFilter Filter { get; } - - /// - /// Gets or sets the folder to watch. - /// - /// - /// The folder path to watch. This value should include the leading "\" to indicate the root folder. - /// - /// Thrown if the folder specified does not exist or contains invalid characters. - [DefaultValue(root), Category("Behavior"), Description("Indicates the folder to watch.")] - public string Folder - { - get { return folder; } - set - { - if (string.IsNullOrEmpty(value)) - value = root; - if (!value.EndsWith("\\")) - value += "\\"; - if (string.Compare(folder, value, StringComparison.OrdinalIgnoreCase) == 0) return; - if ((DesignMode && (value.IndexOfAny(new[] { '*', '?' }) != -1 || value.IndexOfAny(Path.GetInvalidPathChars()) != -1)) || (TaskService.GetFolder(value == root ? value : value.TrimEnd('\\')) == null)) - throw new ArgumentException($"Invalid folder name: {value}"); - folder = value; - } - } - - /// - /// Gets or sets a value indicating whether to include events from subfolders when the - /// property is set. If the property is set, - /// this property is ignored. - /// - /// true if include events from subfolders; otherwise, false. - [DefaultValue(false), Category("Behavior"), Description("Indicates whether to include events from subfolders.")] - public bool IncludeSubfolders - { - get { return includeSubfolders; } - set - { - if (includeSubfolders == value) return; - includeSubfolders = value; - Restart(); - } - } - - /// - /// Gets or sets the synchronizing object. - /// - /// - /// The synchronizing object. - /// - [Browsable(false), DefaultValue(null)] - public ISynchronizeInvoke SynchronizingObject - { - get - { - if (synchronizingObject == null && DesignMode) - { - var so = ((IDesignerHost)GetService(typeof(IDesignerHost)))?.RootComponent as ISynchronizeInvoke; - if (so != null) - synchronizingObject = so; - } - return synchronizingObject; - } - set { synchronizingObject = value; } - } - - /// - /// Gets or sets the name of the computer that is running the Task Scheduler service that the user is connected to. - /// - [Category("Connection"), Description("The name of the computer to connect to."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string TargetServer - { - get { return TaskService.TargetServer; } - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, TaskService.TargetServer, StringComparison.OrdinalIgnoreCase) == 0) return; - TaskService.TargetServer = value; - Restart(); - } - } - - /// - /// Gets or sets the instance associated with this event watcher. Setting this value - /// will override any values set for , , - /// , and and set them to those values in the supplied - /// instance. - /// - /// The TaskService. - [Category("Data"), Description("The TaskService for this event watcher.")] - public TaskService TaskService - { - get { return ts; } - set { ts = value; Restart(); } - } - - /// - /// Gets or sets the user account domain to be used when connecting to the . - /// - /// The user account domain. - [Category("Connection"), Description("The user account domain to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string UserAccountDomain - { - get { return TaskService.UserAccountDomain; } - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, TaskService.UserAccountDomain, StringComparison.OrdinalIgnoreCase) == 0) return; - TaskService.UserAccountDomain = value; - Restart(); - } - } - - /// - /// Gets or sets the user name to be used when connecting to the . - /// - /// The user name. - [Category("Connection"), Description("The user name to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string UserName - { - get { return TaskService.UserName; } - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, TaskService.UserName, StringComparison.OrdinalIgnoreCase) == 0) return; - TaskService.UserName = value; - Restart(); - } - } - - /// - /// Gets or sets the user password to be used when connecting to the . - /// - /// The user password. - [Category("Connection"), Description("The user password to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - public string UserPassword - { - get { return TaskService.UserPassword; } - set - { - if (value == null || value.Trim() == string.Empty) value = null; - if (string.Compare(value, TaskService.UserPassword, StringComparison.OrdinalIgnoreCase) == 0) return; - TaskService.UserPassword = value; - Restart(); - } - } - - /// - /// Gets a value indicating if watching is available. - /// - [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] - private bool IsHandleInvalid => IsV1 ? v1Watcher == null : watcher == null; - - private static bool IsV1 => Environment.OSVersion.Version.Major < 6; - - /// - /// Signals the object that initialization is starting. - /// - public void BeginInit() - { - System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(BeginInit)}"); - initializing = true; - var localEnabled = enabled; - StopRaisingEvents(); - enabled = localEnabled; - TaskService.BeginInit(); - } - - /// - /// Signals the object that initialization is complete. - /// - public void EndInit() - { - System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(EndInit)}"); - initializing = false; - TaskService.EndInit(); - if (enabled) - StartRaisingEvents(); - } - - /// - /// Releases the unmanaged resources used by the FileSystemWatcher 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) - { - try - { - if (disposing) - { - StopRaisingEvents(); - TaskService = null; - } - else - { - StopListening(); - } - } - finally - { - disposed = true; - base.Dispose(disposing); - } - } - - /// - /// Fires the event. - /// - /// The sender. - /// The instance containing the event data. - protected virtual void OnEventRecorded(object sender, TaskEventArgs e) - { - var h = EventRecorded; - if (h == null) return; - if (SynchronizingObject != null && SynchronizingObject.InvokeRequired) - SynchronizingObject.BeginInvoke(h, new object[] { this, e }); - else - h(sender, e); - } - - private void InitTask(string taskPath) - { - Filter.TaskName = Path.GetFileNameWithoutExtension(taskPath); - Folder = Path.GetDirectoryName(taskPath); - } - - private bool IsSuspended() => initializing || DesignMode; - - private void ReleaseWatcher() - { - if (IsV1) - { - if (v1Watcher == null) return; - v1Watcher.EnableRaisingEvents = false; - v1Watcher.Changed -= Watcher_DirectoryChanged; - v1Watcher.Created -= Watcher_DirectoryChanged; - v1Watcher.Deleted -= Watcher_DirectoryChanged; - v1Watcher.Renamed -= Watcher_DirectoryChanged; - v1Watcher = null; - } - else - { - if (watcher == null) return; - watcher.Enabled = false; - watcher.EventRecordWritten -= Watcher_EventRecordWritten; - watcher = null; - } - } - - private void Restart() - { - if (IsSuspended() || !enabled) return; - System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(Restart)}"); - StopRaisingEvents(); - StartRaisingEvents(); - } - - private void SetupWatcher() - { - ReleaseWatcher(); - string taskPath = null; - if (Filter.Wildcard == null) - taskPath = Path.Combine(folder, Filter.TaskName); - if (IsV1) - { - var di = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.System)); - string dir = di.Parent != null ? Path.Combine(di.Parent.FullName, "Tasks") : "Tasks"; - v1Watcher = new FileSystemWatcher(dir, "*.job") { NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Attributes }; - v1Watcher.Changed += Watcher_DirectoryChanged; - v1Watcher.Created += Watcher_DirectoryChanged; - v1Watcher.Deleted += Watcher_DirectoryChanged; - v1Watcher.Renamed += Watcher_DirectoryChanged; - } - else - { - var log = new TaskEventLog(taskPath, Filter.EventIds, Filter.EventLevels, DateTime.UtcNow, TargetServer, UserAccountDomain, UserName, UserPassword); - log.Query.ReverseDirection = false; - watcher = new EventLogWatcher(log.Query); - watcher.EventRecordWritten += Watcher_EventRecordWritten; - } - } - - private void StartRaisingEvents() - { - if (disposed) - throw new ObjectDisposedException(GetType().Name); - - if (IsSuspended()) return; - System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(StartRaisingEvents)}"); - enabled = true; - SetupWatcher(); - if (IsV1) - try { v1Watcher.EnableRaisingEvents = true; } catch { } - else - try { watcher.Enabled = true; } catch { } - } - - private void StopListening() - { - enabled = false; - ReleaseWatcher(); - } - - private void StopRaisingEvents() - { - System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(StopRaisingEvents)}"); - if (IsSuspended()) - enabled = false; - else if (!IsHandleInvalid) - StopListening(); - } - - private void Watcher_DirectoryChanged(object sender, FileSystemEventArgs e) - { - StandardTaskEventId id = StandardTaskEventId.TaskUpdated; - if (e.ChangeType == WatcherChangeTypes.Deleted) - id = StandardTaskEventId.TaskDeleted; - else if (e.ChangeType == WatcherChangeTypes.Created) - id = StandardTaskEventId.JobRegistered; - if (lastId == id && DateTime.Now.Subtract(lastIdTime) <= MaxV1EventLapse) return; - OnEventRecorded(this, new TaskEventArgs(new TaskEvent(Path.Combine("\\", e.Name.Replace(".job", "")), id, DateTime.Now), TaskService)); - lastId = id; - lastIdTime = DateTime.Now; - } - - private void Watcher_EventRecordWritten(object sender, EventRecordWrittenEventArgs e) - { - try - { - var taskEvent = new TaskEvent(e.EventRecord); - System.Diagnostics.Debug.WriteLine("Task event: " + taskEvent.ToString()); - - // Get the task name and folder - if (string.IsNullOrEmpty(taskEvent.TaskPath)) return; - int cpos = taskEvent.TaskPath.LastIndexOf('\\'); - string name = taskEvent.TaskPath.Substring(cpos + 1); - string fld = taskEvent.TaskPath.Substring(0, cpos + 1); - - // Check folder and name filters - if (!string.IsNullOrEmpty(Filter.TaskName) && string.Compare(Filter.TaskName, taskEvent.TaskPath, StringComparison.OrdinalIgnoreCase) != 0) - { - if (Filter.Wildcard != null && !Filter.Wildcard.IsMatch(name)) - return; - if (IncludeSubfolders && !fld.StartsWith(folder, StringComparison.OrdinalIgnoreCase)) - return; - if (!IncludeSubfolders && string.Compare(folder, fld, StringComparison.OrdinalIgnoreCase) != 0) - return; - } - - OnEventRecorded(this, new TaskEventArgs(taskEvent, TaskService)); - } - catch (Exception ex) - { - System.Diagnostics.Debug.WriteLine($"{nameof(Watcher_EventRecordWritten)} has failed. Error: {ex.ToString()}"); - } - } - - /// - /// Holds filter information for a . - /// - [TypeConverter(typeof(ExpandableObjectConverter)), Serializable] - [PublicAPI] - public class EventFilter - { - private string filter = star; - private int[] ids; - private int[] levels; - private readonly TaskEventWatcher parent; - - internal EventFilter([NotNull] TaskEventWatcher parent) - { - this.parent = parent; - } - - /// - /// Gets or sets an optional array of event identifiers to use when filtering those events that will fire a event. - /// - /// - /// The array of event identifier filters. All know task event identifiers are declared in the enumeration. - /// - [DefaultValue(null), Category("Filter"), Description("An array of event identifiers to use when filtering.")] - public int[] EventIds - { - get { return ids; } - set - { - if (ids != value) - { - ids = value; - parent.Restart(); - } - } - } - - /// - /// Gets or sets an optional array of event levels to use when filtering those events that will fire a event. - /// - /// - /// The array of event levels. While event providers can define custom levels, most will use integers defined in the System.Diagnostics.Eventing.Reader.StandardEventLevel enumeration. - /// - [DefaultValue(null), Category("Filter"), Description("An array of event levels to use when filtering.")] - public int[] EventLevels - { - get { return levels; } - set - { - if (levels != value) - { - levels = value; - parent.Restart(); - } - } - } - - /// - /// Gets or sets the task name, which can utilize wildcards, to look for when watching a folder. - /// - /// A task name or wildcard. - [DefaultValue(star), Category("Filter"), Description("A task name, which can utilize wildcards, for filtering.")] - public string TaskName - { - get { return filter; } - set - { - if (string.IsNullOrEmpty(value)) - value = star; - if (string.Compare(filter, value, StringComparison.OrdinalIgnoreCase) != 0) - { - filter = value; - Wildcard = (value.IndexOfAny(new[] { '?', '*' }) == -1) ? null : new Wildcard(value); - parent.Restart(); - } - } - } - - internal Wildcard Wildcard { get; private set; } = new Wildcard(star); - - /// - /// Returns a that represents this instance. - /// - /// - /// A that represents this instance. - /// - public override string ToString() => filter + (levels == null ? "" : " +levels") + (ids == null ? "" : " +id's"); - } - } - -#if DESIGNER - namespace Design - { - internal class TaskEventWatcherDesigner : ComponentDesigner - { - public override void InitializeNewComponent(IDictionary defaultValues) - { - base.InitializeNewComponent(defaultValues); - var refs = GetService(); - var tsColl = refs?.GetReferences(typeof(TaskService)); - System.Diagnostics.Debug.Assert(refs != null && tsColl != null && tsColl.Length > 0, "Designer couldn't find host, reference service, or existing TaskService."); - if (tsColl != null && tsColl.Length > 0) - { - TaskEventWatcher tsComp = Component as TaskEventWatcher; - TaskService ts = tsColl[0] as TaskService; - if (tsComp != null) - tsComp.TaskService = ts; - } - } - - protected virtual T GetService() => (T)Component?.Site?.GetService(typeof(T)); - } - } -#endif -} \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolder.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolder.cs index 143bc15..d64609d 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolder.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolder.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using System.Security; using System.Security.AccessControl; +using System.Text; using System.Text.RegularExpressions; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.V1Interop; +using winPEAS.TaskScheduler.V2Interop; namespace winPEAS.TaskScheduler { @@ -43,12 +46,70 @@ namespace winPEAS.TaskScheduler 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. /// @@ -61,7 +122,8 @@ namespace winPEAS.TaskScheduler { if (v2Folder != null) return new TaskFolderCollection(this, v2Folder.GetFolders(0)); - } catch { } + } + catch { } return new TaskFolderCollection(); } } @@ -87,6 +149,28 @@ namespace winPEAS.TaskScheduler /// 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. /// @@ -96,7 +180,7 @@ namespace winPEAS.TaskScheduler /// 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. + /// 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(); @@ -138,7 +222,7 @@ namespace winPEAS.TaskScheduler /// /// 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. + /// Not supported under Task Scheduler 1.0. public void DeleteFolder([NotNull] string subFolderName, bool exceptionOnNotExists = true) { if (v2Folder != null) @@ -157,6 +241,52 @@ namespace winPEAS.TaskScheduler 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. @@ -168,11 +298,35 @@ namespace winPEAS.TaskScheduler 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. /// @@ -199,6 +353,60 @@ namespace winPEAS.TaskScheduler 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. /// @@ -278,7 +486,7 @@ namespace winPEAS.TaskScheduler 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 = userId ?? definition.Principal.Account; + userId ??= definition.Principal.Account; if (userId == string.Empty) userId = null; User user = new User(userId); if (v2Folder != null) @@ -331,7 +539,7 @@ namespace winPEAS.TaskScheduler break; case TaskLogonType.ServiceAccount: flags &= ~(TaskFlags.Interactive | TaskFlags.RunOnlyIfLoggedOn); - definition.v1Task.SetAccountInformation((string.IsNullOrEmpty(userId) || user.IsSystem) ? String.Empty : user.Name, IntPtr.Zero); + definition.v1Task.SetAccountInformation((String.IsNullOrEmpty(userId) || user.IsSystem) ? String.Empty : user.Name, IntPtr.Zero); break; case TaskLogonType.InteractiveTokenOrPassword: flags |= TaskFlags.Interactive; @@ -369,6 +577,34 @@ namespace winPEAS.TaskScheduler 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. /// @@ -397,4 +633,4 @@ namespace winPEAS.TaskScheduler yield return task; } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolderCollection.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolderCollection.cs index 9cfaa51..03c7b19 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolderCollection.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskFolderCollection.cs @@ -1,71 +1,104 @@ using System; using System.Collections.Generic; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V2; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.TaskEditor.Native; namespace winPEAS.TaskScheduler { - /// - /// Provides information and control for a collection of folders that contain tasks. - /// - public sealed class TaskFolderCollection : ICollection, IDisposable + /// Provides information and control for a collection of folders that contain tasks. + public sealed class TaskFolderCollection : ICollection, IDisposable, INotifyCollectionChanged, INotifyPropertyChanged { + private const string IndexerName = "Item[]"; private readonly TaskFolder parent; private readonly TaskFolder[] v1FolderList; - private readonly ITaskFolderCollection v2FolderList; + private readonly V2Interop.ITaskFolderCollection v2FolderList; - internal TaskFolderCollection() - { - v1FolderList = new TaskFolder[0]; - } + internal TaskFolderCollection() => v1FolderList = new TaskFolder[0]; - internal TaskFolderCollection([NotNull] TaskFolder folder, [NotNull] ITaskFolderCollection iCollection) + internal TaskFolderCollection([NotNull] TaskFolder folder, [NotNull] V2Interop.ITaskFolderCollection iCollection) { parent = folder; v2FolderList = iCollection; } - /// - /// Gets the number of items in the collection. - /// + /// Occurs when a collection changes. + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// Gets the number of items in the collection. public int Count => v2FolderList?.Count ?? v1FolderList.Length; - /// - /// Gets a value indicating whether the is read-only. - /// + /// Gets a value indicating whether the is read-only. bool ICollection.IsReadOnly => false; - /// - /// Adds an item to the . - /// - /// The object to add to the . - /// This action is technically unfeasible due to limitations of the underlying library. Use the instead. - public void Add([NotNull] TaskFolder item) { throw new NotImplementedException(); } + /// Gets the specified folder from the collection. + /// The index of the folder to be retrieved. + /// A TaskFolder instance that represents the requested folder. + public TaskFolder this[int index] + { + get + { + if (v2FolderList != null) + return new TaskFolder(parent.TaskService, v2FolderList[++index]); + return v1FolderList[index]; + } + } - /// - /// Removes all items from the . - /// + /// Gets the specified folder from the collection. + /// The path of the folder to be retrieved. + /// A TaskFolder instance that represents the requested folder. + public TaskFolder this[[NotNull] string path] + { + get + { + try + { + if (v2FolderList != null) + return parent.GetFolder(path); + if (v1FolderList != null && v1FolderList.Length > 0 && (path == string.Empty || path == "\\")) + return v1FolderList[0]; + } + catch { } + throw new ArgumentException(@"Path not found", nameof(path)); + } + } + + /// Adds an item to the . + /// The object to add to the . + /// + /// This action is technically unfeasible due to limitations of the underlying library. Use the instead. + /// + public void Add([NotNull] TaskFolder item) => throw new NotImplementedException(); + + /// Removes all items from the . public void Clear() { if (v2FolderList != null) { - for (int i = v2FolderList.Count; i > 0; i--) + for (var i = v2FolderList.Count; i > 0; i--) parent.DeleteFolder(v2FolderList[i].Name, false); + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } } - /// - /// Determines whether the contains a specific value. - /// - /// The object to locate in the . - /// - /// true if is found in the ; otherwise, false. - /// + /// Determines whether the contains a specific value. + /// The object to locate in the . + /// true if is found in the ; otherwise, false. public bool Contains([NotNull] TaskFolder item) { if (v2FolderList != null) { - for (int i = v2FolderList.Count; i > 0; i--) + for (var i = v2FolderList.Count; i > 0; i--) if (string.Equals(item.Path, v2FolderList[i].Path, StringComparison.CurrentCultureIgnoreCase)) return true; } @@ -74,10 +107,11 @@ namespace winPEAS.TaskScheduler return false; } - /// - /// Copies the elements of the ICollection to an Array, starting at a particular Array index. - /// - /// The one-dimensional Array that is the destination of the elements copied from . The Array must have zero-based indexing. + /// Copies the elements of the ICollection to an Array, starting at a particular Array index. + /// + /// The one-dimensional Array that is the destination of the elements copied from . The Array must have + /// zero-based indexing. + /// /// The zero-based index in array at which copying begins. public void CopyTo(TaskFolder[] array, int arrayIndex) { @@ -98,9 +132,7 @@ namespace winPEAS.TaskScheduler } } - /// - /// Releases all resources used by this class. - /// + /// Releases all resources used by this class. public void Dispose() { if (v1FolderList != null && v1FolderList.Length > 0) @@ -112,35 +144,77 @@ namespace winPEAS.TaskScheduler System.Runtime.InteropServices.Marshal.ReleaseComObject(v2FolderList); } - /// - /// Gets a list of items in a collection. - /// + /// Determines whether the specified folder exists. + /// The path of the folder. + /// true if folder exists; otherwise, false. + public bool Exists([NotNull] string path) + { + try + { + parent.GetFolder(path); + return true; + } + catch { } + return false; + } + + /// Gets a list of items in a collection. /// Enumerated list of items in the collection. public IEnumerator GetEnumerator() { if (v2FolderList != null) - return new ComEnumerator(() => v2FolderList.Count, (object o) => v2FolderList[o], o => new TaskFolder(parent.TaskService, o)); + return new ComEnumerator(() => v2FolderList.Count, (object o) => v2FolderList[o], o => new TaskFolder(parent.TaskService, o)); return Array.AsReadOnly(v1FolderList).GetEnumerator(); } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . + /* + /// Returns the index of the TaskFolder within the collection. + /// TaskFolder to find. + /// Index of the TaskFolder; -1 if not found. + public int IndexOf(TaskFolder item) + { + return IndexOf(item.Path); + } + + /// Returns the index of the TaskFolder within the collection. + /// Path to find. + /// Index of the TaskFolder; -1 if not found. + public int IndexOf(string path) + { + if (v2FolderList != null) + { + for (int i = 0; i < v2FolderList.Count; i++) + { + if (v2FolderList[new System.Runtime.InteropServices.VariantWrapper(i)].Path == path) + return i; + } + return -1; + } + else + return (v1FolderList.Length > 0 && (path == string.Empty || path == "\\")) ? 0 : -1; + } + */ + + /// Removes the first occurrence of a specific object from the . + /// The object to remove from the . /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . + /// true if was successfully removed from the ; otherwise, false. This method + /// also returns false if is not found in the original . /// public bool Remove([NotNull] TaskFolder item) { if (v2FolderList != null) { - for (int i = v2FolderList.Count; i > 0; i--) + for (var i = v2FolderList.Count; i > 0; i--) { if (string.Equals(item.Path, v2FolderList[i].Path, StringComparison.CurrentCultureIgnoreCase)) { try { parent.DeleteFolder(v2FolderList[i].Name); + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, i)); } catch { @@ -154,5 +228,9 @@ namespace winPEAS.TaskScheduler } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); + + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } } diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskHandlerInterfaces.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskHandlerInterfaces.cs index b69c0af..b25b562 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskHandlerInterfaces.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskHandlerInterfaces.cs @@ -1,8 +1,41 @@ -using System.Runtime.InteropServices; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { - + /// + /// Defines the methods that are called by the Task Scheduler service to manage a COM handler. + /// + /// + /// This interface must be implemented for a task to perform a COM handler action. When the Task Scheduler performs a COM handler action, it creates and activates the handler and calls the methods of this interface as needed. For information on specifying a COM handler action, see the class. + /// + [ComImport, Guid("839D7762-5121-4009-9234-4F0D19394F04"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity] + public interface ITaskHandler + { + /// + /// Called to start the COM handler. This method must be implemented by the handler. + /// + /// An IUnkown interface that is used to communicate back with the Task Scheduler. + /// The arguments that are required by the handler. These arguments are defined in the property of the COM handler action. + void Start([In, MarshalAs(UnmanagedType.IUnknown)] object pHandlerServices, [In, MarshalAs(UnmanagedType.BStr)] string data); + /// + /// Called to stop the COM handler. This method must be implemented by the handler. + /// + /// The return code that the Task Schedule will raise as an event when the COM handler action is completed. + void Stop([MarshalAs(UnmanagedType.Error)] out int pRetCode); + /// + /// Called to pause the COM handler. This method is optional and should only be implemented to give the Task Scheduler the ability to pause and restart the handler. + /// + void Pause(); + /// + /// Called to resume the COM handler. This method is optional and should only be implemented to give the Task Scheduler the ability to resume the handler. + /// + void Resume(); + } /// /// Provides the methods that are used by COM handlers to notify the Task Scheduler about the status of the handler. diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskSecurity.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskSecurity.cs index 8689d61..9da1624 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskSecurity.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskSecurity.cs @@ -2,14 +2,13 @@ using System.Security; using System.Security.AccessControl; using System.Security.Principal; -using winPEAS.TaskScheduler.Native; namespace winPEAS.TaskScheduler { - /// - /// Specifies the access control rights that can be applied to Task Scheduler tasks. - /// - [Flags] + /// + /// Specifies the access control rights that can be applied to Task Scheduler tasks. + /// + [Flags] public enum TaskRights { /// Specifies the right to exert full control over a task folder or task, and to modify access control and audit rules. This value represents the right to do anything with a task and is the combination of all rights in this enumeration. @@ -66,6 +65,17 @@ namespace winPEAS.TaskScheduler { } + /// + /// Initializes a new instance of the class, specifying the name of the user or group the rule applies to, the access rights, and whether the specified access rights are allowed or denied. + /// + /// The name of the user or group the rule applies to. + /// A bitwise combination of values specifying the rights allowed or denied. + /// One of the values specifying whether the rights are allowed or denied. + public TaskAccessRule([NotNull] string identity, TaskRights eventRights, AccessControlType type) + : this(new NTAccount(identity), (int)eventRights, false, InheritanceFlags.None, PropagationFlags.None, type) + { + } + private TaskAccessRule([NotNull] IdentityReference identity, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) : base(identity, accessMask, isInherited, inheritanceFlags, propagationFlags, type) { @@ -85,6 +95,17 @@ namespace winPEAS.TaskScheduler /// public sealed class TaskAuditRule : AuditRule { + /// + /// Initializes a new instance of the class, specifying the user or group to audit, the rights to audit, and whether to audit success, failure, or both. + /// + /// The user or group the rule applies to. Must be of type or a type such as that can be converted to type . + /// A bitwise combination of values specifying the kinds of access to audit. + /// The audit flags. + public TaskAuditRule([NotNull] IdentityReference identity, TaskRights eventRights, AuditFlags flags) + : this(identity, (int)eventRights, false, InheritanceFlags.None, PropagationFlags.None, flags) + { + } + internal TaskAuditRule([NotNull] IdentityReference identity, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) : base(identity, accessMask, isInherited, inheritanceFlags, propagationFlags, flags) { @@ -149,7 +170,17 @@ namespace winPEAS.TaskScheduler this.CanonicalizeAccessRules(); } - + /// + /// Initializes a new instance of the class with the specified sections of the access control security rules from the specified task. + /// + /// The folder. + /// The sections of the ACL to retrieve. + public TaskSecurity([NotNull] TaskFolder folder, AccessControlSections sections = Task.defaultAccessControlSections) + : base(false) + { + SetSecurityDescriptorSddlForm(folder.GetSecurityDescriptorSddlForm(Convert(sections)), sections); + this.CanonicalizeAccessRules(); + } /// /// Gets the enumeration that the class uses to represent access rights. @@ -169,7 +200,25 @@ namespace winPEAS.TaskScheduler /// A object representing the class. public override Type AuditRuleType => typeof(TaskAuditRule); - /// + /// + /// Gets a object that represent the default access rights. + /// + /// The default task security. + public static TaskSecurity DefaultTaskSecurity + { + get + { + var ret = new TaskSecurity(); + ret.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.LocalSystemSid, null), TaskRights.FullControl, AccessControlType.Allow)); + ret.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.BuiltinAdministratorsSid, null), TaskRights.Read | TaskRights.Write | TaskRights.Execute, AccessControlType.Allow)); + ret.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.LocalServiceSid, null), TaskRights.Read, AccessControlType.Allow)); + ret.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null), TaskRights.Read, AccessControlType.Allow)); + ret.AddAccessRule(new TaskAccessRule(new SecurityIdentifier(WellKnownSidType.NetworkServiceSid, null), TaskRights.Read, AccessControlType.Allow)); + return ret; + } + } + + /// /// Creates a new access control rule for the specified user, with the specified access rights, access control, and flags. /// /// An that identifies the user or group the rule applies to. @@ -183,6 +232,24 @@ namespace winPEAS.TaskScheduler /// public override AccessRule AccessRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AccessControlType type) => new TaskAccessRule(identityReference, (TaskRights)accessMask, type); + /// + /// Searches for a matching rule with which the new rule can be merged. If none are found, adds the new rule. + /// + /// The access control rule to add. + public void AddAccessRule([NotNull] TaskAccessRule rule) + { + base.AddAccessRule(rule); + } + + /// + /// Searches for an audit rule with which the new rule can be merged. If none are found, adds the new rule. + /// + /// The audit rule to add. The user specified by this rule determines the search. + public void AddAuditRule([NotNull] TaskAuditRule rule) + { + base.AddAuditRule(rule); + } + /// /// Creates a new audit rule, specifying the user the rule applies to, the access rights to audit, and the outcome that triggers the audit rule. /// @@ -197,6 +264,83 @@ namespace winPEAS.TaskScheduler /// public override AuditRule AuditRuleFactory(IdentityReference identityReference, int accessMask, bool isInherited, InheritanceFlags inheritanceFlags, PropagationFlags propagationFlags, AuditFlags flags) => new TaskAuditRule(identityReference, accessMask, isInherited, inheritanceFlags, propagationFlags, flags); + /// + /// Searches for an access control rule with the same user and (allow or deny) as the specified rule, and with compatible inheritance and propagation flags; if such a rule is found, the rights contained in the specified access rule are removed from it. + /// + /// A that specifies the user and to search for, and a set of inheritance and propagation flags that a matching rule, if found, must be compatible with. Specifies the rights to remove from the compatible rule, if found. + /// true if a compatible rule is found; otherwise false. + public bool RemoveAccessRule([NotNull] TaskAccessRule rule) => base.RemoveAccessRule(rule); + + /// + /// Searches for all access control rules with the same user and (allow or deny) as the specified rule and, if found, removes them. + /// + /// A that specifies the user and to search for, and a set of inheritance and propagation flags that a matching rule, if found, must be compatible with. Any rights specified by this rule are ignored. + public void RemoveAccessRuleAll([NotNull] TaskAccessRule rule) + { + base.RemoveAccessRuleAll(rule); + } + + /// + /// Searches for an access control rule that exactly matches the specified rule and, if found, removes it. + /// + /// The to remove. + public void RemoveAccessRuleSpecific([NotNull] TaskAccessRule rule) + { + base.RemoveAccessRuleSpecific(rule); + } + + /// + /// Searches for an audit control rule with the same user as the specified rule, and with compatible inheritance and propagation flags; if a compatible rule is found, the rights contained in the specified rule are removed from it. + /// + /// A that specifies the user to search for, and a set of inheritance and propagation flags that a matching rule, if found, must be compatible with. Specifies the rights to remove from the compatible rule, if found. + /// true if a compatible rule is found; otherwise false. + public bool RemoveAuditRule([NotNull] TaskAuditRule rule) => base.RemoveAuditRule(rule); + + /// + /// Searches for all audit rules with the same user as the specified rule and, if found, removes them. + /// + /// A that specifies the user to search for. Any rights specified by this rule are ignored. + public void RemoveAuditRuleAll(TaskAuditRule rule) + { + base.RemoveAuditRuleAll(rule); + } + + /// + /// Searches for an audit rule that exactly matches the specified rule and, if found, removes it. + /// + /// The to remove. + public void RemoveAuditRuleSpecific([NotNull] TaskAuditRule rule) + { + base.RemoveAuditRuleSpecific(rule); + } + + /// + /// Removes all access control rules with the same user as the specified rule, regardless of , and then adds the specified rule. + /// + /// The to add. The user specified by this rule determines the rules to remove before this rule is added. + public void ResetAccessRule([NotNull] TaskAccessRule rule) + { + base.ResetAccessRule(rule); + } + + /// + /// Removes all access control rules with the same user and (allow or deny) as the specified rule, and then adds the specified rule. + /// + /// The to add. The user and of this rule determine the rules to remove before this rule is added. + public void SetAccessRule([NotNull] TaskAccessRule rule) + { + base.SetAccessRule(rule); + } + + /// + /// Removes all audit rules with the same user as the specified rule, regardless of the value, and then adds the specified rule. + /// + /// The to add. The user specified by this rule determines the rules to remove before this rule is added. + public void SetAuditRule([NotNull] TaskAuditRule rule) + { + base.SetAuditRule(rule); + } + /// /// Returns a that represents this instance. /// @@ -219,6 +363,20 @@ namespace winPEAS.TaskScheduler return ret; } + private static AccessControlSections Convert(SecurityInfos si) + { + AccessControlSections ret = AccessControlSections.None; + if ((si & SecurityInfos.SystemAcl) != 0) + ret |= AccessControlSections.Audit; + if ((si & SecurityInfos.DiscretionaryAcl) != 0) + ret |= AccessControlSections.Access; + if ((si & SecurityInfos.Group) != 0) + ret |= AccessControlSections.Group; + if ((si & SecurityInfos.Owner) != 0) + ret |= AccessControlSections.Owner; + return ret; + } + private AccessControlSections GetAccessControlSectionsFromChanges() { AccessControlSections none = AccessControlSections.None; @@ -264,5 +422,43 @@ namespace winPEAS.TaskScheduler WriteUnlock(); } } - } -} \ No newline at end of file + + /// + /// Saves the specified sections of the security descriptor associated with this object to permanent storage. We recommend that the values of the parameters passed to the constructor and persist methods be identical. + /// + /// The task folder used to retrieve the persisted information. + /// One of the enumeration values that specifies the sections of the security descriptor (access rules, audit rules, owner, primary group) of the securable object to save. + [SecurityCritical] + internal void Persist([NotNull] TaskFolder folder, AccessControlSections includeSections = Task.defaultAccessControlSections) + { + WriteLock(); + try + { + AccessControlSections accessControlSectionsFromChanges = GetAccessControlSectionsFromChanges(); + if (accessControlSectionsFromChanges != AccessControlSections.None) + { + folder.SetSecurityDescriptorSddlForm(GetSecurityDescriptorSddlForm(accessControlSectionsFromChanges)); + OwnerModified = GroupModified = AccessRulesModified = AuditRulesModified = false; + } + } + finally + { + WriteUnlock(); + } + } + + /// + /// Saves the specified sections of the security descriptor associated with this object to permanent storage. We recommend that the values of the parameters passed to the constructor and persist methods be identical. For more information, see Remarks. + /// + /// The name used to retrieve the persisted information. + /// One of the enumeration values that specifies the sections of the security descriptor (access rules, audit rules, owner, primary group) of the securable object to save. + protected override void Persist([NotNull] string name, AccessControlSections includeSections = Task.defaultAccessControlSections) + { + using (var ts = new TaskService()) + { + var task = ts.GetTask(name); + Persist(task, includeSections); + } + } + } +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskService.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskService.cs index 026aed8..175f00e 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskService.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskService.cs @@ -1,16 +1,17 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.IO; +using System.Linq; using System.Runtime.InteropServices; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using System.Text; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { /// /// Quick simple trigger types for the - /// method. + /// method. /// public enum QuickTriggerType { @@ -77,14 +78,14 @@ namespace winPEAS.TaskScheduler { 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(CTask).GUID; - private static Guid IID_ITask = typeof(ITask).GUID; + 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 ITaskScheduler v1TaskScheduler; - internal ITaskService v2TaskService; + internal V1Interop.ITaskScheduler v1TaskScheduler; + internal V2Interop.ITaskService v2TaskService; private bool connecting; private bool forceV1; private bool initializing; @@ -131,6 +132,18 @@ namespace winPEAS.TaskScheduler 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. @@ -205,6 +218,40 @@ namespace winPEAS.TaskScheduler [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: @@ -331,10 +378,106 @@ namespace winPEAS.TaskScheduler } } + /// 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 @@ -377,6 +520,75 @@ namespace winPEAS.TaskScheduler 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; @@ -398,6 +610,46 @@ namespace winPEAS.TaskScheduler 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 @@ -434,6 +686,44 @@ namespace winPEAS.TaskScheduler /// 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. @@ -447,6 +737,23 @@ namespace winPEAS.TaskScheduler 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) @@ -458,9 +765,9 @@ namespace winPEAS.TaskScheduler info.AddValue("forceV1", forceV1, typeof(bool)); } - internal static IRegisteredTask GetTask([NotNull] ITaskService iSvc, [NotNull] string name) + internal static V2Interop.IRegisteredTask GetTask([NotNull] V2Interop.ITaskService iSvc, [NotNull] string name) { - ITaskFolder fld = null; + V2Interop.ITaskFolder fld = null; try { fld = iSvc.GetFolder("\\"); @@ -476,7 +783,7 @@ namespace winPEAS.TaskScheduler } } - internal static ITask GetTask([NotNull] ITaskScheduler iSvc, [NotNull] string name) + internal static V1Interop.ITask GetTask([NotNull] V1Interop.ITaskScheduler iSvc, [NotNull] string name) { if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name)); @@ -584,7 +891,7 @@ namespace winPEAS.TaskScheduler if (LibraryIsV2 && !forceV1) { - v2TaskService = new ITaskService(); + v2TaskService = new V2Interop.ITaskService(); if (!string.IsNullOrEmpty(targetServer)) { // Check to ensure character only server name. (Suggested by bigsan) @@ -605,7 +912,7 @@ namespace winPEAS.TaskScheduler else { v1Impersonation = new WindowsImpersonatedIdentity(userName, userDomain, userPassword); - v1TaskScheduler = new ITaskScheduler(); + v1TaskScheduler = new V1Interop.ITaskScheduler(); if (!string.IsNullOrEmpty(targetServer)) { // Check to ensure UNC format for server name. (Suggested by bigsan) @@ -696,6 +1003,8 @@ namespace winPEAS.TaskScheduler 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); @@ -713,19 +1022,102 @@ namespace winPEAS.TaskScheduler 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); @@ -738,6 +1130,15 @@ namespace winPEAS.TaskScheduler 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) && @@ -747,6 +1148,18 @@ namespace winPEAS.TaskScheduler 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); + } } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskServiceCronExt.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskServiceCronExt.cs deleted file mode 100644 index 32e5e9d..0000000 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TaskServiceCronExt.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace winPEAS.TaskScheduler -{ - public abstract partial class Trigger - { - internal class CronExpression - { - private FieldVal[] Fields = new FieldVal[5]; - - private CronExpression() { } - - public enum CronFieldType { Minutes, Hours, Days, Months, DaysOfWeek }; - - public struct FieldVal - { - private const string rangeRegEx = @"^(?:(?\*)|(?\d+)(?:-(?\d+))?)(?:\/(?\d+))?$"; - private readonly static Dictionary dow = new Dictionary(7) - { - { "SUN", "0" }, - { "MON", "1" }, - { "TUE", "2" }, - { "WED", "3" }, - { "THU", "4" }, - { "FRI", "5" }, - { "SAT", "6" }, - }; - private readonly static Dictionary mon = new Dictionary(12) - { - { "JAN", "1" }, - { "FEB", "2" }, - { "MAR", "3" }, - { "APR", "4" }, - { "MAY", "5" }, - { "JUN", "6" }, - { "JUL", "7" }, - { "AUG", "8" }, - { "SEP", "9" }, - { "OCT", "10" }, - { "NOV", "11" }, - { "DEC", "12" }, - }; - private readonly static Dictionary validRange = new Dictionary(5) - { - { CronFieldType.Days, new MinMax(1, 31) }, - { CronFieldType.DaysOfWeek, new MinMax(0, 6) }, - { CronFieldType.Hours, new MinMax(0, 23) }, - { CronFieldType.Minutes, new MinMax(0, 59) }, - { CronFieldType.Months, new MinMax(1, 12) }, - }; - private CronFieldType cft; - private FieldFlags flags; - private int incr; - private int[] vals; - - enum FieldFlags { List, Every, Range, Increment }; - - public override string ToString() => $"Type:{flags}; Vals:{string.Join(",", vals.Select(i => i.ToString()).ToArray())}; Incr:{incr}"; - - private struct MinMax - { - public int Min, Max; - public MinMax(int min, int max) { Min = min; Max = max; } - } - } - } - } -} \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Trigger.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Trigger.cs index b95e3a7..e3e3dd1 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Trigger.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Trigger.cs @@ -2,12 +2,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using winPEAS.Properties; +using winPEAS.TaskScheduler.V2Interop; namespace winPEAS.TaskScheduler { @@ -17,18 +19,25 @@ namespace winPEAS.TaskScheduler { /// Sunday Sunday = 0x1, + /// Monday Monday = 0x2, + /// Tuesday Tuesday = 0x4, + /// Wednesday Wednesday = 0x8, + /// Thursday Thursday = 0x10, + /// Friday Friday = 0x20, + /// Saturday Saturday = 0x40, + /// All days AllDays = 0x7F } @@ -39,28 +48,40 @@ namespace winPEAS.TaskScheduler { /// January January = 0x1, + /// February February = 0x2, + /// March March = 0x4, + /// April April = 0x8, + /// May May = 0x10, + /// June June = 0x20, + /// July July = 0x40, + /// August August = 0x80, + /// September September = 0x100, + /// October October = 0x200, + /// November November = 0x400, + /// December December = 0x800, + /// All months AllMonths = 0xFFF } @@ -71,26 +92,37 @@ namespace winPEAS.TaskScheduler { /// Triggers the task when a specific event occurs. Version 1.2 only. Event = 0, + /// Triggers the task at a specific time of day. Time = 1, + /// Triggers the task on a daily schedule. Daily = 2, + /// Triggers the task on a weekly schedule. Weekly = 3, + /// Triggers the task on a monthly schedule. Monthly = 4, + /// Triggers the task on a monthly day-of-week schedule. MonthlyDOW = 5, + /// Triggers the task when the computer goes into an idle state. Idle = 6, + /// Triggers the task when the task is registered. Version 1.2 only. Registration = 7, + /// Triggers the task when the computer boots. Boot = 8, + /// Triggers the task when a specific user logs on. Logon = 9, + /// Triggers the task when a specific user session state changes. Version 1.2 only. SessionStateChange = 11, + /// Triggers the custom trigger. Version 1.3 only. Custom = 12 } @@ -101,14 +133,19 @@ namespace winPEAS.TaskScheduler { /// First week of the month FirstWeek = 1, + /// Second week of the month SecondWeek = 2, + /// Third week of the month ThirdWeek = 4, + /// Fourth week of the month FourthWeek = 8, + /// Last week of the month LastWeek = 0x10, + /// Every week of the month AllWeeks = 0x1F } @@ -132,14 +169,17 @@ namespace winPEAS.TaskScheduler } /// Represents a trigger that starts a task when the system is booted. - /// A BootTrigger will fire when the system starts. It can only be delayed. All triggers that support a delay implement the ITriggerDelay interface. + /// + /// A BootTrigger will fire when the system starts. It can only be delayed. All triggers that support a delay implement the + /// ITriggerDelay interface. + /// /// /// - /// + ///]]> /// /// public sealed class BootTrigger : Trigger, ITriggerDelay @@ -147,9 +187,13 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public BootTrigger() : base(TaskTriggerType.Boot) { } - internal BootTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.OnSystemStart) { } + internal BootTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnSystemStart) + { + } - internal BootTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal BootTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started. /// Not supported under Task Scheduler 1.0. @@ -166,17 +210,18 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(Delay)] = value; + OnNotifyPropertyChanged(); } } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. - protected override string V2GetTriggerString() => winPEAS.Properties.Resources.TriggerBoot1; + protected override string V2GetTriggerString() => Properties.Resources.TriggerBoot1; } /// - /// Represents a custom trigger. This class is based on undocumented features and may change. This type of trigger is only available for reading custom - /// triggers. It cannot be used to create custom triggers. + /// Represents a custom trigger. This class is based on undocumented features and may change. This type of trigger is only + /// available for reading custom triggers. It cannot be used to create custom triggers. /// public sealed class CustomTrigger : Trigger, ITriggerDelay { @@ -184,7 +229,9 @@ namespace winPEAS.TaskScheduler private TimeSpan delay = TimeSpan.MinValue; private string name = string.Empty; - internal CustomTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal CustomTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets a value that indicates the amount of time between the trigger events and when the task is started. /// This value cannot be set. @@ -194,6 +241,14 @@ namespace winPEAS.TaskScheduler set => throw new NotImplementedException(); } + /// Gets the name of the custom trigger type. + /// The name of the XML element representing this custom trigger. + public string Name => name; + + /// Gets the properties from the XML definition if possible. + [XmlArray, XmlArrayItem("Property")] + public NamedValueCollection Properties => nvc; + /// Clones this instance. /// This method will always throw an exception. /// CustomTrigger cannot be cloned due to OS restrictions. @@ -246,23 +301,23 @@ namespace winPEAS.TaskScheduler /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. - protected override string V2GetTriggerString() => winPEAS.Properties.Resources.TriggerCustom1; + protected override string V2GetTriggerString() => Resources.TriggerCustom1; } /// - /// Represents a trigger that starts a task based on a daily schedule. For example, the task starts at a specific time every day, every other day, every - /// third day, and so on. + /// Represents a trigger that starts a task based on a daily schedule. For example, the task starts at a specific time every day, every + /// other day, every third day, and so on. /// /// A DailyTrigger will fire at a specified time every day or interval of days. /// /// - /// + ///]]> /// /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] @@ -270,15 +325,17 @@ namespace winPEAS.TaskScheduler { /// Creates an unbound instance of a . /// Interval between the days in the schedule. - public DailyTrigger(short daysInterval = 1) : base(TaskTriggerType.Daily) { DaysInterval = daysInterval; } + public DailyTrigger(short daysInterval = 1) : base(TaskTriggerType.Daily) => DaysInterval = daysInterval; - internal DailyTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.RunDaily) + internal DailyTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunDaily) { if (v1TriggerData.Data.daily.DaysInterval == 0) v1TriggerData.Data.daily.DaysInterval = 1; } - internal DailyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal DailyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Sets or retrieves the interval between the days in the schedule. [DefaultValue(1)] @@ -302,6 +359,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(DaysInterval)] = value; } + OnNotifyPropertyChanged(); } } @@ -320,6 +378,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; + OnNotifyPropertyChanged(); } } @@ -332,8 +391,8 @@ namespace winPEAS.TaskScheduler } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -352,15 +411,15 @@ namespace winPEAS.TaskScheduler System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { CalendarTrigger.ReadXml(reader, this, ReadMyXml); } + void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { CalendarTrigger.WriteXml(writer, this, WriteMyXml); } + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => CalendarTrigger.WriteXml(writer, this, WriteMyXml); /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() => DaysInterval == 1 ? - string.Format(winPEAS.Properties.Resources.TriggerDaily1, AdjustToLocal(StartBoundary)) : - string.Format(winPEAS.Properties.Resources.TriggerDaily2, AdjustToLocal(StartBoundary), DaysInterval); + string.Format(Properties.Resources.TriggerDaily1, AdjustToLocal(StartBoundary)) : + string.Format(Properties.Resources.TriggerDaily2, AdjustToLocal(StartBoundary), DaysInterval); private void ReadMyXml(System.Xml.XmlReader reader) { @@ -381,18 +440,18 @@ namespace winPEAS.TaskScheduler } /// - /// Represents a trigger that starts a task when a system event occurs. Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 - /// and later. + /// Represents a trigger that starts a task when a system event occurs. Only available for Task Scheduler 2.0 on Windows Vista or + /// Windows Server 2003 and later. /// /// The EventTrigger runs when a system event fires. /// /// - /// "; /// eTrigger.ValueQueries.Add("Name", "Value"); - /// ]]> + ///]]> /// /// [XmlType(IncludeInSchema = false)] @@ -403,7 +462,15 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public EventTrigger() : base(TaskTriggerType.Event) { } - internal EventTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + /// Initializes an unbound instance of the class and sets a basic event. + /// The event's log. + /// The event's source. Can be null. + /// The event's id. Can be null. + public EventTrigger(string log, string source, int? eventId) : this() => SetBasic(log, source, eventId); + + internal EventTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -416,6 +483,7 @@ namespace winPEAS.TaskScheduler ((IEventTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else unboundValues[nameof(Delay)] = value; + OnNotifyPropertyChanged(); } } @@ -430,20 +498,50 @@ namespace winPEAS.TaskScheduler ((IEventTrigger)v2Trigger).Subscription = value; else unboundValues[nameof(Subscription)] = value; + OnNotifyPropertyChanged(); } } /// - /// Gets a collection of named XPath queries. Each query in the collection is applied to the last matching event XML returned from the subscription query - /// specified in the Subscription property. The name of the query can be used as a variable in the message of a action. + /// Gets a collection of named XPath queries. Each query in the collection is applied to the last matching event XML returned from + /// the subscription query specified in the Subscription property. The name of the query can be used as a variable in the message of + /// a action. /// [XmlArray] [XmlArrayItem("Value", typeof(NameValuePair))] - public NamedValueCollection ValueQueries => nvc ?? (nvc = v2Trigger == null ? new NamedValueCollection() : new NamedValueCollection(((IEventTrigger)v2Trigger).ValueQueries)); + public NamedValueCollection ValueQueries => nvc ??= v2Trigger == null ? new NamedValueCollection() : new NamedValueCollection(((IEventTrigger)v2Trigger).ValueQueries); + + /// Builds an event log XML query string based on the input parameters. + /// The event's log. + /// The event's source. Can be null. + /// The event's id. Can be null. + /// XML query string. + /// log + public static string BuildQuery(string log, string source, int? eventId) + { + var sb = new StringBuilder(); + if (string.IsNullOrEmpty(log)) + throw new ArgumentNullException(nameof(log)); + sb.AppendFormat(""); + return sb.ToString(); + } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -472,46 +570,57 @@ namespace winPEAS.TaskScheduler eventId = null; if (!string.IsNullOrEmpty(Subscription)) { - using (var str = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(Subscription))) + using var str = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(Subscription)); + using var rdr = new System.Xml.XmlTextReader(str) { - using (var rdr = new System.Xml.XmlTextReader(str)) + WhitespaceHandling = System.Xml.WhitespaceHandling.None + }; + try + { + rdr.MoveToContent(); + rdr.ReadStartElement("QueryList"); + if (rdr.Name == "Query" && rdr.MoveToAttribute("Path")) { - rdr.WhitespaceHandling = System.Xml.WhitespaceHandling.None; - try + var path = rdr.Value; + if (rdr.MoveToElement() && rdr.ReadToDescendant("Select") && path.Equals(rdr["Path"], StringComparison.InvariantCultureIgnoreCase)) { - rdr.MoveToContent(); - rdr.ReadStartElement("QueryList"); - if (rdr.Name == "Query" && rdr.MoveToAttribute("Path")) + var content = rdr.ReadString(); + var m = System.Text.RegularExpressions.Regex.Match(content, + @"\*(?:\[System\[(?:Provider\[\@Name='(?[^']+)'\])?(?:\s+and\s+)?(?:EventID=(?\d+))?\]\])", + System.Text.RegularExpressions.RegexOptions.IgnoreCase | + System.Text.RegularExpressions.RegexOptions.Compiled | + System.Text.RegularExpressions.RegexOptions.Singleline | + System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace); + if (m.Success) { - var path = rdr.Value; - if (rdr.MoveToElement() && rdr.ReadToDescendant("Select") && path.Equals(rdr["Path"], StringComparison.InvariantCultureIgnoreCase)) - { - var content = rdr.ReadString(); - var m = System.Text.RegularExpressions.Regex.Match(content, - @"\*(?:\[System\[(?:Provider\[\@Name='(?[^']+)'\])?(?:\s+and\s+)?(?:EventID=(?\d+))?\]\])", - System.Text.RegularExpressions.RegexOptions.IgnoreCase | - System.Text.RegularExpressions.RegexOptions.Compiled | - System.Text.RegularExpressions.RegexOptions.Singleline | - System.Text.RegularExpressions.RegexOptions.IgnorePatternWhitespace); - if (m.Success) - { - log = path; - if (m.Groups["s"].Success) - source = m.Groups["s"].Value; - if (m.Groups["e"].Success) - eventId = Convert.ToInt32(m.Groups["e"].Value); - return true; - } - } + log = path; + if (m.Groups["s"].Success) + source = m.Groups["s"].Value; + if (m.Groups["e"].Success) + eventId = Convert.ToInt32(m.Groups["e"].Value); + return true; } } - catch { /* ignored */ } } } + catch { /* ignored */ } } return false; } + /// + /// Sets the subscription for a basic event. This will replace the contents of the property and clear all + /// entries in the property. + /// + /// The event's log. + /// The event's source. Can be null. + /// The event's id. Can be null. + public void SetBasic([NotNull] string log, string source, int? eventId) + { + ValueQueries.Clear(); + Subscription = BuildQuery(log, source, eventId); + } + internal override void Bind(ITaskDefinition iTaskDef) { base.Bind(iTaskDef); @@ -523,28 +632,30 @@ namespace winPEAS.TaskScheduler protected override string V2GetTriggerString() { if (!GetBasic(out var log, out var source, out var id)) - return winPEAS.Properties.Resources.TriggerEvent1; + return Properties.Resources.TriggerEvent1; var sb = new StringBuilder(); - sb.AppendFormat(winPEAS.Properties.Resources.TriggerEventBasic1, log); + sb.AppendFormat(Properties.Resources.TriggerEventBasic1, log); if (!string.IsNullOrEmpty(source)) - sb.AppendFormat(winPEAS.Properties.Resources.TriggerEventBasic2, source); + sb.AppendFormat(Properties.Resources.TriggerEventBasic2, source); if (id.HasValue) - sb.AppendFormat(winPEAS.Properties.Resources.TriggerEventBasic3, id.Value); + sb.AppendFormat(Properties.Resources.TriggerEventBasic3, id.Value); return sb.ToString(); } } /// - /// Represents a trigger that starts a task when the computer goes into an idle state. For information about idle conditions, see Task Idle Conditions. + /// Represents a trigger that starts a task when the computer goes into an idle state. For information about idle conditions, see Task + /// Idle Conditions. /// /// - /// An IdleTrigger will fire when the system becomes idle. It is generally a good practice to set a limit on how long it can run using the ExecutionTimeLimit property. + /// An IdleTrigger will fire when the system becomes idle. It is generally a good practice to set a limit on how long it can run using + /// the ExecutionTimeLimit property. /// /// /// - /// + /// /// /// public sealed class IdleTrigger : Trigger @@ -552,30 +663,36 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public IdleTrigger() : base(TaskTriggerType.Idle) { } - internal IdleTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.OnIdle) { } + internal IdleTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnIdle) + { + } - internal IdleTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal IdleTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. - protected override string V2GetTriggerString() => winPEAS.Properties.Resources.TriggerIdle1; + protected override string V2GetTriggerString() => Properties.Resources.TriggerIdle1; } /// - /// Represents a trigger that starts a task when a user logs on. When the Task Scheduler service starts, all logged-on users are enumerated and any tasks - /// registered with logon triggers that match the logged on user are run. Not available on Task Scheduler 1.0. + /// Represents a trigger that starts a task when a user logs on. When the Task Scheduler service starts, all logged-on users are + /// enumerated and any tasks registered with logon triggers that match the logged on user are run. Not available on Task Scheduler 1.0. /// - /// A LogonTrigger will fire after a user logs on. It can only be delayed. Under V2, you can specify which user it applies to. + /// + /// A LogonTrigger will fire after a user logs on. It can only be delayed. Under V2, you can specify which user it applies to. + /// /// /// - /// + ///]]> /// /// public sealed class LogonTrigger : Trigger, ITriggerDelay, ITriggerUserId @@ -583,9 +700,13 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public LogonTrigger() : base(TaskTriggerType.Logon) { } - internal LogonTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.OnLogon) { } + internal LogonTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnLogon) + { + } - internal LogonTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal LogonTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started. /// Not supported under Task Scheduler 1.0. @@ -602,6 +723,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(Delay)] = value; + OnNotifyPropertyChanged(); } } @@ -612,9 +734,9 @@ namespace winPEAS.TaskScheduler /// • NULL: The task is started when any user logs on to the computer. /// /// - /// If you want a task to be triggered when any member of a group logs on to the computer rather than when a specific user logs on, then do not assign a - /// value to the LogonTrigger.UserId property. Instead, create a logon trigger with an empty LogonTrigger.UserId property and assign a value to the - /// principal for the task using the Principal.GroupId property. + /// If you want a task to be triggered when any member of a group logs on to the computer rather than when a specific user logs on, + /// then do not assign a value to the LogonTrigger.UserId property. Instead, create a logon trigger with an empty + /// LogonTrigger.UserId property and assign a value to the principal for the task using the Principal.GroupId property. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] @@ -630,6 +752,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(UserId)] = value; + OnNotifyPropertyChanged(); } } @@ -637,13 +760,14 @@ namespace winPEAS.TaskScheduler /// String describing the trigger. protected override string V2GetTriggerString() { - var user = string.IsNullOrEmpty(UserId) ? winPEAS.Properties.Resources.TriggerAnyUser : UserId; - return string.Format(winPEAS.Properties.Resources.TriggerLogon1, user); + var user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId; + return string.Format(Properties.Resources.TriggerLogon1, user); } } /// - /// Represents a trigger that starts a task on a monthly day-of-week schedule. For example, the task starts on every first Thursday, May through October. + /// Represents a trigger that starts a task on a monthly day-of-week schedule. For example, the task starts on every first Thursday, May + /// through October. /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class MonthlyDOWTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable @@ -659,7 +783,7 @@ namespace winPEAS.TaskScheduler WeeksOfMonth = weeksOfMonth; } - internal MonthlyDOWTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.RunMonthlyDOW) + internal MonthlyDOWTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunMonthlyDOW) { if (v1TriggerData.Data.monthlyDOW.Months == 0) v1TriggerData.Data.monthlyDOW.Months = MonthsOfTheYear.AllMonths; @@ -667,7 +791,9 @@ namespace winPEAS.TaskScheduler v1TriggerData.Data.monthlyDOW.DaysOfTheWeek = DaysOfTheWeek.Sunday; } - internal MonthlyDOWTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal MonthlyDOWTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets the days of the week during which the task runs. [DefaultValue(0)] @@ -688,6 +814,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(DaysOfWeek)] = (short)value; } + OnNotifyPropertyChanged(); } } @@ -710,6 +837,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(MonthsOfYear)] = (short)value; } + OnNotifyPropertyChanged(); } } @@ -728,6 +856,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; + OnNotifyPropertyChanged(); } } @@ -746,6 +875,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RunOnLastWeekOfMonth)] = value; + OnNotifyPropertyChanged(); } } @@ -767,8 +897,8 @@ namespace winPEAS.TaskScheduler } set { - // In Windows 10, the native library no longer acknowledges the LastWeek value and requires the RunOnLastWeekOfMonth to - // be expressly set. I think this is wrong so I am correcting their changed functionality. (thanks @SebastiaanPolfliet) + // In Windows 10, the native library no longer acknowledges the LastWeek value and requires the RunOnLastWeekOfMonth to be + // expressly set. I think this is wrong so I am correcting their changed functionality. (thanks @SebastiaanPolfliet) if (value.IsFlagSet(WhichWeek.LastWeek)) RunOnLastWeekOfMonth = true; if (v2Trigger != null) @@ -790,6 +920,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(WeeksOfMonth)] = (short)value; } + OnNotifyPropertyChanged(); } } @@ -802,8 +933,8 @@ namespace winPEAS.TaskScheduler } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -828,9 +959,9 @@ namespace winPEAS.TaskScheduler System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { CalendarTrigger.ReadXml(reader, this, ReadMyXml); } + void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { CalendarTrigger.WriteXml(writer, this, WriteMyXml); } + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => CalendarTrigger.WriteXml(writer, this, WriteMyXml); /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. @@ -839,7 +970,7 @@ namespace winPEAS.TaskScheduler var ww = TaskEnumGlobalizer.GetString(WeeksOfMonth); var days = TaskEnumGlobalizer.GetString(DaysOfWeek); var months = TaskEnumGlobalizer.GetString(MonthsOfYear); - return string.Format(winPEAS.Properties.Resources.TriggerMonthlyDOW1, AdjustToLocal(StartBoundary), ww, days, months); + return string.Format(Properties.Resources.TriggerMonthlyDOW1, AdjustToLocal(StartBoundary), ww, days, months); } /// Reads the subclass XML for V1 streams. @@ -862,27 +993,14 @@ namespace winPEAS.TaskScheduler WeeksOfMonth = WhichWeek.LastWeek; else { - switch (Int32.Parse(wk)) + WeeksOfMonth = (int.Parse(wk)) switch { - case 1: - WeeksOfMonth = WhichWeek.FirstWeek; - break; - - case 2: - WeeksOfMonth = WhichWeek.SecondWeek; - break; - - case 3: - WeeksOfMonth = WhichWeek.ThirdWeek; - break; - - case 4: - WeeksOfMonth = WhichWeek.FourthWeek; - break; - - default: - throw new System.Xml.XmlException("Week element must contain a 1-4 or Last as content."); - } + 1 => WhichWeek.FirstWeek, + 2 => WhichWeek.SecondWeek, + 3 => WhichWeek.ThirdWeek, + 4 => WhichWeek.FourthWeek, + _ => throw new System.Xml.XmlException("Week element must contain a 1-4 or Last as content."), + }; } } } @@ -968,14 +1086,16 @@ namespace winPEAS.TaskScheduler } } - /// Represents a trigger that starts a job based on a monthly schedule. For example, the task starts on specific days of specific months. + /// + /// Represents a trigger that starts a job based on a monthly schedule. For example, the task starts on specific days of specific months. + /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class MonthlyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable { /// Creates an unbound instance of a . /// - /// The day of the month. This must be a value between 1 and 32. If this value is set to 32, then the - /// value will be set and no days will be added regardless of the month. + /// The day of the month. This must be a value between 1 and 32. If this value is set to 32, then the value will be set and no days will be added regardless of the month. /// /// The months of the year. public MonthlyTrigger(int dayOfMonth = 1, MonthsOfTheYear monthsOfYear = MonthsOfTheYear.AllMonths) : base(TaskTriggerType.Monthly) @@ -992,7 +1112,7 @@ namespace winPEAS.TaskScheduler MonthsOfYear = monthsOfYear; } - internal MonthlyTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.RunMonthly) + internal MonthlyTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunMonthly) { if (v1TriggerData.Data.monthlyDate.Months == 0) v1TriggerData.Data.monthlyDate.Months = MonthsOfTheYear.AllMonths; @@ -1000,7 +1120,9 @@ namespace winPEAS.TaskScheduler v1TriggerData.Data.monthlyDate.Days = 1; } - internal MonthlyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal MonthlyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets the days of the month during which the task runs. public int[] DaysOfMonth @@ -1019,6 +1141,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(DaysOfMonth)] = mask; } + OnNotifyPropertyChanged(); } } @@ -1041,6 +1164,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(MonthsOfYear)] = (short)value; } + OnNotifyPropertyChanged(); } } @@ -1059,6 +1183,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; + OnNotifyPropertyChanged(); } } @@ -1077,6 +1202,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RunOnLastDayOfMonth)] = value; + OnNotifyPropertyChanged(); } } @@ -1089,8 +1215,8 @@ namespace winPEAS.TaskScheduler } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -1114,24 +1240,24 @@ namespace winPEAS.TaskScheduler System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { CalendarTrigger.ReadXml(reader, this, ReadMyXml); } + void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { CalendarTrigger.WriteXml(writer, this, WriteMyXml); } + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => CalendarTrigger.WriteXml(writer, this, WriteMyXml); /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() { - var days = string.Join(winPEAS.Properties.Resources.ListSeparator, Array.ConvertAll(DaysOfMonth, i => i.ToString())); + var days = string.Join(Properties.Resources.ListSeparator, Array.ConvertAll(DaysOfMonth, i => i.ToString())); if (RunOnLastDayOfMonth) - days += (days.Length == 0 ? "" : winPEAS.Properties.Resources.ListSeparator) + winPEAS.Properties.Resources.WWLastWeek; + days += (days.Length == 0 ? "" : Properties.Resources.ListSeparator) + Properties.Resources.WWLastWeek; var months = TaskEnumGlobalizer.GetString(MonthsOfYear); - return string.Format(winPEAS.Properties.Resources.TriggerMonthly1, AdjustToLocal(StartBoundary), days, months); + return string.Format(Properties.Resources.TriggerMonthly1, AdjustToLocal(StartBoundary), days, months); } /// - /// Converts an array of bit indices into a mask with bits turned ON at every index contained in the array. Indices must be from 1 to 32 and bits are - /// numbered the same. + /// Converts an array of bit indices into a mask with bits turned ON at every index contained in the array. Indices must be from 1 + /// to 32 and bits are numbered the same. /// /// An array with an element for each bit of the mask which is ON. /// An integer to be interpreted as a mask. @@ -1142,7 +1268,7 @@ namespace winPEAS.TaskScheduler foreach (var index in indices) { if (index < 1 || index > 31) throw new ArgumentException("Days must be in the range 1..31"); - mask = mask | 1 << (index - 1); + mask |= 1 << (index - 1); } return mask; } @@ -1165,8 +1291,8 @@ namespace winPEAS.TaskScheduler } /// - /// Convert an integer representing a mask to an array where each element contains the index of a bit that is ON in the mask. Bits are considered to - /// number from 1 to 32. + /// Convert an integer representing a mask to an array where each element contains the index of a bit that is ON in the mask. Bits + /// are considered to number from 1 to 32. /// /// An integer to be interpreted as a mask. /// An array with an element for each bit of the mask which is ON. @@ -1175,7 +1301,7 @@ namespace winPEAS.TaskScheduler //count bits in mask var cnt = 0; for (var i = 0; mask >> i > 0; i++) - cnt = cnt + (1 & (mask >> i)); + cnt += (1 & (mask >> i)); //allocate return array with one entry for each bit var indices = new int[cnt]; //fill array with bit indices @@ -1257,17 +1383,17 @@ namespace winPEAS.TaskScheduler } /// - /// Represents a trigger that starts a task when the task is registered or updated. Not available on Task Scheduler 1.0. Only available for Task - /// Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. + /// Represents a trigger that starts a task when the task is registered or updated. Not available on Task Scheduler 1.0. Only + /// available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. /// /// The RegistrationTrigger will fire after the task is registered (saved). It is advisable to put in a delay. /// /// - /// + ///]]> /// /// [XmlType(IncludeInSchema = false)] @@ -1276,7 +1402,9 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public RegistrationTrigger() : base(TaskTriggerType.Registration) { } - internal RegistrationTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal RegistrationTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started. /// Not supported under Task Scheduler 1.0. @@ -1293,19 +1421,20 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(Delay)] = value; + OnNotifyPropertyChanged(); } } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. - protected override string V2GetTriggerString() => winPEAS.Properties.Resources.TriggerRegistration1; + protected override string V2GetTriggerString() => Properties.Resources.TriggerRegistration1; } /// Defines how often the task is run and how long the repetition pattern is repeated after the task is started. /// This can be used directly or by assignment for a . /// /// - /// + ///]]> /// /// [XmlRoot("Repetition", Namespace = TaskDefinition.tns, IsNullable = true)] [TypeConverter(typeof(RepetitionPatternConverter))] - public sealed class RepetitionPattern : IDisposable, IXmlSerializable, IEquatable + public sealed class RepetitionPattern : IDisposable, IXmlSerializable, IEquatable, INotifyPropertyChanged { private readonly Trigger pTrigger; private readonly IRepetitionPattern v2Pattern; private TimeSpan unboundInterval = TimeSpan.Zero, unboundDuration = TimeSpan.Zero; private bool unboundStopAtDurationEnd; + /// Initializes a new instance of the class. + /// + /// The amount of time between each restart of the task. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// + /// + /// The duration of how long the pattern is repeated. The minimum time allowed is one minute. If TimeSpan.Zero is specified, + /// the pattern is repeated indefinitely. + /// + /// + /// If set to true the running instance of the task is stopped at the end of repetition pattern duration. + /// + public RepetitionPattern(TimeSpan interval, TimeSpan duration, bool stopAtDurationEnd = false) + { + Interval = interval; + Duration = duration; + StopAtDurationEnd = stopAtDurationEnd; + } + internal RepetitionPattern([NotNull] Trigger parent) { pTrigger = parent; @@ -1336,9 +1483,13 @@ namespace winPEAS.TaskScheduler v2Pattern = pTrigger.v2Trigger.Repetition; } + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + /// Gets or sets how long the pattern is repeated. /// - /// The duration that the pattern is repeated. The minimum time allowed is one minute. If TimeSpan.Zero is specified, the pattern is repeated indefinitely. + /// The duration that the pattern is repeated. The minimum time allowed is one minute. If TimeSpan.Zero is specified, the + /// pattern is repeated indefinitely. /// /// If you specify a repetition duration for a task, you must also specify the repetition interval. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -1362,13 +1513,18 @@ namespace winPEAS.TaskScheduler } else unboundDuration = value; + OnNotifyPropertyChanged(); } } /// Gets or sets the amount of time between each restart of the task. - /// The amount of time between each restart of the task. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// + /// The amount of time between each restart of the task. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// /// If you specify a repetition duration for a task, you must also specify the repetition interval. - /// The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// + /// The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// [DefaultValue(typeof(TimeSpan), "00:00:00")] public TimeSpan Interval { @@ -1392,10 +1548,13 @@ namespace winPEAS.TaskScheduler } else unboundInterval = value; + OnNotifyPropertyChanged(); } } - /// Gets or sets a Boolean value that indicates if a running instance of the task is stopped at the end of repetition pattern duration. + /// + /// Gets or sets a Boolean value that indicates if a running instance of the task is stopped at the end of repetition pattern duration. + /// [DefaultValue(false)] public bool StopAtDurationEnd { @@ -1404,7 +1563,7 @@ namespace winPEAS.TaskScheduler if (v2Pattern != null) return v2Pattern.StopAtDurationEnd; if (pTrigger != null) - return (pTrigger.v1TriggerData.Flags & TaskTriggerFlags.KillAtDurationEnd) == TaskTriggerFlags.KillAtDurationEnd; + return (pTrigger.v1TriggerData.Flags & V1Interop.TaskTriggerFlags.KillAtDurationEnd) == V1Interop.TaskTriggerFlags.KillAtDurationEnd; return unboundStopAtDurationEnd; } set @@ -1414,13 +1573,14 @@ namespace winPEAS.TaskScheduler else if (pTrigger != null) { if (value) - pTrigger.v1TriggerData.Flags |= TaskTriggerFlags.KillAtDurationEnd; + pTrigger.v1TriggerData.Flags |= V1Interop.TaskTriggerFlags.KillAtDurationEnd; else - pTrigger.v1TriggerData.Flags &= ~TaskTriggerFlags.KillAtDurationEnd; + pTrigger.v1TriggerData.Flags &= ~V1Interop.TaskTriggerFlags.KillAtDurationEnd; Bind(); } else unboundStopAtDurationEnd = value; + OnNotifyPropertyChanged(); } } @@ -1430,9 +1590,9 @@ namespace winPEAS.TaskScheduler if (v2Pattern != null) Marshal.ReleaseComObject(v2Pattern); } - /// 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. + /// 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. // ReSharper disable once BaseObjectEqualsIsObjectEquals public override bool Equals(object obj) => obj is RepetitionPattern pattern ? Equals(pattern) : base.Equals(obj); @@ -1445,6 +1605,17 @@ namespace winPEAS.TaskScheduler /// 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 = Duration, B = Interval, C = StopAtDurationEnd }.GetHashCode(); + /// Determines whether any properties for this have been set. + /// true if properties have been set; otherwise, false. + public bool IsSet() + { + if (v2Pattern != null) + return v2Pattern.StopAtDurationEnd || !string.IsNullOrEmpty(v2Pattern.Duration) || !string.IsNullOrEmpty(v2Pattern.Interval); + if (pTrigger != null) + return (pTrigger.v1TriggerData.Flags & V1Interop.TaskTriggerFlags.KillAtDurationEnd) == V1Interop.TaskTriggerFlags.KillAtDurationEnd || pTrigger.v1TriggerData.MinutesDuration > 0 || pTrigger.v1TriggerData.MinutesInterval > 0; + return false; + } + System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) @@ -1459,7 +1630,7 @@ namespace winPEAS.TaskScheduler reader.Skip(); } - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { XmlSerializationHelper.WriteObjectProperties(writer, this); } + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this); internal void Bind() { @@ -1471,7 +1642,7 @@ namespace winPEAS.TaskScheduler v2Pattern.Interval = $"PT{pTrigger.v1TriggerData.MinutesInterval}M"; if (pTrigger.v1TriggerData.MinutesDuration != 0) v2Pattern.Duration = $"PT{pTrigger.v1TriggerData.MinutesDuration}M"; - v2Pattern.StopAtDurationEnd = (pTrigger.v1TriggerData.Flags & TaskTriggerFlags.KillAtDurationEnd) == TaskTriggerFlags.KillAtDurationEnd; + v2Pattern.StopAtDurationEnd = (pTrigger.v1TriggerData.Flags & V1Interop.TaskTriggerFlags.KillAtDurationEnd) == V1Interop.TaskTriggerFlags.KillAtDurationEnd; } } @@ -1482,7 +1653,11 @@ namespace winPEAS.TaskScheduler StopAtDurationEnd = value.StopAtDurationEnd; } - private bool ReadXmlConverter(System.Reflection.PropertyInfo pi, Object obj, ref Object value) + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + private bool ReadXmlConverter(System.Reflection.PropertyInfo pi, object obj, ref object value) { if (pi.Name != "Interval" || !(value is TimeSpan span) || span.Equals(TimeSpan.Zero) || Duration > span) return false; @@ -1492,22 +1667,23 @@ namespace winPEAS.TaskScheduler } /// - /// Triggers tasks for console connect or disconnect, remote connect or disconnect, or workstation lock or unlock notifications. Only available for - /// Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. + /// Triggers tasks for console connect or disconnect, remote connect or disconnect, or workstation lock or unlock notifications. + /// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later. /// /// - /// The SessionStateChangeTrigger will fire after six different system events: connecting or disconnecting locally or remotely, or locking or unlocking the session. + /// The SessionStateChangeTrigger will fire after six different system events: connecting or disconnecting locally or remotely, or + /// locking or unlocking the session. /// /// /// - /// + /// /// /// [XmlType(IncludeInSchema = false)] @@ -1516,7 +1692,14 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public SessionStateChangeTrigger() : base(TaskTriggerType.SessionStateChange) { } - internal SessionStateChangeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + /// Initializes a new instance of the class. + /// The state change. + /// The user identifier. + public SessionStateChangeTrigger(TaskSessionStateChangeType stateChange, string userId = null) : this() { StateChange = stateChange; UserId = userId; } + + internal SessionStateChangeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -1529,6 +1712,7 @@ namespace winPEAS.TaskScheduler ((ISessionStateChangeTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else unboundValues[nameof(Delay)] = value; + OnNotifyPropertyChanged(); } } @@ -1543,10 +1727,13 @@ namespace winPEAS.TaskScheduler ((ISessionStateChangeTrigger)v2Trigger).StateChange = value; else unboundValues[nameof(StateChange)] = value; + OnNotifyPropertyChanged(); } } - /// Gets or sets the user for the Terminal Server session. When a session state change is detected for this user, a task is started. + /// + /// Gets or sets the user for the Terminal Server session. When a session state change is detected for this user, a task is started. + /// [DefaultValue(null)] public string UserId { @@ -1557,12 +1744,13 @@ namespace winPEAS.TaskScheduler ((ISessionStateChangeTrigger)v2Trigger).UserId = value; else unboundValues[nameof(UserId)] = value; + OnNotifyPropertyChanged(); } } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -1581,10 +1769,10 @@ namespace winPEAS.TaskScheduler /// String describing the trigger. protected override string V2GetTriggerString() { - var str = winPEAS.Properties.Resources.ResourceManager.GetString("TriggerSession" + StateChange.ToString()); - var user = string.IsNullOrEmpty(UserId) ? winPEAS.Properties.Resources.TriggerAnyUser : UserId; + var str = Properties.Resources.ResourceManager.GetString("TriggerSession" + StateChange.ToString()); + var user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId; if (StateChange != TaskSessionStateChangeType.SessionLock && StateChange != TaskSessionStateChangeType.SessionUnlock) - user = string.Format(winPEAS.Properties.Resources.TriggerSessionUserSession, user); + user = string.Format(Properties.Resources.TriggerSessionUserSession, user); return string.Format(str, user); } @@ -1597,11 +1785,11 @@ namespace winPEAS.TaskScheduler /// A TimeTrigger runs at a specified date and time. /// /// - /// + ///]]> /// /// public sealed class TimeTrigger : Trigger, ITriggerDelay, ICalendarTrigger @@ -1609,9 +1797,17 @@ namespace winPEAS.TaskScheduler /// Creates an unbound instance of a . public TimeTrigger() : base(TaskTriggerType.Time) { } - internal TimeTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.RunOnce) { } + /// Creates an unbound instance of a and assigns the execution time. + /// Date and time for the trigger to fire. + public TimeTrigger(DateTime startBoundary) : base(TaskTriggerType.Time) => StartBoundary = startBoundary; - internal TimeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal TimeTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunOnce) + { + } + + internal TimeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets a delay time that is randomly added to the start time of the trigger. /// Not supported under Task Scheduler 1.0. @@ -1628,6 +1824,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; + OnNotifyPropertyChanged(); } } @@ -1641,29 +1838,31 @@ namespace winPEAS.TaskScheduler /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. - protected override string V2GetTriggerString() => string.Format(winPEAS.Properties.Resources.TriggerTime1, AdjustToLocal(StartBoundary)); + protected override string V2GetTriggerString() => string.Format(Properties.Resources.TriggerTime1, AdjustToLocal(StartBoundary)); } /// - /// Abstract base class which provides the common properties that are inherited by all trigger classes. A trigger can be created using the - /// or the method. + /// Abstract base class which provides the common properties that are inherited by all trigger classes. A trigger can be created using + /// the or the method. /// - public abstract partial class Trigger : IDisposable, ICloneable, IEquatable, IComparable, IComparable + public abstract partial class Trigger : IDisposable, ICloneable, IEquatable, IComparable, IComparable, INotifyPropertyChanged { internal const string V2BoundaryDateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFK"; internal static readonly CultureInfo DefaultDateCulture = CultureInfo.CreateSpecificCulture("en-US"); - internal ITaskTrigger v1Trigger; - internal TaskTrigger v1TriggerData; + internal V1Interop.ITaskTrigger v1Trigger; + internal V1Interop.TaskTrigger v1TriggerData; internal ITrigger v2Trigger; + /// In testing and may change. Do not use until officially introduced into library. protected Dictionary unboundValues = new Dictionary(); + private static bool? foundTimeSpan2; private static Type timeSpan2Type; private readonly TaskTriggerType ttype; private RepetitionPattern repititionPattern; - internal Trigger([NotNull] ITaskTrigger trigger, V1.TaskTriggerType type) + internal Trigger([NotNull] V1Interop.ITaskTrigger trigger, V1Interop.TaskTriggerType type) { v1Trigger = trigger; v1TriggerData = trigger.GetTrigger(); @@ -1683,7 +1882,7 @@ namespace winPEAS.TaskScheduler { ttype = triggerType; - v1TriggerData.TriggerSize = (ushort)Marshal.SizeOf(typeof(TaskTrigger)); + v1TriggerData.TriggerSize = (ushort)Marshal.SizeOf(typeof(V1Interop.TaskTrigger)); if (ttype != TaskTriggerType.Registration && ttype != TaskTriggerType.Event && ttype != TaskTriggerType.SessionStateChange) v1TriggerData.Type = ConvertToV1TriggerType(ttype); @@ -1691,22 +1890,26 @@ namespace winPEAS.TaskScheduler StartBoundary = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); } + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + /// Gets or sets a Boolean value that indicates whether the trigger is enabled. public bool Enabled { - get => v2Trigger?.Enabled ?? GetUnboundValueOrDefault(nameof(Enabled), !v1TriggerData.Flags.IsFlagSet(TaskTriggerFlags.Disabled)); + get => v2Trigger?.Enabled ?? GetUnboundValueOrDefault(nameof(Enabled), !v1TriggerData.Flags.IsFlagSet(V1Interop.TaskTriggerFlags.Disabled)); set { if (v2Trigger != null) v2Trigger.Enabled = value; else { - v1TriggerData.Flags = v1TriggerData.Flags.SetFlags(TaskTriggerFlags.Disabled, !value); + v1TriggerData.Flags = v1TriggerData.Flags.SetFlags(V1Interop.TaskTriggerFlags.Disabled, !value); if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(Enabled)] = value; } + OnNotifyPropertyChanged(); } } @@ -1742,7 +1945,7 @@ namespace winPEAS.TaskScheduler if (v2Trigger != null) { if (value <= StartBoundary) - throw new ArgumentException(winPEAS.Properties.Resources.Error_TriggerEndBeforeStart); + throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); v2Trigger.EndBoundary = value == DateTime.MaxValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture); } else @@ -1753,11 +1956,13 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(EndBoundary)] = value; } + OnNotifyPropertyChanged(); } } /// - /// Gets or sets the maximum amount of time that the task launched by this trigger is allowed to run. Not available with Task Scheduler 1.0. + /// Gets or sets the maximum amount of time that the task launched by this trigger is allowed to run. Not available with Task + /// Scheduler 1.0. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] @@ -1773,6 +1978,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(ExecutionTimeLimit)] = value; + OnNotifyPropertyChanged(); } } @@ -1791,39 +1997,51 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(Id)] = value; + OnNotifyPropertyChanged(); } } /// - /// Gets a instance that indicates how often the task is run and how long the repetition pattern is repeated after the - /// task is started. + /// Gets a instance that indicates how often the task is run and how long the repetition pattern is + /// repeated after the task is started. /// public RepetitionPattern Repetition { - get => repititionPattern ?? (repititionPattern = new RepetitionPattern(this)); - set => Repetition.Set(value); + get => repititionPattern ??= new RepetitionPattern(this); + set + { + Repetition.Set(value); + OnNotifyPropertyChanged(); + } } /// Gets or sets the date and time when the trigger is activated. /// /// - /// Version 1 (1.1 on all systems prior to Vista) of the native library only allows for values where the - /// is unspecified. If the DateTime value Kind is then it will be used as is. If the - /// DateTime value Kind is then it will be converted to the local time and then used. + /// Version 1 (1.1 on all systems prior to Vista) of the native library only allows for values where the is unspecified. If the DateTime value Kind is then it will be used as + /// is. If the DateTime value Kind is then it will be converted to the local time and then used. /// /// - /// Version 2 (1.2 or higher) of the native library only allows for all values. However, the user interface and - /// methods will always show the time translated to local time. The library makes every attempt to maintain the Kind - /// value. When using the UI elements provided in the TaskSchedulerEditor library, the "Synchronize across time zones" checkbox will be checked if the - /// Kind is Local or Utc. If the Kind is Unspecified and the user selects the checkbox, the Kind will be changed to Utc and the time adjusted from the - /// value displayed as the local time. + /// Version 2 (1.2 or higher) of the native library only allows for all values. However, the user + /// interface and methods will always show the time translated to local time. The library makes + /// every attempt to maintain the Kind value. When using the UI elements provided in the TaskSchedulerEditor library, the + /// "Synchronize across time zones" checkbox will be checked if the Kind is Local or Utc. If the Kind is Unspecified and the user + /// selects the checkbox, the Kind will be changed to Utc and the time adjusted from the value displayed as the local time. /// /// - /// Under Version 2, when converting the string used in the native library for this value (ITrigger.Startboundary) this library will behave as follows: + /// Under Version 2, when converting the string used in the native library for this value (ITrigger.Startboundary) this library will + /// behave as follows: /// - /// YYYY-MM-DDTHH:MM:SS format uses DateTimeKind.Unspecified and the time specified. - /// YYYY-MM-DDTHH:MM:SSZ format uses DateTimeKind.Utc and the time specified as the GMT time. - /// YYYY-MM-DDTHH:MM:SS±HH:MM format uses DateTimeKind.Local and the time specified in that time zone. + /// + /// YYYY-MM-DDTHH:MM:SS format uses DateTimeKind.Unspecified and the time specified. + /// + /// + /// YYYY-MM-DDTHH:MM:SSZ format uses DateTimeKind.Utc and the time specified as the GMT time. + /// + /// + /// YYYY-MM-DDTHH:MM:SS±HH:MM format uses DateTimeKind.Local and the time specified in that time zone. + /// /// /// /// @@ -1844,7 +2062,7 @@ namespace winPEAS.TaskScheduler if (v2Trigger != null) { if (value > EndBoundary) - throw new ArgumentException(winPEAS.Properties.Resources.Error_TriggerEndBeforeStart); + throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); v2Trigger.StartBoundary = value == DateTime.MinValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture); } else @@ -1855,6 +2073,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(StartBoundary)] = value; } + OnNotifyPropertyChanged(); } } @@ -1922,16 +2141,16 @@ namespace winPEAS.TaskScheduler } /// - /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, - /// follows, or occurs in the same position in the sort order as the other object. + /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current + /// instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// A value that indicates the relative order of the objects being compared. public int CompareTo(Trigger other) => string.Compare(Id, other?.Id, StringComparison.InvariantCulture); /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public virtual void CopyProperties(Trigger sourceTrigger) @@ -1964,9 +2183,9 @@ namespace winPEAS.TaskScheduler Marshal.ReleaseComObject(v1Trigger); } - /// 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. + /// 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. // ReSharper disable once BaseObjectEqualsIsObjectEquals public override bool Equals(object obj) => obj is Trigger trigger ? Equals(trigger) : base.Equals(obj); @@ -2003,65 +2222,67 @@ namespace winPEAS.TaskScheduler I = (this as ITriggerUserId)?.UserId }.GetHashCode(); + /// Sets the repetition. + /// + /// The amount of time between each restart of the task. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. + /// + /// + /// The duration of how long the pattern is repeated. The minimum time allowed is one minute. If TimeSpan.Zero is specified, + /// the pattern is repeated indefinitely. + /// + /// + /// if set to true the running instance of the task is stopped at the end of repetition pattern duration. + /// + [Obsolete("Set the Repetition property directly with a new instance of RepetitionPattern.", false)] + public void SetRepetition(TimeSpan interval, TimeSpan duration, bool stopAtDurationEnd = true) + { + Repetition.Duration = duration; + Repetition.Interval = interval; + Repetition.StopAtDurationEnd = stopAtDurationEnd; + } + /// Returns a string representing this trigger. /// String value of trigger. public override string ToString() => v1Trigger != null ? v1Trigger.GetTriggerString() : V2GetTriggerString() + V2BaseTriggerString(); + /// Returns a that represents this trigger in a specific language. + /// The language of the resulting string. + /// String value of trigger. + public virtual string ToString([NotNull] CultureInfo culture) + { + using (new CultureSwitcher(culture)) + return ToString(); + } + int IComparable.CompareTo(object obj) => CompareTo(obj as Trigger); internal static DateTime AdjustToLocal(DateTime dt) => dt.Kind == DateTimeKind.Utc ? dt.ToLocalTime() : dt; - internal static V1.TaskTriggerType ConvertToV1TriggerType(TaskTriggerType type) + internal static V1Interop.TaskTriggerType ConvertToV1TriggerType(TaskTriggerType type) { if (type == TaskTriggerType.Registration || type == TaskTriggerType.Event || type == TaskTriggerType.SessionStateChange) throw new NotV1SupportedException(); var tv1 = (int)type - 1; if (tv1 >= 7) tv1--; - return (V1.TaskTriggerType)tv1; + return (V1Interop.TaskTriggerType)tv1; } - internal static Trigger CreateTrigger([NotNull] ITaskTrigger trigger) => CreateTrigger(trigger, trigger.GetTrigger().Type); + internal static Trigger CreateTrigger([NotNull] V1Interop.ITaskTrigger trigger) => CreateTrigger(trigger, trigger.GetTrigger().Type); - internal static Trigger CreateTrigger([NotNull] ITaskTrigger trigger, V1.TaskTriggerType triggerType) + internal static Trigger CreateTrigger([NotNull] V1Interop.ITaskTrigger trigger, V1Interop.TaskTriggerType triggerType) { - Trigger t; - switch (triggerType) + Trigger t = triggerType switch { - case V1.TaskTriggerType.RunOnce: - t = new TimeTrigger(trigger); - break; - - case V1.TaskTriggerType.RunDaily: - t = new DailyTrigger(trigger); - break; - - case V1.TaskTriggerType.RunWeekly: - t = new WeeklyTrigger(trigger); - break; - - case V1.TaskTriggerType.RunMonthly: - t = new MonthlyTrigger(trigger); - break; - - case V1.TaskTriggerType.RunMonthlyDOW: - t = new MonthlyDOWTrigger(trigger); - break; - - case V1.TaskTriggerType.OnIdle: - t = new IdleTrigger(trigger); - break; - - case V1.TaskTriggerType.OnSystemStart: - t = new BootTrigger(trigger); - break; - - case V1.TaskTriggerType.OnLogon: - t = new LogonTrigger(trigger); - break; - - default: - throw new ArgumentOutOfRangeException(nameof(triggerType), triggerType, null); - } + V1Interop.TaskTriggerType.RunOnce => new TimeTrigger(trigger), + V1Interop.TaskTriggerType.RunDaily => new DailyTrigger(trigger), + V1Interop.TaskTriggerType.RunWeekly => new WeeklyTrigger(trigger), + V1Interop.TaskTriggerType.RunMonthly => new MonthlyTrigger(trigger), + V1Interop.TaskTriggerType.RunMonthlyDOW => new MonthlyDOWTrigger(trigger), + V1Interop.TaskTriggerType.OnIdle => new IdleTrigger(trigger), + V1Interop.TaskTriggerType.OnSystemStart => new BootTrigger(trigger), + V1Interop.TaskTriggerType.OnLogon => new LogonTrigger(trigger), + _ => throw new ArgumentOutOfRangeException(nameof(triggerType), triggerType, null), + }; return t; } @@ -2144,7 +2365,7 @@ namespace winPEAS.TaskScheduler return span.ToString(); } - internal virtual void Bind([NotNull] ITask iTask) + internal virtual void Bind([NotNull] V1Interop.ITask iTask) { if (v1Trigger == null) { @@ -2159,7 +2380,7 @@ namespace winPEAS.TaskScheduler v2Trigger = iTriggers.Create(ttype); Marshal.ReleaseComObject(iTriggers); if ((unboundValues.TryGetValue("StartBoundary", out var dt) ? (DateTime)dt : StartBoundary) > (unboundValues.TryGetValue("EndBoundary", out dt) ? (DateTime)dt : EndBoundary)) - throw new ArgumentException(winPEAS.Properties.Resources.Error_TriggerEndBeforeStart); + throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); foreach (var key in unboundValues.Keys) { try @@ -2184,7 +2405,7 @@ namespace winPEAS.TaskScheduler if (v1TriggerData.MinutesInterval != 0 && v1TriggerData.MinutesInterval >= v1TriggerData.MinutesDuration) throw new ArgumentException("Trigger.Repetition.Interval must be less than Trigger.Repetition.Duration under Task Scheduler 1.0."); if (v1TriggerData.EndDate <= v1TriggerData.BeginDate) - throw new ArgumentException(winPEAS.Properties.Resources.Error_TriggerEndBeforeStart); + throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); if (v1TriggerData.BeginDate == DateTime.MinValue) v1TriggerData.BeginDate = DateTime.Now; v1Trigger?.SetTrigger(ref v1TriggerData); @@ -2214,11 +2435,15 @@ namespace winPEAS.TaskScheduler /// The unbound value, if set, or the default value. protected T GetUnboundValueOrDefault(string prop, T def = default) => unboundValues.TryGetValue(prop, out var val) ? (T)val : def; + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected virtual string V2GetTriggerString() => string.Empty; - private static TaskTriggerType ConvertFromV1TriggerType(V1.TaskTriggerType v1Type) + private static TaskTriggerType ConvertFromV1TriggerType(V1Interop.TaskTriggerType v1Type) { var tv2 = (int)v1Type + 1; if (tv2 > 6) tv2++; @@ -2230,31 +2455,31 @@ namespace winPEAS.TaskScheduler var ret = new StringBuilder(); if (Repetition.Interval != TimeSpan.Zero) { - var sduration = Repetition.Duration == TimeSpan.Zero ? winPEAS.Properties.Resources.TriggerDuration0 : string.Format(winPEAS.Properties.Resources.TriggerDurationNot0, GetBestTimeSpanString(Repetition.Duration)); - ret.AppendFormat(winPEAS.Properties.Resources.TriggerRepetition, GetBestTimeSpanString(Repetition.Interval), sduration); + var sduration = Repetition.Duration == TimeSpan.Zero ? Properties.Resources.TriggerDuration0 : string.Format(Properties.Resources.TriggerDurationNot0, GetBestTimeSpanString(Repetition.Duration)); + ret.AppendFormat(Properties.Resources.TriggerRepetition, GetBestTimeSpanString(Repetition.Interval), sduration); } if (EndBoundary != DateTime.MaxValue) - ret.AppendFormat(winPEAS.Properties.Resources.TriggerEndBoundary, AdjustToLocal(EndBoundary)); + ret.AppendFormat(Properties.Resources.TriggerEndBoundary, AdjustToLocal(EndBoundary)); if (ret.Length > 0) - ret.Insert(0, winPEAS.Properties.Resources.HyphenSeparator); + ret.Insert(0, Properties.Resources.HyphenSeparator); return ret.ToString(); } } /// - /// Represents a trigger that starts a task based on a weekly schedule. For example, the task starts at 8:00 A.M. on a specific day of the week every week or - /// every other week. + /// Represents a trigger that starts a task based on a weekly schedule. For example, the task starts at 8:00 A.M. on a specific day of + /// the week every week or every other week. /// /// A WeeklyTrigger runs at a specified time on specified days of the week every week or interval of weeks. /// /// - /// + ///]]> /// /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] @@ -2269,7 +2494,7 @@ namespace winPEAS.TaskScheduler WeeksInterval = weeksInterval; } - internal WeeklyTrigger([NotNull] ITaskTrigger iTrigger) : base(iTrigger, V1.TaskTriggerType.RunWeekly) + internal WeeklyTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunWeekly) { if (v1TriggerData.Data.weekly.DaysOfTheWeek == 0) v1TriggerData.Data.weekly.DaysOfTheWeek = DaysOfTheWeek.Sunday; @@ -2277,7 +2502,9 @@ namespace winPEAS.TaskScheduler v1TriggerData.Data.weekly.WeeksInterval = 1; } - internal WeeklyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } + internal WeeklyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) + { + } /// Gets or sets the days of the week on which the task runs. [DefaultValue(0)] @@ -2298,6 +2525,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(DaysOfWeek)] = (short)value; } + OnNotifyPropertyChanged(); } } @@ -2316,6 +2544,7 @@ namespace winPEAS.TaskScheduler throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; + OnNotifyPropertyChanged(); } } @@ -2336,6 +2565,7 @@ namespace winPEAS.TaskScheduler else unboundValues[nameof(WeeksInterval)] = value; } + OnNotifyPropertyChanged(); } } @@ -2348,8 +2578,8 @@ namespace winPEAS.TaskScheduler } /// - /// Copies the properties from another the current instance. This will not copy any properties associated with any derived triggers - /// except those supporting the interface. + /// Copies the properties from another the current instance. This will not copy any properties associated with + /// any derived triggers except those supporting the interface. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) @@ -2369,16 +2599,16 @@ namespace winPEAS.TaskScheduler System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; - void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { CalendarTrigger.ReadXml(reader, this, ReadMyXml); } + void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); - void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { CalendarTrigger.WriteXml(writer, this, WriteMyXml); } + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => CalendarTrigger.WriteXml(writer, this, WriteMyXml); /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() { var days = TaskEnumGlobalizer.GetString(DaysOfWeek); - return string.Format(WeeksInterval == 1 ? winPEAS.Properties.Resources.TriggerWeekly1Week : winPEAS.Properties.Resources.TriggerWeeklyMultWeeks, AdjustToLocal(StartBoundary), days, WeeksInterval); + return string.Format(WeeksInterval == 1 ? Properties.Resources.TriggerWeekly1Week : Properties.Resources.TriggerWeeklyMultWeeks, AdjustToLocal(StartBoundary), days, WeeksInterval); } /// Reads the subclass XML for V1 streams. @@ -2484,13 +2714,9 @@ namespace winPEAS.TaskScheduler if (t != null) { - using (var ms = new System.IO.StringReader(xml)) - { - using (var iReader = System.Xml.XmlReader.Create(ms)) - { - ((IXmlSerializable)t).ReadXml(iReader); - } - } + using var ms = new System.IO.StringReader(xml); + using var iReader = System.Xml.XmlReader.Create(ms); + ((IXmlSerializable)t).ReadXml(iReader); } } return t; @@ -2541,5 +2767,15 @@ namespace winPEAS.TaskScheduler internal sealed class RepetitionPatternConverter : TypeConverter { + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(string) || base.CanConvertTo(context, destinationType); + + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + var rp = (RepetitionPattern)value; + if (destinationType != typeof(string)) return base.ConvertTo(context, culture, value, destinationType); + if (rp.Interval == TimeSpan.Zero) return ""; + var sduration = rp.Duration == TimeSpan.Zero ? Properties.Resources.TriggerDuration0 : string.Format(Properties.Resources.TriggerDurationNot0Short, Trigger.GetBestTimeSpanString(rp.Duration)); + return string.Format(Properties.Resources.TriggerRepetitionShort, Trigger.GetBestTimeSpanString(rp.Interval), sduration); + } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TriggerCollection.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TriggerCollection.cs index b04f286..8b88ca9 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/TriggerCollection.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/TriggerCollection.cs @@ -1,60 +1,105 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; using System.Xml.Serialization; -using winPEAS.TaskScheduler.Native; -using winPEAS.TaskScheduler.V1; -using winPEAS.TaskScheduler.V2; +using winPEAS.TaskScheduler.TaskEditor.Native; +using winPEAS.TaskScheduler.V1Interop; namespace winPEAS.TaskScheduler { - /// - /// Provides the methods that are used to add to, remove from, and get the triggers of a task. - /// [XmlRoot("Triggers", Namespace = TaskDefinition.tns, IsNullable = false)] - public sealed class TriggerCollection : IList, IDisposable, IXmlSerializable, IList + public sealed class TriggerCollection : IList, IDisposable, IXmlSerializable, IList, INotifyCollectionChanged, INotifyPropertyChanged { - private ITask v1Task; - private readonly ITriggerCollection v2Coll; - private ITaskDefinition v2Def; + private const string IndexerName = "Item[]"; + private readonly V2Interop.ITriggerCollection v2Coll; + private bool inV2set; + private V1Interop.ITask v1Task; + private V2Interop.ITaskDefinition v2Def; - internal TriggerCollection([NotNull] ITask iTask) - { - v1Task = iTask; - } + internal TriggerCollection([NotNull] V1Interop.ITask iTask) => v1Task = iTask; - internal TriggerCollection([NotNull] ITaskDefinition iTaskDef) + internal TriggerCollection([NotNull] V2Interop.ITaskDefinition iTaskDef) { v2Def = iTaskDef; v2Coll = v2Def.Triggers; } - /// - /// Gets the number of triggers in the collection. - /// + /// Occurs when a collection changes. + public event NotifyCollectionChangedEventHandler CollectionChanged; + + /// Occurs when a property value changes. + public event PropertyChangedEventHandler PropertyChanged; + + /// Gets the number of triggers in the collection. public int Count => v2Coll?.Count ?? v1Task.GetTriggerCount(); + bool IList.IsFixedSize => false; + + bool ICollection.IsReadOnly => false; + + bool IList.IsReadOnly => false; + bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; - bool ICollection.IsReadOnly => false; - - bool IList.IsFixedSize => false; - - bool IList.IsReadOnly => false; - - object IList.this[int index] + /// Gets or sets a specified trigger from the collection. + /// The . + /// The id ( ) of the trigger to be retrieved. + /// Specialized instance. + /// + /// + /// + /// Mismatching Id for trigger and lookup. + public Trigger this[[NotNull] string triggerId] { - get { return this[index]; } - set { this[index] = (Trigger)value; } + get + { + if (string.IsNullOrEmpty(triggerId)) + throw new ArgumentNullException(nameof(triggerId)); + foreach (var t in this) + if (string.Equals(t.Id, triggerId)) + return t; + throw new ArgumentOutOfRangeException(nameof(triggerId)); + } + set + { + if (value == null) + throw new NullReferenceException(); + if (string.IsNullOrEmpty(triggerId)) + throw new ArgumentNullException(nameof(triggerId)); + if (triggerId != value.Id) + throw new InvalidOperationException("Mismatching Id for trigger and lookup."); + var index = IndexOf(triggerId); + if (index >= 0) + { + var orig = this[index].Clone(); + inV2set = true; + try + { + RemoveAt(index); + Insert(index, value); + } + finally + { + inV2set = true; + } + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index)); + } + else + Add(value); + } } - - /// - /// Gets a specified trigger from the collection. - /// + /// Gets a specified trigger from the collection. /// The index of the trigger to be retrieved. /// Specialized instance. public Trigger this[int index] @@ -67,21 +112,35 @@ namespace winPEAS.TaskScheduler } set { - if (Count <= index) + if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(nameof(index), index, @"Index is not a valid index in the TriggerCollection"); - Insert(index, value); - RemoveAt(index + 1); + var orig = this[index].Clone(); + inV2set = true; + try + { + Insert(index, value); + RemoveAt(index + 1); + } + finally + { + inV2set = false; + } + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index)); } } - /// - /// Add an unbound to the task. - /// - /// A type derived from . - /// derivative to add to the task. - /// Bound trigger. - /// unboundTrigger is null. - public TTrigger Add([NotNull] TTrigger unboundTrigger) where TTrigger : Trigger + object IList.this[int index] + { + get => this[index]; + set => this[index] = (Trigger)value; + } + + /*/// + /// Add an unbound to the task. derivative to + /// add to the task. Bound trigger. unboundTrigger + /// is null. + public Trigger Add([NotNull] Trigger unboundTrigger) { if (unboundTrigger == null) throw new ArgumentNullException(nameof(unboundTrigger)); @@ -90,64 +149,112 @@ namespace winPEAS.TaskScheduler else unboundTrigger.Bind(v1Task); return unboundTrigger; + }*/ + + /// Add an unbound to the task. + /// A type derived from . + /// derivative to add to the task. + /// Bound trigger. + /// unboundTrigger is null. + public TTrigger Add([NotNull] TTrigger unboundTrigger) where TTrigger : Trigger + { + if (unboundTrigger == null) + throw new ArgumentNullException(nameof(unboundTrigger)); + if (v2Def != null) + unboundTrigger.Bind(v2Def); + else + unboundTrigger.Bind(v1Task); + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, unboundTrigger)); + return unboundTrigger; } - /// - /// Add a new trigger to the collections of triggers for the task. - /// + /// Add a new trigger to the collections of triggers for the task. /// The type of trigger to create. /// A instance of the specified type. public Trigger AddNew(TaskTriggerType taskTriggerType) { if (v1Task != null) - { - ushort idx; - return Trigger.CreateTrigger(v1Task.CreateTrigger(out idx), Trigger.ConvertToV1TriggerType(taskTriggerType)); - } + return Trigger.CreateTrigger(v1Task.CreateTrigger(out _), Trigger.ConvertToV1TriggerType(taskTriggerType)); return Trigger.CreateTrigger(v2Coll.Create(taskTriggerType), v2Def); } - /// - /// Clears all triggers from the task. - /// + /// Adds a collection of unbound triggers to the end of the . + /// + /// The triggers to be added to the end of the . The collection itself cannot be null and + /// cannot contain null elements. + /// + /// is null. + public void AddRange([NotNull] IEnumerable triggers) + { + if (triggers == null) + throw new ArgumentNullException(nameof(triggers)); + foreach (var item in triggers) + Add(item); + } + + /// Clears all triggers from the task. public void Clear() { if (v2Coll != null) v2Coll.Clear(); else { - for (int i = Count - 1; i >= 0; i--) - RemoveAt(i); + inV2set = true; + try + { + for (var i = Count - 1; i >= 0; i--) + RemoveAt(i); + } + finally + { + inV2set = false; + } } + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } - /// - /// Determines whether the contains a specific value. - /// - /// The object to locate in the . - /// - /// true if is found in the ; otherwise, false. - /// + /// Determines whether the contains a specific value. + /// The object to locate in the . + /// true if is found in the ; otherwise, false. public bool Contains([NotNull] Trigger item) => Find(a => a.Equals(item)) != null; - /// - /// Copies the elements of the to an , starting at a particular index. - /// - /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. - /// The zero-based index in at which copying begins. - public void CopyTo(Trigger[] array, int arrayIndex) { CopyTo(0, array, arrayIndex, Count); } + /// Determines whether the specified trigger type is contained in this collection. + /// Type of the trigger. + /// true if the specified trigger type is contained in this collection; otherwise, false. + public bool ContainsType(Type triggerType) => Find(a => a.GetType() == triggerType) != null; /// - /// Copies the elements of the to a array, starting at a particular array index. + /// Copies the elements of the to an , starting at a particular index. + /// + /// + /// The one-dimensional that is the destination of the elements copied from . The + /// must have zero-based indexing. + /// + /// The zero-based index in at which copying begins. + public void CopyTo(Trigger[] array, int arrayIndex) => CopyTo(0, array, arrayIndex, Count); + + /// + /// Copies the elements of the to a array, starting at a particular array index. /// /// The zero-based index in the source at which copying begins. - /// The array that is the destination of the elements copied from . The array must have zero-based indexing. - /// The zero-based index in array at which copying begins. + /// + /// The array that is the destination of the elements copied from . The array must have zero-based indexing. + /// + /// The zero-based index in array at which copying begins. /// The number of elements to copy. - /// is null. - /// is less than 0. - /// The number of elements in the source is greater than the available space from to the end of the destination . + /// is null. + /// is less than 0. + /// + /// The number of elements in the source is greater than the available space from to the end of the destination . + /// public void CopyTo(int index, Trigger[] array, int arrayIndex, int count) { if (array == null) @@ -160,13 +267,11 @@ namespace winPEAS.TaskScheduler throw new ArgumentOutOfRangeException(nameof(count)); if ((Count - index) > (array.Length - arrayIndex)) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); - for (int i = 0; i < count; i++) + for (var i = 0; i < count; i++) array[arrayIndex + i] = (Trigger)this[index + i].Clone(); } - /// - /// Releases all resources used by this class. - /// + /// Releases all resources used by this class. public void Dispose() { if (v2Coll != null) Marshal.ReleaseComObject(v2Coll); @@ -175,10 +280,15 @@ namespace winPEAS.TaskScheduler } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the first occurrence within the entire collection. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the first + /// occurrence within the entire collection. /// - /// The delegate that defines the conditions of the to search for. - /// The first that matches the conditions defined by the specified predicate, if found; otherwise, null. + /// + /// The delegate that defines the conditions of the to search for. + /// + /// + /// The first that matches the conditions defined by the specified predicate, if found; otherwise, null. + /// public Trigger Find([NotNull] Predicate match) { if (match == null) @@ -189,12 +299,15 @@ namespace winPEAS.TaskScheduler } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based + /// index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements. /// /// The zero-based starting index of the search. /// The number of elements in the collection to search. /// The delegate that defines the conditions of the element to search for. - /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// + /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// public int FindIndexOf(int startIndex, int count, [NotNull] Predicate match) { if (startIndex < 0 || startIndex >= Count) @@ -203,29 +316,135 @@ namespace winPEAS.TaskScheduler throw new ArgumentOutOfRangeException(nameof(count)); if (match == null) throw new ArgumentNullException(nameof(match)); - for (int i = startIndex; i < startIndex + count; i++) + for (var i = startIndex; i < startIndex + count; i++) if (match(this[i])) return i; return -1; } /// - /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based index of the first occurrence within the collection. + /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based + /// index of the first occurrence within the collection. /// /// The delegate that defines the conditions of the element to search for. - /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// + /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. + /// public int FindIndexOf([NotNull] Predicate match) => FindIndexOf(0, Count, match); - /// - /// Gets the collection enumerator for this collection. - /// + /// Gets the collection enumerator for this collection. /// The for this collection. public IEnumerator GetEnumerator() { if (v1Task != null) return new V1TriggerEnumerator(v1Task); - return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], o => Trigger.CreateTrigger(o, v2Def)); + return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], o => Trigger.CreateTrigger(o, v2Def)); } + /// Determines the index of a specific item in the . + /// The object to locate in the . + /// The index of if found in the list; otherwise, -1. + public int IndexOf([NotNull] Trigger item) => FindIndexOf(a => a.Equals(item)); + + /// Determines the index of a specific item in the . + /// The id ( ) of the trigger to be retrieved. + /// The index of if found in the list; otherwise, -1. + public int IndexOf([NotNull] string triggerId) + { + if (string.IsNullOrEmpty(triggerId)) + throw new ArgumentNullException(triggerId); + return FindIndexOf(a => string.Equals(a.Id, triggerId)); + } + + /// Inserts an trigger at the specified index. + /// The zero-based index at which trigger should be inserted. + /// The trigger to insert into the list. + public void Insert(int index, [NotNull] Trigger trigger) + { + if (trigger == null) + throw new ArgumentNullException(nameof(trigger)); + if (index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + var pushItems = new Trigger[Count - index]; + CopyTo(index, pushItems, 0, Count - index); + for (var j = Count - 1; j >= index; j--) + RemoveAt(j); + Add(trigger); + foreach (var t in pushItems) + Add(t); + } + + /// Removes the first occurrence of a specific object from the . + /// The object to remove from the . + /// + /// true if was successfully removed from the ; otherwise, false. This method + /// also returns false if is not found in the original . + /// + public bool Remove([NotNull] Trigger item) + { + var idx = IndexOf(item); + if (idx != -1) + { + try + { + RemoveAt(idx); + return true; + } + catch { } + } + return false; + } + + /// Removes the trigger at a specified index. + /// Index of trigger to remove. + /// Index out of range. + public void RemoveAt(int index) + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index), index, @"Failed to remove Trigger. Index out of range."); + var item = this[index].Clone(); + if (v2Coll != null) + v2Coll.Remove(++index); + else + v1Task.DeleteTrigger((ushort)index); //Remove the trigger from the Task Scheduler + if (!inV2set) + { + OnNotifyPropertyChanged(nameof(Count)); + OnNotifyPropertyChanged(IndexerName); + CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); + } + } + + /// Copies the elements of the to a new array. + /// An array containing copies of the elements of the . + public Trigger[] ToArray() + { + var ret = new Trigger[Count]; + CopyTo(ret, 0); + return ret; + } + + /// Returns a that represents the triggers in this collection. + /// A that represents the triggers in this collection. + public override string ToString() + { + if (Count == 1) + return this[0].ToString(); + if (Count > 1) + return Properties.Resources.MultipleTriggers; + return string.Empty; + } + + void ICollection.Add(Trigger item) => Add(item); + + int IList.Add(object value) + { + Add((Trigger)value); + return Count - 1; + } + + bool IList.Contains(object value) => Contains((Trigger)value); + void ICollection.CopyTo(Array array, int index) { if (array != null && array.Rank != 1) @@ -235,53 +454,13 @@ namespace winPEAS.TaskScheduler Array.Copy(src, 0, array, index, Count); } - void ICollection.Add(Trigger item) { Add(item); } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - int IList.Add(object value) - { - Add((Trigger)value); - return Count - 1; - } - - bool IList.Contains(object value) => Contains((Trigger)value); + System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; int IList.IndexOf(object value) => IndexOf((Trigger)value); - void IList.Insert(int index, object value) { Insert(index, (Trigger)value); } - - void IList.Remove(object value) { Remove((Trigger)value); } - - /// - /// Determines the index of a specific item in the . - /// - /// The object to locate in the . - /// - /// The index of if found in the list; otherwise, -1. - /// - public int IndexOf([NotNull] Trigger item) => FindIndexOf(a => a.Equals(item)); - - /// - /// Inserts an trigger at the specified index. - /// - /// The zero-based index at which trigger should be inserted. - /// The trigger to insert into the list. - public void Insert(int index, [NotNull] Trigger trigger) - { - if (trigger == null) - throw new ArgumentNullException(nameof(trigger)); - if (index >= Count) - throw new ArgumentOutOfRangeException(nameof(index)); - - Trigger[] pushItems = new Trigger[Count - index]; - CopyTo(index, pushItems, 0, Count - index); - for (int j = Count - 1; j >= index; j--) - RemoveAt(j); - Add(trigger); - foreach (Trigger t in pushItems) - Add(t); - } - - System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; + void IList.Insert(int index, object value) => Insert(index, (Trigger)value); void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { @@ -318,92 +497,41 @@ namespace winPEAS.TaskScheduler reader.ReadEndElement(); } + void IList.Remove(object value) => Remove((Trigger)value); + void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { foreach (var t in this) XmlSerializationHelper.WriteObject(writer, t); } - /// - /// Removes the first occurrence of a specific object from the . - /// - /// The object to remove from the . - /// - /// true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - /// - public bool Remove([NotNull] Trigger item) - { - int idx = IndexOf(item); - if (idx != -1) - { - try - { - RemoveAt(idx); - return true; - } - catch { } - } - return false; - } - - /// - /// Removes the trigger at a specified index. - /// - /// Index of trigger to remove. - /// Index out of range. - public void RemoveAt(int index) - { - if (index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, @"Failed to remove Trigger. Index out of range."); - if (v2Coll != null) - v2Coll.Remove(++index); - else - v1Task.DeleteTrigger((ushort)index); //Remove the trigger from the Task Scheduler - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - - /// - /// Returns a that represents the triggers in this collection. - /// - /// - /// A that represents the triggers in this collection. - /// - public override string ToString() - { - if (Count == 1) - return this[0].ToString(); - if (Count > 1) - return winPEAS.Properties.Resources.MultipleTriggers; - return string.Empty; - } - internal void Bind() { - foreach (Trigger t in this) + foreach (var t in this) t.SetV1TriggerData(); } + /// Called when a property has changed to notify any attached elements. + /// Name of the property. + private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + private sealed class V1TriggerEnumerator : IEnumerator { private short curItem = -1; - private ITask iTask; + private V1Interop.ITask iTask; - internal V1TriggerEnumerator(ITask task) { iTask = task; } + internal V1TriggerEnumerator(V1Interop.ITask task) => iTask = task; public Trigger Current => Trigger.CreateTrigger(iTask.GetTrigger((ushort)curItem)); object IEnumerator.Current => Current; - /// - /// Releases all resources used by this class. - /// - public void Dispose() { iTask = null; } + /// Releases all resources used by this class. + public void Dispose() => iTask = null; public bool MoveNext() => (++curItem < iTask.GetTriggerCount()); - public void Reset() { curItem = -1; } + public void Reset() => curItem = -1; } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/User.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/User.cs index 4742e37..6042805 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/User.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/User.cs @@ -1,6 +1,10 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Security.Principal; -using winPEAS.TaskScheduler.Native; +using System.Text; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.TaskEditor.Native; namespace winPEAS.TaskScheduler { @@ -66,10 +70,22 @@ namespace winPEAS.TaskScheduler /// The . internal User(WindowsIdentity wid) { Identity = wid; sid = wid.User; } + /// Gets the current user. + /// The current user. + public static User Current => new User(cur); + /// Gets the identity. /// The identity. public WindowsIdentity Identity { get; private set; } + /// Gets a value indicating whether this instance is in an administrator role. + /// true if this instance is an admin; otherwise, false. + public bool IsAdmin => Identity != null ? new WindowsPrincipal(Identity).IsInRole(WindowsBuiltInRole.Administrator) : false; + + /// Gets a value indicating whether this instance is the interactive user. + /// true if this instance is the current user; otherwise, false. + public bool IsCurrent => Identity?.User.Equals(cur.User) ?? false; + /// Gets a value indicating whether this instance is a service account. /// true if this instance is a service account; otherwise, false. public bool IsServiceAccount @@ -89,6 +105,10 @@ namespace winPEAS.TaskScheduler /// true if this instance is the SYSTEM account; otherwise, false. public bool IsSystem => sid != null && sid.IsWellKnown(WellKnownSidType.LocalSystemSid); + /// Gets the SID string. + /// The SID string. + public string SidString => sid?.ToString(); + /// Gets the NT name (DOMAIN\username). /// The name of the user. public string Name => Identity?.Name ?? ((NTAccount)sid?.Translate(typeof(NTAccount)))?.Value; @@ -128,4 +148,4 @@ namespace winPEAS.TaskScheduler /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. public override int GetHashCode() => sid?.GetHashCode() ?? 0; } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Interop.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Interop.cs index 60bac0f..47dd76e 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Interop.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Interop.cs @@ -1,11 +1,15 @@ -using System; +using System; +using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.TaskEditor.Native; -// ReSharper disable InconsistentNaming -// ReSharper disable FieldCanBeMadeReadOnly.Global - -namespace winPEAS.TaskScheduler.V1 +namespace winPEAS.TaskScheduler.V1Interop { +#pragma warning disable CS0618 // Type or member is obsolete + #region class HRESULT -- Values peculiar to the task scheduler. internal class HResult { @@ -322,8 +326,11 @@ namespace winPEAS.TaskScheduler.V1 IEnumWorkItems Enum(); [return: MarshalAs(UnmanagedType.Interface)] ITask Activate([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); + void Delete([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name); [return: MarshalAs(UnmanagedType.Interface)] ITask NewWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); + void AddWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.Interface)] ITask WorkItem); + void IsOfType([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid); } [Guid("148BD528-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity] @@ -386,16 +393,29 @@ namespace winPEAS.TaskScheduler.V1 ushort GetTriggerCount(); [return: MarshalAs(UnmanagedType.Interface)] ITaskTrigger GetTrigger([In] ushort TriggerIndex); + CoTaskMemString GetTriggerString([In] ushort TriggerIndex); + void GetRunTimes([In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME Begin, [In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME End, ref ushort Count, [In, Out] ref IntPtr TaskTimes); [return: MarshalAs(UnmanagedType.Struct)] + NativeMethods.SYSTEMTIME GetNextRunTime(); void SetIdleWait([In] ushort IdleMinutes, [In] ushort DeadlineMinutes); void GetIdleWait([Out] out ushort IdleMinutes, [Out] out ushort DeadlineMinutes); + void Run(); + void Terminate(); + void EditWorkItem([In] IntPtr hParent, [In] uint dwReserved); + [return: MarshalAs(UnmanagedType.Struct)] + NativeMethods.SYSTEMTIME GetMostRecentRunTime(); TaskStatus GetStatus(); + uint GetExitCode(); void SetComment([In, MarshalAs(UnmanagedType.LPWStr)] string Comment); CoTaskMemString GetComment(); void SetCreator([In, MarshalAs(UnmanagedType.LPWStr)] string Creator); CoTaskMemString GetCreator(); void SetWorkItemData([In] ushort DataLen, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U1)] byte[] Data); void GetWorkItemData(out ushort DataLen, [Out] out IntPtr Data); + void SetErrorRetryCount([In] ushort RetryCount); + ushort GetErrorRetryCount(); + void SetErrorRetryInterval([In] ushort RetryInterval); + ushort GetErrorRetryInterval(); void SetFlags([In] TaskFlags Flags); TaskFlags GetFlags(); void SetAccountInformation([In, MarshalAs(UnmanagedType.LPWStr)] string AccountName, [In] IntPtr Password); @@ -409,6 +429,7 @@ namespace winPEAS.TaskScheduler.V1 void SetPriority([In] uint Priority); uint GetPriority(); void SetTaskFlags([In] uint Flags); + uint GetTaskFlags(); void SetMaxRunTime([In] uint MaxRunTimeMS); uint GetMaxRunTime(); } @@ -438,7 +459,7 @@ namespace winPEAS.TaskScheduler.V1 public CoTaskMemString(IntPtr handle) : this() { SetHandle(handle); } public CoTaskMemString(string text) : this() { SetHandle(Marshal.StringToCoTaskMemUni(text)); } - public static implicit operator string (CoTaskMemString cmem) => cmem.ToString(); + public static implicit operator string(CoTaskMemString cmem) => cmem.ToString(); public override bool IsInvalid => handle == IntPtr.Zero; @@ -450,4 +471,4 @@ namespace winPEAS.TaskScheduler.V1 public override string ToString() => Marshal.PtrToStringUni(handle); } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Schema.xsd b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Schema.xsd new file mode 100644 index 0000000..8758076 --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V1/TaskSchedulerV1Schema.xsd @@ -0,0 +1,449 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Interop.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Interop.cs index 5715af9..f590c06 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Interop.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Interop.cs @@ -1,15 +1,28 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using winPEAS.TaskScheduler.Native; +using System.Text; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.TaskEditor.Native; +using winPEAS.TaskScheduler.V1Interop; -namespace winPEAS.TaskScheduler.V2 +namespace winPEAS.TaskScheduler.V2Interop { + + internal enum TaskEnumFlags + { + Hidden = 1 + } + +#pragma warning disable CS0618 // Type or member is obsolete [ComImport, Guid("BAE54997-48B1-4CBE-9965-D6BE263EBEA4"), InterfaceType(ComInterfaceType.InterfaceIsDual), System.Security.SuppressUnmanagedCodeSecurity] internal interface IAction { + string Id { [return: MarshalAs(UnmanagedType.BStr)] get; [param: In, MarshalAs(UnmanagedType.BStr)] set; } TaskActionType Type { get; } } @@ -19,6 +32,9 @@ namespace winPEAS.TaskScheduler.V2 int Count { get; } IAction this[int index] { [return: MarshalAs(UnmanagedType.Interface)] get; } [return: MarshalAs(UnmanagedType.Interface)] + IEnumerator GetEnumerator(); + string XmlText { [return: MarshalAs(UnmanagedType.BStr)] get; [param: In, MarshalAs(UnmanagedType.BStr)] set; } + [return: MarshalAs(UnmanagedType.Interface)] IAction Create([In] TaskActionType Type); void Remove([In, MarshalAs(UnmanagedType.Struct)][NotNull] object index); void Clear(); @@ -496,6 +512,19 @@ namespace winPEAS.TaskScheduler.V2 bool Exclusive { [param: In] set; get; } } + [ComImport, Guid("3E4C9351-D966-4B8B-BB87-CEBA68BB0107"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity] + internal interface ITaskVariables + { + [return: MarshalAs(UnmanagedType.BStr)] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + string GetInput(); + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + void SetOutput([In, MarshalAs(UnmanagedType.BStr)] string input); + [return: MarshalAs(UnmanagedType.BStr)] + [MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)] + string GetContext(); + } + [ComImport, Guid("B45747E0-EBA7-4276-9F29-85C5BB300006"), InterfaceType(ComInterfaceType.InterfaceIsDual), System.Security.SuppressUnmanagedCodeSecurity] internal interface ITimeTrigger : ITrigger { @@ -551,4 +580,3 @@ namespace winPEAS.TaskScheduler.V2 string RandomDelay { [return: MarshalAs(UnmanagedType.BStr)] get; [param: In, MarshalAs(UnmanagedType.BStr)] set; } } } - diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Schema.xsd b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Schema.xsd new file mode 100644 index 0000000..624324b --- /dev/null +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/V2/TaskSchedulerV2Schema.xsd @@ -0,0 +1,691 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Wildcard.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Wildcard.cs index 240cdfa..3c029ad 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Wildcard.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/Wildcard.cs @@ -1,4 +1,9 @@ -using System.Text.RegularExpressions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; namespace winPEAS.TaskScheduler { diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/WindowsImpersonatedIdentity.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/WindowsImpersonatedIdentity.cs similarity index 93% rename from winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/WindowsImpersonatedIdentity.cs rename to winPEAS/winPEASexe/winPEAS/TaskScheduler/WindowsImpersonatedIdentity.cs index c30ca89..a1aeb11 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/Native/WindowsImpersonatedIdentity.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/WindowsImpersonatedIdentity.cs @@ -1,9 +1,14 @@ -using System; +using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Runtime.InteropServices; using System.Security.Principal; +using System.Text; +using System.Threading.Tasks; +using winPEAS.TaskScheduler.TaskEditor.Native; -namespace winPEAS.TaskScheduler.Native +namespace winPEAS.TaskScheduler { /// /// Impersonation of a user. Allows to execute code under another @@ -77,4 +82,4 @@ namespace winPEAS.TaskScheduler.Native identity.Dispose(); } } -} \ No newline at end of file +} diff --git a/winPEAS/winPEASexe/winPEAS/TaskScheduler/XmlSerializationHelper.cs b/winPEAS/winPEASexe/winPEAS/TaskScheduler/XmlSerializationHelper.cs index a8d408b..5e36e9f 100644 --- a/winPEAS/winPEASexe/winPEAS/TaskScheduler/XmlSerializationHelper.cs +++ b/winPEAS/winPEASexe/winPEAS/TaskScheduler/XmlSerializationHelper.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; diff --git a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj index 13b5a49..1bb9ea4 100755 --- a/winPEAS/winPEASexe/winPEAS/winPEAS.csproj +++ b/winPEAS/winPEASexe/winPEAS/winPEAS.csproj @@ -166,6 +166,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -182,41 +214,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -245,6 +242,12 @@ + + Designer + + + Designer +