using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using winPEAS.TaskScheduler.TaskEditor.Native;
using winPEAS.TaskScheduler.V1;
using winPEAS.TaskScheduler.V2;
using IPrincipal = winPEAS.TaskScheduler.V2.IPrincipal;
using TaskStatus = winPEAS.TaskScheduler.V1.TaskStatus;

namespace winPEAS.TaskScheduler
{
    /// <summary>Defines what versions of Task Scheduler or the AT command that the task is compatible with.</summary>
    public enum TaskCompatibility
	{
		/// <summary>The task is compatible with the AT command.</summary>
		AT,

		/// <summary>
		/// The task is compatible with Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).
		/// <para>Items not available when compared to V2:</para>
		/// <list type="bullet">
		/// <item>
		/// <term>TaskDefinition.Principal.GroupId - All account information can be retrieved via the UserId property.</term>
		/// </item>
		/// <item>
		/// <term>TaskLogonType values Group, None and S4U are not supported.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Principal.RunLevel == TaskRunLevel.Highest is not supported.</term>
		/// </item>
		/// <item>
		/// <term>
		/// Assigning access security to a task is not supported using TaskDefinition.RegistrationInfo.SecurityDescriptorSddlForm or in RegisterTaskDefinition.
		/// </term>
		/// </item>
		/// <item>
		/// <term>
		/// TaskDefinition.RegistrationInfo.Documentation, Source, URI and Version properties are only supported using this library. See
		/// details in the remarks for <see cref="TaskDefinition.Data"/>.
		/// </term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.AllowDemandStart cannot be false.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.AllowHardTerminate cannot be false.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.MultipleInstances can only be IgnoreNew.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.NetworkSettings cannot have any values.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.RestartCount can only be 0.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.StartWhenAvailable can only be false.</term>
		/// </item>
		/// <item>
		/// <term>
		/// TaskDefinition.Actions can only contain ExecAction instances unless the TaskDefinition.Actions.PowerShellConversion property has
		/// the Version1 flag set.
		/// </term>
		/// </item>
		/// <item>
		/// <term>
		/// TaskDefinition.Triggers cannot contain CustomTrigger, EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger instances.
		/// </term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Triggers cannot contain instances with delays set.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Triggers cannot contain instances with ExecutionTimeLimit or Id properties set.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Triggers cannot contain LogonTriggers instances with the UserId property set.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Triggers cannot contain MonthlyDOWTrigger instances with the RunOnLastWeekOfMonth property set to <c>true</c>.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Triggers cannot contain MonthlyTrigger instances with the RunOnDayWeekOfMonth property set to <c>true</c>.</term>
		/// </item>
		/// </list>
		/// </summary>
		V1,

		/// <summary>
		/// The task is compatible with Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).
		/// <para>
		/// This version is the baseline for the new, non-file based Task Scheduler. See <see cref="TaskCompatibility.V1"/> remarks for
		/// functionality that was not forward-compatible.
		/// </para>
		/// </summary>
		V2,

		/// <summary>
		/// The task is compatible with Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).
		/// <para>Changes from V2:</para>
		/// <list type="bullet">
		/// <item>
		/// <term>TaskDefinition.Principal.ProcessTokenSidType can be defined as a value other than Default.</term>
		/// </item>
		/// <item>
		/// <term>
		/// TaskDefinition.Actions may not contain EmailAction or ShowMessageAction instances unless the
		/// TaskDefinition.Actions.PowerShellConversion property has the Version2 flag set.
		/// </term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Principal.RequiredPrivileges can have privilege values assigned.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.DisallowStartOnRemoteAppSession can be set to true.</term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.UseUnifiedSchedulingEngine can be set to true.</term>
		/// </item>
		/// </list>
		/// </summary>
		V2_1,

		/// <summary>
		/// The task is compatible with Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).
		/// <para>Changes from V2_1:</para>
		/// <list type="bullet">
		/// <item>
		/// <term>
		/// TaskDefinition.Settings.MaintenanceSettings can have Period or Deadline be values other than TimeSpan.Zero or the Exclusive
		/// property set to true.
		/// </term>
		/// </item>
		/// <item>
		/// <term>TaskDefinition.Settings.Volatile can be set to true.</term>
		/// </item>
		/// </list>
		/// </summary>
		V2_2,

		/// <summary>
		/// The task is compatible with Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).
		/// <para>Changes from V2_2:</para>
		/// <list type="bullet">
		/// <item>
		/// <term>None published.</term>
		/// </item>
		/// </list>
		/// </summary>
		V2_3
	}

	/// <summary>Defines how the Task Scheduler service creates, updates, or disables the task.</summary>
	[DefaultValue(CreateOrUpdate)]
	public enum TaskCreation
	{
		/// <summary>The Task Scheduler service registers the task as a new task.</summary>
		Create = 2,

		/// <summary>
		/// The Task Scheduler service either registers the task as a new task or as an updated version if the task already exists.
		/// Equivalent to Create | Update.
		/// </summary>
		CreateOrUpdate = 6,

		/// <summary>
		/// The Task Scheduler service registers the disabled task. A disabled task cannot run until it is enabled. For more information,
		/// see Enabled Property of TaskSettings and Enabled Property of RegisteredTask.
		/// </summary>
		Disable = 8,

		/// <summary>
		/// The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal. When the
		/// TaskFolder.RegisterTaskDefinition or TaskFolder.RegisterTask functions are called with this flag to update a task, the Task
		/// Scheduler service does not add the ACE for the new context principal and does not remove the ACE from the old context principal.
		/// </summary>
		DontAddPrincipalAce = 0x10,

		/// <summary>
		/// The Task Scheduler service creates the task, but ignores the registration triggers in the task. By ignoring the registration
		/// triggers, the task will not execute when it is registered unless a time-based trigger causes it to execute on registration.
		/// </summary>
		IgnoreRegistrationTriggers = 0x20,

		/// <summary>
		/// The Task Scheduler service registers the task as an updated version of an existing task. When a task with a registration trigger
		/// is updated, the task will execute after the update occurs.
		/// </summary>
		Update = 4,

		/// <summary>
		/// The Task Scheduler service checks the syntax of the XML that describes the task but does not register the task. This constant
		/// cannot be combined with the Create, Update, or CreateOrUpdate values.
		/// </summary>
		ValidateOnly = 1
	}

	/// <summary>Defines how the Task Scheduler handles existing instances of the task when it starts a new instance of the task.</summary>
	[DefaultValue(IgnoreNew)]
	public enum TaskInstancesPolicy
	{
		/// <summary>Starts new instance while an existing instance is running.</summary>
		Parallel,

		/// <summary>Starts a new instance of the task after all other instances of the task are complete.</summary>
		Queue,

		/// <summary>Does not start a new instance if an existing instance of the task is running.</summary>
		IgnoreNew,

		/// <summary>Stops an existing instance of the task before it starts a new instance.</summary>
		StopExisting
	}

	/// <summary>Defines what logon technique is required to run a task.</summary>
	[DefaultValue(S4U)]
	public enum TaskLogonType
	{
		/// <summary>The logon method is not specified. Used for non-NT credentials.</summary>
		None,

		/// <summary>Use a password for logging on the user. The password must be supplied at registration time.</summary>
		Password,

		/// <summary>
		/// Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is
		/// used, no password is stored by the system and there is no access to either the network or to encrypted files.
		/// </summary>
		S4U,

		/// <summary>User must already be logged on. The task will be run only in an existing interactive session.</summary>
		InteractiveToken,

		/// <summary>Group activation. The groupId field specifies the group.</summary>
		Group,

		/// <summary>
		/// Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task.
		/// </summary>
		ServiceAccount,

		/// <summary>
		/// First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used.
		/// The password must be specified when a task is registered. This flag is not recommended for new tasks because it is less reliable
		/// than Password.
		/// </summary>
		InteractiveTokenOrPassword
	}

	/// <summary>Defines which privileges must be required for a secured task.</summary>
	public enum TaskPrincipalPrivilege
	{
		/// <summary>Required to create a primary token. User Right: Create a token object.</summary>
		SeCreateTokenPrivilege = 1,

		/// <summary>Required to assign the primary token of a process. User Right: Replace a process-level token.</summary>
		SeAssignPrimaryTokenPrivilege,

		/// <summary>Required to lock physical pages in memory. User Right: Lock pages in memory.</summary>
		SeLockMemoryPrivilege,

		/// <summary>Required to increase the quota assigned to a process. User Right: Adjust memory quotas for a process.</summary>
		SeIncreaseQuotaPrivilege,

		/// <summary>Required to read unsolicited input from a terminal device. User Right: Not applicable.</summary>
		SeUnsolicitedInputPrivilege,

		/// <summary>Required to create a computer account. User Right: Add workstations to domain.</summary>
		SeMachineAccountPrivilege,

		/// <summary>
		/// This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this
		/// privilege. User Right: Act as part of the operating system.
		/// </summary>
		SeTcbPrivilege,

		/// <summary>
		/// Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege
		/// identifies its holder as a security operator. User Right: Manage auditing and the security log.
		/// </summary>
		SeSecurityPrivilege,

		/// <summary>
		/// Required to take ownership of an object without being granted discretionary access. This privilege allows the owner value to be
		/// set only to those values that the holder may legitimately assign as the owner of an object. User Right: Take ownership of files
		/// or other objects.
		/// </summary>
		SeTakeOwnershipPrivilege,

		/// <summary>Required to load or unload a device driver. User Right: Load and unload device drivers.</summary>
		SeLoadDriverPrivilege,

		/// <summary>Required to gather profiling information for the entire system. User Right: Profile system performance.</summary>
		SeSystemProfilePrivilege,

		/// <summary>Required to modify the system time. User Right: Change the system time.</summary>
		SeSystemtimePrivilege,

		/// <summary>Required to gather profiling information for a single process. User Right: Profile single process.</summary>
		SeProfileSingleProcessPrivilege,

		/// <summary>Required to increase the base priority of a process. User Right: Increase scheduling priority.</summary>
		SeIncreaseBasePriorityPrivilege,

		/// <summary>Required to create a paging file. User Right: Create a pagefile.</summary>
		SeCreatePagefilePrivilege,

		/// <summary>Required to create a permanent object. User Right: Create permanent shared objects.</summary>
		SeCreatePermanentPrivilege,

		/// <summary>
		/// Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless
		/// of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL.
		/// This privilege is required by the RegSaveKey and RegSaveKeyExfunctions. The following access rights are granted if this
		/// privilege is held: READ_CONTROL, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_READ, FILE_TRAVERSE. User Right: Back up files and directories.
		/// </summary>
		SeBackupPrivilege,

		/// <summary>
		/// Required to perform restore operations. This privilege causes the system to grant all write access control to any file,
		/// regardless of the ACL specified for the file. Any access request other than write is still evaluated with the ACL. Additionally,
		/// this privilege enables you to set any valid user or group security identifier (SID) as the owner of a file. This privilege is
		/// required by the RegLoadKey function. The following access rights are granted if this privilege is held: WRITE_DAC, WRITE_OWNER,
		/// ACCESS_SYSTEM_SECURITY, FILE_GENERIC_WRITE, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, DELETE. User Right: Restore files and directories.
		/// </summary>
		SeRestorePrivilege,

		/// <summary>Required to shut down a local system. User Right: Shut down the system.</summary>
		SeShutdownPrivilege,

		/// <summary>Required to debug and adjust the memory of a process owned by another account. User Right: Debug programs.</summary>
		SeDebugPrivilege,

		/// <summary>Required to generate audit-log entries. Give this privilege to secure servers. User Right: Generate security audits.</summary>
		SeAuditPrivilege,

		/// <summary>
		/// Required to modify the nonvolatile RAM of systems that use this type of memory to store configuration information. User Right:
		/// Modify firmware environment values.
		/// </summary>
		SeSystemEnvironmentPrivilege,

		/// <summary>
		/// Required to receive notifications of changes to files or directories. This privilege also causes the system to skip all
		/// traversal access checks. It is enabled by default for all users. User Right: Bypass traverse checking.
		/// </summary>
		SeChangeNotifyPrivilege,

		/// <summary>Required to shut down a system by using a network request. User Right: Force shutdown from a remote system.</summary>
		SeRemoteShutdownPrivilege,

		/// <summary>Required to undock a laptop. User Right: Remove computer from docking station.</summary>
		SeUndockPrivilege,

		/// <summary>
		/// Required for a domain controller to use the LDAP directory synchronization services. This privilege allows the holder to read
		/// all objects and properties in the directory, regardless of the protection on the objects and properties. By default, it is
		/// assigned to the Administrator and LocalSystem accounts on domain controllers. User Right: Synchronize directory service data.
		/// </summary>
		SeSyncAgentPrivilege,

		/// <summary>
		/// Required to mark user and computer accounts as trusted for delegation. User Right: Enable computer and user accounts to be
		/// trusted for delegation.
		/// </summary>
		SeEnableDelegationPrivilege,

		/// <summary>Required to enable volume management privileges. User Right: Manage the files on a volume.</summary>
		SeManageVolumePrivilege,

		/// <summary>
		/// Required to impersonate. User Right: Impersonate a client after authentication. Windows XP/2000: This privilege is not
		/// supported. Note that this value is supported starting with Windows Server 2003, Windows XP with SP2, and Windows 2000 with SP4.
		/// </summary>
		SeImpersonatePrivilege,

		/// <summary>
		/// Required to create named file mapping objects in the global namespace during Terminal Services sessions. This privilege is
		/// enabled by default for administrators, services, and the local system account. User Right: Create global objects. Windows
		/// XP/2000: This privilege is not supported. Note that this value is supported starting with Windows Server 2003, Windows XP with
		/// SP2, and Windows 2000 with SP4.
		/// </summary>
		SeCreateGlobalPrivilege,

		/// <summary>Required to access Credential Manager as a trusted caller. User Right: Access Credential Manager as a trusted caller.</summary>
		SeTrustedCredManAccessPrivilege,

		/// <summary>Required to modify the mandatory integrity level of an object. User Right: Modify an object label.</summary>
		SeRelabelPrivilege,

		/// <summary>
		/// Required to allocate more memory for applications that run in the context of users. User Right: Increase a process working set.
		/// </summary>
		SeIncreaseWorkingSetPrivilege,

		/// <summary>Required to adjust the time zone associated with the computer's internal clock. User Right: Change the time zone.</summary>
		SeTimeZonePrivilege,

		/// <summary>Required to create a symbolic link. User Right: Create symbolic links.</summary>
		SeCreateSymbolicLinkPrivilege
	}

	/// <summary>
	/// Defines the types of process security identifier (SID) that can be used by tasks. These changes are used to specify the type of
	/// process SID in the IPrincipal2 interface.
	/// </summary>
	public enum TaskProcessTokenSidType
	{
		/// <summary>No changes will be made to the process token groups list.</summary>
		None = 0,

		/// <summary>
		/// A task SID that is derived from the task name will be added to the process token groups list, and the token default
		/// discretionary access control list (DACL) will be modified to allow only the task SID and local system full control and the
		/// account SID read control.
		/// </summary>
		Unrestricted = 1,

		/// <summary>A Task Scheduler will apply default settings to the task process.</summary>
		Default = 2
	}

	/// <summary>Defines how a task is run.</summary>
	[Flags]
	public enum TaskRunFlags
	{
		/// <summary>The task is run with all flags ignored.</summary>
		NoFlags = 0,

		/// <summary>The task is run as the user who is calling the Run method.</summary>
		AsSelf = 1,

		/// <summary>The task is run regardless of constraints such as "do not run on batteries" or "run only if idle".</summary>
		IgnoreConstraints = 2,

		/// <summary>The task is run using a terminal server session identifier.</summary>
		UseSessionId = 4,

		/// <summary>The task is run using a security identifier.</summary>
		UserSID = 8
	}

	/// <summary>Defines LUA elevation flags that specify with what privilege level the task will be run.</summary>
	public enum TaskRunLevel
	{
		/// <summary>Tasks will be run with the least privileges.</summary>
		[XmlEnum("LeastPrivilege")]
		LUA,

		/// <summary>Tasks will be run with the highest privileges.</summary>
		[XmlEnum("HighestAvailable")]
		Highest
	}

	/// <summary>
	/// Defines what kind of Terminal Server session state change you can use to trigger a task to start. These changes are used to specify
	/// the type of state change in the SessionStateChangeTrigger.
	/// </summary>
	public enum TaskSessionStateChangeType
	{
		/// <summary>
		/// Terminal Server console connection state change. For example, when you connect to a user session on the local computer by
		/// switching users on the computer.
		/// </summary>
		ConsoleConnect = 1,

		/// <summary>
		/// Terminal Server console disconnection state change. For example, when you disconnect to a user session on the local computer by
		/// switching users on the computer.
		/// </summary>
		ConsoleDisconnect = 2,

		/// <summary>
		/// Terminal Server remote connection state change. For example, when a user connects to a user session by using the Remote Desktop
		/// Connection program from a remote computer.
		/// </summary>
		RemoteConnect = 3,

		/// <summary>
		/// Terminal Server remote disconnection state change. For example, when a user disconnects from a user session while using the
		/// Remote Desktop Connection program from a remote computer.
		/// </summary>
		RemoteDisconnect = 4,

		/// <summary>
		/// Terminal Server session locked state change. For example, this state change causes the task to run when the computer is locked.
		/// </summary>
		SessionLock = 7,

		/// <summary>
		/// Terminal Server session unlocked state change. For example, this state change causes the task to run when the computer is unlocked.
		/// </summary>
		SessionUnlock = 8
	}

	/// <summary>Options for use when calling the SetSecurityDescriptorSddlForm methods.</summary>
	[Flags]
	public enum TaskSetSecurityOptions
	{
		/// <summary>No special handling.</summary>
		None = 0,

		/// <summary>The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal.</summary>
		DontAddPrincipalAce = 0x10
	}

	/***** WAITING TO DETERMINE USE CASE *****
	/// <summary>Success and error codes that some methods will expose through <see cref="COMExcpetion"/>.</summary>
	public enum TaskResultCode
	{
		/// <summary>The task is ready to run at its next scheduled time.</summary>
		TaskReady = 0x00041300,
		/// <summary>The task is currently running.</summary>
		TaskRunning = 0x00041301,
		/// <summary>The task will not run at the scheduled times because it has been disabled.</summary>
		TaskDisabled = 0x00041302,
		/// <summary>The task has not yet run.</summary>
		TaskHasNotRun = 0x00041303,
		/// <summary>There are no more runs scheduled for this task.</summary>
		TaskNoMoreRuns = 0x00041304,
		/// <summary>One or more of the properties that are needed to run this task on a schedule have not been set.</summary>
		TaskNotScheduled = 0x00041305,
		/// <summary>The last run of the task was terminated by the user.</summary>
		TaskTerminated = 0x00041306,
		/// <summary>Either the task has no triggers or the existing triggers are disabled or not set.</summary>
		TaskNoValidTriggers = 0x00041307,
		/// <summary>Event triggers do not have set run times.</summary>
		EventTrigger = 0x00041308,
		/// <summary>A task's trigger is not found.</summary>
		TriggerNotFound = 0x80041309,
		/// <summary>One or more of the properties required to run this task have not been set.</summary>
		TaskNotReady = 0x8004130A,
		/// <summary>There is no running instance of the task.</summary>
		TaskNotRunning = 0x8004130B,
		/// <summary>The Task Scheduler service is not installed on this computer.</summary>
		ServiceNotInstalled = 0x8004130C,
		/// <summary>The task object could not be opened.</summary>
		CannotOpenTask = 0x8004130D,
		/// <summary>The object is either an invalid task object or is not a task object.</summary>
		InvalidTask = 0x8004130E,
		/// <summary>No account information could be found in the Task Scheduler security database for the task indicated.</summary>
		AccountInformationNotSet = 0x8004130F,
		/// <summary>Unable to establish existence of the account specified.</summary>
		AccountNameNotFound = 0x80041310,
		/// <summary>Corruption was detected in the Task Scheduler security database; the database has been reset.</summary>
		AccountDbaseCorrupt = 0x80041311,
		/// <summary>Task Scheduler security services are available only on Windows NT.</summary>
		NoSecurityServices = 0x80041312,
		/// <summary>The task object version is either unsupported or invalid.</summary>
		UnknownObjectVersion = 0x80041313,
		/// <summary>The task has been configured with an unsupported combination of account settings and run time options.</summary>
		UnsupportedAccountOption = 0x80041314,
		/// <summary>The Task Scheduler Service is not running.</summary>
		ServiceNotRunning = 0x80041315,
		/// <summary>The task XML contains an unexpected node.</summary>
		UnexpectedNode = 0x80041316,
		/// <summary>The task XML contains an element or attribute from an unexpected namespace.</summary>
		Namespace = 0x80041317,
		/// <summary>The task XML contains a value which is incorrectly formatted or out of range.</summary>
		InvalidValue = 0x80041318,
		/// <summary>The task XML is missing a required element or attribute.</summary>
		MissingNode = 0x80041319,
		/// <summary>The task XML is malformed.</summary>
		MalformedXml = 0x8004131A,
		/// <summary>The task is registered, but not all specified triggers will start the task.</summary>
		SomeTriggersFailed = 0x0004131B,
		/// <summary>The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal.</summary>
		BatchLogonProblem = 0x0004131C,
		/// <summary>The task XML contains too many nodes of the same type.</summary>
		TooManyNodes = 0x8004131D,
		/// <summary>The task cannot be started after the trigger end boundary.</summary>
		PastEndBoundary = 0x8004131E,
		/// <summary>An instance of this task is already running.</summary>
		AlreadyRunning = 0x8004131F,
		/// <summary>The task will not run because the user is not logged on.</summary>
		UserNotLoggedOn = 0x80041320,
		/// <summary>The task image is corrupt or has been tampered with.</summary>
		InvalidTaskHash = 0x80041321,
		/// <summary>The Task Scheduler service is not available.</summary>
		ServiceNotAvailable = 0x80041322,
		/// <summary>The Task Scheduler service is too busy to handle your request. Please try again later.</summary>
		ServiceTooBusy = 0x80041323,
		/// <summary>
		/// The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition.
		/// </summary>
		TaskAttempted = 0x80041324,
		/// <summary>The Task Scheduler service has asked the task to run.</summary>
		TaskQueued = 0x00041325,
		/// <summary>The task is disabled.</summary>
		TaskDisabled = 0x80041326,
		/// <summary>The task has properties that are not compatible with earlier versions of Windows.</summary>
		TaskNotV1Compatible = 0x80041327,
		/// <summary>The task settings do not allow the task to start on demand.</summary>
		StartOnDemand = 0x80041328,
	}
	*/

	/// <summary>Defines the different states that a registered task can be in.</summary>
	public enum TaskState
	{
		/// <summary>The state of the task is unknown.</summary>
		Unknown,

		/// <summary>
		/// The task is registered but is disabled and no instances of the task are queued or running. The task cannot be run until it is enabled.
		/// </summary>
		Disabled,

		/// <summary>Instances of the task are queued.</summary>
		Queued,

		/// <summary>The task is ready to be executed, but no instances are queued or running.</summary>
		Ready,

		/// <summary>One or more instances of the task is running.</summary>
		Running
	}

	/// <summary>
	/// Specifies how the Task Scheduler performs tasks when the computer is in an idle condition. For information about idle conditions,
	/// see Task Idle Conditions.
	/// </summary>
	[PublicAPI]
	public sealed class IdleSettings : IDisposable, INotifyPropertyChanged
	{
		private readonly IIdleSettings v2Settings;
		private ITask v1Task;

		internal IdleSettings([NotNull] IIdleSettings iSettings) => v2Settings = iSettings;

		internal IdleSettings([NotNull] ITask iTask) => v1Task = iTask;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Gets or sets a value that indicates the amount of time that the computer must be in an idle state before the task is run.
		/// </summary>
		/// <value>
		/// A value that indicates the amount of time that the computer must be in an idle state before the task is run. The minimum value
		/// is one minute. If this value is <c>TimeSpan.Zero</c>, then the delay will be set to the default of 10 minutes.
		/// </value>
		[DefaultValue(typeof(TimeSpan), "00:10:00")]
		[XmlElement("Duration")]
		public TimeSpan IdleDuration
		{
			get
			{
				if (v2Settings != null)
					return Task.StringToTimeSpan(v2Settings.IdleDuration);
				v1Task.GetIdleWait(out var _, out var deadMin);
				return TimeSpan.FromMinutes(deadMin);
			}
			set
			{
				if (v2Settings != null)
				{
					if (value != TimeSpan.Zero && value < TimeSpan.FromMinutes(1))
						throw new ArgumentOutOfRangeException(nameof(IdleDuration));
					v2Settings.IdleDuration = Task.TimeSpanToString(value);
				}
				else
				{
					v1Task.SetIdleWait((ushort)WaitTimeout.TotalMinutes, (ushort)value.TotalMinutes);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates whether the task is restarted when the computer cycles into an idle condition more
		/// than once.
		/// </summary>
		[DefaultValue(false)]
		public bool RestartOnIdle
		{
			get => v2Settings?.RestartOnIdle ?? v1Task.HasFlags(TaskFlags.RestartOnIdleResume);
			set
			{
				if (v2Settings != null)
					v2Settings.RestartOnIdle = value;
				else
					v1Task.SetFlags(TaskFlags.RestartOnIdleResume, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the Task Scheduler will terminate the task if the idle condition ends before
		/// the task is completed.
		/// </summary>
		[DefaultValue(true)]
		public bool StopOnIdleEnd
		{
			get => v2Settings?.StopOnIdleEnd ?? v1Task.HasFlags(TaskFlags.KillOnIdleEnd);
			set
			{
				if (v2Settings != null)
					v2Settings.StopOnIdleEnd = value;
				else
					v1Task.SetFlags(TaskFlags.KillOnIdleEnd, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. If no
		/// value is specified for this property, then the Task Scheduler service will wait indefinitely for an idle condition to occur.
		/// </summary>
		/// <value>
		/// A value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. The minimum time
		/// allowed is 1 minute. If this value is <c>TimeSpan.Zero</c>, then the delay will be set to the default of 1 hour.
		/// </value>
		[DefaultValue(typeof(TimeSpan), "01:00:00")]
		public TimeSpan WaitTimeout
		{
			get
			{
				if (v2Settings != null)
					return Task.StringToTimeSpan(v2Settings.WaitTimeout);
				v1Task.GetIdleWait(out var idleMin, out var _);
				return TimeSpan.FromMinutes(idleMin);
			}
			set
			{
				if (v2Settings != null)
				{
					if (value != TimeSpan.Zero && value < TimeSpan.FromMinutes(1))
						throw new ArgumentOutOfRangeException(nameof(WaitTimeout));
					v2Settings.WaitTimeout = Task.TimeSpanToString(value);
				}
				else
				{
					v1Task.SetIdleWait((ushort)value.TotalMinutes, (ushort)IdleDuration.TotalMinutes);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (v2Settings != null)
				Marshal.ReleaseComObject(v2Settings);
			v1Task = null;
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString()
		{
			if (v2Settings != null || v1Task != null)
				return DebugHelper.GetDebugString(this);
			return base.ToString();
		}

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	/// <summary>Specifies the task settings the Task scheduler will use to start task during Automatic maintenance.</summary>
	[XmlType(IncludeInSchema = false)]
	[PublicAPI]
	public sealed class MaintenanceSettings : IDisposable, INotifyPropertyChanged
	{
		private readonly ITaskSettings3 iSettings;
		private IMaintenanceSettings iMaintSettings;

		internal MaintenanceSettings([CanBeNull] ITaskSettings3 iSettings3)
		{
			iSettings = iSettings3;
			if (iSettings3 != null)
				iMaintSettings = iSettings.MaintenanceSettings;
		}

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Gets or sets the amount of time after which the Task scheduler attempts to run the task during emergency Automatic maintenance,
		/// if the task failed to complete during regular Automatic maintenance. The minimum value is one day. The value of the <see
		/// cref="Deadline"/> property should be greater than the value of the <see cref="Period"/> property. If the deadline is not
		/// specified the task will not be started during emergency Automatic maintenance.
		/// </summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.2.</exception>
		[DefaultValue(typeof(TimeSpan), "00:00:00")]
		public TimeSpan Deadline
		{
			get => iMaintSettings != null ? Task.StringToTimeSpan(iMaintSettings.Deadline) : TimeSpan.Zero;
			set
			{
				if (iSettings != null)
				{
					if (iMaintSettings == null && value != TimeSpan.Zero)
						iMaintSettings = iSettings.CreateMaintenanceSettings();
					if (iMaintSettings != null)
						iMaintSettings.Deadline = Task.TimeSpanToString(value);
				}
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_2);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a value indicating whether the Task Scheduler must start the task during the Automatic maintenance in exclusive
		/// mode. The exclusivity is guaranteed only between other maintenance tasks and doesn't grant any ordering priority of the task. If
		/// exclusivity is not specified, the task is started in parallel with other maintenance tasks.
		/// </summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.2.</exception>
		[DefaultValue(false)]
		public bool Exclusive
		{
			get => iMaintSettings != null && iMaintSettings.Exclusive;
			set
			{
				if (iSettings != null)
				{
					if (iMaintSettings == null && value)
						iMaintSettings = iSettings.CreateMaintenanceSettings();
					if (iMaintSettings != null)
						iMaintSettings.Exclusive = value;
				}
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_2);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the amount of time the task needs to be started during Automatic maintenance. The minimum value is one minute.
		/// </summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.2.</exception>
		[DefaultValue(typeof(TimeSpan), "00:00:00")]
		public TimeSpan Period
		{
			get => iMaintSettings != null ? Task.StringToTimeSpan(iMaintSettings.Period) : TimeSpan.Zero;
			set
			{
				if (iSettings != null)
				{
					if (iMaintSettings == null && value != TimeSpan.Zero)
						iMaintSettings = iSettings.CreateMaintenanceSettings();
					if (iMaintSettings != null)
						iMaintSettings.Period = Task.TimeSpanToString(value);
				}
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_2);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (iMaintSettings != null)
				Marshal.ReleaseComObject(iMaintSettings);
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString() => iMaintSettings != null ? DebugHelper.GetDebugString(this) : base.ToString();

		internal bool IsSet() => iMaintSettings != null && (iMaintSettings.Period != null || iMaintSettings.Deadline != null || iMaintSettings.Exclusive);

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	/// <summary>Provides the settings that the Task Scheduler service uses to obtain a network profile.</summary>
	[XmlType(IncludeInSchema = false)]
	[PublicAPI]
	public sealed class NetworkSettings : IDisposable, INotifyPropertyChanged
	{
		private readonly INetworkSettings v2Settings;

		internal NetworkSettings([CanBeNull] INetworkSettings iSettings) => v2Settings = iSettings;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>Gets or sets a GUID value that identifies a network profile.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(typeof(Guid), "00000000-0000-0000-0000-000000000000")]
		public Guid Id
		{
			get
			{
				string id = null;
				if (v2Settings != null)
					id = v2Settings.Id;
				return string.IsNullOrEmpty(id) ? Guid.Empty : new Guid(id);
			}
			set
			{
				if (v2Settings != null)
					v2Settings.Id = value == Guid.Empty ? null : value.ToString();
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the name of a network profile. The name is used for display purposes.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(null)]
		public string Name
		{
			get => v2Settings?.Name;
			set
			{
				if (v2Settings != null)
					v2Settings.Name = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (v2Settings != null)
				Marshal.ReleaseComObject(v2Settings);
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString()
		{
			if (v2Settings != null)
				return DebugHelper.GetDebugString(this);
			return base.ToString();
		}

		internal bool IsSet() => v2Settings != null && (!string.IsNullOrEmpty(v2Settings.Id) || !string.IsNullOrEmpty(v2Settings.Name));

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	/// <summary>Provides the methods to get information from and control a running task.</summary>
	[XmlType(IncludeInSchema = false)]
	[PublicAPI]
	public sealed class RunningTask : Task
	{
		private readonly IRunningTask v2RunningTask;

		internal RunningTask([NotNull] TaskService svc, [NotNull] IRegisteredTask iTask, [NotNull] IRunningTask iRunningTask)
			: base(svc, iTask) => v2RunningTask = iRunningTask;

		internal RunningTask([NotNull] TaskService svc, [NotNull] ITask iTask)
			: base(svc, iTask)
		{
		}

		/// <summary>Gets the process ID for the engine (process) which is running the task.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		public uint EnginePID
		{
			get
			{
				if (v2RunningTask != null)
					return v2RunningTask.EnginePID;
				throw new NotV1SupportedException();
			}
		}

		/// <summary>Gets the name of the current action that the running task is performing.</summary>
		public string CurrentAction => v2RunningTask != null ? v2RunningTask.CurrentAction : v1Task.GetApplicationName();

		/// <summary>Gets the GUID identifier for this instance of the task.</summary>
		public Guid InstanceGuid => v2RunningTask != null ? new Guid(v2RunningTask.InstanceGuid) : Guid.Empty;

		/// <summary>Gets the operational state of the running task.</summary>
		public override TaskState State => v2RunningTask?.State ?? base.State;

		/// <summary>Releases all resources used by this class.</summary>
		public new void Dispose()
		{
			base.Dispose();
			if (v2RunningTask != null) Marshal.ReleaseComObject(v2RunningTask);
		}

		/// <summary>Refreshes all of the local instance variables of the task.</summary>
		/// <exception cref="InvalidOperationException">Thrown if task is no longer running.</exception>
		public void Refresh()
		{
			try { v2RunningTask?.Refresh(); }
			catch (COMException ce) when ((uint)ce.ErrorCode == 0x8004130B)
			{
				throw new InvalidOperationException("The current task is no longer running.", ce);
			}
		}
	}

	/// <summary>
	/// Provides the methods that are used to run the task immediately, get any running instances of the task, get or set the credentials
	/// that are used to register the task, and the properties that describe the task.
	/// </summary>
	[XmlType(IncludeInSchema = false)]
	[PublicAPI]
	public class Task : IDisposable, IComparable, IComparable<Task>, INotifyPropertyChanged
	{
		internal const AccessControlSections defaultAccessControlSections = AccessControlSections.Owner | AccessControlSections.Group | AccessControlSections.Access;
		internal const SecurityInfos defaultSecurityInfosSections = SecurityInfos.Owner | SecurityInfos.Group | SecurityInfos.DiscretionaryAcl;
		internal ITask v1Task;

		private static readonly int osLibMinorVer = GetOSLibraryMinorVersion();
		private static readonly DateTime v2InvalidDate = new DateTime(1899, 12, 30);
		private readonly IRegisteredTask v2Task;
		private TaskDefinition myTD;

		internal Task([NotNull] TaskService svc, [NotNull] ITask iTask)
		{
			TaskService = svc;
			v1Task = iTask;
			ReadOnly = false;
		}

		internal Task([NotNull] TaskService svc, [NotNull] IRegisteredTask iTask, ITaskDefinition iDef = null)
		{
			TaskService = svc;
			v2Task = iTask;
			if (iDef != null)
				myTD = new TaskDefinition(iDef);
			ReadOnly = false;
		}

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>Gets the definition of the task.</summary>
		[NotNull]
		public TaskDefinition Definition => myTD ??= v2Task != null ? new TaskDefinition(GetV2Definition(TaskService, v2Task, true)) : new TaskDefinition(v1Task, Name);

		/// <summary>Gets or sets a Boolean value that indicates if the registered task is enabled.</summary>
		/// <remarks>
		/// As of version 1.8.1, under V1 systems (prior to Vista), this property will immediately update the Disabled state and re-save the
		/// current task. If changes have been made to the <see cref="TaskDefinition"/>, then those changes will be saved.
		/// </remarks>
		public bool Enabled
		{
			get => v2Task?.Enabled ?? Definition.Settings.Enabled;
			set
			{
				if (v2Task != null)
					v2Task.Enabled = value;
				else
				{
					Definition.Settings.Enabled = value;
					Definition.V1Save(null);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets an instance of the parent folder.</summary>
		/// <value>A <see cref="TaskFolder"/> object representing the parent folder of this task.</value>
		[NotNull]
		public TaskFolder Folder
		{
			get
			{
				if (v2Task == null)
					return TaskService.RootFolder;

				var path = v2Task.Path;
				var parentPath = System.IO.Path.GetDirectoryName(path);
				if (string.IsNullOrEmpty(parentPath) || parentPath == TaskFolder.rootString)
					return TaskService.RootFolder;
				return TaskService.GetFolder(parentPath);
			}
		}

		/// <summary>Gets a value indicating whether this task instance is active.</summary>
		/// <value><c>true</c> if this task instance is active; otherwise, <c>false</c>.</value>
		public bool IsActive
		{
			get
			{
				var now = DateTime.Now;
				if (!Definition.Settings.Enabled) return false;
				foreach (var trigger in Definition.Triggers)
				{
					if (!trigger.Enabled || now < trigger.StartBoundary || now > trigger.EndBoundary) continue;
					if (!(trigger is ICalendarTrigger) || DateTime.MinValue != NextRunTime || trigger is TimeTrigger)
						return true;
				}
				return false;
			}
		}

		/// <summary>Gets the time the registered task was last run.</summary>
		/// <value>Returns <see cref="DateTime.MinValue"/> if there are no prior run times.</value>
		public DateTime LastRunTime
		{
			get
			{
				if (v2Task == null) return v1Task.GetMostRecentRunTime();
				var dt = v2Task.LastRunTime;
				return dt == v2InvalidDate ? DateTime.MinValue : dt;
			}
		}

		/// <summary>Gets the results that were returned the last time the registered task was run.</summary>
		/// <remarks>The value returned is the last exit code of the last program run via an <see cref="Action.ExecAction"/>.</remarks>
		/// <example>
		/// <code lang="cs">
		///<![CDATA[
		/// // See if the last run of a task returned an error code
		/// if (TaskService.Instance.GetTask("MyTask").LastTaskResult != 0)
		/// MessageBox.Show("This program has an error.");
		///]]>
		/// </code>
		/// </example>
		public int LastTaskResult
		{
			get
			{
				if (v2Task != null)
					return v2Task.LastTaskResult;
				return (int)v1Task.GetExitCode();
			}
		}

		/// <summary>Gets the time when the registered task is next scheduled to run.</summary>
		/// <value>Returns <see cref="DateTime.MinValue"/> if there are no future run times.</value>
		/// <remarks>
		/// Potentially breaking change in release 1.8.2. For Task Scheduler 2.0, the return value prior to 1.8.2 would be Dec 30, 1899 if
		/// there were no future run times. For 1.0, that value would have been <c>DateTime.MinValue</c>. In release 1.8.2 and later, all
		/// versions will return <c>DateTime.MinValue</c> if there are no future run times. While this is different from the native 2.0
		/// library, it was deemed more appropriate to have consistency between the two libraries and with other .NET libraries.
		/// </remarks>
		public DateTime NextRunTime
		{
			get
			{
				if (v2Task == null) return v1Task.GetNextRunTime();
				var ret = v2Task.NextRunTime;
				if (ret != DateTime.MinValue && ret != v2InvalidDate) return ret == v2InvalidDate ? DateTime.MinValue : ret;
				var nrts = GetRunTimes(DateTime.Now, DateTime.MaxValue, 1);
				ret = nrts.Length > 0 ? nrts[0] : DateTime.MinValue;
				return ret == v2InvalidDate ? DateTime.MinValue : ret;
			}
		}

		/// <summary>
		/// Gets a value indicating whether this task is read only. Only available if <see
		/// cref="TaskScheduler.TaskService.AllowReadOnlyTasks"/> is <c>true</c>.
		/// </summary>
		/// <value><c>true</c> if read only; otherwise, <c>false</c>.</value>
		public bool ReadOnly { get; internal set; }

		/// <summary>Gets or sets the security descriptor for the task.</summary>
		/// <value>The security descriptor.</value>
		[Obsolete("This property will be removed in deference to the GetAccessControl, GetSecurityDescriptorSddlForm, SetAccessControl and SetSecurityDescriptorSddlForm methods.")]
		public GenericSecurityDescriptor SecurityDescriptor
		{
			get
			{
				var sddl = GetSecurityDescriptorSddlForm();
				return new RawSecurityDescriptor(sddl);
			}
			set => SetSecurityDescriptorSddlForm(value.GetSddlForm(defaultAccessControlSections));
		}

		/// <summary>Gets the operational state of the registered task.</summary>
		public virtual TaskState State
		{
			get
			{
				if (v2Task != null)
					return v2Task.State;

				V1Reactivate();
				if (!Enabled)
					return TaskState.Disabled;
				switch (v1Task.GetStatus())
				{
					case TaskStatus.Ready:
					case TaskStatus.NeverRun:
					case TaskStatus.NoMoreRuns:
					case TaskStatus.Terminated:
						return TaskState.Ready;

					case TaskStatus.Running:
						return TaskState.Running;

					case TaskStatus.Disabled:
						return TaskState.Disabled;
					// case TaskStatus.NotScheduled: case TaskStatus.NoTriggers: case TaskStatus.NoTriggerTime:
					default:
						return TaskState.Unknown;
				}
			}
		}

		/// <summary>Gets or sets the <see cref="TaskService"/> that manages this task.</summary>
		/// <value>The task service.</value>
		public TaskService TaskService { get; }

		/// <summary>Gets the name of the registered task.</summary>
		[NotNull]
		public string Name => v2Task != null ? v2Task.Name : System.IO.Path.GetFileNameWithoutExtension(GetV1Path(v1Task));

		/// <summary>Gets the number of times the registered task has missed a scheduled run.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		public int NumberOfMissedRuns => v2Task?.NumberOfMissedRuns ?? throw new NotV1SupportedException();

		/// <summary>Gets the path to where the registered task is stored.</summary>
		[NotNull]
		public string Path => v2Task != null ? v2Task.Path : "\\" + Name;

		/// <summary>Gets the XML-formatted registration information for the registered task.</summary>
		public string Xml => v2Task != null ? v2Task.Xml : Definition.XmlText;

		/// <summary>
		/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current
		/// instance precedes, follows, or occurs in the same position in the sort order as the other object.
		/// </summary>
		/// <param name="other">An object to compare with this instance.</param>
		/// <returns>A value that indicates the relative order of the objects being compared.</returns>
		public int CompareTo(Task other) => string.Compare(Path, other?.Path, StringComparison.InvariantCulture);

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (v2Task != null)
				Marshal.ReleaseComObject(v2Task);
			v1Task = null;
		}

		/// <summary>Exports the task to the specified file in XML.</summary>
		/// <param name="outputFileName">Name of the output file.</param>
		public void Export([NotNull] string outputFileName) => File.WriteAllText(outputFileName, Xml, Encoding.Unicode);

		/// <summary>
		/// Gets a <see cref="TaskSecurity"/> object that encapsulates the specified type of access control list (ACL) entries for the task
		/// described by the current <see cref="Task"/> object.
		/// </summary>
		/// <returns>A <see cref="TaskSecurity"/> object that encapsulates the access control rules for the current task.</returns>
		public TaskSecurity GetAccessControl() => GetAccessControl(defaultAccessControlSections);

		/// <summary>
		/// Gets a <see cref="TaskSecurity"/> object that encapsulates the specified type of access control list (ACL) entries for the task
		/// described by the current <see cref="Task"/> object.
		/// </summary>
		/// <param name="includeSections">
		/// One of the <see cref="System.Security.AccessControl.AccessControlSections"/> values that specifies which group of access control
		/// entries to retrieve.
		/// </param>
		/// <returns>A <see cref="TaskSecurity"/> object that encapsulates the access control rules for the current task.</returns>
		public TaskSecurity GetAccessControl(AccessControlSections includeSections) => new TaskSecurity(this, includeSections);

		/// <summary>Gets all instances of the currently running registered task.</summary>
		/// <returns>A <see cref="RunningTaskCollection"/> with all instances of current task.</returns>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[NotNull, ItemNotNull]
		public RunningTaskCollection GetInstances() => v2Task != null
			? new RunningTaskCollection(TaskService, v2Task.GetInstances(0))
			: throw new NotV1SupportedException();

		/// <summary>
		/// Gets the last registration time, looking first at the <see cref="TaskRegistrationInfo.Date"/> value and then looking for the
		/// most recent registration event in the Event Log.
		/// </summary>
		/// <returns><see cref="DateTime"/> of the last registration or <see cref="DateTime.MinValue"/> if no value can be found.</returns>
		public DateTime GetLastRegistrationTime()
		{
			var ret = Definition.RegistrationInfo.Date;
			if (ret != DateTime.MinValue) return ret;
			var log = new TaskEventLog(Path, new[] { (int)StandardTaskEventId.JobRegistered }, null, TaskService.TargetServer, TaskService.UserAccountDomain, TaskService.UserName, TaskService.UserPassword);
			if (!log.Enabled) return ret;
			foreach (var item in log)
			{
				if (item.TimeCreated.HasValue)
					return item.TimeCreated.Value;
			}
			return ret;
		}

		/// <summary>Gets the times that the registered task is scheduled to run during a specified time.</summary>
		/// <param name="start">The starting time for the query.</param>
		/// <param name="end">The ending time for the query.</param>
		/// <param name="count">The requested number of runs. A value of 0 will return all times requested.</param>
		/// <returns>The scheduled times that the task will run.</returns>
		[NotNull]
		public DateTime[] GetRunTimes(DateTime start, DateTime end, uint count = 0)
		{
			const ushort TASK_MAX_RUN_TIMES = 1440;

			NativeMethods.SYSTEMTIME stStart = start;
			NativeMethods.SYSTEMTIME stEnd = end;
			var runTimes = IntPtr.Zero;
			var ret = new DateTime[0];
			try
			{
				if (v2Task != null)
					v2Task.GetRunTimes(ref stStart, ref stEnd, ref count, ref runTimes);
				else
				{
					var count1 = count > 0 && count <= TASK_MAX_RUN_TIMES ? (ushort)count : TASK_MAX_RUN_TIMES;
					v1Task.GetRunTimes(ref stStart, ref stEnd, ref count1, ref runTimes);
					count = count1;
				}
				ret = InteropUtil.ToArray<NativeMethods.SYSTEMTIME, DateTime>(runTimes, (int)count);
			}
			catch (Exception ex)
			{
				Debug.WriteLine($"Task.GetRunTimes failed: Error {ex}.");
			}
			finally
			{
				Marshal.FreeCoTaskMem(runTimes);
			}
			Debug.WriteLine($"Task.GetRunTimes ({(v2Task != null ? "V2" : "V1")}): Returned {count} items from {stStart} to {stEnd}.");
			return ret;
		}

		/// <summary>Gets the security descriptor for the task. Not available to Task Scheduler 1.0.</summary>
		/// <param name="includeSections">Section(s) of the security descriptor to return.</param>
		/// <returns>The security descriptor for the task.</returns>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		public string GetSecurityDescriptorSddlForm(SecurityInfos includeSections = defaultSecurityInfosSections) => v2Task != null ? v2Task.GetSecurityDescriptor((int)includeSections) : throw new NotV1SupportedException();

		/// <summary>
		/// Updates the task with any changes made to the <see cref="Definition"/> by calling <see
		/// cref="TaskFolder.RegisterTaskDefinition(string, TaskDefinition)"/> from the currently registered folder using the currently
		/// registered name.
		/// </summary>
		/// <exception cref="System.Security.SecurityException">Thrown if task was previously registered with a password.</exception>
		public void RegisterChanges()
		{
			if (Definition.Principal.RequiresPassword())
				throw new SecurityException("Tasks which have been registered previously with stored passwords must use the TaskFolder.RegisterTaskDefinition method for updates.");
			if (v2Task != null)
				TaskService.GetFolder(System.IO.Path.GetDirectoryName(Path)).RegisterTaskDefinition(Name, Definition, TaskCreation.Update, null, null, Definition.Principal.LogonType);
			else
				TaskService.RootFolder.RegisterTaskDefinition(Name, Definition);
		}

		/// <summary>Runs the registered task immediately.</summary>
		/// <param name="parameters">
		/// <para>
		/// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters,
		/// call this method without any values (e.g.
		/// <code>Run()</code>
		/// ).
		/// </para>
		/// <para>
		/// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value,
		/// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in
		/// the action properties.
		/// </para>
		/// <para>
		/// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables,
		/// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties.
		/// </para>
		/// <para>
		/// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see
		/// <a href="https://docs.microsoft.com/en-us/windows/desktop/taskschd/task-actions#using-variables-in-action-properties">Task Actions</a>.
		/// </para>
		/// </param>
		/// <returns>A <see cref="RunningTask"/> instance that defines the new instance of the task.</returns>
		/// <example>
		/// <code lang="cs">
		///<![CDATA[
		/// // Run the current task with a parameter
		/// var runningTask = myTaskInstance.Run("info");
		/// Console.Write(string.Format("Running task's current action is {0}.", runningTask.CurrentAction));
		///]]>
		/// </code>
		/// </example>
		public RunningTask Run(params string[] parameters)
		{
			if (v2Task != null)
			{
				if (parameters.Length > 32)
					throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 values is allowed.");
				if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260))
					throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, all individual parameters must be less than 260 characters.");
				var irt = v2Task.Run(parameters.Length == 0 ? null : parameters);
				return irt != null ? new RunningTask(TaskService, v2Task, irt) : null;
			}

			v1Task.Run();
			return new RunningTask(TaskService, v1Task);
		}

		/// <summary>Runs the registered task immediately using specified flags and a session identifier.</summary>
		/// <param name="flags">Defines how the task is run.</param>
		/// <param name="sessionID">
		/// <para>The terminal server session in which you want to start the task.</para>
		/// <para>
		/// If the <see cref="TaskRunFlags.UseSessionId"/> value is not passed into the <paramref name="flags"/> parameter, then the value
		/// specified in this parameter is ignored.If the <see cref="TaskRunFlags.UseSessionId"/> value is passed into the flags parameter
		/// and the sessionID value is less than or equal to 0, then an invalid argument error will be returned.
		/// </para>
		/// <para>
		/// If the <see cref="TaskRunFlags.UseSessionId"/> value is passed into the <paramref name="flags"/> parameter and the sessionID
		/// value is a valid session ID greater than 0 and if no value is specified for the user parameter, then the Task Scheduler service
		/// will try to start the task interactively as the user who is logged on to the specified session.
		/// </para>
		/// <para>
		/// If the <see cref="TaskRunFlags.UseSessionId"/> value is passed into the <paramref name="flags"/> parameter and the sessionID
		/// value is a valid session ID greater than 0 and if a user is specified in the user parameter, then the Task Scheduler service
		/// will try to start the task interactively as the user who is specified in the user parameter.
		/// </para>
		/// </param>
		/// <param name="user">The user for which the task runs.</param>
		/// <param name="parameters">
		/// <para>
		/// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters,
		/// call this method without any values (e.g.
		/// <code>RunEx(0, 0, "MyUserName")</code>
		/// ).
		/// </para>
		/// <para>
		/// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value,
		/// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in
		/// the action properties.
		/// </para>
		/// <para>
		/// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables,
		/// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties.
		/// </para>
		/// <para>
		/// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see
		/// <a href="https://docs.microsoft.com/en-us/windows/desktop/taskschd/task-actions#using-variables-in-action-properties">Task Actions</a>.
		/// </para>
		/// </param>
		/// <returns>A <see cref="RunningTask"/> instance that defines the new instance of the task.</returns>
		/// <remarks>
		/// <para>
		/// This method will return without error, but the task will not run if the AllowDemandStart property of ITaskSettings is set to
		/// false for the task.
		/// </para>
		/// <para>If RunEx is invoked from a disabled task, it will return <c>null</c> and the task will not be run.</para>
		/// </remarks>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		/// <example>
		/// <code lang="cs">
		///<![CDATA[
		/// // Run the current task with a parameter as a different user and ignoring any of the conditions.
		/// var runningTask = myTaskInstance.RunEx(TaskRunFlags.IgnoreConstraints, 0, "DOMAIN\\User", "info");
		/// Console.Write(string.Format("Running task's current action is {0}.", runningTask.CurrentAction));
		///]]>
		/// </code>
		/// </example>
		public RunningTask RunEx(TaskRunFlags flags, int sessionID, string user, params string[] parameters)
		{
			if (v2Task == null) throw new NotV1SupportedException();
			if (parameters == null || parameters.Any(s => s == null))
				throw new ArgumentNullException(nameof(parameters), "The array and none of the values passed as parameters may be `null`.");
			if (parameters.Length > 32)
				throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 parameters can be supplied to RunEx.");
			if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260))
				throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, no individual parameter may be more than 260 characters.");
			var irt = v2Task.RunEx(parameters.Length == 0 ? null : parameters, (int)flags, sessionID, user);
			return irt != null ? new RunningTask(TaskService, v2Task, irt) : null;
		}

		/// <summary>
		/// Applies access control list (ACL) entries described by a <see cref="TaskSecurity"/> object to the file described by the current
		/// <see cref="Task"/> object.
		/// </summary>
		/// <param name="taskSecurity">
		/// A <see cref="TaskSecurity"/> object that describes an access control list (ACL) entry to apply to the current task.
		/// </param>
		/// <example>
		/// <para>Give read access to all authenticated users for a task.</para>
		/// <code lang="cs">
		///<![CDATA[
		/// // Assume variable 'task' is a valid Task instance
		/// var taskSecurity = task.GetAccessControl();
		/// taskSecurity.AddAccessRule(new TaskAccessRule("Authenticated Users", TaskRights.Read, System.Security.AccessControl.AccessControlType.Allow));
		/// task.SetAccessControl(taskSecurity);
		///]]>
		/// </code>
		/// </example>
		public void SetAccessControl([NotNull] TaskSecurity taskSecurity) => taskSecurity.Persist(this);

		/// <summary>Sets the security descriptor for the task. Not available to Task Scheduler 1.0.</summary>
		/// <param name="sddlForm">The security descriptor for the task.</param>
		/// <param name="options">Flags that specify how to set the security descriptor.</param>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		public void SetSecurityDescriptorSddlForm([NotNull] string sddlForm, TaskSetSecurityOptions options = TaskSetSecurityOptions.None)
		{
			if (v2Task != null)
				v2Task.SetSecurityDescriptor(sddlForm, (int)options);
			else
				throw new NotV1SupportedException();
		}

		/// <summary>Dynamically tries to load the assembly for the editor and displays it as editable for this task.</summary>
		/// <returns><c>true</c> if editor returns with OK response; <c>false</c> otherwise.</returns>
		/// <remarks>
		/// The Microsoft.Win32.TaskSchedulerEditor.dll assembly must reside in the same directory as the Microsoft.Win32.TaskScheduler.dll
		/// or in the GAC.
		/// </remarks>
		public bool ShowEditor()
		{
			try
			{
				var t = ReflectionHelper.LoadType("Microsoft.Win32.TaskScheduler.TaskEditDialog", "Microsoft.Win32.TaskSchedulerEditor.dll");
				if (t != null)
					return ReflectionHelper.InvokeMethod<int>(t, new object[] { this, true, true }, "ShowDialog") == 1;
			}
			catch { }
			return false;
		}

		/// <summary>Shows the property page for the task (v1.0 only).</summary>
		public void ShowPropertyPage()
		{
			if (v1Task != null)
				v1Task.EditWorkItem(IntPtr.Zero, 0);
			else
				throw new NotV2SupportedException();
		}

		/// <summary>Stops the registered task immediately.</summary>
		/// <remarks>
		/// <para>The <c>Stop</c> method stops all instances of the task.</para>
		/// <para>
		/// System account users can stop a task, users with Administrator group privileges can stop a task, and if a user has rights to
		/// execute and read a task, then the user can stop the task. A user can stop the task instances that are running under the same
		/// credentials as the user account. In all other cases, the user is denied access to stop the task.
		/// </para>
		/// </remarks>
		public void Stop()
		{
			if (v2Task != null)
				v2Task.Stop(0);
			else
				v1Task.Terminate();
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString() => Name;

		int IComparable.CompareTo(object other) => CompareTo(other as Task);

		internal static Task CreateTask(TaskService svc, IRegisteredTask iTask, bool throwError = false)
		{
			var iDef = GetV2Definition(svc, iTask, throwError);
			if (iDef != null || !svc.AllowReadOnlyTasks) return new Task(svc, iTask, iDef);
			iDef = GetV2StrippedDefinition(svc, iTask);
			return new Task(svc, iTask, iDef) { ReadOnly = true };
		}

		internal static int GetOSLibraryMinorVersion() => TaskService.LibraryVersion.Minor;

		[NotNull]
		internal static string GetV1Path(ITask v1Task)
		{
			var iFile = (IPersistFile)v1Task;
			iFile.GetCurFile(out var fileName);
			return fileName ?? string.Empty;
		}

		/// <summary>
		/// Gets the ITaskDefinition for a V2 task and prevents the errors that come when connecting remotely to a higher version of the
		/// Task Scheduler.
		/// </summary>
		/// <param name="svc">The local task service.</param>
		/// <param name="iTask">The task instance.</param>
		/// <param name="throwError">if set to <c>true</c> this method will throw an exception if unable to get the task definition.</param>
		/// <returns>A valid ITaskDefinition that should not throw errors on the local instance.</returns>
		/// <exception cref="System.InvalidOperationException">Unable to get a compatible task definition for this version of the library.</exception>
		internal static ITaskDefinition GetV2Definition(TaskService svc, IRegisteredTask iTask, bool throwError = false)
		{
			var xmlVer = new Version();
			try
			{
				var dd = new DefDoc(iTask.Xml);
				xmlVer = dd.Version;
				if (xmlVer.Minor > osLibMinorVer)
				{
					var newMinor = xmlVer.Minor;
					if (!dd.Contains("Volatile", "false", true) &&
						!dd.Contains("MaintenanceSettings"))
						newMinor = 3;
					if (!dd.Contains("UseUnifiedSchedulingEngine", "false", true) &&
						!dd.Contains("DisallowStartOnRemoteAppSession", "false", true) &&
						!dd.Contains("RequiredPrivileges") &&
						!dd.Contains("ProcessTokenSidType", "Default", true))
						newMinor = 2;
					if (!dd.Contains("DisplayName") &&
						!dd.Contains("GroupId") &&
						!dd.Contains("RunLevel", "LeastPrivilege", true) &&
						!dd.Contains("SecurityDescriptor") &&
						!dd.Contains("Source") &&
						!dd.Contains("URI") &&
						!dd.Contains("AllowStartOnDemand", "true", true) &&
						!dd.Contains("AllowHardTerminate", "true", true) &&
						!dd.Contains("MultipleInstancesPolicy", "IgnoreNew", true) &&
						!dd.Contains("NetworkSettings") &&
						!dd.Contains("StartWhenAvailable", "false", true) &&
						!dd.Contains("SendEmail") &&
						!dd.Contains("ShowMessage") &&
						!dd.Contains("ComHandler") &&
						!dd.Contains("EventTrigger") &&
						!dd.Contains("SessionStateChangeTrigger") &&
						!dd.Contains("RegistrationTrigger") &&
						!dd.Contains("RestartOnFailure") &&
						!dd.Contains("LogonType", "None", true))
						newMinor = 1;

					if (newMinor > osLibMinorVer && throwError)
						throw new InvalidOperationException($"The current version of the native library (1.{osLibMinorVer}) does not support the original or minimum version of the \"{iTask.Name}\" task ({xmlVer}/1.{newMinor}). This is likely due to attempting to read the remote tasks of a newer version of Windows from a down-level client.");

					if (newMinor != xmlVer.Minor)
					{
						dd.Version = new Version(1, newMinor);
						var def = svc.v2TaskService.NewTask(0);
						def.XmlText = dd.Xml;
						return def;
					}
				}
				return iTask.Definition;
			}
			catch (COMException comEx)
			{
				if (throwError)
				{
					if ((uint)comEx.ErrorCode == 0x80041318 && xmlVer.Minor != osLibMinorVer) // Incorrect XML value
						throw new InvalidOperationException($"The current version of the native library (1.{osLibMinorVer}) does not support the version of the \"{iTask.Name}\" task ({xmlVer})");
					throw;
				}
			}
			catch
			{
				if (throwError)
					throw;
			}
			return null;
		}

		internal static ITaskDefinition GetV2StrippedDefinition(TaskService svc, IRegisteredTask iTask)
		{
			try
			{
				var dd = new DefDoc(iTask.Xml);
				var xmlVer = dd.Version;
				if (xmlVer.Minor > osLibMinorVer)
				{
					if (osLibMinorVer < 4)
					{
						dd.RemoveTag("Volatile");
						dd.RemoveTag("MaintenanceSettings");
					}
					if (osLibMinorVer < 3)
					{
						dd.RemoveTag("UseUnifiedSchedulingEngine");
						dd.RemoveTag("DisallowStartOnRemoteAppSession");
						dd.RemoveTag("RequiredPrivileges");
						dd.RemoveTag("ProcessTokenSidType");
					}
					if (osLibMinorVer < 2)
					{
						dd.RemoveTag("DisplayName");
						dd.RemoveTag("GroupId");
						dd.RemoveTag("RunLevel");
						dd.RemoveTag("SecurityDescriptor");
						dd.RemoveTag("Source");
						dd.RemoveTag("URI");
						dd.RemoveTag("AllowStartOnDemand");
						dd.RemoveTag("AllowHardTerminate");
						dd.RemoveTag("MultipleInstancesPolicy");
						dd.RemoveTag("NetworkSettings");
						dd.RemoveTag("StartWhenAvailable");
						dd.RemoveTag("SendEmail");
						dd.RemoveTag("ShowMessage");
						dd.RemoveTag("ComHandler");
						dd.RemoveTag("EventTrigger");
						dd.RemoveTag("SessionStateChangeTrigger");
						dd.RemoveTag("RegistrationTrigger");
						dd.RemoveTag("RestartOnFailure");
						dd.RemoveTag("LogonType");
					}
					dd.RemoveTag("WnfStateChangeTrigger"); // Remove custom triggers that can't be sent to Xml
					dd.Version = new Version(1, osLibMinorVer);
					var def = svc.v2TaskService.NewTask(0);
#if DEBUG
					var path = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
						$"TS_Stripped_Def_{xmlVer.Minor}-{osLibMinorVer}_{iTask.Name}.xml");
					File.WriteAllText(path, dd.Xml, Encoding.Unicode);
#endif
					def.XmlText = dd.Xml;
					return def;
				}
			}
			catch (Exception ex)
			{
				Debug.WriteLine($"Error in GetV2StrippedDefinition: {ex}");
#if DEBUG
				throw;
#endif
			}
			return iTask.Definition;
		}

		internal static TimeSpan StringToTimeSpan(string input)
		{
			if (!string.IsNullOrEmpty(input))
				try { return XmlConvert.ToTimeSpan(input); } catch { }
			return TimeSpan.Zero;
		}

		internal static string TimeSpanToString(TimeSpan span)
		{
			if (span != TimeSpan.Zero)
				try { return XmlConvert.ToString(span); } catch { }
			return null;
		}

		internal void V1Reactivate()
		{
			var iTask = TaskService.GetTask(TaskService.v1TaskScheduler, Name);
			if (iTask != null)
				v1Task = iTask;
		}

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

		private class DefDoc
		{
			private readonly XmlDocument doc;

			public DefDoc(string xml)
			{
				doc = new XmlDocument();
				doc.LoadXml(xml);
			}

			public Version Version
			{
				get
				{
					try
					{
						return new Version(doc["Task"].Attributes["version"].Value);
					}
					catch
					{
						throw new InvalidOperationException("Task definition does not contain a version.");
					}
				}
				set
				{
					var task = doc["Task"];
					if (task != null) task.Attributes["version"].Value = value.ToString(2);
				}
			}

			public string Xml => doc.OuterXml;

			public bool Contains(string tag, string defaultVal = null, bool removeIfFound = false)
			{
				var nl = doc.GetElementsByTagName(tag);
				while (nl.Count > 0)
				{
					var e = nl[0];
					if (e.InnerText != defaultVal || !removeIfFound || e.ParentNode == null)
						return true;
					e.ParentNode?.RemoveChild(e);
					nl = doc.GetElementsByTagName(tag);
				}
				return false;
			}

			public void RemoveTag(string tag)
			{
				var nl = doc.GetElementsByTagName(tag);
				while (nl.Count > 0)
				{
					var e = nl[0];
					e.ParentNode?.RemoveChild(e);
					nl = doc.GetElementsByTagName(tag);
				}
			}
		}
	}

	/// <summary>Contains information about the compatibility of the current configuration with a specified version.</summary>
	[PublicAPI]
	public class TaskCompatibilityEntry
	{
		internal TaskCompatibilityEntry(TaskCompatibility comp, string prop, string reason)
		{
			CompatibilityLevel = comp;
			Property = prop;
			Reason = reason;
		}

		/// <summary>Gets the compatibility level.</summary>
		/// <value>The compatibility level.</value>
		public TaskCompatibility CompatibilityLevel { get; }

		/// <summary>Gets the property name with the incompatibility.</summary>
		/// <value>The property name.</value>
		public string Property { get; }

		/// <summary>Gets the reason for the incompatibility.</summary>
		/// <value>The reason.</value>
		public string Reason { get; }
	}

	/// <summary>Defines all the components of a task, such as the task settings, triggers, actions, and registration information.</summary>
	[XmlRoot("Task", Namespace = tns, IsNullable = false)]
	[XmlSchemaProvider("GetV1SchemaFile")]
	[PublicAPI, Serializable]
	public sealed class TaskDefinition : IDisposable, IXmlSerializable, INotifyPropertyChanged
	{
		internal const string tns = "http://schemas.microsoft.com/windows/2004/02/mit/task";

		internal string v1Name = string.Empty;
		internal ITask v1Task;
		internal ITaskDefinition v2Def;

		private ActionCollection actions;
		private TaskPrincipal principal;
		private TaskRegistrationInfo regInfo;
		private TaskSettings settings;
		private TriggerCollection triggers;

		internal TaskDefinition([NotNull] ITask iTask, string name)
		{
			v1Task = iTask;
			v1Name = name;
		}

		internal TaskDefinition([NotNull] ITaskDefinition iDef) => v2Def = iDef;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>Gets a collection of actions that are performed by the task.</summary>
		[XmlArrayItem(ElementName = "Exec", IsNullable = true, Type = typeof(Action.ExecAction))]
		[XmlArrayItem(ElementName = "ShowMessage", IsNullable = true, Type = typeof(Action.ShowMessageAction))]
		[XmlArrayItem(ElementName = "ComHandler", IsNullable = true, Type = typeof(Action.ComHandlerAction))]
		[XmlArrayItem(ElementName = "SendEmail", IsNullable = true, Type = typeof(Action.EmailAction))]
		[XmlArray]
		[NotNull, ItemNotNull]
		public ActionCollection Actions => actions ??= v2Def != null ? new ActionCollection(v2Def) : new ActionCollection(v1Task);

		/// <summary>
		/// Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by
		/// third-parties who wish to extend the task format.
		/// </summary>
		/// <remarks>
		/// For V1 tasks, this library makes special use of the SetWorkItemData and GetWorkItemData methods and does not expose that data
		/// stream directly. Instead, it uses that data stream to hold a dictionary of properties that are not supported under V1, but can
		/// have values under V2. An example of this is the <see cref="TaskRegistrationInfo.URI"/> value which is stored in the data stream.
		/// <para>
		/// The library does not provide direct access to the V1 work item data. If using V2 properties with a V1 task, programmatic access
		/// to the task using the native API will retrieve unreadable results from GetWorkItemData and will eliminate those property values
		/// if SetWorkItemData is used.
		/// </para>
		/// </remarks>
		[CanBeNull]
		public string Data
		{
			get => v2Def != null ? v2Def.Data : v1Task.GetDataItem(nameof(Data));
			set
			{
				if (v2Def != null)
					v2Def.Data = value;
				else
					v1Task.SetDataItem(nameof(Data), value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets the lowest supported version that supports the settings for this <see cref="TaskDefinition"/>.</summary>
		[XmlIgnore]
		public TaskCompatibility LowestSupportedVersion => GetLowestSupportedVersion();

		/// <summary>Gets a collection of triggers that are used to start a task.</summary>
		[XmlArrayItem(ElementName = "BootTrigger", IsNullable = true, Type = typeof(BootTrigger))]
		[XmlArrayItem(ElementName = "CalendarTrigger", IsNullable = true, Type = typeof(CalendarTrigger))]
		[XmlArrayItem(ElementName = "IdleTrigger", IsNullable = true, Type = typeof(IdleTrigger))]
		[XmlArrayItem(ElementName = "LogonTrigger", IsNullable = true, Type = typeof(LogonTrigger))]
		[XmlArrayItem(ElementName = "TimeTrigger", IsNullable = true, Type = typeof(TimeTrigger))]
		[XmlArray]
		[NotNull, ItemNotNull]
		public TriggerCollection Triggers => triggers ??= v2Def != null ? new TriggerCollection(v2Def) : new TriggerCollection(v1Task);

		/// <summary>Gets or sets the XML-formatted definition of the task.</summary>
		[XmlIgnore]
		public string XmlText
		{
			get => v2Def != null ? v2Def.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this);
			set
			{
				if (v2Def != null)
					v2Def.XmlText = value;
				else
					XmlSerializationHelper.ReadObjectFromXmlText(value, this);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets the principal for the task that provides the security credentials for the task.</summary>
		[NotNull]
		public TaskPrincipal Principal => principal ??= v2Def != null ? new TaskPrincipal(v2Def.Principal, () => XmlText) : new TaskPrincipal(v1Task);

		/// <summary>
		/// Gets a class instance of registration information that is used to describe a task, such as the description of the task, the
		/// author of the task, and the date the task is registered.
		/// </summary>
		public TaskRegistrationInfo RegistrationInfo => regInfo ??= v2Def != null ? new TaskRegistrationInfo(v2Def.RegistrationInfo) : new TaskRegistrationInfo(v1Task);

		/// <summary>Gets the settings that define how the Task Scheduler service performs the task.</summary>
		[NotNull]
		public TaskSettings Settings => settings ??= v2Def != null ? new TaskSettings(v2Def.Settings) : new TaskSettings(v1Task);

		/// <summary>Gets the XML Schema file for V1 tasks.</summary>
		/// <param name="xs">The <see cref="System.Xml.Schema.XmlSchemaSet"/> for V1 tasks.</param>
		/// <returns>An object containing the XML Schema for V1 tasks.</returns>
		public static XmlSchemaComplexType GetV1SchemaFile([NotNull] XmlSchemaSet xs)
		{
			XmlSchema schema;
			using (var xsdFile = Assembly.GetAssembly(typeof(TaskDefinition)).GetManifestResourceStream("Microsoft.Win32.TaskScheduler.V1.TaskSchedulerV1Schema.xsd"))
			{
				var schemaSerializer = new XmlSerializer(typeof(XmlSchema));
				schema = (XmlSchema)schemaSerializer.Deserialize(XmlReader.Create(xsdFile));
				xs.Add(schema);
			}

			// target namespace
			var name = new XmlQualifiedName("taskType", tns);
			var productType = (XmlSchemaComplexType)schema.SchemaTypes[name];

			return productType;
		}

		/// <summary>
		/// Determines whether this <see cref="TaskDefinition"/> can use the Unified Scheduling Engine or if it contains unsupported properties.
		/// </summary>
		/// <param name="throwExceptionWithDetails">
		/// if set to <c>true</c> throws an <see cref="InvalidOperationException"/> with details about unsupported properties in the Data
		/// property of the exception.
		/// </param>
		/// <param name="taskSchedulerVersion"></param>
		/// <returns><c>true</c> if this <see cref="TaskDefinition"/> can use the Unified Scheduling Engine; otherwise, <c>false</c>.</returns>
		public bool CanUseUnifiedSchedulingEngine(bool throwExceptionWithDetails = false, Version taskSchedulerVersion = null)
		{
			var tsVer = taskSchedulerVersion ?? TaskService.LibraryVersion;
			if (tsVer < TaskServiceVersion.V1_3) return false;
			var ex = new InvalidOperationException { HelpLink = "http://msdn.microsoft.com/en-us/library/windows/desktop/aa384138(v=vs.85).aspx" };
			var bad = false;
			/*if (Principal.LogonType == TaskLogonType.InteractiveTokenOrPassword)
			{
				bad = true;
				if (!throwExceptionWithDetails) return false;
				TryAdd(ex.Data, "Principal.LogonType", "== TaskLogonType.InteractiveTokenOrPassword");
			}
			if (Settings.MultipleInstances == TaskInstancesPolicy.StopExisting)
			{
				bad = true;
				if (!throwExceptionWithDetails) return false;
				TryAdd(ex.Data, "Settings.MultipleInstances", "== TaskInstancesPolicy.StopExisting");
			}*/
			if (Settings.NetworkSettings.Id != Guid.Empty && tsVer >= TaskServiceVersion.V1_5)
			{
				bad = true;
				if (!throwExceptionWithDetails) return false;
				TryAdd(ex.Data, "Settings.NetworkSettings.Id", "!= Guid.Empty");
			}
			/*if (!Settings.AllowHardTerminate)
			{
				bad = true;
				if (!throwExceptionWithDetails) return false;
				TryAdd(ex.Data, "Settings.AllowHardTerminate", "== false");
			}*/
			if (!Actions.PowerShellConversion.IsFlagSet(PowerShellActionPlatformOption.Version2))
				for (var i = 0; i < Actions.Count; i++)
				{
					var a = Actions[i];
					switch (a)
					{
						case Action.EmailAction _:
							bad = true;
							if (!throwExceptionWithDetails) return false;
							TryAdd(ex.Data, $"Actions[{i}]", "== typeof(EmailAction)");
							break;

						case Action.ShowMessageAction _:
							bad = true;
							if (!throwExceptionWithDetails) return false;
							TryAdd(ex.Data, $"Actions[{i}]", "== typeof(ShowMessageAction)");
							break;
					}
				}
			if (tsVer == TaskServiceVersion.V1_3)
				for (var i = 0; i < Triggers.Count; i++)
				{
					Trigger t;
					try { t = Triggers[i]; }
					catch
					{
						if (!throwExceptionWithDetails) return false;
						TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable.");
						continue;
					}
					switch (t)
					{
						case MonthlyTrigger _:
							bad = true;
							if (!throwExceptionWithDetails) return false;
							TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyTrigger)");
							break;

						case MonthlyDOWTrigger _:
							bad = true;
							if (!throwExceptionWithDetails) return false;
							TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyDOWTrigger)");
							break;
							/*case ICalendarTrigger _ when t.Repetition.IsSet():
								bad = true;
								if (!throwExceptionWithDetails) return false;
								TryAdd(ex.Data, $"Triggers[{i}].Repetition", "");
								break;

							case EventTrigger _ when ((EventTrigger)t).ValueQueries.Count > 0:
								bad = true;
								if (!throwExceptionWithDetails) return false;
								TryAdd(ex.Data, $"Triggers[{i}].ValueQueries.Count", "!= 0");
								break;*/
					}
					if (t.ExecutionTimeLimit != TimeSpan.Zero)
					{
						bad = true;
						if (!throwExceptionWithDetails) return false;
						TryAdd(ex.Data, $"Triggers[{i}].ExecutionTimeLimit", "!= TimeSpan.Zero");
					}
				}
			if (bad && throwExceptionWithDetails)
				throw ex;
			return !bad;
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			regInfo = null;
			triggers = null;
			settings = null;
			principal = null;
			actions = null;
			if (v2Def != null) Marshal.ReleaseComObject(v2Def);
			v1Task = null;
		}

		/// <summary>Validates the current <see cref="TaskDefinition"/>.</summary>
		/// <param name="throwException">
		/// if set to <c>true</c> throw a <see cref="InvalidOperationException"/> with details about invalid properties.
		/// </param>
		/// <returns><c>true</c> if current <see cref="TaskDefinition"/> is valid; <c>false</c> if not.</returns>
		public bool Validate(bool throwException = false)
		{
			var ex = new InvalidOperationException();
			if (Settings.UseUnifiedSchedulingEngine)
			{
				try { CanUseUnifiedSchedulingEngine(throwException); }
				catch (InvalidOperationException iox)
				{
					foreach (DictionaryEntry kvp in iox.Data)
						TryAdd(ex.Data, (kvp.Key as ICloneable)?.Clone() ?? kvp.Key, (kvp.Value as ICloneable)?.Clone() ?? kvp.Value);
				}
			}

			if (Settings.Compatibility >= TaskCompatibility.V2_2)
			{
				var PT1D = TimeSpan.FromDays(1);
				if (Settings.MaintenanceSettings.IsSet() && (Settings.MaintenanceSettings.Period < PT1D || Settings.MaintenanceSettings.Deadline < PT1D || Settings.MaintenanceSettings.Deadline <= Settings.MaintenanceSettings.Period))
					TryAdd(ex.Data, "Settings.MaintenanceSettings", "Period or Deadline must be at least 1 day and Deadline must be greater than Period.");
			}

			var list = new List<TaskCompatibilityEntry>();
			if (GetLowestSupportedVersion(list) > Settings.Compatibility)
				foreach (var item in list)
					TryAdd(ex.Data, item.Property, item.Reason);

			var startWhenAvailable = Settings.StartWhenAvailable;
			var delOldTask = Settings.DeleteExpiredTaskAfter != TimeSpan.Zero;
			var v1 = Settings.Compatibility < TaskCompatibility.V2;
			var hasEndBound = false;
			for (var i = 0; i < Triggers.Count; i++)
			{
				Trigger trigger;
				try { trigger = Triggers[i]; }
				catch
				{
					TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable.");
					continue;
				}
				if (startWhenAvailable && trigger.Repetition.Duration != TimeSpan.Zero && trigger.EndBoundary == DateTime.MaxValue)
					TryAdd(ex.Data, "Settings.StartWhenAvailable", "== true requires time-based tasks with an end boundary or time-based tasks that are set to repeat infinitely.");
				if (v1 && trigger.Repetition.Interval != TimeSpan.Zero && trigger.Repetition.Interval >= trigger.Repetition.Duration)
					TryAdd(ex.Data, "Trigger.Repetition.Interval", ">= Trigger.Repetition.Duration under Task Scheduler 1.0.");
				if (trigger.EndBoundary <= trigger.StartBoundary)
					TryAdd(ex.Data, "Trigger.StartBoundary", ">= Trigger.EndBoundary is not allowed.");
				if (delOldTask && trigger.EndBoundary != DateTime.MaxValue)
					hasEndBound = true;
			}
			if (delOldTask && !hasEndBound)
				TryAdd(ex.Data, "Settings.DeleteExpiredTaskAfter", "!= TimeSpan.Zero requires at least one trigger with an end boundary.");

			if (throwException && ex.Data.Count > 0)
				throw ex;
			return ex.Data.Count == 0;
		}

		XmlSchema IXmlSerializable.GetSchema() => null;

		void IXmlSerializable.ReadXml(XmlReader reader)
		{
			reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), tns);
			XmlSerializationHelper.ReadObjectProperties(reader, this);
			reader.ReadEndElement();
		}

		void IXmlSerializable.WriteXml(XmlWriter writer) =>
			// TODO:FIX writer.WriteAttributeString("version", "1.1");
			XmlSerializationHelper.WriteObjectProperties(writer, this);

		internal static Dictionary<string, string> GetV1TaskDataDictionary(ITask v1Task)
		{
			Dictionary<string, string> dict;
			var o = GetV1TaskData(v1Task);
			if (o is string)
				dict = new Dictionary<string, string>(2) { { "Data", o.ToString() }, { "Documentation", o.ToString() } };
			else
				dict = o as Dictionary<string, string>;
			return dict ?? new Dictionary<string, string>();
		}

		internal static void SetV1TaskData(ITask v1Task, object value)
		{
			if (value == null)
				v1Task.SetWorkItemData(0, null);
			else
			{
				var b = new BinaryFormatter();
				var stream = new MemoryStream();
				b.Serialize(stream, value);
				v1Task.SetWorkItemData((ushort)stream.Length, stream.ToArray());
			}
		}

		internal void V1Save(string newName)
		{
			if (v1Task != null)
			{
				Triggers.Bind();

				var iFile = (IPersistFile)v1Task;
				if (string.IsNullOrEmpty(newName) || newName == v1Name)
				{
					try
					{
						iFile.Save(null, false);
						iFile = null;
						return;
					}
					catch { }
				}

				iFile.GetCurFile(out var path);
				File.Delete(path);
				path = Path.GetDirectoryName(path) + Path.DirectorySeparatorChar + newName + Path.GetExtension(path);
				File.Delete(path);
				iFile.Save(path, true);
			}
		}

		private static object GetV1TaskData(ITask v1Task)
		{
			var Data = IntPtr.Zero;
			try
			{
				v1Task.GetWorkItemData(out var DataLen, out Data);
				if (DataLen == 0)
					return null;
				var bytes = new byte[DataLen];
				Marshal.Copy(Data, bytes, 0, DataLen);
				var stream = new MemoryStream(bytes, false);
				var b = new BinaryFormatter();
				return b.Deserialize(stream);
			}
			catch { }
			finally
			{
				if (Data != IntPtr.Zero)
					Marshal.FreeCoTaskMem(Data);
			}
			return null;
		}

		private static void TryAdd(IDictionary d, object k, object v)
		{
			if (!d.Contains(k))
				d.Add(k, v);
		}

		/// <summary>Gets the lowest supported version.</summary>
		/// <param name="outputList">The output list.</param>
		/// <returns></returns>
		private TaskCompatibility GetLowestSupportedVersion(IList<TaskCompatibilityEntry> outputList = null)
		{
			var res = TaskCompatibility.V1;
			var list = new List<TaskCompatibilityEntry>();

			//if (Principal.DisplayName != null)
			//	{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.DisplayName", "cannot have a value.")); }
			if (Principal.GroupId != null)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.GroupId", "cannot have a value.")); }
			//this.Principal.Id != null ||
			if (Principal.LogonType == TaskLogonType.Group || Principal.LogonType == TaskLogonType.None || Principal.LogonType == TaskLogonType.S4U)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.LogonType", "cannot be Group, None or S4U.")); }
			if (Principal.RunLevel == TaskRunLevel.Highest)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.RunLevel", "cannot be set to Highest.")); }
			if (RegistrationInfo.SecurityDescriptorSddlForm != null)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.SecurityDescriptorSddlForm", "cannot have a value.")); }
			//if (RegistrationInfo.Source != null)
			//	{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Source", "cannot have a value.")); }
			//if (RegistrationInfo.URI != null)
			//	{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.URI", "cannot have a value.")); }
			//if (RegistrationInfo.Version != new Version(1, 0))
			//	{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Version", "cannot be set or equal 1.0.")); }
			if (Settings.AllowDemandStart == false)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowDemandStart", "must be true.")); }
			if (Settings.AllowHardTerminate == false)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowHardTerminate", "must be true.")); }
			if (Settings.MultipleInstances != TaskInstancesPolicy.IgnoreNew)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.MultipleInstances", "must be set to IgnoreNew.")); }
			if (Settings.NetworkSettings.IsSet())
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.NetworkSetting", "cannot have a value.")); }
			if (Settings.RestartCount != 0)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartCount", "must be 0.")); }
			if (Settings.RestartInterval != TimeSpan.Zero)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartInterval", "must be 0 (TimeSpan.Zero).")); }
			if (Settings.StartWhenAvailable)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.StartWhenAvailable", "must be false.")); }

			if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version1) != PowerShellActionPlatformOption.Version1 && (Actions.ContainsType(typeof(Action.EmailAction)) || Actions.ContainsType(typeof(Action.ShowMessageAction)) || Actions.ContainsType(typeof(Action.ComHandlerAction))))
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Actions", "may only contain ExecAction types unless Actions.PowerShellConversion includes Version1.")); }
			if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version2) != PowerShellActionPlatformOption.Version2 && (Actions.ContainsType(typeof(Action.EmailAction)) || Actions.ContainsType(typeof(Action.ShowMessageAction))))
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Actions", "may only contain ExecAction and ComHanlderAction types unless Actions.PowerShellConversion includes Version2.")); }

			try
			{
				if (null != Triggers.Find(t => t is ITriggerDelay && ((ITriggerDelay)t).Delay != TimeSpan.Zero))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain delays.")); }
				if (null != Triggers.Find(t => t.ExecutionTimeLimit != TimeSpan.Zero || t.Id != null))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain an ExecutionTimeLimit or Id.")); }
				if (null != Triggers.Find(t => (t as LogonTrigger)?.UserId != null))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a LogonTrigger with a UserId.")); }
				if (null != Triggers.Find(t => t is MonthlyDOWTrigger && ((MonthlyDOWTrigger)t).RunOnLastWeekOfMonth || t is MonthlyDOWTrigger && (((MonthlyDOWTrigger)t).WeeksOfMonth & (((MonthlyDOWTrigger)t).WeeksOfMonth - 1)) != 0))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyDOWTrigger with RunOnLastWeekOfMonth set or multiple WeeksOfMonth.")); }
				if (null != Triggers.Find(t => t is MonthlyTrigger && ((MonthlyTrigger)t).RunOnLastDayOfMonth))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyTrigger with RunOnLastDayOfMonth set.")); }
				if (Triggers.ContainsType(typeof(EventTrigger)) || Triggers.ContainsType(typeof(SessionStateChangeTrigger)) || Triggers.ContainsType(typeof(RegistrationTrigger)))
				{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger types.")); }
			}
			catch
			{
				list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain Custom triggers."));
			}

			if (Principal.ProcessTokenSidType != TaskProcessTokenSidType.Default)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.ProcessTokenSidType", "must be Default.")); }
			if (Principal.RequiredPrivileges.Count > 0)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.RequiredPrivileges", "must be empty.")); }
			if (Settings.DisallowStartOnRemoteAppSession)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.DisallowStartOnRemoteAppSession", "must be false.")); }
			if (Settings.UseUnifiedSchedulingEngine)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.UseUnifiedSchedulingEngine", "must be false.")); }

			if (Settings.MaintenanceSettings.IsSet())
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, "this.Settings.MaintenanceSettings", "must have no values set.")); }
			if (Settings.Volatile)
			{ list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, " this.Settings.Volatile", "must be false.")); }

			foreach (var item in list)
			{
				if (res < item.CompatibilityLevel) res = item.CompatibilityLevel;
				outputList?.Add(item);
			}
			return res;
		}

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	/// <summary>
	/// Provides the security credentials for a principal. These security credentials define the security context for the tasks that are
	/// associated with the principal.
	/// </summary>
	[XmlRoot("Principals", Namespace = TaskDefinition.tns, IsNullable = true)]
	[PublicAPI]
	public sealed class TaskPrincipal : IDisposable, IXmlSerializable, INotifyPropertyChanged
	{
		private const string localSystemAcct = "SYSTEM";
		private readonly IPrincipal v2Principal;
		private readonly IPrincipal2 v2Principal2;
		private readonly Func<string> xmlFunc;
		private TaskPrincipalPrivileges reqPriv;
		private string v1CachedAcctInfo;
		private ITask v1Task;

		internal TaskPrincipal([NotNull] IPrincipal iPrincipal, Func<string> defXml)
		{
			xmlFunc = defXml;
			v2Principal = iPrincipal;
			try { if (Environment.OSVersion.Version >= new Version(6, 1)) v2Principal2 = (IPrincipal2)v2Principal; }
			catch { }
		}

		internal TaskPrincipal([NotNull] ITask iTask) => v1Task = iTask;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Gets the account associated with this principal. This value is pulled from the TaskDefinition's XMLText property if set.
		/// </summary>
		/// <value>The account.</value>
		[DefaultValue(null), Browsable(false)]
		public string Account
		{
			get
			{
				try
				{
					var xml = xmlFunc?.Invoke();
					if (!string.IsNullOrEmpty(xml))
					{
						var doc = new XmlDocument();
						doc.LoadXml(xml);
						var pn = doc.DocumentElement?["Principals"]?["Principal"];
						if (pn != null)
						{
							var un = pn["UserId"] ?? pn["GroupId"];
							if (un != null)
								try { return User.FromSidString(un.InnerText).Name; }
								catch
								{
									try { return new User(un.InnerText).Name; }
									catch { }
								}
						}
					}
					return new User(ToString()).Name;
				}
				catch
				{
					return null;
				}
			}
		}

		/// <summary>Gets or sets the name of the principal that is displayed in the Task Scheduler UI.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(null)]
		public string DisplayName
		{
			get => v2Principal != null ? v2Principal.DisplayName : v1Task.GetDataItem("PrincipalDisplayName");
			set
			{
				if (v2Principal != null)
					v2Principal.DisplayName = value;
				else
					v1Task.SetDataItem("PrincipalDisplayName", value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the identifier of the user group that is required to run the tasks that are associated with the principal. Setting
		/// this property to something other than a null or empty string, will set the <see cref="UserId"/> property to NULL and will set
		/// the <see cref="LogonType"/> property to TaskLogonType.Group;
		/// </summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(null)]
		[XmlIgnore]
		public string GroupId
		{
			get => v2Principal?.GroupId;
			set
			{
				if (v2Principal != null)
				{
					if (string.IsNullOrEmpty(value))
						value = null;
					else
					{
						v2Principal.UserId = null;
						v2Principal.LogonType = TaskLogonType.Group;
					}
					v2Principal.GroupId = value;
				}
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the identifier of the principal.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(null)]
		[XmlAttribute(AttributeName = "id", DataType = "ID")]
		public string Id
		{
			get => v2Principal != null ? v2Principal.Id : v1Task.GetDataItem("PrincipalId");
			set
			{
				if (v2Principal != null)
					v2Principal.Id = value;
				else
					v1Task.SetDataItem("PrincipalId", value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the security logon method that is required to run the tasks that are associated with the principal.</summary>
		/// <exception cref="NotV1SupportedException">
		/// TaskLogonType values of Group, None, or S4UNot are not supported under Task Scheduler 1.0.
		/// </exception>
		[DefaultValue(typeof(TaskLogonType), "None")]
		public TaskLogonType LogonType
		{
			get
			{
				if (v2Principal != null)
					return v2Principal.LogonType;
				if (UserId == localSystemAcct)
					return TaskLogonType.ServiceAccount;
				if (v1Task.HasFlags(TaskFlags.RunOnlyIfLoggedOn))
					return TaskLogonType.InteractiveToken;
				return TaskLogonType.InteractiveTokenOrPassword;
			}
			set
			{
				if (v2Principal != null)
					v2Principal.LogonType = value;
				else
				{
					if (value == TaskLogonType.Group || value == TaskLogonType.None || value == TaskLogonType.S4U)
						throw new NotV1SupportedException();
					v1Task.SetFlags(TaskFlags.RunOnlyIfLoggedOn, value == TaskLogonType.InteractiveToken);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the task process security identifier (SID) type.</summary>
		/// <value>One of the <see cref="TaskProcessTokenSidType"/> enumeration constants.</value>
		/// <remarks>Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems.</remarks>
		/// <exception cref="NotSupportedPriorToException">Not supported under Task Scheduler versions prior to 2.1.</exception>
		[XmlIgnore, DefaultValue(typeof(TaskProcessTokenSidType), "Default")]
		public TaskProcessTokenSidType ProcessTokenSidType
		{
			get => v2Principal2?.ProcessTokenSidType ?? TaskProcessTokenSidType.Default;
			set
			{
				if (v2Principal2 != null)
					v2Principal2.ProcessTokenSidType = value;
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_1);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets the security credentials for a principal. These security credentials define the security context for the tasks that are
		/// associated with the principal.
		/// </summary>
		/// <remarks>Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems.</remarks>
		[XmlIgnore]
		public TaskPrincipalPrivileges RequiredPrivileges => reqPriv ??= new TaskPrincipalPrivileges(v2Principal2);

		/// <summary>
		/// Gets or sets the identifier that is used to specify the privilege level that is required to run the tasks that are associated
		/// with the principal.
		/// </summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(typeof(TaskRunLevel), "LUA")]
		[XmlIgnore]
		public TaskRunLevel RunLevel
		{
			get => v2Principal?.RunLevel ?? TaskRunLevel.LUA;
			set
			{
				if (v2Principal != null)
					v2Principal.RunLevel = value;
				else if (value != TaskRunLevel.LUA)
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the user identifier that is required to run the tasks that are associated with the principal. Setting this property
		/// to something other than a null or empty string, will set the <see cref="GroupId"/> property to NULL;
		/// </summary>
		[DefaultValue(null)]
		public string UserId
		{
			get
			{
				if (v2Principal != null)
					return v2Principal.UserId;
				if (v1CachedAcctInfo == null)
				{
					try
					{
						string acct = v1Task.GetAccountInformation();
						v1CachedAcctInfo = string.IsNullOrEmpty(acct) ? localSystemAcct : acct;
					}
					catch { v1CachedAcctInfo = string.Empty; }
				}
				return v1CachedAcctInfo == string.Empty ? null : v1CachedAcctInfo;
			}
			set
			{
				if (v2Principal != null)
				{
					if (string.IsNullOrEmpty(value))
						value = null;
					else
					{
						v2Principal.GroupId = null;
						//if (value.Contains(@"\") && !value.Contains(@"\\"))
						//	value = value.Replace(@"\", @"\\");
					}
					v2Principal.UserId = value;
				}
				else
				{
					if (value.Equals(localSystemAcct, StringComparison.CurrentCultureIgnoreCase))
						value = "";
					v1Task.SetAccountInformation(value, IntPtr.Zero);
					v1CachedAcctInfo = null;
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Validates the supplied account against the supplied <see cref="TaskProcessTokenSidType"/>.</summary>
		/// <param name="acct">The user or group account name.</param>
		/// <param name="sidType">The SID type for the process.</param>
		/// <returns><c>true</c> if supplied account can be used for the supplied SID type.</returns>
		public static bool ValidateAccountForSidType(string acct, TaskProcessTokenSidType sidType)
		{
			string[] validUserIds = { "NETWORK SERVICE", "LOCAL SERVICE", "S-1-5-19", "S-1-5-20" };
			return sidType == TaskProcessTokenSidType.Default || Array.Find(validUserIds, id => id.Equals(acct, StringComparison.InvariantCultureIgnoreCase)) != null;
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (v2Principal != null)
				Marshal.ReleaseComObject(v2Principal);
			v1Task = null;
		}

		/// <summary>Gets a value indicating whether current Principal settings require a password to be provided.</summary>
		/// <value><c>true</c> if settings requires a password to be provided; otherwise, <c>false</c>.</value>
		public bool RequiresPassword() => LogonType == TaskLogonType.InteractiveTokenOrPassword ||
			LogonType == TaskLogonType.Password || LogonType == TaskLogonType.S4U && UserId != null && string.Compare(UserId, WindowsIdentity.GetCurrent().Name, StringComparison.OrdinalIgnoreCase) != 0;

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString() => LogonType == TaskLogonType.Group ? GroupId : UserId;

		XmlSchema IXmlSerializable.GetSchema() => null;

		void IXmlSerializable.ReadXml(XmlReader reader)
		{
			reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns);
			if (reader.HasAttributes)
				Id = reader.GetAttribute("id");
			reader.Read();
			while (reader.MoveToContent() == XmlNodeType.Element)
			{
				switch (reader.LocalName)
				{
					case "Principal":
						reader.Read();
						XmlSerializationHelper.ReadObjectProperties(reader, this);
						reader.ReadEndElement();
						break;

					default:
						reader.Skip();
						break;
				}
			}
			reader.ReadEndElement();
		}

		void IXmlSerializable.WriteXml(XmlWriter writer)
		{
			if (string.IsNullOrEmpty(ToString()) && LogonType == TaskLogonType.None) return;
			writer.WriteStartElement("Principal");
			if (!string.IsNullOrEmpty(Id))
				writer.WriteAttributeString("id", Id);
			XmlSerializationHelper.WriteObjectProperties(writer, this);
			writer.WriteEndElement();
		}

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	/// <summary>
	/// List of security credentials for a principal under version 1.3 of the Task Scheduler. These security credentials define the security
	/// context for the tasks that are associated with the principal.
	/// </summary>
	[PublicAPI]
	public sealed class TaskPrincipalPrivileges : IList<TaskPrincipalPrivilege>
	{
		private readonly IPrincipal2 v2Principal2;

		internal TaskPrincipalPrivileges(IPrincipal2 iPrincipal2 = null) => v2Principal2 = iPrincipal2;

		/// <summary>Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
		/// <returns>The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</returns>
		public int Count => v2Principal2?.RequiredPrivilegeCount ?? 0;

		/// <summary>Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</summary>
		/// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false.</returns>
		public bool IsReadOnly => false;

		/// <summary>Gets or sets the element at the specified index.</summary>
		/// <returns>The element at the specified index.</returns>
		/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
		/// <exception cref="T:System.NotSupportedException">
		/// The property is set and the <see cref="T:System.Collections.Generic.IList`1"/> is read-only.
		/// </exception>
		public TaskPrincipalPrivilege this[int index]
		{
			get
			{
				if (v2Principal2 != null)
					return (TaskPrincipalPrivilege)Enum.Parse(typeof(TaskPrincipalPrivilege), v2Principal2[index + 1]);
				throw new IndexOutOfRangeException();
			}
			set => throw new NotImplementedException();
		}

		/// <summary>Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
		/// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
		public void Add(TaskPrincipalPrivilege item)
		{
			if (v2Principal2 != null)
				v2Principal2.AddRequiredPrivilege(item.ToString());
			else
				throw new NotSupportedPriorToException(TaskCompatibility.V2_1);
		}

		/// <summary>Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value.</summary>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
		/// <returns>
		/// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false.
		/// </returns>
		public bool Contains(TaskPrincipalPrivilege item) => IndexOf(item) != -1;

		/// <summary>Copies to.</summary>
		/// <param name="array">The array.</param>
		/// <param name="arrayIndex">Index of the array.</param>
		public void CopyTo(TaskPrincipalPrivilege[] array, int arrayIndex)
		{
			using var pEnum = GetEnumerator();
			for (var i = arrayIndex; i < array.Length; i++)
			{
				if (!pEnum.MoveNext())
					break;
				array[i] = pEnum.Current;
			}
		}

		/// <summary>Returns an enumerator that iterates through the collection.</summary>
		/// <returns>A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.</returns>
		public IEnumerator<TaskPrincipalPrivilege> GetEnumerator() => new TaskPrincipalPrivilegesEnumerator(v2Principal2);

		/// <summary>Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>.</summary>
		/// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
		/// <returns>The index of <paramref name="item"/> if found in the list; otherwise, -1.</returns>
		public int IndexOf(TaskPrincipalPrivilege item)
		{
			for (var i = 0; i < Count; i++)
			{
				if (item == this[i])
					return i;
			}
			return -1;
		}

		/// <summary>Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
		void ICollection<TaskPrincipalPrivilege>.Clear() => throw new NotImplementedException();

		IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();

		/// <summary>Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index.</summary>
		/// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param>
		/// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param>
		/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
		void IList<TaskPrincipalPrivilege>.Insert(int index, TaskPrincipalPrivilege item) => throw new NotImplementedException();

		/// <summary>Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</summary>
		/// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param>
		/// <returns>
		/// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>;
		/// otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>.
		/// </returns>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only.</exception>
		bool ICollection<TaskPrincipalPrivilege>.Remove(TaskPrincipalPrivilege item) => throw new NotImplementedException();

		/// <summary>Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index.</summary>
		/// <param name="index">The zero-based index of the item to remove.</param>
		/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>.</exception>
		/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Generic.IList`1"/> is read-only.</exception>
		void IList<TaskPrincipalPrivilege>.RemoveAt(int index) => throw new NotImplementedException();

		/// <summary>Enumerates the privileges set for a principal under version 1.3 of the Task Scheduler.</summary>
		public sealed class TaskPrincipalPrivilegesEnumerator : IEnumerator<TaskPrincipalPrivilege>
		{
			private readonly IPrincipal2 v2Principal2;
			private int cur;

			internal TaskPrincipalPrivilegesEnumerator(IPrincipal2 iPrincipal2 = null)
			{
				v2Principal2 = iPrincipal2;
				Reset();
			}

			/// <summary>Gets the element in the collection at the current position of the enumerator.</summary>
			/// <returns>The element in the collection at the current position of the enumerator.</returns>
			public TaskPrincipalPrivilege Current { get; private set; }

			object IEnumerator.Current => Current;

			/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
			public void Dispose() { }

			/// <summary>Advances the enumerator to the next element of the collection.</summary>
			/// <returns>
			/// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection.
			/// </returns>
			/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
			public bool MoveNext()
			{
				if (v2Principal2 != null && cur < v2Principal2.RequiredPrivilegeCount)
				{
					cur++;
					Current = (TaskPrincipalPrivilege)Enum.Parse(typeof(TaskPrincipalPrivilege), v2Principal2[cur]);
					return true;
				}
				Current = 0;
				return false;
			}

			/// <summary>Sets the enumerator to its initial position, which is before the first element in the collection.</summary>
			/// <exception cref="T:System.InvalidOperationException">The collection was modified after the enumerator was created.</exception>
			public void Reset()
			{
				cur = 0;
				Current = 0;
			}
		}
	}

	/// <summary>
	/// Provides the administrative information that can be used to describe the task. This information includes details such as a
	/// description of the task, the author of the task, the date the task is registered, and the security descriptor of the task.
	/// </summary>
	[XmlRoot("RegistrationInfo", Namespace = TaskDefinition.tns, IsNullable = true)]
	[PublicAPI]
	public sealed class TaskRegistrationInfo : IDisposable, IXmlSerializable, INotifyPropertyChanged
	{
		private readonly IRegistrationInfo v2RegInfo;
		private ITask v1Task;

		internal TaskRegistrationInfo([NotNull] IRegistrationInfo iRegInfo) => v2RegInfo = iRegInfo;

		internal TaskRegistrationInfo([NotNull] ITask iTask) => v1Task = iTask;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>Gets or sets the author of the task.</summary>
		[DefaultValue(null)]
		public string Author
		{
			get => v2RegInfo != null ? v2RegInfo.Author : v1Task.GetCreator();
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Author = value;
				else
					v1Task.SetCreator(value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the date and time when the task is registered.</summary>
		[DefaultValue(typeof(DateTime), "0001-01-01T00:00:00")]
		public DateTime Date
		{
			get
			{
				if (v2RegInfo != null)
				{
					if (DateTime.TryParse(v2RegInfo.Date, Trigger.DefaultDateCulture, DateTimeStyles.AssumeLocal, out var ret))
						return ret;
				}
				else
				{
					var v1Path = Task.GetV1Path(v1Task);
					if (!string.IsNullOrEmpty(v1Path) && File.Exists(v1Path))
						return File.GetLastWriteTime(v1Path);
				}
				return DateTime.MinValue;
			}
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Date = value == DateTime.MinValue ? null : value.ToString(Trigger.V2BoundaryDateFormat, Trigger.DefaultDateCulture);
				else
				{
					var v1Path = Task.GetV1Path(v1Task);
					if (!string.IsNullOrEmpty(v1Path) && File.Exists(v1Path))
						File.SetLastWriteTime(v1Path, value);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the description of the task.</summary>
		[DefaultValue(null)]
		public string Description
		{
			get => v2RegInfo != null ? FixCrLf(v2RegInfo.Description) : v1Task.GetComment();
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Description = value;
				else
					v1Task.SetComment(value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets any additional documentation for the task.</summary>
		[DefaultValue(null)]
		public string Documentation
		{
			get => v2RegInfo != null ? FixCrLf(v2RegInfo.Documentation) : v1Task.GetDataItem(nameof(Documentation));
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Documentation = value;
				else
					v1Task.SetDataItem(nameof(Documentation), value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the security descriptor of the task.</summary>
		/// <value>The security descriptor.</value>
		[XmlIgnore]
		public GenericSecurityDescriptor SecurityDescriptor
		{
			get => new RawSecurityDescriptor(SecurityDescriptorSddlForm);
			set => SecurityDescriptorSddlForm = value?.GetSddlForm(Task.defaultAccessControlSections);
		}

		/// <summary>Gets or sets the security descriptor of the task.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(null)]
		[XmlIgnore]
		public string SecurityDescriptorSddlForm
		{
			get
			{
				object sddl = null;
				if (v2RegInfo != null)
					sddl = v2RegInfo.SecurityDescriptor;
				return sddl?.ToString();
			}
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.SecurityDescriptor = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets where the task originated from. For example, a task may originate from a component, service, application, or user.
		/// </summary>
		[DefaultValue(null)]
		public string Source
		{
			get => v2RegInfo != null ? v2RegInfo.Source : v1Task.GetDataItem(nameof(Source));
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Source = value;
				else
					v1Task.SetDataItem(nameof(Source), value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the URI of the task.</summary>
		/// <remarks>
		/// <c>Note:</c> Breaking change in version 2.0. This property was previously of type <see cref="Uri"/>. It was found that in
		/// Windows 8, many of the native tasks use this property in a string format rather than in a URI format.
		/// </remarks>
		[DefaultValue(null)]
		public string URI
		{
			get
			{
				var uri = v2RegInfo != null ? v2RegInfo.URI : v1Task.GetDataItem(nameof(URI));
				return string.IsNullOrEmpty(uri) ? null : uri;
			}
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.URI = value;
				else
					v1Task.SetDataItem(nameof(URI), value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the version number of the task.</summary>
		[DefaultValueEx(typeof(Version), "1.0")]
		public Version Version
		{
			get
			{
				var sver = v2RegInfo != null ? v2RegInfo.Version : v1Task.GetDataItem(nameof(Version));
				if (sver != null) try { return new Version(sver); } catch { }
				return new Version(1, 0);
			}
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.Version = value?.ToString();
				else
					v1Task.SetDataItem(nameof(Version), value.ToString());
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets an XML-formatted version of the registration information for the task.</summary>
		[XmlIgnore]
		public string XmlText
		{
			get => v2RegInfo != null ? v2RegInfo.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this);
			set
			{
				if (v2RegInfo != null)
					v2RegInfo.XmlText = value;
				else
					XmlSerializationHelper.ReadObjectFromXmlText(value, this);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			v1Task = null;
			if (v2RegInfo != null)
				Marshal.ReleaseComObject(v2RegInfo);
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString()
		{
			if (v2RegInfo != null || v1Task != null)
				return DebugHelper.GetDebugString(this);
			return base.ToString();
		}

		XmlSchema IXmlSerializable.GetSchema() => null;

		void IXmlSerializable.ReadXml(XmlReader reader)
		{
			if (!reader.IsEmptyElement)
			{
				reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns);
				XmlSerializationHelper.ReadObjectProperties(reader, this, ProcessVersionXml);
				reader.ReadEndElement();
			}
			else
				reader.Skip();
		}

		void IXmlSerializable.WriteXml(XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this, ProcessVersionXml);

		internal static string FixCrLf(string text) => text == null ? null : Regex.Replace(text, "(?<!\r)\n|\r(?!\n)", "\r\n");

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

		private bool ProcessVersionXml(PropertyInfo pi, object obj, ref object value)
		{
			if (pi.Name != "Version" || value == null) return false;
			if (value is Version)
				value = value.ToString();
			else if (value is string)
				value = new Version(value.ToString());
			return true;
		}
	}

	/// <summary>Provides the settings that the Task Scheduler service uses to perform the task.</summary>
	[XmlRoot("Settings", Namespace = TaskDefinition.tns, IsNullable = true)]
	[PublicAPI]
	public sealed class TaskSettings : IDisposable, IXmlSerializable, INotifyPropertyChanged
	{
		private const uint InfiniteRunTimeV1 = 0xFFFFFFFF;

		private readonly ITaskSettings v2Settings;
		private readonly ITaskSettings2 v2Settings2;
		private readonly ITaskSettings3 v2Settings3;
		private IdleSettings idleSettings;
		private MaintenanceSettings maintenanceSettings;
		private NetworkSettings networkSettings;
		private ITask v1Task;

		internal TaskSettings([NotNull] ITaskSettings iSettings)
		{
			v2Settings = iSettings;
			try { if (Environment.OSVersion.Version >= new Version(6, 1)) v2Settings2 = (ITaskSettings2)v2Settings; }
			catch { }
			try { if (Environment.OSVersion.Version >= new Version(6, 2)) v2Settings3 = (ITaskSettings3)v2Settings; }
			catch { }
		}

		internal TaskSettings([NotNull] ITask iTask) => v1Task = iTask;

		/// <summary>Occurs when a property value changes.</summary>
		public event PropertyChangedEventHandler PropertyChanged;

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the task can be started by using either the Run command or the Context menu.
		/// </summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(true)]
		[XmlElement("AllowStartOnDemand")]
		[XmlIgnore]
		public bool AllowDemandStart
		{
			get => v2Settings == null || v2Settings.AllowDemandStart;
			set
			{
				if (v2Settings != null)
					v2Settings.AllowDemandStart = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a Boolean value that indicates that the task may be terminated by using TerminateProcess.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(true)]
		[XmlIgnore]
		public bool AllowHardTerminate
		{
			get => v2Settings == null || v2Settings.AllowHardTerminate;
			set
			{
				if (v2Settings != null)
					v2Settings.AllowHardTerminate = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets an integer value that indicates which version of Task Scheduler a task is compatible with.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[XmlIgnore]
		public TaskCompatibility Compatibility
		{
			get => v2Settings?.Compatibility ?? TaskCompatibility.V1;
			set
			{
				if (v2Settings != null)
					v2Settings.Compatibility = value;
				else
					if (value != TaskCompatibility.V1)
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. If no value is
		/// specified for this property, then the Task Scheduler service will not delete the task.
		/// </summary>
		/// <value>
		/// Gets and sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. A TimeSpan value
		/// of 1 second indicates the task is set to delete when done. A value of TimeSpan.Zero indicates that the task should not be deleted.
		/// </value>
		/// <remarks>
		/// A task expires after the end boundary has been exceeded for all triggers associated with the task. The end boundary for a
		/// trigger is specified by the <c>EndBoundary</c> property of all trigger types.
		/// </remarks>
		[DefaultValue(typeof(TimeSpan), "12:00:00")]
		public TimeSpan DeleteExpiredTaskAfter
		{
			get
			{
				if (v2Settings != null)
					return v2Settings.DeleteExpiredTaskAfter == "PT0S" ? TimeSpan.FromSeconds(1) : Task.StringToTimeSpan(v2Settings.DeleteExpiredTaskAfter);
				return v1Task.HasFlags(TaskFlags.DeleteWhenDone) ? TimeSpan.FromSeconds(1) : TimeSpan.Zero;
			}
			set
			{
				if (v2Settings != null)
					v2Settings.DeleteExpiredTaskAfter = value == TimeSpan.FromSeconds(1) ? "PT0S" : Task.TimeSpanToString(value);
				else
					v1Task.SetFlags(TaskFlags.DeleteWhenDone, value >= TimeSpan.FromSeconds(1));
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the task will not be started if the computer is running on battery power.
		/// </summary>
		[DefaultValue(true)]
		public bool DisallowStartIfOnBatteries
		{
			get => v2Settings?.DisallowStartIfOnBatteries ?? v1Task.HasFlags(TaskFlags.DontStartIfOnBatteries);
			set
			{
				if (v2Settings != null)
					v2Settings.DisallowStartIfOnBatteries = value;
				else
					v1Task.SetFlags(TaskFlags.DontStartIfOnBatteries, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the task will not be started if the task is triggered to run in a Remote
		/// Applications Integrated Locally (RAIL) session.
		/// </summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.1.</exception>
		[DefaultValue(false)]
		[XmlIgnore]
		public bool DisallowStartOnRemoteAppSession
		{
			get
			{
				if (v2Settings2 != null)
					return v2Settings2.DisallowStartOnRemoteAppSession;
				if (v2Settings3 != null)
					return v2Settings3.DisallowStartOnRemoteAppSession;
				return false;
			}
			set
			{
				if (v2Settings2 != null)
					v2Settings2.DisallowStartOnRemoteAppSession = value;
				else if (v2Settings3 != null)
					v2Settings3.DisallowStartOnRemoteAppSession = value;
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_1);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the task is enabled. The task can be performed only when this setting is TRUE.
		/// </summary>
		[DefaultValue(true)]
		public bool Enabled
		{
			get => v2Settings?.Enabled ?? !v1Task.HasFlags(TaskFlags.Disabled);
			set
			{
				if (v2Settings != null)
					v2Settings.Enabled = value;
				else
					v1Task.SetFlags(TaskFlags.Disabled, !value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the amount of time that is allowed to complete the task. By default, a task will be stopped 72 hours after it
		/// starts to run.
		/// </summary>
		/// <value>
		/// The amount of time that is allowed to complete the task. When this parameter is set to <see cref="TimeSpan.Zero"/>, the
		/// execution time limit is infinite.
		/// </value>
		/// <remarks>
		/// If a task is started on demand, the ExecutionTimeLimit setting is bypassed. Therefore, a task that is started on demand will not
		/// be terminated if it exceeds the ExecutionTimeLimit.
		/// </remarks>
		[DefaultValue(typeof(TimeSpan), "3")]
		public TimeSpan ExecutionTimeLimit
		{
			get
			{
				if (v2Settings != null)
					return Task.StringToTimeSpan(v2Settings.ExecutionTimeLimit);
				var ms = v1Task.GetMaxRunTime();
				return ms == InfiniteRunTimeV1 ? TimeSpan.Zero : TimeSpan.FromMilliseconds(ms);
			}
			set
			{
				if (v2Settings != null)
					v2Settings.ExecutionTimeLimit = value == TimeSpan.Zero ? "PT0S" : Task.TimeSpanToString(value);
				else
				{
					// Due to an issue introduced in Vista, and propagated to Windows 7, setting the MaxRunTime to INFINITE results in the
					// task only running for 72 hours. For these operating systems, setting the RunTime to "INFINITE - 1" gets the desired
					// behavior of allowing an "infinite" run of the task.
					var ms = value == TimeSpan.Zero ? InfiniteRunTimeV1 : Convert.ToUInt32(value.TotalMilliseconds);
					v1Task.SetMaxRunTime(ms);
					if (value == TimeSpan.Zero && v1Task.GetMaxRunTime() != InfiniteRunTimeV1)
						v1Task.SetMaxRunTime(InfiniteRunTimeV1 - 1);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a Boolean value that indicates that the task will not be visible in the UI by default.</summary>
		[DefaultValue(false)]
		public bool Hidden
		{
			get => v2Settings?.Hidden ?? v1Task.HasFlags(TaskFlags.Hidden);
			set
			{
				if (v2Settings != null)
					v2Settings.Hidden = value;
				else
					v1Task.SetFlags(TaskFlags.Hidden, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the information that the Task Scheduler uses during Automatic maintenance.</summary>
		[XmlIgnore]
		[NotNull]
		public MaintenanceSettings MaintenanceSettings => maintenanceSettings ??= new MaintenanceSettings(v2Settings3);

		/// <summary>Gets or sets the policy that defines how the Task Scheduler handles multiple instances of the task.</summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(typeof(TaskInstancesPolicy), "IgnoreNew")]
		[XmlIgnore]
		public TaskInstancesPolicy MultipleInstances
		{
			get => v2Settings?.MultipleInstances ?? TaskInstancesPolicy.IgnoreNew;
			set
			{
				if (v2Settings != null)
					v2Settings.MultipleInstances = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the priority level of the task.</summary>
		/// <value>The priority.</value>
		/// <exception cref="NotV1SupportedException">Value set to AboveNormal or BelowNormal on Task Scheduler 1.0.</exception>
		[DefaultValue(typeof(ProcessPriorityClass), "Normal")]
		public ProcessPriorityClass Priority
		{
			get => v2Settings != null ? GetPriorityFromInt(v2Settings.Priority) : (ProcessPriorityClass)v1Task.GetPriority();
			set
			{
				if (v2Settings != null)
				{
					v2Settings.Priority = GetPriorityAsInt(value);
				}
				else
				{
					if (value == ProcessPriorityClass.AboveNormal || value == ProcessPriorityClass.BelowNormal)
						throw new NotV1SupportedException("Unsupported priority level on Task Scheduler 1.0.");
					v1Task.SetPriority((uint)value);
				}
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets the number of times that the Task Scheduler will attempt to restart the task.</summary>
		/// <value>
		/// The number of times that the Task Scheduler will attempt to restart the task. If this property is set, the <see
		/// cref="RestartInterval"/> property must also be set.
		/// </value>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(0)]
		[XmlIgnore]
		public int RestartCount
		{
			get => v2Settings?.RestartCount ?? 0;
			set
			{
				if (v2Settings != null)
					v2Settings.RestartCount = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a value that specifies how long the Task Scheduler will attempt to restart the task.</summary>
		/// <value>
		/// A value that specifies how long the Task Scheduler will attempt to restart the task. If this property is set, the <see
		/// cref="RestartCount"/> property must also be set. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute.
		/// </value>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(typeof(TimeSpan), "00:00:00")]
		[XmlIgnore]
		public TimeSpan RestartInterval
		{
			get => v2Settings != null ? Task.StringToTimeSpan(v2Settings.RestartInterval) : TimeSpan.Zero;
			set
			{
				if (v2Settings != null)
					v2Settings.RestartInterval = Task.TimeSpanToString(value);
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the computer is in an idle condition.
		/// </summary>
		[DefaultValue(false)]
		public bool RunOnlyIfIdle
		{
			get => v2Settings?.RunOnlyIfIdle ?? v1Task.HasFlags(TaskFlags.StartOnlyIfIdle);
			set
			{
				if (v2Settings != null)
					v2Settings.RunOnlyIfIdle = value;
				else
					v1Task.SetFlags(TaskFlags.StartOnlyIfIdle, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the user is logged on (v1.0 only)
		/// </summary>
		/// <exception cref="NotV2SupportedException">Property set for a task on a Task Scheduler version other than 1.0.</exception>
		[XmlIgnore]
		public bool RunOnlyIfLoggedOn
		{
			get => v2Settings != null || v1Task.HasFlags(TaskFlags.RunOnlyIfLoggedOn);
			set
			{
				if (v1Task != null)
					v1Task.SetFlags(TaskFlags.RunOnlyIfLoggedOn, value);
				else if (v2Settings != null)
					throw new NotV2SupportedException("Task Scheduler 2.0 (1.2) does not support setting this property. You must use an InteractiveToken in order to have the task run in the current user session.");
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only when a network is available.</summary>
		[DefaultValue(false)]
		public bool RunOnlyIfNetworkAvailable
		{
			get => v2Settings?.RunOnlyIfNetworkAvailable ?? v1Task.HasFlags(TaskFlags.RunIfConnectedToInternet);
			set
			{
				if (v2Settings != null)
					v2Settings.RunOnlyIfNetworkAvailable = value;
				else
					v1Task.SetFlags(TaskFlags.RunIfConnectedToInternet, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the Task Scheduler can start the task at any time after its scheduled time has passed.
		/// </summary>
		/// <exception cref="NotV1SupportedException">Not supported under Task Scheduler 1.0.</exception>
		[DefaultValue(false)]
		[XmlIgnore]
		public bool StartWhenAvailable
		{
			get => v2Settings != null && v2Settings.StartWhenAvailable;
			set
			{
				if (v2Settings != null)
					v2Settings.StartWhenAvailable = value;
				else
					throw new NotV1SupportedException();
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a Boolean value that indicates that the task will be stopped if the computer switches to battery power.</summary>
		[DefaultValue(true)]
		public bool StopIfGoingOnBatteries
		{
			get => v2Settings?.StopIfGoingOnBatteries ?? v1Task.HasFlags(TaskFlags.KillIfGoingOnBatteries);
			set
			{
				if (v2Settings != null)
					v2Settings.StopIfGoingOnBatteries = value;
				else
					v1Task.SetFlags(TaskFlags.KillIfGoingOnBatteries, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a Boolean value that indicates that the Unified Scheduling Engine will be utilized to run this task.</summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.1.</exception>
		[DefaultValue(false)]
		[XmlIgnore]
		public bool UseUnifiedSchedulingEngine
		{
			get
			{
				if (v2Settings2 != null)
					return v2Settings2.UseUnifiedSchedulingEngine;
				if (v2Settings3 != null)
					return v2Settings3.UseUnifiedSchedulingEngine;
				return false;
			}
			set
			{
				if (v2Settings2 != null)
					v2Settings2.UseUnifiedSchedulingEngine = value;
				else if (v2Settings3 != null)
					v2Settings3.UseUnifiedSchedulingEngine = value;
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_1);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets a boolean value that indicates whether the task is automatically disabled every time Windows starts.</summary>
		/// <exception cref="NotSupportedPriorToException">Property set for a task on a Task Scheduler version prior to 2.2.</exception>
		[DefaultValue(false)]
		[XmlIgnore]
		public bool Volatile
		{
			get => v2Settings3 != null && v2Settings3.Volatile;
			set
			{
				if (v2Settings3 != null)
					v2Settings3.Volatile = value;
				else
					throw new NotSupportedPriorToException(TaskCompatibility.V2_2);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets a Boolean value that indicates that the Task Scheduler will wake the computer when it is time to run the task.
		/// </summary>
		[DefaultValue(false)]
		public bool WakeToRun
		{
			get => v2Settings?.WakeToRun ?? v1Task.HasFlags(TaskFlags.SystemRequired);
			set
			{
				if (v2Settings != null)
					v2Settings.WakeToRun = value;
				else
					v1Task.SetFlags(TaskFlags.SystemRequired, value);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>Gets or sets an XML-formatted definition of the task settings.</summary>
		[XmlIgnore]
		public string XmlText
		{
			get => v2Settings != null ? v2Settings.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this);
			set
			{
				if (v2Settings != null)
					v2Settings.XmlText = value;
				else
					XmlSerializationHelper.ReadObjectFromXmlText(value, this);
				OnNotifyPropertyChanged();
			}
		}

		/// <summary>
		/// Gets or sets the information that specifies how the Task Scheduler performs tasks when the computer is in an idle state.
		/// </summary>
		[NotNull]
		public IdleSettings IdleSettings => idleSettings ??= v2Settings != null ? new IdleSettings(v2Settings.IdleSettings) : new IdleSettings(v1Task);

		/// <summary>
		/// Gets or sets the network settings object that contains a network profile identifier and name. If the RunOnlyIfNetworkAvailable
		/// property of ITaskSettings is true and a network profile is specified in the NetworkSettings property, then the task will run
		/// only if the specified network profile is available.
		/// </summary>
		[XmlIgnore]
		[NotNull]
		public NetworkSettings NetworkSettings => networkSettings ??= new NetworkSettings(v2Settings?.NetworkSettings);

		/// <summary>Releases all resources used by this class.</summary>
		public void Dispose()
		{
			if (v2Settings != null)
				Marshal.ReleaseComObject(v2Settings);
			idleSettings = null;
			networkSettings = null;
			v1Task = null;
		}

		/// <summary>Returns a <see cref="string"/> that represents this instance.</summary>
		/// <returns>A <see cref="string"/> that represents this instance.</returns>
		public override string ToString()
		{
			if (v2Settings != null || v1Task != null)
				return DebugHelper.GetDebugString(this);
			return base.ToString();
		}

		XmlSchema IXmlSerializable.GetSchema() => null;

		void IXmlSerializable.ReadXml(XmlReader reader)
		{
			if (!reader.IsEmptyElement)
			{
				reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns);
				XmlSerializationHelper.ReadObjectProperties(reader, this, ConvertXmlProperty);
				reader.ReadEndElement();
			}
			else
				reader.Skip();
		}

		void IXmlSerializable.WriteXml(XmlWriter writer) => XmlSerializationHelper.WriteObjectProperties(writer, this, ConvertXmlProperty);

		private bool ConvertXmlProperty(PropertyInfo pi, object obj, ref object value)
		{
			if (pi.Name == "Priority" && value != null)
			{
				if (value is int)
					value = GetPriorityFromInt((int)value);
				else if (value is ProcessPriorityClass)
					value = GetPriorityAsInt((ProcessPriorityClass)value);
				return true;
			}
			return false;
		}

		private int GetPriorityAsInt(ProcessPriorityClass value)
		{
			// Check for back-door case where exact value is being passed and cast to ProcessPriorityClass
			if ((int)value <= 10 && value >= 0) return (int)value;
			var p = 7;
			switch (value)
			{
				case ProcessPriorityClass.AboveNormal:
					p = 3;
					break;

				case ProcessPriorityClass.High:
					p = 1;
					break;

				case ProcessPriorityClass.Idle:
					p = 10;
					break;

				case ProcessPriorityClass.Normal:
					p = 5;
					break;

				case ProcessPriorityClass.RealTime:
					p = 0;
					break;
					// case ProcessPriorityClass.BelowNormal: default: break;
			}
			return p;
		}

		private ProcessPriorityClass GetPriorityFromInt(int value)
		{
			switch (value)
			{
				case 0:
					return ProcessPriorityClass.RealTime;

				case 1:
					return ProcessPriorityClass.High;

				case 2:
				case 3:
					return ProcessPriorityClass.AboveNormal;

				case 4:
				case 5:
				case 6:
					return ProcessPriorityClass.Normal;
				// case 7: case 8:
				default:
					return ProcessPriorityClass.BelowNormal;

				case 9:
				case 10:
					return ProcessPriorityClass.Idle;
			}
		}

		/// <summary>Called when a property has changed to notify any attached elements.</summary>
		/// <param name="propertyName">Name of the property.</param>
		private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
	}

	internal static class DebugHelper
	{
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Language", "CSE0003:Use expression-bodied members", Justification = "<Pending>")]
		public static string GetDebugString(object inst)
		{
#if DEBUG
			var sb = new StringBuilder();
			foreach (var pi in inst.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
			{
				if (pi.Name.StartsWith("Xml"))
					continue;
				var outval = pi.GetValue(inst, null);
				if (outval != null)
				{
					var defval = XmlSerializationHelper.GetDefaultValue(pi);
					if (!outval.Equals(defval))
					{
						var s = $"{pi.Name}:{outval}";
						if (s.Length > 30) s = s.Remove(30);
						sb.Append(s + "; ");
					}
				}
			}
			return sb.ToString();
#else
			return inst.GetType().ToString();
#endif
		}
	}

	internal static class TSInteropExt
	{
		public static string GetDataItem(this ITask v1Task, string name)
		{
			TaskDefinition.GetV1TaskDataDictionary(v1Task).TryGetValue(name, out var ret);
			return ret;
		}

		public static bool HasFlags(this ITask v1Task, TaskFlags flags) => v1Task.GetFlags().IsFlagSet(flags);

		public static void SetDataItem(this ITask v1Task, string name, string value)
		{
			var d = TaskDefinition.GetV1TaskDataDictionary(v1Task);
			d[name] = value;
			TaskDefinition.SetV1TaskData(v1Task, d);
		}

		public static void SetFlags(this ITask v1Task, TaskFlags flags, bool value = true) => v1Task.SetFlags(v1Task.GetFlags().SetFlags(flags, value));
	}

	internal class DefaultValueExAttribute : DefaultValueAttribute
	{
		public DefaultValueExAttribute(Type type, string value) : base(null)
		{
			try
			{
				if (type == typeof(Version))
				{
					SetValue(new Version(value));
					return;
				}
				SetValue(TypeDescriptor.GetConverter(type).ConvertFromInvariantString(value));
			}
			catch
			{
				Debug.Fail("Default value attribute of type " + type.FullName + " threw converting from the string '" + value + "'.");
			}
		}
	}
}
