// This stuff happens when you click the "Update config on selected computers button" private void Button_Click_3(object sender, RoutedEventArgs e) { if (SelectedComputerList.SelectedIndex < 0) { System.Windows.MessageBox.Show("Please Select a Computer"); return; } // If the selected listbox item is the tag of the config, rather than the configuration itself, pop up an error and let the user try again string selectedItem = Configs.Items[Configs.SelectedIndex].ToString(); bool containsTag = selectedItem.Contains("Tag"); if (containsTag == true) { System.Windows.MessageBox.Show("Error, Select config value, not the tag"); return; } // UpdateConfigs Regex ConfigToDeployRegEx = new Regex(@"([^\\]*)$"); Match MatchedSysmonConfig = ConfigToDeployRegEx.Match(selectedItem); System.Collections.IList // This grabs the selected computer variable ComputerSelected = SelectedComputerList.SelectedItems; // If there is no computer selected to update the configuration on, pop up an error and let the user try again if (SelectedComputerList.SelectedIndex < 0) { System.Windows.MessageBox.Show("Please Select a Computer"); return; } // Run command on whatever computers we selected - probably need a better way to do this at some point, with multiple threads etc - this is a pretty funky loop as we are doing some validation here as well foreach (object SelectedComputer in ComputerSelected) { ManagementClass processClass = new ManagementClass($@"\\{SelectedComputer}\root\cimv2:Win32_Process"); ManagementBaseObject inParams = processClass.GetMethodParameters("Create"); //Selected Sysmon Config Variable Name = FinalSysmonMatchedConfig var FinalSysmonMatchedConfig = MatchedSysmonConfig.ToString(); //Commands ran on the remote host to update the configuration file inParams["CommandLine"] = "C:\\SysmonFiles\\Sysmon.exe -c " + FinalSysmonMatchedConfig; inParams["CurrentDirectory"] = @"C:\SysmonFiles"; ManagementBaseObject outParams = processClass.InvokeMethod("Create", inParams, null); Log.Information("Updated " + SelectedComputer + " with " + FinalSysmonMatchedConfig); // XPath Query for Event ID 16s only, this is the "Sysmon config state changed" event - later we specify the log channel and extract the SHA256 value of the configuration file hash as it exists on the remote host string logQuery = "*[System[(EventID = 16)]]"; //Establish a remote event log session on the computer in this for loop EventLogSession session = new EventLogSession(SelectedComputer.ToString()); EventLogQuery query = new EventLogQuery("Microsoft-Windows-Sysmon/Operational", PathType.LogName, logQuery); query.Session = session; try { EventLogReader logReader = new EventLogReader(query); // Loop through the events that were returned in the above query for (EventRecord eventdetail = logReader.ReadEvent(); eventdetail != null; eventdetail = logReader.ReadEvent()) { // EventData variable contains the detail of each event in XML format, I tried to use LINQ to extract the XML elements instead of regex but found regex to be simpler, please don't hate me for the upcoming dirty regexes string EventData = eventdetail.ToXml(); // RegEx used to extract just the SHA256 hash from Event ID 16 Regex SHA256 = new Regex(@"[A-Fa-f0-9]{64}"); // Put the matched regex (the SHA256) hash into a variable called SHA256Value Match SHA256Value = SHA256.Match(EventData); // Another awful regex to extract the time stamp from Event ID 16 - the SHA256 value of the updated config as well as the time stamp get logged, this way you can validate that the right configuration file got pushed to the right computer Regex LoggedEventTime = new Regex(@"\d\d\d\d\-\d\d\-\d\d.\d\d\:\d\d\:\d\d\.\d\d\d"); Match MatchedLoggedEventTime = LoggedEventTime.Match(EventData); // Log showing that we found an Event ID 16 on the selected remote host, and we log the time and SHA256 value of the configuration file pushed Log.Information("Found Config Update Event on " + SelectedComputer + " Logged at " + MatchedLoggedEventTime + "." + " Updated with config file with the SHA256 Hash of: " + SHA256Value.ToString()); } } catch (Exception eventlogexception) { Log.Debug(eventlogexception.Message + ": You may have hit the update configs button on a host with Sysmon not installed"); } } }
// This takes the selected items from the "ComputerList" ListBox, loops through them and puts them into another ListBox named "SelectedComputerList" - we want to perform whatever actions we need on this list public void SelectComputers_Click(object sender, RoutedEventArgs e) { // If we click on select computers with no actual computers selected pop up a message box since the GUI is a bit confusing if (ComputerList.SelectedIndex < 0) { System.Windows.MessageBox.Show("Please Select a Computer from the Live Computers in Domain List"); return; } // Need better logic for handling duplicates here SelectedComputerList.Items.Clear(); // Put the computers the user selected in the GUI into a variable var SelectedComputers = ComputerList.SelectedItems; // Set parallel options int computercount = ((IList)SelectedComputers).Count; var options = new ParallelOptions { MaxDegreeOfParallelism = 100 }; // Need to put the list of computers into a blocking collection for parallel for each BlockingCollection <string> SelectedComputersCollection = new BlockingCollection <string>(); // Loop through the list of selected computers and add them to the new blocking list foreach (object SelectedComputer in SelectedComputers) { SelectedComputersCollection.Add((string)SelectedComputer); } // Looping through the computers that are in the domain or populated via a text file - this logic performs a ping check on the computers and adds them to the middle computers you want to action listbox, these are the computers that will have various commands issued to them //REF: http://hk.uwenku.com/question/p-dyussklc-gg.html (sketchy site) // Setting the value for ping timeout, 5000ms int pingtimeout = 5000; // Setting the current variable to 0, this is used for the progress bar int current = 0; // Setting the max value of the progress bar equtal to the amount of computers myprogressDialog.Maximum = computercount; // Initailizing the percentcomplete variable, used for the progress bar int percentcomplete = 0; // Start the task for the foreach loop, this loops through a list of computers and adds only the live ones Task looper = new Task(() => { Parallel.ForEach(SelectedComputersCollection, options, SelectedComputer => // foreach (object SelectedComputer in SelectedComputers) -- this is the old for loop, leaving here just in case { // Need to use the Dispatcher method to update GUI Dispatcher.Invoke(async() => { StatusLabel.Content = "Working..."; // This stuff updates the progress bar, needs a bit of work current++; percentcomplete = (current / computercount) * 100; myprogressDialog.Value = percentcomplete; // End of progress bar update // Within the loop, try, catch - try the pings and log successes, if it fails log the error message Ping pingSender = new Ping(); //PingReply ComputerPingReply = pingSender.Send(SelectedComputer.ToString(), pingtimeout); -- this is the old single threaded ping, leaving here just in case try { PingReply ComputerPingReply = await pingSender.SendPingAsync(SelectedComputer.ToString(), pingtimeout); if (ComputerPingReply.Status == IPStatus.Success) { myprogressDialog.Value = current; SelectedComputerList.Items.Add(SelectedComputer); Log.Information(SelectedComputer + " Passed Ping Check"); } if (ComputerPingReply.Status == IPStatus.DestinationHostUnreachable) { myprogressDialog.Value = current; Log.Information(SelectedComputer + " Was Unreachable"); } } catch (Exception pingexception) { Log.Debug(SelectedComputer + " : " + pingexception.InnerException.Message); } }); }); }); // closing brackets for the Task // Start our looper task looper.Start(); // When the task is done, update the status label and dispose of the task looper.GetAwaiter().OnCompleted(() => { StatusLabel.Content = "Done!"; looper.Dispose(); }); }