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)
{