From fdb4fce9778b90f3284ba3feb3a7ebc7eb7c32a2 Mon Sep 17 00:00:00 2001 From: Terry MacDonald Date: Mon, 22 Nov 2021 21:53:14 +1300 Subject: [PATCH] [WIP] ProcessUtils refactor and trim Tried to remove the unneeded bits of the ProcessUtils module, and to streamline the process monitoring process to make it sane, and have code only live in one place. I also wanted to make use of the TryExecute_Impersonated if possible. I hope I've done that all today! --- DisplayMagician/GameLibraries/EpicGame.cs | 6 +- DisplayMagician/GameLibraries/GOGGame.cs | 6 +- DisplayMagician/GameLibraries/OriginGame.cs | 6 +- DisplayMagician/GameLibraries/SteamGame.cs | 8 +- DisplayMagician/GameLibraries/UplayGame.cs | 6 +- DisplayMagician/Processes/ProcessUtils.cs | 1654 +++---------------- DisplayMagician/ShortcutRepository.cs | 14 +- 7 files changed, 225 insertions(+), 1475 deletions(-) diff --git a/DisplayMagician/GameLibraries/EpicGame.cs b/DisplayMagician/GameLibraries/EpicGame.cs index ca43e88..74809ce 100644 --- a/DisplayMagician/GameLibraries/EpicGame.cs +++ b/DisplayMagician/GameLibraries/EpicGame.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using DisplayMagician.Resources; using System.Diagnostics; +using DisplayMagician.Processes; namespace DisplayMagician.GameLibraries { @@ -101,7 +102,8 @@ namespace DisplayMagician.GameLibraries { get { - int numGameProcesses = 0; + return ProcessUtils.ProcessExited(_epicGameProcessName); + /*int numGameProcesses = 0; _epicGameProcesses = Process.GetProcessesByName(_epicGameProcessName).ToList(); foreach (Process gameProcess in _epicGameProcesses) { @@ -135,7 +137,7 @@ namespace DisplayMagician.GameLibraries if (numGameProcesses > 0) return true; else - return false; + return false;*/ } } diff --git a/DisplayMagician/GameLibraries/GOGGame.cs b/DisplayMagician/GameLibraries/GOGGame.cs index 3bc6d83..903beec 100644 --- a/DisplayMagician/GameLibraries/GOGGame.cs +++ b/DisplayMagician/GameLibraries/GOGGame.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using DisplayMagician.Resources; using System.Diagnostics; +using DisplayMagician.Processes; namespace DisplayMagician.GameLibraries { @@ -101,7 +102,8 @@ namespace DisplayMagician.GameLibraries { get { - int numGameProcesses = 0; + return ProcessUtils.ProcessExited(_gogGameProcessName); + /*int numGameProcesses = 0; _gogGameProcesses = Process.GetProcessesByName(_gogGameProcessName).ToList(); foreach (Process gameProcess in _gogGameProcesses) { @@ -135,7 +137,7 @@ namespace DisplayMagician.GameLibraries if (numGameProcesses > 0) return true; else - return false; + return false;*/ } } diff --git a/DisplayMagician/GameLibraries/OriginGame.cs b/DisplayMagician/GameLibraries/OriginGame.cs index e6aee80..34c2ec5 100644 --- a/DisplayMagician/GameLibraries/OriginGame.cs +++ b/DisplayMagician/GameLibraries/OriginGame.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using DisplayMagician.Resources; using System.Diagnostics; +using DisplayMagician.Processes; namespace DisplayMagician.GameLibraries { @@ -100,7 +101,8 @@ namespace DisplayMagician.GameLibraries { get { - int numGameProcesses = 0; + return ProcessUtils.ProcessExited(_originGameProcessName); + /*int numGameProcesses = 0; _originGameProcesses = Process.GetProcessesByName(_originGameProcessName).ToList(); foreach (Process gameProcess in _originGameProcesses) { @@ -134,7 +136,7 @@ namespace DisplayMagician.GameLibraries if (numGameProcesses > 0) return true; else - return false; + return false;*/ } } diff --git a/DisplayMagician/GameLibraries/SteamGame.cs b/DisplayMagician/GameLibraries/SteamGame.cs index 9e38ef9..2e3c45a 100644 --- a/DisplayMagician/GameLibraries/SteamGame.cs +++ b/DisplayMagician/GameLibraries/SteamGame.cs @@ -7,6 +7,7 @@ using System.Security; using DisplayMagician.Resources; using Microsoft.Win32; using System.Diagnostics; +using DisplayMagician.Processes; namespace DisplayMagician.GameLibraries { @@ -99,8 +100,9 @@ namespace DisplayMagician.GameLibraries { get { - int numGameProcesses = 0; - _steamGameProcesses = Process.GetProcessesByName(_steamGameProcessName).ToList(); + //int numGameProcesses = 0; + return ProcessUtils.ProcessExited(_steamGameProcessName); + /*_steamGameProcesses = Process.GetProcessesByName(_steamGameProcessName).ToList(); foreach (Process gameProcess in _steamGameProcesses) { try @@ -138,7 +140,7 @@ namespace DisplayMagician.GameLibraries if (numGameProcesses > 0) return true; else - return false; + return false;*/ } } diff --git a/DisplayMagician/GameLibraries/UplayGame.cs b/DisplayMagician/GameLibraries/UplayGame.cs index c089189..23bf4ff 100644 --- a/DisplayMagician/GameLibraries/UplayGame.cs +++ b/DisplayMagician/GameLibraries/UplayGame.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using DisplayMagician.Resources; using System.Diagnostics; +using DisplayMagician.Processes; namespace DisplayMagician.GameLibraries { @@ -99,7 +100,8 @@ namespace DisplayMagician.GameLibraries { get { - int numGameProcesses = 0; + return ProcessUtils.ProcessExited(_uplayGameProcessName); + /*int numGameProcesses = 0; _uplayGameProcesses = Process.GetProcessesByName(_uplayGameProcessName).ToList(); foreach (Process gameProcess in _uplayGameProcesses) { @@ -133,7 +135,7 @@ namespace DisplayMagician.GameLibraries if (numGameProcesses > 0) return true; else - return false; + return false;*/ } } diff --git a/DisplayMagician/Processes/ProcessUtils.cs b/DisplayMagician/Processes/ProcessUtils.cs index 6c69f0b..8106140 100644 --- a/DisplayMagician/Processes/ProcessUtils.cs +++ b/DisplayMagician/Processes/ProcessUtils.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using System.Management; using System.Runtime.InteropServices; using System.Security.Principal; @@ -56,21 +57,78 @@ namespace DisplayMagician.Processes public static List StartProcess(string executable, string arguments, ProcessPriority processPriority, int startTimeout = 1) { - List startedProcesses = new List(); - List returnedProcesses; - //bool usingChildProcess = false; - - if (TryExecute_AutoImpersonate(executable, arguments, out returnedProcesses)) + List returnedProcesses = new List(); + Process processCreated; + if (TryExecute(executable, arguments, out processCreated)) { - logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} has successfully been started"); + logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} has successfully been started by TryExecute"); } else { - logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} was unable to be started"); + logger.Warn($"ProcessUtils/StartProcess: {executable} {arguments} was unable to be started by TryExecute, so attempting with TryExecute_Impersonate"); + ImpersonationProcess impProcessCreated; + if (IsImpersonated()) + { + logger.Trace($"ProcessUtils/StartProcess: Useer CAN be impersonated, so trying to run {executable} {arguments} with TryExecute_Impersonated"); + if (TryExecute_Impersonated(executable, arguments, out impProcessCreated)) + { + logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} has successfully been started by TryExecute_Impersonated"); + processCreated = impProcessCreated; + } + else + { + logger.Error($"ProcessUtils/StartProcess: {executable} {arguments} was unable to be started by TryExecute_Impersonated, so giving up"); + } + } + else + { + logger.Error($"ProcessUtils/StartProcess: {executable} {arguments} was unable to be attempted by TryExecute_Impersonated as the User can't be impersonated, so giving up"); + } } - startedProcesses = returnedProcesses; - return startedProcesses; + if (processCreated != null && processCreated.Id > 0) + { + try + { + + processCreated.WaitForExit(1000); + + if (processCreated.HasExited) + { + // If the process has exited, then it's likely to be a launcher, so we try to find the children processes + List childProcesses = GetChildProcesses(processCreated); + returnedProcesses.AddRange(childProcesses); + } + else + { + ProcessPriorityClass wantedPriority = TranslatePriorityToClass(processPriority); + // If we're here then the process was created and hasn't exited! + try + { + + if (processCreated.PriorityClass != wantedPriority) + { + processCreated.PriorityClass = wantedPriority; + } + } + catch (Exception ex) + { + logger.Warn(ex, $"ProcessUtils/StartProcess: Exception while trying to set the Priority Class to {wantedPriority.ToString("G")} for {executable}."); + } + returnedProcesses.Add(processCreated); + } + + } + catch (Exception ex) + { + // Oops - something went wrong. We'll log it and have to move on :( + //process = null; + logger.Error(ex, $"ProcessUtils/StartProcess: Exception while trying to start {executable}. We were unable to start it."); + } + + } + + return returnedProcesses; } public static List GetChildProcesses(Process process) @@ -132,6 +190,38 @@ namespace DisplayMagician.Processes } } + public static bool ProcessExited(string executable) + { + List wantedProcesses = Process.GetProcessesByName(GetProcessName(executable)).ToList(); + + if (ProcessExited(wantedProcesses)) + { + logger.Trace($"ProcessUtils/ProcessExited4: All processes being monitored have exited, so no processes still running!"); + return true; + } + else + { + logger.Trace($"ProcessUtils/ProcessExited4: At least one process is still running!"); + return false; + } + } + + public static bool ProcessExited(int processId) + { + Process process = Process.GetProcessById(processId); + + if (ProcessExited(process)) + { + logger.Trace($"ProcessUtils/ProcessExited3: Process with ID {processId} has exited, so no processes still running!"); + return true; + } + else + { + logger.Trace($"ProcessUtils/ProcessExited3: Process with ID {processId} is still running!"); + return false; + } + } + public static bool ProcessExited(List processes) { int processClosedCount = 0; @@ -255,13 +345,13 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// true if process was executed and finished correctly - public static bool TryExecute(string executable, string arguments, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + /*public static bool TryExecute(string executable, string arguments, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { List unused; return TryExecute(executable, arguments, out unused, priorityClass, maxWaitMs); - } + }*/ - /// + /*/// /// Executes the and waits a maximum time of for completion. If the process doesn't end in /// this time, it gets aborted. This helper method automatically decides if an impersonation should be done, depending on the current identity's /// . @@ -284,7 +374,7 @@ namespace DisplayMagician.Processes } //process = processReturned; return result; - } + }*/ /// /// Executes the and waits a maximum time of for completion. If the process doesn't end in @@ -295,77 +385,69 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// true if process was executed and finished correctly - public static bool TryExecute_Impersonated(string executable, string arguments, out List processes, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + public static bool TryExecute_Impersonated(string executable, string arguments, out ImpersonationProcess processCreated, int maxWaitMs = 1000) { IntPtr userToken; - processes = new List(); if (!ImpersonationHelper.GetTokenByProcess(out userToken, true)) + { + processCreated = null; return false; + } + try { //return TryExecute_Impersonated(executable, arguments, userToken, false, out unused, priorityClass, maxWaitMs); - return TryExecute_Impersonated(executable, arguments, userToken, out processes, priorityClass, maxWaitMs); - } - finally - { - ImpersonationHelper.SafeCloseHandle(userToken); - } - } + StringBuilder outputBuilder = new StringBuilder(); + //startedProcesses = new List(); + processCreated = new ImpersonationProcess { StartInfo = new ProcessStartInfo(executable, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = false } }; + /*if (redirectInputOutput) + { + // Set UTF-8 encoding for standard output. + process.StartInfo.StandardOutputEncoding = CONSOLE_ENCODING; + // Enable raising events because Process does not raise events by default. + process.EnableRaisingEvents = true; + // Attach the event handler for OutputDataReceived before starting the process. + process.OutputDataReceived += (sender, e) => outputBuilder.Append(e.Data); + }*/ - /// - /// Executes the and waits a maximum time of for completion and returns the contents of - /// . If the process doesn't end in this time, it gets aborted. - /// - /// Program to execute - /// Program arguments - /// Returns the contents of standard output - /// Process priority - /// Maximum time to wait for completion - /// - public static bool TryExecuteReadString(string executable, string arguments, out List process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) - { - return TryExecute(executable, arguments, out process, priorityClass, maxWaitMs); - } + try + { + processCreated.StartAsUser(userToken); + return true; + } + catch (ObjectDisposedException ex) + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. The process object has already been disposed."); + return false; + } + catch (InvalidOperationException ex) + { + if (processCreated.StartInfo.UseShellExecute && (processCreated.StartInfo.RedirectStandardInput || processCreated.StartInfo.RedirectStandardOutput || processCreated.StartInfo.RedirectStandardError)) + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. The UseShellExecute member of the StartInfo property is true while RedirectStandardInput, RedirectStandardOutput, or RedirectStandardError is true."); + } + else + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. No file name was specified in the Process component's StartInfo."); + } + return false; + } + catch (Win32Exception ex) + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. There was an error in opening the associated file."); + return false; + } + catch (PlatformNotSupportedException ex) + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. Method not supported on operating systems without shell support such as Nano Server (.NET Core only)."); + return false; + } + catch (Exception ex) + { + logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. Not sure what specific exception it is."); + return false; + } - /// - /// Executes the and waits a maximum time of for completion and returns the contents of - /// . If the process doesn't end in this time, it gets aborted. - /// - /// Program to execute - /// Program arguments - /// Returns the contents of standard output - /// Process priority - /// Maximum time to wait for completion - /// - public static bool TryExecuteReadString_AutoImpersonate(string executable, string arguments, out List processes, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) - { - return IsImpersonated ? - TryExecuteReadString_Impersonated(executable, arguments, out processes, priorityClass, maxWaitMs) : - TryExecuteReadString(executable, arguments, out processes, priorityClass, maxWaitMs); - } - - /// - /// Executes the and waits a maximum time of for completion and returns the contents of - /// . If the process doesn't end in this time, it gets aborted. - /// This method tries to impersonate the interactive user and run the process under its identity. - /// - /// Program to execute - /// Program arguments - /// Returns the contents of standard output - /// Process priority - /// Maximum time to wait for completion - /// true if process was executed and finished correctly - public static bool TryExecuteReadString_Impersonated(string executable, string arguments, out List processes, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = INFINITE) - { - IntPtr userToken; - if (!ImpersonationHelper.GetTokenByProcess(out userToken, true)) - { - processes = new List(); - return false; - } - try - { - return TryExecute_Impersonated(executable, arguments, userToken, out processes, priorityClass, maxWaitMs); } finally { @@ -376,13 +458,10 @@ namespace DisplayMagician.Processes /// /// Indicates if the current uses impersonation. /// - private static bool IsImpersonated + private static bool IsImpersonated() { - get - { - WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent(); - return windowsIdentity != null && windowsIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation; - } + WindowsIdentity windowsIdentity = WindowsIdentity.GetCurrent(); + return windowsIdentity != null && windowsIdentity.ImpersonationLevel == TokenImpersonationLevel.Impersonation; } /*/// @@ -439,28 +518,11 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// - private static bool TryExecute(string executable, string arguments, out List startedProcesses, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + private static bool TryExecute(string executable, string arguments, out Process processCreated, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { - //StringBuilder outputBuilder = new StringBuilder(); - startedProcesses = new List(); - bool _isFile = false; - bool _isExe = false; - if (File.Exists(executable)) - { - _isFile = true; - if (Path.GetExtension(executable).Equals(".exe",StringComparison.CurrentCultureIgnoreCase) || - Path.GetExtension(executable).Equals(".com", StringComparison.CurrentCultureIgnoreCase) || - Path.GetExtension(executable).Equals(".msi", StringComparison.CurrentCultureIgnoreCase) || - Path.GetExtension(executable).Equals(".bat", StringComparison.CurrentCultureIgnoreCase) || - Path.GetExtension(executable).Equals(".cmd", StringComparison.CurrentCultureIgnoreCase) || - Path.GetExtension(executable).Equals(".ps1", StringComparison.CurrentCultureIgnoreCase)) - { - _isExe = true; - } - } - + //StringBuilder outputBuilder = new StringBuilder(); ProcessStartInfo psi; - if (_isFile && _isExe) + if (File.Exists(executable) && IsExecutableFileType(executable)) { // Is exe file psi = new ProcessStartInfo(executable, arguments) @@ -482,7 +544,7 @@ namespace DisplayMagician.Processes }; } - Process processCreated = new Process { StartInfo = psi }; + processCreated = new Process { StartInfo = psi }; /*if (redirectInputOutput) { @@ -496,7 +558,15 @@ namespace DisplayMagician.Processes try { - processCreated.Start(); + if (processCreated.Start()) + { + logger.Trace($"ProcessUtils/TryExecute: {executable} was started successfully."); + } + else + { + logger.Trace($"ProcessUtils/TryExecute: {executable} was reused successfully."); + } + return true; } catch (ObjectDisposedException ex) { @@ -534,222 +604,39 @@ namespace DisplayMagician.Processes /*if (redirectInputOutput) process.BeginOutputReadLine();*/ - //result = RemoveEncodingPreamble(outputBuilder.ToString()); - if (processCreated != null) - { - try - { - - processCreated.WaitForExit(1000); - - if (processCreated.HasExited) - { - // If the process has exited, then it's likely to be a launcher, so we try to find the children processes - List childProcesses = GetChildProcesses(processCreated); - startedProcesses.AddRange(childProcesses); - } - else - { - // If we're here then the process was created and hasn't exited! - try - { - if (processCreated.PriorityClass != priorityClass) - { - processCreated.PriorityClass = priorityClass; - } - } - catch (Exception ex) - { - logger.Warn(ex, $"ProcessUtils/StartProcess: Exception while trying to set the Priority Class to {priorityClass.ToString("G")} for {executable}."); - } - startedProcesses.Add(processCreated); - } - - } - catch (Exception ex) - { - // Oops- the process didn't work. Let's skip this one - //process = null; - return false; - } - - //process = processCreated; - return true; - } + //result = RemoveEncodingPreamble(outputBuilder.ToString()); //process = null; - return false; + //return false; } - - /*/// - /// Executes the and waits a maximum time of for completion. If the process doesn't end in - /// this time, it gets aborted. This method tries to impersonate the interactive user and run the process under its identity. - /// - /// Program to execute - /// Program arguments - /// User token to run process - /// true to redirect standard streams. - /// Returns the contents of standard output. - /// Process priority - /// Maximum time to wait for completion - /// true if process was executed and finished correctly - private static bool TryExecute_Impersonated(string executable, string arguments, IntPtr token, bool redirectInputOutput, out string result, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = INFINITE) + + public static bool IsExecutableFileType(string executable) { - // TODO: code is 99% redundant to TryExecute, refactor Process/ImpersonationProcess and Start/StartAsUser! - StringBuilder outputBuilder = new StringBuilder(); - using (ImpersonationProcess process = new ImpersonationProcess { StartInfo = new ProcessStartInfo(executable, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = redirectInputOutput } }) + if (Path.GetExtension(executable).Equals(".exe", StringComparison.CurrentCultureIgnoreCase) || + Path.GetExtension(executable).Equals(".com", StringComparison.CurrentCultureIgnoreCase) || + Path.GetExtension(executable).Equals(".msi", StringComparison.CurrentCultureIgnoreCase) || + Path.GetExtension(executable).Equals(".bat", StringComparison.CurrentCultureIgnoreCase) || + Path.GetExtension(executable).Equals(".cmd", StringComparison.CurrentCultureIgnoreCase) || + Path.GetExtension(executable).Equals(".ps1", StringComparison.CurrentCultureIgnoreCase)) { - *//*if (redirectInputOutput) - { - // Set UTF-8 encoding for standard output. - process.StartInfo.StandardOutputEncoding = CONSOLE_ENCODING; - // Enable raising events because Process does not raise events by default. - process.EnableRaisingEvents = true; - // Attach the event handler for OutputDataReceived before starting the process. - process.OutputDataReceived += (sender, e) => outputBuilder.Append(e.Data); - }*//* - process.StartAsUser(token); - process.PriorityClass = priorityClass; - - *//*if (redirectInputOutput) - process.BeginOutputReadLine();*/ - - /*if (process.WaitForExit(maxWaitMs)) - { - result = RemoveEncodingPreamble(outputBuilder.ToString()); - return process.ExitCode == 0; - } - if (!process.HasExited) - process.Kill();*//* - } - result = null; - return false; - }*/ - - /// - /// Executes the and waits a maximum time of for completion. If the process doesn't end in - /// this time, it gets aborted. This method tries to impersonate the interactive user and run the process under its identity. - /// - /// Program to execute - /// Program arguments - /// User token to run process - /// true to redirect standard streams. - /// Returns the contents of standard output. - /// Process priority - /// Maximum time to wait for completion - /// true if process was executed and finished correctly - private static bool TryExecute_Impersonated(string executable, string arguments, IntPtr token, out List startedProcesses, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) - { - // TODO: code is 99% redundant to TryExecute, refactor Process/ImpersonationProcess and Start/StartAsUser! - StringBuilder outputBuilder = new StringBuilder(); - startedProcesses = new List(); - ImpersonationProcess processCreated = new ImpersonationProcess { StartInfo = new ProcessStartInfo(executable, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = false } }; - /*if (redirectInputOutput) - { - // Set UTF-8 encoding for standard output. - process.StartInfo.StandardOutputEncoding = CONSOLE_ENCODING; - // Enable raising events because Process does not raise events by default. - process.EnableRaisingEvents = true; - // Attach the event handler for OutputDataReceived before starting the process. - process.OutputDataReceived += (sender, e) => outputBuilder.Append(e.Data); - }*/ - - try - { - processCreated.StartAsUser(token); - } - catch (ObjectDisposedException ex) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. The process object has already been disposed."); - return false; - } - catch (InvalidOperationException ex) - { - if (processCreated.StartInfo.UseShellExecute && (processCreated.StartInfo.RedirectStandardInput || processCreated.StartInfo.RedirectStandardOutput || processCreated.StartInfo.RedirectStandardError)) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. The UseShellExecute member of the StartInfo property is true while RedirectStandardInput, RedirectStandardOutput, or RedirectStandardError is true."); - } - else - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. No file name was specified in the Process component's StartInfo."); - } - return false; - } - catch (Win32Exception ex) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. There was an error in opening the associated file."); - return false; - } - catch (PlatformNotSupportedException ex) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. Method not supported on operating systems without shell support such as Nano Server (.NET Core only)."); - return false; - } - catch (Exception ex) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to start {executable}. Not sure what specific exception it is."); - return false; - } - - /*if (redirectInputOutput) - process.BeginOutputReadLine();*/ - - if (processCreated != null) - { - try - { - - processCreated.WaitForExit(1000); - - if (processCreated.HasExited) - { - // If the process has exited, then it's likely to be a launcher, so we try to find the children processes - List childProcesses = GetChildProcesses(processCreated); - startedProcesses.AddRange(childProcesses); - - } - else - { - // If we're here then the process was created and hasn't exited! - try - { - if (processCreated.PriorityClass != priorityClass) - { - processCreated.PriorityClass = priorityClass; - } - } - catch (Exception ex) - { - logger.Warn(ex, $"ProcessUtils/StartProcess: Exception while trying to set the Priority Class to {priorityClass.ToString("G")} for {executable}."); - } - startedProcesses.Add(processCreated); - } - - } - catch (Exception ex) - { - // Oops- the process didn't work. Let's skip this one - //process = null; - return false; - } - - //process = processCreated; return true; } - - return false; + else + { + return false; + } } - /// - /// Helper method to remove an existing encoding preamble () from the given . - /// - /// Raw string that might include the preamble (BOM). - /// String without preamble. - private static string RemoveEncodingPreamble(string rawString) + public static string GetProcessName(string executable) { - if (!string.IsNullOrWhiteSpace(rawString) && rawString.StartsWith(CONSOLE_ENCODING_PREAMBLE)) - return rawString.Substring(CONSOLE_ENCODING_PREAMBLE.Length); - return rawString; + if (executable.Contains(Path.DirectorySeparatorChar) || executable.Contains(Path.AltDirectorySeparatorChar) || executable.Contains(Path.VolumeSeparatorChar)) + { + return Path.GetFileNameWithoutExtension(executable); + } + else + { + return executable; + } } public static ProcessPriorityClass TranslatePriorityToClass(ProcessPriority processPriorityClass) @@ -808,1154 +695,5 @@ namespace DisplayMagician.Processes } - public class ProcessUtilsOld - { - - private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger(); - - const uint SE_GROUP_INTEGRITY = 0x00000020; - private const uint INVALID_SESSION_ID = 0xFFFFFFFF; - private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; - - [Flags] - public enum PROCESS_CREATION_FLAGS : UInt32 - { - ZERO_FLAG = 0x00000000, - DEBUG_PROCESS = 0x00000001, - DEBUG_ONLY_THIS_PROCESS = 0x00000002, - CREATE_SUSPENDED = 0x00000004, - DETACHED_PROCESS = 0x00000008, - CREATE_NEW_CONSOLE = 0x00000010, - CREATE_BREAKAWAY_FROM_JOB = 0x01000000, - CREATE_DEFAULT_ERROR_MODE = 0x04000000, - - CREATE_NEW_PROCESS_GROUP = 0x00000200, - CREATE_NO_WINDOW = 0x08000000, - CREATE_PROTECTED_PROCESS = 0x00040000, - CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000, - CREATE_SEPARATE_WOW_VDM = 0x00001000, - CREATE_SHARED_WOW_VDM = 0x00001000, - CREATE_UNICODE_ENVIRONMENT = 0x00000400, - EXTENDED_STARTUPINFO_PRESENT = 0x00080000, - INHERIT_PARENT_AFFINITY = 0x00010000, - - // Process creations flags - ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000, - BELOW_NORMAL_PRIORITY_CLASS = 0x00004000, - HIGH_PRIORITY_CLASS = 0x00000080, - IDLE_PRIORITY_CLASS = 0x00000040, - NORMAL_PRIORITY_CLASS = 0x00000020, - REALTIME_PRIORITY_CLASS = 0x00000100, - } - - private enum SW - { - SW_HIDE = 0, - SW_SHOWNORMAL = 1, - SW_NORMAL = 1, - SW_SHOWMINIMIZED = 2, - SW_SHOWMAXIMIZED = 3, - SW_MAXIMIZE = 3, - SW_SHOWNOACTIVATE = 4, - SW_SHOW = 5, - SW_MINIMIZE = 6, - SW_SHOWMINNOACTIVE = 7, - SW_SHOWNA = 8, - SW_RESTORE = 9, - SW_SHOWDEFAULT = 10, - SW_MAX = 10 - } - - private enum WTS_CONNECTSTATE_CLASS - { - WTSActive, - WTSConnected, - WTSConnectQuery, - WTSShadow, - WTSDisconnected, - WTSIdle, - WTSListen, - WTSReset, - WTSDown, - WTSInit - } - - private enum SECURITY_IMPERSONATION_LEVEL - { - SecurityAnonymous = 0, - SecurityIdentification = 1, - SecurityImpersonation = 2, - SecurityDelegation = 3, - } - - public enum SaferLevel : uint - { - Disallowed = 0, - Untrusted = 0x1000, - Constrained = 0x10000, - NormalUser = 0x20000, - FullyTrusted = 0x40000 - } - - public enum SaferScope : uint - { - Machine = 1, - User = 2 - } - - [Flags] - public enum SaferOpenFlags : uint - { - Open = 1 - } - - private enum TOKEN_TYPE - { - TokenPrimary = 1, - TokenImpersonation = 2 - } - - public enum TOKEN_INFORMATION_CLASS - { - /// - /// The buffer receives a TOKEN_USER structure that contains the user account of the token. - /// - TokenUser = 1, - - /// - /// The buffer receives a TOKEN_GROUPS structure that contains the group accounts associated with the token. - /// - TokenGroups, - - /// - /// The buffer receives a TOKEN_PRIVILEGES structure that contains the privileges of the token. - /// - TokenPrivileges, - - /// - /// The buffer receives a TOKEN_OWNER structure that contains the default owner security identifier (SID) for newly created objects. - /// - TokenOwner, - - /// - /// The buffer receives a TOKEN_PRIMARY_GROUP structure that contains the default primary group SID for newly created objects. - /// - TokenPrimaryGroup, - - /// - /// The buffer receives a TOKEN_DEFAULT_DACL structure that contains the default DACL for newly created objects. - /// - TokenDefaultDacl, - - /// - /// The buffer receives a TOKEN_SOURCE structure that contains the source of the token. TOKEN_QUERY_SOURCE access is needed to retrieve this information. - /// - TokenSource, - - /// - /// The buffer receives a TOKEN_TYPE value that indicates whether the token is a primary or impersonation token. - /// - TokenType, - - /// - /// The buffer receives a SECURITY_IMPERSONATION_LEVEL value that indicates the impersonation level of the token. If the access token is not an impersonation token, the function fails. - /// - TokenImpersonationLevel, - - /// - /// The buffer receives a TOKEN_STATISTICS structure that contains various token statistics. - /// - TokenStatistics, - - /// - /// The buffer receives a TOKEN_GROUPS structure that contains the list of restricting SIDs in a restricted token. - /// - TokenRestrictedSids, - - /// - /// The buffer receives a DWORD value that indicates the Terminal Services session identifier that is associated with the token. - /// - TokenSessionId, - - /// - /// The buffer receives a TOKEN_GROUPS_AND_PRIVILEGES structure that contains the user SID, the group accounts, the restricted SIDs, and the authentication ID associated with the token. - /// - TokenGroupsAndPrivileges, - - /// - /// Reserved. - /// - TokenSessionReference, - - /// - /// The buffer receives a DWORD value that is nonzero if the token includes the SANDBOX_INERT flag. - /// - TokenSandBoxInert, - - /// - /// Reserved. - /// - TokenAuditPolicy, - - /// - /// The buffer receives a TOKEN_ORIGIN value. - /// - TokenOrigin, - - /// - /// The buffer receives a TOKEN_ELEVATION_TYPE value that specifies the elevation level of the token. - /// - TokenElevationType, - - /// - /// The buffer receives a TOKEN_LINKED_TOKEN structure that contains a handle to another token that is linked to this token. - /// - TokenLinkedToken, - - /// - /// The buffer receives a TOKEN_ELEVATION structure that specifies whether the token is elevated. - /// - TokenElevation, - - /// - /// The buffer receives a DWORD value that is nonzero if the token has ever been filtered. - /// - TokenHasRestrictions, - - /// - /// The buffer receives a TOKEN_ACCESS_INFORMATION structure that specifies security information contained in the token. - /// - TokenAccessInformation, - - /// - /// The buffer receives a DWORD value that is nonzero if virtualization is allowed for the token. - /// - TokenVirtualizationAllowed, - - /// - /// The buffer receives a DWORD value that is nonzero if virtualization is enabled for the token. - /// - TokenVirtualizationEnabled, - - /// - /// The buffer receives a TOKEN_MANDATORY_LABEL structure that specifies the token's integrity level. - /// - TokenIntegrityLevel, - - /// - /// The buffer receives a DWORD value that is nonzero if the token has the UIAccess flag set. - /// - TokenUIAccess, - - /// - /// The buffer receives a TOKEN_MANDATORY_POLICY structure that specifies the token's mandatory integrity policy. - /// - TokenMandatoryPolicy, - - /// - /// The buffer receives the token's logon security identifier (SID). - /// - TokenLogonSid, - - /// - /// The maximum value for this enumeration - /// - MaxTokenInfoClass - } - - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct STARTUPINFOEX - { - public STARTUPINFO StartupInfo; - public IntPtr lpAttributeList; - } - - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - public struct STARTUPINFO - { - public Int32 cb; - public string lpReserved; - public string lpDesktop; - public string lpTitle; - public UInt32 dwX; - public UInt32 dwY; - public UInt32 dwXSize; - public UInt32 dwYSize; - public UInt32 dwXCountChars; - public UInt32 dwYCountChars; - public UInt32 dwFillAttribute; - public UInt32 dwFlags; - public Int16 wShowWindow; - public Int16 cbReserved2; - public IntPtr lpReserved2; - public IntPtr hStdInput; - public IntPtr hStdOutput; - public IntPtr hStdError; - } - - [StructLayout(LayoutKind.Sequential)] - public struct PROCESS_INFORMATION - { - public IntPtr hProcess; - public IntPtr hThread; - public uint dwProcessId; - public uint dwThreadId; - } - - [StructLayout(LayoutKind.Sequential)] - public struct SECURITY_ATTRIBUTES - { - public int nLength; - public IntPtr lpSecurityDescriptor; - public int bInheritHandle; - } - - [StructLayout(LayoutKind.Sequential)] - private struct SID_AND_ATTRIBUTES - { - public IntPtr Sid; - public uint Attributes; - } - - [StructLayout(LayoutKind.Sequential)] - private struct TOKEN_MANDATORY_LABEL - { - public SID_AND_ATTRIBUTES Label; - } - - [StructLayout(LayoutKind.Sequential)] - private struct WTS_SESSION_INFO - { - public readonly UInt32 SessionID; - - [MarshalAs(UnmanagedType.LPStr)] - public readonly String pWinStationName; - - public readonly WTS_CONNECTSTATE_CLASS State; - } - - - - - [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - private static extern bool SaferCreateLevel(SaferScope scope, SaferLevel level, SaferOpenFlags openFlags, out IntPtr pLevelHandle, IntPtr lpReserved); - - [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - private static extern bool SaferComputeTokenFromLevel(IntPtr LevelHandle, IntPtr InAccessToken, out IntPtr OutAccessToken, int dwFlags, IntPtr lpReserved); - - [DllImport("advapi32.dll", SetLastError = true)] - private static extern bool SaferCloseLevel(IntPtr hLevelHandle); - - [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid); - - private static bool SafeCloseHandle(IntPtr hObject) - { - return (hObject == IntPtr.Zero) ? true : CloseHandle(hObject); - } - - [DllImport("kernel32.dll", SetLastError = true)] - static extern IntPtr LocalFree(IntPtr hMem); - - [DllImport("kernel32.dll")] - [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")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool CreateProcess( - string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, - IntPtr lpThreadAttributes, bool bInheritHandles, PROCESS_CREATION_FLAGS dwCreationFlags, - IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFOEX lpStartupInfo, - out PROCESS_INFORMATION lpProcessInformation); - - [DllImport("advapi32.dll", SetLastError = true)] - static extern Boolean SetTokenInformation( - IntPtr TokenHandle, - TOKEN_INFORMATION_CLASS TokenInformationClass, - IntPtr TokenInformation, - UInt32 TokenInformationLength); - - [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] - static extern bool CreateProcessAsUser( - IntPtr hToken, - string lpApplicationName, - string lpCommandLine, - IntPtr lpProcessAttributes, - IntPtr lpThreadAttributes, - bool bInheritHandles, - PROCESS_CREATION_FLAGS dwCreationFlags, - IntPtr lpEnvironment, - string lpCurrentDirectory, - ref STARTUPINFO lpStartupInfo, - out PROCESS_INFORMATION lpProcessInformation); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool UpdateProcThreadAttribute( - IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, - IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool InitializeProcThreadAttributeList( - IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint ResumeThread(IntPtr hThread); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern uint SuspendThread(IntPtr hThread); - - [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] - private static extern bool DuplicateTokenEx( - IntPtr ExistingTokenHandle, - uint dwDesiredAccess, - IntPtr lpThreadAttributes, - int TokenType, - int ImpersonationLevel, - ref IntPtr DuplicateTokenHandle); - - [DllImport("userenv.dll", SetLastError = true)] - private static extern bool CreateEnvironmentBlock(ref IntPtr lpEnvironment, IntPtr hToken, bool bInherit); - - [DllImport("userenv.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DestroyEnvironmentBlock(IntPtr lpEnvironment); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool CloseHandle(IntPtr hSnapshot); - - [DllImport("kernel32.dll")] - private static extern uint WTSGetActiveConsoleSessionId(); - - [DllImport("Wtsapi32.dll")] - private static extern uint WTSQueryUserToken(uint SessionId, ref IntPtr phToken); - - [DllImport("wtsapi32.dll", SetLastError = true)] - private static extern int WTSEnumerateSessions( - IntPtr hServer, - int Reserved, - int Version, - ref IntPtr ppSessionInfo, - ref int pCount); - - - - public static ProcessPriorityClass TranslatePriorityToClass(ProcessPriority processPriorityClass) - { - ProcessPriorityClass wantedPriorityClass = ProcessPriorityClass.Normal; - switch (processPriorityClass) - { - case ProcessPriority.High: - wantedPriorityClass = ProcessPriorityClass.High; - break; - case ProcessPriority.AboveNormal: - wantedPriorityClass = ProcessPriorityClass.AboveNormal; - break; - case ProcessPriority.Normal: - wantedPriorityClass = ProcessPriorityClass.Normal; - break; - case ProcessPriority.BelowNormal: - wantedPriorityClass = ProcessPriorityClass.BelowNormal; - break; - case ProcessPriority.Idle: - wantedPriorityClass = ProcessPriorityClass.Idle; - break; - default: - wantedPriorityClass = ProcessPriorityClass.Normal; - break; - } - return wantedPriorityClass; - } - - public static PROCESS_CREATION_FLAGS TranslatePriorityClassToFlags(ProcessPriorityClass processPriorityClass) - { - PROCESS_CREATION_FLAGS wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS; - switch (processPriorityClass) - { - case ProcessPriorityClass.High: - wantedPriorityClass = PROCESS_CREATION_FLAGS.HIGH_PRIORITY_CLASS; - break; - case ProcessPriorityClass.AboveNormal: - wantedPriorityClass = PROCESS_CREATION_FLAGS.ABOVE_NORMAL_PRIORITY_CLASS; - break; - case ProcessPriorityClass.Normal: - wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS; - break; - case ProcessPriorityClass.BelowNormal: - wantedPriorityClass = PROCESS_CREATION_FLAGS.BELOW_NORMAL_PRIORITY_CLASS; - break; - case ProcessPriorityClass.Idle: - wantedPriorityClass = PROCESS_CREATION_FLAGS.IDLE_PRIORITY_CLASS; - break; - default: - wantedPriorityClass = PROCESS_CREATION_FLAGS.NORMAL_PRIORITY_CLASS; - break; - } - return wantedPriorityClass; - } - - public static List StartProcess(string executable, string arguments, ProcessPriority processPriority, int startTimeout = 1) - { - List runningProcesses = new List(); - Process process = null; - PROCESS_INFORMATION processInfo; - bool usingChildProcess = false; - try - { - //if (CreateProcessWithPriorityAsRestrictedUser(executable, arguments, ProcessUtils.TranslatePriorityToClass(processPriority), out processInfo)) - //if (CreateProcessWithPriority(executable, arguments, ProcessUtils.TranslatePriorityToClass(processPriority), out processInfo)) - if (StartProcessAsCurrentUser(out processInfo, executable)) - { - if (processInfo.dwProcessId > 0) - { - try - { - process = Process.GetProcessById((int)processInfo.dwProcessId); - Task.Delay(500); - if (process.HasExited) - { - // it's a launcher! We need to look for children - List childProcesses = GetChildProcesses(process); - runningProcesses.AddRange(childProcesses); - usingChildProcess = true; - } - else - { - runningProcesses.Add(process); - } - } - catch (Exception ex) - { - // it's a launcher! We need to look for children - List childProcesses = GetChildProcesses((int)processInfo.dwProcessId); - runningProcesses.AddRange(childProcesses); - usingChildProcess = true; - } - - } - else - { - logger.Warn($"ProcessUtils/StartProcess: CreateProcessWithPriority returned a process with PID 0 when trying to start process {executable}. This indicates that the process was not started, so we'll try it a different way."); - // Start the process using built in process library - ProcessStartInfo psi = new ProcessStartInfo(); - psi.FileName = executable; - psi.Arguments = arguments; - psi.WorkingDirectory = Path.GetDirectoryName(executable); - process = Process.Start(psi); - processInfo.hProcess = process.Handle; - processInfo.dwProcessId = (uint)process.Id; - if (!process.HasExited) - { - processInfo.dwThreadId = (uint)process.Threads[0].Id; - - // Change priority if we can (not always possible in this mode :( - try - { - // If this process is a protected process, then this will fail! - process.PriorityClass = TranslatePriorityToClass(processPriority); - } - catch (Exception ex) - { - // We would need need higher rights for this processto set the priority - // https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights - // At this stage I am not writing this, as it is a lot of work for a niche issue. - } - runningProcesses.Add(process); - } - } - } - else - { - // Start the process using built in process library - ProcessStartInfo psi = new ProcessStartInfo(); - string extension = Path.GetExtension(executable); - if (extension.Equals("com", StringComparison.CurrentCultureIgnoreCase) - || extension.Equals("exe", StringComparison.CurrentCultureIgnoreCase) - || extension.Equals("msi", StringComparison.CurrentCultureIgnoreCase)) - { - psi.UseShellExecute = false; - } - else - { - psi.Verb = "Open"; - } - psi.FileName = executable; - psi.Arguments = arguments; - psi.WorkingDirectory = Path.GetDirectoryName(executable); - process = Process.Start(psi); - processInfo.hProcess = process.Handle; - processInfo.dwProcessId = (uint)process.Id; - if (!process.HasExited) - { - processInfo.dwThreadId = (uint)process.Threads[0].Id; - // Change priority if we can (not always possible in this mode :( - try - { - // If this process is a protected process, then this will fail! - process.PriorityClass = TranslatePriorityToClass(processPriority); - } - catch(Exception ex) - { - // We would need need higher rights for this processto set the priority - // https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights - // At this stage I am not writing this, as it is a lot of work for a niche issue. - } - runningProcesses.Add(process); - } - - } - - } - catch (Exception ex) - { - if (!process.HasExited) - { - // Start the process using built in process library - ProcessStartInfo psi = new ProcessStartInfo(); - psi.FileName = executable; - psi.Arguments = arguments; - psi.WorkingDirectory = Path.GetDirectoryName(executable); - process = Process.Start(psi); - processInfo.hProcess = process.Handle; - processInfo.dwProcessId = (uint)process.Id; - processInfo.dwThreadId = (uint)process.Threads[0].Id; - //pInfo.dwThreadId = process.Threads[0].Id; - // Change priority - if (!process.HasExited) - { - runningProcesses.Add(process); - } - } - - } - - - // Check the launched exe hasn't exited within 2 secs - if (!usingChildProcess) - { - for (int secs = 0; secs <= (startTimeout * 1000); secs += 500) - { - // If we have no more processes left then we're done! - if (process.HasExited) - { - logger.Trace($"ProcessUtils/StartProcess: {executable} has exited early! It's likely to be a launcher! Trying to detect it's children."); - // As the original process has left the building, we'll overwrite it with the children processes - runningProcesses = GetChildProcesses(process); - break; - } - // Send a message to windows so that it doesn't think - // we're locked and try to kill us - System.Threading.Thread.CurrentThread.Join(0); - Thread.Sleep(500); - } - - } - - return runningProcesses; - } - - public static List GetChildProcesses(Process process) - { - List children = new List(); - ManagementObjectSearcher mos = new ManagementObjectSearcher($"Select * From Win32_Process Where ParentProcessID={process.Id}"); - foreach (ManagementObject mo in mos.Get()) - { - children.Add(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"]))); - } - return children; - } - - public static List GetChildProcesses(int processId) - { - List children = new List(); - ManagementObjectSearcher mos = new ManagementObjectSearcher($"Select * From Win32_Process Where ParentProcessID={processId}"); - foreach (ManagementObject mo in mos.Get()) - { - children.Add(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"]))); - } - return children; - } - - public static bool CreateProcessWithPriority(string fileName, string args, ProcessPriorityClass priorityClass, out PROCESS_INFORMATION processInfo) - { - PROCESS_CREATION_FLAGS processFlags = TranslatePriorityClassToFlags(priorityClass); - var cmd = new StringBuilder(); - cmd.Append('"').Append(fileName).Append('"'); - if (!string.IsNullOrWhiteSpace(args)) - { - cmd.Append(' ').Append(args); - } - bool success = false; - PROCESS_INFORMATION pInfo = new PROCESS_INFORMATION(); - var pSec = new SECURITY_ATTRIBUTES(); - var tSec = new SECURITY_ATTRIBUTES(); - pSec.nLength = Marshal.SizeOf(pSec); - tSec.nLength = Marshal.SizeOf(tSec); - var sInfoEx = new STARTUPINFOEX(); - sInfoEx.StartupInfo.cb = Marshal.SizeOf(sInfoEx); - try - { - success = CreateProcess(null, cmd.ToString(), ref pSec, ref tSec, false, processFlags, IntPtr.Zero, null, ref sInfoEx, out pInfo); - } - catch (Exception ex) - { - // This is a problem - } - if (!success) - { - try - { - success = CreateProcess(null, cmd.ToString(), IntPtr.Zero, IntPtr.Zero, false, processFlags, IntPtr.Zero, null, ref sInfoEx, out pInfo); - } - catch (Exception ex) - { - // This is a problem too - } - } - processInfo = pInfo; - - return success; - } - - /// Runs a process as a non-elevated version of the current user. - public static bool CreateProcessWithPriorityAsRestrictedUser(string fileName, string args, ProcessPriorityClass priorityClass, out PROCESS_INFORMATION processInfo) - { - if (string.IsNullOrWhiteSpace(fileName)) - throw new ArgumentException("Value cannot be null or whitespace.", nameof(fileName)); - - var pi = new PROCESS_INFORMATION(); - if (GetRestrictedSessionUserToken(out var hRestrictedToken)) - { - try - { - var si = new STARTUPINFO(); - var cmd = new StringBuilder(); - cmd.Append('"').Append(fileName).Append('"'); - if (!string.IsNullOrWhiteSpace(args)) - { - cmd.Append(' ').Append(args); - } - - if (!CreateProcessAsUser( - hRestrictedToken, - null, - cmd.ToString(), - IntPtr.Zero, - IntPtr.Zero, - true, // inherit handle - 0, - IntPtr.Zero, - null, - ref si, - out pi)) - { - processInfo = pi; - return false; - } - - - } - finally - { - CloseHandle(hRestrictedToken); - } - processInfo = pi; - return true; - } - else - { - processInfo = pi; - return false; - } - - } - - // based on https://stackoverflow.com/a/16110126/862099 - private static bool GetRestrictedSessionUserToken(out IntPtr token) - { - token = IntPtr.Zero; - if (!SaferCreateLevel(SaferScope.User, SaferLevel.NormalUser, SaferOpenFlags.Open, out var hLevel, IntPtr.Zero)) - { - return false; - } - - IntPtr hRestrictedToken = IntPtr.Zero; - TOKEN_MANDATORY_LABEL tml = default; - tml.Label.Sid = IntPtr.Zero; - IntPtr tmlPtr = IntPtr.Zero; - - try - { - if (!SaferComputeTokenFromLevel(hLevel, IntPtr.Zero, out hRestrictedToken, 0, IntPtr.Zero)) - { - return false; - } - - // Set the token to medium integrity. - tml.Label.Attributes = SE_GROUP_INTEGRITY; - tml.Label.Sid = IntPtr.Zero; - if (!ConvertStringSidToSid("S-1-16-8192", out tml.Label.Sid)) - { - return false; - } - - tmlPtr = Marshal.AllocHGlobal(Marshal.SizeOf(tml)); - Marshal.StructureToPtr(tml, tmlPtr, false); - if (!SetTokenInformation(hRestrictedToken, - TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, - tmlPtr, (uint)Marshal.SizeOf(tml))) - { - return false; - } - - token = hRestrictedToken; - hRestrictedToken = IntPtr.Zero; // make sure finally() doesn't close the handle - } - finally - { - SaferCloseLevel(hLevel); - SafeCloseHandle(hRestrictedToken); - if (tml.Label.Sid != IntPtr.Zero) - { - LocalFree(tml.Label.Sid); - } - if (tmlPtr != IntPtr.Zero) - { - Marshal.FreeHGlobal(tmlPtr); - } - } - - return true; - } - - public static void ResumeProcess(PROCESS_INFORMATION processInfo) - { - ResumeThread(processInfo.hThread); - } - - 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); - } - } - } - - // Gets the user token from the currently active session - private static bool GetSessionUserToken(ref IntPtr phUserToken) - { - var bResult = false; - var hImpersonationToken = IntPtr.Zero; - var activeSessionId = INVALID_SESSION_ID; - var pSessionInfo = IntPtr.Zero; - var sessionCount = 0; - - // Get a handle to the user access token for the current active session. - if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount) != 0) - { - var arrayElementSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO)); - var current = pSessionInfo; - - for (var i = 0; i < sessionCount; i++) - { - var si = (WTS_SESSION_INFO)Marshal.PtrToStructure((IntPtr)current, typeof(WTS_SESSION_INFO)); - current += arrayElementSize; - - if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) - { - activeSessionId = si.SessionID; - } - } - } - - // If enumerating did not work, fall back to the old method - if (activeSessionId == INVALID_SESSION_ID) - { - activeSessionId = WTSGetActiveConsoleSessionId(); - } - - if (WTSQueryUserToken(activeSessionId, ref hImpersonationToken) != 0) - { - // Convert the impersonation token to a primary token - bResult = DuplicateTokenEx(hImpersonationToken, 0, IntPtr.Zero, - (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, - ref phUserToken); - - CloseHandle(hImpersonationToken); - } - - return bResult; - } - - public static bool StartProcessAsCurrentUser(out PROCESS_INFORMATION processInfo, string appPath, string cmdLine = null, string workDir = null, bool visible = true ) - { - var hUserToken = IntPtr.Zero; - var startInfo = new STARTUPINFO(); - var procInfo = new PROCESS_INFORMATION(); - var pEnv = IntPtr.Zero; - int iResultOfCreateProcessAsUser; - - startInfo.cb = Marshal.SizeOf(typeof(STARTUPINFO)); - - try - { - if (!GetSessionUserToken(ref hUserToken)) - { - throw new Exception("StartProcessAsCurrentUser: GetSessionUserToken failed."); - } - - PROCESS_CREATION_FLAGS dwCreationFlags = PROCESS_CREATION_FLAGS.CREATE_UNICODE_ENVIRONMENT | (visible ? PROCESS_CREATION_FLAGS.CREATE_NEW_CONSOLE : PROCESS_CREATION_FLAGS.CREATE_NO_WINDOW); - startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); - startInfo.lpDesktop = "winsta0\\default"; - - if (!CreateEnvironmentBlock(ref pEnv, hUserToken, false)) - { - throw new Exception("StartProcessAsCurrentUser: CreateEnvironmentBlock failed."); - } - - if (!CreateProcessAsUser(hUserToken, - appPath, // Application Name - cmdLine, // Command Line - IntPtr.Zero, - IntPtr.Zero, - false, - dwCreationFlags, - pEnv, - workDir, // Working directory - ref startInfo, - out procInfo)) - { - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - throw new Exception("StartProcessAsCurrentUser: CreateProcessAsUser failed. Error Code -" + iResultOfCreateProcessAsUser); - } - - iResultOfCreateProcessAsUser = Marshal.GetLastWin32Error(); - } - finally - { - CloseHandle(hUserToken); - if (pEnv != IntPtr.Zero) - { - DestroyEnvironmentBlock(pEnv); - } - CloseHandle(procInfo.hThread); - CloseHandle(procInfo.hProcess); - } - processInfo = procInfo; - return true; - } - - public static bool ProcessExited(Process process) - { - try - { - Process processToTest = Process.GetProcessById(process.Id); - if (processToTest.HasExited) - { - logger.Trace($"ProcessUtils/ProcessExited: {process.Id} has exited and is not running. This means the process has finished!"); - return true; - } - else - { - logger.Trace($"ProcessUtils/ProcessExited: {process.Id} is still running as is has not exited yet."); - return false; - } - } - catch (ArgumentException ex) - { - logger.Trace($"ProcessUtils/ProcessExited: {process.Id} is not running, and the process ID has expired. This means the process has finished!"); - return true; - } - catch (InvalidOperationException ex) - { - logger.Warn($"ProcessUtils/ProcessExited: {process.Id} was not started by this process object. This likely means the process has finished!"); - return true; - } - catch (Exception ex) - { - logger.Trace($"ProcessUtils/ProcessExited: Exception when checking if {process.Id} is still running, so assuming the process has finished!"); - return true; - } - } - - public static bool ProcessExited(List processes) - { - int processClosedCount = 0; - foreach (Process p in processes) - { - if (ProcessExited(p)) - { - processClosedCount++; - } - } - if (processClosedCount == processes.Count) - { - logger.Trace($"ProcessUtils/ProcessExited2: All processes being monitored have exited, so no processes still running!"); - return true; - } - else - { - logger.Trace($"ProcessUtils/ProcessExited2: {processClosedCount} processes out of {processes.Count} processes have exited. At least one process is still running!"); - return false; - } - } - - public static bool StopProcess(Process processToStop) - { - try - { - // Stop the process - processToStop.CloseMainWindow(); - if (!processToStop.WaitForExit(1000)) - { - 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 bool StopProcess(List processes) - { - // Stop the programs in the reverse order we started them - foreach (Process processToStop in processes) - { - // Stop the process if it hasn't stopped already - try - { - if (!processToStop.HasExited) - { - logger.Debug($"ShortcutRepository/RunShortcut: Stopping process {processToStop.StartInfo.FileName}"); - if (ProcessUtils.StopProcess(processToStop)) - { - logger.Debug($"ShortcutRepository/RunShortcut: Successfully stopped process {processToStop.StartInfo.FileName}"); - } - else - { - logger.Warn($"ShortcutRepository/RunShortcut: Failed to stop process {processToStop.StartInfo.FileName} after main executable or game was exited by the user."); - } - } - } - catch (Exception ex) - { - logger.Error(ex, $"ShortcutRepository/RunShortcut: Exception while checking if processToStop has already exited"); - } - - } - return true; - } - } } diff --git a/DisplayMagician/ShortcutRepository.cs b/DisplayMagician/ShortcutRepository.cs index e8bd69b..8e22c9e 100644 --- a/DisplayMagician/ShortcutRepository.cs +++ b/DisplayMagician/ShortcutRepository.cs @@ -923,8 +923,7 @@ namespace DisplayMagician catch (Exception ex) { logger.Warn(ex, $"ShortcutRepository/RunShortcut: Exception setting priority of already running process {processToStart.Executable} to {processToStart.ProcessPriority.ToString("G")}"); - } - + } continue; } @@ -1138,7 +1137,7 @@ namespace DisplayMagician // We use the a user supplied executable as the thing we're monitoring instead! try { - processesToMonitor.AddRange(Process.GetProcessesByName(shortcutToUse.DifferentExecutableToMonitor)); + processesToMonitor.AddRange(Process.GetProcessesByName(ProcessUtils.GetProcessName(shortcutToUse.DifferentExecutableToMonitor))); logger.Trace($"ShortcutRepository/RunShortcut: {processesToMonitor.Count} '{shortcutToUse.DifferentExecutableToMonitor}' user specified processes to monitor are running"); foundSomethingToMonitor = true; } @@ -1270,19 +1269,22 @@ namespace DisplayMagician // And then show this notification DesktopNotifications.DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast); + // Start the game! + // NOTE: We now have to try and find the processes, as the game library will start to run the game itself, and we have no idea what process it is + // We'll have to look for the game exe later on in this process... List gameProcesses; gameProcesses = gameLibraryToUse.StartGame(gameToRun, shortcutToUse.GameArguments, shortcutToUse.ProcessPriority); // Delay 500ms Thread.Sleep(500); - if (gameProcesses.Count == 0) + /*if (gameProcesses.Count == 0) { // If there are no children found, then try to find all the running programs with the same names // (Some games relaunch themselves!) List sameNamedProcesses = Process.GetProcessesByName(Path.GetFileNameWithoutExtension(gameToRun.Executable)).ToList(); gameProcesses.AddRange(sameNamedProcesses); - } + }*/ // Wait for GameLibrary to start @@ -1410,7 +1412,7 @@ namespace DisplayMagician if (shortcutToUse.MonitorDifferentGameExe) { // If we are monitoring a different executable rather than the game itself, then lets get that name ready instead - string altGameProcessToMonitor = System.IO.Path.GetFileNameWithoutExtension(shortcutToUse.DifferentGameExeToMonitor); + string altGameProcessToMonitor = ProcessUtils.GetProcessName(shortcutToUse.DifferentGameExeToMonitor); // Now look for the thing we're supposed to monitor // and wait until it starts up