// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Diagnostics; using System.Globalization; using System.IO; using ManagedCommon; using RunnerV2.Helpers; namespace RunnerV2.Models { /// /// Base abstract class for modules that launch and manage external processes. /// internal abstract class ProcessModuleAbstractClass { /// /// Options for launching a process. /// [Flags] public enum ProcessLaunchOptions { /// /// Only a single instance of the process should be running. /// SingletonProcess = 1, /// /// Elevate the process if the current process is elevated. /// ElevateIfApplicable = 2, /// /// Provide the runner process ID as the first argument to the launched process. /// RunnerProcessIdAsFirstArgument = 4, /// /// Indicates that the application should not launch automatically when the specified module is enabled. /// SupressLaunchOnModuleEnabled = 8, /// /// Specifies that the process should be started using the operating system shell. /// UseShellExecute = 16, /// /// Indicates that the process should never exit automatically. /// /// Use this value when using a helper process to launch an application like explorer.exe. NeverExit = 32, /// /// Suppresses UI when process is launched. /// HideUI = 64, /// /// Sets the launched process to realtime priority. /// RealtimePriority = 128, /// /// Indicates that the process should never be launched with elevated privileges, even if the runner process is elevated. /// NeverElevate = 256, } /// /// Gets the relative or absolute path to the process executable. /// public abstract string ProcessPath { get; } /// /// Gets the name of the process without the .exe extension. /// /// /// Has no effect if the has the flag set. /// public abstract string ProcessName { get; } /// /// Gets the arguments to pass to the process on launch. /// /// /// If not overridden, no arguments are passed. /// If the has the flag is set, the runner process ID is prepended to these arguments. /// public virtual string ProcessArguments { get; } = string.Empty; /// /// Gets the options used to configure how the process is launched. /// public abstract ProcessLaunchOptions LaunchOptions { get; } /// /// Ensures that atleast one process is launched. If the process is already running, does nothing. /// public void EnsureLaunched() { Process[] processes = Process.GetProcessesByName(ProcessName); if (processes.Length > 0) { return; } LaunchProcess(); } /// /// Launches the process with the specified options. /// /// Specifies if the class is currently calling this function as part of a module startup public void LaunchProcess(bool isModuleEnableProcess = false) { if (isModuleEnableProcess && LaunchOptions.HasFlag(ProcessLaunchOptions.SupressLaunchOnModuleEnabled)) { return; } if (LaunchOptions.HasFlag(ProcessLaunchOptions.SingletonProcess)) { Process[] processes = Process.GetProcessesByName(ProcessName); if (processes.Length > 0) { return; } } string arguments = (LaunchOptions.HasFlag(ProcessLaunchOptions.RunnerProcessIdAsFirstArgument) ? Environment.ProcessId.ToString(CultureInfo.InvariantCulture) + (string.IsNullOrEmpty(ProcessArguments) ? string.Empty : " ") : string.Empty) + ProcessArguments; if (ElevationHelper.IsProcessElevated() && LaunchOptions.HasFlag(ProcessLaunchOptions.NeverElevate)) { PowerToys.Interop.Elevation.RunNonElevated(Path.GetFullPath(ProcessPath), arguments); return; } Process? p = Process.Start(new ProcessStartInfo() { UseShellExecute = LaunchOptions.HasFlag(ProcessLaunchOptions.UseShellExecute), FileName = ProcessPath, Arguments = arguments, Verb = LaunchOptions.HasFlag(ProcessLaunchOptions.ElevateIfApplicable) && ElevationHelper.IsProcessElevated() ? "runas" : "open", }); if (LaunchOptions.HasFlag(ProcessLaunchOptions.RealtimePriority)) { try { if (p != null && !p.HasExited) { p.PriorityClass = ProcessPriorityClass.RealTime; } } catch (Exception ex) { Logger.LogError($"Failed to set realtime priority for process {ProcessName}", ex); } } } /// /// Schedules all processes with the specified . /// /// /// If the has the flag set, this function does nothing. /// public void ProcessExit() { if (LaunchOptions.HasFlag(ProcessLaunchOptions.NeverExit)) { return; } ProcessHelper.ScheudleProcessKill(ProcessName); } } }