I’m not sure how best to share it, so I’m going to do a write up and attach the files.
Here is the BAQ Portion
Create the below Calculated Fields in the BAQ.
Set the BAQ to Updatable, Advanced BPM Update Only.
Query Phrase:
/*
* Disclaimer!!!
* This is not a real query being executed, but a simplified version for general vision.
* Executing it with any other tool may produce a different result.
*/
select
(null) as [Calculated_RowNumber],
(null) as [Calculated_ProcessedRowNumber],
(null) as [Calculated_Level],
(null) as [Calculated_DateAndTime],
(null) as [Calculated_Source],
(null) as [Calculated_EventID],
(null) as [Calculated_TaskCategory],
(null) as [Calculated_General],
(null) as [Calculated_Details],
(null) as [Calculated_PreSelectXPath],
(DateAndTime) as [Calculated_DateGTE],
(DateAndTime) as [Calculated_DateLTE],
(null) as [Calculated_EventLogName],
(null) as [Calculated_MaxReturnRecords],
(null) as [Calculated_TaskID],
(null) as [Calculated_MaxProcessRecords],
(null) as [Calculated_ReplaceXPath],
(null) as [Calculated_PrettyXML]
Open the Actions, Run BPM Designer, and add a new Base Processing method on GetList.
Mine is called “GetEventLogs”, because it… “Gets Event Logs”
Enable it, and choose Design.
Add a custom code block, and open it.
In the “Usings & References…” area, add the following to “Usings”
using System.Reflection;
using System.Xml.Linq;
using System.Text.RegularExpressions;
Here is the code for the main section, add it, and save all the way down.
/*******************************************************************************
* Title: GetEventLogs GetList UBAQ BPM
* Author: Kevin Lincecum
* Contact: klincecum @ this "moc.smlifesm" backwards lol
*
* License: Free (None) - (Mention me if you want to be cool.)
*******************************************************************************/
//Place these usings in "Usings & References..."
//using System.Reflection;
//using System.Xml.Linq;
//using System.Text.RegularExpressions;
//Just Debug/Messaging and Shortcut Variables
string retString = "";
string nl = Environment.NewLine;
//Damn Zulu Time
Func<DateTime, string> GetZuluDateTimeString = (dateTimeToConvert) =>
{
return DateTime.Parse(dateTimeToConvert.ToString()).ToUniversalTime().ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fff'Z'");
};
//Check Where Clause Function Dictionary to see if the row fields match the tracker fields from the dashboard.
Func<Dictionary<string, dynamic>, List<string>, ResultsUbaqRow, bool> DoesRowMeetCriteria = (funcDict, excludedFieldsList, baqRow) =>
{
bool retVal = true; //Assume row matches
try
{
foreach(var col in result.Results.Columns)
{
string fieldName = col.ColumnName;
var fieldValue = baqRow[fieldName];
bool fldExcluded = false;
if(excludedFieldsList.Contains(fieldName)) fldExcluded = true; //Exclude our tracker fields we are using for variables
if( fldExcluded == false )
{
if(funcDict.Keys.Contains(fieldName))
{
//Run the compare function in the dictionary for the field we are on
if (funcDict[fieldName].matchingFunction(fieldValue, funcDict[fieldName].storedCompareData) == false)
{
retVal = false; //Row does not match a condition
break; //Exit For Loop (no reason to continue)
}
}
}
}//foreach
}//try
catch (Exception ex)
{
retString += "DoesRowMeetCriteria: Main" + nl;
retString += ex.Message + nl;
//OMG, always check for null on InnerException if you like your sanity. Ever had an error in your error catcher.....not fun;
retString += ex.InnerException.Message != null ? ex.InnerException.Message + nl : "";
}//catch
return retVal; //Return Match or No Match
};
//This monstrosity builds the Where Clause Function Dictionary. A dictionary object containing a key(fieldName), and a dynamic Func<T> delegate to compare matches from the filter(tracker) fields.
Func<List<string>, Dictionary<string, dynamic>> BuildFunctionDictionaryForWhereClauses = (excludedFieldsList) =>
{
Dictionary<string, dynamic> funcDict = new Dictionary<string, dynamic>();
var whereRows = (from whereRow in executionParams.ExecutionSetting
where whereRow.Name == "Where"
select whereRow);
//We have whereRows! Yay!
if(whereRows.Count() > 0)
{
try
{
var wRow = whereRows.FirstOrDefault();
string[] whereClauseArray = wRow.Value.Split(new[] {"AND"}, StringSplitOptions.None);
foreach (var wClause in whereClauseArray)
{
//Break out Field Names, Operands, and Values.
//I had some regular expressions here, but this is easier to understand.
string fieldName = wClause.Trim().Split(' ')[0].Replace("(","");
string fieldOperand = wClause.Trim().Split(' ')[1];
string fieldValue = wClause.Trim().Split(new[] {"N'"}, StringSplitOptions.None)[1].Replace("')", "");
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//retString += $"{wClause.Trim()}\n";
//retString += $"{fieldName} {fieldOperand} {fieldValue}\n";
//
//Just found a crappy bug "undocumented feature?" that I cannot fix, as it's not mine.
//If the where clause from the tracker has a period in it, like say you were searching for ".NET", the where clause comes in like '%NET' instead of '.NET', so it would search
//for Ends With "NET".
//Tried to escape the period in the tracker, but doesn't work.
//Will use this as a workaround, could add others as found.
if(fieldValue.ToLower().Contains("<period>") == true) fieldValue = Regex.Replace(fieldValue, "<period>", ".", RegexOptions.IgnoreCase);
//
//retString += $"{fieldValue}\n";
//
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
bool fldExcluded = false;
if(excludedFieldsList.Contains(fieldName)) fldExcluded = true; //Exclude our tracker fields we are using for variables
if( fldExcluded == false )
{
//OK, what this switch case does is determine what operand we are working with, LIKE, Greater Than, Equality, etc. It then adds a function to a dictionary
//as a dynamic object with a custom comparator. We can call this dictionary function with the field name, and the data we want to validate.
//Functions return true on validate.
switch (fieldOperand)
{
case "LIKE":
//Strings Only
if (fieldValue.EndsWith("%") && !fieldValue.StartsWith("%")) //Starts With
{
string fieldValueTemp = fieldValue.Replace("%", "");
//Define function delegate comparator, "comp" is stored in the dictionary, "data" is the data we are validating
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is string) { retValue = ((string)data).StartsWith((string)comp); }
return retValue;
};
//retString += "StartsWith" + nl;
//We actually store an object in the dictionary, with two properties, the "fieldValue" from our tracker field, and the matching function.
dynamic compareObject = new { storedCompareData = fieldValueTemp, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
continue;
}
if (fieldValue.StartsWith("%") && !fieldValue.EndsWith("%")) //Ends With
{
string fieldValueTemp = fieldValue.Replace("%", "");
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is string) { retValue = ((string)data).EndsWith((string)comp); }
return retValue;
};
//retString += "EndsWith" + nl;
dynamic compareObject = new { storedCompareData = fieldValueTemp, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
continue;
}
if ( (fieldValue.StartsWith("%") && fieldValue.EndsWith("%")) || !fieldValue.Contains("%")) //Matches/Contains
{
string fieldValueTemp = fieldValue.Replace("%", "");
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is string) { retValue = ((string)data).Contains((string)comp); }
return retValue;
};
//retString += "Matches" + nl;
dynamic compareObject = new { storedCompareData = fieldValueTemp, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
continue;
}
break;
case "<>": //Not Equal
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data != Int32.Parse(comp); }
if(data is DateTime){ retValue = data != DateTime.Parse(comp); }
if(data is string) { retValue = data != comp; }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
case "<": //Less Than
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data < Int32.Parse(comp); }
if(data is DateTime){ retValue = data < DateTime.Parse(comp); }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
case "<=": //Less Than or Equal To
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data <= Int32.Parse(comp); }
if(data is DateTime){ retValue = data <= DateTime.Parse(comp); }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
case ">": //Greater Than
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data > Int32.Parse(comp); }
if(data is DateTime){ retValue = data > DateTime.Parse(comp); }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
case ">=": //Greater Than Or Equal To
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data >= Int32.Parse(comp); }
if(data is DateTime){ retValue = data >= DateTime.Parse(comp); }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
default: //Equals
{
Func<dynamic, dynamic, bool> matchFunc = (data, comp) =>
{
bool retValue = false;
if(data is Int32) { retValue = data == Int32.Parse(comp); }
if(data is DateTime){ retValue = data == DateTime.Parse(comp); }
if(data is string) { retValue = data == comp; }
return retValue;
};
dynamic compareObject = new { storedCompareData = fieldValue, matchingFunction = matchFunc };
funcDict.Add(fieldName, compareObject);
}
break;
}//switch case
}
}//Main For Loop
}
catch (Exception ex)
{
retString += "BuildFunctionDictionaryForWhereClauses: Main" + nl;
retString += ex.Message + nl;
//OMG, always check for null on InnerException if you like your sanity. Ever had an error in your error catcher.....not fun;
retString += ex.InnerException.Message != null ? ex.InnerException.Message + nl : "";
}
}
return funcDict;
};//BuildFunctionDictionaryForWhereClauses
//Get our config variables from the tracker fields
Func<string, string> GetConfigValueFromWhereClause = (configField) =>
{
string retVal = "";
var whereRows = (from whereRow in executionParams.ExecutionSetting
where whereRow.Name == "Where"
select whereRow);
if(whereRows.Count() > 0)
{
try
{
var wRow = whereRows.FirstOrDefault();
string[] whereClauseArray = wRow.Value.Split(new[] {"AND"}, StringSplitOptions.None);
foreach (var wClause in whereClauseArray)
{
//Break out Field Names, Operands, and Values.
//I had some regular expressions here, but this is easier to understand.
string fieldName = wClause.Trim().Split(' ')[0].Replace("(","");
//string fieldOperand = wClause.Trim().Split(' ')[1]; //Not needed here
string fieldValue = wClause.Trim().Split(new[] {"N'"}, StringSplitOptions.None)[1].Replace("')", "");
if(fieldName == configField)
{
return fieldValue;
}
}
}
catch(Exception ex)
{
retString += ex.Message + nl;
}
}
return retVal;
};//GetConfigValueFromWhereClauses
//The meat and potatoes. Read the EventLogs from the Operating System. Could be expanded to read event log files with a little work.
Func<string, string, bool, bool, DateTime, DateTime, Dictionary<string, dynamic>, List<string>, int, int, List<ResultsUbaqRow>> GetEventLog = (eventLogName, preSelectXPath, replaceXPath, prettyXML, startDateTime, endDateTime, wcFuncDict, excludedFieldsList, maxRecordsToProcess, maxRecordsToReturn) =>
{
List<ResultsUbaqRow> rowsToReturn = new List<ResultsUbaqRow>(); //Rows we will add to result.Results
try
{
//Event Log Data is Zulu Time
string zuluStartDateTime = GetZuluDateTimeString(startDateTime);
string zuluEndDateTime = GetZuluDateTimeString(endDateTime );
//No reference available in "Usings & References - Assemblies", so we will load by reflection.
Assembly System_Diagnostics_EventLog = Assembly.Load("System.Diagnostics.EventLog");
//The types we will need: Extracted from the Assembly
Type typeEventLogReader = System_Diagnostics_EventLog.GetType("System.Diagnostics.Eventing.Reader.EventLogReader");
Type typeEventLogQuery = System_Diagnostics_EventLog.GetType("System.Diagnostics.Eventing.Reader.EventLogQuery");
Type typeEventLogRecord = System_Diagnostics_EventLog.GetType("System.Diagnostics.Eventing.Reader.EventLogRecord");
Type typePathTypeEnum = System_Diagnostics_EventLog.GetType("System.Diagnostics.Eventing.Reader.PathType");
//We need this Enum - Enums in reflection suck
object PathTypeEnum_LogName = Enum.Parse(typePathTypeEnum, "LogName");
//EventLogRecords are selected via XPath Queries, we will use the dates from the tracker fields as a base XPath Query
string xPathQuery = $"*[System[TimeCreated[@SystemTime >= '{zuluStartDateTime}']]] and *[System[TimeCreated[@SystemTime <= '{zuluEndDateTime}']]]";
//Append/Replace xPathQuery with one from tracker field
if(preSelectXPath != null)
{
if(preSelectXPath.Trim() != String.Empty)
{
xPathQuery = replaceXPath == true ? preSelectXPath : $"{xPathQuery} and {preSelectXPath}";
}
}
//retString += xPathQuery + nl;
//EventLogQuery Object Creation
dynamic eventLogQuery = Activator.CreateInstance(typeEventLogQuery, new object[]{eventLogName, PathTypeEnum_LogName, xPathQuery});
eventLogQuery.ReverseDirection = true; //Lets read events in reverse order
//EventLogReader - This puppy does our reading of the Event Logs
dynamic eventLogReader = Activator.CreateInstance(typeEventLogReader, eventLogQuery);
int recordsProcessed = 0;
int recordsKept = 0;
for(dynamic eventLogRecord = eventLogReader.ReadEvent(); eventLogRecord != null; eventLogRecord = eventLogReader.ReadEvent())
{
try
{
if(recordsProcessed >= maxRecordsToProcess) break; //We're done
if(recordsKept >= maxRecordsToReturn ) break; //We're done
string taskDisplayName = eventLogRecord.TaskDisplayName != null ? eventLogRecord.TaskDisplayName : "";
string formatDescription = "";
string xmlDetail = "";
try{ formatDescription = eventLogRecord.FormatDescription(); }catch{} //These error out if unavailable (probably a more elegant way to ignore)
try{ xmlDetail = eventLogRecord.ToXml(); }catch{} //These error out if unavailable (probably a more elegant way to ignore)
if(prettyXML == true && xmlDetail != String.Empty) xmlDetail = XDocument.Parse(xmlDetail).ToString();
//Build a row with our Event Log Data
ResultsUbaqRow recordRow = new ResultsUbaqRow
{
Calculated_Level = eventLogRecord.LevelDisplayName.ToString(),
Calculated_DateAndTime = eventLogRecord.TimeCreated,
Calculated_EventID = eventLogRecord.Id,
Calculated_Source = eventLogRecord.ProviderName,
Calculated_TaskID = Convert.ToInt32(eventLogRecord.Task),
Calculated_TaskCategory = taskDisplayName,
Calculated_General = formatDescription,
Calculated_Details = xmlDetail,
Calculated_EventLogName = eventLogName,
Calculated_RowNumber = rowsToReturn.Count + 1,
Calculated_ProcessedRowNumber = recordsProcessed + 1,
RowIdent = DateTime.Now.Ticks.ToString(),
RowMod = "A"
};
recordsProcessed++; //In case you limited it...
//Check if the record returned meets the criteria set in the tracker fields from the dashboard
if(DoesRowMeetCriteria(wcFuncDict, excludedFieldsList, recordRow) == true)
{
rowsToReturn.Add(recordRow); //Keep the row if it meets criteria
recordsKept++; //In case you limited it...
}
//eventLogRecord.Dispose(); //I don't think we need it and it causes side effects
}
catch (Exception ex)
{
retString += "GetEventLog: In Loop" + nl;
retString += ex.Message + nl;
//OMG, always check for null on InnerException if you like your sanity. Ever had an error in your error catcher.....not fun;
retString += ex.InnerException.Message != null ? ex.InnerException.Message + nl : "";
eventLogReader.Dispose();
return rowsToReturn;
}
}//for loop eventLogRecords
eventLogReader.Dispose();
//retString += recordsProcessed.ToString() + nl;
return rowsToReturn; //return our list of records
}
catch (Exception ex)
{
retString += "GetEventLog: Main" + nl;
retString += ex.Message + nl;
//OMG, always check for null on InnerException if you like your sanity. Ever had an error in your error catcher.....not fun;
retString += ex.InnerException.Message != null ? ex.InnerException.Message + nl : "";
return rowsToReturn;
}
};//Func GetEventLog
//*****************************************************************************************************
//Begin Processing Here:
//This is the list of fields from the tracker we need to exclude from matching fields.
//We are using these as config values, outside the where clause matching function dictionary
List<string> excludeFields = new List<string>();
excludeFields.Add("Calculated_EventLogName");
excludeFields.Add("Calculated_PreSelectXPath");
excludeFields.Add("Calculated_MaxProcessRecords");
excludeFields.Add("Calculated_MaxReturnRecords");
excludeFields.Add("Calculated_ReplaceXPath");
excludeFields.Add("Calculated_PrettyXML");
//We exclude these as well, because they are used in the XPath query, not our where clause comparer
excludeFields.Add("Calculated_DateGTE");
excludeFields.Add("Calculated_DateLTE");
//Define and Build the Where Clause Function Dictionary - It checks fields for matches with the criteria from the dashboard tracker
Dictionary<string, dynamic> whereClauseFunctionDictionary = BuildFunctionDictionaryForWhereClauses(excludeFields);
//Get EventLogName (System, Application) "Security" will not work in cloud, no access
string EventLogName = GetConfigValueFromWhereClause("Calculated_EventLogName");
EventLogName = EventLogName == "" ? "System" : EventLogName;
//Get Dates for events between
string logDateGTEString = GetConfigValueFromWhereClause("Calculated_DateGTE");
string logDateLTEString = GetConfigValueFromWhereClause("Calculated_DateLTE");
//Processed Dates
//string TheBeginningOfTime = "1979-11-02T00:00:00.000000000Z"; //My Birthday
//DateTime logDateGTE = logDateGTEString == "" ? DateTime.Parse(TheBeginningOfTime) : DateTime.Parse(logDateGTEString);
DateTime logDateGTE = logDateGTEString == "" ? DateTime.Now.AddDays(-1) : DateTime.Parse(logDateGTEString); //Default: Now - 1 Day
DateTime logDateLTE = logDateLTEString == "" ? DateTime.Now : DateTime.Parse(logDateLTEString);
//How many records we will PROCESS in total, whether it matches criteria or not (MAX)
int MaxRecordsToProcess = 0;
try
{
MaxRecordsToProcess = Convert.ToInt32(GetConfigValueFromWhereClause("Calculated_MaxProcessRecords"));
}
catch
{
MaxRecordsToProcess = 0;
}
MaxRecordsToProcess = MaxRecordsToProcess <= 0 ? 2147483647 : MaxRecordsToProcess;
//How many records we will RETURN in total (MAX)
int MaxRecordsToReturn = 0;
try
{
MaxRecordsToReturn = Convert.ToInt32(GetConfigValueFromWhereClause("Calculated_MaxReturnRecords"));
}
catch
{
MaxRecordsToReturn = 0;
}
MaxRecordsToReturn = MaxRecordsToReturn <= 0 ? 2147483647 : MaxRecordsToReturn;
string xPathPreSelect = GetConfigValueFromWhereClause("Calculated_PreSelectXPath");
bool ReplaceXPath = Convert.ToBoolean(GetConfigValueFromWhereClause("Calculated_ReplaceXPath"));
bool PrettyXML = Convert.ToBoolean(GetConfigValueFromWhereClause("Calculated_PrettyXML"));
//Define & Grab the list of events
List<ResultsUbaqRow> eventRecords = GetEventLog(EventLogName, xPathPreSelect, ReplaceXPath, PrettyXML, logDateGTE, logDateLTE, whereClauseFunctionDictionary, excludeFields, MaxRecordsToProcess, MaxRecordsToReturn);
result.Results.Clear();//Make sure return data is empty
if(eventRecords.Count > 0)
{
result.Results.AddRange(eventRecords); //Add records from List
}
//Display errors/information
if(!String.IsNullOrEmpty(retString)) InfoMessage.Publish(retString);
Your BAQ should be complete.