using JetBrains.Annotations; using Microsoft.Win32.TaskScheduler.V2Interop; using System; using System.Collections.Generic; using System.ComponentModel; using System.Globalization; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using System.Xml.Serialization; namespace Microsoft.Win32.TaskScheduler { /// Values for days of the week (Monday, Tuesday, etc.) [Flags] public enum DaysOfTheWeek : short { /// 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 } /// Values for months of the year (January, February, etc.) [Flags] public enum MonthsOfTheYear : short { /// 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 } /// Defines the type of triggers that can be used by tasks. [DefaultValue(Time)] public enum TaskTriggerType { /// 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 } /// Values for week of month (first, second, ..., last) [Flags] public enum WhichWeek : short { /// 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 } /// Interface that categorizes the trigger as a calendar trigger. public interface ICalendarTrigger { } /// Interface for triggers that support a delay. public interface ITriggerDelay { /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan Delay { get; set; } } /// Interface for triggers that support a user identifier. public interface ITriggerUserId { /// Gets or sets the user for the . string UserId { get; set; } } /// 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. /// /// /// /// /// /// public sealed class BootTrigger : Trigger, ITriggerDelay { /// Creates an unbound instance of a . public BootTrigger() : base(TaskTriggerType.Boot) { } internal BootTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnSystemStart) { } 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. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan Delay { get => v2Trigger != null ? Task.StringToTimeSpan(((IBootTrigger)v2Trigger).Delay) : GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero); set { if (v2Trigger != null) ((IBootTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else if (v1Trigger != null) 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() => 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. /// public sealed class CustomTrigger : Trigger, ITriggerDelay { private readonly NamedValueCollection nvc = new NamedValueCollection(); private TimeSpan delay = TimeSpan.MinValue; private string name = string.Empty; 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. public TimeSpan Delay { get => delay; 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. public override object Clone() => throw new InvalidOperationException("CustomTrigger cannot be cloned due to OS restrictions."); /// Updates custom properties from XML provided by definition. /// The XML from the TaskDefinition. internal void UpdateFromXml(string xml) { nvc.Clear(); try { var xmlDoc = new System.Xml.XmlDocument(); xmlDoc.LoadXml(xml); var nsmgr = new System.Xml.XmlNamespaceManager(xmlDoc.NameTable); nsmgr.AddNamespace("n", "http://schemas.microsoft.com/windows/2004/02/mit/task"); var elem = xmlDoc.DocumentElement?.SelectSingleNode("n:Triggers/*[@id='" + Id + "']", nsmgr); if (elem == null) { var nodes = xmlDoc.GetElementsByTagName("WnfStateChangeTrigger"); if (nodes.Count == 1) elem = nodes[0]; } if (elem == null) return; name = elem.LocalName; foreach (System.Xml.XmlNode node in elem.ChildNodes) { switch (node.LocalName) { case "Delay": delay = Task.StringToTimeSpan(node.InnerText); break; case "StartBoundary": case "Enabled": case "EndBoundary": case "ExecutionTimeLimit": break; default: nvc.Add(node.LocalName, node.InnerText); break; } } } catch { /* ignored */ } } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() => TaskScheduler.Properties.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. /// /// A DailyTrigger will fire at a specified time every day or interval of days. /// /// /// /// /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class DailyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable { /// Creates an unbound instance of a . /// Interval between the days in the schedule. public DailyTrigger(short daysInterval = 1) : base(TaskTriggerType.Daily) => DaysInterval = daysInterval; 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) { } /// Sets or retrieves the interval between the days in the schedule. [DefaultValue(1)] public short DaysInterval { get { if (v2Trigger != null) return ((IDailyTrigger)v2Trigger).DaysInterval; return (short)v1TriggerData.Data.daily.DaysInterval; } set { if (v2Trigger != null) ((IDailyTrigger)v2Trigger).DaysInterval = value; else { v1TriggerData.Data.daily.DaysInterval = (ushort)value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(DaysInterval)] = value; } OnNotifyPropertyChanged(); } } /// Gets or sets a delay time that is randomly added to the start time of the trigger. /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan RandomDelay { get => v2Trigger != null ? Task.StringToTimeSpan(((IDailyTrigger)v2Trigger).RandomDelay) : GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero); set { if (v2Trigger != null) ((IDailyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan ITriggerDelay.Delay { get => RandomDelay; set => RandomDelay = value; } /// /// 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) { base.CopyProperties(sourceTrigger); if (sourceTrigger is DailyTrigger dt) { DaysInterval = dt.DaysInterval; } } /// 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. public override bool Equals(Trigger other) => other is DailyTrigger dt && base.Equals(dt) && DaysInterval == dt.DaysInterval; System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); 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(Properties.Resources.TriggerDaily1, AdjustToLocal(StartBoundary)) : string.Format(Properties.Resources.TriggerDaily2, AdjustToLocal(StartBoundary), DaysInterval); private void ReadMyXml(System.Xml.XmlReader reader) { reader.ReadStartElement("ScheduleByDay"); if (reader.MoveToContent() == System.Xml.XmlNodeType.Element && reader.LocalName == "DaysInterval") // ReSharper disable once AssignNullToNotNullAttribute DaysInterval = (short)reader.ReadElementContentAs(typeof(short), null); reader.Read(); reader.ReadEndElement(); } private void WriteMyXml(System.Xml.XmlWriter writer) { writer.WriteStartElement("ScheduleByDay"); writer.WriteElementString("DaysInterval", DaysInterval.ToString()); writer.WriteEndElement(); } } /// /// 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)] public sealed class EventTrigger : Trigger, ITriggerDelay { private NamedValueCollection nvc; /// Creates an unbound instance of a . public EventTrigger() : base(TaskTriggerType.Event) { } /// 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")] public TimeSpan Delay { get => v2Trigger != null ? Task.StringToTimeSpan(((IEventTrigger)v2Trigger).Delay) : GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero); set { if (v2Trigger != null) ((IEventTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else unboundValues[nameof(Delay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets the XPath query string that identifies the event that fires the trigger. [DefaultValue(null)] public string Subscription { get => v2Trigger != null ? ((IEventTrigger)v2Trigger).Subscription : GetUnboundValueOrDefault(nameof(Subscription)); set { if (v2Trigger != null) ((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. /// [XmlArray] [XmlArrayItem("Value", typeof(NameValuePair))] 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. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) { base.CopyProperties(sourceTrigger); if (sourceTrigger is EventTrigger et) { Subscription = et.Subscription; et.ValueQueries.CopyTo(ValueQueries); } } /// 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. public override bool Equals(Trigger other) => other is EventTrigger et && base.Equals(et) && Subscription == et.Subscription; /// Gets basic event information. /// The event's log. /// The event's source. Can be null. /// The event's id. Can be null. /// true if subscription represents a basic event, false if not. public bool GetBasic(out string log, out string source, out int? eventId) { log = source = null; eventId = null; if (!string.IsNullOrEmpty(Subscription)) { using var str = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(Subscription)); 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")) { 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; } } } } 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); nvc?.Bind(((IEventTrigger)v2Trigger).ValueQueries); } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() { if (!GetBasic(out var log, out var source, out var id)) return Properties.Resources.TriggerEvent1; var sb = new StringBuilder(); sb.AppendFormat(Properties.Resources.TriggerEventBasic1, log); if (!string.IsNullOrEmpty(source)) sb.AppendFormat(Properties.Resources.TriggerEventBasic2, source); if (id.HasValue) 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. /// /// /// 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 { /// Creates an unbound instance of a . public IdleTrigger() : base(TaskTriggerType.Idle) { } internal IdleTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnIdle) { } internal IdleTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. 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. /// /// /// 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 { /// Creates an unbound instance of a . public LogonTrigger() : base(TaskTriggerType.Logon) { } internal LogonTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnLogon) { } 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. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan Delay { get => v2Trigger != null ? Task.StringToTimeSpan(((ILogonTrigger)v2Trigger).Delay) : GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero); set { if (v2Trigger != null) ((ILogonTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(Delay)] = value; OnNotifyPropertyChanged(); } } /// /// Gets or sets The identifier of the user. For example, "MyDomain\MyName" or for a local account, "Administrator". /// This property can be in one of the following formats: /// • User name or SID: The task is started when the user logs on to the computer. /// • 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. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] [XmlIgnore] public string UserId { get => v2Trigger != null ? ((ILogonTrigger)v2Trigger).UserId : GetUnboundValueOrDefault(nameof(UserId)); set { if (v2Trigger != null) ((ILogonTrigger)v2Trigger).UserId = value; else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(UserId)] = value; OnNotifyPropertyChanged(); } } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() { 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. /// [XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class MonthlyDOWTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable { /// Creates an unbound instance of a . /// The days of the week. /// The months of the year. /// The weeks of the month. public MonthlyDOWTrigger(DaysOfTheWeek daysOfWeek = DaysOfTheWeek.Sunday, MonthsOfTheYear monthsOfYear = MonthsOfTheYear.AllMonths, WhichWeek weeksOfMonth = WhichWeek.FirstWeek) : base(TaskTriggerType.MonthlyDOW) { DaysOfWeek = daysOfWeek; MonthsOfYear = monthsOfYear; WeeksOfMonth = weeksOfMonth; } internal MonthlyDOWTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunMonthlyDOW) { if (v1TriggerData.Data.monthlyDOW.Months == 0) v1TriggerData.Data.monthlyDOW.Months = MonthsOfTheYear.AllMonths; if (v1TriggerData.Data.monthlyDOW.DaysOfTheWeek == 0) v1TriggerData.Data.monthlyDOW.DaysOfTheWeek = DaysOfTheWeek.Sunday; } internal MonthlyDOWTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } /// Gets or sets the days of the week during which the task runs. [DefaultValue(0)] public DaysOfTheWeek DaysOfWeek { get => v2Trigger != null ? (DaysOfTheWeek)((IMonthlyDOWTrigger)v2Trigger).DaysOfWeek : v1TriggerData.Data.monthlyDOW.DaysOfTheWeek; set { if (v2Trigger != null) ((IMonthlyDOWTrigger)v2Trigger).DaysOfWeek = (short)value; else { v1TriggerData.Data.monthlyDOW.DaysOfTheWeek = value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(DaysOfWeek)] = (short)value; } OnNotifyPropertyChanged(); } } /// Gets or sets the months of the year during which the task runs. [DefaultValue(0)] public MonthsOfTheYear MonthsOfYear { get => v2Trigger != null ? (MonthsOfTheYear)((IMonthlyDOWTrigger)v2Trigger).MonthsOfYear : v1TriggerData.Data.monthlyDOW.Months; set { if (v2Trigger != null) ((IMonthlyDOWTrigger)v2Trigger).MonthsOfYear = (short)value; else { v1TriggerData.Data.monthlyDOW.Months = value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(MonthsOfYear)] = (short)value; } OnNotifyPropertyChanged(); } } /// Gets or sets a delay time that is randomly added to the start time of the trigger. /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan RandomDelay { get => v2Trigger != null ? Task.StringToTimeSpan(((IMonthlyDOWTrigger)v2Trigger).RandomDelay) : GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero); set { if (v2Trigger != null) ((IMonthlyDOWTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the task runs on the last week of the month. /// Not supported under Task Scheduler 1.0. [DefaultValue(false)] [XmlIgnore] public bool RunOnLastWeekOfMonth { get => ((IMonthlyDOWTrigger)v2Trigger)?.RunOnLastWeekOfMonth ?? GetUnboundValueOrDefault(nameof(RunOnLastWeekOfMonth), false); set { if (v2Trigger != null) ((IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth = value; else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RunOnLastWeekOfMonth)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets the weeks of the month during which the task runs. [DefaultValue(0)] public WhichWeek WeeksOfMonth { get { if (v2Trigger == null) return v1Trigger != null ? v1TriggerData.Data.monthlyDOW.V2WhichWeek : GetUnboundValueOrDefault(nameof(WeeksOfMonth), WhichWeek.FirstWeek); var ww = (WhichWeek)((IMonthlyDOWTrigger)v2Trigger).WeeksOfMonth; // Following addition give accurate results for confusing RunOnLastWeekOfMonth property (thanks kbergeron) if (((IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth) ww |= WhichWeek.LastWeek; return ww; } 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) if (value.IsFlagSet(WhichWeek.LastWeek)) RunOnLastWeekOfMonth = true; if (v2Trigger != null) { ((IMonthlyDOWTrigger)v2Trigger).WeeksOfMonth = (short)value; } else { try { v1TriggerData.Data.monthlyDOW.V2WhichWeek = value; } catch (NotV1SupportedException) { if (v1Trigger != null) throw; } if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(WeeksOfMonth)] = (short)value; } OnNotifyPropertyChanged(); } } /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan ITriggerDelay.Delay { get => RandomDelay; set => RandomDelay = value; } /// /// 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) { base.CopyProperties(sourceTrigger); if (sourceTrigger is MonthlyDOWTrigger mt) { DaysOfWeek = mt.DaysOfWeek; MonthsOfYear = mt.MonthsOfYear; try { RunOnLastWeekOfMonth = mt.RunOnLastWeekOfMonth; } catch { /* ignored */ } WeeksOfMonth = mt.WeeksOfMonth; } if (sourceTrigger is MonthlyTrigger m) MonthsOfYear = m.MonthsOfYear; } /// 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. public override bool Equals(Trigger other) => other is MonthlyDOWTrigger mt && base.Equals(other) && DaysOfWeek == mt.DaysOfWeek && MonthsOfYear == mt.MonthsOfYear && WeeksOfMonth == mt.WeeksOfMonth && v1Trigger == null && RunOnLastWeekOfMonth == mt.RunOnLastWeekOfMonth; System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); 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 ww = TaskEnumGlobalizer.GetString(WeeksOfMonth); var days = TaskEnumGlobalizer.GetString(DaysOfWeek); var months = TaskEnumGlobalizer.GetString(MonthsOfYear); return string.Format(Properties.Resources.TriggerMonthlyDOW1, AdjustToLocal(StartBoundary), ww, days, months); } /// Reads the subclass XML for V1 streams. /// The reader. private void ReadMyXml([NotNull] System.Xml.XmlReader reader) { reader.ReadStartElement("ScheduleByMonthDayOfWeek"); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { switch (reader.LocalName) { case "Weeks": reader.Read(); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { if (reader.LocalName == "Week") { var wk = reader.ReadElementContentAsString(); if (wk == "Last") WeeksOfMonth = WhichWeek.LastWeek; else { WeeksOfMonth = (int.Parse(wk)) switch { 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."), }; } } } reader.ReadEndElement(); break; case "DaysOfWeek": reader.Read(); DaysOfWeek = 0; while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { try { DaysOfWeek |= (DaysOfTheWeek)Enum.Parse(typeof(DaysOfTheWeek), reader.LocalName); } catch { throw new System.Xml.XmlException("Invalid days of the week element."); } reader.Read(); } reader.ReadEndElement(); break; case "Months": reader.Read(); MonthsOfYear = 0; while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { try { MonthsOfYear |= (MonthsOfTheYear)Enum.Parse(typeof(MonthsOfTheYear), reader.LocalName); } catch { throw new System.Xml.XmlException("Invalid months of the year element."); } reader.Read(); } reader.ReadEndElement(); break; default: reader.Skip(); break; } } reader.ReadEndElement(); } /// Writes the subclass XML for V1 streams. /// The writer. private void WriteMyXml([NotNull] System.Xml.XmlWriter writer) { writer.WriteStartElement("ScheduleByMonthDayOfWeek"); writer.WriteStartElement("Weeks"); if ((WeeksOfMonth & WhichWeek.FirstWeek) == WhichWeek.FirstWeek) writer.WriteElementString("Week", "1"); if ((WeeksOfMonth & WhichWeek.SecondWeek) == WhichWeek.SecondWeek) writer.WriteElementString("Week", "2"); if ((WeeksOfMonth & WhichWeek.ThirdWeek) == WhichWeek.ThirdWeek) writer.WriteElementString("Week", "3"); if ((WeeksOfMonth & WhichWeek.FourthWeek) == WhichWeek.FourthWeek) writer.WriteElementString("Week", "4"); if ((WeeksOfMonth & WhichWeek.LastWeek) == WhichWeek.LastWeek) writer.WriteElementString("Week", "Last"); writer.WriteEndElement(); writer.WriteStartElement("DaysOfWeek"); foreach (DaysOfTheWeek e in Enum.GetValues(typeof(DaysOfTheWeek))) if (e != DaysOfTheWeek.AllDays && (DaysOfWeek & e) == e) writer.WriteElementString(e.ToString(), null); writer.WriteEndElement(); writer.WriteStartElement("Months"); foreach (MonthsOfTheYear e in Enum.GetValues(typeof(MonthsOfTheYear))) if (e != MonthsOfTheYear.AllMonths && (MonthsOfYear & e) == e) writer.WriteElementString(e.ToString(), null); writer.WriteEndElement(); writer.WriteEndElement(); } } /// /// 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 months of the year. public MonthlyTrigger(int dayOfMonth = 1, MonthsOfTheYear monthsOfYear = MonthsOfTheYear.AllMonths) : base(TaskTriggerType.Monthly) { if (dayOfMonth < 1 || dayOfMonth > 32) throw new ArgumentOutOfRangeException(nameof(dayOfMonth)); if (!monthsOfYear.IsValidFlagValue()) throw new ArgumentOutOfRangeException(nameof(monthsOfYear)); if (dayOfMonth == 32) { DaysOfMonth = new int[0]; RunOnLastDayOfMonth = true; } else DaysOfMonth = new[] { dayOfMonth }; MonthsOfYear = monthsOfYear; } internal MonthlyTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunMonthly) { if (v1TriggerData.Data.monthlyDate.Months == 0) v1TriggerData.Data.monthlyDate.Months = MonthsOfTheYear.AllMonths; if (v1TriggerData.Data.monthlyDate.Days == 0) v1TriggerData.Data.monthlyDate.Days = 1; } internal MonthlyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } /// Gets or sets the days of the month during which the task runs. public int[] DaysOfMonth { get => v2Trigger != null ? MaskToIndices(((IMonthlyTrigger)v2Trigger).DaysOfMonth) : MaskToIndices((int)v1TriggerData.Data.monthlyDate.Days); set { var mask = IndicesToMask(value); if (v2Trigger != null) ((IMonthlyTrigger)v2Trigger).DaysOfMonth = mask; else { v1TriggerData.Data.monthlyDate.Days = (uint)mask; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(DaysOfMonth)] = mask; } OnNotifyPropertyChanged(); } } /// Gets or sets the months of the year during which the task runs. [DefaultValue(0)] public MonthsOfTheYear MonthsOfYear { get => v2Trigger != null ? (MonthsOfTheYear)((IMonthlyTrigger)v2Trigger).MonthsOfYear : v1TriggerData.Data.monthlyDOW.Months; set { if (v2Trigger != null) ((IMonthlyTrigger)v2Trigger).MonthsOfYear = (short)value; else { v1TriggerData.Data.monthlyDOW.Months = value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(MonthsOfYear)] = (short)value; } OnNotifyPropertyChanged(); } } /// Gets or sets a delay time that is randomly added to the start time of the trigger. /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan RandomDelay { get => v2Trigger != null ? Task.StringToTimeSpan(((IMonthlyTrigger)v2Trigger).RandomDelay) : GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero); set { if (v2Trigger != null) ((IMonthlyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the task runs on the last day of the month. /// Not supported under Task Scheduler 1.0. [DefaultValue(false)] [XmlIgnore] public bool RunOnLastDayOfMonth { get => ((IMonthlyTrigger)v2Trigger)?.RunOnLastDayOfMonth ?? GetUnboundValueOrDefault(nameof(RunOnLastDayOfMonth), false); set { if (v2Trigger != null) ((IMonthlyTrigger)v2Trigger).RunOnLastDayOfMonth = value; else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RunOnLastDayOfMonth)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan ITriggerDelay.Delay { get => RandomDelay; set => RandomDelay = value; } /// /// 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) { base.CopyProperties(sourceTrigger); if (sourceTrigger is MonthlyTrigger mt) { DaysOfMonth = mt.DaysOfMonth; MonthsOfYear = mt.MonthsOfYear; try { RunOnLastDayOfMonth = mt.RunOnLastDayOfMonth; } catch { /* ignored */ } } if (sourceTrigger is MonthlyDOWTrigger mdt) MonthsOfYear = mdt.MonthsOfYear; } /// 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. public override bool Equals(Trigger other) => other is MonthlyTrigger mt && base.Equals(mt) && ListsEqual(DaysOfMonth, mt.DaysOfMonth) && MonthsOfYear == mt.MonthsOfYear && v1Trigger == null && RunOnLastDayOfMonth == mt.RunOnLastDayOfMonth; System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); 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(Properties.Resources.ListSeparator, Array.ConvertAll(DaysOfMonth, i => i.ToString())); if (RunOnLastDayOfMonth) days += (days.Length == 0 ? "" : Properties.Resources.ListSeparator) + Properties.Resources.WWLastWeek; var months = TaskEnumGlobalizer.GetString(MonthsOfYear); 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. /// /// An array with an element for each bit of the mask which is ON. /// An integer to be interpreted as a mask. private static int IndicesToMask(int[] indices) { if (indices is null || indices.Length == 0) return 0; var mask = 0; foreach (var index in indices) { if (index < 1 || index > 31) throw new ArgumentException("Days must be in the range 1..31"); mask |= 1 << (index - 1); } return mask; } /// Compares two collections. /// Item type of collections. /// The first collection. /// The second collection /// true if the collections values are equal; false otherwise. private static bool ListsEqual(ICollection left, ICollection right) where T : IComparable { if (left == null && right == null) return true; if (left == null || right == null) return false; if (left.Count != right.Count) return false; List l1 = new List(left), l2 = new List(right); l1.Sort(); l2.Sort(); for (var i = 0; i < l1.Count; i++) if (l1[i].CompareTo(l2[i]) != 0) return false; return true; } /// /// 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. private static int[] MaskToIndices(int mask) { //count bits in mask var cnt = 0; for (var i = 0; mask >> i > 0; i++) cnt += (1 & (mask >> i)); //allocate return array with one entry for each bit var indices = new int[cnt]; //fill array with bit indices cnt = 0; for (var i = 0; mask >> i > 0; i++) if ((1 & (mask >> i)) == 1) indices[cnt++] = i + 1; return indices; } /// Reads the subclass XML for V1 streams. /// The reader. private void ReadMyXml([NotNull] System.Xml.XmlReader reader) { reader.ReadStartElement("ScheduleByMonth"); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { switch (reader.LocalName) { case "DaysOfMonth": reader.Read(); var days = new List(); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { if (reader.LocalName != "Day") continue; var sday = reader.ReadElementContentAsString(); if (sday.Equals("Last", StringComparison.InvariantCultureIgnoreCase)) continue; var day = int.Parse(sday); if (day >= 1 && day <= 31) days.Add(day); } DaysOfMonth = days.ToArray(); reader.ReadEndElement(); break; case "Months": reader.Read(); MonthsOfYear = 0; while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { try { MonthsOfYear |= (MonthsOfTheYear)Enum.Parse(typeof(MonthsOfTheYear), reader.LocalName); } catch { throw new System.Xml.XmlException("Invalid months of the year element."); } reader.Read(); } reader.ReadEndElement(); break; default: reader.Skip(); break; } } reader.ReadEndElement(); } private void WriteMyXml([NotNull] System.Xml.XmlWriter writer) { writer.WriteStartElement("ScheduleByMonth"); writer.WriteStartElement("DaysOfMonth"); foreach (var day in DaysOfMonth) writer.WriteElementString("Day", day.ToString()); writer.WriteEndElement(); writer.WriteStartElement("Months"); foreach (MonthsOfTheYear e in Enum.GetValues(typeof(MonthsOfTheYear))) if (e != MonthsOfTheYear.AllMonths && (MonthsOfYear & e) == e) writer.WriteElementString(e.ToString(), null); writer.WriteEndElement(); writer.WriteEndElement(); } } /// /// 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)] public sealed class RegistrationTrigger : Trigger, ITriggerDelay { /// Creates an unbound instance of a . public RegistrationTrigger() : base(TaskTriggerType.Registration) { } 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. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan Delay { get => v2Trigger != null ? Task.StringToTimeSpan(((IRegistrationTrigger)v2Trigger).Delay) : GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero); set { if (v2Trigger != null) ((IRegistrationTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else if (v1Trigger != null) 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() => 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, 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; if (pTrigger?.v2Trigger != null) 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. /// /// If you specify a repetition duration for a task, you must also specify the repetition interval. [DefaultValue(typeof(TimeSpan), "00:00:00")] public TimeSpan Duration { get => v2Pattern != null ? Task.StringToTimeSpan(v2Pattern.Duration) : (pTrigger != null ? TimeSpan.FromMinutes(pTrigger.v1TriggerData.MinutesDuration) : unboundDuration); set { if (value.Ticks < 0 || value != TimeSpan.Zero && value < TimeSpan.FromMinutes(1)) throw new ArgumentOutOfRangeException(nameof(Duration)); if (v2Pattern != null) { v2Pattern.Duration = Task.TimeSpanToString(value); } else if (pTrigger != null) { pTrigger.v1TriggerData.MinutesDuration = (uint)value.TotalMinutes; Bind(); } 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. /// /// 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. /// [DefaultValue(typeof(TimeSpan), "00:00:00")] public TimeSpan Interval { get => v2Pattern != null ? Task.StringToTimeSpan(v2Pattern.Interval) : (pTrigger != null ? TimeSpan.FromMinutes(pTrigger.v1TriggerData.MinutesInterval) : unboundInterval); set { if (value.Ticks < 0 || (v2Pattern != null || pTrigger == null) && value != TimeSpan.Zero && (value < TimeSpan.FromMinutes(1) || value > TimeSpan.FromDays(31))) throw new ArgumentOutOfRangeException(nameof(Interval)); if (v2Pattern != null) { v2Pattern.Interval = Task.TimeSpanToString(value); } else if (pTrigger != null) { if (value != TimeSpan.Zero && value < TimeSpan.FromMinutes(1)) throw new ArgumentOutOfRangeException(nameof(Interval)); pTrigger.v1TriggerData.MinutesInterval = (uint)value.TotalMinutes; Bind(); } 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. /// [DefaultValue(false)] public bool StopAtDurationEnd { get { if (v2Pattern != null) return v2Pattern.StopAtDurationEnd; if (pTrigger != null) return (pTrigger.v1TriggerData.Flags & V1Interop.TaskTriggerFlags.KillAtDurationEnd) == V1Interop.TaskTriggerFlags.KillAtDurationEnd; return unboundStopAtDurationEnd; } set { if (v2Pattern != null) v2Pattern.StopAtDurationEnd = value; else if (pTrigger != null) { if (value) pTrigger.v1TriggerData.Flags |= V1Interop.TaskTriggerFlags.KillAtDurationEnd; else pTrigger.v1TriggerData.Flags &= ~V1Interop.TaskTriggerFlags.KillAtDurationEnd; Bind(); } else unboundStopAtDurationEnd = value; OnNotifyPropertyChanged(); } } /// Releases all resources used by this class. public void Dispose() { 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. // ReSharper disable once BaseObjectEqualsIsObjectEquals public override bool Equals(object obj) => obj is RepetitionPattern pattern ? Equals(pattern) : base.Equals(obj); /// 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. public bool Equals(RepetitionPattern other) => other != null && Duration == other.Duration && Interval == other.Interval && StopAtDurationEnd == other.StopAtDurationEnd; /// 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 = 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) { if (!reader.IsEmptyElement) { reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns); XmlSerializationHelper.ReadObjectProperties(reader, this, ReadXmlConverter); reader.ReadEndElement(); } else reader.Skip(); } void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this); internal void Bind() { if (pTrigger.v1Trigger != null) pTrigger.SetV1TriggerData(); else if (pTrigger.v2Trigger != null) { if (pTrigger.v1TriggerData.MinutesInterval != 0) v2Pattern.Interval = $"PT{pTrigger.v1TriggerData.MinutesInterval}M"; if (pTrigger.v1TriggerData.MinutesDuration != 0) v2Pattern.Duration = $"PT{pTrigger.v1TriggerData.MinutesDuration}M"; v2Pattern.StopAtDurationEnd = (pTrigger.v1TriggerData.Flags & V1Interop.TaskTriggerFlags.KillAtDurationEnd) == V1Interop.TaskTriggerFlags.KillAtDurationEnd; } } internal void Set([NotNull] RepetitionPattern value) { Duration = value.Duration; Interval = value.Interval; StopAtDurationEnd = value.StopAtDurationEnd; } /// 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; Duration = span.Add(TimeSpan.FromMinutes(1)); return true; } } /// /// 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. /// /// /// /// /// /// [XmlType(IncludeInSchema = false)] public sealed class SessionStateChangeTrigger : Trigger, ITriggerDelay, ITriggerUserId { /// Creates an unbound instance of a . public SessionStateChangeTrigger() : base(TaskTriggerType.SessionStateChange) { } /// 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")] public TimeSpan Delay { get => v2Trigger != null ? Task.StringToTimeSpan(((ISessionStateChangeTrigger)v2Trigger).Delay) : GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero); set { if (v2Trigger != null) ((ISessionStateChangeTrigger)v2Trigger).Delay = Task.TimeSpanToString(value); else unboundValues[nameof(Delay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets the kind of Terminal Server session change that would trigger a task launch. [DefaultValue(1)] public TaskSessionStateChangeType StateChange { get => ((ISessionStateChangeTrigger)v2Trigger)?.StateChange ?? GetUnboundValueOrDefault(nameof(StateChange), TaskSessionStateChangeType.ConsoleConnect); set { if (v2Trigger != null) ((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. /// [DefaultValue(null)] public string UserId { get => v2Trigger != null ? ((ISessionStateChangeTrigger)v2Trigger).UserId : GetUnboundValueOrDefault(nameof(UserId)); set { if (v2Trigger != null) ((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. /// /// The source . public override void CopyProperties(Trigger sourceTrigger) { base.CopyProperties(sourceTrigger); if (sourceTrigger is SessionStateChangeTrigger st && !StateChangeIsSet()) StateChange = st.StateChange; } /// 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. public override bool Equals(Trigger other) => other is SessionStateChangeTrigger st && base.Equals(st) && StateChange == st.StateChange; /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. protected override string V2GetTriggerString() { 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(Properties.Resources.TriggerSessionUserSession, user); return string.Format(str, user); } /// Returns a value indicating if the StateChange property has been set. /// StateChange property has been set. private bool StateChangeIsSet() => v2Trigger != null || (unboundValues?.ContainsKey("StateChange") ?? false); } /// Represents a trigger that starts a task at a specific date and time. /// A TimeTrigger runs at a specified date and time. /// /// /// /// /// public sealed class TimeTrigger : Trigger, ITriggerDelay, ICalendarTrigger { /// Creates an unbound instance of a . public TimeTrigger() : base(TaskTriggerType.Time) { } /// 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] 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. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan RandomDelay { get => v2Trigger != null ? Task.StringToTimeSpan(((ITimeTrigger)v2Trigger).RandomDelay) : GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero); set { if (v2Trigger != null) ((ITimeTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan ITriggerDelay.Delay { get => RandomDelay; set => RandomDelay = value; } /// Gets the non-localized trigger string for V2 triggers. /// String describing the trigger. 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. /// 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 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] V1Interop.ITaskTrigger trigger, V1Interop.TaskTriggerType type) { v1Trigger = trigger; v1TriggerData = trigger.GetTrigger(); v1TriggerData.Type = type; ttype = ConvertFromV1TriggerType(type); } internal Trigger([NotNull] ITrigger iTrigger) { v2Trigger = iTrigger; ttype = iTrigger.Type; if (string.IsNullOrEmpty(v2Trigger.StartBoundary) && this is ICalendarTrigger) StartBoundary = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Unspecified); } internal Trigger(TaskTriggerType triggerType) { ttype = triggerType; v1TriggerData.TriggerSize = (ushort)Marshal.SizeOf(typeof(V1Interop.TaskTrigger)); if (ttype != TaskTriggerType.Registration && ttype != TaskTriggerType.Event && ttype != TaskTriggerType.SessionStateChange) v1TriggerData.Type = ConvertToV1TriggerType(ttype); if (this is ICalendarTrigger) 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(V1Interop.TaskTriggerFlags.Disabled)); set { if (v2Trigger != null) v2Trigger.Enabled = value; else { v1TriggerData.Flags = v1TriggerData.Flags.SetFlags(V1Interop.TaskTriggerFlags.Disabled, !value); if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(Enabled)] = value; } OnNotifyPropertyChanged(); } } /// /// Gets or sets the date and time when the trigger is deactivated. The trigger cannot start the task after it is deactivated. /// While the maximum value for this property is , the Windows Task Scheduler management /// application that is part of the OS will fail if this value is greater than December 31, 9998. /// /// /// /// Version 1 (1.1 on all systems prior to Vista) of the native library only allows for the Day, Month and Year values of the structure. /// /// /// Version 2 (1.2 or higher) of the native library only allows for both date and time and 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. /// /// [DefaultValue(typeof(DateTime), "9999-12-31T23:59:59.9999999")] public DateTime EndBoundary { get { if (v2Trigger != null) return string.IsNullOrEmpty(v2Trigger.EndBoundary) ? DateTime.MaxValue : DateTime.Parse(v2Trigger.EndBoundary, DefaultDateCulture); return GetUnboundValueOrDefault(nameof(EndBoundary), v1TriggerData.EndDate.GetValueOrDefault(DateTime.MaxValue)); } set { if (v2Trigger != null) { if (value <= StartBoundary) throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); v2Trigger.EndBoundary = value == DateTime.MaxValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture); } else { v1TriggerData.EndDate = value == DateTime.MaxValue ? (DateTime?)null : value; if (v1Trigger != null) SetV1TriggerData(); 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. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan ExecutionTimeLimit { get => v2Trigger != null ? Task.StringToTimeSpan(v2Trigger.ExecutionTimeLimit) : GetUnboundValueOrDefault(nameof(ExecutionTimeLimit), TimeSpan.Zero); set { if (v2Trigger != null) v2Trigger.ExecutionTimeLimit = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(ExecutionTimeLimit)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets the identifier for the trigger. Cannot set with Task Scheduler 1.0. /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] [XmlIgnore] public string Id { get => v2Trigger != null ? v2Trigger.Id : GetUnboundValueOrDefault(nameof(Id)); set { if (v2Trigger != null) v2Trigger.Id = value; else if (v1Trigger != null) 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. /// public RepetitionPattern Repetition { 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 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: /// /// /// 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. /// /// /// /// public DateTime StartBoundary { get { if (v2Trigger == null) return GetUnboundValueOrDefault(nameof(StartBoundary), v1TriggerData.BeginDate); if (string.IsNullOrEmpty(v2Trigger.StartBoundary)) return DateTime.MinValue; var ret = DateTime.Parse(v2Trigger.StartBoundary, DefaultDateCulture); if (v2Trigger.StartBoundary.EndsWith("Z")) ret = ret.ToUniversalTime(); return ret; } set { if (v2Trigger != null) { if (value > EndBoundary) throw new ArgumentException(Properties.Resources.Error_TriggerEndBeforeStart); v2Trigger.StartBoundary = value == DateTime.MinValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture); } else { v1TriggerData.BeginDate = value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(StartBoundary)] = value; } OnNotifyPropertyChanged(); } } /// Gets the type of the trigger. /// The of the trigger. [XmlIgnore] public TaskTriggerType TriggerType => ttype; /// Creates the specified trigger. /// Type of the trigger to instantiate. /// of specified type. public static Trigger CreateTrigger(TaskTriggerType triggerType) { switch (triggerType) { case TaskTriggerType.Boot: return new BootTrigger(); case TaskTriggerType.Daily: return new DailyTrigger(); case TaskTriggerType.Event: return new EventTrigger(); case TaskTriggerType.Idle: return new IdleTrigger(); case TaskTriggerType.Logon: return new LogonTrigger(); case TaskTriggerType.Monthly: return new MonthlyTrigger(); case TaskTriggerType.MonthlyDOW: return new MonthlyDOWTrigger(); case TaskTriggerType.Registration: return new RegistrationTrigger(); case TaskTriggerType.SessionStateChange: return new SessionStateChangeTrigger(); case TaskTriggerType.Time: return new TimeTrigger(); case TaskTriggerType.Weekly: return new WeeklyTrigger(); case TaskTriggerType.Custom: break; default: throw new ArgumentOutOfRangeException(nameof(triggerType), triggerType, null); } return null; } /// Creates a new that is an unbound copy of this instance. /// A new that is an unbound copy of this instance. public virtual object Clone() { var ret = CreateTrigger(TriggerType); 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. /// /// 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. /// /// The source . public virtual void CopyProperties(Trigger sourceTrigger) { if (sourceTrigger == null) return; Enabled = sourceTrigger.Enabled; EndBoundary = sourceTrigger.EndBoundary; try { ExecutionTimeLimit = sourceTrigger.ExecutionTimeLimit; } catch { /* ignored */ } Id = sourceTrigger.Id; Repetition.Duration = sourceTrigger.Repetition.Duration; Repetition.Interval = sourceTrigger.Repetition.Interval; Repetition.StopAtDurationEnd = sourceTrigger.Repetition.StopAtDurationEnd; StartBoundary = sourceTrigger.StartBoundary; if (sourceTrigger is ITriggerDelay delay && this is ITriggerDelay) try { ((ITriggerDelay)this).Delay = delay.Delay; } catch { /* ignored */ } if (sourceTrigger is ITriggerUserId id && this is ITriggerUserId) try { ((ITriggerUserId)this).UserId = id.UserId; } catch { /* ignored */ } } /// Releases all resources used by this class. public virtual void Dispose() { if (v2Trigger != null) Marshal.ReleaseComObject(v2Trigger); if (v1Trigger != null) 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. // ReSharper disable once BaseObjectEqualsIsObjectEquals public override bool Equals(object obj) => obj is Trigger trigger ? Equals(trigger) : base.Equals(obj); /// 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. public virtual bool Equals(Trigger other) { if (other == null) return false; var ret = TriggerType == other.TriggerType && Enabled == other.Enabled && EndBoundary == other.EndBoundary && ExecutionTimeLimit == other.ExecutionTimeLimit && Id == other.Id && Repetition.Equals(other.Repetition) && StartBoundary == other.StartBoundary; if (other is ITriggerDelay delay && this is ITriggerDelay) try { ret = ret && ((ITriggerDelay)this).Delay == delay.Delay; } catch { /* ignored */ } if (other is ITriggerUserId id && this is ITriggerUserId) try { ret = ret && ((ITriggerUserId)this).UserId == id.UserId; } catch { /* ignored */ } return ret; } /// 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 = TriggerType, B = Enabled, C = EndBoundary, D = ExecutionTimeLimit, E = Id, F = Repetition, G = StartBoundary, H = (this as ITriggerDelay)?.Delay ?? TimeSpan.Zero, 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 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 (V1Interop.TaskTriggerType)tv1; } internal static Trigger CreateTrigger([NotNull] V1Interop.ITaskTrigger trigger) => CreateTrigger(trigger, trigger.GetTrigger().Type); internal static Trigger CreateTrigger([NotNull] V1Interop.ITaskTrigger trigger, V1Interop.TaskTriggerType triggerType) { Trigger t = triggerType switch { 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; } internal static Trigger CreateTrigger([NotNull] ITrigger iTrigger, ITaskDefinition iDef = null) { switch (iTrigger.Type) { case TaskTriggerType.Boot: return new BootTrigger((IBootTrigger)iTrigger); case TaskTriggerType.Daily: return new DailyTrigger((IDailyTrigger)iTrigger); case TaskTriggerType.Event: return new EventTrigger((IEventTrigger)iTrigger); case TaskTriggerType.Idle: return new IdleTrigger((IIdleTrigger)iTrigger); case TaskTriggerType.Logon: return new LogonTrigger((ILogonTrigger)iTrigger); case TaskTriggerType.Monthly: return new MonthlyTrigger((IMonthlyTrigger)iTrigger); case TaskTriggerType.MonthlyDOW: return new MonthlyDOWTrigger((IMonthlyDOWTrigger)iTrigger); case TaskTriggerType.Registration: return new RegistrationTrigger((IRegistrationTrigger)iTrigger); case TaskTriggerType.SessionStateChange: return new SessionStateChangeTrigger((ISessionStateChangeTrigger)iTrigger); case TaskTriggerType.Time: return new TimeTrigger((ITimeTrigger)iTrigger); case TaskTriggerType.Weekly: return new WeeklyTrigger((IWeeklyTrigger)iTrigger); case TaskTriggerType.Custom: var ct = new CustomTrigger(iTrigger); if (iDef != null) try { ct.UpdateFromXml(iDef.XmlText); } catch { /* ignored */ } return ct; default: throw new ArgumentOutOfRangeException(); } } /// Gets the best time span string. /// The to display. /// Either the full string representation created by TimeSpan2 or the default TimeSpan representation. internal static string GetBestTimeSpanString(TimeSpan span) { // See if the TimeSpan2 assembly is accessible if (!foundTimeSpan2.HasValue) { try { foundTimeSpan2 = false; timeSpan2Type = System.Reflection.ReflectionHelper.LoadType("System.TimeSpan2", "TimeSpan2.dll"); if (timeSpan2Type != null) foundTimeSpan2 = true; } catch { /* ignored */ } } // If the TimeSpan2 assembly is available, try to call the ToString("f") method and return the result. if (foundTimeSpan2 == true && timeSpan2Type != null) { try { return System.Reflection.ReflectionHelper.InvokeMethod(timeSpan2Type, new object[] { span }, "ToString", "f"); } catch { /* ignored */ } } return span.ToString(); } internal virtual void Bind([NotNull] V1Interop.ITask iTask) { if (v1Trigger == null) { v1Trigger = iTask.CreateTrigger(out var _); } SetV1TriggerData(); } internal virtual void Bind([NotNull] ITaskDefinition iTaskDef) { var iTriggers = iTaskDef.Triggers; 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(Properties.Resources.Error_TriggerEndBeforeStart); foreach (var key in unboundValues.Keys) { try { var o = unboundValues[key]; CheckBindValue(key, ref o); v2Trigger.GetType().InvokeMember(key, System.Reflection.BindingFlags.SetProperty, null, v2Trigger, new[] { o }); } catch (System.Reflection.TargetInvocationException tie) when (tie.InnerException != null) { throw tie.InnerException; } catch { /* ignored */ } } unboundValues.Clear(); unboundValues = null; repititionPattern = new RepetitionPattern(this); repititionPattern.Bind(); } /// Assigns the unbound TriggerData structure to the V1 trigger instance. internal void SetV1TriggerData() { 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(Properties.Resources.Error_TriggerEndBeforeStart); if (v1TriggerData.BeginDate == DateTime.MinValue) v1TriggerData.BeginDate = DateTime.Now; v1Trigger?.SetTrigger(ref v1TriggerData); System.Diagnostics.Debug.WriteLine(v1TriggerData); } /// Checks the bind value for any conversion. /// The key (property) name. /// The value. protected virtual void CheckBindValue(string key, ref object o) { if (o is TimeSpan ts) o = Task.TimeSpanToString(ts); if (o is DateTime dt) { if (key == "EndBoundary" && dt == DateTime.MaxValue || key == "StartBoundary" && dt == DateTime.MinValue) o = null; else o = dt.ToString(V2BoundaryDateFormat, DefaultDateCulture); } } /// Gets the unbound value or a default. /// Return type. /// The property name. /// The default value if not found in unbound value list. /// 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(V1Interop.TaskTriggerType v1Type) { var tv2 = (int)v1Type + 1; if (tv2 > 6) tv2++; return (TaskTriggerType)tv2; } private string V2BaseTriggerString() { var ret = new StringBuilder(); if (Repetition.Interval != TimeSpan.Zero) { 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(Properties.Resources.TriggerEndBoundary, AdjustToLocal(EndBoundary)); if (ret.Length > 0) 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. /// /// 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)] public sealed class WeeklyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable { /// Creates an unbound instance of a . /// The days of the week. /// The interval between the weeks in the schedule. public WeeklyTrigger(DaysOfTheWeek daysOfWeek = DaysOfTheWeek.Sunday, short weeksInterval = 1) : base(TaskTriggerType.Weekly) { DaysOfWeek = daysOfWeek; WeeksInterval = weeksInterval; } internal WeeklyTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.RunWeekly) { if (v1TriggerData.Data.weekly.DaysOfTheWeek == 0) v1TriggerData.Data.weekly.DaysOfTheWeek = DaysOfTheWeek.Sunday; if (v1TriggerData.Data.weekly.WeeksInterval == 0) v1TriggerData.Data.weekly.WeeksInterval = 1; } internal WeeklyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { } /// Gets or sets the days of the week on which the task runs. [DefaultValue(0)] public DaysOfTheWeek DaysOfWeek { get => v2Trigger != null ? (DaysOfTheWeek)((IWeeklyTrigger)v2Trigger).DaysOfWeek : v1TriggerData.Data.weekly.DaysOfTheWeek; set { if (v2Trigger != null) ((IWeeklyTrigger)v2Trigger).DaysOfWeek = (short)value; else { v1TriggerData.Data.weekly.DaysOfTheWeek = value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(DaysOfWeek)] = (short)value; } OnNotifyPropertyChanged(); } } /// Gets or sets a delay time that is randomly added to the start time of the trigger. /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TimeSpan), "00:00:00")] [XmlIgnore] public TimeSpan RandomDelay { get => v2Trigger != null ? Task.StringToTimeSpan(((IWeeklyTrigger)v2Trigger).RandomDelay) : GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero); set { if (v2Trigger != null) ((IWeeklyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value); else if (v1Trigger != null) throw new NotV1SupportedException(); else unboundValues[nameof(RandomDelay)] = value; OnNotifyPropertyChanged(); } } /// Gets or sets the interval between the weeks in the schedule. [DefaultValue(1)] public short WeeksInterval { get => ((IWeeklyTrigger)v2Trigger)?.WeeksInterval ?? (short)v1TriggerData.Data.weekly.WeeksInterval; set { if (v2Trigger != null) ((IWeeklyTrigger)v2Trigger).WeeksInterval = value; else { v1TriggerData.Data.weekly.WeeksInterval = (ushort)value; if (v1Trigger != null) SetV1TriggerData(); else unboundValues[nameof(WeeksInterval)] = value; } OnNotifyPropertyChanged(); } } /// Gets or sets a value that indicates the amount of time before the task is started. /// The delay duration. TimeSpan ITriggerDelay.Delay { get => RandomDelay; set => RandomDelay = value; } /// /// 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) { base.CopyProperties(sourceTrigger); if (sourceTrigger is WeeklyTrigger wt) { DaysOfWeek = wt.DaysOfWeek; WeeksInterval = wt.WeeksInterval; } } /// 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. public override bool Equals(Trigger other) => other is WeeklyTrigger wt && base.Equals(wt) && DaysOfWeek == wt.DaysOfWeek && WeeksInterval == wt.WeeksInterval; System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) => CalendarTrigger.ReadXml(reader, this, ReadMyXml); 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 ? Properties.Resources.TriggerWeekly1Week : Properties.Resources.TriggerWeeklyMultWeeks, AdjustToLocal(StartBoundary), days, WeeksInterval); } /// Reads the subclass XML for V1 streams. /// The reader. private void ReadMyXml(System.Xml.XmlReader reader) { reader.ReadStartElement("ScheduleByWeek"); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { switch (reader.LocalName) { case "WeeksInterval": WeeksInterval = (short)reader.ReadElementContentAsInt(); break; case "DaysOfWeek": reader.Read(); DaysOfWeek = 0; while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { try { DaysOfWeek |= (DaysOfTheWeek)Enum.Parse(typeof(DaysOfTheWeek), reader.LocalName); } catch { throw new System.Xml.XmlException("Invalid days of the week element."); } reader.Read(); } reader.ReadEndElement(); break; default: reader.Skip(); break; } } reader.ReadEndElement(); } /// Writes the subclass XML for V1 streams. /// The writer. private void WriteMyXml(System.Xml.XmlWriter writer) { writer.WriteStartElement("ScheduleByWeek"); if (WeeksInterval != 1) writer.WriteElementString("WeeksInterval", WeeksInterval.ToString()); writer.WriteStartElement("DaysOfWeek"); foreach (DaysOfTheWeek e in Enum.GetValues(typeof(DaysOfTheWeek))) if (e != DaysOfTheWeek.AllDays && (DaysOfWeek & e) == e) writer.WriteElementString(e.ToString(), null); writer.WriteEndElement(); writer.WriteEndElement(); } } internal static class CalendarTrigger { internal delegate void CalendarXmlReader(System.Xml.XmlReader reader); internal delegate void CalendarXmlWriter(System.Xml.XmlWriter writer); public static void WriteXml([NotNull] System.Xml.XmlWriter writer, [NotNull] Trigger t, [NotNull] CalendarXmlWriter calWriterProc) { if (!t.Enabled) writer.WriteElementString("Enabled", System.Xml.XmlConvert.ToString(t.Enabled)); if (t.EndBoundary != DateTime.MaxValue) writer.WriteElementString("EndBoundary", System.Xml.XmlConvert.ToString(t.EndBoundary, System.Xml.XmlDateTimeSerializationMode.RoundtripKind)); XmlSerializationHelper.WriteObject(writer, t.Repetition); writer.WriteElementString("StartBoundary", System.Xml.XmlConvert.ToString(t.StartBoundary, System.Xml.XmlDateTimeSerializationMode.RoundtripKind)); calWriterProc(writer); } internal static Trigger GetTriggerFromXml([NotNull] System.Xml.XmlReader reader) { Trigger t = null; var xml = reader.ReadOuterXml(); var match = System.Text.RegularExpressions.Regex.Match(xml, @"\<(?ScheduleBy.+)\>"); if (match.Success && match.Groups.Count == 2) { switch (match.Groups[1].Value) { case "ScheduleByDay": t = new DailyTrigger(); break; case "ScheduleByWeek": t = new WeeklyTrigger(); break; case "ScheduleByMonth": t = new MonthlyTrigger(); break; case "ScheduleByMonthDayOfWeek": t = new MonthlyDOWTrigger(); break; } if (t != null) { using var ms = new System.IO.StringReader(xml); using var iReader = System.Xml.XmlReader.Create(ms); ((IXmlSerializable)t).ReadXml(iReader); } } return t; } internal static void ReadXml([NotNull] System.Xml.XmlReader reader, [NotNull] Trigger t, [NotNull] CalendarXmlReader calReaderProc) { reader.ReadStartElement("CalendarTrigger", TaskDefinition.tns); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { switch (reader.LocalName) { case "Enabled": t.Enabled = reader.ReadElementContentAsBoolean(); break; case "EndBoundary": t.EndBoundary = reader.ReadElementContentAsDateTime(); break; case "RandomDelay": ((ITriggerDelay)t).Delay = Task.StringToTimeSpan(reader.ReadElementContentAsString()); break; case "StartBoundary": t.StartBoundary = reader.ReadElementContentAsDateTime(); break; case "Repetition": XmlSerializationHelper.ReadObject(reader, t.Repetition); break; case "ScheduleByDay": case "ScheduleByWeek": case "ScheduleByMonth": case "ScheduleByMonthDayOfWeek": calReaderProc(reader); break; default: reader.Skip(); break; } } reader.ReadEndElement(); } } 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); } } }