using System; using System.Collections.Generic; using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; namespace Microsoft.Win32.TaskScheduler.Events { /// /// Object which encapsulates the filter query used by objects. /// [XmlRoot("QueryList", IsNullable = false)] public class EventQuery { /// /// Initializes a new instance of the class. /// public EventQuery() { } /// /// Initializes a new instance of the class. /// /// The path. /// The event ids. /// The start time. /// The end time. public EventQuery(string path, int[] eventIDs = null, DateTime? startTime = null, DateTime? endTime = null) { Query.AddPath(path); if (eventIDs != null) Query.IDs.AddRange(Array.ConvertAll(eventIDs, i => new CQuery.CID(i))); if (startTime.HasValue || endTime.HasValue) Query.Times = new CQuery.CTimeCreated(startTime, endTime); } /// /// Initializes a new instance of the class. /// /// The path. /// The last span. /// The event ids. public EventQuery(string path, TimeSpan lastSpan, int[] eventIDs = null) { Query.AddPath(path); if (eventIDs != null) Query.IDs.AddRange(Array.ConvertAll(eventIDs, i => new CQuery.CID(i))); Query.Times = new CQuery.CTimeCreated(lastSpan); } /// /// The query sub element /// [XmlElement] public CQuery Query = new CQuery(); private static Serializer QLSerializer = new Serializer(); /// /// Deserializes the specified XML. /// /// The XML. /// public static EventQuery Deserialize(string xml) { return QLSerializer.Deserialize(xml); } /// /// Serializes the specified q. /// /// The q. /// public static string Serialize(EventQuery q) { q.FinalizeObject(); return QLSerializer.Serialize(q); } internal void FinalizeObject() { this.Query.Suppress.Clear(); if (this.Query.SuppressedIDs.Count > 0) { foreach (var i in this.Query.Select) this.Query.Suppress.Add(new CQuery.CSuppress(this.Query, i.Path)); } } /// /// Represents the Query sub object. /// public class CQuery : IXmlSerializable { /// /// The identifier /// [XmlAttribute] public int Id = 0; /// /// The path /// [XmlAttribute] public string Path; /// /// The computers /// [XmlIgnore] public List Computers = new List(); /// /// The data items (e.g. TaskName) that are being requested /// [XmlIgnore] public Dictionary Data = new Dictionary(); /// /// The ids /// [XmlIgnore] public List IDs = new List(); /// /// The is select vs suppress /// [XmlIgnore] public bool IsSelect = true; /// /// The keywords /// [XmlIgnore] public long Keywords = 0; /// /// The levels /// [XmlIgnore] public List Levels = new List(); /// /// The providers /// [XmlIgnore] public List Providers = new List(); /// /// The select /// [XmlElement("Select")] public List Select = new List(); /// /// The suppress /// [XmlElement("Suppress")] public List Suppress = new List(); /// /// The suppressed i ds /// [XmlIgnore] public List SuppressedIDs = new List(); /// /// The tasks /// [XmlIgnore] public List Tasks = new List(); /// /// The times /// [XmlIgnore] public CTimeCreated Times; /// /// The user /// [XmlIgnore] public string User = null; /// /// Gets or sets the identifier string. /// /// /// The identifier string. /// [XmlIgnore] public string IDString { get { int tc = SuppressedIDs.Count + IDs.Count; if (tc == 0) return string.Empty; var output = new string[tc]; for (int i = 0; i < IDs.Count; i++) output[i] = IDs[i].Text; for (int i = 0; i < SuppressedIDs.Count; i++) output[i + IDs.Count] = string.Format("{0}", SuppressedIDs[i].Text); return string.Join(",", output); } set { SuppressedIDs.Clear(); IDs.Clear(); value = value.Replace(" ", ""); var IDRegex = new Regex(@"(?(?\d+)-(?\d+))|(?-?\d+)", RegexOptions.ExplicitCapture | RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace); foreach (Match m in IDRegex.Matches(value)) { if (m.Groups["n"].Success) { var i = int.Parse(m.Groups["n"].Value); if (i >= 0) IDs.Add(new CID(i)); else SuppressedIDs.Add(new CID(-i)); } else if (m.Groups["r"].Success) IDs.Add(new CID(int.Parse(m.Groups["r1"].Value), int.Parse(m.Groups["r2"].Value))); } } } /// /// Adds the path. /// /// The path. public void AddPath(string path) { this.Path = path; this.Select.Add(new CSelect(this, path)); } /// /// Adds the validated computers. /// /// The computers. public void AddValidatedComputers(string computers) { this.Computers.Clear(); // TODO: Validate items this.Computers.AddRange(computers.Replace(" ", "").Split(',', ';')); } /// /// Adds the validated user. /// /// The user. /// public void AddValidatedUser(string user) { string sid = NativeMethods.AccountUtils.SidStringFromUserName(user); if (sid == null) throw new System.Security.Principal.IdentityNotMappedException(); this.User = sid; } System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { return null; } void IXmlSerializable.ReadXml(XmlReader reader) { if (reader.MoveToContent() == XmlNodeType.Element && reader.LocalName == "Query") { Id = int.Parse(reader["Id"]); Path = reader["Path"]; if (reader.ReadToDescendant("Select")) { while (reader.MoveToContent() == XmlNodeType.Element && (reader.LocalName == "Select" || reader.LocalName == "Suppress")) { if (reader.LocalName == "Select") { CSelect sel = new CSelect(this, null); sel.ReadXml(reader); this.Select.Add(sel); } else { CSuppress sup = new CSuppress(this, null); sup.ReadXml(reader); this.Suppress.Add(sup); } } } reader.ReadEndElement(); } } void IXmlSerializable.WriteXml(XmlWriter writer) { // Serialize attributes writer.WriteAttributeString("Id", Id.ToString()); if (!string.IsNullOrEmpty(Path)) writer.WriteAttributeString("Path", Path); // Serialize Selects var selSerializer = new Serializer(); foreach (var s in Select) selSerializer.Serialize(writer, s); // Serialize Suppress var supSerializer = new Serializer(); foreach (var s in Suppress) supSerializer.Serialize(writer, s); } /// /// Represents event IDs /// public class CID { private int low, high; /// /// Initializes a new instance of the class. /// /// The identifier. public CID(int id) { low = id; high = -1; } /// /// Initializes a new instance of the class. /// /// The identifier low. /// The identifier high. public CID(int idLow, int idHigh) { low = idLow; high = idHigh; } /// /// Gets the text. /// /// /// The text. /// [XmlIgnore] public string Text { get { if (high == -1) return string.Format("{0}", low); return string.Format("{0}-{1}", low, high); } } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { if (high == -1) return string.Format("EventID={0}", low); return string.Format("(EventID >= {0} and EventID <= {1})", low, high); } } /// /// Represents the Select element /// [XmlRoot("Select")] public class CSelect : IXmlSerializable { /// /// The path /// [XmlAttribute] public string Path; /// /// The parent /// [XmlIgnore] public CQuery Parent; /// /// The is select /// [XmlIgnore] protected bool IsSelect = true; // Regex patterns to find different values private const string computer = @"(?\(\s*(?:Computer\s*=\s*'([^']+)')(?:\s*or\s*(?:Computer\s*=\s*'([^']+)'))*\s*\))"; private const string evid = @"(?:EventID\s*=\s*(?-?\d+))"; private const string evidrange = @"(?EventID\s*>=\s*(\d+)\s*and\s*EventID\s*<=\s*(\d+))"; private const string keyword = @"band\s*\(\s*Keywords\s*,\s*(?\d+)\s*\)"; private const string level = @"(?\(\s*(?:Level\s*=\s*(\d+))(?:\s*or\s*(?:Level\s*=\s*(\d+)))*\s*\))"; private const string prov = @"(?Provider\s*\[\s*(?:@Name\s*=\s*'([^']+)')(?:\s*or\s*(?:@Name\s*=\s*'([^']+)'))*\s*\])"; private const string tasks = @"(?\(\s*(?:Task\s*=\s*(\d+))(?:\s*or\s*(?:Task\s*=\s*(\d+)))*\s*\))"; private const string time2dates = @"(?TimeCreated\s*\[\s*(?:\s*@SystemTime\s*([<>]=)\s*'([^']+)')\s*and\s*@SystemTime\s*([<>]=)\s*'([^']+)'\s*\])"; private const string timedate2end = @"TimeCreated\s*\[\s*(?:\s*@SystemTime\s*>=\s*'(?[^']+)')\s*\]"; private const string timediff = @"TimeCreated\s*\[\s*(?:timediff\s*\(\s*@SystemTime\s*\)\s*<=\s*(?\d+))\s*\]"; private const string timestart2date = @"TimeCreated\s*\[\s*(?:\s*@SystemTime\s*<=\s*'(?[^']+)')\s*\]"; private const string user = @"Security\s*\[\s*(?:@UserID\s*=\s*'(?[^']+)')\s*\]"; private const string OR = " or "; private const string AND = " and "; private const string eventData = @"\s*Data\s*\[\s*@Name\s*=\s*'(?[^']*)'\s*\]\s*=\s*'(?[^']*)'\s*(?:and\s*)?"; /// /// Initializes a new instance of the class. /// /// The parent. /// The path. public CSelect(CQuery parent, string path) { Parent = parent; Path = path; } /// /// Initializes a new instance of the class. /// protected CSelect() { } private void CheckSelectValue(string value) { string[] inner = InnerValue(value); if (inner[0] == null && inner[1] == null) return; string newVal = Regex.Replace(inner[0], string.Join("|", new string[] { computer, evidrange, evid, keyword, level, prov, tasks, user, time2dates, timedate2end, timestart2date, timediff, AND, @"\s*\(\s*\)\s*", @"\s+" }), string.Empty, RegexOptions.IgnoreCase); newVal = Regex.Replace(newVal, @"\s*\(\s*(?:or\s*)*\)\s*", string.Empty, RegexOptions.IgnoreCase); ThrowIfBadXml(value, newVal, "Select"); newVal = Regex.Replace(inner[1], eventData, string.Empty, RegexOptions.IgnoreCase); ThrowIfBadXml(value, newVal, "Select"); } private void CheckSuppressValue(string value) { string[] inner = InnerValue(value); if (inner[0] == null) return; string newVal = Regex.Replace(inner[0], evid, string.Empty, RegexOptions.IgnoreCase); ThrowIfBadXml(value, newVal, "Suppress"); } private string[] InnerValue(string value) { string[] ret = new string[] { null, null }; var r = new Regex(@"^\s*\*\s*(?:\[\s*System\s*\[(?.*)\]\s*\]\s*)?(?:and\s*)?(?:\*\s*\[\s*EventData\s*\[(?.*)\]\s*\]\s*)?$", RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.RightToLeft); Match m = r.Match(value); if (m.Success) { if (m.Groups["sys"].Success) ret[0] = m.Groups["sys"].Value; if (m.Groups["data"].Success) ret[1] = m.Groups["data"].Value; } return ret; } private void ThrowIfBadXml(string value, string xtraXml, string parentNode) { if (Regex.Replace(xtraXml, @"\s+", string.Empty).Length > 0) { var exc = new InvalidOperationException(string.Format("Invalid value for {0} node.", parentNode)); exc.Data.Add("Remaining text", xtraXml); var idx = value.IndexOf(xtraXml); if (idx > -1) exc.Data.Add("Value index", idx); throw exc; } } /// /// Gets or sets the value. /// /// /// The value. /// [XmlText] public virtual string Value { get { var sb = new System.Text.StringBuilder(); sb.Append("*"); if (IsSelect) { if (this.Parent.Providers.Count > 0) sb.AppendFormat("Provider[{0}]", string.Join(OR, this.Parent.Providers.ConvertAll(s => string.Format("@Name='{0}'", s)).ToArray())); if (this.Parent.Computers.Count > 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("({0})", string.Join(OR, this.Parent.Computers.ConvertAll(i => string.Format("Computer='{0}'", i)).ToArray())); } if (this.Parent.Levels.Count > 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("({0})", string.Join(OR, this.Parent.Levels.ConvertAll(i => string.Format("Level={0}", i)).ToArray())); } if (this.Parent.Tasks.Count > 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("({0})", string.Join(OR, this.Parent.Tasks.ConvertAll(i => string.Format("Task={0}", i)).ToArray())); } if (this.Parent.Keywords != 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("(band(Keywords,{0}))", this.Parent.Keywords); } if (this.Parent.IDs.Count > 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("({0})", string.Join(OR, this.Parent.IDs.ConvertAll(i => i.ToString()).ToArray())); } if (!string.IsNullOrEmpty(this.Parent.User)) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("Security[@UserID='{0}']", this.Parent.User); } if (this.Parent.Times != null && ((this.Parent.Times.span.HasValue && this.Parent.Times.span.Value != TimeSpan.Zero) || this.Parent.Times.HasDates)) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("TimeCreated[{0}]", this.Parent.Times); } } else { if (this.Parent.SuppressedIDs.Count > 0) { if (sb.Length > 1) sb.Append(AND); sb.AppendFormat("({0})", string.Join(OR, this.Parent.SuppressedIDs.ConvertAll(i => i.ToString()).ToArray())); } } if (sb.Length > 1) { sb.Insert(1, "[System["); sb.Append("]]"); } if (IsSelect && this.Parent.Data.Count > 0) { if (sb.Length > 1) sb.Append(AND + "*"); sb.Append("[EventData["); var dataItems = new List(); foreach (var kv in this.Parent.Data) dataItems.Add(string.Format("Data[@Name='{0}']='{1}'", kv.Key, kv.Value)); sb.Append(string.Join(AND, dataItems.ToArray())); sb.Append("]]"); } return sb.ToString(); } set { if (IsSelect) { CheckSelectValue(value); // Providers this.Parent.Providers.AddRange(GetParsedValue(prov, value).ConvertAll(o => (string)o)); // Levels this.Parent.Levels.AddRange(GetParsedValue(level, value).ConvertAll(o => (int)o)); // IDs this.Parent.IDs.AddRange(GetParsedValue(evid, value).ConvertAll(o => new CID((int)o))); var idr = GetParsedValue(evidrange, value); for (int i = 0; i < idr.Count; i += 2) this.Parent.IDs.Add(new CID((int)idr[i], (int)idr[i + 1])); // Tasks this.Parent.Tasks.AddRange(GetParsedValue(tasks, value).ConvertAll(o => (int)o)); // Keywords var k = GetParsedValue(keyword, value); if (k.Count > 0) this.Parent.Keywords = (long)k[0]; // Users var u = GetParsedValue(user, value); if (u.Count > 0) this.Parent.User = NativeMethods.AccountUtils.UserNameFromSidString((string)u[0]); // Computers this.Parent.Computers.AddRange(GetParsedValue(computer, value).ConvertAll(o => (string)o)); // Times var tc = GetParsedValue(timediff, value); if (tc.Count > 0) this.Parent.Times = new CTimeCreated(TimeSpan.FromMilliseconds((long)tc[0])); else { DateTime? s = null, e = null; tc = GetParsedValue(time2dates, value); if (tc.Count > 1) { s = (DateTime)tc[0]; e = (DateTime)tc[1]; } else { tc = GetParsedValue(timestart2date, value); if (tc.Count > 0) e = (DateTime)tc[0]; else { tc = GetParsedValue(timedate2end, value); if (tc.Count > 0) s = (DateTime)tc[0]; } } if (s.HasValue || e.HasValue) this.Parent.Times = new CTimeCreated(s, e); } // Data value var regex = new Regex(eventData, RegexOptions.IgnoreCase | RegexOptions.Compiled); foreach (Match m in regex.Matches(value)) { if (m.Groups["key"].Success && m.Groups["val"].Success) this.Parent.Data.Add(m.Groups["key"].Value, m.Groups["val"].Value); } } else { CheckSuppressValue(value); // SuppressedIDs this.Parent.SuppressedIDs.AddRange(GetParsedValue(evid, value).ConvertAll(o => new CID(-(int)o))); } } } System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema() { return null; } /// /// Generates an object from its XML representation. /// /// The stream from which the object is deserialized. public void ReadXml(XmlReader reader) { if (reader.MoveToContent() == XmlNodeType.Element) { if (reader.LocalName == "Suppress") this.IsSelect = false; this.Path = reader["Path"]; this.Value = reader.ReadString(); reader.ReadEndElement(); } } /// /// Converts an object into its XML representation. /// /// The stream to which the object is serialized. public void WriteXml(XmlWriter writer) { writer.WriteAttributeString("Path", this.Path); writer.WriteString(this.Value); } private List GetParsedValue(string regexPattern, string input) { var regex = new Regex(regexPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); List l = new List(); int i; foreach (Match m in regex.Matches(input)) { if (m.Groups["str"].Success) l.Add(m.Groups["str"].Value); else if (m.Groups["long"].Success) l.Add(long.Parse(m.Groups["long"].Value)); else if (m.Groups["int"].Success) l.Add(int.Parse(m.Groups["int"].Value)); else if (m.Groups["date"].Success) l.Add(DateTime.Parse(m.Groups["date"].Value)); else if (m.Groups["strs"].Success) { for (i = 1; i < m.Groups.Count - 1; i++) if (m.Groups[i].Captures.Count > 0) l.Add(m.Groups[i].Value); } else if (m.Groups["ints"].Success) { for (i = 1; i < m.Groups.Count - 1; i++) if (m.Groups[i].Captures.Count > 0) l.Add(int.Parse(m.Groups[i].Value)); } else if (m.Groups["dates"].Success) { char lastGL = '\0'; for (i = 1; i < m.Groups.Count - 1; i++) { if (m.Groups[i].Success) { if (m.Groups[i].Value.Length > 0 && (m.Groups[i].Value[0] == '<' || m.Groups[i].Value[0] == '>')) lastGL = m.Groups[i].Value[0]; else { var dt = DateTime.Parse(m.Groups[i].Value); if (lastGL == '>') l.Insert(0, dt); else l.Add(dt); } } } } } return l; } } /// /// Represents the Suppress element /// [XmlRoot("Suppress")] public class CSuppress : CSelect { /// /// Initializes a new instance of the class. /// /// The parent. /// The path. public CSuppress(CQuery parent, string path) : base(parent, path) { IsSelect = false; } /// /// Initializes a new instance of the class. /// protected CSuppress() { IsSelect = false; } } /// /// Represents the event time creation values /// public class CTimeCreated { /// /// The low /// public DateTime? low, high; /// /// The span /// public System.TimeSpan? span; /// /// Initializes a new instance of the class. /// /// The last. public CTimeCreated(TimeSpan last) { span = last; } /// /// Initializes a new instance of the class. /// /// The start. /// The end. public CTimeCreated(DateTime? start, DateTime? end) { low = start; high = end; } /// /// Gets a value indicating whether this instance has dates. /// /// /// true if this instance has dates; otherwise, false. /// public bool HasDates { get { return !span.HasValue; } } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override string ToString() { if (this.span.HasValue) { if (this.span.Value != TimeSpan.Zero && this.span.Value != TimeSpan.MaxValue) return string.Format("timediff(@SystemTime) <= {0}", span.Value.TotalMilliseconds); } else { string d1 = low.HasValue ? string.Format("@SystemTime >= '{0}'", low.Value) : null; string d2 = high.HasValue ? string.Format("@SystemTime <= '{0}'", high.Value) : null; if (low.HasValue && !high.HasValue) return d1; else if (!low.HasValue && high.HasValue) return d2; else if (low.HasValue && high.HasValue) return string.Concat(d1, " and ", d2); } return null; } } } internal class Serializer where T : class { private static readonly XmlSerializerNamespaces ns; private XmlSerializer ser; static Serializer() { ns = new XmlSerializerNamespaces(); ns.Add("", ""); } public Serializer() { ser = new XmlSerializer(typeof(T)); } public T Deserialize(string xml) { return this.Deserialize(XmlReader.Create(new System.IO.StringReader(xml))); } public T Deserialize(XmlReader reader) { return (T)ser.Deserialize(reader); } public string Serialize(T obj) { var writer = new StringBuilder(); using (var xmlW = XmlWriter.Create(writer, new XmlWriterSettings { OmitXmlDeclaration = true, Indent = true })) { ser.Serialize(xmlW, obj, ns); xmlW.Flush(); return writer.ToString(); } } public void Serialize(XmlWriter writer, T obj) { ser.Serialize(writer, obj, ns); } } } }