2025-12-11 01:43:04 +01:00
// 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 ;
2026-02-12 02:02:28 +01:00
using System.IO ;
2026-02-03 02:05:42 +01:00
using ManagedCommon ;
2025-12-11 01:43:04 +01:00
using RunnerV2.Helpers ;
2025-12-19 20:32:03 +01:00
namespace RunnerV2.Models
2025-12-11 01:43:04 +01:00
{
2025-12-11 18:15:07 +01:00
/// <summary>
/// Base abstract class for modules that launch and manage external processes.
/// </summary>
2025-12-11 01:43:04 +01:00
internal abstract class ProcessModuleAbstractClass
{
/// <summary>
/// Options for launching a process.
/// </summary>
[Flags]
public enum ProcessLaunchOptions
{
/// <summary>
/// Only a single instance of the process should be running.
/// </summary>
SingletonProcess = 1 ,
/// <summary>
/// Elevate the process if the current process is elevated.
/// </summary>
ElevateIfApplicable = 2 ,
/// <summary>
/// Provide the runner process ID as the first argument to the launched process.
/// </summary>
RunnerProcessIdAsFirstArgument = 4 ,
/// <summary>
/// Indicates that the application should not launch automatically when the specified module is enabled.
/// </summary>
SupressLaunchOnModuleEnabled = 8 ,
/// <summary>
/// Specifies that the process should be started using the operating system shell.
/// </summary>
UseShellExecute = 16 ,
/// <summary>
/// Indicates that the process should never exit automatically.
/// </summary>
/// <remarks>Use this value when using a helper process to launch an application like explorer.exe.</remarks>
NeverExit = 32 ,
2025-12-14 22:48:34 +01:00
/// <summary>
/// Suppresses UI when process is launched.
/// </summary>
HideUI = 64 ,
2025-12-21 23:59:39 +01:00
/// <summary>
/// Sets the launched process to realtime priority.
/// </summary>
RealtimePriority = 128 ,
2026-02-12 02:02:28 +01:00
/// <summary>
/// Indicates that the process should never be launched with elevated privileges, even if the runner process is elevated.
/// </summary>
NeverElevate = 256 ,
2025-12-11 01:43:04 +01:00
}
2025-12-11 18:15:07 +01:00
/// <summary>
/// Gets the relative or absolute path to the process executable.
/// </summary>
2025-12-11 01:43:04 +01:00
public abstract string ProcessPath { get ; }
2025-12-11 18:15:07 +01:00
/// <summary>
/// Gets the name of the process without the .exe extension.
/// </summary>
/// <remarks>
/// Has no effect if the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.UseShellExecute"/> flag set.
/// </remarks>
2025-12-11 01:43:04 +01:00
public abstract string ProcessName { get ; }
2025-12-11 18:15:07 +01:00
/// <summary>
/// Gets the arguments to pass to the process on launch.
/// </summary>
/// <remarks>
/// If not overridden, no arguments are passed.
/// If the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.RunnerProcessIdAsFirstArgument"/> flag is set, the runner process ID is prepended to these arguments.
/// </remarks>
2025-12-11 01:43:04 +01:00
public virtual string ProcessArguments { get ; } = string . Empty ;
2025-12-11 18:15:07 +01:00
/// <summary>
/// Gets the options used to configure how the process is launched.
/// </summary>
2025-12-11 01:43:04 +01:00
public abstract ProcessLaunchOptions LaunchOptions { get ; }
2025-12-11 18:15:07 +01:00
/// <summary>
/// Ensures that atleast one process is launched. If the process is already running, does nothing.
/// </summary>
2025-12-11 01:43:04 +01:00
public void EnsureLaunched ( )
{
Process [ ] processes = Process . GetProcessesByName ( ProcessName ) ;
if ( processes . Length > 0 )
{
return ;
}
LaunchProcess ( ) ;
}
2025-12-11 18:15:07 +01:00
/// <summary>
/// Launches the process with the specified options.
/// </summary>
/// <param name="isModuleEnableProcess">Specifies if the <see cref="Runner"/> class is currently calling this function as part of a module startup</param>
2025-12-11 01:43:04 +01:00
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 ;
2026-02-12 02:02:28 +01:00
if ( ElevationHelper . IsProcessElevated ( ) & & LaunchOptions . HasFlag ( ProcessLaunchOptions . NeverElevate ) )
{
PowerToys . Interop . Elevation . RunNonElevated ( Path . GetFullPath ( ProcessPath ) , arguments ) ;
return ;
}
2025-12-21 23:59:39 +01:00
Process ? p = Process . Start ( new ProcessStartInfo ( )
2025-12-11 01:43:04 +01:00
{
UseShellExecute = LaunchOptions . HasFlag ( ProcessLaunchOptions . UseShellExecute ) ,
FileName = ProcessPath ,
Arguments = arguments ,
Verb = LaunchOptions . HasFlag ( ProcessLaunchOptions . ElevateIfApplicable ) & & ElevationHelper . IsProcessElevated ( ) ? "runas" : "open" ,
} ) ;
2025-12-21 23:59:39 +01:00
if ( LaunchOptions . HasFlag ( ProcessLaunchOptions . RealtimePriority ) )
{
try
{
if ( p ! = null & & ! p . HasExited )
{
p . PriorityClass = ProcessPriorityClass . RealTime ;
}
}
catch ( Exception ex )
{
2026-02-03 02:05:42 +01:00
Logger . LogError ( $"Failed to set realtime priority for process {ProcessName}" , ex ) ;
2025-12-21 23:59:39 +01:00
}
}
2025-12-11 01:43:04 +01:00
}
2025-12-11 18:15:07 +01:00
/// <summary>
/// Schedules all processes with the specified <see cref="ProcessName"/>.
/// </summary>
/// <remarks>
/// If the <see cref="LaunchOptions"/> has the <see cref="ProcessLaunchOptions.NeverExit"/> flag set, this function does nothing.
/// </remarks>
public void ProcessExit ( )
2025-12-11 01:43:04 +01:00
{
if ( LaunchOptions . HasFlag ( ProcessLaunchOptions . NeverExit ) )
{
return ;
}
2025-12-11 18:15:07 +01:00
ProcessHelper . ScheudleProcessKill ( ProcessName ) ;
2025-12-11 01:43:04 +01:00
}
}
}