using JetBrains.Annotations;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
namespace Microsoft.Win32.TaskScheduler
{
///
/// Quick simple trigger types for the
/// method.
///
public enum QuickTriggerType
{
/// At boot.
Boot,
/// On system idle.
Idle,
/// At logon of any user.
Logon,
/// When the task is registered.
TaskRegistration,
/// Hourly, starting now.
Hourly,
/// Daily, starting now.
Daily,
/// Weekly, starting now.
Weekly,
/// Monthly, starting now.
Monthly
}
///
/// Known versions of the native Task Scheduler library. This can be used as a decoder for the
/// and values.
///
public static class TaskServiceVersion
{
/// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).
[Description("Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).")]
public static readonly Version V1_1 = new Version(1, 1);
/// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).
[Description("Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).")]
public static readonly Version V1_2 = new Version(1, 2);
/// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).
[Description("Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).")]
public static readonly Version V1_3 = new Version(1, 3);
/// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).
[Description("Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).")]
public static readonly Version V1_4 = new Version(1, 4);
/// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).
[Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).")]
public static readonly Version V1_5 = new Version(1, 5);
/// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703).
[Description("Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016 post build 1703).")]
public static readonly Version V1_6 = new Version(1, 6);
}
/// Provides access to the Task Scheduler service for managing registered tasks.
[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;
/// Creates a new instance of a TaskService connecting to the local machine as the current user.
public TaskService()
{
ResetHighestSupportedVersion();
Connect();
}
/// Initializes a new instance of the class.
///
/// 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.
///
///
/// The user name that is used during the connection to the computer. If the user is not specified, then the current token is used.
///
/// The domain of the user specified in the parameter.
///
/// 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.
///
/// If set to true force Task Scheduler 1.0 compatibility.
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();
}
/// Delegate for methods that support update calls during COM handler execution.
/// The percentage of completion (0 to 100).
/// An optional message.
public delegate void ComHandlerUpdate(short percentage, string message);
/// Occurs when the Task Scheduler is connected to the local or remote target.
public event EventHandler ServiceConnected;
/// Occurs when the Task Scheduler is disconnected from the local or remote target.
public event EventHandler ServiceDisconnected;
/// Gets a local instance of the using the current user's credentials.
/// Local user instance.
public static TaskService Instance
{
get
{
if (instance is null)
{
instance = new TaskService();
instance.ServiceDisconnected += Instance_ServiceDisconnected;
}
return instance;
}
}
///
/// 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.
///
/// The library version.
///
/// The following table list the various versions and their host operating system:
///
///
/// Version
/// Operating System
///
/// -
/// 1.1
/// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).
///
/// -
/// 1.2
/// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).
///
/// -
/// 1.3
/// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).
///
/// -
/// 1.4
/// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).
///
/// -
/// 1.5
/// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).
///
/// -
/// 1.6
/// Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703).
///
///
///
[Browsable(false)]
public static Version LibraryVersion { get; } = Instance.HighestSupportedVersion;
///
/// Gets or sets a value indicating whether to allow tasks from later OS versions with new properties to be retrieved as read only tasks.
///
/// true if allow read only tasks; otherwise, false.
[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; }
/// Gets the name of the domain to which the computer is connected.
[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;
}
}
/// Gets the name of the user that is connected to the Task Scheduler service.
[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];
}
}
/// Gets the highest version of Task Scheduler that a computer supports.
///
/// The following table list the various versions and their host operating system:
///
///
/// Version
/// Operating System
///
/// -
/// 1.1
/// Task Scheduler 1.0 (Windows Server™ 2003, Windows® XP, or Windows® 2000).
///
/// -
/// 1.2
/// Task Scheduler 2.0 (Windows Vista™, Windows Server™ 2008).
///
/// -
/// 1.3
/// Task Scheduler 2.1 (Windows® 7, Windows Server™ 2008 R2).
///
/// -
/// 1.4
/// Task Scheduler 2.2 (Windows® 8.x, Windows Server™ 2012).
///
/// -
/// 1.5
/// Task Scheduler 2.3 (Windows® 10, Windows Server™ 2016).
///
/// -
/// 1.6
/// Task Scheduler 2.4 (Windows® 10 Version 1703, Windows Server™ 2016 Version 1703).
///
///
///
[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();
}
}
/// Gets the root ("\") folder. For Task Scheduler 1.0, this is the only folder.
[Browsable(false)]
public TaskFolder RootFolder => GetFolder(TaskFolder.rootString);
/// Gets or sets the name of the computer that is running the Task Scheduler service that the user is connected to.
[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();
}
}
}
/// Gets or sets the user account domain to be used when connecting to the .
/// The user account domain.
[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();
}
}
}
/// Gets or sets the user name to be used when connecting to the .
/// The user name.
[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();
}
}
}
/// Gets or sets the user password to be used when connecting to the .
/// The user password.
[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();
}
}
}
/// Gets a which enumerates all the tasks in all folders.
/// A for all instances.
[Browsable(false)]
public System.Collections.Generic.IEnumerable AllTasks => RootFolder.AllTasks;
/// Gets a Boolean value that indicates if you are connected to the Task Scheduler service.
[Browsable(false)]
public bool Connected => v2TaskService != null && v2TaskService.Connected || v1TaskScheduler != null;
///
/// Gets the connection token for this instance. This token is thread safe and can be used to create new
/// instances on other threads using the static method.
///
/// The connection token.
public ConnectionToken Token =>
ConnectionDataManager.TokenFromInstance(TargetServer, UserName, UserAccountDomain, UserPassword, forceV1);
/// Gets a value indicating whether the component can raise an event.
protected override bool CanRaiseEvents { get; } = false;
///
/// Creates a new 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.
///
/// The token.
/// A instance valid for the thread calling this method.
public static TaskService CreateFromToken(ConnectionToken token) => ConnectionDataManager.InstanceFromToken(token);
/// Gets a formatted string that tells the Task Scheduler to retrieve a string from a resource .dll file.
/// The path to the .dll file that contains the resource.
/// The identifier for the resource text (typically a negative number).
/// A string in the format of $(@ [dllPath], [resourceId]).
///
/// 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.
///
public static string GetDllResourceString([NotNull] string dllPath, int resourceId) => $"$(@ {dllPath}, {resourceId})";
///
/// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the
/// interface.
///
/// The CLSID of the COM object.
/// An optional string passed to the COM object at startup.
/// The number of milliseconds to wait or -1 for indefinitely.
///
/// An optional delegate that is called when the COM object calls the
/// method.
///
/// The value set by the COM object via a call to the method.
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;
}
///
/// Runs an action that is defined via a COM handler. COM CLSID must be registered to an object that implements the
/// interface.
///
/// The CLSID of the COM object.
/// The action to run on thread completion.
/// An optional string passed to the COM object at startup.
/// The number of milliseconds to wait or -1 for indefinitely.
///
/// An optional delegate that is called when the COM object calls the
/// method.
///
public static void RunComHandlerActionAsync(Guid clsid, Action onComplete, string data = null, int millisecondsTimeout = -1, ComHandlerUpdate onUpdate = null) => new ComHandlerThread(clsid, data, millisecondsTimeout, onUpdate, onComplete).Start();
/// Adds or updates an Automatic Maintenance Task on the connected machine.
/// Name of the task with full path.
/// The amount of time the task needs once executed during regular Automatic maintenance.
///
/// 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 path to an executable file.
/// The arguments associated with the command-line operation.
///
/// The directory that contains either the executable file or the files that are used by the executable file.
///
/// A instance of the Automatic Maintenance Task.
///
/// Automatic Maintenance tasks are only supported on Windows 8/Server 2012 and later.
///
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)");
}
/// Creates a new task, registers the task, and returns the instance.
///
/// 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.
///
/// The to determine when to run the task.
/// The to determine what happens when the task is triggered.
/// The user credentials used to register the task.
/// The password for the userId used to register the task.
///
/// A value that defines what logon technique is used to run the registered task.
///
/// The task description.
/// A instance of the registered task.
///
/// This method is shorthand for creating a new TaskDescription, adding a trigger and action, and then registering it in the root folder.
///
///
///
///
///
///
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);
}
/// Creates a new task, registers the task, and returns the instance.
///
/// 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.
///
/// The to determine when to run the task.
/// The executable path.
/// The arguments (optional). Value can be NULL.
/// The user credentials used to register the task.
/// The password for the userId used to register the task.
///
/// A value that defines what logon technique is used to run the registered task.
///
/// The task description.
/// A instance of the registered task.
///
///
///
///
///
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);
}
/// Signals the object that initialization is starting.
public void BeginInit() => initializing = true;
/// Signals the object that initialization is complete.
public void EndInit()
{
initializing = false;
Connect();
}
/// Determines whether the specified , is equal to this instance.
/// The to compare with this instance.
/// true if the specified is equal to this instance; otherwise, false.
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);
}
/// Finds all tasks matching a name or standard wildcards.
/// Name of the task in regular expression form.
/// if set to true search all sub folders.
/// An array of containing all tasks matching .
public Task[] FindAllTasks(System.Text.RegularExpressions.Regex name, bool searchAllFolders = true)
{
var results = new System.Collections.Generic.List();
FindTaskInFolder(RootFolder, name, ref results, searchAllFolders);
return results.ToArray();
}
/// Finds all tasks matching a name or standard wildcards.
/// The filter used to determine tasks to select.
/// if set to true search all sub folders.
/// An array of containing all tasks matching .
public Task[] FindAllTasks(Predicate filter, bool searchAllFolders = true)
{
if (filter == null) filter = t => true;
var results = new System.Collections.Generic.List();
FindTaskInFolder(RootFolder, filter, ref results, searchAllFolders);
return results.ToArray();
}
/// Finds a task given a name and standard wildcards.
/// The task name. This can include the wildcards * or ?.
/// if set to true search all sub folders.
/// A if one matches , otherwise NULL.
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;
}
/// Gets the event log for this instance.
/// (Optional) The task path if only the events for a single task are desired.
/// A instance.
public TaskEventLog GetEventLog(string taskPath = null) => new TaskEventLog(TargetServer, taskPath, UserAccountDomain, UserName, UserPassword);
/// Gets the path to a folder of registered tasks.
///
/// 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.
///
/// instance for the requested folder or null if was unrecognized.
///
/// Folder other than the root (\) was requested on a system not supporting Task Scheduler 2.0.
///
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;
}
/// Returns a hash code for this instance.
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
public override int GetHashCode() => new { A = TargetServer, B = UserAccountDomain, C = UserName, D = UserPassword, E = forceV1 }.GetHashCode();
/// Gets a collection of running tasks.
/// True to include hidden tasks.
/// instance with the list of running tasks.
public RunningTaskCollection GetRunningTasks(bool includeHidden = true)
{
if (v2TaskService != null)
try
{
return new RunningTaskCollection(this, v2TaskService.GetRunningTasks(includeHidden ? 1 : 0));
}
catch { }
return new RunningTaskCollection(this);
}
/// Gets the task with the specified path.
/// The task path.
///
/// The instance matching the , if found. If not found, this method returns null.
///
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;
}
///
/// Returns an empty task definition object to be filled in with settings and properties and then registered using the
/// method.
///
/// A instance for setting properties.
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);
}
/// Returns a populated with the properties defined in an XML file.
/// The XML file to use as input.
/// A instance.
/// Importing from an XML file is only supported under Task Scheduler 2.0.
public TaskDefinition NewTaskFromFile([NotNull] string xmlFile)
{
var td = NewTask();
td.XmlText = System.IO.File.ReadAllText(xmlFile);
return td;
}
/// Starts the Task Scheduler UI for the OS hosting the assembly if the session is running in interactive mode.
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;
}
}
///
/// Releases the unmanaged resources used by the and optionally releases the managed resources.
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
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();
/// Connects this instance of the class to a running Task Scheduler.
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.");
}
}
}
/// Finds the task in folder.
/// The folder.
/// The wildcard expression to compare task names with.
/// The results.
/// if set to true recurse folders.
/// True if any tasks are found, False if not.
private bool FindTaskInFolder([NotNull] TaskFolder fld, System.Text.RegularExpressions.Regex taskName, ref System.Collections.Generic.List 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;
}
/// Finds the task in folder.
/// The folder.
/// The filter to use when looking for tasks.
/// The results.
/// if set to true recurse folders.
/// True if any tasks are found, False if not.
private bool FindTaskInFolder([NotNull] TaskFolder fld, Predicate filter, ref System.Collections.Generic.List 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);
///
/// 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 between threads.
///
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 connections = new List() { 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 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 OnCompleted;
private readonly ComHandlerUpdate OnUpdate;
public TaskHandlerStatus(Action 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
{
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);
}
}
}
}