diff --git a/DisplayMagician/DisplayMagician.csproj b/DisplayMagician/DisplayMagician.csproj index da08356..d8f73a6 100644 --- a/DisplayMagician/DisplayMagician.csproj +++ b/DisplayMagician/DisplayMagician.csproj @@ -116,6 +116,7 @@ + True diff --git a/DisplayMagician/ProcessUtils.cs b/DisplayMagician/ProcessUtils.cs new file mode 100644 index 0000000..6cbaeda --- /dev/null +++ b/DisplayMagician/ProcessUtils.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Management; +using System.Text; +using System.Threading.Tasks; + +namespace DisplayMagician +{ + + class ProcessInfo : IComparable + { + public Process TheProcess; + public ProcessInfo Parent; + public List Children = new List(); + + public ProcessInfo(Process the_process) + { + TheProcess = the_process; + } + + public override string ToString() + { + return string.Format("{0} [{1}]", + TheProcess.ProcessName, TheProcess.Id); + } + + public int CompareTo(ProcessInfo other) + { + return TheProcess.ProcessName.CompareTo( + other.TheProcess.ProcessName); + } + } + + static class ProcessUtils + { + private static Dictionary allProcessInfosDict; + private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); + + public static void Initialise() + { + allProcessInfosDict = new Dictionary(); + + try + { + // Get the parent/child info. + ManagementObjectSearcher searcher = new ManagementObjectSearcher( + "SELECT ProcessId, ParentProcessId FROM Win32_Process"); + ManagementObjectCollection collection = searcher.Get(); + + // Get the processes. + foreach (Process process in Process.GetProcesses()) + { + allProcessInfosDict.Add(process.Id, new ProcessInfo(process)); + } + + // Create the child lists. + foreach (var item in collection) + { + // Find the parent and child in the dictionary. + int child_id = Convert.ToInt32(item["ProcessId"]); + int parent_id = Convert.ToInt32(item["ParentProcessId"]); + + ProcessInfo child_info = null; + ProcessInfo parent_info = null; + if (allProcessInfosDict.ContainsKey(child_id)) + child_info = allProcessInfosDict[child_id]; + if (allProcessInfosDict.ContainsKey(parent_id)) + parent_info = allProcessInfosDict[parent_id]; + + if (child_info == null) + Console.WriteLine( + "Cannot find child " + child_id.ToString() + + " for parent " + parent_id.ToString()); + + if (parent_info == null) + Console.WriteLine( + "Cannot find parent " + parent_id.ToString() + + " for child " + child_id.ToString()); + + if ((child_info != null) && (parent_info != null)) + { + parent_info.Children.Add(child_info); + child_info.Parent = parent_info; + } + } + + } + catch(Exception ex) + { + logger.Error(ex,$"ProcessUtils/Initialise: Exception (re)initialising the process information to figure out process hierarchy"); + } + + } + + public static List FindChildProcesses(Process parentProcess) + { + List childProcesses = new List() { }; + + try + { + int parentId = parentProcess.Id; + if (allProcessInfosDict.ContainsKey(parentId)) + { + foreach (ProcessInfo childProcess in allProcessInfosDict[parentId].Children) + { + childProcesses.Add(childProcess.TheProcess); + } + + } + } + catch (Exception ex) + { + logger.Error(ex, $"ProcessUtils/FindChildProcesses: Exception finding the child processes of the parentProcess"); + } + + return childProcesses; + } + + } +} diff --git a/DisplayMagician/ShortcutRepository.cs b/DisplayMagician/ShortcutRepository.cs index 95966ea..9cbd91b 100644 --- a/DisplayMagician/ShortcutRepository.cs +++ b/DisplayMagician/ShortcutRepository.cs @@ -1608,16 +1608,123 @@ namespace DisplayMagician { logger.Debug($"ShortcutRepository/RunShortcut: We started {startProgramsToStart.Count} programs before the main executable or game, and now we want to stop {startProgramsToStop.Count } of them"); + // Prepare the processInfos we need for finding child processes. + ProcessUtils.Initialise(); + // Stop the programs in the reverse order we started them foreach (Process processToStop in startProgramsToStop.Reverse()) { if (processToStop.HasExited) { - // if the process has already exited (e.g. the user exited it themselves) - // then stop trying to stop the process, and instead log the fact it already stopped. - Console.WriteLine($"Stopping process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); - logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); - continue; + // First check whether it was a loader process that started a subprocess + // If so, we need to go through and find and close all subprocesses + List childProcesses = ProcessUtils.FindChildProcesses(processToStop); + if (childProcesses.Count > 0) + { + foreach (Process childProcessToStop in childProcesses) + { + if (processToStop.HasExited) + { + // if there were no child processes, and the only process has already exited (e.g. the user exited it themselves) + // then stop trying to stop the process, and instead log the fact it already stopped. + Console.WriteLine($"Stopping child process {childProcessToStop.StartInfo.FileName} but was already stopped by user or another process."); + logger.Warn($"ShortcutRepository/RunShortcut: Stopping child process {childProcessToStop.StartInfo.FileName} but was already stopped by user or another process."); + continue; + } + + Console.WriteLine($"Stopping child process {childProcessToStop.StartInfo.FileName} of parent process {processToStop.StartInfo.FileName}"); + logger.Debug($"ShortcutRepository/RunShortcut: Stopping child process {childProcessToStop.StartInfo.FileName} of parent process {processToStop.StartInfo.FileName}"); + try + { + // Stop the program + childProcessToStop.CloseMainWindow(); + childProcessToStop.WaitForExit(5000); + if (!childProcessToStop.HasExited) + { + Console.WriteLine($"- Process {childProcessToStop.StartInfo.FileName} wouldn't stop cleanly. Forcing program close."); + logger.Warn($"ShortcutRepository/RunShortcut: Process {childProcessToStop.StartInfo.FileName} wouldn't stop cleanly. Forcing program close."); + childProcessToStop.Kill(); + childProcessToStop.WaitForExit(5000); + } + childProcessToStop.Close(); + } + catch (Win32Exception ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't access the wait status for a child process we're trying to stop."); + } + catch (InvalidOperationException ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't kill the child process as the child process appears to have closed already. This can be caused if your {childProcessToStop.StartInfo.FileName} loaded another exe then closed itself. DisplayMagician cannot track that sort of behaviour."); + } + catch (SystemException ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't WaitForExit the child process as there is no child process associated with the Process object (or cannot get the ID from the process handle)."); + } + + catch (AggregateException ae) + { + logger.Error(ae, $"ShortcutRepository/RunShortcut: Got an AggregateException."); + } + + } + } + else + { + // if there were no child processes found, and the only process has already exited (e.g. the user exited it themselves) + // then we try to stop any processes with the same name as the application we started + // Look for the processes with the ProcessName we sorted out earlier + string processName = Path.GetFileNameWithoutExtension(processToStop.StartInfo.FileName); + List namedProcessesToStop = Process.GetProcessesByName(processName).ToList(); + + // If we have found one or more processes then we should be good to go + if (namedProcessesToStop.Count > 0) + { + logger.Warn($"ShortcutRepository/RunShortcut: We couldn't find any children processes so we've looked for named processes with the name '{processToStop.StartInfo.FileName}' and we found {namedProcessesToStop.Count}. Closing them."); + foreach (Process namedProcessToStop in namedProcessesToStop) + { + try + { + // Stop the named process + namedProcessToStop.CloseMainWindow(); + namedProcessToStop.WaitForExit(5000); + namedProcessToStop.Kill(); + /*if (!namedProcessToStop.HasExited) + { + logger.Warn($"ShortcutRepository/RunShortcut: Named process {namedProcessToStop.StartInfo.FileName} wouldn't stop cleanly. Forcing program close."); + namedProcessToStop.Kill(); + namedProcessToStop.WaitForExit(5000); + }*/ + namedProcessToStop.Close(); + } + catch (Win32Exception ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't access the wait status for a named process we're trying to stop."); + } + catch (InvalidOperationException ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't kill the named process as the process appears to have closed already."); + } + catch (SystemException ex) + { + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't WaitForExit the named process as there is no process associated with the Process object (or cannot get the ID from the named process handle)."); + } + + catch (AggregateException ae) + { + logger.Error(ae, $"ShortcutRepository/RunShortcut: Got an AggregateException."); + } + } + } + else + { + // then stop trying to stop the process, and instead log the fact it already stopped. + Console.WriteLine($"Stopping only process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); + logger.Debug($"ShortcutRepository/RunShortcut: Stopping only process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); + + } + continue; + } + } Console.WriteLine($"Stopping process {processToStop.StartInfo.FileName}"); logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName}"); @@ -1639,7 +1746,7 @@ namespace DisplayMagician logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't access the wait status for a process we're trying to stop."); } catch (InvalidOperationException ex) { - logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't kill the process as the proicess appears to have closed already. This can be caused if your {processToStop.ProcessName}.exe loaded another exe then closed itself. DisplayMagician cannot track that sort of behaviour."); + logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't kill the process as the proicess appears to have closed already. This can be caused if your {processToStop.StartInfo.FileName} loaded another exe then closed itself. DisplayMagician cannot track that sort of behaviour."); } catch (SystemException ex) {