Initial ProcessUtils update

ProcessUtils functions don't work at the moment, so I need to fix them!
This commit is contained in:
Terry MacDonald 2021-11-06 18:38:37 +13:00
parent 712f0333f5
commit a80349f31a
4 changed files with 219 additions and 255 deletions

View File

@ -1,18 +1,15 @@
using System; using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.IO;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace DisplayMagician namespace DisplayMagician
{
public class ProcessCreator
{ {
[Flags] [Flags]
public enum ProcessCreationFlags : uint public enum PROCESS_CREATION_FLAGS : uint
{ {
ZERO_FLAG = 0x00000000, ZERO_FLAG = 0x00000000,
CREATE_BREAKAWAY_FROM_JOB = 0x01000000, CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
@ -41,211 +38,85 @@ namespace DisplayMagician
REALTIME_PRIORITY_CLASS = 0x00000100, REALTIME_PRIORITY_CLASS = 0x00000100,
} }
public struct PROCESS_INFORMATION [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFOEX
{ {
public IntPtr hProcess; public STARTUPINFO StartupInfo;
public IntPtr hThread; public IntPtr lpAttributeList;
public uint dwProcessId;
public uint dwThreadId;
} }
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct STARTUPINFO public struct STARTUPINFO
{ {
public uint cb; public Int32 cb;
public string lpReserved; public string lpReserved;
public string lpDesktop; public string lpDesktop;
public string lpTitle; public string lpTitle;
public uint dwX; public Int32 dwX;
public uint dwY; public Int32 dwY;
public uint dwXSize; public Int32 dwXSize;
public uint dwYSize; public Int32 dwYSize;
public uint dwXCountChars; public Int32 dwXCountChars;
public uint dwYCountChars; public Int32 dwYCountChars;
public uint dwFillAttribute; public Int32 dwFillAttribute;
public uint dwFlags; public Int32 dwFlags;
public short wShowWindow; public Int16 wShowWindow;
public short cbReserved2; public Int16 cbReserved2;
public IntPtr lpReserved2; public IntPtr lpReserved2;
public IntPtr hStdInput; public IntPtr hStdInput;
public IntPtr hStdOutput; public IntPtr hStdOutput;
public IntPtr hStdError; public IntPtr hStdError;
} }
public static class NativeMethods [StructLayout(LayoutKind.Sequential)]
public struct PROCESS_INFORMATION
{ {
[DllImport("kernel32.dll")] public IntPtr hProcess;
public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, public IntPtr hThread;
bool bInheritHandles, ProcessCreationFlags dwCreationFlags, IntPtr lpEnvironment, public int dwProcessId;
string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); public int dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[DllImport("kernel32.dll")] [DllImport("kernel32.dll")]
public static extern uint ResumeThread(IntPtr hThread); [return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CreateProcess(
string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, PROCESS_CREATION_FLAGS dwCreationFlags,
IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll")] [DllImport("kernel32.dll", SetLastError = true)]
public static extern uint SuspendThread(IntPtr hThread); [return: MarshalAs(UnmanagedType.Bool)]
} private static extern bool UpdateProcThreadAttribute(
IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue,
IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
class ProcessInfo : IComparable<ProcessInfo> [DllImport("kernel32.dll", SetLastError = true)]
{ [return: MarshalAs(UnmanagedType.Bool)]
public Process TheProcess; private static extern bool InitializeProcThreadAttributeList(
public ProcessInfo Parent; IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
public List<ProcessInfo> Children = new List<ProcessInfo>();
public ProcessInfo(Process the_process) [DllImport("kernel32.dll", SetLastError = true)]
{ [return: MarshalAs(UnmanagedType.Bool)]
TheProcess = the_process; private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList);
}
public override string ToString() [DllImport("kernel32.dll", SetLastError = true)]
{ private static extern bool CloseHandle(IntPtr hObject);
return string.Format("{0} [{1}]",
TheProcess.ProcessName, TheProcess.Id);
}
public int CompareTo(ProcessInfo other) [DllImport("kernel32.dll", SetLastError = true)]
{ private static extern uint ResumeThread(IntPtr hThread);
return TheProcess.ProcessName.CompareTo(
other.TheProcess.ProcessName);
}
}
static class ProcessUtils [DllImport("kernel32.dll", SetLastError = true)]
{ private static extern uint SuspendThread(IntPtr hThread);
private static Dictionary<int, ProcessInfo> allProcessInfosDict;
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
private static IntPtr ThreadHandle = IntPtr.Zero;
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;
// TODO: We *possibly* could walk the tree to find the program hierarchy, to get the full list of the
// process tree, but this seems like the best way at this stage. I'm expecting I'll find an edge case in the future
// that requires some sort of modification, but this is working well in 2 days of testing so far!
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;
}
public static bool StopProcess(Process processToStop)
{
try
{
// Stop the process
processToStop.CloseMainWindow();
if (!processToStop.WaitForExit(5000))
{
logger.Trace($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} wouldn't stop cleanly. Forcing program close.");
processToStop.Kill();
if (!processToStop.WaitForExit(5000))
{
logger.Error($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} couldn't be killed! It seems like something is actively preventing us from stopping the process");
return false;
}
logger.Trace($"ProcessUtils/StopProcess: Process {processToStop.StartInfo.FileName} was successfully killed.");
}
processToStop.Close();
return true;
}
catch (Win32Exception ex)
{
logger.Warn(ex, $"ProcessUtils/StopProcess: Win32Exception! Couldn't access the wait status for a named process we're trying to stop. So now just killing the process.");
processToStop.Kill();
if (!processToStop.WaitForExit(5000))
{
logger.Error($"ProcessUtils/StopProcess: Win32Exception! Process {processToStop.StartInfo.FileName} couldn't be killed! It seems like something is actively preventing us from stopping the process");
return false;
}
logger.Trace($"ProcessUtils/StopProcess: Win32Exception! Process {processToStop.StartInfo.FileName} was successfully killed.");
processToStop.Close();
return true;
}
catch (InvalidOperationException ex)
{
logger.Error(ex, $"ProcessUtils/StopProcess: Couldn't kill the named process as the process appears to have closed already.");
}
catch (SystemException ex)
{
logger.Error(ex, $"ProcessUtils/StopProcess: 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, $"ProcessUtils/StopProcess: Got an AggregateException.");
}
return false;
}
public static ProcessPriorityClass TranslatePriorityToClass(ProcessPriority processPriorityClass) public static ProcessPriorityClass TranslatePriorityToClass(ProcessPriority processPriorityClass)
{ {
@ -274,49 +145,137 @@ namespace DisplayMagician
return wantedPriorityClass; return wantedPriorityClass;
} }
public static ProcessCreationFlags TranslatePriorityClassToFlags(ProcessPriorityClass processPriorityClass) public static PROCESS_CREATION_FLAGS TranslatePriorityClassToFlags(ProcessPriorityClass processPriorityClass)
{ {
ProcessCreationFlags wantedPriorityClass = ProcessCreationFlags.NORMAL_PRIORITY_CLASS; PROCESS_CREATION_FLAGS wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS;
switch (processPriorityClass) switch (processPriorityClass)
{ {
case ProcessPriorityClass.High: case ProcessPriorityClass.High:
wantedPriorityClass = ProcessCreationFlags.HIGH_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.HIGH_PRIORITY_CLASS;
break; break;
case ProcessPriorityClass.AboveNormal: case ProcessPriorityClass.AboveNormal:
wantedPriorityClass = ProcessCreationFlags.ABOVE_NORMAL_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.ABOVE_NORMAL_PRIORITY_CLASS;
break; break;
case ProcessPriorityClass.Normal: case ProcessPriorityClass.Normal:
wantedPriorityClass = ProcessCreationFlags.NORMAL_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS;
break; break;
case ProcessPriorityClass.BelowNormal: case ProcessPriorityClass.BelowNormal:
wantedPriorityClass = ProcessCreationFlags.BELOW_NORMAL_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.BELOW_NORMAL_PRIORITY_CLASS;
break; break;
case ProcessPriorityClass.Idle: case ProcessPriorityClass.Idle:
wantedPriorityClass = ProcessCreationFlags.IDLE_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.IDLE_PRIORITY_CLASS;
break; break;
default: default:
wantedPriorityClass = ProcessCreationFlags.NORMAL_PRIORITY_CLASS; wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS;
break; break;
} }
return wantedPriorityClass; return wantedPriorityClass;
} }
public static bool LaunchProcessWithPriority(string exeName, string cmdLine, ProcessPriorityClass priorityClass, out uint PID) public static bool CreateProcessWithPriority(string exeName, string cmdLine, ProcessPriorityClass priorityClass, out PROCESS_INFORMATION processInfo)
{ {
ProcessCreationFlags processFlags = TranslatePriorityClassToFlags(priorityClass); PROCESS_CREATION_FLAGS processFlags = TranslatePriorityClassToFlags(priorityClass) | PROCESS_CREATION_FLAGS.CREATE_SUSPENDED;
bool success = false;
STARTUPINFO si = new STARTUPINFO(); PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION();
PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); var pSec = new SECURITY_ATTRIBUTES();
bool success = NativeMethods.CreateProcess(exeName, cmdLine, IntPtr.Zero, IntPtr.Zero, false, processFlags, IntPtr.Zero, null, ref si, out pi); var tSec = new SECURITY_ATTRIBUTES();
ThreadHandle = pi.hThread; pSec.nLength = Marshal.SizeOf(pSec);
PID = pi.dwProcessId; tSec.nLength = Marshal.SizeOf(tSec);
var sInfoEx = new STARTUPINFOEX();
return success; sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
} try
public static void ResumeProcess()
{ {
NativeMethods.ResumeThread(ThreadHandle); success = CreateProcess(exeName, cmdLine, ref pSec, ref tSec, false, processFlags, IntPtr.Zero, null, ref sInfoEx, out pInfo);
}
catch (Exception ex)
{
// This is
}
processInfo = pInfo;
return true;
}
public static void ResumeProcess(IntPtr threadHandle)
{
ResumeThread(threadHandle);
}
public static bool CreateProcessWithParent(int parentProcessId)
{
const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
var pInfo = new PROCESS_INFORMATION();
var sInfoEx = new STARTUPINFOEX();
sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx);
IntPtr lpValue = IntPtr.Zero;
try
{
if (parentProcessId > 0)
{
var lpSize = IntPtr.Zero;
var success = InitializeProcThreadAttributeList(IntPtr.Zero, 1, 0, ref lpSize);
if (success || lpSize == IntPtr.Zero)
{
return false;
}
sInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
success = InitializeProcThreadAttributeList(sInfoEx.lpAttributeList, 1, 0, ref lpSize);
if (!success)
{
return false;
}
var parentHandle = Process.GetProcessById(parentProcessId).Handle;
// This value should persist until the attribute list is destroyed using the DeleteProcThreadAttributeList function
lpValue = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValue, parentHandle);
success = UpdateProcThreadAttribute(
sInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
lpValue,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
return false;
}
}
var pSec = new SECURITY_ATTRIBUTES();
var tSec = new SECURITY_ATTRIBUTES();
pSec.nLength = Marshal.SizeOf(pSec);
tSec.nLength = Marshal.SizeOf(tSec);
var lpApplicationName = Path.Combine(Environment.SystemDirectory, "notepad.exe");
return CreateProcess(lpApplicationName, null, ref pSec, ref tSec, false, PROCESS_CREATION_FLAGS.EXTENDED_STARTUPINFO_PRESENT, IntPtr.Zero, null, ref sInfoEx, out pInfo);
}
finally
{
// Free the attribute list
if (sInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(sInfoEx.lpAttributeList);
Marshal.FreeHGlobal(sInfoEx.lpAttributeList);
}
Marshal.FreeHGlobal(lpValue);
// Close process and thread handles
if (pInfo.hProcess != IntPtr.Zero)
{
CloseHandle(pInfo.hProcess);
}
if (pInfo.hThread != IntPtr.Zero)
{
CloseHandle(pInfo.hThread);
} }
} }
} }
}
}

View File

@ -26,8 +26,8 @@ using System.Resources;
[assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")]
// Version information // Version information
[assembly: AssemblyVersion("2.1.0.160")] [assembly: AssemblyVersion("2.1.0.164")]
[assembly: AssemblyFileVersion("2.1.0.160")] [assembly: AssemblyFileVersion("2.1.0.164")]
[assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: NeutralResourcesLanguageAttribute( "en" )]
[assembly: CLSCompliant(true)] [assembly: CLSCompliant(true)]

View File

@ -895,6 +895,7 @@ namespace DisplayMagician
Process process = null; Process process = null;
try try
{ {
ProcessUtils.ScanProcesses();
uint processID = 0; uint processID = 0;
if (ProcessUtils.LaunchProcessWithPriority(processToStart.Executable, processToStart.Arguments, ProcessUtils.TranslatePriorityToClass(processToStart.ProcessPriority), out processID)) if (ProcessUtils.LaunchProcessWithPriority(processToStart.Executable, processToStart.Arguments, ProcessUtils.TranslatePriorityToClass(processToStart.ProcessPriority), out processID))
{ {
@ -1810,7 +1811,7 @@ 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. // Prepare the processInfos we need for finding child processes.
ProcessUtils.Initialise(); ProcessUtils.ScanProcesses();
// 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>())

View File

@ -1375,7 +1375,11 @@ namespace DisplayMagician.UIForms
txt_alternative_executable.Text = _shortcutToEdit.DifferentExecutableToMonitor; txt_alternative_executable.Text = _shortcutToEdit.DifferentExecutableToMonitor;
// Set the shortcut name // Set the shortcut name
if (_editingExistingShortcut)
{
txt_shortcut_save_name.Text = _shortcutToEdit.Name; txt_shortcut_save_name.Text = _shortcutToEdit.Name;
}
// Set the selected image and available images (originalBitmap is set during shortcut update) // Set the selected image and available images (originalBitmap is set during shortcut update)