Added stronger start program closing

Any start program that is started then
tries to doubly make sure that the program
is also closed. Now checks for something to
close in the following order:
- tries to close the process it opened
- if that was closed it tries to find any child
  processes started by the original process in
  case it was a 'launcher' process
- If there are no children with the same parent
  process then it just tries to close all processes
  with the same name as the one it opened.
  That final close also is a forced kill, as I've found
  programs like SimHub resist closing....
This commit is contained in:
Terry MacDonald 2021-06-02 21:53:18 +12:00
parent abb2a3b905
commit e31bbbd4aa
3 changed files with 236 additions and 6 deletions

View File

@ -116,6 +116,7 @@
<Compile Include="IconFromFile.cs" /> <Compile Include="IconFromFile.cs" />
<Compile Include="IconUtils.cs" /> <Compile Include="IconUtils.cs" />
<Compile Include="ImageUtils.cs" /> <Compile Include="ImageUtils.cs" />
<Compile Include="ProcessUtils.cs" />
<Compile Include="ProgramSettings.cs" /> <Compile Include="ProgramSettings.cs" />
<Compile Include="Properties\Resources.Designer.cs"> <Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen> <AutoGen>True</AutoGen>

View File

@ -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<ProcessInfo>
{
public Process TheProcess;
public ProcessInfo Parent;
public List<ProcessInfo> Children = new List<ProcessInfo>();
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<int, ProcessInfo> allProcessInfosDict;
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
public static void Initialise()
{
allProcessInfosDict = new Dictionary<int, ProcessInfo>();
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<Process> FindChildProcesses(Process parentProcess)
{
List<Process> childProcesses = new List<Process>() { };
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;
}
}
}

View File

@ -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"); 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 // Stop the programs in the reverse order we started them
foreach (Process processToStop in startProgramsToStop.Reverse<Process>()) foreach (Process processToStop in startProgramsToStop.Reverse<Process>())
{ {
if (processToStop.HasExited) if (processToStop.HasExited)
{ {
// if the process has already exited (e.g. the user exited it themselves) // First check whether it was a loader process that started a subprocess
// then stop trying to stop the process, and instead log the fact it already stopped. // If so, we need to go through and find and close all subprocesses
Console.WriteLine($"Stopping process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); List<Process> childProcesses = ProcessUtils.FindChildProcesses(processToStop);
logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName} but was already stopped by user or another process."); if (childProcesses.Count > 0)
continue; {
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<Process> 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}"); Console.WriteLine($"Stopping process {processToStop.StartInfo.FileName}");
logger.Debug($"ShortcutRepository/RunShortcut: 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."); logger.Error(ex, $"ShortcutRepository/RunShortcut: Couldn't access the wait status for a process we're trying to stop.");
} }
catch (InvalidOperationException ex) { 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) catch (SystemException ex)
{ {