3760 lines
146 KiB
C#
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 + "'.");
|
|
}
|
|
}
|
|
}
|
|
} |