< Summary - Kestrun — Combined Coverage

Information
Class: Kestrun.Hosting.KestrunHost
Assembly: Kestrun
File(s): /home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHost.cs
Tag: Kestrun/Kestrun@2d87023b37eb91155071c91dd3d6a2eeb3004705
Line coverage
64%
Covered lines: 354
Uncovered lines: 197
Coverable lines: 551
Total lines: 1571
Line coverage: 64.2%
Branch coverage
56%
Covered branches: 148
Total branches: 260
Branch coverage: 56.9%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 08/26/2025 - 01:25:22 Line coverage: 81.1% (232/286) Branch coverage: 71.5% (103/144) Total lines: 840 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/01/2025 - 04:08:24 Line coverage: 81.1% (233/287) Branch coverage: 71.5% (103/144) Total lines: 843 Tag: Kestrun/Kestrun@d6f26a131219b7a7fcb4e129af3193ec2ec4892909/04/2025 - 17:02:01 Line coverage: 81.5% (235/288) Branch coverage: 71.5% (103/144) Total lines: 844 Tag: Kestrun/Kestrun@f3880b25ea131298aa2f8b1e0d0a8d55eb160bc009/06/2025 - 18:30:33 Line coverage: 81.9% (236/288) Branch coverage: 72.2% (104/144) Total lines: 844 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/07/2025 - 18:41:40 Line coverage: 79.5% (237/298) Branch coverage: 72.2% (107/148) Total lines: 861 Tag: Kestrun/Kestrun@2192d4ccb46312ce89b7f7fda1aa8c915bfa228409/08/2025 - 16:52:37 Line coverage: 74.7% (249/333) Branch coverage: 71% (108/152) Total lines: 906 Tag: Kestrun/Kestrun@14183915904bc10657c407aa6953ae9be314429f09/08/2025 - 20:34:03 Line coverage: 74% (257/347) Branch coverage: 71% (108/152) Total lines: 920 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/09/2025 - 05:44:24 Line coverage: 73.9% (255/345) Branch coverage: 71% (108/152) Total lines: 918 Tag: Kestrun/Kestrun@a26a91936c400a7f2324671b2222643fb772438109/09/2025 - 21:56:59 Line coverage: 73.9% (256/346) Branch coverage: 71% (108/152) Total lines: 919 Tag: Kestrun/Kestrun@739093f321f10605cc4d1029da7300e3bb4dcba909/12/2025 - 03:43:11 Line coverage: 73.8% (257/348) Branch coverage: 71% (108/152) Total lines: 938 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904209/12/2025 - 16:20:13 Line coverage: 73.8% (257/348) Branch coverage: 71% (108/152) Total lines: 926 Tag: Kestrun/Kestrun@bd014be0a15f3c9298922d2ff67068869adda2a009/13/2025 - 21:11:10 Line coverage: 68.3% (257/376) Branch coverage: 60.6% (108/178) Total lines: 1032 Tag: Kestrun/Kestrun@c00c04d65edffc6840698a5c67a70cae1ad411d909/14/2025 - 21:23:16 Line coverage: 68.1% (257/377) Branch coverage: 60.6% (108/178) Total lines: 1023 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422309/16/2025 - 04:01:29 Line coverage: 68% (256/376) Branch coverage: 60.6% (108/178) Total lines: 1028 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb10/13/2025 - 16:52:37 Line coverage: 69.4% (329/474) Branch coverage: 62.1% (138/222) Total lines: 1409 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 01:01:18 Line coverage: 65.8% (329/500) Branch coverage: 58.4% (138/236) Total lines: 1449 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/15/2025 - 21:27:26 Line coverage: 63.6% (335/526) Branch coverage: 56% (138/246) Total lines: 1493 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/17/2025 - 15:48:30 Line coverage: 64.2% (354/551) Branch coverage: 56.9% (148/260) Total lines: 1571 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d86 08/26/2025 - 01:25:22 Line coverage: 81.1% (232/286) Branch coverage: 71.5% (103/144) Total lines: 840 Tag: Kestrun/Kestrun@07f821172e5dc3657f1be7e6818f18d6721cf38a09/01/2025 - 04:08:24 Line coverage: 81.1% (233/287) Branch coverage: 71.5% (103/144) Total lines: 843 Tag: Kestrun/Kestrun@d6f26a131219b7a7fcb4e129af3193ec2ec4892909/04/2025 - 17:02:01 Line coverage: 81.5% (235/288) Branch coverage: 71.5% (103/144) Total lines: 844 Tag: Kestrun/Kestrun@f3880b25ea131298aa2f8b1e0d0a8d55eb160bc009/06/2025 - 18:30:33 Line coverage: 81.9% (236/288) Branch coverage: 72.2% (104/144) Total lines: 844 Tag: Kestrun/Kestrun@aeddbedb8a96e9137aac94c2d5edd011b57ac87109/07/2025 - 18:41:40 Line coverage: 79.5% (237/298) Branch coverage: 72.2% (107/148) Total lines: 861 Tag: Kestrun/Kestrun@2192d4ccb46312ce89b7f7fda1aa8c915bfa228409/08/2025 - 16:52:37 Line coverage: 74.7% (249/333) Branch coverage: 71% (108/152) Total lines: 906 Tag: Kestrun/Kestrun@14183915904bc10657c407aa6953ae9be314429f09/08/2025 - 20:34:03 Line coverage: 74% (257/347) Branch coverage: 71% (108/152) Total lines: 920 Tag: Kestrun/Kestrun@3790ee5884494a7a2a829344a47743e0bf492e7209/09/2025 - 05:44:24 Line coverage: 73.9% (255/345) Branch coverage: 71% (108/152) Total lines: 918 Tag: Kestrun/Kestrun@a26a91936c400a7f2324671b2222643fb772438109/09/2025 - 21:56:59 Line coverage: 73.9% (256/346) Branch coverage: 71% (108/152) Total lines: 919 Tag: Kestrun/Kestrun@739093f321f10605cc4d1029da7300e3bb4dcba909/12/2025 - 03:43:11 Line coverage: 73.8% (257/348) Branch coverage: 71% (108/152) Total lines: 938 Tag: Kestrun/Kestrun@d160286e3020330b1eb862d66a37db2e26fc904209/12/2025 - 16:20:13 Line coverage: 73.8% (257/348) Branch coverage: 71% (108/152) Total lines: 926 Tag: Kestrun/Kestrun@bd014be0a15f3c9298922d2ff67068869adda2a009/13/2025 - 21:11:10 Line coverage: 68.3% (257/376) Branch coverage: 60.6% (108/178) Total lines: 1032 Tag: Kestrun/Kestrun@c00c04d65edffc6840698a5c67a70cae1ad411d909/14/2025 - 21:23:16 Line coverage: 68.1% (257/377) Branch coverage: 60.6% (108/178) Total lines: 1023 Tag: Kestrun/Kestrun@c9d2f0b3dd164d7dc0dc2407a9f006293d92422309/16/2025 - 04:01:29 Line coverage: 68% (256/376) Branch coverage: 60.6% (108/178) Total lines: 1028 Tag: Kestrun/Kestrun@e5263347b0baba68d9fd62ffbf60a7dd87f994bb10/13/2025 - 16:52:37 Line coverage: 69.4% (329/474) Branch coverage: 62.1% (138/222) Total lines: 1409 Tag: Kestrun/Kestrun@10d476bee71c71ad215bb8ab59f219887b5b4a5e10/15/2025 - 01:01:18 Line coverage: 65.8% (329/500) Branch coverage: 58.4% (138/236) Total lines: 1449 Tag: Kestrun/Kestrun@7c4ce528870211ad6c2d2398c31ec13097fc584010/15/2025 - 21:27:26 Line coverage: 63.6% (335/526) Branch coverage: 56% (138/246) Total lines: 1493 Tag: Kestrun/Kestrun@c33ec02a85e4f8d6061aeaab5a5e8c3a8b66559410/17/2025 - 15:48:30 Line coverage: 64.2% (354/551) Branch coverage: 56.9% (148/260) Total lines: 1571 Tag: Kestrun/Kestrun@b8199aff869a847b75e185d0527ba45e04a43d86

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_Builder()100%11100%
get_App()100%22100%
get_ApplicationName()100%22100%
get_Options()100%11100%
.ctor(...)87.5%88100%
get_IsConfigured()100%11100%
get_StartTime()100%11100%
get_StopTime()100%11100%
get_Uptime()0%4260%
get_RunspacePool()50%22100%
get_FeatureQueue()100%11100%
get_HealthProbes()100%11100%
get_KestrunRoot()100%11100%
get_Logger()100%11100%
get_Scheduler()100%11100%
get_Tasks()100%210%
get_RouteGroupStack()100%11100%
get_RegisteredRoutes()100%210%
get_RegisteredAuthentications()100%11100%
get_DefaultCacheControl()100%11100%
get_PowershellMiddlewareEnabled()100%11100%
get_DefaultHost()100%11100%
get_StatusCodeOptions()100%11100%
set_StatusCodeOptions(...)0%620%
get_ExceptionOptions()100%11100%
set_ExceptionOptions(...)50%2275%
get_ForwardedHeaderOptions()100%11100%
set_ForwardedHeaderOptions(...)100%22100%
.ctor(...)100%11100%
LogConstructorArgs(...)100%22100%
SetWorkingDirectoryIfNeeded(...)100%44100%
AddKestrunModulePathIfMissing(...)75%8883.33%
InitializeOptions(...)100%22100%
AddUserModulePaths(...)70%111077.77%
AddProbe(...)100%210%
AddProbe(...)100%210%
AddProbe(...)0%2040%
GetHealthProbesSnapshot()100%210%
RegisterProbeInternal(...)50%2277.77%
ConfigureListener(...)100%88100%
ConfigureListener(...)100%11100%
ConfigureListener(...)100%11100%
ConfigureListener(...)0%272160%
ConfigureListener(...)0%110100%
ValidateConfiguration()100%66100%
InitializeRunspacePool(...)50%5466.66%
ConfigureKestrelBase()100%11100%
ConfigureNamedPipes()75%9433.33%
ConfigureHttpsAdapter(...)50%5213.33%
BindListeners(...)68.75%231669.56%
LogConfiguredEndpoints()25%5457.14%
HandleConfigurationError(...)100%11100%
EnableConfiguration(...)100%2284.21%
RegisterDefaultHealthProbes()50%2266.66%
Build()100%11100%
ValidateBuilderState()50%2266.66%
ApplyQueuedServices()100%22100%
BuildWebApplication()100%11100%
ConfigureBuiltInMiddleware()100%11100%
ConfigureExceptionHandling()83.33%6685.71%
ConfigureForwardedHeaders()75%4480%
ConfigureStatusCodePages()25%7440%
ConfigurePowerShellRuntime()16.66%24620%
LogApplicationInfo()100%11100%
LogPagesDirectory()25%4471.42%
ApplyQueuedMiddleware()100%22100%
ApplyFeatures()100%22100%
IsServiceRegistered(...)50%44100%
IsServiceRegistered()100%210%
AddService(...)100%11100%
Use(...)100%11100%
AddFeature(...)100%11100%
AddScheduling(...)100%1212100%
AddTasks(...)0%210140%
AddControllers(...)0%620%
AddPowerShellRuntime(...)100%22100%
AddSignalR(...)0%4260%
Run()0%2040%
StartAsync()100%44100%
StopAsync()100%4470%
Stop()66.66%6685.71%
get_IsRunning()75%44100%
CreateRunspacePool(...)80.76%282684.61%
Dispose()75%88100%

File(s)

/home/runner/work/Kestrun/Kestrun/src/CSharp/Kestrun/Hosting/KestrunHost.cs

#LineLine coverage
 1using Microsoft.AspNetCore.Server.Kestrel.Core;
 2using System.Net;
 3using System.Management.Automation;
 4using System.Management.Automation.Runspaces;
 5using Kestrun.Utilities;
 6using Microsoft.CodeAnalysis;
 7using System.Reflection;
 8using System.Security.Cryptography.X509Certificates;
 9using Serilog;
 10using Serilog.Events;
 11using Microsoft.AspNetCore.SignalR;
 12using Kestrun.Scheduling;
 13using Kestrun.SharedState;
 14using Kestrun.Middleware;
 15using Kestrun.Scripting;
 16using Kestrun.Hosting.Options;
 17using System.Runtime.InteropServices;
 18using Microsoft.PowerShell;
 19using System.Net.Sockets;
 20using Microsoft.Net.Http.Headers;
 21using Kestrun.Authentication;
 22using Kestrun.Health;
 23using Kestrun.Tasks;
 24using Kestrun.Runtime;
 25
 26namespace Kestrun.Hosting;
 27
 28/// <summary>
 29/// Provides hosting and configuration for the Kestrun application, including service registration, middleware setup, an
 30/// </summary>
 31public class KestrunHost : IDisposable
 32{
 33    #region Fields
 129034    internal WebApplicationBuilder Builder { get; }
 35
 36    private WebApplication? _app;
 37
 10838    internal WebApplication App => _app ?? throw new InvalidOperationException("WebApplication is not built yet. Call Bu
 39
 40    /// <summary>
 41    /// Gets the application name for the Kestrun host.
 42    /// </summary>
 243    public string ApplicationName => Options.ApplicationName ?? "KestrunApp";
 44
 45    /// <summary>
 46    /// Gets the configuration options for the Kestrun host.
 47    /// </summary>
 116148    public KestrunOptions Options { get; private set; } = new();
 49
 50    /// <summary>
 51    /// List of PowerShell module paths to be loaded.
 52    /// </summary>
 32353    private readonly List<string> _modulePaths = [];
 54
 55    /// <summary>
 56    /// Indicates whether the Kestrun host is stopping.
 57    /// </summary>
 58    private int _stopping; // 0 = running, 1 = stopping
 59
 60    /// <summary>
 61    /// Indicates whether the Kestrun host configuration has been applied.
 62    /// </summary>
 25063    public bool IsConfigured { get; private set; }
 64
 65    /// <summary>
 66    /// Gets the timestamp when the Kestrun host was started.
 67    /// </summary>
 1768    public DateTime? StartTime { get; private set; }
 69
 70    /// <summary>
 71    /// Gets the timestamp when the Kestrun host was stopped.
 72    /// </summary>
 1873    public DateTime? StopTime { get; private set; }
 74
 75    /// <summary>
 76    /// Gets the uptime duration of the Kestrun host.
 77    /// While running (no StopTime yet), this returns DateTime.UtcNow - StartTime.
 78    /// After stopping, it returns StopTime - StartTime.
 79    /// If StartTime is not set, returns null.
 80    /// </summary>
 81    public TimeSpan? Uptime =>
 082        !StartTime.HasValue
 083            ? null
 084            : StopTime.HasValue
 085                ? StopTime - StartTime
 086                : DateTime.UtcNow - StartTime.Value;
 87    /// <summary>
 88    /// The runspace pool manager for PowerShell execution.
 89    /// </summary>
 90    private KestrunRunspacePoolManager? _runspacePool;
 91
 92    /// <summary>
 93    /// Status code options for configuring status code pages.
 94    /// </summary>
 95    private StatusCodeOptions? _statusCodeOptions;
 96    /// <summary>
 97    /// Exception options for configuring exception handling.
 98    /// </summary>
 99    private ExceptionOptions? _exceptionOptions;
 100    /// <summary>
 101    /// Forwarded headers options for configuring forwarded headers handling.
 102    /// </summary>
 103    private ForwardedHeadersOptions? _forwardedHeaderOptions;
 104
 4105    internal KestrunRunspacePoolManager RunspacePool => _runspacePool ?? throw new InvalidOperationException("Runspace p
 106
 107    // ── ✦ QUEUE #1 : SERVICE REGISTRATION ✦ ─────────────────────────────
 323108    private readonly List<Action<IServiceCollection>> _serviceQueue = [];
 109
 110    // ── ✦ QUEUE #2 : MIDDLEWARE STAGES ✦ ────────────────────────────────
 323111    private readonly List<Action<IApplicationBuilder>> _middlewareQueue = [];
 112
 406113    internal List<Action<KestrunHost>> FeatureQueue { get; } = [];
 114
 485115    internal List<IProbe> HealthProbes { get; } = [];
 116#if NET9_0_OR_GREATER
 323117    private readonly Lock _healthProbeLock = new();
 118#else
 119    private readonly object _healthProbeLock = new();
 120#endif
 121
 323122    internal readonly Dictionary<(string Pattern, string Method), MapRouteOptions> _registeredRoutes =
 323123    new(new RouteKeyComparer());
 124
 125    //internal readonly Dictionary<(string Scheme, string Type), AuthenticationSchemeOptions> _registeredAuthentications
 126    //  new(new AuthKeyComparer());
 127
 128    /// <summary>
 129    /// Gets the root directory path for the Kestrun application.
 130    /// </summary>
 134131    public string? KestrunRoot { get; private set; }
 132
 133    /// <summary>
 134    /// Gets the Serilog logger instance used by the Kestrun host.
 135    /// </summary>
 5957136    public Serilog.ILogger Logger { get; private set; }
 137
 138    /// <summary>
 139    /// Gets the scheduler service used for managing scheduled tasks in the Kestrun host.
 140    /// </summary>
 84141    public SchedulerService Scheduler { get; internal set; } = null!; // Initialized in ConfigureServices
 142
 143    /// <summary>
 144    /// Gets the ad-hoc task service used for running one-off tasks (PowerShell, C#, VB.NET).
 145    /// </summary>
 0146    public KestrunTaskService Tasks { get; internal set; } = null!; // Initialized via AddTasks()
 147
 148    /// <summary>
 149    /// Gets the stack used for managing route groups in the Kestrun host.
 150    /// </summary>
 323151    public System.Collections.Stack RouteGroupStack { get; } = new();
 152
 153
 154    /// <summary>
 155    /// Gets the registered routes in the Kestrun host.
 156    /// </summary>
 0157    public Dictionary<(string, string), MapRouteOptions> RegisteredRoutes => _registeredRoutes;
 158
 159    /// <summary>
 160    /// Gets the registered authentication schemes in the Kestrun host.
 161    /// </summary>
 343162    public AuthenticationRegistry RegisteredAuthentications { get; } = new();
 163
 164    /// <summary>
 165    /// Gets or sets the default cache control settings for HTTP responses.
 166    /// </summary>
 4167    public CacheControlHeaderValue? DefaultCacheControl { get; internal set; }
 168
 169    /// <summary>
 170    /// Gets the shared state manager for managing shared data across requests and sessions.
 171    /// </summary>
 100172    public bool PowershellMiddlewareEnabled { get; set; } = false;
 173
 174    /// <summary>
 175    /// Gets or sets a value indicating whether this instance is the default Kestrun host.
 176    /// </summary>
 1177    public bool DefaultHost { get; internal set; }
 178
 179    /// <summary>
 180    /// Gets or sets the status code options for configuring status code pages.
 181    /// </summary>
 182    public StatusCodeOptions? StatusCodeOptions
 183    {
 80184        get => _statusCodeOptions;
 185        set
 186        {
 0187            if (IsConfigured)
 188            {
 0189                throw new InvalidOperationException("Cannot modify StatusCodeOptions after configuration is applied.");
 190            }
 0191            _statusCodeOptions = value;
 0192        }
 193    }
 194
 195    /// <summary>
 196    /// Gets or sets the exception options for configuring exception handling.
 197    /// </summary>
 198    public ExceptionOptions? ExceptionOptions
 199    {
 91200        get => _exceptionOptions;
 201        set
 202        {
 5203            if (IsConfigured)
 204            {
 0205                throw new InvalidOperationException("Cannot modify ExceptionOptions after configuration is applied.");
 206            }
 5207            _exceptionOptions = value;
 5208        }
 209    }
 210
 211    /// <summary>
 212    /// Gets or sets the forwarded headers options for configuring forwarded headers handling.
 213    /// </summary>
 214    public ForwardedHeadersOptions? ForwardedHeaderOptions
 215    {
 83216        get => _forwardedHeaderOptions;
 217        set
 218        {
 4219            if (IsConfigured)
 220            {
 1221                throw new InvalidOperationException("Cannot modify ForwardedHeaderOptions after configuration is applied
 222            }
 3223            _forwardedHeaderOptions = value;
 3224        }
 225    }
 226
 227    #endregion
 228
 229    // Accepts optional module paths (from PowerShell)
 230    #region Constructor
 231
 232    /// <summary>
 233    /// Initializes a new instance of the <see cref="KestrunHost"/> class with the specified application name, root dire
 234    /// </summary>
 235    /// <param name="appName">The name of the application.</param>
 236    /// <param name="kestrunRoot">The root directory for the Kestrun application.</param>
 237    /// <param name="modulePathsObj">An array of module paths to be loaded.</param>
 238    public KestrunHost(string? appName, string? kestrunRoot = null, string[]? modulePathsObj = null) :
 96239            this(appName, Log.Logger, kestrunRoot, modulePathsObj)
 96240    { }
 241
 242    /// <summary>
 243    /// Initializes a new instance of the <see cref="KestrunHost"/> class with the specified application name, logger, r
 244    /// </summary>
 245    /// <param name="appName">The name of the application.</param>
 246    /// <param name="logger">The Serilog logger instance to use.</param>
 247    /// <param name="kestrunRoot">The root directory for the Kestrun application.</param>
 248    /// <param name="modulePathsObj">An array of module paths to be loaded.</param>
 249    /// <param name="args">Command line arguments to pass to the application.</param>
 323250    public KestrunHost(string? appName, Serilog.ILogger logger,
 323251    string? kestrunRoot = null, string[]? modulePathsObj = null, string[]? args = null)
 252    {
 253        // ① Logger
 323254        Logger = logger ?? Log.Logger;
 323255        LogConstructorArgs(appName, logger == null, kestrunRoot, modulePathsObj?.Length ?? 0);
 256
 257        // ② Working directory/root
 323258        SetWorkingDirectoryIfNeeded(kestrunRoot);
 259
 260        // ③ Ensure Kestrun module path is available
 323261        AddKestrunModulePathIfMissing(modulePathsObj);
 262
 263        // ④ WebApplicationBuilder
 323264        Builder = WebApplication.CreateBuilder(new WebApplicationOptions()
 323265        {
 323266            ContentRootPath = string.IsNullOrWhiteSpace(kestrunRoot) ? Directory.GetCurrentDirectory() : kestrunRoot,
 323267            Args = args ?? [],
 323268            EnvironmentName = EnvironmentHelper.Name
 323269        });
 270        // Enable Serilog for the host
 323271        _ = Builder.Host.UseSerilog();
 272
 273        // Make this KestrunHost available via DI so framework-created components (e.g., auth handlers)
 274        // can resolve it. We register the current instance as a singleton.
 323275        _ = Builder.Services.AddSingleton(this);
 276
 277        // Expose Serilog.ILogger via DI for components (e.g., SignalR hubs) that depend on Serilog's logger
 278        // ASP.NET Core registers Microsoft.Extensions.Logging.ILogger by default; we also bind Serilog.ILogger
 279        // to the same instance so constructors like `KestrunHub(Serilog.ILogger logger)` resolve properly.
 323280        _ = Builder.Services.AddSingleton(Logger);
 281
 282        // ⑤ Options
 323283        InitializeOptions(appName);
 284
 285        // ⑥ Add user-provided module paths
 323286        AddUserModulePaths(modulePathsObj);
 287
 323288        Logger.Information("Current working directory: {CurrentDirectory}", Directory.GetCurrentDirectory());
 323289    }
 290    #endregion
 291
 292    #region Helpers
 293
 294
 295    /// <summary>
 296    /// Logs constructor arguments at Debug level for diagnostics.
 297    /// </summary>
 298    private void LogConstructorArgs(string? appName, bool defaultLogger, string? kestrunRoot, int modulePathsLength)
 299    {
 323300        if (Logger.IsEnabled(LogEventLevel.Debug))
 301        {
 202302            Logger.Debug(
 202303                "KestrunHost ctor: AppName={AppName}, DefaultLogger={DefaultLogger}, KestrunRoot={KestrunRoot}, ModulePa
 202304                appName, defaultLogger, kestrunRoot, modulePathsLength);
 305        }
 323306    }
 307
 308    /// <summary>
 309    /// Sets the current working directory to the provided Kestrun root if needed and stores it.
 310    /// </summary>
 311    /// <param name="kestrunRoot">The Kestrun root directory path.</param>
 312    private void SetWorkingDirectoryIfNeeded(string? kestrunRoot)
 313    {
 323314        if (string.IsNullOrWhiteSpace(kestrunRoot))
 315        {
 190316            return;
 317        }
 318
 133319        if (!string.Equals(Directory.GetCurrentDirectory(), kestrunRoot, StringComparison.Ordinal))
 320        {
 96321            Directory.SetCurrentDirectory(kestrunRoot);
 96322            Logger.Information("Changed current directory to Kestrun root: {KestrunRoot}", kestrunRoot);
 323        }
 324        else
 325        {
 37326            Logger.Verbose("Current directory is already set to Kestrun root: {KestrunRoot}", kestrunRoot);
 327        }
 328
 133329        KestrunRoot = kestrunRoot;
 133330    }
 331
 332    /// <summary>
 333    /// Ensures the core Kestrun module path is present; if missing, locates and adds it.
 334    /// </summary>
 335    /// <param name="modulePathsObj">The array of module paths to check.</param>
 336    private void AddKestrunModulePathIfMissing(string[]? modulePathsObj)
 337    {
 323338        var needsLocate = modulePathsObj is null ||
 360339                          (modulePathsObj?.Any(p => p.Contains("Kestrun.psm1", StringComparison.Ordinal)) == false);
 323340        if (!needsLocate)
 341        {
 37342            return;
 343        }
 344
 286345        var kestrunModulePath = PowerShellModuleLocator.LocateKestrunModule();
 286346        if (string.IsNullOrWhiteSpace(kestrunModulePath))
 347        {
 0348            Logger.Fatal("Kestrun module not found. Ensure the Kestrun module is installed.");
 0349            throw new FileNotFoundException("Kestrun module not found.");
 350        }
 351
 286352        Logger.Information("Found Kestrun module at: {KestrunModulePath}", kestrunModulePath);
 286353        Logger.Verbose("Adding Kestrun module path: {KestrunModulePath}", kestrunModulePath);
 286354        _modulePaths.Add(kestrunModulePath);
 286355    }
 356
 357    /// <summary>
 358    /// Initializes Kestrun options and sets the application name when provided.
 359    /// </summary>
 360    /// <param name="appName">The name of the application.</param>
 361    private void InitializeOptions(string? appName)
 362    {
 323363        if (string.IsNullOrEmpty(appName))
 364        {
 1365            Logger.Information("No application name provided, using default.");
 1366            Options = new KestrunOptions();
 367        }
 368        else
 369        {
 322370            Logger.Information("Setting application name: {AppName}", appName);
 322371            Options = new KestrunOptions { ApplicationName = appName };
 372        }
 322373    }
 374
 375    /// <summary>
 376    /// Adds user-provided module paths if they exist, logging warnings for invalid entries.
 377    /// </summary>
 378    /// <param name="modulePathsObj">The array of module paths to check.</param>
 379    private void AddUserModulePaths(string[]? modulePathsObj)
 380    {
 323381        if (modulePathsObj is IEnumerable<object> modulePathsEnum)
 382        {
 148383            foreach (var modPathObj in modulePathsEnum)
 384            {
 37385                if (modPathObj is string modPath && !string.IsNullOrWhiteSpace(modPath))
 386                {
 37387                    if (File.Exists(modPath))
 388                    {
 37389                        Logger.Information("[KestrunHost] Adding module path: {ModPath}", modPath);
 37390                        _modulePaths.Add(modPath);
 391                    }
 392                    else
 393                    {
 0394                        Logger.Warning("[KestrunHost] Module path does not exist: {ModPath}", modPath);
 395                    }
 396                }
 397                else
 398                {
 0399                    Logger.Warning("[KestrunHost] Invalid module path provided.");
 400                }
 401            }
 402        }
 323403    }
 404    #endregion
 405
 406
 407    #region Health Probes
 408
 409    /// <summary>
 410    /// Registers the provided <see cref="IProbe"/> instance with the host.
 411    /// </summary>
 412    /// <param name="probe">The probe to register.</param>
 413    /// <returns>The current <see cref="KestrunHost"/> instance.</returns>
 414    public KestrunHost AddProbe(IProbe probe)
 415    {
 0416        ArgumentNullException.ThrowIfNull(probe);
 0417        RegisterProbeInternal(probe);
 0418        return this;
 419    }
 420
 421    /// <summary>
 422    /// Registers a delegate-based probe.
 423    /// </summary>
 424    /// <param name="name">Probe name.</param>
 425    /// <param name="tags">Optional tag list used for filtering.</param>
 426    /// <param name="callback">Delegate executed when the probe runs.</param>
 427    /// <returns>The current <see cref="KestrunHost"/> instance.</returns>
 428    public KestrunHost AddProbe(string name, string[]? tags, Func<CancellationToken, Task<ProbeResult>> callback)
 429    {
 0430        ArgumentException.ThrowIfNullOrEmpty(name);
 0431        ArgumentNullException.ThrowIfNull(callback);
 432
 0433        var probe = new DelegateProbe(name, tags, callback);
 0434        RegisterProbeInternal(probe);
 0435        return this;
 436    }
 437
 438    /// <summary>
 439    /// Registers a script-based probe written in any supported language.
 440    /// </summary>
 441    /// <param name="name">Probe name.</param>
 442    /// <param name="tags">Optional tag list used for filtering.</param>
 443    /// <param name="code">Script contents.</param>
 444    /// <param name="language">Optional language override. When null, <see cref="KestrunOptions.Health"/> defaults are u
 445    /// <param name="arguments">Optional argument dictionary exposed to the script.</param>
 446    /// <param name="extraImports">Optional language-specific imports.</param>
 447    /// <param name="extraRefs">Optional additional assembly references.</param>
 448    /// <returns>The current <see cref="KestrunHost"/> instance.</returns>
 449    public KestrunHost AddProbe(
 450        string name,
 451        string[]? tags,
 452        string code,
 453        ScriptLanguage? language = null,
 454        IReadOnlyDictionary<string, object?>? arguments = null,
 455        string[]? extraImports = null,
 456        Assembly[]? extraRefs = null)
 457    {
 0458        ArgumentException.ThrowIfNullOrEmpty(name);
 0459        ArgumentException.ThrowIfNullOrEmpty(code);
 460
 0461        var effectiveLanguage = language ?? Options.Health.DefaultScriptLanguage;
 0462        var logger = Logger.ForContext("HealthProbe", name);
 0463        var probe = ScriptProbeFactory.Create(
 0464            name,
 0465            tags,
 0466            effectiveLanguage,
 0467            code,
 0468            logger,
 0469            effectiveLanguage == ScriptLanguage.PowerShell ? () => RunspacePool : null,
 0470            arguments,
 0471            extraImports,
 0472            extraRefs);
 473
 0474        RegisterProbeInternal(probe);
 0475        return this;
 476    }
 477
 478    /// <summary>
 479    /// Returns a snapshot of the currently registered probes.
 480    /// </summary>
 481    internal IReadOnlyList<IProbe> GetHealthProbesSnapshot()
 0482    {
 483        lock (_healthProbeLock)
 484        {
 0485            return [.. HealthProbes];
 486        }
 0487    }
 488
 489    private void RegisterProbeInternal(IProbe probe)
 54490    {
 491        lock (_healthProbeLock)
 492        {
 54493            var index = HealthProbes.FindIndex(p => string.Equals(p.Name, probe.Name, StringComparison.OrdinalIgnoreCase
 54494            if (index >= 0)
 495            {
 0496                HealthProbes[index] = probe;
 0497                Logger.Information("Replaced health probe {ProbeName}.", probe.Name);
 498            }
 499            else
 500            {
 54501                HealthProbes.Add(probe);
 54502                Logger.Information("Registered health probe {ProbeName}.", probe.Name);
 503            }
 54504        }
 54505    }
 506
 507    #endregion
 508
 509
 510    #region ListenerOptions
 511
 512    /// <summary>
 513    /// Configures a listener for the Kestrun host with the specified port, optional IP address, certificate, protocols,
 514    /// </summary>
 515    /// <param name="port">The port number to listen on.</param>
 516    /// <param name="ipAddress">The IP address to bind to. If null, binds to any address.</param>
 517    /// <param name="x509Certificate">The X509 certificate for HTTPS. If null, HTTPS is not used.</param>
 518    /// <param name="protocols">The HTTP protocols to use.</param>
 519    /// <param name="useConnectionLogging">Specifies whether to enable connection logging.</param>
 520    /// <returns>The current KestrunHost instance.</returns>
 521    public KestrunHost ConfigureListener(
 522    int port,
 523    IPAddress? ipAddress = null,
 524    X509Certificate2? x509Certificate = null,
 525    HttpProtocols protocols = HttpProtocols.Http1,
 526    bool useConnectionLogging = false)
 527    {
 37528        if (Logger.IsEnabled(LogEventLevel.Debug))
 529        {
 18530            Logger.Debug("ConfigureListener port={Port}, ipAddress={IPAddress}, protocols={Protocols}, useConnectionLogg
 531        }
 532
 37533        if (protocols == HttpProtocols.Http1AndHttp2AndHttp3 && !CcUtilities.PreviewFeaturesEnabled())
 534        {
 2535            Logger.Warning("Http3 is not supported in this version of Kestrun. Using Http1 and Http2 only.");
 2536            protocols = HttpProtocols.Http1AndHttp2;
 537        }
 538
 37539        Options.Listeners.Add(new ListenerOptions
 37540        {
 37541            IPAddress = ipAddress ?? IPAddress.Any,
 37542            Port = port,
 37543            UseHttps = x509Certificate != null,
 37544            X509Certificate = x509Certificate,
 37545            Protocols = protocols,
 37546            UseConnectionLogging = useConnectionLogging
 37547        });
 37548        return this;
 549    }
 550
 551    /// <summary>
 552    /// Configures a listener for the Kestrun host with the specified port, optional IP address, and connection logging.
 553    /// </summary>
 554    /// <param name="port">The port number to listen on.</param>
 555    /// <param name="ipAddress">The IP address to bind to. If null, binds to any address.</param>
 556    /// <param name="useConnectionLogging">Specifies whether to enable connection logging.</param>
 557    public void ConfigureListener(
 558    int port,
 559    IPAddress? ipAddress = null,
 20560    bool useConnectionLogging = false) => _ = ConfigureListener(port: port, ipAddress: ipAddress, x509Certificate: null,
 561
 562    /// <summary>
 563    /// Configures a listener for the Kestrun host with the specified port and connection logging option.
 564    /// </summary>
 565    /// <param name="port">The port number to listen on.</param>
 566    /// <param name="useConnectionLogging">Specifies whether to enable connection logging.</param>
 567    public void ConfigureListener(
 568    int port,
 1569    bool useConnectionLogging = false) => _ = ConfigureListener(port: port, ipAddress: null, x509Certificate: null, prot
 570
 571
 572    /// <summary>
 573    /// Configures listeners for the Kestrun host by resolving the specified host name to IP addresses and binding to ea
 574    /// </summary>
 575    /// <param name="hostName">The host name to resolve and bind to.</param>
 576    /// <param name="port">The port number to listen on.</param>
 577    /// <param name="x509Certificate">The X509 certificate for HTTPS. If null, HTTPS is not used.</param>
 578    /// <param name="protocols">The HTTP protocols to use.</param>
 579    /// <param name="useConnectionLogging">Specifies whether to enable connection logging.</param>
 580    /// <param name="families">Optional array of address families to filter resolved addresses (e.g., IPv4-only).</param
 581    /// <returns>The current KestrunHost instance.</returns>
 582    /// <exception cref="ArgumentException">Thrown when the host name is null or whitespace.</exception>
 583    /// <exception cref="InvalidOperationException">Thrown when no valid IP addresses are resolved.</exception>
 584    public KestrunHost ConfigureListener(
 585    string hostName,
 586    int port,
 587    X509Certificate2? x509Certificate = null,
 588    HttpProtocols protocols = HttpProtocols.Http1,
 589    bool useConnectionLogging = false,
 590    AddressFamily[]? families = null) // e.g. new[] { AddressFamily.InterNetwork } for IPv4-only
 591    {
 0592        if (string.IsNullOrWhiteSpace(hostName))
 593        {
 0594            throw new ArgumentException("Host name must be provided.", nameof(hostName));
 595        }
 596
 597        // If caller passed an IP literal, just bind once.
 0598        if (IPAddress.TryParse(hostName, out var parsedIp))
 599        {
 0600            _ = ConfigureListener(port, parsedIp, x509Certificate, protocols, useConnectionLogging);
 0601            return this;
 602        }
 603
 604        // Resolve and bind to ALL matching addresses (IPv4/IPv6)
 0605        var addrs = Dns.GetHostAddresses(hostName)
 0606                       .Where(a => families is null || families.Length == 0 || families.Contains(a.AddressFamily))
 0607                       .Where(a => a.AddressFamily is AddressFamily.InterNetwork or AddressFamily.InterNetworkV6)
 0608                       .ToArray();
 609
 0610        if (addrs.Length == 0)
 611        {
 0612            throw new InvalidOperationException($"No IPv4/IPv6 addresses resolved for host '{hostName}'.");
 613        }
 614
 0615        foreach (var addr in addrs)
 616        {
 0617            _ = ConfigureListener(port, addr, x509Certificate, protocols, useConnectionLogging);
 618        }
 619
 0620        return this;
 621    }
 622
 623    /// <summary>
 624    /// Configures listeners for the Kestrun host based on the provided absolute URI, resolving the host to IP addresses
 625    /// </summary>
 626    /// <param name="uri">The absolute URI to configure the listener for.</param>
 627    /// <param name="x509Certificate">The X509 certificate for HTTPS. If null, HTTPS is not used.</param>
 628    /// <param name="protocols">The HTTP protocols to use.</param>
 629    /// <param name="useConnectionLogging">Specifies whether to enable connection logging.</param>
 630    /// <param name="families">Optional array of address families to filter resolved addresses (e.g., IPv4-only).</param
 631    /// <returns>The current KestrunHost instance.</returns>
 632    /// <exception cref="ArgumentException">Thrown when the provided URI is not absolute.</exception>
 633    /// <exception cref="InvalidOperationException">Thrown when no valid IP addresses are resolved.</exception>
 634    public KestrunHost ConfigureListener(
 635    Uri uri,
 636    X509Certificate2? x509Certificate = null,
 637    HttpProtocols? protocols = null,
 638    bool useConnectionLogging = false,
 639    AddressFamily[]? families = null)
 640    {
 0641        ArgumentNullException.ThrowIfNull(uri);
 642
 0643        if (!uri.IsAbsoluteUri)
 644        {
 0645            throw new ArgumentException("URL must be absolute.", nameof(uri));
 646        }
 647
 0648        var isHttps = uri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
 0649        var port = uri.IsDefaultPort ? (isHttps ? 443 : 80) : uri.Port;
 650
 651        // Default: HTTPS → H1+H2, HTTP → H1
 0652        var chosenProtocols = protocols ?? (isHttps ? HttpProtocols.Http1AndHttp2 : HttpProtocols.Http1);
 653
 654        // Delegate to hostname overload (which will resolve or handle IP literal)
 0655        return ConfigureListener(
 0656            hostName: uri.Host,
 0657            port: port,
 0658            x509Certificate: x509Certificate,
 0659            protocols: chosenProtocols,
 0660            useConnectionLogging: useConnectionLogging,
 0661            families: families
 0662        );
 663    }
 664
 665    #endregion
 666
 667    #region Configuration
 668
 669
 670    /// <summary>
 671    /// Validates if configuration can be applied and returns early if already configured.
 672    /// </summary>
 673    /// <returns>True if configuration should proceed, false if it should be skipped.</returns>
 674    internal bool ValidateConfiguration()
 675    {
 74676        if (Logger.IsEnabled(LogEventLevel.Debug))
 677        {
 41678            Logger.Debug("EnableConfiguration(options) called");
 679        }
 680
 74681        if (IsConfigured)
 682        {
 18683            if (Logger.IsEnabled(LogEventLevel.Debug))
 684            {
 2685                Logger.Debug("Configuration already applied, skipping");
 686            }
 18687            return false; // Already configured
 688        }
 689
 56690        return true;
 691    }
 692
 693    /// <summary>
 694    /// Creates and initializes the runspace pool for PowerShell execution.
 695    /// </summary>
 696    /// <param name="userVariables">User-defined variables to inject into the runspace pool.</param>
 697    /// <param name="userFunctions">User-defined functions to inject into the runspace pool.</param>
 698    /// <exception cref="InvalidOperationException">Thrown when runspace pool creation fails.</exception>
 699    internal void InitializeRunspacePool(Dictionary<string, object>? userVariables, Dictionary<string, string>? userFunc
 700    {
 57701        _runspacePool = CreateRunspacePool(Options.MaxRunspaces, userVariables, userFunctions);
 57702        if (_runspacePool == null)
 703        {
 0704            throw new InvalidOperationException("Failed to create runspace pool.");
 705        }
 706
 57707        if (Logger.IsEnabled(LogEventLevel.Verbose))
 708        {
 0709            Logger.Verbose("Runspace pool created with max runspaces: {MaxRunspaces}", Options.MaxRunspaces);
 710        }
 57711    }
 712
 713    /// <summary>
 714    /// Configures the Kestrel web server with basic options.
 715    /// </summary>
 716    internal void ConfigureKestrelBase()
 717    {
 55718        _ = Builder.WebHost.UseKestrel(opts =>
 55719        {
 54720            opts.CopyFromTemplate(Options.ServerOptions);
 109721        });
 55722    }
 723
 724    /// <summary>
 725    /// Configures named pipe listeners if supported on the current platform.
 726    /// </summary>
 727    internal void ConfigureNamedPipes()
 728    {
 56729        if (Options.NamedPipeOptions is not null)
 730        {
 1731            if (OperatingSystem.IsWindows())
 732            {
 0733                _ = Builder.WebHost.UseNamedPipes(opts =>
 0734                {
 0735                    opts.ListenerQueueCount = Options.NamedPipeOptions.ListenerQueueCount;
 0736                    opts.MaxReadBufferSize = Options.NamedPipeOptions.MaxReadBufferSize;
 0737                    opts.MaxWriteBufferSize = Options.NamedPipeOptions.MaxWriteBufferSize;
 0738                    opts.CurrentUserOnly = Options.NamedPipeOptions.CurrentUserOnly;
 0739                    opts.PipeSecurity = Options.NamedPipeOptions.PipeSecurity;
 0740                });
 741            }
 742            else
 743            {
 1744                Logger.Verbose("Named pipe listeners configuration is supported only on Windows; skipping UseNamedPipes 
 745            }
 746        }
 56747    }
 748
 749    /// <summary>
 750    /// Configures HTTPS connection adapter defaults.
 751    /// </summary>
 752    /// <param name="serverOptions">The Kestrel server options to configure.</param>
 753    internal void ConfigureHttpsAdapter(KestrelServerOptions serverOptions)
 754    {
 55755        if (Options.HttpsConnectionAdapter is not null)
 756        {
 0757            Logger.Verbose("Applying HTTPS connection adapter options from KestrunOptions.");
 758
 759            // Apply HTTPS defaults if needed
 0760            serverOptions.ConfigureHttpsDefaults(httpsOptions =>
 0761            {
 0762                httpsOptions.SslProtocols = Options.HttpsConnectionAdapter.SslProtocols;
 0763                httpsOptions.ClientCertificateMode = Options.HttpsConnectionAdapter.ClientCertificateMode;
 0764                httpsOptions.ClientCertificateValidation = Options.HttpsConnectionAdapter.ClientCertificateValidation;
 0765                httpsOptions.CheckCertificateRevocation = Options.HttpsConnectionAdapter.CheckCertificateRevocation;
 0766                httpsOptions.ServerCertificate = Options.HttpsConnectionAdapter.ServerCertificate;
 0767                httpsOptions.ServerCertificateChain = Options.HttpsConnectionAdapter.ServerCertificateChain;
 0768                httpsOptions.ServerCertificateSelector = Options.HttpsConnectionAdapter.ServerCertificateSelector;
 0769                httpsOptions.HandshakeTimeout = Options.HttpsConnectionAdapter.HandshakeTimeout;
 0770                httpsOptions.OnAuthenticate = Options.HttpsConnectionAdapter.OnAuthenticate;
 0771            });
 772        }
 55773    }
 774
 775    /// <summary>
 776    /// Binds all configured listeners (Unix sockets, named pipes, TCP) to the server.
 777    /// </summary>
 778    /// <param name="serverOptions">The Kestrel server options to configure.</param>
 779    internal void BindListeners(KestrelServerOptions serverOptions)
 780    {
 781        // Unix domain socket listeners
 112782        foreach (var unixSocket in Options.ListenUnixSockets)
 783        {
 0784            if (!string.IsNullOrWhiteSpace(unixSocket))
 785            {
 0786                Logger.Verbose("Binding Unix socket: {Sock}", unixSocket);
 0787                serverOptions.ListenUnixSocket(unixSocket);
 788                // NOTE: control access via directory perms/umask; UDS file perms are inherited from process umask
 789                // Prefer placing the socket under a group-owned dir (e.g., /var/run/kestrun) with 0770.
 790            }
 791        }
 792
 793        // Named pipe listeners
 112794        foreach (var namedPipeName in Options.NamedPipeNames)
 795        {
 0796            if (!string.IsNullOrWhiteSpace(namedPipeName))
 797            {
 0798                Logger.Verbose("Binding Named Pipe: {Pipe}", namedPipeName);
 0799                serverOptions.ListenNamedPipe(namedPipeName);
 800            }
 801        }
 802
 803        // TCP listeners
 172804        foreach (var opt in Options.Listeners)
 805        {
 30806            serverOptions.Listen(opt.IPAddress, opt.Port, listenOptions =>
 30807            {
 30808                listenOptions.Protocols = opt.Protocols;
 30809                listenOptions.DisableAltSvcHeader = opt.DisableAltSvcHeader;
 30810                if (opt.UseHttps && opt.X509Certificate is not null)
 30811                {
 2812                    _ = listenOptions.UseHttps(opt.X509Certificate);
 30813                }
 30814                if (opt.UseConnectionLogging)
 30815                {
 0816                    _ = listenOptions.UseConnectionLogging();
 30817                }
 60818            });
 819        }
 56820    }
 821
 822    /// <summary>
 823    /// Logs the configured endpoints after building the application.
 824    /// </summary>
 825    internal void LogConfiguredEndpoints()
 826    {
 827        // build the app to validate configuration
 55828        _app = Build();
 829        // Log configured endpoints
 55830        var dataSource = _app.Services.GetRequiredService<EndpointDataSource>();
 831
 55832        if (dataSource.Endpoints.Count == 0)
 833        {
 55834            Logger.Warning("EndpointDataSource is empty. No endpoints configured.");
 835        }
 836        else
 837        {
 0838            foreach (var ep in dataSource.Endpoints)
 839            {
 0840                Logger.Information("➡️  Endpoint: {DisplayName}", ep.DisplayName);
 841            }
 842        }
 0843    }
 844
 845    /// <summary>
 846    /// Handles configuration errors and wraps them with meaningful messages.
 847    /// </summary>
 848    /// <param name="ex">The exception that occurred during configuration.</param>
 849    /// <exception cref="InvalidOperationException">Always thrown with wrapped exception.</exception>
 850    internal void HandleConfigurationError(Exception ex)
 851    {
 1852        Logger.Error(ex, "Error applying configuration: {Message}", ex.Message);
 1853        throw new InvalidOperationException("Failed to apply configuration.", ex);
 854    }
 855
 856    /// <summary>
 857    /// Applies the configured options to the Kestrel server and initializes the runspace pool.
 858    /// </summary>
 859    public void EnableConfiguration(Dictionary<string, object>? userVariables = null, Dictionary<string, string>? userFu
 860    {
 71861        if (!ValidateConfiguration())
 862        {
 17863            return;
 864        }
 865
 866        try
 867        {
 54868            InitializeRunspacePool(userVariables, userFunctions);
 54869            ConfigureKestrelBase();
 54870            ConfigureNamedPipes();
 871
 872            // Apply Kestrel listeners and HTTPS settings
 54873            _ = Builder.WebHost.ConfigureKestrel(serverOptions =>
 54874            {
 54875                ConfigureHttpsAdapter(serverOptions);
 54876                BindListeners(serverOptions);
 108877            });
 878
 54879            LogConfiguredEndpoints();
 880
 881            // Register default probes after endpoints are logged but before marking configured
 54882            RegisterDefaultHealthProbes();
 54883            IsConfigured = true;
 54884            Logger.Information("Configuration applied successfully.");
 54885        }
 0886        catch (Exception ex)
 887        {
 0888            HandleConfigurationError(ex);
 0889        }
 54890    }
 891
 892    /// <summary>
 893    /// Registers built-in default health probes (idempotent). Currently includes disk space probe.
 894    /// </summary>
 895    private void RegisterDefaultHealthProbes()
 896    {
 897        try
 54898        {
 899            // Avoid duplicate registration if user already added a probe named "disk".
 900            lock (_healthProbeLock)
 901            {
 54902                if (HealthProbes.Any(p => string.Equals(p.Name, "disk", StringComparison.OrdinalIgnoreCase)))
 903                {
 0904                    return; // already present
 905                }
 54906            }
 907
 54908            var tags = new[] { IProbe.TAG_SELF }; // neutral tag; user can filter by name if needed
 54909            var diskProbe = new DiskSpaceProbe("disk", tags);
 54910            RegisterProbeInternal(diskProbe);
 54911        }
 0912        catch (Exception ex)
 913        {
 0914            Logger.Warning(ex, "Failed to register default disk space probe.");
 0915        }
 54916    }
 917
 918    #endregion
 919    #region Builder
 920    /* More information about the KestrunHost class
 921    https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.webapplication?view=aspnetcore-8.0
 922
 923    */
 924
 925    /// <summary>
 926    /// Builds the WebApplication.
 927    /// This method applies all queued services and middleware stages,
 928    /// and returns the built WebApplication instance.
 929    /// </summary>
 930    /// <returns>The built WebApplication.</returns>
 931    /// <exception cref="InvalidOperationException"></exception>
 932    public WebApplication Build()
 933    {
 80934        ValidateBuilderState();
 80935        ApplyQueuedServices();
 80936        BuildWebApplication();
 80937        ConfigureBuiltInMiddleware();
 80938        LogApplicationInfo();
 80939        ApplyQueuedMiddleware();
 80940        ApplyFeatures();
 941
 80942        return _app!;
 943    }
 944
 945    /// <summary>
 946    /// Validates that the builder is properly initialized before building.
 947    /// </summary>
 948    /// <exception cref="InvalidOperationException">Thrown when the builder is not initialized.</exception>
 949    private void ValidateBuilderState()
 950    {
 80951        if (Builder == null)
 952        {
 0953            throw new InvalidOperationException("Call CreateBuilder() first.");
 954        }
 80955    }
 956
 957    /// <summary>
 958    /// Applies all queued service configurations to the service collection.
 959    /// </summary>
 960    private void ApplyQueuedServices()
 961    {
 250962        foreach (var configure in _serviceQueue)
 963        {
 45964            configure(Builder.Services);
 965        }
 80966    }
 967
 968    /// <summary>
 969    /// Builds the WebApplication instance from the configured builder.
 970    /// </summary>
 971    private void BuildWebApplication()
 972    {
 80973        _app = Builder.Build();
 80974        Logger.Information("Application built successfully.");
 80975    }
 976
 977    /// <summary>
 978    /// Configures built-in middleware components in the correct order.
 979    /// </summary>
 980    private void ConfigureBuiltInMiddleware()
 981    {
 80982        ConfigureExceptionHandling();
 80983        ConfigureForwardedHeaders();
 80984        ConfigureStatusCodePages();
 80985        ConfigurePowerShellRuntime();
 80986    }
 987
 988    /// <summary>
 989    /// Configures exception handling middleware if enabled.
 990    /// </summary>
 991    private void ConfigureExceptionHandling()
 992    {
 80993        if (ExceptionOptions is not null)
 994        {
 5995            if (Logger.IsEnabled(LogEventLevel.Debug))
 996            {
 0997                Logger.Debug("Exception handling middleware is enabled.");
 998            }
 5999            _ = ExceptionOptions.DeveloperExceptionPageOptions is not null
 51000                ? _app!.UseDeveloperExceptionPage(ExceptionOptions.DeveloperExceptionPageOptions)
 51001                : _app!.UseExceptionHandler(ExceptionOptions);
 1002        }
 761003    }
 1004
 1005    /// <summary>
 1006    /// Configures forwarded headers middleware if enabled.
 1007    /// </summary>
 1008    private void ConfigureForwardedHeaders()
 1009    {
 801010        if (ForwardedHeaderOptions is not null)
 1011        {
 31012            if (Logger.IsEnabled(LogEventLevel.Debug))
 1013            {
 01014                Logger.Debug("Forwarded headers middleware is enabled.");
 1015            }
 31016            _ = _app!.UseForwardedHeaders(ForwardedHeaderOptions);
 1017        }
 801018    }
 1019
 1020    /// <summary>
 1021    /// Configures status code pages middleware if enabled.
 1022    /// </summary>
 1023    private void ConfigureStatusCodePages()
 1024    {
 1025        // Register StatusCodePages BEFORE language runtimes so that re-executed requests
 1026        // pass through language middleware again (and get fresh RouteValues/context).
 801027        if (StatusCodeOptions is not null)
 1028        {
 01029            if (Logger.IsEnabled(LogEventLevel.Debug))
 1030            {
 01031                Logger.Debug("Status code pages middleware is enabled.");
 1032            }
 01033            _ = _app!.UseStatusCodePages(StatusCodeOptions);
 1034        }
 801035    }
 1036
 1037    /// <summary>
 1038    /// Configures PowerShell runtime middleware if enabled.
 1039    /// </summary>
 1040    /// <exception cref="InvalidOperationException">Thrown when PowerShell is enabled but runspace pool is not initializ
 1041    private void ConfigurePowerShellRuntime()
 1042    {
 801043        if (PowershellMiddlewareEnabled)
 1044        {
 01045            if (Logger.IsEnabled(LogEventLevel.Debug))
 1046            {
 01047                Logger.Debug("PowerShell middleware is enabled.");
 1048            }
 1049
 01050            if (_runspacePool is null)
 1051            {
 01052                throw new InvalidOperationException("Runspace pool is not initialized. Call EnableConfiguration first.")
 1053            }
 01054            Logger.Information("Adding PowerShell runtime");
 01055            _ = _app!.UseLanguageRuntime(
 01056                    ScriptLanguage.PowerShell,
 01057                    b => b.UsePowerShellRunspace(_runspacePool));
 1058        }
 801059    }
 1060
 1061    /// <summary>
 1062    /// Logs application information including working directory and Pages directory contents.
 1063    /// </summary>
 1064    private void LogApplicationInfo()
 1065    {
 801066        Logger.Information("CWD: {CWD}", Directory.GetCurrentDirectory());
 801067        Logger.Information("ContentRoot: {Root}", _app!.Environment.ContentRootPath);
 801068        LogPagesDirectory();
 801069    }
 1070
 1071    /// <summary>
 1072    /// Logs information about the Pages directory and its contents.
 1073    /// </summary>
 1074    private void LogPagesDirectory()
 1075    {
 801076        var pagesDir = Path.Combine(_app!.Environment.ContentRootPath, "Pages");
 801077        Logger.Information("Pages Dir: {PagesDir}", pagesDir);
 1078
 801079        if (Directory.Exists(pagesDir))
 1080        {
 01081            foreach (var file in Directory.GetFiles(pagesDir, "*.*", SearchOption.AllDirectories))
 1082            {
 01083                Logger.Information("Pages file: {File}", file);
 1084            }
 1085        }
 1086        else
 1087        {
 801088            Logger.Warning("Pages directory does not exist: {PagesDir}", pagesDir);
 1089        }
 801090    }
 1091
 1092    /// <summary>
 1093    /// Applies all queued middleware stages to the application pipeline.
 1094    /// </summary>
 1095    private void ApplyQueuedMiddleware()
 1096    {
 2401097        foreach (var stage in _middlewareQueue)
 1098        {
 401099            stage(_app!);
 1100        }
 801101    }
 1102
 1103    /// <summary>
 1104    /// Applies all queued features to the host.
 1105    /// </summary>
 1106    private void ApplyFeatures()
 1107    {
 1641108        foreach (var feature in FeatureQueue)
 1109        {
 21110            feature(this);
 1111        }
 801112    }
 1113
 1114    /// <summary>
 1115    /// Returns true if the specified service type has already been registered in the IServiceCollection.
 1116    /// </summary>
 1117    public bool IsServiceRegistered(Type serviceType)
 7771118        => Builder?.Services?.Any(sd => sd.ServiceType == serviceType) ?? false;
 1119
 1120    /// <summary>
 1121    /// Generic convenience overload.
 1122    /// </summary>
 01123    public bool IsServiceRegistered<TService>() => IsServiceRegistered(typeof(TService));
 1124
 1125    /// <summary>
 1126    /// Adds a service configuration action to the service queue.
 1127    /// This action will be executed when the services are built.
 1128    /// </summary>
 1129    /// <param name="configure">The service configuration action.</param>
 1130    /// <returns>The current KestrunHost instance.</returns>
 1131    public KestrunHost AddService(Action<IServiceCollection> configure)
 1132    {
 811133        _serviceQueue.Add(configure);
 811134        return this;
 1135    }
 1136
 1137    /// <summary>
 1138    /// Adds a middleware stage to the application pipeline.
 1139    /// </summary>
 1140    /// <param name="stage">The middleware stage to add.</param>
 1141    /// <returns>The current KestrunHost instance.</returns>
 1142    public KestrunHost Use(Action<IApplicationBuilder> stage)
 1143    {
 871144        _middlewareQueue.Add(stage);
 871145        return this;
 1146    }
 1147
 1148    /// <summary>
 1149    /// Adds a feature configuration action to the feature queue.
 1150    /// This action will be executed when the features are applied.
 1151    /// </summary>
 1152    /// <param name="feature">The feature configuration action.</param>
 1153    /// <returns>The current KestrunHost instance.</returns>
 1154    public KestrunHost AddFeature(Action<KestrunHost> feature)
 1155    {
 21156        FeatureQueue.Add(feature);
 21157        return this;
 1158    }
 1159
 1160    /// <summary>
 1161    /// Adds a scheduling feature to the Kestrun host, optionally specifying the maximum number of runspaces for the sch
 1162    /// </summary>
 1163    /// <param name="MaxRunspaces">The maximum number of runspaces for the scheduler. If null, uses the default value.</
 1164    /// <returns>The current KestrunHost instance.</returns>
 1165    public KestrunHost AddScheduling(int? MaxRunspaces = null)
 1166    {
 41167        return MaxRunspaces is not null and <= 0
 41168            ? throw new ArgumentOutOfRangeException(nameof(MaxRunspaces), "MaxRunspaces must be greater than zero.")
 41169            : AddFeature(host =>
 41170        {
 21171            if (Logger.IsEnabled(LogEventLevel.Debug))
 41172            {
 21173                Logger.Debug("AddScheduling (deferred)");
 41174            }
 41175
 21176            if (host.Scheduler is null)
 41177            {
 11178                if (MaxRunspaces is not null and > 0)
 41179                {
 11180                    Logger.Information("Setting MaxSchedulerRunspaces to {MaxRunspaces}", MaxRunspaces);
 11181                    host.Options.MaxSchedulerRunspaces = MaxRunspaces.Value;
 41182                }
 11183                Logger.Verbose("Creating SchedulerService with MaxSchedulerRunspaces={MaxRunspaces}",
 11184                    host.Options.MaxSchedulerRunspaces);
 11185                var pool = host.CreateRunspacePool(host.Options.MaxSchedulerRunspaces);
 11186                var logger = Logger.ForContext<KestrunHost>();
 11187                host.Scheduler = new SchedulerService(pool, logger);
 41188            }
 41189            else
 41190            {
 11191                Logger.Warning("SchedulerService already configured; skipping.");
 41192            }
 51193        });
 1194    }
 1195
 1196    /// <summary>
 1197    /// Adds the Tasks feature to run ad-hoc scripts with status/result/cancellation.
 1198    /// </summary>
 1199    /// <param name="MaxRunspaces">Optional max runspaces for the task PowerShell pool; when null uses scheduler default
 1200    /// <returns>The current KestrunHost instance.</returns>
 1201    public KestrunHost AddTasks(int? MaxRunspaces = null)
 1202    {
 01203        return MaxRunspaces is not null and <= 0
 01204            ? throw new ArgumentOutOfRangeException(nameof(MaxRunspaces), "MaxRunspaces must be greater than zero.")
 01205            : AddFeature(host =>
 01206        {
 01207            if (Logger.IsEnabled(LogEventLevel.Debug))
 01208            {
 01209                Logger.Debug("AddTasks (deferred)");
 01210            }
 01211
 01212            if (host.Tasks is null)
 01213            {
 01214                // Reuse scheduler pool sizing unless explicitly overridden
 01215                if (MaxRunspaces is not null and > 0)
 01216                {
 01217                    Logger.Information("Setting MaxTaskRunspaces to {MaxRunspaces}", MaxRunspaces);
 01218                }
 01219                var pool = host.CreateRunspacePool(MaxRunspaces ?? host.Options.MaxSchedulerRunspaces);
 01220                var logger = Logger.ForContext<KestrunHost>();
 01221                host.Tasks = new KestrunTaskService(pool, logger);
 01222            }
 01223            else
 01224            {
 01225                Logger.Warning("KestrunTaskService already configured; skipping.");
 01226            }
 01227        });
 1228    }
 1229
 1230    /// <summary>
 1231    /// Adds MVC / API controllers to the application.
 1232    /// </summary>
 1233    /// <param name="cfg">The configuration options for MVC / API controllers.</param>
 1234    /// <returns>The current KestrunHost instance.</returns>
 1235    public KestrunHost AddControllers(Action<Microsoft.AspNetCore.Mvc.MvcOptions>? cfg = null)
 1236    {
 01237        return AddService(services =>
 01238        {
 01239            var builder = services.AddControllers();
 01240            if (cfg != null)
 01241            {
 01242                _ = builder.ConfigureApplicationPartManager(pm => { }); // customise if you wish
 01243            }
 01244        });
 1245    }
 1246
 1247
 1248
 1249
 1250    /// <summary>
 1251    /// Adds a PowerShell runtime to the application.
 1252    /// This middleware allows you to execute PowerShell scripts in response to HTTP requests.
 1253    /// </summary>
 1254    /// <param name="routePrefix">The route prefix to use for the PowerShell runtime.</param>
 1255    /// <returns>The current KestrunHost instance.</returns>
 1256    public KestrunHost AddPowerShellRuntime(PathString? routePrefix = null)
 1257    {
 11258        if (Logger.IsEnabled(LogEventLevel.Debug))
 1259        {
 11260            Logger.Debug("Adding PowerShell runtime with route prefix: {RoutePrefix}", routePrefix);
 1261        }
 1262
 11263        return Use(app =>
 11264        {
 11265            ArgumentNullException.ThrowIfNull(_runspacePool);
 11266            // ── mount PowerShell at the root ──
 11267            _ = app.UseLanguageRuntime(
 11268                ScriptLanguage.PowerShell,
 21269                b => b.UsePowerShellRunspace(_runspacePool));
 21270        });
 1271    }
 1272
 1273    /// <summary>
 1274    /// Adds a SignalR hub to the application at the specified path.
 1275    /// </summary>
 1276    /// <typeparam name="T">The type of the SignalR hub.</typeparam>
 1277    /// <param name="path">The path at which to map the SignalR hub.</param>
 1278    /// <returns>The current KestrunHost instance.</returns>
 1279    public KestrunHost AddSignalR<T>(string path) where T : Hub
 1280    {
 01281        return AddService(s =>
 01282        {
 01283            _ = s.AddSignalR().AddJsonProtocol(opts =>
 01284            {
 01285                // Avoid failures when payloads contain cycles; our sanitizer should prevent most, this is a safety net.
 01286                opts.PayloadSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreC
 01287            });
 01288            // Register IRealtimeBroadcaster as singleton if it's the KestrunHub
 01289            if (typeof(T) == typeof(SignalR.KestrunHub))
 01290            {
 01291                _ = s.AddSingleton<SignalR.IRealtimeBroadcaster, SignalR.RealtimeBroadcaster>();
 01292                _ = s.AddSingleton<SignalR.IConnectionTracker, SignalR.InMemoryConnectionTracker>();
 01293            }
 01294        })
 01295        .Use(app => ((IEndpointRouteBuilder)app).MapHub<T>(path));
 1296    }
 1297
 1298    /// <summary>
 1299    /// Adds the default SignalR hub (KestrunHub) to the application at the specified path.
 1300    /// </summary>
 1301    /// <param name="path">The path at which to map the SignalR hub.</param>
 1302    /// <returns></returns>
 01303    public KestrunHost AddSignalR(string path) => AddSignalR<SignalR.KestrunHub>(path);
 1304
 1305    /*
 1306        // ④ gRPC
 1307        public KestrunHost AddGrpc<TService>() where TService : class
 1308        {
 1309            return AddService(s => s.AddGrpc())
 1310                   .Use(app => app.MapGrpcService<TService>());
 1311        }
 1312    */
 1313
 1314    /*   public KestrunHost AddSwagger()
 1315       {
 1316           AddService(s =>
 1317           {
 1318               s.AddEndpointsApiExplorer();
 1319               s.AddSwaggerGen();
 1320           });
 1321           //  ⚠️ Swagger’s middleware normally goes first in the pipeline
 1322           return Use(app =>
 1323           {
 1324               app.UseSwagger();
 1325               app.UseSwaggerUI();
 1326           });
 1327       }*/
 1328
 1329    // Add as many tiny helpers as you wish:
 1330    // • AddAuthentication(jwt => { … })
 1331    // • AddSignalR()
 1332    // • AddHealthChecks()
 1333    // • AddGrpc()
 1334    // etc.
 1335
 1336    #endregion
 1337    #region Run/Start/Stop
 1338
 1339    /// <summary>
 1340    /// Runs the Kestrun web application, applying configuration and starting the server.
 1341    /// </summary>
 1342    public void Run()
 1343    {
 01344        if (Logger.IsEnabled(LogEventLevel.Debug))
 1345        {
 01346            Logger.Debug("Run() called");
 1347        }
 1348
 01349        EnableConfiguration();
 01350        StartTime = DateTime.UtcNow;
 01351        _app?.Run();
 01352    }
 1353
 1354    /// <summary>
 1355    /// Starts the Kestrun web application asynchronously.
 1356    /// </summary>
 1357    /// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
 1358    /// <returns>A task that represents the asynchronous start operation.</returns>
 1359    public async Task StartAsync(CancellationToken cancellationToken = default)
 1360    {
 171361        if (Logger.IsEnabled(LogEventLevel.Debug))
 1362        {
 11363            Logger.Debug("StartAsync() called");
 1364        }
 1365
 171366        EnableConfiguration();
 171367        if (_app != null)
 1368        {
 171369            StartTime = DateTime.UtcNow;
 171370            await _app.StartAsync(cancellationToken);
 1371        }
 171372    }
 1373
 1374    /// <summary>
 1375    /// Stops the Kestrun web application asynchronously.
 1376    /// </summary>
 1377    /// <param name="cancellationToken">A cancellation token to observe while waiting for the task to complete.</param>
 1378    /// <returns>A task that represents the asynchronous stop operation.</returns>
 1379    public async Task StopAsync(CancellationToken cancellationToken = default)
 1380    {
 221381        if (Logger.IsEnabled(LogEventLevel.Debug))
 1382        {
 61383            Logger.Debug("StopAsync() called");
 1384        }
 1385
 221386        if (_app != null)
 1387        {
 1388            try
 1389            {
 1390                // Initiate graceful shutdown
 171391                await _app.StopAsync(cancellationToken);
 171392                StopTime = DateTime.UtcNow;
 171393            }
 01394            catch (Exception ex) when (ex.GetType().FullName == "System.Net.Quic.QuicException")
 1395            {
 1396                // QUIC exceptions can occur during shutdown, especially if the server is not using QUIC.
 1397                // We log this as a debug message to avoid cluttering the logs with expected exceptions.
 1398                // This is a workaround for
 1399
 01400                Logger.Debug("Ignored QUIC exception during shutdown: {Message}", ex.Message);
 01401            }
 1402        }
 221403    }
 1404
 1405    /// <summary>
 1406    /// Initiates a graceful shutdown of the Kestrun web application.
 1407    /// </summary>
 1408    public void Stop()
 1409    {
 11410        if (Interlocked.Exchange(ref _stopping, 1) == 1)
 1411        {
 01412            return; // already stopping
 1413        }
 11414        if (Logger.IsEnabled(LogEventLevel.Debug))
 1415        {
 11416            Logger.Debug("Stop() called");
 1417        }
 1418        // This initiates a graceful shutdown.
 11419        _app?.Lifetime.StopApplication();
 11420        StopTime = DateTime.UtcNow;
 11421    }
 1422
 1423    /// <summary>
 1424    /// Determines whether the Kestrun web application is currently running.
 1425    /// </summary>
 1426    /// <returns>True if the application is running; otherwise, false.</returns>
 1427    public bool IsRunning
 1428    {
 1429        get
 1430        {
 81431            var appField = typeof(KestrunHost)
 81432                .GetField("_app", BindingFlags.NonPublic | BindingFlags.Instance);
 1433
 81434            return appField?.GetValue(this) is WebApplication app && !app.Lifetime.ApplicationStopping.IsCancellationReq
 1435        }
 1436    }
 1437
 1438    #endregion
 1439
 1440
 1441
 1442    #region Runspace Pool Management
 1443
 1444    /// <summary>
 1445    /// Creates and returns a new <see cref="KestrunRunspacePoolManager"/> instance with the specified maximum number of
 1446    /// </summary>
 1447    /// <param name="maxRunspaces">The maximum number of runspaces to create. If not specified or zero, defaults to twic
 1448    /// <param name="userVariables">A dictionary of user-defined variables to inject into the runspace pool.</param>
 1449    /// <param name="userFunctions">A dictionary of user-defined functions to inject into the runspace pool.</param>
 1450    /// <returns>A configured <see cref="KestrunRunspacePoolManager"/> instance.</returns>
 1451    public KestrunRunspacePoolManager CreateRunspacePool(int? maxRunspaces = 0, Dictionary<string, object>? userVariable
 1452    {
 581453        if (Logger.IsEnabled(LogEventLevel.Debug))
 1454        {
 411455            Logger.Debug("CreateRunspacePool() called: {@MaxRunspaces}", maxRunspaces);
 1456        }
 1457
 1458        // Create a default InitialSessionState with an unrestricted policy:
 581459        var iss = InitialSessionState.CreateDefault();
 1460
 581461        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
 1462        {
 1463            // On Windows, we can use the full .NET Framework modules
 01464            iss.ExecutionPolicy = ExecutionPolicy.Unrestricted;
 1465        }
 2321466        foreach (var p in _modulePaths)
 1467        {
 581468            iss.ImportPSModule([p]);
 1469        }
 1470
 1471        // Inject 'KrServer' variable to provide access to the host instance
 581472        iss.Variables.Add(
 581473            new SessionStateVariableEntry(
 581474                "KrServer",
 581475                this,
 581476                "The Kestrun Server Host (KestrunHost) instance"
 581477            )
 581478        );
 1479        // Inject global variables into all runspaces
 6361480        foreach (var kvp in SharedStateStore.Snapshot())
 1481        {
 1482            // kvp.Key = "Visits", kvp.Value = 0
 2601483            iss.Variables.Add(
 2601484                new SessionStateVariableEntry(
 2601485                    kvp.Key,
 2601486                    kvp.Value,
 2601487                    "Global variable"
 2601488                )
 2601489            );
 1490        }
 1491
 1201492        foreach (var kvp in userVariables ?? [])
 1493        {
 21494            if (kvp.Value is PSVariable psVar)
 1495            {
 01496                iss.Variables.Add(
 01497                    new SessionStateVariableEntry(
 01498                        kvp.Key,
 01499                        psVar.Value,
 01500                        psVar.Description ?? "User-defined variable"
 01501                    )
 01502                );
 1503            }
 1504            else
 1505            {
 21506                iss.Variables.Add(
 21507                    new SessionStateVariableEntry(
 21508                        kvp.Key,
 21509                        kvp.Value,
 21510                        "User-defined variable"
 21511                    )
 21512                );
 1513            }
 1514        }
 1515
 1201516        foreach (var r in userFunctions ?? [])
 1517        {
 21518            var name = r.Key;
 21519            var def = r.Value;
 1520
 1521            // Use the string-based ctor available in 7.4 ref/net8.0
 21522            var entry = new SessionStateFunctionEntry(
 21523                name,
 21524                def,
 21525                ScopedItemOptions.ReadOnly,   // or ScopedItemOptions.None if you want them mutable
 21526                helpFile: null
 21527            );
 1528
 21529            iss.Commands.Add(entry);
 1530        }
 1531
 1532        // Determine max runspaces
 581533        var maxRs = (maxRunspaces.HasValue && maxRunspaces.Value > 0) ? maxRunspaces.Value : Environment.ProcessorCount 
 1534
 581535        Logger.Information($"Creating runspace pool with max runspaces: {maxRs}");
 581536        var runspacePool = new KestrunRunspacePoolManager(this, Options?.MinRunspaces ?? 1, maxRunspaces: maxRs, initial
 1537        // Return the created runspace pool
 581538        return runspacePool;
 1539    }
 1540
 1541
 1542    #endregion
 1543
 1544
 1545    #region Disposable
 1546
 1547    /// <summary>
 1548    /// Releases all resources used by the <see cref="KestrunHost"/> instance.
 1549    /// </summary>
 1550    public void Dispose()
 1551    {
 801552        if (Logger.IsEnabled(LogEventLevel.Debug))
 1553        {
 601554            Logger.Debug("Dispose() called");
 1555        }
 1556
 801557        _runspacePool?.Dispose();
 801558        _runspacePool = null; // Clear the runspace pool reference
 801559        IsConfigured = false; // Reset configuration state
 801560        _app = null;
 801561        Scheduler?.Dispose();
 801562        (Logger as IDisposable)?.Dispose();
 801563        GC.SuppressFinalize(this);
 801564    }
 1565    #endregion
 1566
 1567    #region Script Validation
 1568
 1569
 1570    #endregion
 1571}

Methods/Properties

get_Builder()
get_App()
get_ApplicationName()
get_Options()
.ctor(System.String,Serilog.ILogger,System.String,System.String[],System.String[])
get_IsConfigured()
get_StartTime()
get_StopTime()
get_Uptime()
get_RunspacePool()
get_FeatureQueue()
get_HealthProbes()
get_KestrunRoot()
get_Logger()
get_Scheduler()
get_Tasks()
get_RouteGroupStack()
get_RegisteredRoutes()
get_RegisteredAuthentications()
get_DefaultCacheControl()
get_PowershellMiddlewareEnabled()
get_DefaultHost()
get_StatusCodeOptions()
set_StatusCodeOptions(Kestrun.Hosting.Options.StatusCodeOptions)
get_ExceptionOptions()
set_ExceptionOptions(Kestrun.Hosting.Options.ExceptionOptions)
get_ForwardedHeaderOptions()
set_ForwardedHeaderOptions(Microsoft.AspNetCore.Builder.ForwardedHeadersOptions)
.ctor(System.String,System.String,System.String[])
LogConstructorArgs(System.String,System.Boolean,System.String,System.Int32)
SetWorkingDirectoryIfNeeded(System.String)
AddKestrunModulePathIfMissing(System.String[])
InitializeOptions(System.String)
AddUserModulePaths(System.String[])
AddProbe(Kestrun.Health.IProbe)
AddProbe(System.String,System.String[],System.Func`2<System.Threading.CancellationToken,System.Threading.Tasks.Task`1<Kestrun.Health.ProbeResult>>)
AddProbe(System.String,System.String[],System.String,System.Nullable`1<Kestrun.Scripting.ScriptLanguage>,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.Object>,System.String[],System.Reflection.Assembly[])
GetHealthProbesSnapshot()
RegisterProbeInternal(Kestrun.Health.IProbe)
ConfigureListener(System.Int32,System.Net.IPAddress,System.Security.Cryptography.X509Certificates.X509Certificate2,Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols,System.Boolean)
ConfigureListener(System.Int32,System.Net.IPAddress,System.Boolean)
ConfigureListener(System.Int32,System.Boolean)
ConfigureListener(System.String,System.Int32,System.Security.Cryptography.X509Certificates.X509Certificate2,Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols,System.Boolean,System.Net.Sockets.AddressFamily[])
ConfigureListener(System.Uri,System.Security.Cryptography.X509Certificates.X509Certificate2,System.Nullable`1<Microsoft.AspNetCore.Server.Kestrel.Core.HttpProtocols>,System.Boolean,System.Net.Sockets.AddressFamily[])
ValidateConfiguration()
InitializeRunspacePool(System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.String>)
ConfigureKestrelBase()
ConfigureNamedPipes()
ConfigureHttpsAdapter(Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions)
BindListeners(Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerOptions)
LogConfiguredEndpoints()
HandleConfigurationError(System.Exception)
EnableConfiguration(System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.String>)
RegisterDefaultHealthProbes()
Build()
ValidateBuilderState()
ApplyQueuedServices()
BuildWebApplication()
ConfigureBuiltInMiddleware()
ConfigureExceptionHandling()
ConfigureForwardedHeaders()
ConfigureStatusCodePages()
ConfigurePowerShellRuntime()
LogApplicationInfo()
LogPagesDirectory()
ApplyQueuedMiddleware()
ApplyFeatures()
IsServiceRegistered(System.Type)
IsServiceRegistered()
AddService(System.Action`1<Microsoft.Extensions.DependencyInjection.IServiceCollection>)
Use(System.Action`1<Microsoft.AspNetCore.Builder.IApplicationBuilder>)
AddFeature(System.Action`1<Kestrun.Hosting.KestrunHost>)
AddScheduling(System.Nullable`1<System.Int32>)
AddTasks(System.Nullable`1<System.Int32>)
AddControllers(System.Action`1<Microsoft.AspNetCore.Mvc.MvcOptions>)
AddPowerShellRuntime(System.Nullable`1<Microsoft.AspNetCore.Http.PathString>)
AddSignalR(System.String)
Run()
StartAsync()
StopAsync()
Stop()
get_IsRunning()
CreateRunspacePool(System.Nullable`1<System.Int32>,System.Collections.Generic.Dictionary`2<System.String,System.Object>,System.Collections.Generic.Dictionary`2<System.String,System.String>)
Dispose()