using JetBrains.Annotations; using Microsoft.Win32.TaskScheduler.V1Interop; using Microsoft.Win32.TaskScheduler.V2Interop; 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 IPrincipal = Microsoft.Win32.TaskScheduler.V2Interop.IPrincipal; // ReSharper disable UnusedMember.Global // ReSharper disable InconsistentNaming ReSharper disable SuspiciousTypeConversion.Global namespace Microsoft.Win32.TaskScheduler { /// Defines what versions of Task Scheduler or the AT command that the task is compatible with. public enum TaskCompatibility { /// The task is compatible with the AT command. AT, /// /// The task is compatible with Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000). /// Items not available when compared to V2: /// /// /// TaskDefinition.Principal.GroupId - All account information can be retrieved via the UserId property. /// /// /// TaskLogonType values Group, None and S4U are not supported. /// /// /// TaskDefinition.Principal.RunLevel == TaskRunLevel.Highest is not supported. /// /// /// /// Assigning access security to a task is not supported using TaskDefinition.RegistrationInfo.SecurityDescriptorSddlForm or in RegisterTaskDefinition. /// /// /// /// /// TaskDefinition.RegistrationInfo.Documentation, Source, URI and Version properties are only supported using this library. See /// details in the remarks for . /// /// /// /// TaskDefinition.Settings.AllowDemandStart cannot be false. /// /// /// TaskDefinition.Settings.AllowHardTerminate cannot be false. /// /// /// TaskDefinition.Settings.MultipleInstances can only be IgnoreNew. /// /// /// TaskDefinition.Settings.NetworkSettings cannot have any values. /// /// /// TaskDefinition.Settings.RestartCount can only be 0. /// /// /// TaskDefinition.Settings.StartWhenAvailable can only be false. /// /// /// /// TaskDefinition.Actions can only contain ExecAction instances unless the TaskDefinition.Actions.PowerShellConversion property has /// the Version1 flag set. /// /// /// /// /// TaskDefinition.Triggers cannot contain CustomTrigger, EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger instances. /// /// /// /// TaskDefinition.Triggers cannot contain instances with delays set. /// /// /// TaskDefinition.Triggers cannot contain instances with ExecutionTimeLimit or Id properties set. /// /// /// TaskDefinition.Triggers cannot contain LogonTriggers instances with the UserId property set. /// /// /// TaskDefinition.Triggers cannot contain MonthlyDOWTrigger instances with the RunOnLastWeekOfMonth property set to true. /// /// /// TaskDefinition.Triggers cannot contain MonthlyTrigger instances with the RunOnDayWeekOfMonth property set to true. /// /// /// V1, /// /// The task is compatible with Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008). /// /// This version is the baseline for the new, non-file based Task Scheduler. See remarks for /// functionality that was not forward-compatible. /// /// V2, /// /// The task is compatible with Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2). /// Changes from V2: /// /// /// TaskDefinition.Principal.ProcessTokenSidType can be defined as a value other than Default. /// /// /// /// TaskDefinition.Actions may not contain EmailAction or ShowMessageAction instances unless the /// TaskDefinition.Actions.PowerShellConversion property has the Version2 flag set. /// /// /// /// TaskDefinition.Principal.RequiredPrivileges can have privilege values assigned. /// /// /// TaskDefinition.Settings.DisallowStartOnRemoteAppSession can be set to true. /// /// /// TaskDefinition.UseUnifiedSchedulingEngine can be set to true. /// /// /// V2_1, /// /// The task is compatible with Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012). /// Changes from V2_1: /// /// /// /// TaskDefinition.Settings.MaintenanceSettings can have Period or Deadline be values other than TimeSpan.Zero or the Exclusive /// property set to true. /// /// /// /// TaskDefinition.Settings.Volatile can be set to true. /// /// /// V2_2, /// /// The task is compatible with Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016). /// Changes from V2_2: /// /// /// None published. /// /// /// V2_3 } /// Defines how the Task Scheduler service creates, updates, or disables the task. [DefaultValue(CreateOrUpdate)] public enum TaskCreation { /// The Task Scheduler service registers the task as a new task. Create = 2, /// /// The Task Scheduler service either registers the task as a new task or as an updated version if the task already exists. /// Equivalent to Create | Update. /// CreateOrUpdate = 6, /// /// The Task Scheduler service registers the disabled task. A disabled task cannot run until it is enabled. For more information, /// see Enabled Property of TaskSettings and Enabled Property of RegisteredTask. /// Disable = 8, /// /// The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal. When the /// TaskFolder.RegisterTaskDefinition or TaskFolder.RegisterTask functions are called with this flag to update a task, the Task /// Scheduler service does not add the ACE for the new context principal and does not remove the ACE from the old context principal. /// DontAddPrincipalAce = 0x10, /// /// The Task Scheduler service creates the task, but ignores the registration triggers in the task. By ignoring the registration /// triggers, the task will not execute when it is registered unless a time-based trigger causes it to execute on registration. /// IgnoreRegistrationTriggers = 0x20, /// /// The Task Scheduler service registers the task as an updated version of an existing task. When a task with a registration trigger /// is updated, the task will execute after the update occurs. /// Update = 4, /// /// The Task Scheduler service checks the syntax of the XML that describes the task but does not register the task. This constant /// cannot be combined with the Create, Update, or CreateOrUpdate values. /// ValidateOnly = 1 } /// Defines how the Task Scheduler handles existing instances of the task when it starts a new instance of the task. [DefaultValue(IgnoreNew)] public enum TaskInstancesPolicy { /// Starts new instance while an existing instance is running. Parallel, /// Starts a new instance of the task after all other instances of the task are complete. Queue, /// Does not start a new instance if an existing instance of the task is running. IgnoreNew, /// Stops an existing instance of the task before it starts a new instance. StopExisting } /// Defines what logon technique is required to run a task. [DefaultValue(S4U)] public enum TaskLogonType { /// The logon method is not specified. Used for non-NT credentials. None, /// Use a password for logging on the user. The password must be supplied at registration time. Password, /// /// Use an existing interactive token to run a task. The user must log on using a service for user (S4U) logon. When an S4U logon is /// used, no password is stored by the system and there is no access to either the network or to encrypted files. /// S4U, /// User must already be logged on. The task will be run only in an existing interactive session. InteractiveToken, /// Group activation. The groupId field specifies the group. Group, /// /// Indicates that a Local System, Local Service, or Network Service account is being used as a security context to run the task. /// ServiceAccount, /// /// First use the interactive token. If the user is not logged on (no interactive token is available), then the password is used. /// The password must be specified when a task is registered. This flag is not recommended for new tasks because it is less reliable /// than Password. /// InteractiveTokenOrPassword } /// Defines which privileges must be required for a secured task. public enum TaskPrincipalPrivilege { /// Required to create a primary token. User Right: Create a token object. SeCreateTokenPrivilege = 1, /// Required to assign the primary token of a process. User Right: Replace a process-level token. SeAssignPrimaryTokenPrivilege, /// Required to lock physical pages in memory. User Right: Lock pages in memory. SeLockMemoryPrivilege, /// Required to increase the quota assigned to a process. User Right: Adjust memory quotas for a process. SeIncreaseQuotaPrivilege, /// Required to read unsolicited input from a terminal device. User Right: Not applicable. SeUnsolicitedInputPrivilege, /// Required to create a computer account. User Right: Add workstations to domain. SeMachineAccountPrivilege, /// /// This privilege identifies its holder as part of the trusted computer base. Some trusted protected subsystems are granted this /// privilege. User Right: Act as part of the operating system. /// SeTcbPrivilege, /// /// Required to perform a number of security-related functions, such as controlling and viewing audit messages. This privilege /// identifies its holder as a security operator. User Right: Manage auditing and the security log. /// SeSecurityPrivilege, /// /// Required to take ownership of an object without being granted discretionary access. This privilege allows the owner value to be /// set only to those values that the holder may legitimately assign as the owner of an object. User Right: Take ownership of files /// or other objects. /// SeTakeOwnershipPrivilege, /// Required to load or unload a device driver. User Right: Load and unload device drivers. SeLoadDriverPrivilege, /// Required to gather profiling information for the entire system. User Right: Profile system performance. SeSystemProfilePrivilege, /// Required to modify the system time. User Right: Change the system time. SeSystemtimePrivilege, /// Required to gather profiling information for a single process. User Right: Profile single process. SeProfileSingleProcessPrivilege, /// Required to increase the base priority of a process. User Right: Increase scheduling priority. SeIncreaseBasePriorityPrivilege, /// Required to create a paging file. User Right: Create a pagefile. SeCreatePagefilePrivilege, /// Required to create a permanent object. User Right: Create permanent shared objects. SeCreatePermanentPrivilege, /// /// Required to perform backup operations. This privilege causes the system to grant all read access control to any file, regardless /// of the access control list (ACL) specified for the file. Any access request other than read is still evaluated with the ACL. /// This privilege is required by the RegSaveKey and RegSaveKeyExfunctions. The following access rights are granted if this /// privilege is held: READ_CONTROL, ACCESS_SYSTEM_SECURITY, FILE_GENERIC_READ, FILE_TRAVERSE. User Right: Back up files and directories. /// SeBackupPrivilege, /// /// Required to perform restore operations. This privilege causes the system to grant all write access control to any file, /// regardless of the ACL specified for the file. Any access request other than write is still evaluated with the ACL. Additionally, /// this privilege enables you to set any valid user or group security identifier (SID) as the owner of a file. This privilege is /// required by the RegLoadKey function. The following access rights are granted if this privilege is held: WRITE_DAC, WRITE_OWNER, /// ACCESS_SYSTEM_SECURITY, FILE_GENERIC_WRITE, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, DELETE. User Right: Restore files and directories. /// SeRestorePrivilege, /// Required to shut down a local system. User Right: Shut down the system. SeShutdownPrivilege, /// Required to debug and adjust the memory of a process owned by another account. User Right: Debug programs. SeDebugPrivilege, /// Required to generate audit-log entries. Give this privilege to secure servers. User Right: Generate security audits. SeAuditPrivilege, /// /// Required to modify the nonvolatile RAM of systems that use this type of memory to store configuration information. User Right: /// Modify firmware environment values. /// SeSystemEnvironmentPrivilege, /// /// Required to receive notifications of changes to files or directories. This privilege also causes the system to skip all /// traversal access checks. It is enabled by default for all users. User Right: Bypass traverse checking. /// SeChangeNotifyPrivilege, /// Required to shut down a system by using a network request. User Right: Force shutdown from a remote system. SeRemoteShutdownPrivilege, /// Required to undock a laptop. User Right: Remove computer from docking station. SeUndockPrivilege, /// /// Required for a domain controller to use the LDAP directory synchronization services. This privilege allows the holder to read /// all objects and properties in the directory, regardless of the protection on the objects and properties. By default, it is /// assigned to the Administrator and LocalSystem accounts on domain controllers. User Right: Synchronize directory service data. /// SeSyncAgentPrivilege, /// /// Required to mark user and computer accounts as trusted for delegation. User Right: Enable computer and user accounts to be /// trusted for delegation. /// SeEnableDelegationPrivilege, /// Required to enable volume management privileges. User Right: Manage the files on a volume. SeManageVolumePrivilege, /// /// Required to impersonate. User Right: Impersonate a client after authentication. Windows XP/2000: This privilege is not /// supported. Note that this value is supported starting with Windows Server 2003, Windows XP with SP2, and Windows 2000 with SP4. /// SeImpersonatePrivilege, /// /// Required to create named file mapping objects in the global namespace during Terminal Services sessions. This privilege is /// enabled by default for administrators, services, and the local system account. User Right: Create global objects. Windows /// XP/2000: This privilege is not supported. Note that this value is supported starting with Windows Server 2003, Windows XP with /// SP2, and Windows 2000 with SP4. /// SeCreateGlobalPrivilege, /// Required to access Credential Manager as a trusted caller. User Right: Access Credential Manager as a trusted caller. SeTrustedCredManAccessPrivilege, /// Required to modify the mandatory integrity level of an object. User Right: Modify an object label. SeRelabelPrivilege, /// /// Required to allocate more memory for applications that run in the context of users. User Right: Increase a process working set. /// SeIncreaseWorkingSetPrivilege, /// Required to adjust the time zone associated with the computer's internal clock. User Right: Change the time zone. SeTimeZonePrivilege, /// Required to create a symbolic link. User Right: Create symbolic links. SeCreateSymbolicLinkPrivilege } /// /// Defines the types of process security identifier (SID) that can be used by tasks. These changes are used to specify the type of /// process SID in the IPrincipal2 interface. /// public enum TaskProcessTokenSidType { /// No changes will be made to the process token groups list. None = 0, /// /// A task SID that is derived from the task name will be added to the process token groups list, and the token default /// discretionary access control list (DACL) will be modified to allow only the task SID and local system full control and the /// account SID read control. /// Unrestricted = 1, /// A Task Scheduler will apply default settings to the task process. Default = 2 } /// Defines how a task is run. [Flags] public enum TaskRunFlags { /// The task is run with all flags ignored. NoFlags = 0, /// The task is run as the user who is calling the Run method. AsSelf = 1, /// The task is run regardless of constraints such as "do not run on batteries" or "run only if idle". IgnoreConstraints = 2, /// The task is run using a terminal server session identifier. UseSessionId = 4, /// The task is run using a security identifier. UserSID = 8 } /// Defines LUA elevation flags that specify with what privilege level the task will be run. public enum TaskRunLevel { /// Tasks will be run with the least privileges. [XmlEnum("LeastPrivilege")] LUA, /// Tasks will be run with the highest privileges. [XmlEnum("HighestAvailable")] Highest } /// /// Defines what kind of Terminal Server session state change you can use to trigger a task to start. These changes are used to specify /// the type of state change in the SessionStateChangeTrigger. /// public enum TaskSessionStateChangeType { /// /// Terminal Server console connection state change. For example, when you connect to a user session on the local computer by /// switching users on the computer. /// ConsoleConnect = 1, /// /// Terminal Server console disconnection state change. For example, when you disconnect to a user session on the local computer by /// switching users on the computer. /// ConsoleDisconnect = 2, /// /// Terminal Server remote connection state change. For example, when a user connects to a user session by using the Remote Desktop /// Connection program from a remote computer. /// RemoteConnect = 3, /// /// Terminal Server remote disconnection state change. For example, when a user disconnects from a user session while using the /// Remote Desktop Connection program from a remote computer. /// RemoteDisconnect = 4, /// /// Terminal Server session locked state change. For example, this state change causes the task to run when the computer is locked. /// SessionLock = 7, /// /// Terminal Server session unlocked state change. For example, this state change causes the task to run when the computer is unlocked. /// SessionUnlock = 8 } /// Options for use when calling the SetSecurityDescriptorSddlForm methods. [Flags] public enum TaskSetSecurityOptions { /// No special handling. None = 0, /// The Task Scheduler service is prevented from adding the allow access-control entry (ACE) for the context principal. DontAddPrincipalAce = 0x10 } /***** WAITING TO DETERMINE USE CASE ***** /// Success and error codes that some methods will expose through . public enum TaskResultCode { /// The task is ready to run at its next scheduled time. TaskReady = 0x00041300, /// The task is currently running. TaskRunning = 0x00041301, /// The task will not run at the scheduled times because it has been disabled. TaskDisabled = 0x00041302, /// The task has not yet run. TaskHasNotRun = 0x00041303, /// There are no more runs scheduled for this task. TaskNoMoreRuns = 0x00041304, /// One or more of the properties that are needed to run this task on a schedule have not been set. TaskNotScheduled = 0x00041305, /// The last run of the task was terminated by the user. TaskTerminated = 0x00041306, /// Either the task has no triggers or the existing triggers are disabled or not set. TaskNoValidTriggers = 0x00041307, /// Event triggers do not have set run times. EventTrigger = 0x00041308, /// A task's trigger is not found. TriggerNotFound = 0x80041309, /// One or more of the properties required to run this task have not been set. TaskNotReady = 0x8004130A, /// There is no running instance of the task. TaskNotRunning = 0x8004130B, /// The Task Scheduler service is not installed on this computer. ServiceNotInstalled = 0x8004130C, /// The task object could not be opened. CannotOpenTask = 0x8004130D, /// The object is either an invalid task object or is not a task object. InvalidTask = 0x8004130E, /// No account information could be found in the Task Scheduler security database for the task indicated. AccountInformationNotSet = 0x8004130F, /// Unable to establish existence of the account specified. AccountNameNotFound = 0x80041310, /// Corruption was detected in the Task Scheduler security database; the database has been reset. AccountDbaseCorrupt = 0x80041311, /// Task Scheduler security services are available only on Windows NT. NoSecurityServices = 0x80041312, /// The task object version is either unsupported or invalid. UnknownObjectVersion = 0x80041313, /// The task has been configured with an unsupported combination of account settings and run time options. UnsupportedAccountOption = 0x80041314, /// The Task Scheduler Service is not running. ServiceNotRunning = 0x80041315, /// The task XML contains an unexpected node. UnexpectedNode = 0x80041316, /// The task XML contains an element or attribute from an unexpected namespace. Namespace = 0x80041317, /// The task XML contains a value which is incorrectly formatted or out of range. InvalidValue = 0x80041318, /// The task XML is missing a required element or attribute. MissingNode = 0x80041319, /// The task XML is malformed. MalformedXml = 0x8004131A, /// The task is registered, but not all specified triggers will start the task. SomeTriggersFailed = 0x0004131B, /// The task is registered, but may fail to start. Batch logon privilege needs to be enabled for the task principal. BatchLogonProblem = 0x0004131C, /// The task XML contains too many nodes of the same type. TooManyNodes = 0x8004131D, /// The task cannot be started after the trigger end boundary. PastEndBoundary = 0x8004131E, /// An instance of this task is already running. AlreadyRunning = 0x8004131F, /// The task will not run because the user is not logged on. UserNotLoggedOn = 0x80041320, /// The task image is corrupt or has been tampered with. InvalidTaskHash = 0x80041321, /// The Task Scheduler service is not available. ServiceNotAvailable = 0x80041322, /// The Task Scheduler service is too busy to handle your request. Please try again later. ServiceTooBusy = 0x80041323, /// /// The Task Scheduler service attempted to run the task, but the task did not run due to one of the constraints in the task definition. /// TaskAttempted = 0x80041324, /// The Task Scheduler service has asked the task to run. TaskQueued = 0x00041325, /// The task is disabled. TaskDisabled = 0x80041326, /// The task has properties that are not compatible with earlier versions of Windows. TaskNotV1Compatible = 0x80041327, /// The task settings do not allow the task to start on demand. StartOnDemand = 0x80041328, } */ /// Defines the different states that a registered task can be in. public enum TaskState { /// The state of the task is unknown. Unknown, /// /// The task is registered but is disabled and no instances of the task are queued or running. The task cannot be run until it is enabled. /// Disabled, /// Instances of the task are queued. Queued, /// The task is ready to be executed, but no instances are queued or running. Ready, /// One or more instances of the task is running. Running } /// /// Specifies how the Task Scheduler performs tasks when the computer is in an idle condition. For information about idle conditions, /// see Task Idle Conditions. /// [PublicAPI] public sealed class IdleSettings : IDisposable, INotifyPropertyChanged { private readonly IIdleSettings v2Settings; private ITask v1Task; internal IdleSettings([NotNull] IIdleSettings iSettings) => v2Settings = iSettings; internal IdleSettings([NotNull] ITask iTask) => v1Task = iTask; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Gets or sets a value that indicates the amount of time that the computer must be in an idle state before the task is run. /// /// /// A value that indicates the amount of time that the computer must be in an idle state before the task is run. The minimum value /// is one minute. If this value is TimeSpan.Zero, then the delay will be set to the default of 10 minutes. /// [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(); } } /// /// Gets or sets a Boolean value that indicates whether the task is restarted when the computer cycles into an idle condition more /// than once. /// [DefaultValue(false)] public bool RestartOnIdle { get => v2Settings?.RestartOnIdle ?? v1Task.HasFlags(TaskFlags.RestartOnIdleResume); set { if (v2Settings != null) v2Settings.RestartOnIdle = value; else v1Task.SetFlags(TaskFlags.RestartOnIdleResume, value); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the Task Scheduler will terminate the task if the idle condition ends before /// the task is completed. /// [DefaultValue(true)] public bool StopOnIdleEnd { get => v2Settings?.StopOnIdleEnd ?? v1Task.HasFlags(TaskFlags.KillOnIdleEnd); set { if (v2Settings != null) v2Settings.StopOnIdleEnd = value; else v1Task.SetFlags(TaskFlags.KillOnIdleEnd, value); OnNotifyPropertyChanged(); } } /// /// Gets or sets a value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. If no /// value is specified for this property, then the Task Scheduler service will wait indefinitely for an idle condition to occur. /// /// /// A value that indicates the amount of time that the Task Scheduler will wait for an idle condition to occur. The minimum time /// allowed is 1 minute. If this value is TimeSpan.Zero, then the delay will be set to the default of 1 hour. /// [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(); } } /// Releases all resources used by this class. public void Dispose() { if (v2Settings != null) Marshal.ReleaseComObject(v2Settings); v1Task = null; } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (v2Settings != null || v1Task != null) return DebugHelper.GetDebugString(this); return base.ToString(); } /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// Specifies the task settings the Task scheduler will use to start task during Automatic maintenance. [XmlType(IncludeInSchema = false)] [PublicAPI] public sealed class MaintenanceSettings : IDisposable, INotifyPropertyChanged { private readonly ITaskSettings3 iSettings; private IMaintenanceSettings iMaintSettings; internal MaintenanceSettings([CanBeNull] ITaskSettings3 iSettings3) { iSettings = iSettings3; if (iSettings3 != null) iMaintSettings = iSettings.MaintenanceSettings; } /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Gets or sets the amount of time after which the Task scheduler attempts to run the task during emergency Automatic maintenance, /// if the task failed to complete during regular Automatic maintenance. The minimum value is one day. The value of the property should be greater than the value of the property. If the deadline is not /// specified the task will not be started during emergency Automatic maintenance. /// /// Property set for a task on a Task Scheduler version prior to 2.2. [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(); } } /// /// Gets or sets a value indicating whether the Task Scheduler must start the task during the Automatic maintenance in exclusive /// mode. The exclusivity is guaranteed only between other maintenance tasks and doesn't grant any ordering priority of the task. If /// exclusivity is not specified, the task is started in parallel with other maintenance tasks. /// /// Property set for a task on a Task Scheduler version prior to 2.2. [DefaultValue(false)] public bool Exclusive { get => iMaintSettings != null && iMaintSettings.Exclusive; set { if (iSettings != null) { if (iMaintSettings == null && value) iMaintSettings = iSettings.CreateMaintenanceSettings(); if (iMaintSettings != null) iMaintSettings.Exclusive = value; } else throw new NotSupportedPriorToException(TaskCompatibility.V2_2); OnNotifyPropertyChanged(); } } /// /// Gets or sets the amount of time the task needs to be started during Automatic maintenance. The minimum value is one minute. /// /// Property set for a task on a Task Scheduler version prior to 2.2. [DefaultValue(typeof(TimeSpan), "00:00:00")] public TimeSpan Period { 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(); } } /// Releases all resources used by this class. public void Dispose() { if (iMaintSettings != null) Marshal.ReleaseComObject(iMaintSettings); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => iMaintSettings != null ? DebugHelper.GetDebugString(this) : base.ToString(); internal bool IsSet() => iMaintSettings != null && (iMaintSettings.Period != null || iMaintSettings.Deadline != null || iMaintSettings.Exclusive); /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// Provides the settings that the Task Scheduler service uses to obtain a network profile. [XmlType(IncludeInSchema = false)] [PublicAPI] public sealed class NetworkSettings : IDisposable, INotifyPropertyChanged { private readonly INetworkSettings v2Settings; internal NetworkSettings([CanBeNull] INetworkSettings iSettings) => v2Settings = iSettings; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets or sets a GUID value that identifies a network profile. /// Not supported under Task Scheduler 1.0. [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(); } } /// Gets or sets the name of a network profile. The name is used for display purposes. /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] public string Name { get => v2Settings?.Name; set { if (v2Settings != null) v2Settings.Name = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Releases all resources used by this class. public void Dispose() { if (v2Settings != null) Marshal.ReleaseComObject(v2Settings); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (v2Settings != null) return DebugHelper.GetDebugString(this); return base.ToString(); } internal bool IsSet() => v2Settings != null && (!string.IsNullOrEmpty(v2Settings.Id) || !string.IsNullOrEmpty(v2Settings.Name)); /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// Provides the methods to get information from and control a running task. [XmlType(IncludeInSchema = false)] [PublicAPI] public sealed class RunningTask : Task { private 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) { } /// Gets the process ID for the engine (process) which is running the task. /// Not supported under Task Scheduler 1.0. public uint EnginePID { get { if (v2RunningTask != null) return v2RunningTask.EnginePID; throw new NotV1SupportedException(); } } /// Gets the name of the current action that the running task is performing. public string CurrentAction => v2RunningTask != null ? v2RunningTask.CurrentAction : v1Task.GetApplicationName(); /// Gets the GUID identifier for this instance of the task. public Guid InstanceGuid => v2RunningTask != null ? new Guid(v2RunningTask.InstanceGuid) : Guid.Empty; /// Gets the operational state of the running task. public override TaskState State => v2RunningTask?.State ?? base.State; /// Releases all resources used by this class. public new void Dispose() { base.Dispose(); if (v2RunningTask != null) Marshal.ReleaseComObject(v2RunningTask); } /// Refreshes all of the local instance variables of the task. /// Thrown if task is no longer running. public void Refresh() { try { v2RunningTask?.Refresh(); } catch (COMException ce) when ((uint)ce.ErrorCode == 0x8004130B) { throw new InvalidOperationException("The current task is no longer running.", ce); } } } /// /// Provides the methods that are used to run the task immediately, get any running instances of the task, get or set the credentials /// that are used to register the task, and the properties that describe the task. /// [XmlType(IncludeInSchema = false)] [PublicAPI] public class Task : IDisposable, IComparable, IComparable, INotifyPropertyChanged { internal const AccessControlSections defaultAccessControlSections = AccessControlSections.Owner | AccessControlSections.Group | AccessControlSections.Access; internal const SecurityInfos defaultSecurityInfosSections = SecurityInfos.Owner | SecurityInfos.Group | SecurityInfos.DiscretionaryAcl; 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; } /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets the definition of the task. [NotNull] public TaskDefinition Definition => myTD ??= v2Task != null ? new TaskDefinition(GetV2Definition(TaskService, v2Task, true)) : new TaskDefinition(v1Task, Name); /// Gets or sets a Boolean value that indicates if the registered task is enabled. /// /// As of version 1.8.1, under V1 systems (prior to Vista), this property will immediately update the Disabled state and re-save the /// current task. If changes have been made to the , then those changes will be saved. /// 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(); } } /// Gets an instance of the parent folder. /// A object representing the parent folder of this task. [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) ?? throw new DirectoryNotFoundException(); } } /// Gets a value indicating whether this task instance is active. /// true if this task instance is active; otherwise, false. public bool IsActive { get { var now = DateTime.Now; if (!Definition.Settings.Enabled) return false; foreach (var trigger in Definition.Triggers) { if (!trigger.Enabled || now < trigger.StartBoundary || now > trigger.EndBoundary) continue; if (!(trigger is ICalendarTrigger) || DateTime.MinValue != NextRunTime || trigger is TimeTrigger) return true; } return false; } } /// Gets the time the registered task was last run. /// Returns if there are no prior run times. public DateTime LastRunTime { get { if (v2Task == null) return v1Task.GetMostRecentRunTime(); var dt = v2Task.LastRunTime; return dt == v2InvalidDate ? DateTime.MinValue : dt; } } /// Gets the results that were returned the last time the registered task was run. /// The value returned is the last exit code of the last program run via an . /// /// /// /// /// public int LastTaskResult { get { if (v2Task != null) return v2Task.LastTaskResult; return (int)v1Task.GetExitCode(); } } /// Gets the time when the registered task is next scheduled to run. /// Returns if there are no future run times. /// /// Potentially breaking change in release 1.8.2. For Task Scheduler 2.0, the return value prior to 1.8.2 would be Dec 30, 1899 if /// there were no future run times. For 1.0, that value would have been DateTime.MinValue. In release 1.8.2 and later, all /// versions will return DateTime.MinValue if there are no future run times. While this is different from the native 2.0 /// library, it was deemed more appropriate to have consistency between the two libraries and with other .NET libraries. /// public DateTime NextRunTime { get { if (v2Task == null) return v1Task.GetNextRunTime(); var ret = v2Task.NextRunTime; if (ret != DateTime.MinValue && ret != v2InvalidDate) return ret == v2InvalidDate ? DateTime.MinValue : ret; var nrts = GetRunTimes(DateTime.Now, DateTime.MaxValue, 1); ret = nrts.Length > 0 ? nrts[0] : DateTime.MinValue; return ret == v2InvalidDate ? DateTime.MinValue : ret; } } /// /// Gets a value indicating whether this task is read only. Only available if is true. /// /// true if read only; otherwise, false. public bool ReadOnly { get; internal set; } /// Gets or sets the security descriptor for the task. /// The security descriptor. [Obsolete("This property will be removed in deference to the GetAccessControl, GetSecurityDescriptorSddlForm, SetAccessControl and SetSecurityDescriptorSddlForm methods.")] public GenericSecurityDescriptor SecurityDescriptor { get { var sddl = GetSecurityDescriptorSddlForm(); return new RawSecurityDescriptor(sddl); } set => SetSecurityDescriptorSddlForm(value.GetSddlForm(defaultAccessControlSections)); } /// Gets the operational state of the registered task. public virtual TaskState State { 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; } } } /// Gets or sets the that manages this task. /// The task service. public TaskService TaskService { get; } /// Gets the name of the registered task. [NotNull] public string Name => v2Task != null ? v2Task.Name : System.IO.Path.GetFileNameWithoutExtension(GetV1Path(v1Task)); /// Gets the number of times the registered task has missed a scheduled run. /// Not supported under Task Scheduler 1.0. public int NumberOfMissedRuns => v2Task?.NumberOfMissedRuns ?? throw new NotV1SupportedException(); /// Gets the path to where the registered task is stored. [NotNull] public string Path => v2Task != null ? v2Task.Path : "\\" + Name; /// Gets the XML-formatted registration information for the registered task. public string Xml => v2Task != null ? v2Task.Xml : Definition.XmlText; /// /// Compares the current instance with another object of the same type and returns an integer that indicates whether the current /// instance precedes, follows, or occurs in the same position in the sort order as the other object. /// /// An object to compare with this instance. /// A value that indicates the relative order of the objects being compared. public int CompareTo(Task other) => string.Compare(Path, other?.Path, StringComparison.InvariantCulture); /// Releases all resources used by this class. public void Dispose() { if (v2Task != null) Marshal.ReleaseComObject(v2Task); v1Task = null; } /// Exports the task to the specified file in XML. /// Name of the output file. public void Export([NotNull] string outputFileName) => File.WriteAllText(outputFileName, Xml, Encoding.Unicode); /// /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task /// described by the current object. /// /// A object that encapsulates the access control rules for the current task. public TaskSecurity GetAccessControl() => GetAccessControl(defaultAccessControlSections); /// /// Gets a object that encapsulates the specified type of access control list (ACL) entries for the task /// described by the current object. /// /// /// One of the values that specifies which group of access control /// entries to retrieve. /// /// A object that encapsulates the access control rules for the current task. public TaskSecurity GetAccessControl(AccessControlSections includeSections) => new TaskSecurity(this, includeSections); /// Gets all instances of the currently running registered task. /// A with all instances of current task. /// Not supported under Task Scheduler 1.0. [NotNull, ItemNotNull] public RunningTaskCollection GetInstances() => v2Task != null ? new RunningTaskCollection(TaskService, v2Task.GetInstances(0)) : throw new NotV1SupportedException(); /// /// Gets the last registration time, looking first at the value and then looking for the /// most recent registration event in the Event Log. /// /// of the last registration or if no value can be found. public DateTime GetLastRegistrationTime() { var ret = Definition.RegistrationInfo.Date; if (ret != DateTime.MinValue) return ret; var log = new TaskEventLog(Path, new[] { (int)StandardTaskEventId.JobRegistered }, null, TaskService.TargetServer, TaskService.UserAccountDomain, TaskService.UserName, TaskService.UserPassword); if (!log.Enabled) return ret; foreach (var item in log) { if (item.TimeCreated.HasValue) return item.TimeCreated.Value; } return ret; } /// Gets the times that the registered task is scheduled to run during a specified time. /// The starting time for the query. /// The ending time for the query. /// The requested number of runs. A value of 0 will return all times requested. /// The scheduled times that the task will run. [NotNull] public DateTime[] GetRunTimes(DateTime start, DateTime end, uint count = 0) { const ushort TASK_MAX_RUN_TIMES = 1440; NativeMethods.SYSTEMTIME stStart = start; NativeMethods.SYSTEMTIME stEnd = end; var runTimes = IntPtr.Zero; var ret = new DateTime[0]; try { if (v2Task != null) v2Task.GetRunTimes(ref stStart, ref stEnd, ref count, ref runTimes); else { var count1 = count > 0 && count <= TASK_MAX_RUN_TIMES ? (ushort)count : TASK_MAX_RUN_TIMES; v1Task.GetRunTimes(ref stStart, ref stEnd, ref count1, ref runTimes); count = count1; } ret = InteropUtil.ToArray(runTimes, (int)count); } catch (Exception ex) { Debug.WriteLine($"Task.GetRunTimes failed: Error {ex}."); } finally { Marshal.FreeCoTaskMem(runTimes); } Debug.WriteLine($"Task.GetRunTimes ({(v2Task != null ? "V2" : "V1")}): Returned {count} items from {stStart} to {stEnd}."); return ret; } /// Gets the security descriptor for the task. Not available to Task Scheduler 1.0. /// Section(s) of the security descriptor to return. /// The security descriptor for the task. /// Not supported under Task Scheduler 1.0. public string GetSecurityDescriptorSddlForm(SecurityInfos includeSections = defaultSecurityInfosSections) => v2Task != null ? v2Task.GetSecurityDescriptor((int)includeSections) : throw new NotV1SupportedException(); /// /// Updates the task with any changes made to the by calling from the currently registered folder using the currently /// registered name. /// /// Thrown if task was previously registered with a password. public void RegisterChanges() { if (Definition.Principal.RequiresPassword()) throw new SecurityException("Tasks which have been registered previously with stored passwords must use the TaskFolder.RegisterTaskDefinition method for updates."); if (v2Task != null) TaskService.GetFolder(System.IO.Path.GetDirectoryName(Path)).RegisterTaskDefinition(Name, Definition, TaskCreation.Update, null, null, Definition.Principal.LogonType); else TaskService.RootFolder.RegisterTaskDefinition(Name, Definition); } /// Runs the registered task immediately. /// /// /// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters, /// call this method without any values (e.g. /// Run() /// ). /// /// /// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value, /// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in /// the action properties. /// /// /// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables, /// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties. /// /// /// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see /// Task Actions. /// /// /// A instance that defines the new instance of the task. /// /// /// /// /// public RunningTask Run(params string[] parameters) { if (v2Task != null) { if (parameters.Length > 32) throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 values is allowed."); if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260)) throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, all individual parameters must be less than 260 characters."); var irt = v2Task.Run(parameters.Length == 0 ? null : parameters); return irt != null ? new RunningTask(TaskService, v2Task, irt) : null; } v1Task.Run(); return new RunningTask(TaskService, v1Task); } /// Runs the registered task immediately using specified flags and a session identifier. /// Defines how the task is run. /// /// The terminal server session in which you want to start the task. /// /// If the value is not passed into the parameter, then the value /// specified in this parameter is ignored.If the value is passed into the flags parameter /// and the sessionID value is less than or equal to 0, then an invalid argument error will be returned. /// /// /// If the value is passed into the parameter and the sessionID /// value is a valid session ID greater than 0 and if no value is specified for the user parameter, then the Task Scheduler service /// will try to start the task interactively as the user who is logged on to the specified session. /// /// /// If the value is passed into the parameter and the sessionID /// value is a valid session ID greater than 0 and if a user is specified in the user parameter, then the Task Scheduler service /// will try to start the task interactively as the user who is specified in the user parameter. /// /// /// The user for which the task runs. /// /// /// The parameters used as values in the task actions. A maximum of 32 parameters can be supplied. To run a task with no parameters, /// call this method without any values (e.g. /// RunEx(0, 0, "MyUserName") /// ). /// /// /// The string values that you specify are paired with names and stored as name-value pairs. If you specify a single string value, /// then Arg0 will be the name assigned to the value. The value can be used in the task action where the $(Arg0) variable is used in /// the action properties. /// /// /// If you pass in values such as "0", "100", and "250" as an array of string values, then "0" will replace the $(Arg0) variables, /// "100" will replace the $(Arg1) variables, and "250" will replace the $(Arg2) variables used in the action properties. /// /// /// For more information and a list of action properties that can use $(Arg0), $(Arg1), ..., $(Arg32) variables in their values, see /// Task Actions. /// /// /// A instance that defines the new instance of the task. /// /// /// This method will return without error, but the task will not run if the AllowDemandStart property of ITaskSettings is set to /// false for the task. /// /// If RunEx is invoked from a disabled task, it will return null and the task will not be run. /// /// Not supported under Task Scheduler 1.0. /// /// /// /// /// public RunningTask RunEx(TaskRunFlags flags, int sessionID, string user, params string[] parameters) { if (v2Task == null) throw new NotV1SupportedException(); if (parameters == null || parameters.Any(s => s == null)) throw new ArgumentNullException(nameof(parameters), "The array and none of the values passed as parameters may be `null`."); if (parameters.Length > 32) throw new ArgumentOutOfRangeException(nameof(parameters), "A maximum of 32 parameters can be supplied to RunEx."); if (TaskService.HighestSupportedVersion < TaskServiceVersion.V1_5 && parameters.Any(p => (p?.Length ?? 0) >= 260)) throw new ArgumentOutOfRangeException(nameof(parameters), "On systems prior to Windows 10, no individual parameter may be more than 260 characters."); var irt = v2Task.RunEx(parameters.Length == 0 ? null : parameters, (int)flags, sessionID, user); return irt != null ? new RunningTask(TaskService, v2Task, irt) : null; } /// /// Applies access control list (ACL) entries described by a object to the file described by the current /// object. /// /// /// A object that describes an access control list (ACL) entry to apply to the current task. /// /// /// Give read access to all authenticated users for a task. /// /// /// /// public void SetAccessControl([NotNull] TaskSecurity taskSecurity) => taskSecurity.Persist(this); /// Sets the security descriptor for the task. Not available to Task Scheduler 1.0. /// The security descriptor for the task. /// Flags that specify how to set the security descriptor. /// Not supported under Task Scheduler 1.0. public void SetSecurityDescriptorSddlForm([NotNull] string sddlForm, TaskSetSecurityOptions options = TaskSetSecurityOptions.None) { if (v2Task != null) v2Task.SetSecurityDescriptor(sddlForm, (int)options); else throw new NotV1SupportedException(); } /// Dynamically tries to load the assembly for the editor and displays it as editable for this task. /// true if editor returns with OK response; false otherwise. /// /// The Microsoft.Win32.TaskSchedulerEditor.dll assembly must reside in the same directory as the Microsoft.Win32.TaskScheduler.dll /// or in the GAC. /// public bool ShowEditor() { try { var t = ReflectionHelper.LoadType("Microsoft.Win32.TaskScheduler.TaskEditDialog", "Microsoft.Win32.TaskSchedulerEditor.dll"); if (t != null) return ReflectionHelper.InvokeMethod(t, new object[] { this, true, true }, "ShowDialog") == 1; } catch { } return false; } /// Shows the property page for the task (v1.0 only). public void ShowPropertyPage() { if (v1Task != null) v1Task.EditWorkItem(IntPtr.Zero, 0); else throw new NotV2SupportedException(); } /// Stops the registered task immediately. /// /// The Stop method stops all instances of the task. /// /// System account users can stop a task, users with Administrator group privileges can stop a task, and if a user has rights to /// execute and read a task, then the user can stop the task. A user can stop the task instances that are running under the same /// credentials as the user account. In all other cases, the user is denied access to stop the task. /// /// public void Stop() { if (v2Task != null) v2Task.Stop(0); else v1Task.Terminate(); } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => Name; int IComparable.CompareTo(object other) => CompareTo(other as Task); 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; } /// /// Gets the ITaskDefinition for a V2 task and prevents the errors that come when connecting remotely to a higher version of the /// Task Scheduler. /// /// The local task service. /// The task instance. /// if set to true this method will throw an exception if unable to get the task definition. /// A valid ITaskDefinition that should not throw errors on the local instance. /// Unable to get a compatible task definition for this version of the library. 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; } /// Called when a property has changed to notify any attached elements. /// Name of the property. protected void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private class DefDoc { private readonly XmlDocument doc; 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); } } } } /// Contains information about the compatibility of the current configuration with a specified version. [PublicAPI] public class TaskCompatibilityEntry { internal TaskCompatibilityEntry(TaskCompatibility comp, string prop, string reason) { CompatibilityLevel = comp; Property = prop; Reason = reason; } /// Gets the compatibility level. /// The compatibility level. public TaskCompatibility CompatibilityLevel { get; } /// Gets the property name with the incompatibility. /// The property name. public string Property { get; } /// Gets the reason for the incompatibility. /// The reason. public string Reason { get; } } /// Defines all the components of a task, such as the task settings, triggers, actions, and registration information. [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; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets a collection of actions that are performed by the task. [XmlArrayItem(ElementName = "Exec", IsNullable = true, Type = typeof(ExecAction))] [XmlArrayItem(ElementName = "ShowMessage", IsNullable = true, Type = typeof(ShowMessageAction))] [XmlArrayItem(ElementName = "ComHandler", IsNullable = true, Type = typeof(ComHandlerAction))] [XmlArrayItem(ElementName = "SendEmail", IsNullable = true, Type = typeof(EmailAction))] [XmlArray] [NotNull, ItemNotNull] public ActionCollection Actions => actions ??= v2Def != null ? new ActionCollection(v2Def) : new ActionCollection(v1Task); /// /// Gets or sets the data that is associated with the task. This data is ignored by the Task Scheduler service, but is used by /// third-parties who wish to extend the task format. /// /// /// For V1 tasks, this library makes special use of the SetWorkItemData and GetWorkItemData methods and does not expose that data /// stream directly. Instead, it uses that data stream to hold a dictionary of properties that are not supported under V1, but can /// have values under V2. An example of this is the value which is stored in the data stream. /// /// The library does not provide direct access to the V1 work item data. If using V2 properties with a V1 task, programmatic access /// to the task using the native API will retrieve unreadable results from GetWorkItemData and will eliminate those property values /// if SetWorkItemData is used. /// /// [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(); } } /// Gets the lowest supported version that supports the settings for this . [XmlIgnore] public TaskCompatibility LowestSupportedVersion => GetLowestSupportedVersion(); /// Gets a collection of triggers that are used to start a task. [XmlArrayItem(ElementName = "BootTrigger", IsNullable = true, Type = typeof(BootTrigger))] [XmlArrayItem(ElementName = "CalendarTrigger", IsNullable = true, Type = typeof(CalendarTrigger))] [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); /// Gets or sets the XML-formatted definition of the task. [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(); } } /// Gets the principal for the task that provides the security credentials for the task. [NotNull] public TaskPrincipal Principal => principal ??= v2Def != null ? new TaskPrincipal(v2Def.Principal, () => XmlText) : new TaskPrincipal(v1Task); /// /// Gets a class instance of registration information that is used to describe a task, such as the description of the task, the /// author of the task, and the date the task is registered. /// public TaskRegistrationInfo RegistrationInfo => regInfo ??= v2Def != null ? new TaskRegistrationInfo(v2Def.RegistrationInfo) : new TaskRegistrationInfo(v1Task); /// Gets the settings that define how the Task Scheduler service performs the task. [NotNull] public TaskSettings Settings => settings ??= v2Def != null ? new TaskSettings(v2Def.Settings) : new TaskSettings(v1Task); /// Gets the XML Schema file for V1 tasks. /// The for V1 tasks. /// An object containing the XML Schema for V1 tasks. public static XmlSchemaComplexType GetV1SchemaFile([NotNull] XmlSchemaSet xs) { XmlSchema schema; using (var xsdFile = Assembly.GetAssembly(typeof(TaskDefinition)).GetManifestResourceStream("Microsoft.Win32.TaskScheduler.V1.TaskSchedulerV1Schema.xsd")) { var schemaSerializer = new XmlSerializer(typeof(XmlSchema)); schema = (XmlSchema)schemaSerializer.Deserialize(XmlReader.Create(xsdFile)); xs.Add(schema); } // target namespace var name = new XmlQualifiedName("taskType", tns); var productType = (XmlSchemaComplexType)schema.SchemaTypes[name]; return productType; } /// /// Determines whether this can use the Unified Scheduling Engine or if it contains unsupported properties. /// /// /// if set to true throws an with details about unsupported properties in the Data /// property of the exception. /// /// /// true if this can use the Unified Scheduling Engine; otherwise, false. public bool CanUseUnifiedSchedulingEngine(bool throwExceptionWithDetails = false, Version taskSchedulerVersion = null) { var tsVer = taskSchedulerVersion ?? TaskService.LibraryVersion; if (tsVer < TaskServiceVersion.V1_3) return false; var ex = new InvalidOperationException { HelpLink = "http://msdn.microsoft.com/en-us/library/windows/desktop/aa384138(v=vs.85).aspx" }; var bad = false; /*if (Principal.LogonType == TaskLogonType.InteractiveTokenOrPassword) { bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, "Principal.LogonType", "== TaskLogonType.InteractiveTokenOrPassword"); } if (Settings.MultipleInstances == TaskInstancesPolicy.StopExisting) { bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, "Settings.MultipleInstances", "== TaskInstancesPolicy.StopExisting"); }*/ if (Settings.NetworkSettings.Id != Guid.Empty && tsVer >= TaskServiceVersion.V1_5) { bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, "Settings.NetworkSettings.Id", "!= Guid.Empty"); } /*if (!Settings.AllowHardTerminate) { bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, "Settings.AllowHardTerminate", "== false"); }*/ if (!Actions.PowerShellConversion.IsFlagSet(PowerShellActionPlatformOption.Version2)) for (var i = 0; i < Actions.Count; i++) { var a = Actions[i]; switch (a) { case EmailAction _: bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Actions[{i}]", "== typeof(EmailAction)"); break; case ShowMessageAction _: bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Actions[{i}]", "== typeof(ShowMessageAction)"); break; } } if (tsVer == TaskServiceVersion.V1_3) for (var i = 0; i < Triggers.Count; i++) { Trigger t; try { t = Triggers[i]; } catch { if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable."); continue; } switch (t) { case MonthlyTrigger _: bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyTrigger)"); break; case MonthlyDOWTrigger _: bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}]", "== typeof(MonthlyDOWTrigger)"); break; /*case ICalendarTrigger _ when t.Repetition.IsSet(): bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}].Repetition", ""); break; case EventTrigger _ when ((EventTrigger)t).ValueQueries.Count > 0: bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}].ValueQueries.Count", "!= 0"); break;*/ } if (t.ExecutionTimeLimit != TimeSpan.Zero) { bad = true; if (!throwExceptionWithDetails) return false; TryAdd(ex.Data, $"Triggers[{i}].ExecutionTimeLimit", "!= TimeSpan.Zero"); } } if (bad && throwExceptionWithDetails) throw ex; return !bad; } /// Releases all resources used by this class. public void Dispose() { regInfo = null; triggers = null; settings = null; principal = null; actions = null; if (v2Def != null) Marshal.ReleaseComObject(v2Def); v1Task = null; } /// Validates the current . /// /// if set to true throw a with details about invalid properties. /// /// true if current is valid; false if not. public bool Validate(bool throwException = false) { var ex = new InvalidOperationException(); if (Settings.UseUnifiedSchedulingEngine) { try { CanUseUnifiedSchedulingEngine(throwException); } catch (InvalidOperationException iox) { foreach (DictionaryEntry kvp in iox.Data) TryAdd(ex.Data, (kvp.Key as ICloneable)?.Clone() ?? kvp.Key, (kvp.Value as ICloneable)?.Clone() ?? kvp.Value); } } if (Settings.Compatibility >= TaskCompatibility.V2_2) { var PT1D = TimeSpan.FromDays(1); if (Settings.MaintenanceSettings.IsSet() && (Settings.MaintenanceSettings.Period < PT1D || Settings.MaintenanceSettings.Deadline < PT1D || Settings.MaintenanceSettings.Deadline <= Settings.MaintenanceSettings.Period)) TryAdd(ex.Data, "Settings.MaintenanceSettings", "Period or Deadline must be at least 1 day and Deadline must be greater than Period."); } var list = new List(); if (GetLowestSupportedVersion(list) > Settings.Compatibility) foreach (var item in list) TryAdd(ex.Data, item.Property, item.Reason); var startWhenAvailable = Settings.StartWhenAvailable; var delOldTask = Settings.DeleteExpiredTaskAfter != TimeSpan.Zero; var v1 = Settings.Compatibility < TaskCompatibility.V2; var hasEndBound = false; for (var i = 0; i < Triggers.Count; i++) { Trigger trigger; try { trigger = Triggers[i]; } catch { TryAdd(ex.Data, $"Triggers[{i}]", "is irretrievable."); continue; } if (startWhenAvailable && trigger.Repetition.Duration != TimeSpan.Zero && trigger.EndBoundary == DateTime.MaxValue) TryAdd(ex.Data, "Settings.StartWhenAvailable", "== true requires time-based tasks with an end boundary or time-based tasks that are set to repeat infinitely."); if (v1 && trigger.Repetition.Interval != TimeSpan.Zero && trigger.Repetition.Interval >= trigger.Repetition.Duration) TryAdd(ex.Data, "Trigger.Repetition.Interval", ">= Trigger.Repetition.Duration under Task Scheduler 1.0."); if (trigger.EndBoundary <= trigger.StartBoundary) TryAdd(ex.Data, "Trigger.StartBoundary", ">= Trigger.EndBoundary is not allowed."); if (delOldTask && trigger.EndBoundary != DateTime.MaxValue) hasEndBound = true; } if (delOldTask && !hasEndBound) TryAdd(ex.Data, "Settings.DeleteExpiredTaskAfter", "!= TimeSpan.Zero requires at least one trigger with an end boundary."); if (throwException && ex.Data.Count > 0) throw ex; return ex.Data.Count == 0; } /// Implements the operator + for triggers on a definition, effectively adding the trigger to the definition. /// The definition to which the trigger is to be added. /// The trigger to add. /// The definition with the added trigger. public static TaskDefinition operator +(TaskDefinition definition, Trigger trigger) { definition.Triggers.Add(trigger); return definition; } /// Implements the operator + for actions on a definition, effectively adding the action to the definition. /// The definition to which the action is to be added. /// The action to add. /// The definition with the added action. public static TaskDefinition operator +(TaskDefinition definition, Action action) { definition.Actions.Add(action); return definition; } 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 GetV1TaskDataDictionary(ITask v1Task) { Dictionary dict; var o = GetV1TaskData(v1Task); if (o is string) dict = new Dictionary(2) { { "Data", o.ToString() }, { "Documentation", o.ToString() } }; else dict = o as Dictionary; return dict ?? new Dictionary(); } 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); } /// Gets the lowest supported version. /// The output list. /// private TaskCompatibility GetLowestSupportedVersion(IList outputList = null) { var res = TaskCompatibility.V1; var list = new List(); //if (Principal.DisplayName != null) // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.DisplayName", "cannot have a value.")); } if (Principal.GroupId != null) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.GroupId", "cannot have a value.")); } //this.Principal.Id != null || if (Principal.LogonType == TaskLogonType.Group || Principal.LogonType == TaskLogonType.None || Principal.LogonType == TaskLogonType.S4U) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.LogonType", "cannot be Group, None or S4U.")); } if (Principal.RunLevel == TaskRunLevel.Highest) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Principal.RunLevel", "cannot be set to Highest.")); } if (RegistrationInfo.SecurityDescriptorSddlForm != null) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.SecurityDescriptorSddlForm", "cannot have a value.")); } //if (RegistrationInfo.Source != null) // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Source", "cannot have a value.")); } //if (RegistrationInfo.URI != null) // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.URI", "cannot have a value.")); } //if (RegistrationInfo.Version != new Version(1, 0)) // { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "RegistrationInfo.Version", "cannot be set or equal 1.0.")); } if (Settings.AllowDemandStart == false) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowDemandStart", "must be true.")); } if (Settings.AllowHardTerminate == false) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.AllowHardTerminate", "must be true.")); } if (Settings.MultipleInstances != TaskInstancesPolicy.IgnoreNew) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.MultipleInstances", "must be set to IgnoreNew.")); } if (Settings.NetworkSettings.IsSet()) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.NetworkSetting", "cannot have a value.")); } if (Settings.RestartCount != 0) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartCount", "must be 0.")); } if (Settings.RestartInterval != TimeSpan.Zero) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.RestartInterval", "must be 0 (TimeSpan.Zero).")); } if (Settings.StartWhenAvailable) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Settings.StartWhenAvailable", "must be false.")); } if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version1) != PowerShellActionPlatformOption.Version1 && (Actions.ContainsType(typeof(EmailAction)) || Actions.ContainsType(typeof(ShowMessageAction)) || Actions.ContainsType(typeof(ComHandlerAction)))) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Actions", "may only contain ExecAction types unless Actions.PowerShellConversion includes Version1.")); } if ((Actions.PowerShellConversion & PowerShellActionPlatformOption.Version2) != PowerShellActionPlatformOption.Version2 && (Actions.ContainsType(typeof(EmailAction)) || Actions.ContainsType(typeof(ShowMessageAction)))) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Actions", "may only contain ExecAction and ComHanlderAction types unless Actions.PowerShellConversion includes Version2.")); } try { if (null != Triggers.Find(t => t is ITriggerDelay && ((ITriggerDelay)t).Delay != TimeSpan.Zero)) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain delays.")); } if (null != Triggers.Find(t => t.ExecutionTimeLimit != TimeSpan.Zero || t.Id != null)) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain an ExecutionTimeLimit or Id.")); } if (null != Triggers.Find(t => (t as LogonTrigger)?.UserId != null)) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a LogonTrigger with a UserId.")); } if (null != Triggers.Find(t => t is MonthlyDOWTrigger && ((MonthlyDOWTrigger)t).RunOnLastWeekOfMonth || t is MonthlyDOWTrigger && (((MonthlyDOWTrigger)t).WeeksOfMonth & (((MonthlyDOWTrigger)t).WeeksOfMonth - 1)) != 0)) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyDOWTrigger with RunOnLastWeekOfMonth set or multiple WeeksOfMonth.")); } if (null != Triggers.Find(t => t is MonthlyTrigger && ((MonthlyTrigger)t).RunOnLastDayOfMonth)) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain a MonthlyTrigger with RunOnLastDayOfMonth set.")); } if (Triggers.ContainsType(typeof(EventTrigger)) || Triggers.ContainsType(typeof(SessionStateChangeTrigger)) || Triggers.ContainsType(typeof(RegistrationTrigger))) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain EventTrigger, SessionStateChangeTrigger, or RegistrationTrigger types.")); } } catch { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2, "Triggers", "cannot contain Custom triggers.")); } if (Principal.ProcessTokenSidType != TaskProcessTokenSidType.Default) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.ProcessTokenSidType", "must be Default.")); } if (Principal.RequiredPrivileges.Count > 0) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Principal.RequiredPrivileges", "must be empty.")); } if (Settings.DisallowStartOnRemoteAppSession) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.DisallowStartOnRemoteAppSession", "must be false.")); } if (Settings.UseUnifiedSchedulingEngine) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_1, "Settings.UseUnifiedSchedulingEngine", "must be false.")); } if (Settings.MaintenanceSettings.IsSet()) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, "this.Settings.MaintenanceSettings", "must have no values set.")); } if (Settings.Volatile) { list.Add(new TaskCompatibilityEntry(TaskCompatibility.V2_2, " this.Settings.Volatile", "must be false.")); } foreach (var item in list) { if (res < item.CompatibilityLevel) res = item.CompatibilityLevel; outputList?.Add(item); } return res; } /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// Provides the security credentials for a principal. These security credentials define the security context for the tasks that are /// associated with the principal. /// [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 xmlFunc; private TaskPrincipalPrivileges reqPriv; private string v1CachedAcctInfo; private ITask v1Task; internal TaskPrincipal([NotNull] IPrincipal iPrincipal, Func 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; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Gets the account associated with this principal. This value is pulled from the TaskDefinition's XMLText property if set. /// /// The account. [DefaultValue(null), Browsable(false)] public string Account { 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; } } } /// Gets or sets the name of the principal that is displayed in the Task Scheduler UI. /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] public string DisplayName { get => v2Principal != null ? v2Principal.DisplayName : v1Task.GetDataItem("PrincipalDisplayName"); set { if (v2Principal != null) v2Principal.DisplayName = value; else v1Task.SetDataItem("PrincipalDisplayName", value); OnNotifyPropertyChanged(); } } /// /// Gets or sets the identifier of the user group that is required to run the tasks that are associated with the principal. Setting /// this property to something other than a null or empty string, will set the property to NULL and will set /// the property to TaskLogonType.Group; /// /// Not supported under Task Scheduler 1.0. [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(); } } /// Gets or sets the identifier of the principal. /// Not supported under Task Scheduler 1.0. [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(); } } /// Gets or sets the security logon method that is required to run the tasks that are associated with the principal. /// /// TaskLogonType values of Group, None, or S4UNot are not supported under Task Scheduler 1.0. /// [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(); } } /// Gets or sets the task process security identifier (SID) type. /// One of the enumeration constants. /// Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems. /// Not supported under Task Scheduler versions prior to 2.1. [XmlIgnore, DefaultValue(typeof(TaskProcessTokenSidType), "Default")] public TaskProcessTokenSidType ProcessTokenSidType { get => v2Principal2?.ProcessTokenSidType ?? TaskProcessTokenSidType.Default; set { if (v2Principal2 != null) v2Principal2.ProcessTokenSidType = value; else throw new NotSupportedPriorToException(TaskCompatibility.V2_1); OnNotifyPropertyChanged(); } } /// /// Gets the security credentials for a principal. These security credentials define the security context for the tasks that are /// associated with the principal. /// /// Setting this value appears to break the Task Scheduler MMC and does not output in XML. Removed to prevent problems. [XmlIgnore] public TaskPrincipalPrivileges RequiredPrivileges => reqPriv ??= new TaskPrincipalPrivileges(v2Principal2); /// /// Gets or sets the identifier that is used to specify the privilege level that is required to run the tasks that are associated /// with the principal. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TaskRunLevel), "LUA")] [XmlIgnore] public TaskRunLevel RunLevel { get => v2Principal?.RunLevel ?? TaskRunLevel.LUA; set { if (v2Principal != null) v2Principal.RunLevel = value; else if (value != TaskRunLevel.LUA) throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// /// Gets or sets the user identifier that is required to run the tasks that are associated with the principal. Setting this property /// to something other than a null or empty string, will set the property to NULL; /// [DefaultValue(null)] public string UserId { 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(); } } /// Validates the supplied account against the supplied . /// The user or group account name. /// The SID type for the process. /// true if supplied account can be used for the supplied SID type. public static bool ValidateAccountForSidType(string acct, TaskProcessTokenSidType sidType) { string[] validUserIds = { "NETWORK SERVICE", "LOCAL SERVICE", "S-1-5-19", "S-1-5-20" }; return sidType == TaskProcessTokenSidType.Default || Array.Find(validUserIds, id => id.Equals(acct, StringComparison.InvariantCultureIgnoreCase)) != null; } /// Releases all resources used by this class. public void Dispose() { if (v2Principal != null) Marshal.ReleaseComObject(v2Principal); v1Task = null; } /// Gets a value indicating whether current Principal settings require a password to be provided. /// true if settings requires a password to be provided; otherwise, false. public bool RequiresPassword() => LogonType == TaskLogonType.InteractiveTokenOrPassword || LogonType == TaskLogonType.Password || LogonType == TaskLogonType.S4U && UserId != null && string.Compare(UserId, WindowsIdentity.GetCurrent().Name, StringComparison.OrdinalIgnoreCase) != 0; /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => LogonType == TaskLogonType.Group ? GroupId : UserId; XmlSchema IXmlSerializable.GetSchema() => null; 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(); } /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } /// /// List of security credentials for a principal under version 1.3 of the Task Scheduler. These security credentials define the security /// context for the tasks that are associated with the principal. /// [PublicAPI] public sealed class TaskPrincipalPrivileges : IList { private readonly IPrincipal2 v2Principal2; internal TaskPrincipalPrivileges(IPrincipal2 iPrincipal2 = null) => v2Principal2 = iPrincipal2; /// Gets the number of elements contained in the . /// The number of elements contained in the . public int Count => v2Principal2?.RequiredPrivilegeCount ?? 0; /// Gets a value indicating whether the is read-only. /// true if the is read-only; otherwise, false. public bool IsReadOnly => false; /// Gets or sets the element at the specified index. /// The element at the specified index. /// is not a valid index in the . /// /// The property is set and the is read-only. /// 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(); } /// Adds an item to the . /// The object to add to the . /// The is read-only. public void Add(TaskPrincipalPrivilege item) { if (v2Principal2 != null) v2Principal2.AddRequiredPrivilege(item.ToString()); else throw new NotSupportedPriorToException(TaskCompatibility.V2_1); } /// Determines whether the contains a specific value. /// The object to locate in the . /// /// true if is found in the ; otherwise, false. /// public bool Contains(TaskPrincipalPrivilege item) => IndexOf(item) != -1; /// Copies to. /// The array. /// Index of the array. 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; } } /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => new TaskPrincipalPrivilegesEnumerator(v2Principal2); /// Determines the index of a specific item in the . /// The object to locate in the . /// The index of if found in the list; otherwise, -1. public int IndexOf(TaskPrincipalPrivilege item) { for (var i = 0; i < Count; i++) { if (item == this[i]) return i; } return -1; } /// Removes all items from the . /// The is read-only. void ICollection.Clear() => throw new NotImplementedException(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// Inserts an item to the at the specified index. /// The zero-based index at which should be inserted. /// The object to insert into the . /// is not a valid index in the . /// The is read-only. void IList.Insert(int index, TaskPrincipalPrivilege item) => throw new NotImplementedException(); /// Removes the first occurrence of a specific object from the . /// The object to remove from the . /// /// true if was successfully removed from the ; /// otherwise, false. This method also returns false if is not found in the original . /// /// The is read-only. bool ICollection.Remove(TaskPrincipalPrivilege item) => throw new NotImplementedException(); /// Removes the item at the specified index. /// The zero-based index of the item to remove. /// is not a valid index in the . /// The is read-only. void IList.RemoveAt(int index) => throw new NotImplementedException(); /// Enumerates the privileges set for a principal under version 1.3 of the Task Scheduler. public sealed class TaskPrincipalPrivilegesEnumerator : IEnumerator { private readonly IPrincipal2 v2Principal2; private int cur; internal TaskPrincipalPrivilegesEnumerator(IPrincipal2 iPrincipal2 = null) { v2Principal2 = iPrincipal2; Reset(); } /// Gets the element in the collection at the current position of the enumerator. /// The element in the collection at the current position of the enumerator. public TaskPrincipalPrivilege Current { get; private set; } object IEnumerator.Current => Current; /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { } /// Advances the enumerator to the next element of the collection. /// /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. /// /// The collection was modified after the enumerator was created. public bool MoveNext() { if (v2Principal2 != null && cur < v2Principal2.RequiredPrivilegeCount) { cur++; Current = (TaskPrincipalPrivilege)Enum.Parse(typeof(TaskPrincipalPrivilege), v2Principal2[cur]); return true; } Current = 0; return false; } /// Sets the enumerator to its initial position, which is before the first element in the collection. /// The collection was modified after the enumerator was created. public void Reset() { cur = 0; Current = 0; } } } /// /// Provides the administrative information that can be used to describe the task. This information includes details such as a /// description of the task, the author of the task, the date the task is registered, and the security descriptor of the task. /// [XmlRoot("RegistrationInfo", Namespace = TaskDefinition.tns, IsNullable = true)] [PublicAPI] public sealed class TaskRegistrationInfo : IDisposable, IXmlSerializable, INotifyPropertyChanged { private readonly IRegistrationInfo v2RegInfo; private ITask v1Task; internal TaskRegistrationInfo([NotNull] IRegistrationInfo iRegInfo) => v2RegInfo = iRegInfo; internal TaskRegistrationInfo([NotNull] ITask iTask) => v1Task = iTask; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets or sets the author of the task. [DefaultValue(null)] public string Author { get => v2RegInfo != null ? v2RegInfo.Author : v1Task.GetCreator(); set { if (v2RegInfo != null) v2RegInfo.Author = value; else v1Task.SetCreator(value); OnNotifyPropertyChanged(); } } /// Gets or sets the date and time when the task is registered. [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(); } } /// Gets or sets the description of the task. [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(); } } /// Gets or sets any additional documentation for the task. [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(); } } /// Gets or sets the security descriptor of the task. /// The security descriptor. [XmlIgnore] public GenericSecurityDescriptor SecurityDescriptor { get => new RawSecurityDescriptor(SecurityDescriptorSddlForm); set => SecurityDescriptorSddlForm = value?.GetSddlForm(Task.defaultAccessControlSections); } /// Gets or sets the security descriptor of the task. /// Not supported under Task Scheduler 1.0. [DefaultValue(null)] [XmlIgnore] public string SecurityDescriptorSddlForm { get { object sddl = null; if (v2RegInfo != null) sddl = v2RegInfo.SecurityDescriptor; return sddl?.ToString(); } set { if (v2RegInfo != null) v2RegInfo.SecurityDescriptor = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// /// Gets or sets where the task originated from. For example, a task may originate from a component, service, application, or user. /// [DefaultValue(null)] public string Source { get => v2RegInfo != null ? v2RegInfo.Source : v1Task.GetDataItem(nameof(Source)); set { if (v2RegInfo != null) v2RegInfo.Source = value; else v1Task.SetDataItem(nameof(Source), value); OnNotifyPropertyChanged(); } } /// Gets or sets the URI of the task. /// /// Note: Breaking change in version 2.0. This property was previously of type . It was found that in /// Windows 8, many of the native tasks use this property in a string format rather than in a URI format. /// [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(); } } /// Gets or sets the version number of the task. [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(); } } /// Gets or sets an XML-formatted version of the registration information for the task. [XmlIgnore] public string XmlText { get => v2RegInfo != null ? v2RegInfo.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this); set { if (v2RegInfo != null) v2RegInfo.XmlText = value; else XmlSerializationHelper.ReadObjectFromXmlText(value, this); OnNotifyPropertyChanged(); } } /// Releases all resources used by this class. public void Dispose() { v1Task = null; if (v2RegInfo != null) Marshal.ReleaseComObject(v2RegInfo); } /// Returns a that represents this instance. /// A that represents this instance. 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, "(?Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private bool ProcessVersionXml(PropertyInfo pi, object obj, ref object value) { if (pi.Name != "Version" || value == null) return false; if (value is Version) value = value.ToString(); else if (value is string) value = new Version(value.ToString()); return true; } } /// Provides the settings that the Task Scheduler service uses to perform the task. [XmlRoot("Settings", Namespace = TaskDefinition.tns, IsNullable = true)] [PublicAPI] public sealed class TaskSettings : IDisposable, IXmlSerializable, 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; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// /// Gets or sets a Boolean value that indicates that the task can be started by using either the Run command or the Context menu. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(true)] [XmlElement("AllowStartOnDemand")] [XmlIgnore] public bool AllowDemandStart { get => v2Settings == null || v2Settings.AllowDemandStart; set { if (v2Settings != null) v2Settings.AllowDemandStart = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the task may be terminated by using TerminateProcess. /// Not supported under Task Scheduler 1.0. [DefaultValue(true)] [XmlIgnore] public bool AllowHardTerminate { get => v2Settings == null || v2Settings.AllowHardTerminate; set { if (v2Settings != null) v2Settings.AllowHardTerminate = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Gets or sets an integer value that indicates which version of Task Scheduler a task is compatible with. /// Not supported under Task Scheduler 1.0. [XmlIgnore] public TaskCompatibility Compatibility { get => v2Settings?.Compatibility ?? TaskCompatibility.V1; set { if (v2Settings != null) v2Settings.Compatibility = value; else if (value != TaskCompatibility.V1) throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// /// Gets or sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. If no value is /// specified for this property, then the Task Scheduler service will not delete the task. /// /// /// Gets and sets the amount of time that the Task Scheduler will wait before deleting the task after it expires. A TimeSpan value /// of 1 second indicates the task is set to delete when done. A value of TimeSpan.Zero indicates that the task should not be deleted. /// /// /// A task expires after the end boundary has been exceeded for all triggers associated with the task. The end boundary for a /// trigger is specified by the EndBoundary property of all trigger types. /// [DefaultValue(typeof(TimeSpan), "12:00:00")] public TimeSpan DeleteExpiredTaskAfter { get { if (v2Settings != null) return v2Settings.DeleteExpiredTaskAfter == "PT0S" ? TimeSpan.FromSeconds(1) : Task.StringToTimeSpan(v2Settings.DeleteExpiredTaskAfter); return v1Task.HasFlags(TaskFlags.DeleteWhenDone) ? TimeSpan.FromSeconds(1) : TimeSpan.Zero; } set { if (v2Settings != null) v2Settings.DeleteExpiredTaskAfter = value == TimeSpan.FromSeconds(1) ? "PT0S" : Task.TimeSpanToString(value); else v1Task.SetFlags(TaskFlags.DeleteWhenDone, value >= TimeSpan.FromSeconds(1)); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the task will not be started if the computer is running on battery power. /// [DefaultValue(true)] public bool DisallowStartIfOnBatteries { get => v2Settings?.DisallowStartIfOnBatteries ?? v1Task.HasFlags(TaskFlags.DontStartIfOnBatteries); set { if (v2Settings != null) v2Settings.DisallowStartIfOnBatteries = value; else v1Task.SetFlags(TaskFlags.DontStartIfOnBatteries, value); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the task will not be started if the task is triggered to run in a Remote /// Applications Integrated Locally (RAIL) session. /// /// Property set for a task on a Task Scheduler version prior to 2.1. [DefaultValue(false)] [XmlIgnore] public bool DisallowStartOnRemoteAppSession { get { if (v2Settings2 != null) return v2Settings2.DisallowStartOnRemoteAppSession; if (v2Settings3 != null) return v2Settings3.DisallowStartOnRemoteAppSession; return false; } set { if (v2Settings2 != null) v2Settings2.DisallowStartOnRemoteAppSession = value; else if (v2Settings3 != null) v2Settings3.DisallowStartOnRemoteAppSession = value; else throw new NotSupportedPriorToException(TaskCompatibility.V2_1); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the task is enabled. The task can be performed only when this setting is TRUE. /// [DefaultValue(true)] public bool Enabled { get => v2Settings?.Enabled ?? !v1Task.HasFlags(TaskFlags.Disabled); set { if (v2Settings != null) v2Settings.Enabled = value; else v1Task.SetFlags(TaskFlags.Disabled, !value); OnNotifyPropertyChanged(); } } /// /// Gets or sets the amount of time that is allowed to complete the task. By default, a task will be stopped 72 hours after it /// starts to run. /// /// /// The amount of time that is allowed to complete the task. When this parameter is set to , the /// execution time limit is infinite. /// /// /// If a task is started on demand, the ExecutionTimeLimit setting is bypassed. Therefore, a task that is started on demand will not /// be terminated if it exceeds the ExecutionTimeLimit. /// [DefaultValue(typeof(TimeSpan), "3")] public TimeSpan ExecutionTimeLimit { get { if (v2Settings != null) return Task.StringToTimeSpan(v2Settings.ExecutionTimeLimit); var ms = v1Task.GetMaxRunTime(); return ms == InfiniteRunTimeV1 ? TimeSpan.Zero : TimeSpan.FromMilliseconds(ms); } set { if (v2Settings != null) v2Settings.ExecutionTimeLimit = value == TimeSpan.Zero ? "PT0S" : Task.TimeSpanToString(value); else { // Due to an issue introduced in Vista, and propagated to Windows 7, setting the MaxRunTime to INFINITE results in the // task only running for 72 hours. For these operating systems, setting the RunTime to "INFINITE - 1" gets the desired // behavior of allowing an "infinite" run of the task. var ms = value == TimeSpan.Zero ? InfiniteRunTimeV1 : Convert.ToUInt32(value.TotalMilliseconds); v1Task.SetMaxRunTime(ms); if (value == TimeSpan.Zero && v1Task.GetMaxRunTime() != InfiniteRunTimeV1) v1Task.SetMaxRunTime(InfiniteRunTimeV1 - 1); } OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the task will not be visible in the UI by default. [DefaultValue(false)] public bool Hidden { get => v2Settings?.Hidden ?? v1Task.HasFlags(TaskFlags.Hidden); set { if (v2Settings != null) v2Settings.Hidden = value; else v1Task.SetFlags(TaskFlags.Hidden, value); OnNotifyPropertyChanged(); } } /// Gets or sets the information that the Task Scheduler uses during Automatic maintenance. [XmlIgnore] [NotNull] public MaintenanceSettings MaintenanceSettings => maintenanceSettings ??= new MaintenanceSettings(v2Settings3); /// Gets or sets the policy that defines how the Task Scheduler handles multiple instances of the task. /// Not supported under Task Scheduler 1.0. [DefaultValue(typeof(TaskInstancesPolicy), "IgnoreNew")] [XmlIgnore] public TaskInstancesPolicy MultipleInstances { get => v2Settings?.MultipleInstances ?? TaskInstancesPolicy.IgnoreNew; set { if (v2Settings != null) v2Settings.MultipleInstances = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Gets or sets the priority level of the task. /// The priority. /// Value set to AboveNormal or BelowNormal on Task Scheduler 1.0. [DefaultValue(typeof(ProcessPriorityClass), "Normal")] public ProcessPriorityClass Priority { get => v2Settings != null ? GetPriorityFromInt(v2Settings.Priority) : (ProcessPriorityClass)v1Task.GetPriority(); set { if (v2Settings != null) { v2Settings.Priority = GetPriorityAsInt(value); } else { if (value == ProcessPriorityClass.AboveNormal || value == ProcessPriorityClass.BelowNormal) throw new NotV1SupportedException("Unsupported priority level on Task Scheduler 1.0."); v1Task.SetPriority((uint)value); } OnNotifyPropertyChanged(); } } /// Gets or sets the number of times that the Task Scheduler will attempt to restart the task. /// /// The number of times that the Task Scheduler will attempt to restart the task. If this property is set, the property must also be set. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(0)] [XmlIgnore] public int RestartCount { get => v2Settings?.RestartCount ?? 0; set { if (v2Settings != null) v2Settings.RestartCount = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Gets or sets a value that specifies how long the Task Scheduler will attempt to restart the task. /// /// A value that specifies how long the Task Scheduler will attempt to restart the task. If this property is set, the property must also be set. The maximum time allowed is 31 days, and the minimum time allowed is 1 minute. /// /// Not supported under Task Scheduler 1.0. [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(); } } /// /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the computer is in an idle condition. /// [DefaultValue(false)] public bool RunOnlyIfIdle { get => v2Settings?.RunOnlyIfIdle ?? v1Task.HasFlags(TaskFlags.StartOnlyIfIdle); set { if (v2Settings != null) v2Settings.RunOnlyIfIdle = value; else v1Task.SetFlags(TaskFlags.StartOnlyIfIdle, value); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only if the user is logged on (v1.0 only) /// /// Property set for a task on a Task Scheduler version other than 1.0. [XmlIgnore] public bool RunOnlyIfLoggedOn { get => v2Settings != null || v1Task.HasFlags(TaskFlags.RunOnlyIfLoggedOn); set { if (v1Task != null) v1Task.SetFlags(TaskFlags.RunOnlyIfLoggedOn, value); else if (v2Settings != null) throw new NotV2SupportedException("Task Scheduler 2.0 (1.2) does not support setting this property. You must use an InteractiveToken in order to have the task run in the current user session."); OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the Task Scheduler will run the task only when a network is available. [DefaultValue(false)] public bool RunOnlyIfNetworkAvailable { get => v2Settings?.RunOnlyIfNetworkAvailable ?? v1Task.HasFlags(TaskFlags.RunIfConnectedToInternet); set { if (v2Settings != null) v2Settings.RunOnlyIfNetworkAvailable = value; else v1Task.SetFlags(TaskFlags.RunIfConnectedToInternet, value); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the Task Scheduler can start the task at any time after its scheduled time has passed. /// /// Not supported under Task Scheduler 1.0. [DefaultValue(false)] [XmlIgnore] public bool StartWhenAvailable { get => v2Settings != null && v2Settings.StartWhenAvailable; set { if (v2Settings != null) v2Settings.StartWhenAvailable = value; else throw new NotV1SupportedException(); OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the task will be stopped if the computer switches to battery power. [DefaultValue(true)] public bool StopIfGoingOnBatteries { get => v2Settings?.StopIfGoingOnBatteries ?? v1Task.HasFlags(TaskFlags.KillIfGoingOnBatteries); set { if (v2Settings != null) v2Settings.StopIfGoingOnBatteries = value; else v1Task.SetFlags(TaskFlags.KillIfGoingOnBatteries, value); OnNotifyPropertyChanged(); } } /// Gets or sets a Boolean value that indicates that the Unified Scheduling Engine will be utilized to run this task. /// Property set for a task on a Task Scheduler version prior to 2.1. [DefaultValue(false)] [XmlIgnore] public bool UseUnifiedSchedulingEngine { get { if (v2Settings2 != null) return v2Settings2.UseUnifiedSchedulingEngine; if (v2Settings3 != null) return v2Settings3.UseUnifiedSchedulingEngine; return false; } set { if (v2Settings2 != null) v2Settings2.UseUnifiedSchedulingEngine = value; else if (v2Settings3 != null) v2Settings3.UseUnifiedSchedulingEngine = value; else throw new NotSupportedPriorToException(TaskCompatibility.V2_1); OnNotifyPropertyChanged(); } } /// Gets or sets a boolean value that indicates whether the task is automatically disabled every time Windows starts. /// Property set for a task on a Task Scheduler version prior to 2.2. [DefaultValue(false)] [XmlIgnore] public bool Volatile { get => v2Settings3 != null && v2Settings3.Volatile; set { if (v2Settings3 != null) v2Settings3.Volatile = value; else throw new NotSupportedPriorToException(TaskCompatibility.V2_2); OnNotifyPropertyChanged(); } } /// /// Gets or sets a Boolean value that indicates that the Task Scheduler will wake the computer when it is time to run the task. /// [DefaultValue(false)] public bool WakeToRun { get => v2Settings?.WakeToRun ?? v1Task.HasFlags(TaskFlags.SystemRequired); set { if (v2Settings != null) v2Settings.WakeToRun = value; else v1Task.SetFlags(TaskFlags.SystemRequired, value); OnNotifyPropertyChanged(); } } /// Gets or sets an XML-formatted definition of the task settings. [XmlIgnore] public string XmlText { get => v2Settings != null ? v2Settings.XmlText : XmlSerializationHelper.WriteObjectToXmlText(this); set { if (v2Settings != null) v2Settings.XmlText = value; else XmlSerializationHelper.ReadObjectFromXmlText(value, this); OnNotifyPropertyChanged(); } } /// /// Gets or sets the information that specifies how the Task Scheduler performs tasks when the computer is in an idle state. /// [NotNull] public IdleSettings IdleSettings => idleSettings ??= v2Settings != null ? new IdleSettings(v2Settings.IdleSettings) : new IdleSettings(v1Task); /// /// Gets or sets the network settings object that contains a network profile identifier and name. If the RunOnlyIfNetworkAvailable /// property of ITaskSettings is true and a network profile is specified in the NetworkSettings property, then the task will run /// only if the specified network profile is available. /// [XmlIgnore] [NotNull] public NetworkSettings NetworkSettings => networkSettings ??= new NetworkSettings(v2Settings?.NetworkSettings); /// Releases all resources used by this class. public void Dispose() { if (v2Settings != null) Marshal.ReleaseComObject(v2Settings); idleSettings = null; networkSettings = null; v1Task = null; } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() { if (v2Settings != null || v1Task != null) return DebugHelper.GetDebugString(this); return base.ToString(); } 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; } } /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } internal static class DebugHelper { [System.Diagnostics.CodeAnalysis.SuppressMessage("Language", "CSE0003:Use expression-bodied members", Justification = "")] 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 + "'."); } } } }