FlashPatcher/FlashPatcher/TaskService/TaskService.cs

1163 lines
46 KiB
C#

using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Win32.TaskScheduler
{
/// <summary>
/// Quick simple trigger types for the
/// <see cref="TaskService.AddTask(string,Microsoft.Win32.TaskScheduler.Trigger,Microsoft.Win32.TaskScheduler.Action,string,string,Microsoft.Win32.TaskScheduler.TaskLogonType,string)"/> method.
/// </summary>
public enum QuickTriggerType
{
/// <summary>At boot.</summary>
Boot,
/// <summary>On system idle.</summary>
Idle,
/// <summary>At logon of any user.</summary>
Logon,
/// <summary>When the task is registered.</summary>
TaskRegistration,
/// <summary>Hourly, starting now.</summary>
Hourly,
/// <summary>Daily, starting now.</summary>
Daily,
/// <summary>Weekly, starting now.</summary>
Weekly,
/// <summary>Monthly, starting now.</summary>
Monthly
}
/// <summary>
/// Known versions of the native Task Scheduler library. This can be used as a decoder for the
/// <see cref="TaskService.HighestSupportedVersion"/> and <see cref="TaskService.LibraryVersion"/> values.
/// </summary>
public static class TaskServiceVersion
{
/// <summary>Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).</summary>
[Description("Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).")]
public static readonly Version V1_1 = new Version(1, 1);
/// <summary>Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).</summary>
[Description("Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).")]
public static readonly Version V1_2 = new Version(1, 2);
/// <summary>Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).</summary>
[Description("Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).")]
public static readonly Version V1_3 = new Version(1, 3);
/// <summary>Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).</summary>
[Description("Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).")]
public static readonly Version V1_4 = new Version(1, 4);
/// <summary>Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).</summary>
[Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).")]
public static readonly Version V1_5 = new Version(1, 5);
/// <summary>Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703).</summary>
[Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703).")]
public static readonly Version V1_6 = new Version(1, 6);
}
/// <summary>Provides access to the Task Scheduler service for managing registered tasks.</summary>
[Description("Provides access to the Task Scheduler service.")]
[ToolboxItem(true), Serializable]
public sealed partial class TaskService : Component, ISupportInitialize, System.Runtime.Serialization.ISerializable
{
internal static readonly bool LibraryIsV2 = Environment.OSVersion.Version.Major >= 6;
internal static readonly Guid PowerShellActionGuid = new Guid("dab4c1e3-cd12-46f1-96fc-3981143c9bab");
private static Guid CLSID_Ctask = typeof(V1Interop.CTask).GUID;
private static Guid IID_ITask = typeof(V1Interop.ITask).GUID;
[ThreadStatic]
private static TaskService instance;
private static Version osLibVer;
internal V1Interop.ITaskScheduler v1TaskScheduler;
internal V2Interop.ITaskService v2TaskService;
private bool connecting;
private bool forceV1;
private bool initializing;
private Version maxVer;
private bool maxVerSet;
private string targetServer;
private bool targetServerSet;
private string userDomain;
private bool userDomainSet;
private string userName;
private bool userNameSet;
private string userPassword;
private bool userPasswordSet;
private WindowsImpersonatedIdentity v1Impersonation;
/// <summary>Creates a new instance of a TaskService connecting to the local machine as the current user.</summary>
public TaskService()
{
ResetHighestSupportedVersion();
Connect();
}
/// <summary>Initializes a new instance of the <see cref="TaskService"/> class.</summary>
/// <param name="targetServer">
/// The name of the computer that you want to connect to. If the this parameter is empty, then this will connect to the local computer.
/// </param>
/// <param name="userName">
/// The user name that is used during the connection to the computer. If the user is not specified, then the current token is used.
/// </param>
/// <param name="accountDomain">The domain of the user specified in the <paramref name="userName"/> parameter.</param>
/// <param name="password">
/// The password that is used to connect to the computer. If the user name and password are not specified, then the current token is used.
/// </param>
/// <param name="forceV1">If set to <c>true</c> force Task Scheduler 1.0 compatibility.</param>
public TaskService(string targetServer, string userName = null, string accountDomain = null, string password = null, bool forceV1 = false)
{
BeginInit();
TargetServer = targetServer;
UserName = userName;
UserAccountDomain = accountDomain;
UserPassword = password;
this.forceV1 = forceV1;
ResetHighestSupportedVersion();
EndInit();
}
private TaskService([NotNull] System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
{
BeginInit();
TargetServer = (string)info.GetValue("TargetServer", typeof(string));
UserName = (string)info.GetValue("UserName", typeof(string));
UserAccountDomain = (string)info.GetValue("UserAccountDomain", typeof(string));
UserPassword = (string)info.GetValue("UserPassword", typeof(string));
forceV1 = (bool)info.GetValue("forceV1", typeof(bool));
ResetHighestSupportedVersion();
EndInit();
}
/// <summary>Delegate for methods that support update calls during COM handler execution.</summary>
/// <param name="percentage">The percentage of completion (0 to 100).</param>
/// <param name="message">An optional message.</param>
public delegate void ComHandlerUpdate(short percentage, string message);
/// <summary>Occurs when the Task Scheduler is connected to the local or remote target.</summary>
public event EventHandler ServiceConnected;
/// <summary>Occurs when the Task Scheduler is disconnected from the local or remote target.</summary>
public event EventHandler ServiceDisconnected;
/// <summary>Gets a local instance of the <see cref="TaskService"/> using the current user's credentials.</summary>
/// <value>Local user <see cref="TaskService"/> instance.</value>
public static TaskService Instance
{
get
{
if (instance is null)
{
instance = new TaskService();
instance.ServiceDisconnected += Instance_ServiceDisconnected;
}
return instance;
}
}
/// <summary>
/// Gets the library version. This is the highest version supported by the local library. Tasks cannot be created using any
/// compatibility level higher than this version.
/// </summary>
/// <value>The library version.</value>
/// <remarks>
/// The following table list the various versions and their host operating system:
/// <list type="table">
/// <listheader>
/// <term>Version</term>
/// <term>Operating System</term>
/// </listheader>
/// <item>
/// <term>1.1</term>
/// <term>Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).</term>
/// </item>
/// <item>
/// <term>1.2</term>
/// <term>Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).</term>
/// </item>
/// <item>
/// <term>1.3</term>
/// <term>Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).</term>
/// </item>
/// <item>
/// <term>1.4</term>
/// <term>Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).</term>
/// </item>
/// <item>
/// <term>1.5</term>
/// <term>Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).</term>
/// </item>
/// <item>
/// <term>1.6</term>
/// <term>Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703).</term>
/// </item>
/// </list>
/// </remarks>
[Browsable(false)]
public static Version LibraryVersion { get; } = Instance.HighestSupportedVersion;
/// <summary>
/// Gets or sets a value indicating whether to allow tasks from later OS versions with new properties to be retrieved as read only tasks.
/// </summary>
/// <value><c>true</c> if allow read only tasks; otherwise, <c>false</c>.</value>
[DefaultValue(false), Category("Behavior"), Description("Allow tasks from later OS versions with new properties to be retrieved as read only tasks.")]
public bool AllowReadOnlyTasks { get; set; }
/// <summary>Gets the name of the domain to which the <see cref="TargetServer"/> computer is connected.</summary>
[Browsable(false)]
[DefaultValue(null)]
[Obsolete("This property has been superseded by the UserAccountDomin property and may not be available in future releases.")]
public string ConnectedDomain
{
get
{
if (v2TaskService != null)
return v2TaskService.ConnectedDomain;
var parts = v1Impersonation.Name.Split('\\');
if (parts.Length == 2)
return parts[0];
return string.Empty;
}
}
/// <summary>Gets the name of the user that is connected to the Task Scheduler service.</summary>
[Browsable(false)]
[DefaultValue(null)]
[Obsolete("This property has been superseded by the UserName property and may not be available in future releases.")]
public string ConnectedUser
{
get
{
if (v2TaskService != null)
return v2TaskService.ConnectedUser;
var parts = v1Impersonation.Name.Split('\\');
if (parts.Length == 2)
return parts[1];
return parts[0];
}
}
/// <summary>Gets the highest version of Task Scheduler that a computer supports.</summary>
/// <remarks>
/// The following table list the various versions and their host operating system:
/// <list type="table">
/// <listheader>
/// <term>Version</term>
/// <term>Operating System</term>
/// </listheader>
/// <item>
/// <term>1.1</term>
/// <term>Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).</term>
/// </item>
/// <item>
/// <term>1.2</term>
/// <term>Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).</term>
/// </item>
/// <item>
/// <term>1.3</term>
/// <term>Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).</term>
/// </item>
/// <item>
/// <term>1.4</term>
/// <term>Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).</term>
/// </item>
/// <item>
/// <term>1.5</term>
/// <term>Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).</term>
/// </item>
/// <item>
/// <term>1.6</term>
/// <term>Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703).</term>
/// </item>
/// </list>
/// </remarks>
[Category("Data"), TypeConverter(typeof(VersionConverter)), Description("Highest version of library that should be used.")]
public Version HighestSupportedVersion
{
get => maxVer;
set
{
if (value > GetLibraryVersionFromLocalOS())
throw new ArgumentOutOfRangeException(nameof(HighestSupportedVersion), @"The value of HighestSupportedVersion cannot exceed that of the underlying Windows version library.");
maxVer = value;
maxVerSet = true;
var localForceV1 = value <= TaskServiceVersion.V1_1;
if (localForceV1 == forceV1) return;
forceV1 = localForceV1;
Connect();
}
}
/// <summary>Gets the root ("\") folder. For Task Scheduler 1.0, this is the only folder.</summary>
[Browsable(false)]
public TaskFolder RootFolder => GetFolder(TaskFolder.rootString);
/// <summary>Gets or sets the name of the computer that is running the Task Scheduler service that the user is connected to.</summary>
[Category("Data"), DefaultValue(null), Description("The name of the computer to connect to.")]
public string TargetServer
{
get => ShouldSerializeTargetServer() ? targetServer : null;
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, targetServer, StringComparison.OrdinalIgnoreCase) != 0)
{
targetServerSet = true;
targetServer = value;
Connect();
}
}
}
/// <summary>Gets or sets the user account domain to be used when connecting to the <see cref="TargetServer"/>.</summary>
/// <value>The user account domain.</value>
[Category("Data"), DefaultValue(null), Description("The user account domain to be used when connecting.")]
public string UserAccountDomain
{
get => ShouldSerializeUserAccountDomain() ? userDomain : null;
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, userDomain, StringComparison.OrdinalIgnoreCase) != 0)
{
userDomainSet = true;
userDomain = value;
Connect();
}
}
}
/// <summary>Gets or sets the user name to be used when connecting to the <see cref="TargetServer"/>.</summary>
/// <value>The user name.</value>
[Category("Data"), DefaultValue(null), Description("The user name to be used when connecting.")]
public string UserName
{
get => ShouldSerializeUserName() ? userName : null;
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, userName, StringComparison.OrdinalIgnoreCase) != 0)
{
userNameSet = true;
userName = value;
Connect();
}
}
}
/// <summary>Gets or sets the user password to be used when connecting to the <see cref="TargetServer"/>.</summary>
/// <value>The user password.</value>
[Category("Data"), DefaultValue(null), Description("The user password to be used when connecting.")]
public string UserPassword
{
get => userPassword;
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.CompareOrdinal(value, userPassword) != 0)
{
userPasswordSet = true;
userPassword = value;
Connect();
}
}
}
/// <summary>Gets a <see cref="System.Collections.Generic.IEnumerator{T}"/> which enumerates all the tasks in all folders.</summary>
/// <value>A <see cref="System.Collections.Generic.IEnumerator{T}"/> for all <see cref="Task"/> instances.</value>
[Browsable(false)]
public System.Collections.Generic.IEnumerable<Task> AllTasks => RootFolder.AllTasks;
/// <summary>Gets a Boolean value that indicates if you are connected to the Task Scheduler service.</summary>
[Browsable(false)]
public bool Connected => v2TaskService != null && v2TaskService.Connected || v1TaskScheduler != null;
/// <summary>
/// Gets the connection token for this <see cref="TaskService"/> instance. This token is thread safe and can be used to create new
/// <see cref="TaskService"/> instances on other threads using the <see cref="CreateFromToken"/> static method.
/// </summary>
/// <value>The connection token.</value>
public ConnectionToken Token =>
ConnectionDataManager.TokenFromInstance(TargetServer, UserName, UserAccountDomain, UserPassword, forceV1);
/// <summary>Gets a value indicating whether the component can raise an event.</summary>
protected override bool CanRaiseEvents { get; } = false;
/// <summary>
/// Creates a new <see cref="TaskService"/> instance from a token. Given that a TaskService instance is thread specific, this is the
/// preferred method for multi-thread creation or asynchronous method parameters.
/// </summary>
/// <param name="token">The token.</param>
/// <returns>A <see cref="TaskService"/> instance valid for the thread calling this method.</returns>
public static TaskService CreateFromToken(ConnectionToken token) => ConnectionDataManager.InstanceFromToken(token);
/// <summary>Gets a formatted string that tells the Task Scheduler to retrieve a string from a resource .dll file.</summary>
/// <param name="dllPath">The path to the .dll file that contains the resource.</param>
/// <param name="resourceId">The identifier for the resource text (typically a negative number).</param>
/// <returns>A string in the format of $(@ [dllPath], [resourceId]).</returns>
/// <example>
/// For example, the setting this property value to $(@ %SystemRoot%\System32\ResourceName.dll, -101) will set the property to the
/// value of the resource text with an identifier equal to -101 in the %SystemRoot%\System32\ResourceName.dll file.
/// </example>
public static string GetDllResourceString([NotNull] string dllPath, int resourceId) => $"$(@ {dllPath}, {resourceId})";
/// <summary>
/// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the
/// <see cref="ITaskHandler"/> interface.
/// </summary>
/// <param name="clsid">The CLSID of the COM object.</param>
/// <param name="data">An optional string passed to the COM object at startup.</param>
/// <param name="millisecondsTimeout">The number of milliseconds to wait or -1 for indefinitely.</param>
/// <param name="onUpdate">
/// An optional <see cref="ComHandlerUpdate"/> delegate that is called when the COM object calls the
/// <see cref="ITaskHandlerStatus.UpdateStatus(short, string)"/> method.
/// </param>
/// <returns>The value set by the COM object via a call to the <see cref="ITaskHandlerStatus.TaskCompleted(int)"/> method.</returns>
public static int RunComHandlerAction(Guid clsid, string data = null, int millisecondsTimeout = -1, ComHandlerUpdate onUpdate = null)
{
var thread = new ComHandlerThread(clsid, data, millisecondsTimeout, onUpdate, null);
thread.Start().Join();
return thread.ReturnCode;
}
/// <summary>
/// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the
/// <see cref="ITaskHandler"/> interface.
/// </summary>
/// <param name="clsid">The CLSID of the COM object.</param>
/// <param name="onComplete">The action to run on thread completion.</param>
/// <param name="data">An optional string passed to the COM object at startup.</param>
/// <param name="millisecondsTimeout">The number of milliseconds to wait or -1 for indefinitely.</param>
/// <param name="onUpdate">
/// An optional <see cref="ComHandlerUpdate"/> delegate that is called when the COM object calls the
/// <see cref="ITaskHandlerStatus.UpdateStatus(short, string)"/> method.
/// </param>
public static void RunComHandlerActionAsync(Guid clsid, Action<int> onComplete, string data = null, int millisecondsTimeout = -1, ComHandlerUpdate onUpdate = null) => new ComHandlerThread(clsid, data, millisecondsTimeout, onUpdate, onComplete).Start();
/// <summary>Adds or updates an Automatic Maintenance Task on the connected machine.</summary>
/// <param name="taskPathAndName">Name of the task with full path.</param>
/// <param name="period">The amount of time the task needs once executed during regular Automatic maintenance.</param>
/// <param name="deadline">
/// 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.
/// </param>
/// <param name="executablePath">The path to an executable file.</param>
/// <param name="arguments">The arguments associated with the command-line operation.</param>
/// <param name="workingDirectory">
/// The directory that contains either the executable file or the files that are used by the executable file.
/// </param>
/// <returns>A <see cref="Task"/> instance of the Automatic Maintenance Task.</returns>
/// <exception cref="System.InvalidOperationException">
/// Automatic Maintenance tasks are only supported on Windows 8/Server 2012 and later.
/// </exception>
public Task AddAutomaticMaintenanceTask([NotNull] string taskPathAndName, TimeSpan period, TimeSpan deadline, string executablePath, string arguments = null, string workingDirectory = null)
{
if (HighestSupportedVersion.Minor < 4)
throw new InvalidOperationException("Automatic Maintenance tasks are only supported on Windows 8/Server 2012 and later.");
var td = NewTask();
td.Settings.UseUnifiedSchedulingEngine = true;
td.Settings.MaintenanceSettings.Period = period;
td.Settings.MaintenanceSettings.Deadline = deadline;
td.Actions.Add(executablePath, arguments, workingDirectory);
// The task needs to grant explicit FRFX to LOCAL SERVICE (A;;FRFX;;;LS)
return RootFolder.RegisterTaskDefinition(taskPathAndName, td, TaskCreation.CreateOrUpdate, null, null, TaskLogonType.InteractiveToken, "D:P(A;;FA;;;BA)(A;;FA;;;SY)(A;;FRFX;;;LS)");
}
/// <summary>Creates a new task, registers the task, and returns the instance.</summary>
/// <param name="path">
/// The task name. If this value is NULL, the task will be registered in the root task folder and the task name will be a GUID value
/// that is created by the Task Scheduler service. A task name cannot begin or end with a space character. The '.' character cannot
/// be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path.
/// </param>
/// <param name="trigger">The <see cref="Trigger"/> to determine when to run the task.</param>
/// <param name="action">The <see cref="Action"/> to determine what happens when the task is triggered.</param>
/// <param name="userId">The user credentials used to register the task.</param>
/// <param name="password">The password for the userId used to register the task.</param>
/// <param name="logonType">
/// A <see cref="TaskLogonType"/> value that defines what logon technique is used to run the registered task.
/// </param>
/// <param name="description">The task description.</param>
/// <returns>A <see cref="Task"/> instance of the registered task.</returns>
/// <remarks>
/// This method is shorthand for creating a new TaskDescription, adding a trigger and action, and then registering it in the root folder.
/// </remarks>
/// <example>
/// <code lang="cs">
/// <![CDATA[
/// // Display a log file every other day
/// TaskService.Instance.AddTask("Test", new DailyTrigger { DaysInterval = 2 }, new ExecAction("notepad.exe", "c:\\test.log", null));
/// ]]>
/// </code>
/// </example>
public Task AddTask([NotNull] string path, [NotNull] Trigger trigger, [NotNull] Action action, string userId = null, string password = null, TaskLogonType logonType = TaskLogonType.InteractiveToken, string description = null)
{
var td = NewTask();
if (!string.IsNullOrEmpty(description))
td.RegistrationInfo.Description = description;
// Create a trigger that will fire the task at a specific date and time
td.Triggers.Add(trigger);
// Create an action that will launch Notepad whenever the trigger fires
td.Actions.Add(action);
// Register the task in the root folder
return RootFolder.RegisterTaskDefinition(path, td, TaskCreation.CreateOrUpdate, userId, password, logonType);
}
/// <summary>Creates a new task, registers the task, and returns the instance.</summary>
/// <param name="path">
/// The task name. If this value is NULL, the task will be registered in the root task folder and the task name will be a GUID value
/// that is created by the Task Scheduler service. A task name cannot begin or end with a space character. The '.' character cannot
/// be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path.
/// </param>
/// <param name="trigger">The <see cref="Trigger"/> to determine when to run the task.</param>
/// <param name="exePath">The executable path.</param>
/// <param name="arguments">The arguments (optional). Value can be NULL.</param>
/// <param name="userId">The user credentials used to register the task.</param>
/// <param name="password">The password for the userId used to register the task.</param>
/// <param name="logonType">
/// A <see cref="TaskLogonType"/> value that defines what logon technique is used to run the registered task.
/// </param>
/// <param name="description">The task description.</param>
/// <returns>A <see cref="Task"/> instance of the registered task.</returns>
/// <example>
/// <code lang="cs">
/// <![CDATA[
/// // Display a log file every day
/// TaskService.Instance.AddTask("Test", QuickTriggerType.Daily, "notepad.exe", "c:\\test.log"));
/// ]]>
/// </code>
/// </example>
public Task AddTask([NotNull] string path, QuickTriggerType trigger, [NotNull] string exePath, string arguments = null, string userId = null, string password = null, TaskLogonType logonType = TaskLogonType.InteractiveToken, string description = null)
{
// Create a trigger based on quick trigger
Trigger newTrigger;
switch (trigger)
{
case QuickTriggerType.Boot:
newTrigger = new BootTrigger();
break;
case QuickTriggerType.Idle:
newTrigger = new IdleTrigger();
break;
case QuickTriggerType.Logon:
newTrigger = new LogonTrigger();
break;
case QuickTriggerType.TaskRegistration:
newTrigger = new RegistrationTrigger();
break;
case QuickTriggerType.Hourly:
newTrigger = new DailyTrigger { Repetition = new RepetitionPattern(TimeSpan.FromHours(1), TimeSpan.FromDays(1)) };
break;
case QuickTriggerType.Daily:
newTrigger = new DailyTrigger();
break;
case QuickTriggerType.Weekly:
newTrigger = new WeeklyTrigger();
break;
case QuickTriggerType.Monthly:
newTrigger = new MonthlyTrigger();
break;
default:
throw new ArgumentOutOfRangeException(nameof(trigger), trigger, null);
}
return AddTask(path, newTrigger, new ExecAction(exePath, arguments), userId, password, logonType, description);
}
/// <summary>Signals the object that initialization is starting.</summary>
public void BeginInit() => initializing = true;
/// <summary>Signals the object that initialization is complete.</summary>
public void EndInit()
{
initializing = false;
Connect();
}
/// <summary>Determines whether the specified <see cref="System.Object"/>, is equal to this instance.</summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns><c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.</returns>
public override bool Equals(object obj)
{
var tsobj = obj as TaskService;
if (tsobj != null)
return tsobj.TargetServer == TargetServer && tsobj.UserAccountDomain == UserAccountDomain && tsobj.UserName == UserName && tsobj.UserPassword == UserPassword && tsobj.forceV1 == forceV1;
return base.Equals(obj);
}
/// <summary>Finds all tasks matching a name or standard wildcards.</summary>
/// <param name="name">Name of the task in regular expression form.</param>
/// <param name="searchAllFolders">if set to <c>true</c> search all sub folders.</param>
/// <returns>An array of <see cref="Task"/> containing all tasks matching <paramref name="name"/>.</returns>
public Task[] FindAllTasks(System.Text.RegularExpressions.Regex name, bool searchAllFolders = true)
{
var results = new System.Collections.Generic.List<Task>();
FindTaskInFolder(RootFolder, name, ref results, searchAllFolders);
return results.ToArray();
}
/// <summary>Finds all tasks matching a name or standard wildcards.</summary>
/// <param name="filter">The filter used to determine tasks to select.</param>
/// <param name="searchAllFolders">if set to <c>true</c> search all sub folders.</param>
/// <returns>An array of <see cref="Task"/> containing all tasks matching <paramref name="filter"/>.</returns>
public Task[] FindAllTasks(Predicate<Task> filter, bool searchAllFolders = true)
{
if (filter == null) filter = t => true;
var results = new System.Collections.Generic.List<Task>();
FindTaskInFolder(RootFolder, filter, ref results, searchAllFolders);
return results.ToArray();
}
/// <summary>Finds a task given a name and standard wildcards.</summary>
/// <param name="name">The task name. This can include the wildcards * or ?.</param>
/// <param name="searchAllFolders">if set to <c>true</c> search all sub folders.</param>
/// <returns>A <see cref="Task"/> if one matches <paramref name="name"/>, otherwise NULL.</returns>
public Task FindTask([NotNull] string name, bool searchAllFolders = true)
{
var results = FindAllTasks(new Wildcard(name), searchAllFolders);
if (results.Length > 0)
return results[0];
return null;
}
/// <summary>Gets the event log for this <see cref="TaskService"/> instance.</summary>
/// <param name="taskPath">(Optional) The task path if only the events for a single task are desired.</param>
/// <returns>A <see cref="TaskEventLog"/> instance.</returns>
public TaskEventLog GetEventLog(string taskPath = null) => new TaskEventLog(TargetServer, taskPath, UserAccountDomain, UserName, UserPassword);
/// <summary>Gets the path to a folder of registered tasks.</summary>
/// <param name="folderName">
/// The path to the folder to retrieve. Do not use a backslash following the last folder name in the path. The root task folder is
/// specified with a backslash (\). An example of a task folder path, under the root task folder, is \MyTaskFolder. The '.' character
/// cannot be used to specify the current task folder and the '..' characters cannot be used to specify the parent task folder in the path.
/// </param>
/// <returns><see cref="TaskFolder"/> instance for the requested folder or <c>null</c> if <paramref name="folderName"/> was unrecognized.</returns>
/// <exception cref="NotV1SupportedException">
/// Folder other than the root (\) was requested on a system not supporting Task Scheduler 2.0.
/// </exception>
public TaskFolder GetFolder(string folderName)
{
TaskFolder f = null;
if (v2TaskService != null)
{
if (string.IsNullOrEmpty(folderName)) folderName = TaskFolder.rootString;
try
{
var ifld = v2TaskService.GetFolder(folderName);
if (ifld != null)
f = new TaskFolder(this, ifld);
}
catch (System.IO.DirectoryNotFoundException) { }
catch (System.IO.FileNotFoundException) { }
}
else if (folderName == TaskFolder.rootString || string.IsNullOrEmpty(folderName))
f = new TaskFolder(this);
else
throw new NotV1SupportedException("Folder other than the root (\\) was requested on a system only supporting Task Scheduler 1.0.");
return f;
}
/// <summary>Returns a hash code for this instance.</summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
public override int GetHashCode() => new { A = TargetServer, B = UserAccountDomain, C = UserName, D = UserPassword, E = forceV1 }.GetHashCode();
/// <summary>Gets a collection of running tasks.</summary>
/// <param name="includeHidden">True to include hidden tasks.</param>
/// <returns><see cref="RunningTaskCollection"/> instance with the list of running tasks.</returns>
public RunningTaskCollection GetRunningTasks(bool includeHidden = true)
{
if (v2TaskService != null)
try
{
return new RunningTaskCollection(this, v2TaskService.GetRunningTasks(includeHidden ? 1 : 0));
}
catch { }
return new RunningTaskCollection(this);
}
/// <summary>Gets the task with the specified path.</summary>
/// <param name="taskPath">The task path.</param>
/// <returns>
/// The <see cref="Task"/> instance matching the <paramref name="taskPath"/>, if found. If not found, this method returns <c>null</c>.
/// </returns>
public Task GetTask([NotNull] string taskPath)
{
Task t = null;
if (v2TaskService != null)
{
var iTask = GetTask(v2TaskService, taskPath);
if (iTask != null)
t = Task.CreateTask(this, iTask);
}
else
{
taskPath = System.IO.Path.GetFileNameWithoutExtension(taskPath);
var iTask = GetTask(v1TaskScheduler, taskPath);
if (iTask != null)
t = new Task(this, iTask);
}
return t;
}
/// <summary>
/// Returns an empty task definition object to be filled in with settings and properties and then registered using the
/// <see cref="TaskFolder.RegisterTaskDefinition(string, TaskDefinition)"/> method.
/// </summary>
/// <returns>A <see cref="TaskDefinition"/> instance for setting properties.</returns>
public TaskDefinition NewTask()
{
if (v2TaskService != null)
return new TaskDefinition(v2TaskService.NewTask(0));
var v1Name = "Temp" + Guid.NewGuid().ToString("B");
return new TaskDefinition(v1TaskScheduler.NewWorkItem(v1Name, CLSID_Ctask, IID_ITask), v1Name);
}
/// <summary>Returns a <see cref="TaskDefinition"/> populated with the properties defined in an XML file.</summary>
/// <param name="xmlFile">The XML file to use as input.</param>
/// <returns>A <see cref="TaskDefinition"/> instance.</returns>
/// <exception cref="NotV1SupportedException">Importing from an XML file is only supported under Task Scheduler 2.0.</exception>
public TaskDefinition NewTaskFromFile([NotNull] string xmlFile)
{
var td = NewTask();
td.XmlText = System.IO.File.ReadAllText(xmlFile);
return td;
}
/// <summary>Starts the Task Scheduler UI for the OS hosting the assembly if the session is running in interactive mode.</summary>
public void StartSystemTaskSchedulerManager()
{
if (Environment.UserInteractive)
System.Diagnostics.Process.Start("control.exe", "schedtasks");
}
[System.Security.SecurityCritical]
void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
{
info.AddValue("TargetServer", TargetServer, typeof(string));
info.AddValue("UserName", UserName, typeof(string));
info.AddValue("UserAccountDomain", UserAccountDomain, typeof(string));
info.AddValue("UserPassword", UserPassword, typeof(string));
info.AddValue("forceV1", forceV1, typeof(bool));
}
internal static V2Interop.IRegisteredTask GetTask([NotNull] V2Interop.ITaskService iSvc, [NotNull] string name)
{
V2Interop.ITaskFolder fld = null;
try
{
fld = iSvc.GetFolder("\\");
return fld.GetTask(name);
}
catch
{
return null;
}
finally
{
if (fld != null) Marshal.ReleaseComObject(fld);
}
}
internal static V1Interop.ITask GetTask([NotNull] V1Interop.ITaskScheduler iSvc, [NotNull] string name)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof(name));
try
{
return iSvc.Activate(name, IID_ITask);
}
catch (UnauthorizedAccessException)
{
// TODO: Take ownership of the file and try again
throw;
}
catch (ArgumentException)
{
return iSvc.Activate(name + ".job", IID_ITask);
}
catch (FileNotFoundException)
{
return null;
}
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.ComponentModel.Component"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (v2TaskService != null)
{
try
{
Marshal.ReleaseComObject(v2TaskService);
}
catch { }
v2TaskService = null;
}
if (v1TaskScheduler != null)
{
try
{
Marshal.ReleaseComObject(v1TaskScheduler);
}
catch { }
v1TaskScheduler = null;
}
if (v1Impersonation != null)
{
v1Impersonation.Dispose();
v1Impersonation = null;
}
if (!connecting)
ServiceDisconnected?.Invoke(this, EventArgs.Empty);
base.Dispose(disposing);
}
private static Version GetLibraryVersionFromLocalOS()
{
if (osLibVer == null)
{
if (Environment.OSVersion.Version.Major < 6)
osLibVer = TaskServiceVersion.V1_1;
else
{
if (Environment.OSVersion.Version.Minor == 0)
osLibVer = TaskServiceVersion.V1_2;
else if (Environment.OSVersion.Version.Minor == 1)
osLibVer = TaskServiceVersion.V1_3;
else
{
try
{
var fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(Path.Combine(Environment.SystemDirectory, "taskschd.dll"));
if (fvi.FileBuildPart > 9600 && fvi.FileBuildPart <= 14393)
osLibVer = TaskServiceVersion.V1_5;
else if (fvi.FileBuildPart >= 15063)
osLibVer = TaskServiceVersion.V1_6;
else // fvi.FileBuildPart <= 9600
osLibVer = TaskServiceVersion.V1_4;
}
catch { /* ignored */ };
}
}
if (osLibVer == null)
throw new NotSupportedException(@"The Task Scheduler library version for this system cannot be determined.");
}
return osLibVer;
}
private static void Instance_ServiceDisconnected(object sender, EventArgs e) => instance?.Connect();
/// <summary>Connects this instance of the <see cref="TaskService"/> class to a running Task Scheduler.</summary>
private void Connect()
{
ResetUnsetProperties();
if (!initializing && !DesignMode)
{
if (!string.IsNullOrEmpty(userDomain) && !string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(userPassword) || string.IsNullOrEmpty(userDomain) && string.IsNullOrEmpty(userName) && string.IsNullOrEmpty(userPassword))
{
// Clear stuff if already connected
connecting = true;
Dispose(true);
if (LibraryIsV2 && !forceV1)
{
v2TaskService = new V2Interop.ITaskService();
if (!string.IsNullOrEmpty(targetServer))
{
// Check to ensure character only server name. (Suggested by bigsan)
if (targetServer.StartsWith(@"\"))
targetServer = targetServer.TrimStart('\\');
// Make sure null is provided for local machine to compensate for a native library oddity (Found by ctrollen)
if (targetServer.Equals(Environment.MachineName, StringComparison.CurrentCultureIgnoreCase))
targetServer = null;
}
else
targetServer = null;
v2TaskService.Connect(targetServer, userName, userDomain, userPassword);
targetServer = v2TaskService.TargetServer;
userName = v2TaskService.ConnectedUser;
userDomain = v2TaskService.ConnectedDomain;
maxVer = GetV2Version();
}
else
{
v1Impersonation = new WindowsImpersonatedIdentity(userName, userDomain, userPassword);
v1TaskScheduler = new V1Interop.ITaskScheduler();
if (!string.IsNullOrEmpty(targetServer))
{
// Check to ensure UNC format for server name. (Suggested by bigsan)
if (!targetServer.StartsWith(@"\\"))
targetServer = @"\\" + targetServer;
}
else
targetServer = null;
v1TaskScheduler.SetTargetComputer(targetServer);
targetServer = v1TaskScheduler.GetTargetComputer();
maxVer = TaskServiceVersion.V1_1;
}
ServiceConnected?.Invoke(this, EventArgs.Empty);
connecting = false;
}
else
{
throw new ArgumentException("A username, password, and domain must be provided.");
}
}
}
/// <summary>Finds the task in folder.</summary>
/// <param name="fld">The folder.</param>
/// <param name="taskName">The wildcard expression to compare task names with.</param>
/// <param name="results">The results.</param>
/// <param name="recurse">if set to <c>true</c> recurse folders.</param>
/// <returns>True if any tasks are found, False if not.</returns>
private bool FindTaskInFolder([NotNull] TaskFolder fld, System.Text.RegularExpressions.Regex taskName, ref System.Collections.Generic.List<Task> results, bool recurse = true)
{
results.AddRange(fld.GetTasks(taskName));
if (recurse)
{
foreach (var f in fld.SubFolders)
{
if (FindTaskInFolder(f, taskName, ref results))
return true;
}
}
return false;
}
/// <summary>Finds the task in folder.</summary>
/// <param name="fld">The folder.</param>
/// <param name="filter">The filter to use when looking for tasks.</param>
/// <param name="results">The results.</param>
/// <param name="recurse">if set to <c>true</c> recurse folders.</param>
/// <returns>True if any tasks are found, False if not.</returns>
private bool FindTaskInFolder([NotNull] TaskFolder fld, Predicate<Task> filter, ref System.Collections.Generic.List<Task> results, bool recurse = true)
{
foreach (var t in fld.GetTasks())
try
{
if (filter(t))
results.Add(t);
}
catch
{
System.Diagnostics.Debug.WriteLine($"Unable to evaluate filter for task '{t.Path}'.");
}
if (recurse)
{
foreach (var f in fld.SubFolders)
{
if (FindTaskInFolder(f, filter, ref results))
return true;
}
}
return false;
}
private Version GetV2Version()
{
var v = v2TaskService.HighestVersion;
return new Version((int)(v >> 16), (int)(v & 0x0000FFFF));
}
private void ResetHighestSupportedVersion() => maxVer = Connected ? (v2TaskService != null ? GetV2Version() : TaskServiceVersion.V1_1) : GetLibraryVersionFromLocalOS();
private void ResetUnsetProperties()
{
if (!maxVerSet) ResetHighestSupportedVersion();
if (!targetServerSet) targetServer = null;
if (!userDomainSet) userDomain = null;
if (!userNameSet) userName = null;
if (!userPasswordSet) userPassword = null;
}
private bool ShouldSerializeHighestSupportedVersion() => LibraryIsV2 && maxVer <= TaskServiceVersion.V1_1;
private bool ShouldSerializeTargetServer() => targetServer != null && !targetServer.Trim('\\').Equals(Environment.MachineName.Trim('\\'), StringComparison.InvariantCultureIgnoreCase);
private bool ShouldSerializeUserAccountDomain() => userDomain != null && !userDomain.Equals(Environment.UserDomainName, StringComparison.InvariantCultureIgnoreCase);
private bool ShouldSerializeUserName() => userName != null && !userName.Equals(Environment.UserName, StringComparison.InvariantCultureIgnoreCase);
/// <summary>
/// Represents a valid, connected session to a Task Scheduler instance. This token is thread-safe and should be the means of passing
/// information about a <see cref="TaskService"/> between threads.
/// </summary>
public struct ConnectionToken
{
internal int token;
internal ConnectionToken(int value) => token = value;
}
// Manages the list of tokens and associated data
private static class ConnectionDataManager
{
public static List<ConnectionData> connections = new List<ConnectionData>() { new ConnectionData(null) };
public static TaskService InstanceFromToken(ConnectionToken token)
{
ConnectionData data;
lock (connections)
{
data = connections[token.token < connections.Count ? token.token : 0];
}
return new TaskService(data.TargetServer, data.UserName, data.UserAccountDomain, data.UserPassword, data.ForceV1);
}
public static ConnectionToken TokenFromInstance(string targetServer, string userName = null,
string accountDomain = null, string password = null, bool forceV1 = false)
{
lock (connections)
{
var newData = new ConnectionData(targetServer, userName, accountDomain, password, forceV1);
for (var i = 0; i < connections.Count; i++)
{
if (connections[i].Equals(newData))
return new ConnectionToken(i);
}
connections.Add(newData);
return new ConnectionToken(connections.Count - 1);
}
}
}
private class ComHandlerThread
{
public int ReturnCode;
private readonly System.Threading.AutoResetEvent completed = new System.Threading.AutoResetEvent(false);
private readonly string Data;
private readonly Type objType;
private readonly TaskHandlerStatus status;
private readonly int Timeout;
public ComHandlerThread(Guid clsid, string data, int millisecondsTimeout, ComHandlerUpdate onUpdate, Action<int> onComplete)
{
objType = Type.GetTypeFromCLSID(clsid, true);
Data = data;
Timeout = millisecondsTimeout;
status = new TaskHandlerStatus(i =>
{
completed.Set();
onComplete?.Invoke(i);
}, onUpdate);
}
public System.Threading.Thread Start()
{
var t = new System.Threading.Thread(ThreadProc);
t.Start();
return t;
}
private void ThreadProc()
{
completed.Reset();
object obj = null;
try { obj = Activator.CreateInstance(objType); } catch { }
if (obj == null) return;
ITaskHandler taskHandler = null;
try { taskHandler = (ITaskHandler)obj; } catch { }
try
{
if (taskHandler != null)
{
taskHandler.Start(status, Data);
completed.WaitOne(Timeout);
taskHandler.Stop(out ReturnCode);
}
}
finally
{
if (taskHandler != null)
Marshal.ReleaseComObject(taskHandler);
Marshal.ReleaseComObject(obj);
}
}
private class TaskHandlerStatus : ITaskHandlerStatus
{
private readonly Action<int> OnCompleted;
private readonly ComHandlerUpdate OnUpdate;
public TaskHandlerStatus(Action<int> onCompleted, ComHandlerUpdate onUpdate)
{
OnCompleted = onCompleted;
OnUpdate = onUpdate;
}
public void TaskCompleted([In, MarshalAs(UnmanagedType.Error)] int taskErrCode) => OnCompleted?.Invoke(taskErrCode);
public void UpdateStatus([In] short percentComplete, [In, MarshalAs(UnmanagedType.BStr)] string statusMessage) => OnUpdate?.Invoke(percentComplete, statusMessage);
}
}
// This private class holds information needed to create a new TaskService instance
private class ConnectionData : IEquatable<ConnectionData>
{
public bool ForceV1;
public string TargetServer, UserAccountDomain, UserName, UserPassword;
public ConnectionData(string targetServer, string userName = null, string accountDomain = null, string password = null, bool forceV1 = false)
{
TargetServer = targetServer;
UserAccountDomain = accountDomain;
UserName = userName;
UserPassword = password;
ForceV1 = forceV1;
}
public bool Equals(ConnectionData other) => string.Equals(TargetServer, other.TargetServer, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(UserAccountDomain, other.UserAccountDomain, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(UserName, other.UserName, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(UserPassword, other.UserPassword, StringComparison.InvariantCultureIgnoreCase) &&
ForceV1 == other.ForceV1;
}
private class VersionConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
return true;
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
var s = value as string;
return s != null ? new Version(s) : base.ConvertFrom(context, culture, value);
}
}
}
}