Refactoring and style changes - no functional changes.

--- TaskService/Trigger.cs
+++ TaskService/Trigger.cs
@@ -1,16 +1,15 @@
-using System;
+using JetBrains.Annotations;
+using Microsoft.Win32.TaskScheduler.V2Interop;
+using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
 using System.Runtime.InteropServices;
 using System.Text;
 using System.Xml.Serialization;
-using JetBrains.Annotations;

 namespace Microsoft.Win32.TaskScheduler
 {
-	#region Enumerations
-
 	/// Values for days of the week (Monday, Tuesday, etc.)
 	[Flags]
 	public enum DaysOfTheWeek : short
@@ -113,719 +112,55 @@
 		AllWeeks = 0x1F
 	}

-	#endregion Enumerations
-
-	#region Interfaces
-
-	///
-	/// Interface that categorizes the trigger as a calendar trigger.
-	///
+	/// Interface that categorizes the trigger as a calendar trigger.
 	public interface ICalendarTrigger { }

-	///
-	/// Interface for triggers that support a delay.
-	///
+	/// Interface for triggers that support a delay.
 	public interface ITriggerDelay
 	{
-		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
-		///
+		/// Gets or sets a value that indicates the amount of time before the task is started.
 		/// <value>The delay duration.</value>
 		TimeSpan Delay { get; set; }
 	}

-	///
-	/// Interface for triggers that support a user identifier.
-	///
+	/// Interface for triggers that support a user identifier.
 	public interface ITriggerUserId
 	{
-		///
-		/// Gets or sets the user for the <see cref="Trigger"/>.
-		///
+		/// Gets or sets the user for the <see cref="Trigger"/>.
 		string UserId { get; set; }
 	}

-	#endregion Interfaces
-
-	///
-	/// 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"/> or the <see cref="TriggerCollection.AddNew"/> method.
-	///
-	public abstract partial class Trigger : IDisposable, ICloneable, IEquatable<Trigger>, IComparable, IComparable<Trigger>
-	{
-		internal const string V2BoundaryDateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFK";
-		internal static readonly CultureInfo DefaultDateCulture = CultureInfo.CreateSpecificCulture("en-US");
-
-		private TaskTriggerType ttype;
-		internal V1Interop.ITaskTrigger v1Trigger;
-		internal V1Interop.TaskTrigger v1TriggerData;
-		internal V2Interop.ITrigger v2Trigger;
-
-		/// In testing and may change. Do not use until officially introduced into library.
-		protected Dictionary<string, object> unboundValues = new Dictionary<string, object>();
-
-		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] V2Interop.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);
-		}
-
-		///
-		/// Gets or sets a Boolean value that indicates whether the trigger is enabled.
-		///
-		public bool Enabled
-		{
-			get
-			{
-				if (v2Trigger != null)
-					return v2Trigger.Enabled;
-				return 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;
-				}
-			}
-		}
-
-		///
-		/// Gets or sets the date and time when the trigger is deactivated. The trigger cannot start the task after it is deactivated.
-		///
-		/// <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)
-					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;
-				}
-			}
-		}
-
-		///
-		/// 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.
-		///
-		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
-		[DefaultValue(typeof(TimeSpan), "00:00:00")]
-		[XmlIgnore]
-		public TimeSpan ExecutionTimeLimit
-		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(v2Trigger.ExecutionTimeLimit);
-				return 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;
-			}
-		}
-
-		///
-		/// Gets or sets the identifier for the trigger. Cannot set with Task Scheduler 1.0.
-		///
-		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
-		[DefaultValue(null)]
-		[XmlIgnore]
-		public string Id
-		{
-			get
-			{
-				if (v2Trigger != null)
-					return v2Trigger.Id;
-				return GetUnboundValueOrDefault<string>(nameof(Id));
-			}
-			set
-			{
-				if (v2Trigger != null)
-					v2Trigger.Id = value;
-				else if (v1Trigger != null)
-					throw new NotV1SupportedException();
-				else
-					unboundValues[nameof(Id)] = value;
-			}
-		}
-
-		///
-		/// 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.
-		///
-		public RepetitionPattern Repetition
-		{
-			get { return repititionPattern ?? (repititionPattern = new RepetitionPattern(this)); }
-			set { Repetition.Set(value); }
-		}
-
-		///
-		/// Gets or sets the date and time when the trigger is activated.
-		///
-		/// <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)
-				{
-					if (string.IsNullOrEmpty(v2Trigger.StartBoundary))
-					{
-						return DateTime.MinValue;
-					}
-					else
-					{
-						DateTime ret = DateTime.Parse(v2Trigger.StartBoundary, DefaultDateCulture);
-						if (v2Trigger.StartBoundary.EndsWith("Z"))
-							ret = ret.ToUniversalTime();
-						return ret;
-					}
-				}
-				return GetUnboundValueOrDefault(nameof(StartBoundary), v1TriggerData.BeginDate);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					v2Trigger.StartBoundary = value == DateTime.MinValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture);
-				else
-				{
-					v1TriggerData.BeginDate = value;
-					if (v1Trigger != null)
-						SetV1TriggerData();
-					else
-						unboundValues[nameof(StartBoundary)] = value;
-				}
-			}
-		}
-
-		///
-		/// Gets the type of the trigger.
-		///
-		/// <value>The <see cref="TaskTriggerType"/> of the trigger.</value>
-		[XmlIgnore]
-		public TaskTriggerType TriggerType => ttype;
-
-		///
-		/// Creates a new <see cref="Trigger"/> that is an unbound copy of this instance.
-		///
-		/// <returns>
-		/// A new <see cref="Trigger"/> that is an unbound copy of this instance.
-		/// </returns>
-		public virtual object Clone()
-		{
-			Trigger ret = CreateTrigger(TriggerType);
-			ret.CopyProperties(this);
-			return ret;
-		}
-
-		/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
-		/// <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);
-
-		///
-		/// 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.
-		///
-		/// <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 { }
-			Repetition.Duration = sourceTrigger.Repetition.Duration;
-			Repetition.Interval = sourceTrigger.Repetition.Interval;
-			Repetition.StopAtDurationEnd = sourceTrigger.Repetition.StopAtDurationEnd;
-			StartBoundary = sourceTrigger.StartBoundary;
-			if (sourceTrigger is ITriggerDelay && this is ITriggerDelay)
-				try { ((ITriggerDelay)this).Delay = ((ITriggerDelay)sourceTrigger).Delay; }
-				catch { }
-			if (sourceTrigger is ITriggerUserId && this is ITriggerUserId)
-				try { ((ITriggerUserId)this).UserId = ((ITriggerUserId)sourceTrigger).UserId; }
-				catch { }
-		}
-
-		///
-		/// Releases all resources used by this class.
-		///
-		public virtual void Dispose()
-		{
-			if (v2Trigger != null)
-				Marshal.ReleaseComObject(v2Trigger);
-			if (v1Trigger != null)
-				Marshal.ReleaseComObject(v1Trigger);
-		}
-
-		///
-		/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
-		///
-		/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
-		/// <returns>
-		///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
-		/// </returns>
-		public override bool Equals(object obj)
-		{
-			if (obj is Trigger)
-				return Equals((Trigger)obj);
-			return base.Equals(obj);
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
-		/// <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)
-		{
-			bool 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 && this is ITriggerDelay)
-				try { ret = ret && ((ITriggerDelay)this).Delay == ((ITriggerDelay)other).Delay; }
-				catch { }
-			if (other is ITriggerUserId && this is ITriggerUserId)
-				try { ret = ret && ((ITriggerUserId)this).UserId == ((ITriggerUserId)other).UserId; }
-				catch { }
-			return ret;
-		}
-
-		///
-		/// Returns a hash code for this instance.
-		///
-		/// <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();
-
-		///
-		/// Sets the repetition.
-		///
-		/// <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;
-		}
-
-		///
-		/// Returns a string representing this trigger.
-		///
-		/// <returns>String value of trigger.</returns>
-		public override string ToString()
-		{
-			if (v1Trigger != null)
-				return v1Trigger.GetTriggerString();
-			return V2GetTriggerString() + V2BaseTriggerString();
-		}
-
-		///
-		/// Returns a <see cref="System.String"/> that represents this trigger in a specific language.
-		///
-		/// <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();
-		}
-
-		private static TaskTriggerType ConvertFromV1TriggerType(V1Interop.TaskTriggerType v1Type)
-		{
-			int v2tt = (int)v1Type + 1;
-			if (v2tt > 6) v2tt++;
-			return (TaskTriggerType)v2tt;
-		}
-
-		internal static V1Interop.TaskTriggerType ConvertToV1TriggerType(TaskTriggerType type)
-		{
-			if (type == TaskTriggerType.Registration || type == TaskTriggerType.Event || type == TaskTriggerType.SessionStateChange)
-				throw new NotV1SupportedException();
-			int v1tt = (int)type - 1;
-			if (v1tt >= 7) v1tt--;
-			return (V1Interop.TaskTriggerType)v1tt;
-		}
-
-		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 = null;
-			switch (triggerType)
-			{
-				case V1Interop.TaskTriggerType.RunOnce:
-					t = new TimeTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.RunDaily:
-					t = new DailyTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.RunWeekly:
-					t = new WeeklyTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.RunMonthly:
-					t = new MonthlyTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.RunMonthlyDOW:
-					t = new MonthlyDOWTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.OnIdle:
-					t = new IdleTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.OnSystemStart:
-					t = new BootTrigger(trigger);
-					break;
-				case V1Interop.TaskTriggerType.OnLogon:
-					t = new LogonTrigger(trigger);
-					break;
-				default:
-					throw new ArgumentOutOfRangeException(nameof(triggerType), triggerType, null);
-			}
-			return t;
-		}
-
-		internal static Trigger CreateTrigger([NotNull] V2Interop.ITrigger iTrigger, V2Interop.ITaskDefinition iDef = null)
-		{
-			switch (iTrigger.Type)
-			{
-				case TaskTriggerType.Boot:
-					return new BootTrigger((V2Interop.IBootTrigger)iTrigger);
-				case TaskTriggerType.Daily:
-					return new DailyTrigger((V2Interop.IDailyTrigger)iTrigger);
-				case TaskTriggerType.Event:
-					return new EventTrigger((V2Interop.IEventTrigger)iTrigger);
-				case TaskTriggerType.Idle:
-					return new IdleTrigger((V2Interop.IIdleTrigger)iTrigger);
-				case TaskTriggerType.Logon:
-					return new LogonTrigger((V2Interop.ILogonTrigger)iTrigger);
-				case TaskTriggerType.Monthly:
-					return new MonthlyTrigger((V2Interop.IMonthlyTrigger)iTrigger);
-				case TaskTriggerType.MonthlyDOW:
-					return new MonthlyDOWTrigger((V2Interop.IMonthlyDOWTrigger)iTrigger);
-				case TaskTriggerType.Registration:
-					return new RegistrationTrigger((V2Interop.IRegistrationTrigger)iTrigger);
-				case TaskTriggerType.SessionStateChange:
-					return new SessionStateChangeTrigger((V2Interop.ISessionStateChangeTrigger)iTrigger);
-				case TaskTriggerType.Time:
-					return new TimeTrigger((V2Interop.ITimeTrigger)iTrigger);
-				case TaskTriggerType.Weekly:
-					return new WeeklyTrigger((V2Interop.IWeeklyTrigger)iTrigger);
-				case TaskTriggerType.Custom:
-					CustomTrigger ct = new CustomTrigger(iTrigger);
-					if (iDef != null)
-						try { ct.UpdateFromXml(iDef.XmlText); } catch { }
-					return ct;
-				default:
-					throw new ArgumentOutOfRangeException();
-			}
-		}
-
-		///
-		/// Creates the specified trigger.
-		///
-		/// <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;
-		}
-
-		internal virtual void Bind([NotNull] V1Interop.ITask iTask)
-		{
-			if (v1Trigger == null)
-			{
-				ushort idx;
-				v1Trigger = iTask.CreateTrigger(out idx);
-			}
-			SetV1TriggerData();
-		}
-
-		internal virtual void Bind([NotNull] V2Interop.ITaskDefinition iTaskDef)
-		{
-			V2Interop.ITriggerCollection iTriggers = iTaskDef.Triggers;
-			v2Trigger = iTriggers.Create(ttype);
-			Marshal.ReleaseComObject(iTriggers);
-			foreach (string key in unboundValues.Keys)
-			{
-				try
-				{
-					object o = unboundValues[key];
-					CheckBindValue(key, ref o);
-					v2Trigger.GetType().InvokeMember(key, System.Reflection.BindingFlags.SetProperty, null, v2Trigger, new[] {o});
-				}
-				catch (System.Reflection.TargetInvocationException tie) { throw tie.InnerException; }
-				catch { }
-			}
-			unboundValues.Clear();
-			unboundValues = null;
-
-			repititionPattern = new RepetitionPattern(this);
-			repititionPattern.Bind();
-		}
-
-		///
-		/// Checks the bind value for any conversion.
-		///
-		/// <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)
-				o = Task.TimeSpanToString((TimeSpan)o);
-			if (o is DateTime)
-			{
-				if ((key == "EndBoundary" && ((DateTime)o) == DateTime.MaxValue) || (key == "StartBoundary" && ((DateTime)o) == DateTime.MinValue))
-					o = null;
-				else
-					o = ((DateTime)o).ToString(V2BoundaryDateFormat, DefaultDateCulture);
-			}
-		}
-
-		/// Assigns the unbound TriggerData structure to the V1 trigger instance.
-		internal void SetV1TriggerData()
-		{
-			if (v1TriggerData.MinutesInterval != 0 && v1TriggerData.MinutesInterval >= v1TriggerData.MinutesDuration)
-				throw new ArgumentException("Trigger.Repetition.Interval must be less than Trigger.Repetition.Duration under Task Scheduler 1.0.");
-			if (v1TriggerData.BeginDate == DateTime.MinValue)
-				v1TriggerData.BeginDate = DateTime.Now;
-			v1Trigger?.SetTrigger(ref v1TriggerData);
-			System.Diagnostics.Debug.WriteLine(v1TriggerData);
-		}
-
-		///
-		/// Gets the unbound value or a default.
-		///
-		/// <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(T))
-		{
-			object val;
-			return unboundValues.TryGetValue(prop, out val) ? (T)val : def;
-		}
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
-		/// <returns>String describing the trigger.</returns>
-		protected virtual string V2GetTriggerString() => string.Empty;
-
-		internal static DateTime AdjustToLocal(DateTime dt) => dt.Kind == DateTimeKind.Utc ? dt.ToLocalTime() : dt;
-
-		private string V2BaseTriggerString()
-		{
-			StringBuilder ret = new StringBuilder();
-			if (Repetition.Interval != TimeSpan.Zero)
-			{
-				string sduration;
-				if (Repetition.Duration == TimeSpan.Zero)
-					sduration = Properties.Resources.TriggerDuration0;
-				else
-					sduration = 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();
-		}
-
-		private static Type timeSpan2Type;
-		private static bool? foundTimeSpan2;
-
-		///
-		/// Gets the best time span string.
-		///
-		/// <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 { }
-			}
-
-			// 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 { }
-			}
-
-			return span.ToString();
-		}
-
-		int IComparable.CompareTo(object obj) => CompareTo(obj as Trigger);
-	}
-
-	///
-	/// Represents a trigger that starts a task when the system is booted.
-	///
+	/// Represents a trigger that starts a task when the system is booted.
 	/// <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><![CDATA[
+	/// <example>
+	///
+	/// <![CDATA[
 	/// // Create trigger that fires 5 minutes after the system starts.
 	/// BootTrigger bt = new BootTrigger();
 	/// bt.Delay = TimeSpan.FromMinutes(5);  // V2 only
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	public sealed class BootTrigger : Trigger, ITriggerDelay
 	{
-		///
-		/// Creates an unbound instance of a <see cref="BootTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="BootTrigger"/>.
 		public BootTrigger() : base(TaskTriggerType.Boot) { }

 		internal BootTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnSystemStart) { }

-		internal BootTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
-		///
+		internal BootTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan Delay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IBootTrigger)v2Trigger).Delay);
-				return GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IBootTrigger)v2Trigger).Delay = Task.TimeSpanToString(value);
+			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
@@ -833,34 +168,45 @@
 			}
 		}

-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString() => Properties.Resources.TriggerBoot1;
 	}

 	///
-	/// 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>
+	/// 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>
 	///
 	public sealed class CustomTrigger : Trigger, ITriggerDelay
 	{
+		private readonly NamedValueCollection nvc = new NamedValueCollection();
 		private TimeSpan delay = TimeSpan.MinValue;
 		private string name = string.Empty;
-		private readonly NamedValueCollection nvc = new NamedValueCollection();
-
-		internal CustomTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
-		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString() => TaskScheduler.Properties.Resources.TriggerCustom1;
-
-		///
-		/// Updates custom properties from XML provided by definition.
-		///
+
+		internal CustomTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets a value that indicates the amount of time between the trigger events and when the task is started.
+		/// <exception cref="System.NotImplementedException">This value cannot be set.</exception>
+		public TimeSpan Delay
+		{
+			get => delay;
+			set => throw new NotImplementedException();
+		}
+
+		/// Gets the name of the custom trigger type.
+		/// <value>The name of the XML element representing this custom trigger.</value>
+		public string Name => name;
+
+		/// Gets the properties from the XML definition if possible.
+		[XmlArray, XmlArrayItem("Property")]
+		public NamedValueCollection Properties => nvc;
+
+		/// Clones this instance.
+		/// <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.");
+
+		/// Updates custom properties from XML provided by definition.
 		/// <param name="xml">The XML from the TaskDefinition.</param>
 		internal void UpdateFromXml(string xml)
 		{
@@ -878,171 +224,58 @@
 					if (nodes.Count == 1)
 						elem = nodes[0];
 				}
-				if (elem != null)
-				{
-					name = elem.LocalName;
-					foreach (System.Xml.XmlNode node in elem.ChildNodes)
+
+				if (elem == null) return;
+
+				name = elem.LocalName;
+				foreach (System.Xml.XmlNode node in elem.ChildNodes)
+				{
+					switch (node.LocalName)
 					{
-						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;
-						}
+						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 { }
-		}
-
-		///
-		/// Gets the properties from the XML definition if possible.
-		///
-		[XmlArray, XmlArrayItem("Property")]
-		public NamedValueCollection Properties => nvc;
-
-		///
-		/// Gets the name of the custom trigger type.
-		///
-		/// <value>
-		/// The name of the XML element representing this custom trigger.
-		/// </value>
-		public string Name => name;
-
-		///
-		/// Gets a value that indicates the amount of time between the trigger events and when the task is started.
-		///
-		/// <exception cref="System.NotImplementedException">This value cannot be set.</exception>
-		public TimeSpan Delay
-		{
-			get { return delay; }
-			set { throw new NotImplementedException(); }
-		}
-
-		///
-		/// Clones this instance.
-		///
-		/// <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."); }
+			catch { /* ignored */ }
+		}
+
+		/// Gets the non-localized trigger string for V2 triggers.
+		/// <returns>String describing the trigger.</returns>
+		protected override string V2GetTriggerString() => TaskScheduler.Properties.Resources.TriggerCustom1;
 	}

-	internal static class CalendarTrigger
-	{
-		internal delegate void CalendarXmlReader(System.Xml.XmlReader reader);
-
-		internal delegate void CalendarXmlWriter(System.Xml.XmlWriter writer);
-
-		internal static Trigger GetTriggerFromXml([NotNull] System.Xml.XmlReader reader)
-		{
-			Trigger t = null;
-			string 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 (System.IO.StringReader ms = new System.IO.StringReader(xml))
-					{
-						using (System.Xml.XmlReader 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();
-		}
-
-		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);
-		}
-	}
-
 	///
-	/// Represents a trigger that starts a task based on a daily schedule. For example, the task starts at a specific time every day, every other day, every third day, and so on.
+	/// Represents a trigger that starts a task based on a daily schedule. For example, the task starts at a specific time every day, every other day, every
+	/// third day, and so on.
 	///
 	/// <remarks>A DailyTrigger will fire at a specified time every day or interval of days.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![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
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
 	public sealed class DailyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
 	{
-		///
-		/// Creates an unbound instance of a <see cref="DailyTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="DailyTrigger"/>.
 		/// <param name="daysInterval">Interval between the days in the schedule.</param>
 		public DailyTrigger(short daysInterval = 1) : base(TaskTriggerType.Daily) { DaysInterval = daysInterval; }

@@ -1052,24 +285,22 @@
 				v1TriggerData.Data.daily.DaysInterval = 1;
 		}

-		internal DailyTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Sets or retrieves the interval between the days in the schedule.
-		///
+		internal DailyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Sets or retrieves the interval between the days in the schedule.
 		[DefaultValue(1)]
 		public short DaysInterval
 		{
 			get
 			{
 				if (v2Trigger != null)
-					return ((V2Interop.IDailyTrigger)v2Trigger).DaysInterval;
+					return ((IDailyTrigger)v2Trigger).DaysInterval;
 				return (short)v1TriggerData.Data.daily.DaysInterval;
 			}
 			set
 			{
 				if (v2Trigger != null)
-					((V2Interop.IDailyTrigger)v2Trigger).DaysInterval = value;
+					((IDailyTrigger)v2Trigger).DaysInterval = value;
 				else
 				{
 					v1TriggerData.Data.daily.DaysInterval = (ushort)value;
@@ -1081,24 +312,17 @@
 			}
 		}

-		///
-		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
-		///
+		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan RandomDelay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IDailyTrigger)v2Trigger).RandomDelay);
-				return GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IDailyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value);
+			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
@@ -1106,178 +330,131 @@
 			}
 		}

-		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
-		///
+		/// Gets or sets a value that indicates the amount of time before the task is started.
 		/// <value>The delay duration.</value>
 		TimeSpan ITriggerDelay.Delay
 		{
-			get { return RandomDelay; }
-			set { RandomDelay = value; }
+			get => RandomDelay;
+			set => RandomDelay = value;
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
 		public override void CopyProperties(Trigger sourceTrigger)
 		{
 			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType())
-			{
-				DaysInterval = ((DailyTrigger)sourceTrigger).DaysInterval;
-			}
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
+			if (sourceTrigger is DailyTrigger dt)
+			{
+				DaysInterval = dt.DaysInterval;
+			}
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
 		/// <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) => base.Equals(other) && DaysInterval == ((DailyTrigger)other).DaysInterval;
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		/// <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); }
+
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString()
-		{
-			if (DaysInterval == 1)
-				return string.Format(Properties.Resources.TriggerDaily1, AdjustToLocal(StartBoundary));
-			return string.Format(Properties.Resources.TriggerDaily2, AdjustToLocal(StartBoundary), DaysInterval);
-		}
-
-		System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null;
+		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();
 		}

-		void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { CalendarTrigger.ReadXml(reader, this, ReadMyXml); }
-
 		private void WriteMyXml(System.Xml.XmlWriter writer)
 		{
 			writer.WriteStartElement("ScheduleByDay");
 			writer.WriteElementString("DaysInterval", DaysInterval.ToString());
 			writer.WriteEndElement();
 		}
-
-		void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { CalendarTrigger.WriteXml(writer, this, WriteMyXml); }
 	}

 	///
-	/// 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>
+	/// 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>
 	///
 	/// <remarks>The EventTrigger runs when a system event fires.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![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");
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlType(IncludeInSchema = false)]
 	public sealed class EventTrigger : Trigger, ITriggerDelay
 	{
 		private NamedValueCollection nvc;

-		///
-		/// Creates an unbound instance of a <see cref="EventTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="EventTrigger"/>.
 		public EventTrigger() : base(TaskTriggerType.Event) { }

-		///
-		/// Initializes an unbound instance of the <see cref="EventTrigger"/> class and sets a basic event.
-		///
+		/// Initializes an unbound instance of the <see cref="EventTrigger"/> class and sets a basic event.
 		/// <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] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
-		///
+		internal EventTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		public TimeSpan Delay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IEventTrigger)v2Trigger).Delay);
-				return GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IEventTrigger)v2Trigger).Delay = Task.TimeSpanToString(value);
+			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;
 			}
 		}

-		///
-		/// Gets or sets the XPath query string that identifies the event that fires the trigger.
-		///
+		/// Gets or sets the XPath query string that identifies the event that fires the trigger.
 		[DefaultValue(null)]
 		public string Subscription
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.IEventTrigger)v2Trigger).Subscription;
-				return GetUnboundValueOrDefault<string>(nameof(Subscription));
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IEventTrigger)v2Trigger).Subscription = value;
+			get => v2Trigger != null ? ((IEventTrigger)v2Trigger).Subscription : GetUnboundValueOrDefault<string>(nameof(Subscription));
+			set
+			{
+				if (v2Trigger != null)
+					((IEventTrigger)v2Trigger).Subscription = value;
 				else
 					unboundValues[nameof(Subscription)] = value;
 			}
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		[XmlArray]
 		[XmlArrayItem("Value", typeof(NameValuePair))]
-		public NamedValueCollection ValueQueries => nvc ?? (nvc = v2Trigger == null ? new NamedValueCollection() : new NamedValueCollection(((V2Interop.IEventTrigger)v2Trigger).ValueQueries));
-
-		///
-		/// 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.
-		///
-		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
-		public override void CopyProperties(Trigger sourceTrigger)
-		{
-			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType())
-			{
-				Subscription = ((EventTrigger)sourceTrigger).Subscription;
-				((EventTrigger)sourceTrigger).ValueQueries.CopyTo(ValueQueries);
-			}
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
-		/// <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) => base.Equals(other) && Subscription == ((EventTrigger)other).Subscription;
-
-		///
-		/// Builds an event log XML query string based on the input parameters.
-		///
+		public NamedValueCollection ValueQueries => nvc ?? (nvc = v2Trigger == null ? new NamedValueCollection() : new NamedValueCollection(((IEventTrigger)v2Trigger).ValueQueries));
+
+		/// Builds an event log XML query string based on the input parameters.
 		/// <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>
@@ -1285,7 +462,7 @@
 		/// <exception cref="System.ArgumentNullException">log</exception>
 		public static string BuildQuery(string log, string source, int? eventId)
 		{
-			StringBuilder sb = new StringBuilder();
+			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);
@@ -1306,8 +483,26 @@
 		}

 		///
-		/// Gets basic event information.
+		/// 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.
 		///
+		/// <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);
+			}
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
+		/// <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;
+
+		/// Gets basic event information.
 		/// <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>
@@ -1318,9 +513,9 @@
 			eventId = null;
 			if (!string.IsNullOrEmpty(Subscription))
 			{
-				using (System.IO.MemoryStream str = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(Subscription)))
-				{
-					using (System.Xml.XmlTextReader rdr = new System.Xml.XmlTextReader(str))
+				using (var str = new System.IO.MemoryStream(Encoding.UTF8.GetBytes(Subscription)))
+				{
+					using (var rdr = new System.Xml.XmlTextReader(str))
 					{
 						rdr.WhitespaceHandling = System.Xml.WhitespaceHandling.None;
 						try
@@ -1329,11 +524,11 @@
 							rdr.ReadStartElement("QueryList");
 							if (rdr.Name == "Query" && rdr.MoveToAttribute("Path"))
 							{
-								string path = rdr.Value;
+								var path = rdr.Value;
 								if (rdr.MoveToElement() && rdr.ReadToDescendant("Select") && path.Equals(rdr["Path"], StringComparison.InvariantCultureIgnoreCase))
 								{
-									string content = rdr.ReadString();
-									System.Text.RegularExpressions.Match m = System.Text.RegularExpressions.Regex.Match(content,
+									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 |
@@ -1351,7 +546,7 @@
 								}
 							}
 						}
-						catch { }
+						catch { /* ignored */ }
 					}
 				}
 			}
@@ -1359,7 +554,8 @@
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="log">The event's log.</param>
 		/// <param name="source">The event's source. Can be <c>null</c>.</param>
@@ -1370,100 +566,92 @@
 			Subscription = BuildQuery(log, source, eventId);
 		}

-		internal override void Bind(V2Interop.ITaskDefinition iTaskDef)
+		internal override void Bind(ITaskDefinition iTaskDef)
 		{
 			base.Bind(iTaskDef);
-			nvc?.Bind(((V2Interop.IEventTrigger)v2Trigger).ValueQueries);
-		}
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+			nvc?.Bind(((IEventTrigger)v2Trigger).ValueQueries);
+		}
+
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString()
 		{
-			string log, source;
-			int? id;
-			if (GetBasic(out log, out source, out id))
-			{
-				StringBuilder 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();
-			}
-			return Properties.Resources.TriggerEvent1;
+			if (!GetBasic(out var log, out var source, out var id))
+				return Properties.Resources.TriggerEvent1;
+			var sb = new StringBuilder();
+			sb.AppendFormat(Properties.Resources.TriggerEventBasic1, log);
+			if (!string.IsNullOrEmpty(source))
+				sb.AppendFormat(Properties.Resources.TriggerEventBasic2, source);
+			if (id.HasValue)
+				sb.AppendFormat(Properties.Resources.TriggerEventBasic3, id.Value);
+			return sb.ToString();
 		}
 	}

 	///
 	/// Represents a trigger that starts a task when the computer goes into an idle state. For information about idle conditions, see Task Idle Conditions.
 	///
-	/// <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><![CDATA[
+	/// <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>
+	///
+	/// <![CDATA[
 	/// IdleTrigger it = new IdleTrigger();
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	public sealed class IdleTrigger : Trigger
 	{
-		///
-		/// Creates an unbound instance of a <see cref="IdleTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="IdleTrigger"/>.
 		public IdleTrigger() : base(TaskTriggerType.Idle) { }

 		internal IdleTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnIdle) { }

-		internal IdleTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		internal IdleTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString() => Properties.Resources.TriggerIdle1;
 	}

 	///
-	/// Represents a trigger that starts a task when a user logs on. When the Task Scheduler service starts, all logged-on users are enumerated and any tasks registered with logon triggers that match the logged on user are run. Not available on Task Scheduler 1.0.
+	/// Represents a trigger that starts a task when a user logs on. When the Task Scheduler service starts, all logged-on users are enumerated and any tasks
+	/// registered with logon triggers that match the logged on user are run. Not available on Task Scheduler 1.0.
 	///
 	/// <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><![CDATA[
+	/// <example>
+	///
+	/// <![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);
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	public sealed class LogonTrigger : Trigger, ITriggerDelay, ITriggerUserId
 	{
-		///
-		/// Creates an unbound instance of a <see cref="LogonTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="LogonTrigger"/>.
 		public LogonTrigger() : base(TaskTriggerType.Logon) { }

 		internal LogonTrigger([NotNull] V1Interop.ITaskTrigger iTrigger) : base(iTrigger, V1Interop.TaskTriggerType.OnLogon) { }

-		internal LogonTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
-		///
+		internal LogonTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan Delay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.ILogonTrigger)v2Trigger).Delay);
-				return GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ILogonTrigger)v2Trigger).Delay = Task.TimeSpanToString(value);
+			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
@@ -1477,22 +665,21 @@
 		/// <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>
 		///
-		/// <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>
+		/// <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
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.ILogonTrigger)v2Trigger).UserId;
-				return GetUnboundValueOrDefault<string>(nameof(UserId));
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ILogonTrigger)v2Trigger).UserId = value;
+			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
@@ -1500,13 +687,11 @@
 			}
 		}

-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString()
 		{
-			string user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId;
+			var user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId;
 			return string.Format(Properties.Resources.TriggerLogon1, user);
 		}
 	}
@@ -1517,9 +702,7 @@
 	[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
 	public sealed class MonthlyDOWTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
 	{
-		///
-		/// Creates an unbound instance of a <see cref="MonthlyDOWTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="MonthlyDOWTrigger"/>.
 		/// <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>
@@ -1538,24 +721,19 @@
 				v1TriggerData.Data.monthlyDOW.DaysOfTheWeek = DaysOfTheWeek.Sunday;
 		}

-		internal MonthlyDOWTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets the days of the week during which the task runs.
-		///
+		internal MonthlyDOWTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets the days of the week during which the task runs.
 		[DefaultValue(0)]
 		public DaysOfTheWeek DaysOfWeek
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return (DaysOfTheWeek)((V2Interop.IMonthlyDOWTrigger)v2Trigger).DaysOfWeek;
-				return v1TriggerData.Data.monthlyDOW.DaysOfTheWeek;
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyDOWTrigger)v2Trigger).DaysOfWeek = (short)value;
+			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;
@@ -1567,22 +745,17 @@
 			}
 		}

-		///
-		/// Gets or sets the months of the year during which the task runs.
-		///
+		/// Gets or sets the months of the year during which the task runs.
 		[DefaultValue(0)]
 		public MonthsOfTheYear MonthsOfYear
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return (MonthsOfTheYear)((V2Interop.IMonthlyDOWTrigger)v2Trigger).MonthsOfYear;
-				return v1TriggerData.Data.monthlyDOW.Months;
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyDOWTrigger)v2Trigger).MonthsOfYear = (short)value;
+			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;
@@ -1594,24 +767,17 @@
 			}
 		}

-		///
-		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
-		///
+		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan RandomDelay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IMonthlyDOWTrigger)v2Trigger).RandomDelay);
-				return GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyDOWTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value);
+			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
@@ -1619,24 +785,17 @@
 			}
 		}

-		///
-		/// Gets or sets a Boolean value that indicates that the task runs on the last week of the month.
-		///
+		/// Gets or sets a Boolean value that indicates that the task runs on the last week of the month.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(false)]
 		[XmlIgnore]
 		public bool RunOnLastWeekOfMonth
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth;
-				return GetUnboundValueOrDefault(nameof(RunOnLastWeekOfMonth), false);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth = value;
+			get => ((IMonthlyDOWTrigger)v2Trigger)?.RunOnLastWeekOfMonth ?? GetUnboundValueOrDefault(nameof(RunOnLastWeekOfMonth), false);
+			set
+			{
+				if (v2Trigger != null)
+					((IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth = value;
 				else if (v1Trigger != null)
 					throw new NotV1SupportedException();
 				else
@@ -1644,31 +803,26 @@
 			}
 		}

-		///
-		/// Gets or sets the weeks of the month during which the task runs.
-		///
+		/// Gets or sets the weeks of the month during which the task runs.
 		[DefaultValue(0)]
 		public WhichWeek WeeksOfMonth
 		{
 			get
 			{
-				if (v2Trigger != null)
-				{
-					WhichWeek ww = (WhichWeek)((V2Interop.IMonthlyDOWTrigger)v2Trigger).WeeksOfMonth;
-					// Following addition give accurate results for confusing RunOnLastWeekOfMonth property (thanks kbergeron)
-					if (((V2Interop.IMonthlyDOWTrigger)v2Trigger).RunOnLastWeekOfMonth)
-						ww |= WhichWeek.LastWeek;
-					return ww;
-				}
-				else if (v1Trigger != null)
-					return v1TriggerData.Data.monthlyDOW.V2WhichWeek;
-				else
-					return GetUnboundValueOrDefault(nameof(WeeksOfMonth), WhichWeek.FirstWeek);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyDOWTrigger)v2Trigger).WeeksOfMonth = (short)value;
+				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
+			{
+				if (v2Trigger != null)
+					((IMonthlyDOWTrigger)v2Trigger).WeeksOfMonth = (short)value;
 				else
 				{
 					try
@@ -1687,48 +841,56 @@
 			}
 		}

-		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
-		///
+		/// Gets or sets a value that indicates the amount of time before the task is started.
 		/// <value>The delay duration.</value>
 		TimeSpan ITriggerDelay.Delay
 		{
-			get { return RandomDelay; }
-			set { RandomDelay = value; }
+			get => RandomDelay;
+			set => RandomDelay = value;
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
 		public override void CopyProperties(Trigger sourceTrigger)
 		{
 			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType())
-			{
-				DaysOfWeek = ((MonthlyDOWTrigger)sourceTrigger).DaysOfWeek;
-				MonthsOfYear = ((MonthlyDOWTrigger)sourceTrigger).MonthsOfYear;
-				try { RunOnLastWeekOfMonth = ((MonthlyDOWTrigger)sourceTrigger).RunOnLastWeekOfMonth; } catch { }
-				WeeksOfMonth = ((MonthlyDOWTrigger)sourceTrigger).WeeksOfMonth;
-			}
-			if (sourceTrigger != null && sourceTrigger.TriggerType == TaskTriggerType.Monthly)
-				MonthsOfYear = ((MonthlyTrigger)sourceTrigger).MonthsOfYear;
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
+			if (sourceTrigger is MonthlyDOWTrigger mt)
+			{
+				DaysOfWeek = mt.DaysOfWeek;
+				MonthsOfYear = mt.MonthsOfYear;
+				try { RunOnLastWeekOfMonth = mt.RunOnLastWeekOfMonth; } catch { /* ignored */ }
+				WeeksOfMonth = mt.WeeksOfMonth;
+			}
+			if (sourceTrigger is MonthlyTrigger m)
+				MonthsOfYear = m.MonthsOfYear;
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
 		/// <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) => base.Equals(other) && DaysOfWeek == ((MonthlyDOWTrigger)other).DaysOfWeek &&
-			MonthsOfYear == ((MonthlyDOWTrigger)other).MonthsOfYear && WeeksOfMonth == ((MonthlyDOWTrigger)other).WeeksOfMonth &&
-			(v1Trigger == null && RunOnLastWeekOfMonth == ((MonthlyDOWTrigger)other).RunOnLastWeekOfMonth);
-
-		///
-		/// Reads the subclass XML for V1 streams.
-		///
+		/// <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); }
+
+		/// Gets the non-localized trigger string for V2 triggers.
+		/// <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);
+		}
+
+		/// Reads the subclass XML for V1 streams.
 		/// <param name="reader">The reader.</param>
 		private void ReadMyXml([NotNull] System.Xml.XmlReader reader)
 		{
@@ -1743,7 +905,7 @@
 						{
 							if (reader.LocalName == "Week")
 							{
-								string wk = reader.ReadElementContentAsString();
+								var wk = reader.ReadElementContentAsString();
 								if (wk == "Last")
 									WeeksOfMonth = WhichWeek.LastWeek;
 								else
@@ -1753,15 +915,19 @@
 										case 1:
 											WeeksOfMonth = WhichWeek.FirstWeek;
 											break;
+
 										case 2:
 											WeeksOfMonth = WhichWeek.SecondWeek;
 											break;
+
 										case 3:
 											WeeksOfMonth = WhichWeek.ThirdWeek;
 											break;
+
 										case 4:
 											WeeksOfMonth = WhichWeek.FourthWeek;
 											break;
+
 										default:
 											throw new System.Xml.XmlException("Week element must contain a 1-4 or Last as content.");
 									}
@@ -1770,6 +936,7 @@
 						}
 						reader.ReadEndElement();
 						break;
+
 					case "DaysOfWeek":
 						reader.Read();
 						DaysOfWeek = 0;
@@ -1787,6 +954,7 @@
 						}
 						reader.ReadEndElement();
 						break;
+
 					case "Months":
 						reader.Read();
 						MonthsOfYear = 0;
@@ -1804,6 +972,7 @@
 						}
 						reader.ReadEndElement();
 						break;
+
 					default:
 						reader.Skip();
 						break;
@@ -1812,21 +981,7 @@
 			reader.ReadEndElement();
 		}

-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
-		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString()
-		{
-			string ww = TaskEnumGlobalizer.GetString(WeeksOfMonth);
-			string days = TaskEnumGlobalizer.GetString(DaysOfWeek);
-			string months = TaskEnumGlobalizer.GetString(MonthsOfYear);
-			return string.Format(Properties.Resources.TriggerMonthlyDOW1, AdjustToLocal(StartBoundary), ww, days, months);
-		}
-
-		///
-		/// Writes the subclass XML for V1 streams.
-		///
+		/// Writes the subclass XML for V1 streams.
 		/// <param name="writer">The writer.</param>
 		private void WriteMyXml([NotNull] System.Xml.XmlWriter writer)
 		{
@@ -1859,28 +1014,18 @@

 			writer.WriteEndElement();
 		}
-
-		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); }
 	}

-	///
-	/// Represents a trigger that starts a job based on a monthly schedule. For example, the task starts on specific days of specific months.
-	///
+	/// Represents a trigger that starts a job based on a monthly schedule. For example, the task starts on specific days of specific months.
 	[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
 	public sealed class MonthlyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
 	{
-		///
-		/// Creates an unbound instance of a <see cref="MonthlyTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="MonthlyTrigger"/>.
 		/// <param name="dayOfMonth">The day 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)
 		{
-			DaysOfMonth = new[] {dayOfMonth};
+			DaysOfMonth = new[] { dayOfMonth };
 			MonthsOfYear = monthsOfYear;
 		}

@@ -1892,24 +1037,17 @@
 				v1TriggerData.Data.monthlyDate.Days = 1;
 		}

-		internal MonthlyTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets the days of the month during which the task runs.
-		///
+		internal MonthlyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets the days of the month during which the task runs.
 		public int[] DaysOfMonth
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return MaskToIndices(((V2Interop.IMonthlyTrigger)v2Trigger).DaysOfMonth);
-				return MaskToIndices((int)v1TriggerData.Data.monthlyDate.Days);
-			}
-			set
-			{
-				int mask = IndicesToMask(value);
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyTrigger)v2Trigger).DaysOfMonth = mask;
+			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;
@@ -1921,22 +1059,17 @@
 			}
 		}

-		///
-		/// Gets or sets the months of the year during which the task runs.
-		///
+		/// Gets or sets the months of the year during which the task runs.
 		[DefaultValue(0)]
 		public MonthsOfTheYear MonthsOfYear
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return (MonthsOfTheYear)((V2Interop.IMonthlyTrigger)v2Trigger).MonthsOfYear;
-				return v1TriggerData.Data.monthlyDOW.Months;
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyTrigger)v2Trigger).MonthsOfYear = (short)value;
+			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;
@@ -1948,24 +1081,17 @@
 			}
 		}

-		///
-		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
-		///
+		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan RandomDelay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IMonthlyTrigger)v2Trigger).RandomDelay);
-				return GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value);
+			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
@@ -1973,24 +1099,17 @@
 			}
 		}

-		///
-		/// Gets or sets a Boolean value that indicates that the task runs on the last day of the month.
-		///
+		/// Gets or sets a Boolean value that indicates that the task runs on the last day of the month.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(false)]
 		[XmlIgnore]
 		public bool RunOnLastDayOfMonth
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.IMonthlyTrigger)v2Trigger).RunOnLastDayOfMonth;
-				return GetUnboundValueOrDefault(nameof(RunOnLastDayOfMonth), false);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IMonthlyTrigger)v2Trigger).RunOnLastDayOfMonth = value;
+			get => ((IMonthlyTrigger)v2Trigger)?.RunOnLastDayOfMonth ?? GetUnboundValueOrDefault(nameof(RunOnLastDayOfMonth), false);
+			set
+			{
+				if (v2Trigger != null)
+					((IMonthlyTrigger)v2Trigger).RunOnLastDayOfMonth = value;
 				else if (v1Trigger != null)
 					throw new NotV1SupportedException();
 				else
@@ -1998,41 +1117,71 @@
 			}
 		}

-		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
-		///
+		/// Gets or sets a value that indicates the amount of time before the task is started.
 		/// <value>The delay duration.</value>
 		TimeSpan ITriggerDelay.Delay
 		{
-			get { return RandomDelay; }
-			set { RandomDelay = value; }
+			get => RandomDelay;
+			set => RandomDelay = value;
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
 		public override void CopyProperties(Trigger sourceTrigger)
 		{
 			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType())
-			{
-				DaysOfMonth = ((MonthlyTrigger)sourceTrigger).DaysOfMonth;
-				MonthsOfYear = ((MonthlyTrigger)sourceTrigger).MonthsOfYear;
-				try { RunOnLastDayOfMonth = ((MonthlyTrigger)sourceTrigger).RunOnLastDayOfMonth; } catch { }
-			}
-			if (sourceTrigger != null && sourceTrigger.TriggerType == TaskTriggerType.MonthlyDOW)
-				MonthsOfYear = ((MonthlyDOWTrigger)sourceTrigger).MonthsOfYear;
+			if (sourceTrigger is MonthlyTrigger mt)
+			{
+				DaysOfMonth = mt.DaysOfMonth;
+				MonthsOfYear = mt.MonthsOfYear;
+				try { RunOnLastDayOfMonth = mt.RunOnLastDayOfMonth; } catch { /* ignored */ }
+			}
+			if (sourceTrigger is MonthlyDOWTrigger mdt)
+				MonthsOfYear = mdt.MonthsOfYear;
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
+		/// <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); }
+
+		/// Gets the non-localized trigger string for V2 triggers.
+		/// <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);
 		}

 		///
-		/// Indicates whether the current object is equal to another object of the same type.
+		/// 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.
 		///
-		/// <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) => base.Equals(other) && ListsEqual(DaysOfMonth, (other as MonthlyTrigger)?.DaysOfMonth) && MonthsOfYear == ((MonthlyTrigger)other).MonthsOfYear && (v1Trigger == null && RunOnLastDayOfMonth == ((MonthlyTrigger)other).RunOnLastDayOfMonth);
+		/// <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([NotNull] int[] indices)
+		{
+			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 = mask | 1 << (index - 1);
+			}
+			return mask;
+		}

 		/// Compares two collections.
 		/// <typeparam name="T">Item type of collections.</typeparam>
@@ -2052,47 +1201,28 @@
 		}

 		///
-		/// 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.
-		///
-		/// <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([NotNull] int[] indices)
-		{
-			int mask = 0;
-			foreach (int index in indices)
-			{
-				if (index < 1 || index > 31) throw new ArgumentException("Days must be in the range 1..31");
-				mask = mask | 1 << (index - 1);
-			}
-			return mask;
-		}
-
-		///
-		/// Convert an integer representing a mask to an array where each element contains the index
-		/// of a bit that is ON in the mask.  Bits are considered to number from 1 to 32.
+		/// Convert an integer representing a mask to an array where each element contains the index of a bit that is ON in the mask. Bits are considered to
+		/// number from 1 to 32.
 		///
 		/// <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
-			int cnt = 0;
-			for (int i = 0; (mask >> i) > 0; i++)
+			var cnt = 0;
+			for (var i = 0; mask >> i > 0; i++)
 				cnt = cnt + (1 & (mask >> i));
 			//allocate return array with one entry for each bit
-			int[] indices = new int[cnt];
+			var indices = new int[cnt];
 			//fill array with bit indices
 			cnt = 0;
-			for (int i = 0; (mask >> i) > 0; i++)
+			for (var i = 0; mask >> i > 0; i++)
 				if ((1 & (mask >> i)) == 1)
 					indices[cnt++] = i + 1;
 			return indices;
 		}

-		///
-		/// Reads the subclass XML for V1 streams.
-		///
+		/// Reads the subclass XML for V1 streams.
 		/// <param name="reader">The reader.</param>
 		private void ReadMyXml([NotNull] System.Xml.XmlReader reader)
 		{
@@ -2103,23 +1233,20 @@
 				{
 					case "DaysOfMonth":
 						reader.Read();
-						List<int> days = new List<int>();
+						var days = new List<int>();
 						while (reader.MoveToContent() == System.Xml.XmlNodeType.Element)
 						{
-							if (reader.LocalName == "Day")
-							{
-								string sday = reader.ReadElementContentAsString();
-								if (!sday.Equals("Last", StringComparison.InvariantCultureIgnoreCase))
-								{
-									int day = Int32.Parse(sday);
-									if (day >= 1 && day <= 31)
-										days.Add(day);
-								}
-							}
+							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;
@@ -2137,25 +1264,13 @@
 						}
 						reader.ReadEndElement();
 						break;
+
 					default:
 						reader.Skip();
 						break;
 				}
 			}
 			reader.ReadEndElement();
-		}
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
-		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString()
-		{
-			string days = string.Join(Properties.Resources.ListSeparator, Array.ConvertAll(DaysOfMonth, delegate(int i) { return i.ToString(); }));
-			if (RunOnLastDayOfMonth)
-				days += (days.Length == 0 ? "" : Properties.Resources.ListSeparator) + Properties.Resources.WWLastWeek;
-			string months = TaskEnumGlobalizer.GetString(MonthsOfYear);
-			return string.Format(Properties.Resources.TriggerMonthly1, AdjustToLocal(StartBoundary), days, months);
 		}

 		private void WriteMyXml([NotNull] System.Xml.XmlWriter writer)
@@ -2175,52 +1290,41 @@

 			writer.WriteEndElement();
 		}
-
-		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); }
 	}

 	///
-	/// 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>
+	/// 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>
 	///
 	/// <remarks>The RegistrationTrigger will fire after the task is registered (saved). It is advisable to put in a delay.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![CDATA[
 	/// // Create a trigger that will fire the task 5 minutes after its registered
 	/// RegistrationTrigger rTrigger = new RegistrationTrigger();
 	/// rTrigger.Delay = TimeSpan.FromMinutes(5);
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlType(IncludeInSchema = false)]
 	public sealed class RegistrationTrigger : Trigger, ITriggerDelay
 	{
-		///
-		/// Creates an unbound instance of a <see cref="RegistrationTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="RegistrationTrigger"/>.
 		public RegistrationTrigger() : base(TaskTriggerType.Registration) { }

-		internal RegistrationTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
-		///
+		internal RegistrationTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan Delay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IRegistrationTrigger)v2Trigger).Delay);
-				return GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IRegistrationTrigger)v2Trigger).Delay = Task.TimeSpanToString(value);
+			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
@@ -2228,18 +1332,16 @@
 			}
 		}

-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString() => Properties.Resources.TriggerRegistration1;
 	}

-	///
-	/// Defines how often the task is run and how long the repetition pattern is repeated after the task is started.
-	///
+	/// Defines how often the task is run and how long the repetition pattern is repeated after the task is started.
 	/// <remarks>This can be used directly or by assignment for a <see cref="Trigger"/>.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![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.
@@ -2248,24 +1350,29 @@
 	/// 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) };
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlRoot("Repetition", Namespace = TaskDefinition.tns, IsNullable = true)]
 	[TypeConverter(typeof(RepetitionPatternConverter))]
 	public sealed class RepetitionPattern : IDisposable, IXmlSerializable, IEquatable<RepetitionPattern>
 	{
 		private readonly Trigger pTrigger;
+		private readonly IRepetitionPattern v2Pattern;
 		private TimeSpan unboundInterval = TimeSpan.Zero, unboundDuration = TimeSpan.Zero;
 		private bool unboundStopAtDurationEnd;
-		private readonly V2Interop.IRepetitionPattern v2Pattern;
-
-		///
-		/// Initializes a new instance of the <see cref="RepetitionPattern"/> class.
-		///
-		/// <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>
+
+		/// Initializes a new instance of the <see cref="RepetitionPattern"/> class.
+		/// <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)
 		{
@@ -2281,9 +1388,7 @@
 				v2Pattern = pTrigger.v2Trigger.Repetition;
 		}

-		///
-		/// Gets or sets how long the pattern is repeated.
-		///
+		/// Gets or sets how long the pattern is repeated.
 		/// <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>
@@ -2291,17 +1396,12 @@
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		public TimeSpan Duration
 		{
-			get
-			{
-				if (v2Pattern != null)
-					return Task.StringToTimeSpan(v2Pattern.Duration);
-				if (pTrigger != null)
-					return TimeSpan.FromMinutes(pTrigger.v1TriggerData.MinutesDuration);
-				return unboundDuration;
-			}
-			set
-			{
-				if (value.Ticks < 0 || (value != TimeSpan.Zero && value < TimeSpan.FromMinutes(1)))
+			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)
 				{
@@ -2317,28 +1417,19 @@
 			}
 		}

-		///
-		/// Gets or sets the amount of time between each restart of the task.
-		///
-		/// <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>
+		/// Gets or sets the amount of time between each restart of the task.
+		/// <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
-			{
-				if (v2Pattern != null)
-					return Task.StringToTimeSpan(v2Pattern.Interval);
-				if (pTrigger != null)
-					return TimeSpan.FromMinutes(pTrigger.v1TriggerData.MinutesInterval);
-				return unboundInterval;
-			}
-			set
-			{
-				if (value.Ticks < 0 || ((v2Pattern != null || pTrigger == null) && value != TimeSpan.Zero && (value < TimeSpan.FromMinutes(1) || value > TimeSpan.FromDays(31))))
+			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)
 				{
@@ -2356,9 +1447,7 @@
 			}
 		}

-		///
-		/// Gets or sets a Boolean value that indicates if a running instance of the task is stopped at the end of repetition pattern duration.
-		///
+		/// Gets or sets a Boolean value that indicates if a running instance of the task is stopped at the end of repetition pattern duration.
 		[DefaultValue(false)]
 		public bool StopAtDurationEnd
 		{
@@ -2387,44 +1476,26 @@
 			}
 		}

-		///
-		/// Releases all resources used by this class.
-		///
+		/// Releases all resources used by this class.
 		public void Dispose()
 		{
 			if (v2Pattern != null) Marshal.ReleaseComObject(v2Pattern);
 		}

-		///
-		/// Determines whether the specified <see cref="System.Object" />, is equal to this instance.
-		///
-		/// <param name="obj">The <see cref="System.Object" /> to compare with this instance.</param>
-		/// <returns>
-		///   <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
-		/// </returns>
-		public override bool Equals(object obj)
-		{
-			if (obj is RepetitionPattern)
-				return Equals((RepetitionPattern)obj);
-			return base.Equals(obj);
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
+		/// Determines whether the specified <see cref="System.Object"/>, is equal to this instance.
+		/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
+		/// <returns><c>true</c> if the specified <see cref="System.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);
+
+		/// Indicates whether the current object is equal to another object of the same type.
 		/// <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) => Duration == other.Duration && Interval == other.Interval && StopAtDurationEnd == other.StopAtDurationEnd;
-
-		///
-		/// Returns a hash code for this instance.
-		///
-		/// <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();
+		/// <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;
+
+		/// Returns a hash code for this instance.
+		/// <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();

 		/// Determines whether any properties for this <see cref="RepetitionPattern"/> have been set.
 		/// <returns><c>true</c> if properties have been set; otherwise, <c>false</c>.</returns>
@@ -2437,6 +1508,22 @@
 			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)
@@ -2458,274 +1545,802 @@
 			StopAtDurationEnd = value.StopAtDurationEnd;
 		}

-		System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null;
-
 		private bool ReadXmlConverter(System.Reflection.PropertyInfo pi, Object obj, ref Object value)
 		{
-			if (pi.Name == "Interval")
-			{
-				if (value is TimeSpan && !((TimeSpan)value).Equals(TimeSpan.Zero) && Duration <= (TimeSpan)value)
-					Duration = ((TimeSpan)value).Add(TimeSpan.FromMinutes(1));
-			}
-			return false;
-		}
-
-		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); }
+			if (pi.Name != "Interval" || !(value is TimeSpan span) || span.Equals(TimeSpan.Zero) || Duration > span)
+				return false;
+			Duration = span.Add(TimeSpan.FromMinutes(1));
+			return true;
+		}
 	}

-	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))
-			{
-				if (rp.Interval != TimeSpan.Zero)
-				{
-					string sduration;
-					if (rp.Duration == TimeSpan.Zero)
-						sduration = Properties.Resources.TriggerDuration0;
-					else
-						sduration = string.Format(Properties.Resources.TriggerDurationNot0Short, Trigger.GetBestTimeSpanString(rp.Duration));
-					return string.Format(Properties.Resources.TriggerRepetitionShort, Trigger.GetBestTimeSpanString(rp.Interval), sduration);
-				}
-				else
-					return "";
-			}
-			return base.ConvertTo(context, culture, value, destinationType);
-		}
-	}
-
 	///
-	/// 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>
+	/// 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>
 	///
-	/// <remarks>The SessionStateChangeTrigger will fire after six different system events: connecting or disconnecting locally or remotely, or locking or unlocking the session.</remarks>
-	/// <example><![CDATA[
+	/// <remarks>
+	/// The SessionStateChangeTrigger will fire after six different system events: connecting or disconnecting locally or remotely, or locking or unlocking the session.
+	/// </remarks>
+	/// <example>
+	///
+	/// <![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 };
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlType(IncludeInSchema = false)]
 	public sealed class SessionStateChangeTrigger : Trigger, ITriggerDelay, ITriggerUserId
 	{
-		///
-		/// Creates an unbound instance of a <see cref="SessionStateChangeTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="SessionStateChangeTrigger"/>.
 		public SessionStateChangeTrigger() : base(TaskTriggerType.SessionStateChange) { }

-		/// Initializes a new instance of the <see cref="SessionStateChangeTrigger" /> class.
+		/// Initializes a new instance of the <see cref="SessionStateChangeTrigger"/> class.
 		/// <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] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
-		///
+		internal SessionStateChangeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a value that indicates the amount of time between when the system is booted and when the task is started.
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		public TimeSpan Delay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.ISessionStateChangeTrigger)v2Trigger).Delay);
-				return GetUnboundValueOrDefault(nameof(Delay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ISessionStateChangeTrigger)v2Trigger).Delay = Task.TimeSpanToString(value);
+			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;
 			}
 		}

-		///
-		/// Gets or sets the kind of Terminal Server session change that would trigger a task launch.
-		///
+		/// Gets or sets the kind of Terminal Server session change that would trigger a task launch.
 		[DefaultValue(1)]
 		public TaskSessionStateChangeType StateChange
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.ISessionStateChangeTrigger)v2Trigger).StateChange;
-				return GetUnboundValueOrDefault(nameof(StateChange), TaskSessionStateChangeType.ConsoleConnect);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ISessionStateChangeTrigger)v2Trigger).StateChange = value;
+			get => ((ISessionStateChangeTrigger)v2Trigger)?.StateChange ?? GetUnboundValueOrDefault(nameof(StateChange), TaskSessionStateChangeType.ConsoleConnect);
+			set
+			{
+				if (v2Trigger != null)
+					((ISessionStateChangeTrigger)v2Trigger).StateChange = value;
 				else
 					unboundValues[nameof(StateChange)] = value;
 			}
 		}

-		///
-		/// Gets or sets the user for the Terminal Server session. When a session state change is detected for this user, a task is started.
-		///
+		/// Gets or sets the user for the Terminal Server session. When a session state change is detected for this user, a task is started.
 		[DefaultValue(null)]
 		public string UserId
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.ISessionStateChangeTrigger)v2Trigger).UserId;
-				return GetUnboundValueOrDefault<string>(nameof(UserId));
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ISessionStateChangeTrigger)v2Trigger).UserId = value;
+			get => v2Trigger != null ? ((ISessionStateChangeTrigger)v2Trigger).UserId : GetUnboundValueOrDefault<string>(nameof(UserId));
+			set
+			{
+				if (v2Trigger != null)
+					((ISessionStateChangeTrigger)v2Trigger).UserId = value;
 				else
 					unboundValues[nameof(UserId)] = value;
 			}
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
 		public override void CopyProperties(Trigger sourceTrigger)
 		{
 			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType() && !StateChangeIsSet())
-				StateChange = ((SessionStateChangeTrigger)sourceTrigger).StateChange;
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
+			if (sourceTrigger is SessionStateChangeTrigger st && !StateChangeIsSet())
+				StateChange = st.StateChange;
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
 		/// <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) => base.Equals(other) && StateChange == ((SessionStateChangeTrigger)other).StateChange;
-
-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
+		/// <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;
+
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
 		protected override string V2GetTriggerString()
 		{
-			string str = Properties.Resources.ResourceManager.GetString("TriggerSession" + StateChange.ToString());
-			string user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId;
+			var str = Properties.Resources.ResourceManager.GetString("TriggerSession" + StateChange.ToString());
+			var user = string.IsNullOrEmpty(UserId) ? Properties.Resources.TriggerAnyUser : UserId;
 			if (StateChange != TaskSessionStateChangeType.SessionLock && StateChange != TaskSessionStateChangeType.SessionUnlock)
 				user = string.Format(Properties.Resources.TriggerSessionUserSession, user);
 			return string.Format(str, user);
 		}

-		///
-		/// Returns a value indicating if the StateChange property has been set.
-		///
+		/// Returns a value indicating if the StateChange property has been set.
 		/// <returns>StateChange property has been set.</returns>
 		private bool StateChangeIsSet() => v2Trigger != null || (unboundValues?.ContainsKey("StateChange") ?? false);
 	}

-	///
-	/// Represents a trigger that starts a task at a specific date and time.
-	///
+	/// Represents a trigger that starts a task at a specific date and time.
 	/// <remarks>A TimeTrigger runs at a specified date and time.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![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);
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	public sealed class TimeTrigger : Trigger, ITriggerDelay, ICalendarTrigger
 	{
-		///
-		/// Creates an unbound instance of a <see cref="TimeTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="TimeTrigger"/>.
 		public TimeTrigger() : base(TaskTriggerType.Time) { }

-		///
-		/// Creates an unbound instance of a <see cref="TimeTrigger"/> and assigns the execution time.
-		///
+		/// Creates an unbound instance of a <see cref="TimeTrigger"/> and assigns the execution time.
 		/// <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] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
+		internal TimeTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
+		/// <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;
+			}
+		}
+
+		/// Gets or sets a value that indicates the amount of time before the task is started.
+		/// <value>The delay duration.</value>
+		TimeSpan ITriggerDelay.Delay
+		{
+			get => RandomDelay;
+			set => RandomDelay = value;
+		}
+
+		/// Gets the non-localized trigger string for V2 triggers.
+		/// <returns>String describing the trigger.</returns>
+		protected override string V2GetTriggerString() => string.Format(Properties.Resources.TriggerTime1, AdjustToLocal(StartBoundary));
+	}
+
+	///
+	/// Abstract base class which provides the common properties that are inherited by all trigger classes. A trigger can be created using the
+	/// <see cref="TriggerCollection.Add{TTrigger}"/> or the <see cref="TriggerCollection.AddNew"/> method.
+	///
+	public abstract partial class Trigger : IDisposable, ICloneable, IEquatable<Trigger>, IComparable, IComparable<Trigger>
+	{
+		internal const string V2BoundaryDateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'FFFK";
+		internal static readonly CultureInfo DefaultDateCulture = CultureInfo.CreateSpecificCulture("en-US");
+
+		internal V1Interop.ITaskTrigger v1Trigger;
+		internal V1Interop.TaskTrigger v1TriggerData;
+		internal ITrigger v2Trigger;
+		/// In testing and may change. Do not use until officially introduced into library.
+		protected Dictionary<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);
+		}
+
+		/// Gets or sets a Boolean value that indicates whether the trigger is enabled.
+		public bool Enabled
+		{
+			get => v2Trigger?.Enabled ?? GetUnboundValueOrDefault(nameof(Enabled), !v1TriggerData.Flags.IsFlagSet(V1Interop.TaskTriggerFlags.Disabled));
+			set
+			{
+				if (v2Trigger != null)
+					v2Trigger.Enabled = value;
+				else
+				{
+					v1TriggerData.Flags = v1TriggerData.Flags.SetFlags(V1Interop.TaskTriggerFlags.Disabled, !value);
+					if (v1Trigger != null)
+						SetV1TriggerData();
+					else
+						unboundValues[nameof(Enabled)] = value;
+				}
+			}
+		}
+
+		/// Gets or sets the date and time when the trigger is deactivated. The trigger cannot start the task after it is deactivated.
+		/// <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)
+					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;
+				}
+			}
+		}

 		///
-		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
+		/// 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.
 		///
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
-		public TimeSpan RandomDelay
-		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.ITimeTrigger)v2Trigger).RandomDelay);
-				return GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.ITimeTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value);
+		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(RandomDelay)] = value;
+					unboundValues[nameof(ExecutionTimeLimit)] = value;
+			}
+		}
+
+		/// Gets or sets the identifier for the trigger. Cannot set with Task Scheduler 1.0.
+		/// <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;
 			}
 		}

 		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
+		/// 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.
 		///
-		/// <value>The delay duration.</value>
-		TimeSpan ITriggerDelay.Delay
-		{
-			get { return RandomDelay; }
-			set { RandomDelay = value; }
+		public RepetitionPattern Repetition
+		{
+			get => repititionPattern ?? (repititionPattern = new RepetitionPattern(this));
+			set => Repetition.Set(value);
+		}
+
+		/// Gets or sets the date and time when the trigger is activated.
+		/// <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)
+					v2Trigger.StartBoundary = value == DateTime.MinValue ? null : value.ToString(V2BoundaryDateFormat, DefaultDateCulture);
+				else
+				{
+					v1TriggerData.BeginDate = value;
+					if (v1Trigger != null)
+						SetV1TriggerData();
+					else
+						unboundValues[nameof(StartBoundary)] = value;
+				}
+			}
+		}
+
+		/// Gets the type of the trigger.
+		/// <value>The <see cref="TaskTriggerType"/> of the trigger.</value>
+		[XmlIgnore]
+		public TaskTriggerType TriggerType => ttype;
+
+		/// Creates the specified trigger.
+		/// <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;
+		}
+
+		/// Creates a new <see cref="Trigger"/> that is an unbound copy of this instance.
+		/// <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;
 		}

 		///
-		/// Gets the non-localized trigger string for V2 triggers.
+		/// 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.
 		///
+		/// <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);
+
+		///
+		/// 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.
+		///
+		/// <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 */ }
+			Repetition.Duration = sourceTrigger.Repetition.Duration;
+			Repetition.Interval = sourceTrigger.Repetition.Interval;
+			Repetition.StopAtDurationEnd = sourceTrigger.Repetition.StopAtDurationEnd;
+			StartBoundary = sourceTrigger.StartBoundary;
+			if (sourceTrigger is ITriggerDelay delay && this is ITriggerDelay)
+				try { ((ITriggerDelay)this).Delay = delay.Delay; }
+				catch { /* ignored */ }
+			if (sourceTrigger is ITriggerUserId id && this is ITriggerUserId)
+				try { ((ITriggerUserId)this).UserId = id.UserId; }
+				catch { /* ignored */ }
+		}
+
+		/// Releases all resources used by this class.
+		public virtual void Dispose()
+		{
+			if (v2Trigger != null)
+				Marshal.ReleaseComObject(v2Trigger);
+			if (v1Trigger != null)
+				Marshal.ReleaseComObject(v1Trigger);
+		}
+
+		/// Determines whether the specified <see cref="System.Object"/>, is equal to this instance.
+		/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
+		/// <returns><c>true</c> if the specified <see cref="System.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);
+
+		/// Indicates whether the current object is equal to another object of the same type.
+		/// <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;
+		}
+
+		/// Returns a hash code for this instance.
+		/// <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();
+
+		/// Sets the repetition.
+		/// <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;
+		}
+
+		/// Returns a string representing this trigger.
+		/// <returns>String value of trigger.</returns>
+		public override string ToString() => v1Trigger != null ? v1Trigger.GetTriggerString() : V2GetTriggerString() + V2BaseTriggerString();
+
+		/// Returns a <see cref="System.String"/> that represents this trigger in a specific language.
+		/// <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;
+			switch (triggerType)
+			{
+				case V1Interop.TaskTriggerType.RunOnce:
+					t = new TimeTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.RunDaily:
+					t = new DailyTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.RunWeekly:
+					t = new WeeklyTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.RunMonthly:
+					t = new MonthlyTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.RunMonthlyDOW:
+					t = new MonthlyDOWTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.OnIdle:
+					t = new IdleTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.OnSystemStart:
+					t = new BootTrigger(trigger);
+					break;
+
+				case V1Interop.TaskTriggerType.OnLogon:
+					t = new LogonTrigger(trigger);
+					break;
+
+				default:
+					throw new ArgumentOutOfRangeException(nameof(triggerType), triggerType, null);
+			}
+			return t;
+		}
+
+		internal static Trigger CreateTrigger([NotNull] ITrigger iTrigger, ITaskDefinition iDef = null)
+		{
+			switch (iTrigger.Type)
+			{
+				case TaskTriggerType.Boot:
+					return new BootTrigger((IBootTrigger)iTrigger);
+
+				case TaskTriggerType.Daily:
+					return new DailyTrigger((IDailyTrigger)iTrigger);
+
+				case TaskTriggerType.Event:
+					return new EventTrigger((IEventTrigger)iTrigger);
+
+				case TaskTriggerType.Idle:
+					return new IdleTrigger((IIdleTrigger)iTrigger);
+
+				case TaskTriggerType.Logon:
+					return new LogonTrigger((ILogonTrigger)iTrigger);
+
+				case TaskTriggerType.Monthly:
+					return new MonthlyTrigger((IMonthlyTrigger)iTrigger);
+
+				case TaskTriggerType.MonthlyDOW:
+					return new MonthlyDOWTrigger((IMonthlyDOWTrigger)iTrigger);
+
+				case TaskTriggerType.Registration:
+					return new RegistrationTrigger((IRegistrationTrigger)iTrigger);
+
+				case TaskTriggerType.SessionStateChange:
+					return new SessionStateChangeTrigger((ISessionStateChangeTrigger)iTrigger);
+
+				case TaskTriggerType.Time:
+					return new TimeTrigger((ITimeTrigger)iTrigger);
+
+				case TaskTriggerType.Weekly:
+					return new WeeklyTrigger((IWeeklyTrigger)iTrigger);
+
+				case TaskTriggerType.Custom:
+					var ct = new CustomTrigger(iTrigger);
+					if (iDef != null)
+						try { ct.UpdateFromXml(iDef.XmlText); } catch { /* ignored */ }
+					return ct;
+
+				default:
+					throw new ArgumentOutOfRangeException();
+			}
+		}
+
+		/// Gets the best time span string.
+		/// <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);
+			foreach (var key in unboundValues.Keys)
+			{
+				try
+				{
+					var o = unboundValues[key];
+					CheckBindValue(key, ref o);
+					v2Trigger.GetType().InvokeMember(key, System.Reflection.BindingFlags.SetProperty, null, v2Trigger, new[] { o });
+				}
+				catch (System.Reflection.TargetInvocationException tie) when (tie.InnerException != null) { throw tie.InnerException; }
+				catch { /* ignored */ }
+			}
+			unboundValues.Clear();
+			unboundValues = null;
+
+			repititionPattern = new RepetitionPattern(this);
+			repititionPattern.Bind();
+		}
+
+		/// Assigns the unbound TriggerData structure to the V1 trigger instance.
+		internal void SetV1TriggerData()
+		{
+			if (v1TriggerData.MinutesInterval != 0 && v1TriggerData.MinutesInterval >= v1TriggerData.MinutesDuration)
+				throw new ArgumentException("Trigger.Repetition.Interval must be less than Trigger.Repetition.Duration under Task Scheduler 1.0.");
+			if (v1TriggerData.BeginDate == DateTime.MinValue)
+				v1TriggerData.BeginDate = DateTime.Now;
+			v1Trigger?.SetTrigger(ref v1TriggerData);
+			System.Diagnostics.Debug.WriteLine(v1TriggerData);
+		}
+
+		/// Checks the bind value for any conversion.
+		/// <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);
+			}
+		}
+
+		/// Gets the unbound value or a default.
+		/// <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;
+
+		/// Gets the non-localized trigger string for V2 triggers.
 		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString() => string.Format(Properties.Resources.TriggerTime1, AdjustToLocal(StartBoundary));
+		protected virtual string V2GetTriggerString() => string.Empty;
+
+		private static TaskTriggerType ConvertFromV1TriggerType(V1Interop.TaskTriggerType v1Type)
+		{
+			var tv2 = (int)v1Type + 1;
+			if (tv2 > 6) tv2++;
+			return (TaskTriggerType)tv2;
+		}
+
+		private string V2BaseTriggerString()
+		{
+			var ret = new StringBuilder();
+			if (Repetition.Interval != TimeSpan.Zero)
+			{
+				var sduration = Repetition.Duration == TimeSpan.Zero ? Properties.Resources.TriggerDuration0 : string.Format(Properties.Resources.TriggerDurationNot0, GetBestTimeSpanString(Repetition.Duration));
+				ret.AppendFormat(Properties.Resources.TriggerRepetition, GetBestTimeSpanString(Repetition.Interval), sduration);
+			}
+			if (EndBoundary != DateTime.MaxValue)
+				ret.AppendFormat(Properties.Resources.TriggerEndBoundary, AdjustToLocal(EndBoundary));
+			if (ret.Length > 0)
+				ret.Insert(0, Properties.Resources.HyphenSeparator);
+			return ret.ToString();
+		}
 	}

 	///
-	/// Represents a trigger that starts a task based on a weekly schedule. For example, the task starts at 8:00 A.M. on a specific day of the week every week or every other week.
+	/// 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.
 	///
 	/// <remarks>A WeeklyTrigger runs at a specified time on specified days of the week every week or interval of weeks.</remarks>
-	/// <example><![CDATA[
+	/// <example>
+	///
+	/// <![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;
-	/// ]]></example>
+	/// ]]>
+	///
+	/// </example>
 	[XmlRoot("CalendarTrigger", Namespace = TaskDefinition.tns, IsNullable = false)]
 	public sealed class WeeklyTrigger : Trigger, ICalendarTrigger, ITriggerDelay, IXmlSerializable
 	{
-		///
-		/// Creates an unbound instance of a <see cref="WeeklyTrigger"/>.
-		///
+		/// Creates an unbound instance of a <see cref="WeeklyTrigger"/>.
 		/// <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)
@@ -2742,24 +2357,19 @@
 				v1TriggerData.Data.weekly.WeeksInterval = 1;
 		}

-		internal WeeklyTrigger([NotNull] V2Interop.ITrigger iTrigger) : base(iTrigger) { }
-
-		///
-		/// Gets or sets the days of the week on which the task runs.
-		///
+		internal WeeklyTrigger([NotNull] ITrigger iTrigger) : base(iTrigger) { }
+
+		/// Gets or sets the days of the week on which the task runs.
 		[DefaultValue(0)]
 		public DaysOfTheWeek DaysOfWeek
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return (DaysOfTheWeek)((V2Interop.IWeeklyTrigger)v2Trigger).DaysOfWeek;
-				return v1TriggerData.Data.weekly.DaysOfTheWeek;
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IWeeklyTrigger)v2Trigger).DaysOfWeek = (short)value;
+			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;
@@ -2771,24 +2381,17 @@
 			}
 		}

-		///
-		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
-		///
+		/// Gets or sets a delay time that is randomly added to the start time of the trigger.
 		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
 		[DefaultValue(typeof(TimeSpan), "00:00:00")]
 		[XmlIgnore]
 		public TimeSpan RandomDelay
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return Task.StringToTimeSpan(((V2Interop.IWeeklyTrigger)v2Trigger).RandomDelay);
-				return GetUnboundValueOrDefault(nameof(RandomDelay), TimeSpan.Zero);
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IWeeklyTrigger)v2Trigger).RandomDelay = Task.TimeSpanToString(value);
+			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
@@ -2796,22 +2399,15 @@
 			}
 		}

-		///
-		/// Gets or sets the interval between the weeks in the schedule.
-		///
+		/// Gets or sets the interval between the weeks in the schedule.
 		[DefaultValue(1)]
 		public short WeeksInterval
 		{
-			get
-			{
-				if (v2Trigger != null)
-					return ((V2Interop.IWeeklyTrigger)v2Trigger).WeeksInterval;
-				return (short)v1TriggerData.Data.weekly.WeeksInterval;
-			}
-			set
-			{
-				if (v2Trigger != null)
-					((V2Interop.IWeeklyTrigger)v2Trigger).WeeksInterval = value;
+			get => ((IWeeklyTrigger)v2Trigger)?.WeeksInterval ?? (short)v1TriggerData.Data.weekly.WeeksInterval;
+			set
+			{
+				if (v2Trigger != null)
+					((IWeeklyTrigger)v2Trigger).WeeksInterval = value;
 				else
 				{
 					v1TriggerData.Data.weekly.WeeksInterval = (ushort)value;
@@ -2823,42 +2419,49 @@
 			}
 		}

-		///
-		/// Gets or sets a value that indicates the amount of time before the task is started.
-		///
+		/// Gets or sets a value that indicates the amount of time before the task is started.
 		/// <value>The delay duration.</value>
 		TimeSpan ITriggerDelay.Delay
 		{
-			get { return RandomDelay; }
-			set { RandomDelay = value; }
+			get => RandomDelay;
+			set => RandomDelay = value;
 		}

 		///
-		/// 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.
+		/// 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.
 		///
 		/// <param name="sourceTrigger">The source <see cref="Trigger"/>.</param>
 		public override void CopyProperties(Trigger sourceTrigger)
 		{
 			base.CopyProperties(sourceTrigger);
-			if (sourceTrigger != null && sourceTrigger.GetType() == GetType())
-			{
-				DaysOfWeek = ((WeeklyTrigger)sourceTrigger).DaysOfWeek;
-				WeeksInterval = ((WeeklyTrigger)sourceTrigger).WeeksInterval;
-			}
-		}
-
-		///
-		/// Indicates whether the current object is equal to another object of the same type.
-		///
+			if (sourceTrigger is WeeklyTrigger wt)
+			{
+				DaysOfWeek = wt.DaysOfWeek;
+				WeeksInterval = wt.WeeksInterval;
+			}
+		}
+
+		/// Indicates whether the current object is equal to another object of the same type.
 		/// <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) => base.Equals(other) && DaysOfWeek == ((WeeklyTrigger)other).DaysOfWeek && WeeksInterval == ((WeeklyTrigger)other).WeeksInterval;
-
-		///
-		/// Reads the subclass XML for V1 streams.
-		///
+		/// <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); }
+
+		/// Gets the non-localized trigger string for V2 triggers.
+		/// <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);
+		}
+
+		/// Reads the subclass XML for V1 streams.
 		/// <param name="reader">The reader.</param>
 		private void ReadMyXml(System.Xml.XmlReader reader)
 		{
@@ -2870,6 +2473,7 @@
 					case "WeeksInterval":
 						WeeksInterval = (short)reader.ReadElementContentAsInt();
 						break;
+
 					case "DaysOfWeek":
 						reader.Read();
 						DaysOfWeek = 0;
@@ -2887,6 +2491,7 @@
 						}
 						reader.ReadEndElement();
 						break;
+
 					default:
 						reader.Skip();
 						break;
@@ -2895,19 +2500,7 @@
 			reader.ReadEndElement();
 		}

-		///
-		/// Gets the non-localized trigger string for V2 triggers.
-		///
-		/// <returns>String describing the trigger.</returns>
-		protected override string V2GetTriggerString()
-		{
-			string days = TaskEnumGlobalizer.GetString(DaysOfWeek);
-			return string.Format(WeeksInterval == 1 ? Properties.Resources.TriggerWeekly1Week : Properties.Resources.TriggerWeeklyMultWeeks, AdjustToLocal(StartBoundary), days, WeeksInterval);
-		}
-
-		///
-		/// Writes the subclass XML for V1 streams.
-		///
+		/// Writes the subclass XML for V1 streams.
 		/// <param name="writer">The writer.</param>
 		private void WriteMyXml(System.Xml.XmlWriter writer)
 		{
@@ -2924,11 +2517,119 @@

 			writer.WriteEndElement();
 		}
-
-		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); }
+	}
+
+	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);
+		}
 	}
 }