// 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);
}
}
}