using JetBrains.Annotations; using System; using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Xml.Serialization; namespace Microsoft.Win32.TaskScheduler { /// Provides the methods that are used to add to, remove from, and get the triggers of a task. [XmlRoot("Triggers", Namespace = TaskDefinition.tns, IsNullable = false)] public sealed class TriggerCollection : IList, IDisposable, IXmlSerializable, IList, INotifyCollectionChanged, INotifyPropertyChanged { private const string IndexerName = "Item[]"; private readonly V2Interop.ITriggerCollection v2Coll; private bool inV2set; private V1Interop.ITask v1Task; private V2Interop.ITaskDefinition v2Def; internal TriggerCollection([NotNull] V1Interop.ITask iTask) => v1Task = iTask; internal TriggerCollection([NotNull] V2Interop.ITaskDefinition iTaskDef) { v2Def = iTaskDef; v2Coll = v2Def.Triggers; } /// Occurs when a collection changes. public event NotifyCollectionChangedEventHandler CollectionChanged; /// Occurs when a property value changes. public event PropertyChangedEventHandler PropertyChanged; /// Gets the number of triggers in the collection. public int Count => v2Coll?.Count ?? v1Task.GetTriggerCount(); bool IList.IsFixedSize => false; bool ICollection.IsReadOnly => false; bool IList.IsReadOnly => false; bool ICollection.IsSynchronized => false; object ICollection.SyncRoot => this; /// Gets or sets a specified trigger from the collection. /// The . /// The id ( ) of the trigger to be retrieved. /// Specialized instance. /// /// /// /// Mismatching Id for trigger and lookup. public Trigger this[[NotNull] string triggerId] { get { if (string.IsNullOrEmpty(triggerId)) throw new ArgumentNullException(nameof(triggerId)); foreach (var t in this) if (string.Equals(t.Id, triggerId)) return t; throw new ArgumentOutOfRangeException(nameof(triggerId)); } set { if (value == null) throw new NullReferenceException(); if (string.IsNullOrEmpty(triggerId)) throw new ArgumentNullException(nameof(triggerId)); if (triggerId != value.Id) throw new InvalidOperationException("Mismatching Id for trigger and lookup."); var index = IndexOf(triggerId); if (index >= 0) { var orig = this[index].Clone(); inV2set = true; try { RemoveAt(index); Insert(index, value); } finally { inV2set = true; } OnNotifyPropertyChanged(IndexerName); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index)); } else Add(value); } } /// Gets a specified trigger from the collection. /// The index of the trigger to be retrieved. /// Specialized instance. public Trigger this[int index] { get { if (v2Coll != null) return Trigger.CreateTrigger(v2Coll[++index], v2Def); return Trigger.CreateTrigger(v1Task.GetTrigger((ushort)index)); } set { if (index < 0 || Count <= index) throw new ArgumentOutOfRangeException(nameof(index), index, @"Index is not a valid index in the TriggerCollection"); var orig = this[index].Clone(); inV2set = true; try { Insert(index, value); RemoveAt(index + 1); } finally { inV2set = false; } OnNotifyPropertyChanged(IndexerName); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, value, orig, index)); } } object IList.this[int index] { get => this[index]; set => this[index] = (Trigger)value; } /*/// /// Add an unbound to the task. derivative to /// add to the task. Bound trigger. unboundTrigger /// is null. public Trigger Add([NotNull] Trigger unboundTrigger) { if (unboundTrigger == null) throw new ArgumentNullException(nameof(unboundTrigger)); if (v2Def != null) unboundTrigger.Bind(v2Def); else unboundTrigger.Bind(v1Task); return unboundTrigger; }*/ /// Add an unbound to the task. /// A type derived from . /// derivative to add to the task. /// Bound trigger. /// unboundTrigger is null. public TTrigger Add([NotNull] TTrigger unboundTrigger) where TTrigger : Trigger { if (unboundTrigger == null) throw new ArgumentNullException(nameof(unboundTrigger)); if (v2Def != null) unboundTrigger.Bind(v2Def); else unboundTrigger.Bind(v1Task); OnNotifyPropertyChanged(nameof(Count)); OnNotifyPropertyChanged(IndexerName); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, unboundTrigger)); return unboundTrigger; } /// Add a new trigger to the collections of triggers for the task. /// The type of trigger to create. /// A instance of the specified type. public Trigger AddNew(TaskTriggerType taskTriggerType) { if (v1Task != null) return Trigger.CreateTrigger(v1Task.CreateTrigger(out _), Trigger.ConvertToV1TriggerType(taskTriggerType)); return Trigger.CreateTrigger(v2Coll.Create(taskTriggerType), v2Def); } /// Adds a collection of unbound triggers to the end of the . /// /// The triggers to be added to the end of the . The collection itself cannot be null and /// cannot contain null elements. /// /// is null. public void AddRange([NotNull] IEnumerable triggers) { if (triggers == null) throw new ArgumentNullException(nameof(triggers)); foreach (var item in triggers) Add(item); } /// Clears all triggers from the task. public void Clear() { if (v2Coll != null) v2Coll.Clear(); else { inV2set = true; try { for (var i = Count - 1; i >= 0; i--) RemoveAt(i); } finally { inV2set = false; } } OnNotifyPropertyChanged(nameof(Count)); OnNotifyPropertyChanged(IndexerName); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); } /// Determines whether the contains a specific value. /// The object to locate in the . /// true if is found in the ; otherwise, false. public bool Contains([NotNull] Trigger item) => Find(a => a.Equals(item)) != null; /// Determines whether the specified trigger type is contained in this collection. /// Type of the trigger. /// true if the specified trigger type is contained in this collection; otherwise, false. public bool ContainsType(Type triggerType) => Find(a => a.GetType() == triggerType) != null; /// /// Copies the elements of the to an , starting at a particular index. /// /// /// The one-dimensional that is the destination of the elements copied from . The /// must have zero-based indexing. /// /// The zero-based index in at which copying begins. public void CopyTo(Trigger[] array, int arrayIndex) => CopyTo(0, array, arrayIndex, Count); /// /// Copies the elements of the to a array, starting at a particular array index. /// /// The zero-based index in the source at which copying begins. /// /// The array that is the destination of the elements copied from . The array must have zero-based indexing. /// /// The zero-based index in array at which copying begins. /// The number of elements to copy. /// is null. /// is less than 0. /// /// The number of elements in the source is greater than the available space from to the end of the destination . /// public void CopyTo(int index, Trigger[] array, int arrayIndex, int count) { if (array == null) throw new ArgumentNullException(nameof(array)); if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index)); if (arrayIndex < 0) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); if (count < 0 || count > (Count - index)) throw new ArgumentOutOfRangeException(nameof(count)); if ((Count - index) > (array.Length - arrayIndex)) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); for (var i = 0; i < count; i++) array[arrayIndex + i] = (Trigger)this[index + i].Clone(); } /// Releases all resources used by this class. public void Dispose() { if (v2Coll != null) Marshal.ReleaseComObject(v2Coll); v2Def = null; v1Task = null; } /// /// Searches for an that matches the conditions defined by the specified predicate, and returns the first /// occurrence within the entire collection. /// /// /// The delegate that defines the conditions of the to search for. /// /// /// The first that matches the conditions defined by the specified predicate, if found; otherwise, null. /// public Trigger Find([NotNull] Predicate match) { if (match == null) throw new ArgumentNullException(nameof(match)); foreach (var item in this) if (match(item)) return item; return null; } /// /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based /// index of the first occurrence within the collection that starts at the specified index and contains the specified number of elements. /// /// The zero-based starting index of the search. /// The number of elements in the collection to search. /// The delegate that defines the conditions of the element to search for. /// /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. /// public int FindIndexOf(int startIndex, int count, [NotNull] Predicate match) { if (startIndex < 0 || startIndex >= Count) throw new ArgumentOutOfRangeException(nameof(startIndex)); if (startIndex + count > Count) throw new ArgumentOutOfRangeException(nameof(count)); if (match == null) throw new ArgumentNullException(nameof(match)); for (var i = startIndex; i < startIndex + count; i++) if (match(this[i])) return i; return -1; } /// /// Searches for an that matches the conditions defined by the specified predicate, and returns the zero-based /// index of the first occurrence within the collection. /// /// The delegate that defines the conditions of the element to search for. /// /// The zero-based index of the first occurrence of an element that matches the conditions defined by match, if found; otherwise, –1. /// public int FindIndexOf([NotNull] Predicate match) => FindIndexOf(0, Count, match); /// Gets the collection enumerator for this collection. /// The for this collection. public IEnumerator GetEnumerator() { if (v1Task != null) return new V1TriggerEnumerator(v1Task); return new ComEnumerator(() => v2Coll.Count, i => v2Coll[i], o => Trigger.CreateTrigger(o, v2Def)); } /// Determines the index of a specific item in the . /// The object to locate in the . /// The index of if found in the list; otherwise, -1. public int IndexOf([NotNull] Trigger item) => FindIndexOf(a => a.Equals(item)); /// Determines the index of a specific item in the . /// The id ( ) of the trigger to be retrieved. /// The index of if found in the list; otherwise, -1. public int IndexOf([NotNull] string triggerId) { if (string.IsNullOrEmpty(triggerId)) throw new ArgumentNullException(triggerId); return FindIndexOf(a => string.Equals(a.Id, triggerId)); } /// Inserts an trigger at the specified index. /// The zero-based index at which trigger should be inserted. /// The trigger to insert into the list. public void Insert(int index, [NotNull] Trigger trigger) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); if (index >= Count) throw new ArgumentOutOfRangeException(nameof(index)); var pushItems = new Trigger[Count - index]; CopyTo(index, pushItems, 0, Count - index); for (var j = Count - 1; j >= index; j--) RemoveAt(j); Add(trigger); foreach (var t in pushItems) Add(t); } /// Removes the first occurrence of a specific object from the . /// The object to remove from the . /// /// true if was successfully removed from the ; otherwise, false. This method /// also returns false if is not found in the original . /// public bool Remove([NotNull] Trigger item) { var idx = IndexOf(item); if (idx != -1) { try { RemoveAt(idx); return true; } catch { } } return false; } /// Removes the trigger at a specified index. /// Index of trigger to remove. /// Index out of range. public void RemoveAt(int index) { if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index), index, @"Failed to remove Trigger. Index out of range."); var item = this[index].Clone(); if (v2Coll != null) v2Coll.Remove(++index); else v1Task.DeleteTrigger((ushort)index); //Remove the trigger from the Task Scheduler if (!inV2set) { OnNotifyPropertyChanged(nameof(Count)); OnNotifyPropertyChanged(IndexerName); CollectionChanged?.Invoke(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index)); } } /// Copies the elements of the to a new array. /// An array containing copies of the elements of the . public Trigger[] ToArray() { var ret = new Trigger[Count]; CopyTo(ret, 0); return ret; } /// Returns a that represents the triggers in this collection. /// A that represents the triggers in this collection. public override string ToString() { if (Count == 1) return this[0].ToString(); if (Count > 1) return Properties.Resources.MultipleTriggers; return string.Empty; } void ICollection.Add(Trigger item) => Add(item); int IList.Add(object value) { Add((Trigger)value); return Count - 1; } bool IList.Contains(object value) => Contains((Trigger)value); void ICollection.CopyTo(Array array, int index) { if (array != null && array.Rank != 1) throw new RankException("Multi-dimensional arrays are not supported."); var src = new Trigger[Count]; CopyTo(src, 0); Array.Copy(src, 0, array, index, Count); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() => null; int IList.IndexOf(object value) => IndexOf((Trigger)value); void IList.Insert(int index, object value) => Insert(index, (Trigger)value); void IXmlSerializable.ReadXml(System.Xml.XmlReader reader) { reader.ReadStartElement(XmlSerializationHelper.GetElementName(this), TaskDefinition.tns); while (reader.MoveToContent() == System.Xml.XmlNodeType.Element) { switch (reader.LocalName) { case "BootTrigger": XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Boot)); break; case "IdleTrigger": XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Idle)); break; case "TimeTrigger": XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Time)); break; case "LogonTrigger": XmlSerializationHelper.ReadObject(reader, AddNew(TaskTriggerType.Logon)); break; case "CalendarTrigger": Add(CalendarTrigger.GetTriggerFromXml(reader)); break; default: reader.Skip(); break; } } reader.ReadEndElement(); } void IList.Remove(object value) => Remove((Trigger)value); void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer) { foreach (var t in this) XmlSerializationHelper.WriteObject(writer, t); } internal void Bind() { foreach (var t in this) t.SetV1TriggerData(); } /// Called when a property has changed to notify any attached elements. /// Name of the property. private void OnNotifyPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); private sealed class V1TriggerEnumerator : IEnumerator { private short curItem = -1; private V1Interop.ITask iTask; internal V1TriggerEnumerator(V1Interop.ITask task) => iTask = task; public Trigger Current => Trigger.CreateTrigger(iTask.GetTrigger((ushort)curItem)); object IEnumerator.Current => Current; /// Releases all resources used by this class. public void Dispose() => iTask = null; public bool MoveNext() => (++curItem < iTask.GetTriggerCount()); public void Reset() => curItem = -1; } } }