FlashPatcher/FlashPatcher/TaskService/Trigger.cs

2779 lines
104 KiB
C#

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
{
/// <summary>Values for days of the week (Monday, Tuesday, etc.)</summary>
[Flags]
public enum DaysOfTheWeek : short
{
/// <summary>Sunday</summary>
Sunday = 0x1,
/// <summary>Monday</summary>
Monday = 0x2,
/// <summary>Tuesday</summary>
Tuesday = 0x4,
/// <summary>Wednesday</summary>
Wednesday = 0x8,
/// <summary>Thursday</summary>
Thursday = 0x10,
/// <summary>Friday</summary>
Friday = 0x20,
/// <summary>Saturday</summary>
Saturday = 0x40,
/// <summary>All days</summary>
AllDays = 0x7F
}
/// <summary>Values for months of the year (January, February, etc.)</summary>
[Flags]
public enum MonthsOfTheYear : short
{
/// <summary>January</summary>
January = 0x1,
/// <summary>February</summary>
February = 0x2,
/// <summary>March</summary>
March = 0x4,
/// <summary>April</summary>
April = 0x8,
/// <summary>May</summary>
May = 0x10,
/// <summary>June</summary>
June = 0x20,
/// <summary>July</summary>
July = 0x40,
/// <summary>August</summary>
August = 0x80,
/// <summary>September</summary>
September = 0x100,
/// <summary>October</summary>
October = 0x200,
/// <summary>November</summary>
November = 0x400,
/// <summary>December</summary>
December = 0x800,
/// <summary>All months</summary>
AllMonths = 0xFFF
}
/// <summary>Defines the type of triggers that can be used by tasks.</summary>
[DefaultValue(Time)]
public enum TaskTriggerType
{
/// <summary>Triggers the task when a specific event occurs. Version 1.2 only.</summary>
Event = 0,
/// <summary>Triggers the task at a specific time of day.</summary>
Time = 1,
/// <summary>Triggers the task on a daily schedule.</summary>
Daily = 2,
/// <summary>Triggers the task on a weekly schedule.</summary>
Weekly = 3,
/// <summary>Triggers the task on a monthly schedule.</summary>
Monthly = 4,
/// <summary>Triggers the task on a monthly day-of-week schedule.</summary>
MonthlyDOW = 5,
/// <summary>Triggers the task when the computer goes into an idle state.</summary>
Idle = 6,
/// <summary>Triggers the task when the task is registered. Version 1.2 only.</summary>
Registration = 7,
/// <summary>Triggers the task when the computer boots.</summary>
Boot = 8,
/// <summary>Triggers the task when a specific user logs on.</summary>
Logon = 9,
/// <summary>Triggers the task when a specific user session state changes. Version 1.2 only.</summary>
SessionStateChange = 11,
/// <summary>Triggers the custom trigger. Version 1.3 only.</summary>
Custom = 12
}
/// <summary>Values for week of month (first, second, ..., last)</summary>
[Flags]
public enum WhichWeek : short
{
/// <summary>First week of the month</summary>
FirstWeek = 1,
/// <summary>Second week of the month</summary>
SecondWeek = 2,
/// <summary>Third week of the month</summary>
ThirdWeek = 4,
/// <summary>Fourth week of the month</summary>
FourthWeek = 8,
/// <summary>Last week of the month</summary>
LastWeek = 0x10,
/// <summary>Every week of the month</summary>
AllWeeks = 0x1F
}
/// <summary>Interface that categorizes the trigger as a calendar trigger.</summary>
public interface ICalendarTrigger { }
/// <summary>Interface for triggers that support a delay.</summary>
public interface ITriggerDelay
{
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan Delay { get; set; }
}
/// <summary>Interface for triggers that support a user identifier.</summary>
public interface ITriggerUserId
{
/// <summary>Gets or sets the user for the <see cref="Trigger"/>.</summary>
string UserId { get; set; }
}
/// <summary>Represents a trigger that starts a task when the system is booted.</summary>
/// <remarks>
/// A BootTrigger will fire when the system starts. It can only be delayed. All triggers that support a delay implement the
/// ITriggerDelay interface.
/// </remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create trigger that fires 5 minutes after the system starts.
/// BootTrigger bt = new BootTrigger();
/// bt.Delay = TimeSpan.FromMinutes(5); // V2 only
///]]>
/// </code>
/// </example>
public sealed class BootTrigger : Trigger, ITriggerDelay
{
/// <summary>Creates an unbound instance of a <see cref="BootTrigger"/>.</summary>
public BootTrigger() : base(TaskTriggerType.Boot) { }
internal BootTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnSystemStart)
{
}
internal BootTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString() => Properties.Resources.TriggerBoot1;
}
/// <summary>
/// Represents a custom trigger. This class is based on undocumented features and may change. <note>This type of trigger is only
/// available for reading custom triggers. It cannot be used to create custom triggers.</note>
/// </summary>
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)
{
}
/// <summary>Gets a value that indicates the amount of time between the trigger events and when the task is started.</summary>
/// <exception cref="System.NotImplementedException">This value cannot be set.</exception>
public TimeSpan Delay
{
get => delay;
set => throw new NotImplementedException();
}
/// <summary>Gets the name of the custom trigger type.</summary>
/// <value>The name of the XML element representing this custom trigger.</value>
public string Name => name;
/// <summary>Gets the properties from the XML definition if possible.</summary>
[XmlArray, XmlArrayItem("Property")]
public NamedValueCollection Properties => nvc;
/// <summary>Clones this instance.</summary>
/// <returns>This method will always throw an exception.</returns>
/// <exception cref="System.InvalidOperationException">CustomTrigger cannot be cloned due to OS restrictions.</exception>
public override object Clone() => throw new InvalidOperationException("CustomTrigger cannot be cloned due to OS restrictions.");
/// <summary>Updates custom properties from XML provided by definition.</summary>
/// <param name="xml">The XML from the TaskDefinition.</param>
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 */ }
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString() => TaskScheduler.Properties.Resources.TriggerCustom1;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>A DailyTrigger will fire at a specified time every day or interval of days.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a trigger that runs every other day and will start randomly between 10 a.m. and 12 p.m.
/// DailyTrigger dt = new DailyTrigger();
/// dt.StartBoundary = DateTime.Today + TimeSpan.FromHours(10);
/// dt.DaysInterval = 2;
/// dt.RandomDelay = TimeSpan.FromHours(2); // V2 only
///]]>
/// </code>
/// </example>
[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
public sealed class DailyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
{
/// <summary>Creates an unbound instance of a <see cref="DailyTrigger"/>.</summary>
/// <param name="daysInterval">Interval between the days in the schedule.</param>
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)
{
}
/// <summary>Sets or retrieves the interval between the days in the schedule.</summary>
[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();
}
}
/// <summary>Gets or sets a delay time that is randomly added to the start time of the trigger.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan ITriggerDelay.Delay
{
get => RandomDelay;
set => RandomDelay = value;
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
public override void CopyProperties(Trigger sourceTrigger)
{
base.CopyProperties(sourceTrigger);
if (sourceTrigger is DailyTrigger dt)
{
DaysInterval = dt.DaysInterval;
}
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
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);
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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();
}
}
/// <summary>
/// Represents a trigger that starts a task when a system event occurs. <note>Only available for Task Scheduler 2.0 on Windows Vista or
/// Windows Server 2003 and later.</note>
/// </summary>
/// <remarks>The EventTrigger runs when a system event fires.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a trigger that will fire whenever a level 2 system event fires.
/// EventTrigger eTrigger = new EventTrigger();
/// eTrigger.Subscription = @"<QueryList><Query Id='1'><Select Path='System'>*[System/Level=2]</Select></Query></QueryList>";
/// eTrigger.ValueQueries.Add("Name", "Value");
///]]>
/// </code>
/// </example>
[XmlType(IncludeInSchema = false)]
public sealed class EventTrigger : Trigger, ITriggerDelay
{
private NamedValueCollection nvc;
/// <summary>Creates an unbound instance of a <see cref="EventTrigger"/>.</summary>
public EventTrigger() : base(TaskTriggerType.Event) { }
/// <summary>Initializes an unbound instance of the <see cref="EventTrigger"/> class and sets a basic event.</summary>
/// <param name="log">The event's log.</param>
/// <param name="source">The event's source. Can be <c>null</c>.</param>
/// <param name="eventId">The event's id. Can be <c>null</c>.</param>
public EventTrigger(string log, string source, int? eventId) : this() => SetBasic(log, source, eventId);
internal EventTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.</summary>
[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();
}
}
/// <summary>Gets or sets the XPath query string that identifies the event that fires the trigger.</summary>
[DefaultValue(null)]
public string Subscription
{
get => v2Trigger != null ? ((IEventTrigger)v2Trigger).Subscription : GetUnboundValueOrDefault<string>(nameof(Subscription));
set
{
if (v2Trigger != null)
((IEventTrigger)v2Trigger).Subscription = value;
else
unboundValues[nameof(Subscription)] = value;
OnNotifyPropertyChanged();
}
}
/// <summary>
/// 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 <see cref="ShowMessageAction"/> action.
/// </summary>
[XmlArray]
[XmlArrayItem("Value", typeof(NameValuePair))]
public NamedValueCollection ValueQueries => nvc ??= v2Trigger == null ? new NamedValueCollection() : new NamedValueCollection(((IEventTrigger)v2Trigger).ValueQueries);
/// <summary>Builds an event log XML query string based on the input parameters.</summary>
/// <param name="log">The event's log.</param>
/// <param name="source">The event's source. Can be <c>null</c>.</param>
/// <param name="eventId">The event's id. Can be <c>null</c>.</param>
/// <returns>XML query string.</returns>
/// <exception cref="System.ArgumentNullException">log</exception>
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("<QueryList><Query Id=\"0\" Path=\"{0}\"><Select Path=\"{0}\">*", log);
bool hasSource = !string.IsNullOrEmpty(source), hasId = eventId.HasValue;
if (hasSource || hasId)
{
sb.Append("[System[");
if (hasSource)
sb.AppendFormat("Provider[@Name='{0}']", source);
if (hasSource && hasId)
sb.Append(" and ");
if (hasId)
sb.AppendFormat("EventID={0}", eventId.Value);
sb.Append("]]");
}
sb.Append("</Select></Query></QueryList>");
return sb.ToString();
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
public override void CopyProperties(Trigger sourceTrigger)
{
base.CopyProperties(sourceTrigger);
if (sourceTrigger is EventTrigger et)
{
Subscription = et.Subscription;
et.ValueQueries.CopyTo(ValueQueries);
}
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
public override bool Equals(Trigger other) => other is EventTrigger et && base.Equals(et) && Subscription == et.Subscription;
/// <summary>Gets basic event information.</summary>
/// <param name="log">The event's log.</param>
/// <param name="source">The event's source. Can be <c>null</c>.</param>
/// <param name="eventId">The event's id. Can be <c>null</c>.</param>
/// <returns><c>true</c> if subscription represents a basic event, <c>false</c> if not.</returns>
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>[^']+)'\])?(?:\s+and\s+)?(?:EventID=(?<e>\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;
}
/// <summary>
/// Sets the subscription for a basic event. This will replace the contents of the <see cref="Subscription"/> property and clear all
/// entries in the <see cref="ValueQueries"/> property.
/// </summary>
/// <param name="log">The event's log.</param>
/// <param name="source">The event's source. Can be <c>null</c>.</param>
/// <param name="eventId">The event's id. Can be <c>null</c>.</param>
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);
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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();
}
}
/// <summary>
/// Represents a trigger that starts a task when the computer goes into an idle state. For information about idle conditions, see Task
/// Idle Conditions.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
///IdleTrigger it = new IdleTrigger();
///]]>
/// </code>
/// </example>
public sealed class IdleTrigger : Trigger
{
/// <summary>Creates an unbound instance of a <see cref="IdleTrigger"/>.</summary>
public IdleTrigger() : base(TaskTriggerType.Idle) { }
internal IdleTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnIdle)
{
}
internal IdleTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString() => Properties.Resources.TriggerIdle1;
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>
/// A LogonTrigger will fire after a user logs on. It can only be delayed. Under V2, you can specify which user it applies to.
/// </remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Add a general logon trigger
/// LogonTrigger lt1 = new LogonTrigger();
///
/// // V2 only: Add a delayed logon trigger for a specific user
/// LogonTrigger lt2 = new LogonTrigger { UserId = "LocalUser" };
/// lt2.Delay = TimeSpan.FromMinutes(15);
///]]>
/// </code>
/// </example>
public sealed class LogonTrigger : Trigger, ITriggerDelay, ITriggerUserId
{
/// <summary>Creates an unbound instance of a <see cref="LogonTrigger"/>.</summary>
public LogonTrigger() : base(TaskTriggerType.Logon) { }
internal LogonTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnLogon)
{
}
internal LogonTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>
/// <para>Gets or sets The identifier of the user. For example, "MyDomain\MyName" or for a local account, "Administrator".</para>
/// <para>This property can be in one of the following formats:</para>
/// <para>• User name or SID: The task is started when the user logs on to the computer.</para>
/// <para>• NULL: The task is started when any user logs on to the computer.</para>
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[DefaultValue(null)]
[XmlIgnore]
public string UserId
{
get => v2Trigger != null ? ((ILogonTrigger)v2Trigger).UserId : GetUnboundValueOrDefault<string>(nameof(UserId));
set
{
if (v2Trigger != null)
((ILogonTrigger)v2Trigger).UserId = value;
else if (v1Trigger != null)
throw new NotV1SupportedException();
else
unboundValues[nameof(UserId)] = value;
OnNotifyPropertyChanged();
}
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString()
{
var user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId;
return string.Format(Properties.Resources.TriggerLogon1, user);
}
}
/// <summary>
/// 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.
/// </summary>
[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
public sealed class MonthlyDOWTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
{
/// <summary>Creates an unbound instance of a <see cref="MonthlyDOWTrigger"/>.</summary>
/// <param name="daysOfWeek">The days of the week.</param>
/// <param name="monthsOfYear">The months of the year.</param>
/// <param name="weeksOfMonth">The weeks of the month.</param>
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)
{
}
/// <summary>Gets or sets the days of the week during which the task runs.</summary>
[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();
}
}
/// <summary>Gets or sets the months of the year during which the task runs.</summary>
[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();
}
}
/// <summary>Gets or sets a delay time that is randomly added to the start time of the trigger.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets a Boolean value that indicates that the task runs on the last week of the month.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets the weeks of the month during which the task runs.</summary>
[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();
}
}
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan ITriggerDelay.Delay
{
get => RandomDelay;
set => RandomDelay = value;
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
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;
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
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);
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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);
}
/// <summary>Reads the subclass XML for V1 streams.</summary>
/// <param name="reader">The reader.</param>
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();
}
/// <summary>Writes the subclass XML for V1 streams.</summary>
/// <param name="writer">The writer.</param>
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();
}
}
/// <summary>
/// Represents a trigger that starts a job based on a monthly schedule. For example, the task starts on specific days of specific months.
/// </summary>
[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
public sealed class MonthlyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
{
/// <summary>Creates an unbound instance of a <see cref="MonthlyTrigger"/>.</summary>
/// <param name="dayOfMonth">
/// The day of the month. This must be a value between 1 and 32. If this value is set to 32, then the <see
/// cref="RunOnLastDayOfMonth"/> value will be set and no days will be added regardless of the month.
/// </param>
/// <param name="monthsOfYear">The months of the year.</param>
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)
{
}
/// <summary>Gets or sets the days of the month during which the task runs.</summary>
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();
}
}
/// <summary>Gets or sets the months of the year during which the task runs.</summary>
[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();
}
}
/// <summary>Gets or sets a delay time that is randomly added to the start time of the trigger.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets a Boolean value that indicates that the task runs on the last day of the month.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan ITriggerDelay.Delay
{
get => RandomDelay;
set => RandomDelay = value;
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
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;
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
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);
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="indices">An array with an element for each bit of the mask which is ON.</param>
/// <returns>An integer to be interpreted as a mask.</returns>
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;
}
/// <summary>Compares two collections.</summary>
/// <typeparam name="T">Item type of collections.</typeparam>
/// <param name="left">The first collection.</param>
/// <param name="right">The second collection</param>
/// <returns><c>true</c> if the collections values are equal; <c>false</c> otherwise.</returns>
private static bool ListsEqual<T>(ICollection<T> left, ICollection<T> 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<T> l1 = new List<T>(left), l2 = new List<T>(right);
l1.Sort(); l2.Sort();
for (var i = 0; i < l1.Count; i++)
if (l1[i].CompareTo(l2[i]) != 0) return false;
return true;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="mask">An integer to be interpreted as a mask.</param>
/// <returns>An array with an element for each bit of the mask which is ON.</returns>
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;
}
/// <summary>Reads the subclass XML for V1 streams.</summary>
/// <param name="reader">The reader.</param>
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<int>();
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();
}
}
/// <summary>
/// Represents a trigger that starts a task when the task is registered or updated. Not available on Task Scheduler 1.0. <note>Only
/// available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later.</note>
/// </summary>
/// <remarks>The RegistrationTrigger will fire after the task is registered (saved). It is advisable to put in a delay.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a trigger that will fire the task 5 minutes after its registered
/// RegistrationTrigger rTrigger = new RegistrationTrigger();
/// rTrigger.Delay = TimeSpan.FromMinutes(5);
///]]>
/// </code>
/// </example>
[XmlType(IncludeInSchema = false)]
public sealed class RegistrationTrigger : Trigger, ITriggerDelay
{
/// <summary>Creates an unbound instance of a <see cref="RegistrationTrigger"/>.</summary>
public RegistrationTrigger() : base(TaskTriggerType.Registration) { }
internal RegistrationTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString() => Properties.Resources.TriggerRegistration1;
}
/// <summary>Defines how often the task is run and how long the repetition pattern is repeated after the task is started.</summary>
/// <remarks>This can be used directly or by assignment for a <see cref="Trigger"/>.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a time trigger with a repetition
/// var tt = new TimeTrigger(new DateTime().Now.AddHours(1));
/// // Set the time in between each repetition of the task after it starts to 30 minutes.
/// tt.Repetition.Interval = TimeSpan.FromMinutes(30); // Default is TimeSpan.Zero (or never)
/// // Set the time the task will repeat to 1 day.
/// tt.Repetition.Duration = TimeSpan.FromDays(1); // Default is TimeSpan.Zero (or never)
/// // Set the task to end even if running when the duration is over
/// tt.Repetition.StopAtDurationEnd = true; // Default is false;
///
/// // Do the same as above with a constructor
/// tt = new TimeTrigger(new DateTime().Now.AddHours(1)) { Repetition = new RepetitionPattern(TimeSpan.FromMinutes(30), TimeSpan.FromDays(1), true) };
///]]>
/// </code>
/// </example>
[XmlRoot("Repetition", Namespace = TaskDefinition.tns, IsNullable = true)]
[TypeConverter(typeof(RepetitionPatternConverter))]
public sealed class RepetitionPattern : IDisposable, IXmlSerializable, IEquatable<RepetitionPattern>, INotifyPropertyChanged
{
private readonly Trigger pTrigger;
private readonly IRepetitionPattern v2Pattern;
private TimeSpan unboundInterval = TimeSpan.Zero, unboundDuration = TimeSpan.Zero;
private bool unboundStopAtDurationEnd;
/// <summary>Initializes a new instance of the <see cref="RepetitionPattern"/> class.</summary>
/// <param name="interval">
/// 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.
/// </param>
/// <param name="duration">
/// The duration of how long the pattern is repeated. The minimum time allowed is one minute. If <c>TimeSpan.Zero</c> is specified,
/// the pattern is repeated indefinitely.
/// </param>
/// <param name="stopAtDurationEnd">
/// If set to <c>true</c> the running instance of the task is stopped at the end of repetition pattern duration.
/// </param>
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;
}
/// <summary>Occurs when a property value changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets or sets how long the pattern is repeated.</summary>
/// <value>
/// The duration that the pattern is repeated. The minimum time allowed is one minute. If <c>TimeSpan.Zero</c> is specified, the
/// pattern is repeated indefinitely.
/// </value>
/// <remarks>If you specify a repetition duration for a task, you must also specify the repetition interval.</remarks>
[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();
}
}
/// <summary>Gets or sets the amount of time between each restart of the task.</summary>
/// <value>
/// 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.
/// </value>
/// <remarks>If you specify a repetition duration for a task, you must also specify the repetition interval.</remarks>
/// <exception cref="System.ArgumentOutOfRangeException">
/// The maximum time allowed is 31 days, and the minimum time allowed is 1 minute.
/// </exception>
[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();
}
}
/// <summary>
/// Gets or sets a Boolean value that indicates if a running instance of the task is stopped at the end of repetition pattern duration.
/// </summary>
[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();
}
}
/// <summary>Releases all resources used by this class.</summary>
public void Dispose()
{
if (v2Pattern != null) Marshal.ReleaseComObject(v2Pattern);
}
/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
// ReSharper disable once BaseObjectEqualsIsObjectEquals
public override bool Equals(object obj) => obj is RepetitionPattern pattern ? Equals(pattern) : base.Equals(obj);
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
public bool Equals(RepetitionPattern other) => other != null && Duration == other.Duration && Interval == other.Interval && StopAtDurationEnd == other.StopAtDurationEnd;
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode() => new { A = Duration, B = Interval, C = StopAtDurationEnd }.GetHashCode();
/// <summary>Determines whether any properties for this <see cref="RepetitionPattern"/> have been set.</summary>
/// <returns><c>true</c> if properties have been set; otherwise, <c>false</c>.</returns>
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;
}
/// <summary>Called when a property has changed to notify any attached elements.</summary>
/// <param name="propertyName">Name of the property.</param>
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;
}
}
/// <summary>
/// Triggers tasks for console connect or disconnect, remote connect or disconnect, or workstation lock or unlock notifications.
/// <note>Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later.</note>
/// </summary>
/// <remarks>
/// The SessionStateChangeTrigger will fire after six different system events: connecting or disconnecting locally or remotely, or
/// locking or unlocking the session.
/// </remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.ConsoleConnect, UserId = "joe" };
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.ConsoleDisconnect };
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.RemoteConnect };
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.RemoteDisconnect };
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.SessionLock, UserId = "joe" };
///new SessionStateChangeTrigger { StateChange = TaskSessionStateChangeType.SessionUnlock };
///]]>
/// </code>
/// </example>
[XmlType(IncludeInSchema = false)]
public sealed class SessionStateChangeTrigger : Trigger, ITriggerDelay, ITriggerUserId
{
/// <summary>Creates an unbound instance of a <see cref="SessionStateChangeTrigger"/>.</summary>
public SessionStateChangeTrigger() : base(TaskTriggerType.SessionStateChange) { }
/// <summary>Initializes a new instance of the <see cref="SessionStateChangeTrigger"/> class.</summary>
/// <param name="stateChange">The state change.</param>
/// <param name="userId">The user identifier.</param>
public SessionStateChangeTrigger(TaskSessionStateChangeType stateChange, string userId = null) : this() { StateChange = stateChange; UserId = userId; }
internal SessionStateChangeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger)
{
}
/// <summary>Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.</summary>
[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();
}
}
/// <summary>Gets or sets the kind of Terminal Server session change that would trigger a task launch.</summary>
[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();
}
}
/// <summary>
/// Gets or sets the user for the Terminal Server session. When a session state change is detected for this user, a task is started.
/// </summary>
[DefaultValue(null)]
public string UserId
{
get => v2Trigger != null ? ((ISessionStateChangeTrigger)v2Trigger).UserId : GetUnboundValueOrDefault<string>(nameof(UserId));
set
{
if (v2Trigger != null)
((ISessionStateChangeTrigger)v2Trigger).UserId = value;
else
unboundValues[nameof(UserId)] = value;
OnNotifyPropertyChanged();
}
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
public override void CopyProperties(Trigger sourceTrigger)
{
base.CopyProperties(sourceTrigger);
if (sourceTrigger is SessionStateChangeTrigger st && !StateChangeIsSet())
StateChange = st.StateChange;
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
public override bool Equals(Trigger other) => other is SessionStateChangeTrigger st && base.Equals(st) && StateChange == st.StateChange;
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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);
}
/// <summary>Returns a value indicating if the StateChange property has been set.</summary>
/// <returns>StateChange property has been set.</returns>
private bool StateChangeIsSet() => v2Trigger != null || (unboundValues?.ContainsKey("StateChange") ?? false);
}
/// <summary>Represents a trigger that starts a task at a specific date and time.</summary>
/// <remarks>A TimeTrigger runs at a specified date and time.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a trigger that runs the last minute of this year
/// TimeTrigger tTrigger = new TimeTrigger();
/// tTrigger.StartBoundary = new DateTime(DateTime.Today.Year, 12, 31, 23, 59, 0);
///]]>
/// </code>
/// </example>
public sealed class TimeTrigger : Trigger, ITriggerDelay, ICalendarTrigger
{
/// <summary>Creates an unbound instance of a <see cref="TimeTrigger"/>.</summary>
public TimeTrigger() : base(TaskTriggerType.Time) { }
/// <summary>Creates an unbound instance of a <see cref="TimeTrigger"/> and assigns the execution time.</summary>
/// <param name="startBoundary">Date and time for the trigger to fire.</param>
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)
{
}
/// <summary>Gets or sets a delay time that is randomly added to the start time of the trigger.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan ITriggerDelay.Delay
{
get => RandomDelay;
set => RandomDelay = value;
}
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString() => string.Format(Properties.Resources.TriggerTime1, AdjustToLocal(StartBoundary));
}
/// <summary>
/// Abstract base class which provides the common properties that are inherited by all trigger classes. A trigger can be created using
/// the <see cref="TriggerCollection.Add{TTrigger}"/> or the <see cref="TriggerCollection.AddNew"/> method.
/// </summary>
public abstract partial class Trigger : IDisposable, ICloneable, IEquatable<Trigger>, IComparable, IComparable<Trigger>, 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;
/// <summary>In testing and may change. Do not use until officially introduced into library.</summary>
protected Dictionary<string, object> unboundValues = new Dictionary<string, object>();
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);
}
/// <summary>Occurs when a property value changes.</summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>Gets or sets a Boolean value that indicates whether the trigger is enabled.</summary>
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();
}
}
/// <summary>
/// Gets or sets the date and time when the trigger is deactivated. The trigger cannot start the task after it is deactivated.
/// <note>While the maximum value for this property is <see cref="DateTime.MaxValue"/>, the Windows Task Scheduler management
/// application that is part of the OS will fail if this value is greater than December 31, 9998.</note>
/// </summary>
/// <remarks>
/// <para>
/// 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 <see
/// cref="DateTime"/> structure.
/// </para>
/// <para>
/// Version 2 (1.2 or higher) of the native library only allows for both date and time and all <see cref="DateTime.Kind"/> values.
/// However, the user interface and <see cref="Trigger.ToString()"/> 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.
/// </para>
/// </remarks>
[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();
}
}
/// <summary>
/// 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.
/// </summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets the identifier for the trigger. Cannot set with Task Scheduler 1.0.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[DefaultValue(null)]
[XmlIgnore]
public string Id
{
get => v2Trigger != null ? v2Trigger.Id : GetUnboundValueOrDefault<string>(nameof(Id));
set
{
if (v2Trigger != null)
v2Trigger.Id = value;
else if (v1Trigger != null)
throw new NotV1SupportedException();
else
unboundValues[nameof(Id)] = value;
OnNotifyPropertyChanged();
}
}
/// <summary>
/// Gets a <see cref="RepetitionPattern"/> instance that indicates how often the task is run and how long the repetition pattern is
/// repeated after the task is started.
/// </summary>
public RepetitionPattern Repetition
{
get => repititionPattern ??= new RepetitionPattern(this);
set
{
Repetition.Set(value);
OnNotifyPropertyChanged();
}
}
/// <summary>Gets or sets the date and time when the trigger is activated.</summary>
/// <remarks>
/// <para>
/// Version 1 (1.1 on all systems prior to Vista) of the native library only allows for <see cref="DateTime"/> values where the <see
/// cref="DateTime.Kind"/> is unspecified. If the DateTime value Kind is <see cref="DateTimeKind.Local"/> then it will be used as
/// is. If the DateTime value Kind is <see cref="DateTimeKind.Utc"/> then it will be converted to the local time and then used.
/// </para>
/// <para>
/// Version 2 (1.2 or higher) of the native library only allows for all <see cref="DateTime.Kind"/> values. However, the user
/// interface and <see cref="Trigger.ToString()"/> 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.
/// </para>
/// <para>
/// Under Version 2, when converting the string used in the native library for this value (ITrigger.Startboundary) this library will
/// behave as follows:
/// <list type="bullet">
/// <item>
/// <description>YYYY-MM-DDTHH:MM:SS format uses DateTimeKind.Unspecified and the time specified.</description>
/// </item>
/// <item>
/// <description>YYYY-MM-DDTHH:MM:SSZ format uses DateTimeKind.Utc and the time specified as the GMT time.</description>
/// </item>
/// <item>
/// <description>YYYY-MM-DDTHH:MM:SS±HH:MM format uses DateTimeKind.Local and the time specified in that time zone.</description>
/// </item>
/// </list>
/// </para>
/// </remarks>
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();
}
}
/// <summary>Gets the type of the trigger.</summary>
/// <value>The <see cref="TaskTriggerType"/> of the trigger.</value>
[XmlIgnore]
public TaskTriggerType TriggerType => ttype;
/// <summary>Creates the specified trigger.</summary>
/// <param name="triggerType">Type of the trigger to instantiate.</param>
/// <returns><see cref="Trigger"/> of specified type.</returns>
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;
}
/// <summary>Creates a new <see cref="Trigger"/> that is an unbound copy of this instance.</summary>
/// <returns>A new <see cref="Trigger"/> that is an unbound copy of this instance.</returns>
public virtual object Clone()
{
var ret = CreateTrigger(TriggerType);
ret.CopyProperties(this);
return ret;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="other">An object to compare with this instance.</param>
/// <returns>A value that indicates the relative order of the objects being compared.</returns>
public int CompareTo(Trigger other) => string.Compare(Id, other?.Id, StringComparison.InvariantCulture);
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
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 */ }
}
/// <summary>Releases all resources used by this class.</summary>
public virtual void Dispose()
{
if (v2Trigger != null)
Marshal.ReleaseComObject(v2Trigger);
if (v1Trigger != null)
Marshal.ReleaseComObject(v1Trigger);
}
/// <summary>Determines whether the specified <see cref="object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
// ReSharper disable once BaseObjectEqualsIsObjectEquals
public override bool Equals(object obj) => obj is Trigger trigger ? Equals(trigger) : base.Equals(obj);
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
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;
}
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
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();
/// <summary>Sets the repetition.</summary>
/// <param name="interval">
/// 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.
/// </param>
/// <param name="duration">
/// The duration of how long the pattern is repeated. The minimum time allowed is one minute. If <c>TimeSpan.Zero</c> is specified,
/// the pattern is repeated indefinitely.
/// </param>
/// <param name="stopAtDurationEnd">
/// if set to <c>true</c> the running instance of the task is stopped at the end of repetition pattern duration.
/// </param>
[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;
}
/// <summary>Returns a string representing this trigger.</summary>
/// <returns>String value of trigger.</returns>
public override string ToString() => v1Trigger != null ? v1Trigger.GetTriggerString() : V2GetTriggerString() + V2BaseTriggerString();
/// <summary>Returns a <see cref="string"/> that represents this trigger in a specific language.</summary>
/// <param name="culture">The language of the resulting string.</param>
/// <returns>String value of trigger.</returns>
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();
}
}
/// <summary>Gets the best time span string.</summary>
/// <param name="span">The <see cref="TimeSpan"/> to display.</param>
/// <returns>Either the full string representation created by TimeSpan2 or the default TimeSpan representation.</returns>
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<string>(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();
}
/// <summary>Assigns the unbound TriggerData structure to the V1 trigger instance.</summary>
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);
}
/// <summary>Checks the bind value for any conversion.</summary>
/// <param name="key">The key (property) name.</param>
/// <param name="o">The value.</param>
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);
}
}
/// <summary>Gets the unbound value or a default.</summary>
/// <typeparam name="T">Return type.</typeparam>
/// <param name="prop">The property name.</param>
/// <param name="def">The default value if not found in unbound value list.</param>
/// <returns>The unbound value, if set, or the default value.</returns>
protected T GetUnboundValueOrDefault<T>(string prop, T def = default) => unboundValues.TryGetValue(prop, out var val) ? (T)val : def;
/// <summary>Called when a property has changed to notify any attached elements.</summary>
/// <param name="propertyName">Name of the property.</param>
protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
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();
}
}
/// <summary>
/// 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.
/// </summary>
/// <remarks>A WeeklyTrigger runs at a specified time on specified days of the week every week or interval of weeks.</remarks>
/// <example>
/// <code lang="cs">
///<![CDATA[
/// // Create a trigger that runs on Monday every third week just after midnight.
/// WeeklyTrigger wTrigger = new WeeklyTrigger();
/// wTrigger.StartBoundary = DateTime.Today + TimeSpan.FromSeconds(15);
/// wTrigger.DaysOfWeek = DaysOfTheWeek.Monday;
/// wTrigger.WeeksInterval = 3;
///]]>
/// </code>
/// </example>
[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
public sealed class WeeklyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
{
/// <summary>Creates an unbound instance of a <see cref="WeeklyTrigger"/>.</summary>
/// <param name="daysOfWeek">The days of the week.</param>
/// <param name="weeksInterval">The interval between the weeks in the schedule.</param>
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)
{
}
/// <summary>Gets or sets the days of the week on which the task runs.</summary>
[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();
}
}
/// <summary>Gets or sets a delay time that is randomly added to the start time of the trigger.</summary>
/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
[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();
}
}
/// <summary>Gets or sets the interval between the weeks in the schedule.</summary>
[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();
}
}
/// <summary>Gets or sets a value that indicates the amount of time before the task is started.</summary>
/// <value>The delay duration.</value>
TimeSpan ITriggerDelay.Delay
{
get => RandomDelay;
set => RandomDelay = value;
}
/// <summary>
/// Copies the properties from another <see cref="Trigger"/> the current instance. This will not copy any properties associated with
/// any derived triggers except those supporting the <see cref="ITriggerDelay"/> interface.
/// </summary>
/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
public override void CopyProperties(Trigger sourceTrigger)
{
base.CopyProperties(sourceTrigger);
if (sourceTrigger is WeeklyTrigger wt)
{
DaysOfWeek = wt.DaysOfWeek;
WeeksInterval = wt.WeeksInterval;
}
}
/// <summary>Indicates whether the current object is equal to another object of the same type.</summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.</returns>
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);
/// <summary>Gets the non-localized trigger string for V2 triggers.</summary>
/// <returns>String describing the trigger.</returns>
protected override string V2GetTriggerString()
{
var days = TaskEnumGlobalizer.GetString(DaysOfWeek);
return string.Format(WeeksInterval == 1 ? Properties.Resources.TriggerWeekly1Week : Properties.Resources.TriggerWeeklyMultWeeks, AdjustToLocal(StartBoundary), days, WeeksInterval);
}
/// <summary>Reads the subclass XML for V1 streams.</summary>
/// <param name="reader">The reader.</param>
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();
}
/// <summary>Writes the subclass XML for V1 streams.</summary>
/// <param name="writer">The writer.</param>
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, @"\<(?<T>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);
}
}
}