Imports System.Text.RegularExpressions Imports System.Windows.Forms Imports DigitalData.Modules.Database Imports DigitalData.Modules.Logging Imports DigitalData.Modules.Windows Imports DigitalData.Modules.ZooFlow.Params Imports DigitalData.Modules.ZooFlow.State Imports DigitalData.Modules.Patterns Imports DigitalData.Modules.Base Public Class ProfileFilter Inherits BaseClass Private ReadOnly _ProfileMatch As ProfileMatch Private ReadOnly _ProfileTable As DataTable Private ReadOnly _ProcessTable As DataTable Private ReadOnly _WindowTable As DataTable Private ReadOnly _ControlTable As DataTable Private ReadOnly _Profiles As List(Of ProfileData) Private ReadOnly _TreeView As TreeView Public Enum ImageIndex Root Profile Clipboard Process Window Control End Enum Public Const ERROR_LOADING_COUNT_SQL_FOR_DATA_SEARCH = "ERROR_LOADING_COUNT_SQL_FOR_DATA_SEARCH" Public Const ERROR_LOADING_COUNT_SQL_FOR_DOC_SEARCH = "ERROR_LOADING_COUNT_SQL_FOR_DOC_SEARCH" Public Const ERROR_EXECUTING_COUNT_SQL_FOR_DATA_SEARCH = "ERROR_EXECUTING_COUNT_SQL_FOR_DATA_SEARCH" Public Const ERROR_EXECUTING_COUNT_SQL_FOR_DOC_SEARCH = "ERROR_EXECUTING_COUNT_SQL_FOR_DOC_SEARCH" Public Const NODE_ROOT = "ROOT" Public Const PROCESS_NAME_CATCHALL = "_CATCHALL_" Public ReadOnly Property Profiles As List(Of ProfileData) Get Return _Profiles End Get End Property Public Sub New(LogConfig As LogConfig, ProfileDatatable As DataTable, ProcessTable As DataTable, WindowDatatable As DataTable, ControlDatatable As DataTable, TreeView As TreeView) MyBase.New(LogConfig) Try LogConfig = LogConfig Logger = LogConfig.GetLogger() Logger.Debug("Initializing Profile Filter") Logger.Debug("Initializing Profile Data") _ProfileTable = ProfileDatatable _ProcessTable = ProcessTable _WindowTable = WindowDatatable _ControlTable = ControlDatatable Logger.Debug("Initializing Profile Debugging") _TreeView = TreeView _ProfileMatch = New ProfileMatch(LogConfig) Logger.Debug("Transforming Profiles") _Profiles = TransformProfiles() Catch ex As Exception Logger.Error(ex) Throw ex End Try End Sub Public Function ToList() As List(Of ProfileData) Return _Profiles End Function Public Function LogRemainingProfiles(Profiles As List(Of ProfileData), StepDescription As String) As List(Of ProfileData) Logger.Debug("Profiles remaining after Step {0}: {1}", StepDescription, Profiles.Count) Return Profiles End Function Public Function FilterProfilesByClipboardRegex(Profiles As List(Of ProfileData), ClipboardContents As String) As List(Of ProfileData) Dim oFilteredProfiles As New List(Of ProfileData) Dim oRootNode As TreeNode = New TreeNode() With { .Text = $"Suche nach '{ClipboardContents}'", .Tag = NODE_ROOT, .ImageIndex = ImageIndex.Root } _TreeView.Nodes.Clear() _TreeView.Nodes.Add(oRootNode) For Each oProfile In Profiles Logger.Debug("Current Profile: {0}", oProfile.Name) Dim oNode = _ProfileMatch.NewProfileNode(oProfile) oRootNode.Nodes.Add(oNode) Try Dim oRegex As New Regex(oProfile.Regex) Dim oMatch = oRegex.Match(ClipboardContents) If oMatch.Success Then Logger.Debug("FilterProfilesByClipboardRegex: Clipboard Regex Matched: {0}", ClipboardContents) Logger.Info("FilterProfilesByClipboardRegex: Profile {0} matched!", oProfile.Name) oFilteredProfiles.Add(oProfile) oProfile.IsMatched = True Dim oSubnode = _ProfileMatch.NewClipboardRegexNode(oProfile, oMatch.Success) oNode.Nodes.Add(oSubnode) End If Catch ex As Exception Logger.Warn("Regex '{0}' could not be processed for input '{1}'", oProfile.Regex, ClipboardContents) Logger.Error(ex) End Try Next Return oFilteredProfiles End Function Public Function FilterProfilesByProcess(Profiles As List(Of ProfileData), ProcessName As String) As List(Of ProfileData) Dim oFilteredProfiles As New List(Of ProfileData) Try For Each oProfile As ProfileData In Profiles Dim oGuid = oProfile.Guid Dim oProcesses As New List(Of ProcessData) For Each oProcess As ProcessData In oProfile.Processes If oProcess.PROFILE_ID <> oGuid Then Continue For End If Dim oIsMatch = oProcess.ProcessName.ToLower = ProcessName.ToLower Dim oIsCatchAll As Boolean = False ' Catch-all processname If oProcess.ProcessName.ToLower = PROCESS_NAME_CATCHALL.ToLower Then oIsMatch = True oIsCatchAll = True End If Dim oParent = _ProfileMatch.FindNodeByTag(_TreeView.Nodes, oProfile.Name & "-REGEX") If oParent IsNot Nothing Then Dim oNode = _ProfileMatch.NewProcessNode(oProfile, oProcess, oIsMatch, oIsCatchAll) oParent.Nodes.Add(oNode) End If Logger.Debug($"FilterProfilesByProcess: Checking Profile: {oProfile.Name}") If oIsMatch Then Logger.Debug($"Processname Matched: {oProcess.ProcessName}") oFilteredProfiles.Add(oProfile) Logger.Info("FilterProfilesByProcess: Profile {0} matched!", oProfile.Name) ' Set Process matched oProcess.IsMatched = True oProcesses.Add(oProcess) ' Set Profile matched oProfile.IsMatched = True oProfile.MatchedProcessID = oProcess.Guid End If If oIsCatchAll Then oProfile.IsCatchAll = True End If Next If oFilteredProfiles.Count > 0 Then oProfile.Processes = oProcesses End If Next Return oFilteredProfiles Catch ex As Exception Logger.Warn("Unexpected error in FilterProfilesByProcess...") Logger.Error(ex) Return Profiles End Try End Function Public Function FilterWindowsByWindowTitleRegex(Profiles As List(Of ProfileData), WindowTitle As String) As List(Of ProfileData) Dim oProfiles As New List(Of ProfileData) For Each oProfile As ProfileData In Profiles Logger.Debug("Checking WindowDefinition for profile: {0}...", oProfile.Name) Dim oFilteredWindows As New List(Of WindowData) ' Create list of all controls that match the current process and matched window For Each oWindow As WindowData In oProfile.Windows If oWindow.WindowProcessID = oProfile.MatchedProcessID Then oFilteredWindows.Add(oWindow) End If Next ' If Profile has no windows at all, it automatically matches If oFilteredWindows.Count = 0 Then Logger.Debug("Profile has no Windows assigned, automatic MATCH") oProfile.IsMatched = True oProfiles.Add(oProfile) Continue For End If Dim oWindows As New List(Of WindowData) For Each oWindowDef As WindowData In oFilteredWindows If oWindowDef.WindowProcessID <> oProfile.MatchedProcessID Then Continue For Try If oWindowDef.Regex = String.Empty Then oProfile.MatchedWindowID = oWindowDef.Guid oWindowDef.IsMatched = True oWindows.Add(oWindowDef) Exit For End If Dim oRegex As New Regex(oWindowDef.Regex) Dim oMatch = oRegex.Match(WindowTitle) If oMatch.Success Then Logger.Debug("MATCH on WindowTitle: {0}", WindowTitle) oProfile.MatchedWindowID = oWindowDef.Guid oWindowDef.IsMatched = True oWindows.Add(oWindowDef) Else Logger.Debug("NO MATCH on WindowTitle: {0} - REGEX: {2}", WindowTitle, oWindowDef.Regex) End If Dim oParent = _ProfileMatch.FindNodeByTag(_TreeView.Nodes, oWindowDef.WindowProcessID & "-PROCESS") If oParent IsNot Nothing Then Dim oNode = _ProfileMatch.NewWindowNode(oProfile, oWindowDef, oMatch.Success) oParent.Nodes.Add(oNode) End If Catch ex As Exception Logger.Warn("Regex '{0}' could not be processed for input '{1}'", oWindowDef.Regex, WindowTitle) Logger.Error(ex) End Try Next ' If Profile has windows that matched, the profile matches If oWindows.Count > 0 Then oProfile.Windows = oWindows oProfile.IsMatched = True oProfiles.Add(oProfile) Logger.Info("FilterWindowsByWindowTitleRegex: Profile {0} matched!", oProfile.Name) End If If oProfile.IsCatchAll Then oProfile.IsMatched = True oProfiles.Add(oProfile) Logger.Info("FilterWindowsByWindowTitleRegex: Profile {0} is marked catchall!", oProfile.Name) End If Next ' Add catch-all profiles to the list oProfiles = AddCatchAllProfiles(Profiles, oProfiles) Return oProfiles End Function Public Function FilterProfilesByFocusedControl(Profiles As List(Of ProfileData), ClipboardContents As String, WindowHandle As IntPtr) As List(Of ProfileData) Dim oProfiles As New List(Of ProfileData) Dim oWindow As New Window(LogConfig) Dim oFocusedWindow As Window.WindowInfo Try oFocusedWindow = oWindow.GetWindowInfo() If oFocusedWindow Is Nothing Then Throw New ApplicationException("Window Information is Empty") End If Catch ex As Exception Logger.Error(ex) Return Profiles End Try For Each oProfile As ProfileData In Profiles Dim oFilteredControls As New List(Of ControlData) ' Create list of all controls that match the current process and matched window For Each oControl As ControlData In oProfile.Controls If oProfile.MatchedWindowID = oControl.WindowId And oFocusedWindow.ProcessName = oControl.ProcessName Then oFilteredControls.Add(oControl) End If Next ' If Profile has no controls at all, it automatically matches If oFilteredControls.Count = 0 Then Logger.Debug("Profile has no Controls assigned, automatic MATCH") oProfiles.Add(oProfile) Continue For End If Dim oMatchingControls As New List(Of ControlData) For Each oControl In oFilteredControls Dim oFound As Boolean = False ' If control name is empty, use coordinates If oControl.ControlName Is Nothing OrElse oControl.ControlName = String.Empty Then Dim oControlBounds As Dictionary(Of String, Window.RectangleInfo) Try oControlBounds = oWindow.GetFocusedControlLocation(WindowHandle) For Each oItem As KeyValuePair(Of String, Window.RectangleInfo) In oControlBounds Select Case oItem.Key Case "TopLeft" If oControl.TopLeft.Top = oItem.Value.Top And oControl.TopLeft.Left = oItem.Value.Left Then oFound = True End If Case "TopRight" If oControl.TopRight.Top = oItem.Value.Top And oControl.TopLeft.Right = oItem.Value.Right Then oFound = True End If Case "BottomLeft" If oControl.BottomLeft.Bottom = oItem.Value.Bottom And oControl.TopLeft.Left = oItem.Value.Left Then oFound = True End If Case "BottomRight" If oControl.BottomRight.Bottom = oItem.Value.Bottom And oControl.TopLeft.Right = oItem.Value.Right Then oFound = True End If End Select Next Catch ex As Exception Logger.Warn("Error while getting focused control location") Logger.Error(ex) End Try Else Dim oControlName As String = oControl.ControlName Try Dim oFocusedControl = oWindow.GetFocusedControl(WindowHandle) If oFocusedControl.ControlName = oControlName Then oFound = True End If Catch ex As Exception Logger.Warn("Error while getting focused control name") Logger.Error(ex) End Try End If If oFound Then Logger.Debug("Control {0} has MATCH", oControl.ControlName) oMatchingControls.Add(oControl) End If Dim oParent = _ProfileMatch.FindNodeByTag(_TreeView.Nodes, oControl.WindowId & "-WINDOW") If oParent IsNot Nothing Then Dim oNode = _ProfileMatch.NewControlNode(oProfile, oControl, oFound) oParent.Nodes.Add(oNode) End If Next ' If Profile has controls that matched, the profile matches If oMatchingControls.Count > 0 Then Logger.Debug("Profile has {0} MATCHING Controls", oMatchingControls.Count) oProfile.Controls = oMatchingControls oProfiles.Add(oProfile) Logger.Info("FilterProfilesByFocusedControl: Profile {0} matched!", oProfile.Name) End If Next ' Add catch-all profiles to the list oProfiles = AddCatchAllProfiles(Profiles, oProfiles) Return oProfiles End Function Public Function FilterProfilesBySearchResults(Profiles As List(Of ProfileData), Database As MSSQLServer, User As UserState, ClipboardContents As String) As List(Of ProfileData) Dim oProfiles As New List(Of ProfileData) For Each oProfile In Profiles Logger.Debug($"Checking SearchResults on ProfileID: {oProfile.Guid}") Dim oResultDocs As Integer = 0 Dim oResultData As Integer = 0 Dim oPatterns As New ClassPatterns(LogConfig) Dim oDTDataSearches As DataTable Dim oDTDocSearches As DataTable Try oDTDataSearches = Database.GetDatatable($"SELECT COUNT_COMMAND, CONN_ID FROM TBCW_PROF_DATA_SEARCH WHERE ACTIVE = 1 AND PROFILE_ID = {oProfile.Guid}") Catch ex As Exception oProfile.ErrorMessage = ERROR_LOADING_COUNT_SQL_FOR_DATA_SEARCH Logger.Error(ex) Continue For End Try Try oDTDocSearches = Database.GetDatatable($"SELECT COUNT_COMMAND, CONN_ID FROM TBCW_PROF_DOC_SEARCH WHERE ACTIVE = 1 AND PROFILE_ID = {oProfile.Guid}") Catch ex As Exception oProfile.ErrorMessage = ERROR_LOADING_COUNT_SQL_FOR_DOC_SEARCH Logger.Error(ex) Continue For End Try For Each oRow As DataRow In oDTDataSearches.Rows Dim oCountCommand = String.Empty Dim oCountConnectionId = String.Empty Try oCountCommand = oRow.ItemEx("COUNT_COMMAND", String.Empty) oCountConnectionId = oRow.ItemEx("CONN_ID", 0) If oCountCommand = String.Empty Then Logger.Debug("Data SQL Query is empty. Skipping.") Continue For End If Logger.Debug("Count-Command DATA BEFORE Replace: [{0}]", oCountCommand) oCountCommand = oPatterns.ReplaceInternalValues(oCountCommand) oCountCommand = oPatterns.ReplaceUserValues(oCountCommand, User) oCountCommand = oPatterns.ReplaceClipboardContents(oCountCommand, ClipboardContents) Logger.Debug("Count-Command DATA before execute: [{0}]", oCountCommand) Dim oConnectionString = ProfileUtils.GetConnectionString(Database, oCountConnectionId) oResultData += ObjectEx.NotNull(Of Integer)(Database.GetScalarValueWithConnection(oCountCommand, oConnectionString), 0) Logger.Debug("Datarows returned from search: [{0}]", oResultData) Catch ex As Exception oProfile.ErrorMessage = ERROR_EXECUTING_COUNT_SQL_FOR_DATA_SEARCH Logger.Error(ex) Logger.Warn("Invalid SQL Query for Counting Data in Profile [{0}]: [{1}]", oProfile.Guid, oCountCommand) End Try Next For Each oRow As DataRow In oDTDocSearches.Rows Dim oCountCommand = String.Empty Dim oCountConnectionId = String.Empty Try oCountCommand = oRow.ItemEx("COUNT_COMMAND", String.Empty) oCountConnectionId = oRow.ItemEx("CONN_ID", 0) If oCountCommand = String.Empty Then Logger.Debug("Document SQL Query is empty. Skipping.") Continue For End If Logger.Debug("Count-Command for Documents BEFORE Replace: [{0}]", oCountCommand) oCountCommand = oPatterns.ReplaceInternalValues(oCountCommand) oCountCommand = oPatterns.ReplaceUserValues(oCountCommand, User) oCountCommand = oPatterns.ReplaceClipboardContents(oCountCommand, ClipboardContents) Logger.Debug("Count-Command for Documents BEFORE execute: [{0}]", oCountCommand) Dim oConnectionString = ProfileUtils.GetConnectionString(Database, oCountConnectionId) oResultDocs += ObjectEx.NotNull(Of Integer)(Database.GetScalarValueWithConnection(oCountCommand, oConnectionString), 0) Logger.Debug("Documents returned from search: [{0}]", oResultDocs) Catch ex As Exception oProfile.ErrorMessage = ERROR_EXECUTING_COUNT_SQL_FOR_DOC_SEARCH Logger.Error(ex) Logger.Warn("Invalid SQL Query for Counting Data in Profile [{0}]: [{1}]", oProfile.Guid, oCountCommand) End Try Next oProfile.CountData = oResultData oProfile.CountDocs = oResultDocs Next For Each oProfile In Profiles Dim oProfileNode = _ProfileMatch.FindNodeByTag(_TreeView.Nodes, oProfile.Name & "-PROFILE") Dim oCombinedResults = oProfile.CountDocs + oProfile.CountData Dim oHasResults = oCombinedResults > 0 oProfileNode.Text = $"{oProfileNode.Text} ({oCombinedResults} Ergebnisse)" oProfileNode.BackColor = _ProfileMatch.GetMatchColor(oHasResults) If oProfile.CountData > 0 Or oProfile.CountDocs > 0 Then oProfiles.Add(oProfile) Logger.Info("Profile [{0}] matched in FilterProfilesBySearchResults!", oProfile.Name) End If Next Return oProfiles End Function Public Function FilterWindowsByWindowClipboardRegex(Profiles As List(Of ProfileData), ClipboardContents As String) As List(Of ProfileData) Dim oProfiles As New List(Of ProfileData) For Each oProfile As ProfileData In Profiles Logger.Debug("Current Profile: {0}", oProfile.Name) Dim oWindows As New List(Of WindowData) For Each w As WindowData In oProfile.Windows Try If w.Regex = String.Empty Then oWindows.Add(w) End If Dim oRegex As New Regex(w.Regex) Dim oMatch = oRegex.Match(ClipboardContents) If oMatch.Success Then Logger.Debug("Window Clipboard Regex Matched: {0}", ClipboardContents) oWindows.Add(w) End If Dim oResult As TreeNode = _ProfileMatch.FindNodeByTag(_TreeView.Nodes, oProfile.Name & "-REGEX") If Not IsNothing(oResult) Then Dim oNode As New TreeNode($"MATCH on WINDOW Clipboard Regex [{w.Regex}]: {oMatch.Success}") oNode.ImageIndex = 2 oNode.Tag = oProfile.Name & "-WINDOW_REGEX" oResult.Nodes.Add(oNode) End If Catch ex As Exception Logger.Warn("Regex '{0}' could not be processed for input '{1}'", w.Regex, ClipboardContents) Logger.Error(ex) End Try Next If oWindows.Count > 0 Then oProfile.Windows = oWindows oProfiles.Add(oProfile) Logger.Info("FilterWindowsByWindowClipboardRegex: Profile {0} matched!", oProfile.Name) End If Next ' Add catch-all profiles to the list oProfiles = AddCatchAllProfiles(Profiles, oProfiles) Return oProfiles End Function Public Function ClearNotMatchedProfiles(Profiles As List(Of ProfileData)) As List(Of ProfileData) Dim oFilteredProfiles As New List(Of ProfileData) For Each oProfile In Profiles If oProfile.IsMatched Then oFilteredProfiles.Add(oProfile) End If Next Return oFilteredProfiles End Function Public Function ClearDuplicateProfiles(Profiles As List(Of ProfileData)) As List(Of ProfileData) Return Profiles. GroupBy(Function(Profile) Profile.Guid). Select(Function(GroupedProfiles) GroupedProfiles.First). ToList() End Function Private Function AddCatchAllProfiles(AllProfiles As List(Of ProfileData), FilteredProfiles As List(Of ProfileData)) As List(Of ProfileData) Dim oCatchAllProfiles As New List(Of ProfileData) For Each oProfile As ProfileData In AllProfiles If oProfile.IsCatchAll And FilteredProfiles.Contains(oProfile) = False Then oProfile.IsMatched = True oCatchAllProfiles.Add(oProfile) End If Next Return FilteredProfiles. Concat(oCatchAllProfiles). ToList() End Function Private Function TransformProfiles() As List(Of ProfileData) Try Dim oList As New List(Of ProfileData) For Each oRow As DataRow In _ProfileTable.Rows Dim oProfileId = oRow.Item("GUID") Dim oProcessList As List(Of ProcessData) = TransformProcesses(oProfileId, _ProcessTable) Dim oWindowList As List(Of WindowData) = TransformWindows(oProfileId, _WindowTable) Dim oControlList As List(Of ControlData) = TransformControls(oProfileId, _ControlTable) oList.Add(New ProfileData() With { .Guid = oRow.Item("GUID"), .Regex = oRow.Item("REGEX_EXPRESSION"), .Name = oRow.ItemEx("NAME", String.Empty), .Comment = oRow.ItemEx("COMMENT", String.Empty), .ProfileType = oRow.ItemEx("PROFILE_TYPE", String.Empty), .Processes = oProcessList, .Windows = oWindowList, .Controls = oControlList }) Next oList = oList. Distinct(). ToList() Return oList Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function Private Function TransformControls(ProfileId As Integer, ControlDatatable As DataTable) As List(Of ControlData) Try Dim oControlList As New List(Of ControlData) For Each oRow As DataRow In ControlDatatable.Rows If oRow.Item("PROFILE_ID") = ProfileId Then oControlList.Add(New ControlData() With { .Guid = oRow.Item("GUID"), .ProcessName = oRow.ItemEx("PROCESS_NAME", String.Empty), .ControlName = oRow.ItemEx("CONTROL_NAME", String.Empty), .Description = oRow.ItemEx("DESCRIPTION", String.Empty), .WindowId = oRow.Item("WINDOW_ID"), .TopLeft = New ControlBounds() With { .Left = oRow.Item("TOPLEFT_LEFT"), .Right = oRow.Item("TOPLEFT_RIGHT"), .Top = oRow.Item("TOPLEFT_TOP"), .Bottom = oRow.Item("TOPLEFT_BOTTOM") }, .TopRight = New ControlBounds() With { .Left = oRow.Item("TOPRIGHT_LEFT"), .Right = oRow.Item("TOPRIGHT_RIGHT"), .Top = oRow.Item("TOPRIGHT_TOP"), .Bottom = oRow.Item("TOPRIGHT_BOTTOM") }, .BottomLeft = New ControlBounds() With { .Left = oRow.Item("BOTTOMLEFT_LEFT"), .Right = oRow.Item("BOTTOMLEFT_RIGHT"), .Top = oRow.Item("BOTTOMLEFT_TOP"), .Bottom = oRow.Item("BOTTOMLEFT_BOTTOM") }, .BottomRight = New ControlBounds() With { .Left = oRow.Item("BOTTOMRIGHT_LEFT"), .Right = oRow.Item("BOTTOMRIGHT_RIGHT"), .Top = oRow.Item("BOTTOMRIGHT_TOP"), .Bottom = oRow.Item("BOTTOMRIGHT_BOTTOM") } }) End If Next Return oControlList Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function Private Function TransformProcesses(ProfileId As Integer, ProcessDatatable As DataTable) As List(Of ProcessData) Try Dim oProcessList As New List(Of ProcessData) For Each oRow As DataRow In ProcessDatatable.Rows If oRow.Item("PROFILE_ID") = ProfileId Then oProcessList.Add(New ProcessData() With { .Guid = oRow.Item("GUID"), .PROFILE_ID = oRow.Item("PROFILE_ID"), .ProcessName = oRow.ItemEx("PROC_NAME", String.Empty) }) End If Next oProcessList = oProcessList. Distinct(). ToList() Return oProcessList Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function Private Function TransformWindows(ProfileId As Integer, WindowDatatable As DataTable) As List(Of WindowData) Try Dim oWindowList As New List(Of WindowData) For Each oRow As DataRow In WindowDatatable.Rows If oRow.Item("PROFILE_ID") = ProfileId Then oWindowList.Add(New WindowData() With { .Guid = oRow.Item("GUID"), .WindowProcessID = oRow.Item("PROCESS_ID"), .Title = oRow.ItemEx("DESCRIPTION", String.Empty), .Regex = oRow.ItemEx("REGEX", String.Empty), .Sequence = oRow.ItemEx("SEQUENCE", 0) }) End If Next Return oWindowList Catch ex As Exception Logger.Error(ex) Throw ex End Try End Function End Class