FlashPatcher/FlashPatcher/TaskService/Task.cs

3760 lines
146 KiB
C#

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