diff --git a/DisplayMagician/GameLibraries/SteamGame.cs b/DisplayMagician/GameLibraries/SteamGame.cs index 165dd32..9e38ef9 100644 --- a/DisplayMagician/GameLibraries/SteamGame.cs +++ b/DisplayMagician/GameLibraries/SteamGame.cs @@ -107,8 +107,11 @@ namespace DisplayMagician.GameLibraries { //if (gameProcess.MainModule.FileName.StartsWith(_steamGameExePath)) // numGameProcesses++; - if (gameProcess.ProcessName.Equals(_steamGameProcessName)) + if (!gameProcess.HasExited) + { numGameProcesses++; + } + } catch (Exception ex) { diff --git a/DisplayMagician/GameLibraries/SteamLibrary.cs b/DisplayMagician/GameLibraries/SteamLibrary.cs index e09c183..f04a6d0 100644 --- a/DisplayMagician/GameLibraries/SteamLibrary.cs +++ b/DisplayMagician/GameLibraries/SteamLibrary.cs @@ -823,6 +823,9 @@ namespace DisplayMagician.GameLibraries } //Process gameProcess = Process.Start(address); List gameProcesses = ProcessUtils.StartProcess(address,null,processPriority); + + // Wait 1 second then see if we need to find the child processes. + return gameProcesses; } #endregion diff --git a/DisplayMagician/Processes/ProcessUtils.cs b/DisplayMagician/Processes/ProcessUtils.cs index 8cfd8ce..6c69f0b 100644 --- a/DisplayMagician/Processes/ProcessUtils.cs +++ b/DisplayMagician/Processes/ProcessUtils.cs @@ -56,16 +56,21 @@ namespace DisplayMagician.Processes public static List StartProcess(string executable, string arguments, ProcessPriority processPriority, int startTimeout = 1) { - List runningProcesses = new List(); - Process process = null; - bool usingChildProcess = false; + List startedProcesses = new List(); + List returnedProcesses; + //bool usingChildProcess = false; - if (TryExecute_AutoImpersonate(executable, arguments, out process)) + if (TryExecute_AutoImpersonate(executable, arguments, out returnedProcesses)) { - logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} has successfully been started (ID: {process.Id})"); - runningProcesses.Add(process); + logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} has successfully been started"); } - return runningProcesses; + else + { + logger.Trace($"ProcessUtils/StartProcess: {executable} {arguments} was unable to be started"); + } + startedProcesses = returnedProcesses; + + return startedProcesses; } public static List GetChildProcesses(Process process) @@ -252,7 +257,7 @@ namespace DisplayMagician.Processes /// true if process was executed and finished correctly public static bool TryExecute(string executable, string arguments, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { - Process unused; + List unused; return TryExecute(executable, arguments, out unused, priorityClass, maxWaitMs); } @@ -266,11 +271,19 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// true if process was executed and finished correctly - public static bool TryExecute_AutoImpersonate(string executable, string arguments, out Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + public static bool TryExecute_AutoImpersonate(string executable, string arguments, out List processes, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { - return IsImpersonated ? - TryExecute_Impersonated(executable, arguments, out process, priorityClass, maxWaitMs) : - TryExecute(executable, arguments, out process, priorityClass, maxWaitMs); + bool result = false; + if (IsImpersonated) + { + result = TryExecute_Impersonated(executable, arguments, out processes, priorityClass, maxWaitMs); + } + else + { + result = TryExecute(executable, arguments, out processes, priorityClass, maxWaitMs); + } + //process = processReturned; + return result; } /// @@ -282,16 +295,16 @@ 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 Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + public static bool TryExecute_Impersonated(string executable, string arguments, out List processes, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { IntPtr userToken; - process = null; + processes = new List(); if (!ImpersonationHelper.GetTokenByProcess(out userToken, true)) return false; try { //return TryExecute_Impersonated(executable, arguments, userToken, false, out unused, priorityClass, maxWaitMs); - return TryExecute_Impersonated(executable, arguments, userToken, out process, priorityClass, maxWaitMs); + return TryExecute_Impersonated(executable, arguments, userToken, out processes, priorityClass, maxWaitMs); } finally { @@ -309,7 +322,7 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// - public static bool TryExecuteReadString(string executable, string arguments, out Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + 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); } @@ -324,11 +337,11 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// - public static bool TryExecuteReadString_AutoImpersonate(string executable, string arguments, out Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + 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 process, priorityClass, maxWaitMs) : - TryExecuteReadString(executable, arguments, out process, priorityClass, maxWaitMs); + TryExecuteReadString_Impersonated(executable, arguments, out processes, priorityClass, maxWaitMs) : + TryExecuteReadString(executable, arguments, out processes, priorityClass, maxWaitMs); } /// @@ -342,17 +355,17 @@ namespace DisplayMagician.Processes /// 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 Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = INFINITE) + 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)) { - process = null; + processes = new List(); return false; } try { - return TryExecute_Impersonated(executable, arguments, userToken, out process, priorityClass, maxWaitMs); + return TryExecute_Impersonated(executable, arguments, userToken, out processes, priorityClass, maxWaitMs); } finally { @@ -426,10 +439,10 @@ namespace DisplayMagician.Processes /// Process priority /// Maximum time to wait for completion /// - private static bool TryExecute(string executable, string arguments, out Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + private static bool TryExecute(string executable, string arguments, out List startedProcesses, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) { //StringBuilder outputBuilder = new StringBuilder(); - + startedProcesses = new List(); bool _isFile = false; bool _isExe = false; if (File.Exists(executable)) @@ -469,44 +482,102 @@ namespace DisplayMagician.Processes }; } - using (Process processCreated = new Process { StartInfo = psi }) + Process processCreated = new Process { StartInfo = psi }; + + /*if (redirectInputOutput) { - /*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); - }*/ - - + // 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.Start(); - - /*if (redirectInputOutput) - process.BeginOutputReadLine();*/ - - if (processCreated.WaitForExit(maxWaitMs)) - { - //result = RemoveEncodingPreamble(outputBuilder.ToString()); - if (!processCreated.HasExited) - { - try - { - processCreated.PriorityClass = priorityClass; - } - catch(Exception ex) - { - logger.Error(ex, $"ProcessUtils/TryExecute: Exception while trying to set the Priority Class to {priorityClass.ToString("G")} for {executable}."); - } - process = processCreated; - return true; - } - } } - process = null; + 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();*/ + + //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; + } + + //process = null; return false; } @@ -567,45 +638,105 @@ namespace DisplayMagician.Processes /// 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 Process process, ProcessPriorityClass priorityClass = ProcessPriorityClass.Normal, int maxWaitMs = 1000) + 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(); - using (ImpersonationProcess processCreated = new ImpersonationProcess { StartInfo = new ProcessStartInfo(executable, arguments) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = false} }) + 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 { - /*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); - }*/ processCreated.StartAsUser(token); - - /*if (redirectInputOutput) - process.BeginOutputReadLine();*/ - - if (processCreated.WaitForExit(maxWaitMs)) + } + 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)) { - //result = RemoveEncodingPreamble(outputBuilder.ToString()); - if (!processCreated.HasExited) + 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 { - processCreated.PriorityClass = priorityClass; + if (processCreated.PriorityClass != priorityClass) + { + processCreated.PriorityClass = priorityClass; + } } catch (Exception ex) { - logger.Error(ex, $"ProcessUtils/TryExecute_Impersonated: Exception while trying to set the Priority Class to {priorityClass.ToString("G")} for {executable}."); + logger.Warn(ex, $"ProcessUtils/StartProcess: Exception while trying to set the Priority Class to {priorityClass.ToString("G")} for {executable}."); } - process = processCreated; - return true; + 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; } - process = null; + return false; } @@ -621,6 +752,33 @@ namespace DisplayMagician.Processes return rawString; } + 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; diff --git a/DisplayMagician/Properties/AssemblyInfo.cs b/DisplayMagician/Properties/AssemblyInfo.cs index 4a3df00..8f9fa3a 100644 --- a/DisplayMagician/Properties/AssemblyInfo.cs +++ b/DisplayMagician/Properties/AssemblyInfo.cs @@ -26,8 +26,8 @@ using System.Resources; [assembly: Guid("e4ceaf5e-ad01-4695-b179-31168eb74c48")] // Version information -[assembly: AssemblyVersion("2.1.0.287")] -[assembly: AssemblyFileVersion("2.1.0.287")] +[assembly: AssemblyVersion("2.1.0.308")] +[assembly: AssemblyFileVersion("2.1.0.308")] [assembly: NeutralResourcesLanguageAttribute( "en" )] [assembly: CLSCompliant(true)] diff --git a/DisplayMagician/ShortcutRepository.cs b/DisplayMagician/ShortcutRepository.cs index 9e805b4..e8bd69b 100644 --- a/DisplayMagician/ShortcutRepository.cs +++ b/DisplayMagician/ShortcutRepository.cs @@ -1276,6 +1276,15 @@ namespace DisplayMagician // Delay 500ms Thread.Sleep(500); + 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 for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500) { @@ -1659,12 +1668,14 @@ namespace DisplayMagician // Now we know the game library app is running then // we wait until the game has started running (*allows for updates to occur) + bool gameRunning = false; for (int secs = 0; secs <= (shortcutToUse.StartTimeout * 1000); secs += 500) { if (gameToRun.IsRunning) { // The game is running! So now we continue processing + gameRunning = true; logger.Debug($"ShortcutRepository/RunShortcut: Found the '{gameToRun.Name}' process has started"); try @@ -1683,13 +1694,14 @@ namespace DisplayMagician break; } + // Delay 500ms Thread.Sleep(500); } // If the game still isn't running then there is an issue so tell the user and revert things back - if (!gameToRun.IsRunning) + if (!gameRunning) { logger.Error($"ShortcutRepository/RunShortcut: The Game {gameToRun.Name} didn't start for some reason (or the game uses a starter exe that launches the game itself)! so reverting changes back if needed..."); logger.Warn($"ShortcutRepository/RunShortcut: We were monitoring {gameToRun.ExePath}. You may need to manually add an alternative game executable to monitor - please run the game manually and check if another executable in {Path.GetDirectoryName(gameToRun.ExePath)} is run, and then monitor that instead.");