using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics.Eventing.Reader;
using System.IO;
using JetBrains.Annotations;
namespace Microsoft.Win32.TaskScheduler
{
///
/// Information about the task event.
///
[PublicAPI]
public class TaskEventArgs : EventArgs
{
private readonly TaskService taskService;
internal TaskEventArgs([NotNull] TaskEvent evt, TaskService ts = null)
{
TaskEvent = evt;
TaskPath = evt.TaskPath;
taskService = ts;
}
///
/// If possible, gets the task associated with this event.
///
///
/// The task or null if unable to retrieve.
///
public Task Task
{
get { try { return taskService?.GetTask(TaskPath); } catch { return null; } }
}
///
/// Gets the .
///
///
/// The TaskEvent.
///
[NotNull]
public TaskEvent TaskEvent { get; }
///
/// Gets the task name.
///
///
/// The task name.
///
public string TaskName => Path.GetFileName(TaskPath);
///
/// Gets the task path.
///
///
/// The task path.
///
public string TaskPath { get; }
}
///
/// Watches system events related to tasks and issues a event when the filtered conditions are met.
/// Only available for Task Scheduler 2.0 on Windows Vista or Windows Server 2003 and later.
///
/// Sometimes, a developer will need to know about events as they occur. In this case, they can use the TaskEventWatcher component that enables the developer to watch a task, a folder, or the entire system for filtered events.
///
/// Below is information on how to watch a folder for all task events. For a complete example, look at this sample project: TestTaskWatcher.zip
///
[DefaultEvent(nameof(EventRecorded)), DefaultProperty(nameof(Folder))]
#if DESIGNER
[Designer(typeof(Design.TaskEventWatcherDesigner))]
#endif
[ToolboxItem(true), Serializable]
[PublicAPI]
public class TaskEventWatcher : Component, ISupportInitialize
{
private const string root = "\\";
private const string star = "*";
private static readonly TimeSpan MaxV1EventLapse = TimeSpan.FromSeconds(1);
private bool disposed;
private bool enabled;
private string folder = root;
private bool includeSubfolders;
private bool initializing;
private StandardTaskEventId lastId = 0;
private DateTime lastIdTime = DateTime.MinValue;
private TaskService ts;
private FileSystemWatcher v1Watcher;
private EventLogWatcher watcher;
private ISynchronizeInvoke synchronizingObject;
///
/// Initializes a new instance of the class. If other
/// properties are not set, this will watch for all events for all tasks on the local machine.
///
public TaskEventWatcher() : this(TaskService.Instance)
{
}
///
/// Initializes a new instance of the class watching only
/// those events for the task with the provided path on the local machine.
///
/// The full path (folders and name) of the task to watch.
/// The task service.
/// $Invalid task name: {taskPath}
public TaskEventWatcher(string taskPath, TaskService taskService = null) : this(taskService ?? TaskService.Instance)
{
InitTask(taskPath);
}
///
/// Initializes a new instance of the class watching only
/// those events for the specified task.
///
/// The task to watch.
/// Occurs if the is null.
public TaskEventWatcher([NotNull] Task task) : this(task?.TaskService)
{
if (task == null)
throw new ArgumentNullException(nameof(task));
InitTask(task);
}
///
/// Initializes a new instance of the class watching only those events for
/// the tasks whose name matches the in the specified
/// and optionally all subfolders.
///
/// The task folder to watch.
/// The filter for task names using standard file system wildcards. Use "*" to include all tasks.
/// if set to true include events from tasks subfolders.
/// Occurs if the is null.
public TaskEventWatcher([NotNull] TaskFolder taskFolder, string taskFilter = "*", bool includeSubfolders = false) : this(taskFolder?.TaskService)
{
if (taskFolder == null)
throw new ArgumentNullException(nameof(taskFolder));
InitTask(taskFolder, taskFilter, includeSubfolders);
}
///
/// Initializes a new instance of the class.
///
/// The task folder to watch.
/// The filter for task names using standard file system wildcards. Use "*" to include all tasks.
/// if set to true include events from tasks subfolders.
/// The task service.
public TaskEventWatcher(string folder, string taskFilter, bool includeSubfolders, TaskService taskService = null) : this(taskService ?? TaskService.Instance)
{
InitTask(folder, taskFilter, includeSubfolders);
}
///
/// Initializes a new instance of the class on a remote machine.
///
/// Name of the remote machine.
/// The task path.
/// The domain of the user account.
/// The user name with permissions on the remote machine.
/// The password for the user.
public TaskEventWatcher(string machineName, string taskPath, string domain = null, string user = null, string password = null) : this(new TaskService(machineName, user, domain, password))
{
InitTask(taskPath);
}
///
/// Initializes a new instance of the class on a remote machine.
///
/// Name of the remote machine.
/// The task folder to watch.
/// The filter for task names using standard file system wildcards. Use "*" to include all tasks.
/// if set to true include events from tasks subfolders.
/// The domain of the user account.
/// The user name with permissions on the remote machine.
/// The password for the user.
public TaskEventWatcher(string machineName, string folder, string taskFilter = "*", bool includeSubfolders = false, string domain = null, string user = null, string password = null) : this(new TaskService(machineName, user, domain, password))
{
InitTask(folder, taskFilter, includeSubfolders);
}
private TaskEventWatcher(TaskService ts)
{
TaskService = ts;
Filter = new EventFilter(this);
}
///
/// Occurs when a task or the task engine records an event.
///
[Category("Action"), Description("Event recorded by a task or the task engine.")]
public event EventHandler EventRecorded;
///
/// Gets or sets a value indicating whether the component is enabled.
///
///
/// true if enabled; otherwise, false.
///
[DefaultValue(false), Category("Behavior"), Description("Indicates whether the component is enabled.")]
public bool Enabled
{
get { return enabled; }
set
{
if (enabled != value)
{
System.Diagnostics.Debug.WriteLine($"TaskEventWather: Set {nameof(Enabled)} = {value}");
enabled = value;
if (!IsSuspended())
{
if (enabled)
StartRaisingEvents();
else
StopRaisingEvents();
}
}
}
}
///
/// Gets the filter for this .
///
///
/// The filter.
///
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content), Category("Behavior"), Description("Indicates the filter for the watcher.")]
public EventFilter Filter { get; }
///
/// Gets or sets the folder to watch.
///
///
/// The folder path to watch. This value should include the leading "\" to indicate the root folder.
///
/// Thrown if the folder specified does not exist or contains invalid characters.
[DefaultValue(root), Category("Behavior"), Description("Indicates the folder to watch.")]
public string Folder
{
get { return folder; }
set
{
if (string.IsNullOrEmpty(value))
value = root;
if (!value.EndsWith("\\"))
value += "\\";
if (string.Compare(folder, value, StringComparison.OrdinalIgnoreCase) == 0) return;
if ((DesignMode && (value.IndexOfAny(new[] { '*', '?' }) != -1 || value.IndexOfAny(Path.GetInvalidPathChars()) != -1)) || (TaskService.GetFolder(value == root ? value : value.TrimEnd('\\')) == null))
throw new ArgumentException($"Invalid folder name: {value}");
folder = value;
}
}
///
/// Gets or sets a value indicating whether to include events from subfolders when the
/// property is set. If the property is set,
/// this property is ignored.
///
/// true if include events from subfolders; otherwise, false.
[DefaultValue(false), Category("Behavior"), Description("Indicates whether to include events from subfolders.")]
public bool IncludeSubfolders
{
get { return includeSubfolders; }
set
{
if (includeSubfolders == value) return;
includeSubfolders = value;
Restart();
}
}
///
/// Gets or sets the synchronizing object.
///
///
/// The synchronizing object.
///
[Browsable(false), DefaultValue(null)]
public ISynchronizeInvoke SynchronizingObject
{
get
{
if (synchronizingObject == null && DesignMode)
{
var so = ((IDesignerHost)GetService(typeof(IDesignerHost)))?.RootComponent as ISynchronizeInvoke;
if (so != null)
synchronizingObject = so;
}
return synchronizingObject;
}
set { synchronizingObject = value; }
}
///
/// Gets or sets the name of the computer that is running the Task Scheduler service that the user is connected to.
///
[Category("Connection"), Description("The name of the computer to connect to."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string TargetServer
{
get { return TaskService.TargetServer; }
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, TaskService.TargetServer, StringComparison.OrdinalIgnoreCase) == 0) return;
TaskService.TargetServer = value;
Restart();
}
}
///
/// Gets or sets the instance associated with this event watcher. Setting this value
/// will override any values set for , ,
/// , and and set them to those values in the supplied
/// instance.
///
/// The TaskService.
[Category("Data"), Description("The TaskService for this event watcher.")]
public TaskService TaskService
{
get { return ts; }
set { ts = value; Restart(); }
}
///
/// Gets or sets the user account domain to be used when connecting to the .
///
/// The user account domain.
[Category("Connection"), Description("The user account domain to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string UserAccountDomain
{
get { return TaskService.UserAccountDomain; }
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, TaskService.UserAccountDomain, StringComparison.OrdinalIgnoreCase) == 0) return;
TaskService.UserAccountDomain = value;
Restart();
}
}
///
/// Gets or sets the user name to be used when connecting to the .
///
/// The user name.
[Category("Connection"), Description("The user name to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string UserName
{
get { return TaskService.UserName; }
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, TaskService.UserName, StringComparison.OrdinalIgnoreCase) == 0) return;
TaskService.UserName = value;
Restart();
}
}
///
/// Gets or sets the user password to be used when connecting to the .
///
/// The user password.
[Category("Connection"), Description("The user password to be used when connecting."), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public string UserPassword
{
get { return TaskService.UserPassword; }
set
{
if (value == null || value.Trim() == string.Empty) value = null;
if (string.Compare(value, TaskService.UserPassword, StringComparison.OrdinalIgnoreCase) == 0) return;
TaskService.UserPassword = value;
Restart();
}
}
///
/// Gets a value indicating if watching is available.
///
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
private bool IsHandleInvalid => IsV1 ? v1Watcher == null : watcher == null;
private static bool IsV1 => Environment.OSVersion.Version.Major < 6;
///
/// Signals the object that initialization is starting.
///
public void BeginInit()
{
System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(BeginInit)}");
initializing = true;
var localEnabled = enabled;
StopRaisingEvents();
enabled = localEnabled;
TaskService.BeginInit();
}
///
/// Signals the object that initialization is complete.
///
public void EndInit()
{
System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(EndInit)}");
initializing = false;
TaskService.EndInit();
if (enabled)
StartRaisingEvents();
}
///
/// Releases the unmanaged resources used by the FileSystemWatcher 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)
{
try
{
if (disposing)
{
StopRaisingEvents();
TaskService = null;
}
else
{
StopListening();
}
}
finally
{
disposed = true;
base.Dispose(disposing);
}
}
///
/// Fires the event.
///
/// The sender.
/// The instance containing the event data.
protected virtual void OnEventRecorded(object sender, TaskEventArgs e)
{
var h = EventRecorded;
if (h == null) return;
if (SynchronizingObject != null && SynchronizingObject.InvokeRequired)
SynchronizingObject.BeginInvoke(h, new object[] { this, e });
else
h(sender, e);
}
private void InitTask([NotNull] Task task)
{
Filter.TaskName = task.Name;
Folder = task.Folder.Path;
}
private void InitTask(TaskFolder taskFolder, string taskFilter, bool includeSubfolders)
{
this.includeSubfolders = includeSubfolders;
Filter.TaskName = taskFilter;
Folder = taskFolder?.Path;
}
private void InitTask(string taskFolder, string taskFilter, bool includeSubfolders)
{
this.includeSubfolders = includeSubfolders;
Filter.TaskName = taskFilter;
Folder = taskFolder;
}
private void InitTask(string taskPath)
{
Filter.TaskName = Path.GetFileNameWithoutExtension(taskPath);
Folder = Path.GetDirectoryName(taskPath);
}
private bool IsSuspended() => initializing || DesignMode;
private void ReleaseWatcher()
{
if (IsV1)
{
if (v1Watcher == null) return;
v1Watcher.EnableRaisingEvents = false;
v1Watcher.Changed -= Watcher_DirectoryChanged;
v1Watcher.Created -= Watcher_DirectoryChanged;
v1Watcher.Deleted -= Watcher_DirectoryChanged;
v1Watcher.Renamed -= Watcher_DirectoryChanged;
v1Watcher = null;
}
else
{
if (watcher == null) return;
watcher.Enabled = false;
watcher.EventRecordWritten -= Watcher_EventRecordWritten;
watcher = null;
}
}
private void ResetTaskService() { ts = TaskService.Instance; }
private void Restart()
{
if (IsSuspended() || !enabled) return;
System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(Restart)}");
StopRaisingEvents();
StartRaisingEvents();
}
private void SetupWatcher()
{
ReleaseWatcher();
string taskPath = null;
if (Filter.Wildcard == null)
taskPath = Path.Combine(folder, Filter.TaskName);
if (IsV1)
{
var di = new DirectoryInfo(Environment.GetFolderPath(Environment.SpecialFolder.System));
string dir = di.Parent != null ? Path.Combine(di.Parent.FullName, "Tasks") : "Tasks";
v1Watcher = new FileSystemWatcher(dir, "*.job") { NotifyFilter = NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.Attributes };
v1Watcher.Changed += Watcher_DirectoryChanged;
v1Watcher.Created += Watcher_DirectoryChanged;
v1Watcher.Deleted += Watcher_DirectoryChanged;
v1Watcher.Renamed += Watcher_DirectoryChanged;
}
else
{
var log = new TaskEventLog(taskPath, Filter.EventIds, Filter.EventLevels, DateTime.UtcNow, TargetServer, UserAccountDomain, UserName, UserPassword);
log.Query.ReverseDirection = false;
watcher = new EventLogWatcher(log.Query);
watcher.EventRecordWritten += Watcher_EventRecordWritten;
}
}
private bool ShouldSerializeFilter() => Filter.ShouldSerialize();
private bool ShouldSerializeTaskService() => !Equals(TaskService, TaskService.Instance);
private void StartRaisingEvents()
{
if (disposed)
throw new ObjectDisposedException(GetType().Name);
if (IsSuspended()) return;
System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(StartRaisingEvents)}");
enabled = true;
SetupWatcher();
if (IsV1)
try { v1Watcher.EnableRaisingEvents = true; } catch { }
else
try { watcher.Enabled = true; } catch { }
}
private void StopListening()
{
enabled = false;
ReleaseWatcher();
}
private void StopRaisingEvents()
{
System.Diagnostics.Debug.WriteLine($"TaskEventWather: {nameof(StopRaisingEvents)}");
if (IsSuspended())
enabled = false;
else if (!IsHandleInvalid)
StopListening();
}
private void Watcher_DirectoryChanged(object sender, FileSystemEventArgs e)
{
StandardTaskEventId id = StandardTaskEventId.TaskUpdated;
if (e.ChangeType == WatcherChangeTypes.Deleted)
id = StandardTaskEventId.TaskDeleted;
else if (e.ChangeType == WatcherChangeTypes.Created)
id = StandardTaskEventId.JobRegistered;
if (lastId == id && DateTime.Now.Subtract(lastIdTime) <= MaxV1EventLapse) return;
OnEventRecorded(this, new TaskEventArgs(new TaskEvent(Path.Combine("\\", e.Name.Replace(".job", "")), id, DateTime.Now), TaskService));
lastId = id;
lastIdTime = DateTime.Now;
}
private void Watcher_EventRecordWritten(object sender, EventRecordWrittenEventArgs e)
{
try
{
var taskEvent = new TaskEvent(e.EventRecord);
System.Diagnostics.Debug.WriteLine("Task event: " + taskEvent.ToString());
// Get the task name and folder
if (string.IsNullOrEmpty(taskEvent.TaskPath)) return;
int cpos = taskEvent.TaskPath.LastIndexOf('\\');
string name = taskEvent.TaskPath.Substring(cpos + 1);
string fld = taskEvent.TaskPath.Substring(0, cpos + 1);
// Check folder and name filters
if (!string.IsNullOrEmpty(Filter.TaskName) && string.Compare(Filter.TaskName, taskEvent.TaskPath, StringComparison.OrdinalIgnoreCase) != 0)
{
if (Filter.Wildcard != null && !Filter.Wildcard.IsMatch(name))
return;
if (IncludeSubfolders && !fld.StartsWith(folder, StringComparison.OrdinalIgnoreCase))
return;
if (!IncludeSubfolders && string.Compare(folder, fld, StringComparison.OrdinalIgnoreCase) != 0)
return;
}
OnEventRecorded(this, new TaskEventArgs(taskEvent, TaskService));
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"{nameof(Watcher_EventRecordWritten)} has failed. Error: {ex.ToString()}");
}
}
///
/// Holds filter information for a .
///
[TypeConverter(typeof(ExpandableObjectConverter)), Serializable]
[PublicAPI]
public class EventFilter
{
private string filter = star;
private int[] ids;
private int[] levels;
private readonly TaskEventWatcher parent;
internal EventFilter([NotNull] TaskEventWatcher parent)
{
this.parent = parent;
}
///
/// Gets or sets an optional array of event identifiers to use when filtering those events that will fire a event.
///
///
/// The array of event identifier filters. All know task event identifiers are declared in the enumeration.
///
[DefaultValue(null), Category("Filter"), Description("An array of event identifiers to use when filtering.")]
public int[] EventIds
{
get { return ids; }
set
{
if (ids != value)
{
ids = value;
parent.Restart();
}
}
}
///
/// Gets or sets an optional array of event levels to use when filtering those events that will fire a event.
///
///
/// The array of event levels. While event providers can define custom levels, most will use integers defined in the System.Diagnostics.Eventing.Reader.StandardEventLevel enumeration.
///
[DefaultValue(null), Category("Filter"), Description("An array of event levels to use when filtering.")]
public int[] EventLevels
{
get { return levels; }
set
{
if (levels != value)
{
levels = value;
parent.Restart();
}
}
}
///
/// Gets or sets the task name, which can utilize wildcards, to look for when watching a folder.
///
/// A task name or wildcard.
[DefaultValue(star), Category("Filter"), Description("A task name, which can utilize wildcards, for filtering.")]
public string TaskName
{
get { return filter; }
set
{
if (string.IsNullOrEmpty(value))
value = star;
if (string.Compare(filter, value, StringComparison.OrdinalIgnoreCase) != 0)
{
filter = value;
Wildcard = (value.IndexOfAny(new[] { '?', '*' }) == -1) ? null : new Wildcard(value);
parent.Restart();
}
}
}
internal Wildcard Wildcard { get; private set; } = new Wildcard(star);
///
/// Returns a that represents this instance.
///
///
/// A that represents this instance.
///
public override string ToString() => filter + (levels == null ? "" : " +levels") + (ids == null ? "" : " +id's");
internal bool ShouldSerialize() => ids != null || levels != null || filter != star;
}
}
#if DESIGNER
namespace Design
{
internal class TaskEventWatcherDesigner : ComponentDesigner
{
public override void InitializeNewComponent(IDictionary defaultValues)
{
base.InitializeNewComponent(defaultValues);
var refs = GetService();
var tsColl = refs?.GetReferences(typeof(TaskService));
System.Diagnostics.Debug.Assert(refs != null && tsColl != null && tsColl.Length > 0, "Designer couldn't find host, reference service, or existing TaskService.");
if (tsColl != null && tsColl.Length > 0)
{
TaskEventWatcher tsComp = Component as TaskEventWatcher;
TaskService ts = tsColl[0] as TaskService;
if (tsComp != null)
tsComp.TaskService = ts;
}
}
protected virtual T GetService() => (T)Component?.Site?.GetService(typeof(T));
}
}
#endif
}