From c28f47b632b028ad25d16bed9c6af138286d6ad1 Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 10 Sep 2025 02:06:53 -0500 Subject: [PATCH 1/4] Create debug handler class to manage script debugging events --- .../UI/Components/PowerShellConsole.razor | 111 +++++---- .../PowerShell/PowerShellDebugHandler.cs | 57 ++++- .../PowerShell/PowerShellScript.cs | 32 ++- LANCommander.SDK/Rpc/Interfaces/Client/Log.cs | 9 + .../Rpc/Interfaces/Client/Script.cs | 12 + .../Rpc/Interfaces/Server/Script.cs | 10 + LANCommander.SDK/Rpc/Log.cs | 12 + LANCommander.SDK/Rpc/Script.cs | 35 +++ LANCommander.SDK/Services/SaveService.cs | 7 +- LANCommander.SDK/Services/ScriptService.cs | 222 +++--------------- LANCommander.Server/Hubs/Log.cs | 6 + LANCommander.Server/Hubs/Script.cs | 49 ++++ LANCommander.Server/Hubs/_RpcHub.cs | 3 + 13 files changed, 302 insertions(+), 263 deletions(-) create mode 100644 LANCommander.SDK/Rpc/Interfaces/Client/Log.cs create mode 100644 LANCommander.SDK/Rpc/Interfaces/Client/Script.cs create mode 100644 LANCommander.SDK/Rpc/Interfaces/Server/Script.cs create mode 100644 LANCommander.SDK/Rpc/Log.cs create mode 100644 LANCommander.SDK/Rpc/Script.cs create mode 100644 LANCommander.Server/Hubs/Log.cs create mode 100644 LANCommander.Server/Hubs/Script.cs diff --git a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor b/LANCommander.Launcher/UI/Components/PowerShellConsole.razor index c4e29809..ee7ee022 100644 --- a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor +++ b/LANCommander.Launcher/UI/Components/PowerShellConsole.razor @@ -1,4 +1,5 @@ -@using XtermBlazor +@using LANCommander.SDK.PowerShell +@using XtermBlazor @using LogLevel = Microsoft.Extensions.Logging.LogLevel @inject SDK.Client Client @@ -7,80 +8,86 @@ @code { + [Parameter] public PowerShellDebugHandler DebugHandler { get; set; } + private bool Visible { get; set; } = false; private Xterm Terminal; private TaskCompletionSource InputTaskCompletionSource; - private TerminalOptions _options = new TerminalOptions + private TerminalOptions _options = new() { CursorBlink = true, CursorStyle = CursorStyle.Bar, }; - private HashSet Addons = new HashSet() + private HashSet Addons = new() { "readline", "addon-fit" }; - protected override async Task OnInitializedAsync() + protected override void OnInitialized() { - Client.Scripts.OnDebugStart = async (ps) => + DebugHandler.OnDebugStart += OnDebugStart; + DebugHandler.OnDebugBreak += OnDebugBreak; + DebugHandler.OnDebugOutput += OnDebugOutput; + } + + private async void OnDebugStart(object? sender, OnDebugStartEventArgs e) + { + Terminal?.Clear(); + Visible = true; + await InvokeAsync(StateHasChanged); + + await Terminal.Addon("addon-fit").InvokeVoidAsync("fit"); + } + + private async void OnDebugBreak(object? sender, OnDebugBreakEventArgs e) + { + while (true) { - Terminal?.Clear(); - Visible = true; - await InvokeAsync(StateHasChanged); + var input = await ReadLine(); - await Terminal.Addon("addon-fit").InvokeVoidAsync("fit"); - }; + if (input.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) + break; - Client.Scripts.OnOutput = async (level, message) => + if (input.StartsWith('$')) + input = "Write-Host " + input; + + e.PowerShell.Commands.Clear(); + e.PowerShell.AddScript(input); + + await e.PowerShell.InvokeAsync(); + } + + Visible = false; + await InvokeAsync(StateHasChanged); + } + + private async void OnDebugOutput(object? sender, OnDebugOutputEventArgs e) + { + switch (e.LogLevel) { - switch (level) - { - case LogLevel.Error: - await Terminal.WriteLine($"\x1b[0;31m{message}"); - break; + case LogLevel.Error: + await Terminal.WriteLine($"\x1b[0;31m{e.Message}"); + break; - case LogLevel.Warning: - await Terminal.WriteLine($"\x1b[0;33m{message}"); - break; + case LogLevel.Warning: + await Terminal.WriteLine($"\x1b[0;33m{e.Message}"); + break; - case LogLevel.Debug: - await Terminal.WriteLine($"\x1b[0;37m{message}"); - break; + case LogLevel.Debug: + await Terminal.WriteLine($"\x1b[0;37m{e.Message}"); + break; - case LogLevel.Trace: - await Terminal.WriteLine($"\x1b[0;36m{message}"); - break; + case LogLevel.Trace: + await Terminal.WriteLine($"\x1b[0;36m{e.Message}"); + break; - case LogLevel.Information: - await Terminal.WriteLine($"\x1b[0;37m{message}"); - break; - } - }; - - Client.Scripts.OnDebugBreak = async (ps) => - { - while (true) - { - var input = await ReadLine(); - - if (input.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) - break; - - if (input.StartsWith('$')) - input = "Write-Host " + input; - - ps.Commands.Clear(); - ps.AddScript(input); - - await ps.InvokeAsync(); - } - - Visible = false; - await InvokeAsync(StateHasChanged); - }; + case LogLevel.Information: + await Terminal.WriteLine($"\x1b[0;37m{e.Message}"); + break; + } } private async Task OnFirstRender() diff --git a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs index 1413f9ce..49ddef05 100644 --- a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs +++ b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs @@ -6,7 +6,58 @@ namespace LANCommander.SDK.PowerShell; public class PowerShellDebugHandler { - public Func OnDebugStart; - public Func OnDebugBreak; - public Func OnOutput; + public Guid SessionId { get; set; } = Guid.NewGuid(); + + public event EventHandler OnDebugStart; + public event EventHandler OnDebugBreak; + public event EventHandler OnDebugOutput; + + private System.Management.Automation.PowerShell _powerShell; + + internal void Start(System.Management.Automation.PowerShell ps = null) + { + if (_powerShell == null) + _powerShell = ps; + + OnDebugStart?.Invoke(this, new OnDebugStartEventArgs + { + PowerShell = _powerShell, + }); + } + + internal void Break(System.Management.Automation.PowerShell ps = null) + { + if (_powerShell == null) + _powerShell = ps; + + OnDebugStart?.Invoke(this, new OnDebugStartEventArgs + { + PowerShell = _powerShell, + }); + } + + internal void Output(LogLevel level, string message) + { + OnDebugOutput?.Invoke(this, new OnDebugOutputEventArgs + { + LogLevel = level, + Message = message + }); + } +} + +public class OnDebugStartEventArgs : EventArgs +{ + public System.Management.Automation.PowerShell PowerShell { get; set; } +} + +public class OnDebugBreakEventArgs : EventArgs +{ + public System.Management.Automation.PowerShell PowerShell { get; set; } +} + +public class OnDebugOutputEventArgs : EventArgs +{ + public LogLevel LogLevel { get; set; } + public string Message { get; set; } } \ No newline at end of file diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index 83db98b3..cff64d4b 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -185,8 +185,9 @@ namespace LANCommander.SDK.PowerShell { ps.Runspace = runspace; + if (Debug) - await (DebugHandler.OnDebugStart?.Invoke(ps) ?? Task.CompletedTask); + DebugHandler.Start(ps); ps.AddScript("Write-Host $Logo"); @@ -206,21 +207,18 @@ namespace LANCommander.SDK.PowerShell ps.AddScript("Write-Host ''"); ps.AddScript("Write-Host 'Enter \"exit\" to continue'"); - - if (DebugHandler.OnOutput != null) - { - ps.Streams.Information.DataAdded += Information_DataAdded; - ps.Streams.Verbose.DataAdded += Verbose_DataAdded; - ps.Streams.Debug.DataAdded += Debug_DataAdded; - ps.Streams.Warning.DataAdded += Warning_DataAdded; - ps.Streams.Error.DataAdded += Error_DataAdded; - } + + ps.Streams.Information.DataAdded += Information_DataAdded; + ps.Streams.Verbose.DataAdded += Verbose_DataAdded; + ps.Streams.Debug.DataAdded += Debug_DataAdded; + ps.Streams.Warning.DataAdded += Warning_DataAdded; + ps.Streams.Error.DataAdded += Error_DataAdded; } var results = await ps.InvokeAsync(); if (Debug) - await (DebugHandler.OnDebugBreak?.Invoke(ps) ?? Task.CompletedTask); + DebugHandler.Break(ps); try { @@ -246,29 +244,29 @@ namespace LANCommander.SDK.PowerShell { var record = ((PSDataCollection)sender)[e.Index]; - DebugHandler.OnOutput?.Invoke(LogLevel.Error, $"{record.InvocationInfo.InvocationName} : {record.Exception.Message}"); - DebugHandler.OnOutput?.Invoke(LogLevel.Error, record.InvocationInfo.PositionMessage); + DebugHandler.Output(LogLevel.Error, $"{record.InvocationInfo.InvocationName} : {record.Exception.Message}"); + DebugHandler.Output(LogLevel.Error, record.InvocationInfo.PositionMessage); } private void Warning_DataAdded(object sender, DataAddedEventArgs e) { var record = ((PSDataCollection)sender)[e.Index]; - DebugHandler.OnOutput?.Invoke(LogLevel.Warning, record.Message); + DebugHandler.Output(LogLevel.Warning, record.Message); } private void Debug_DataAdded(object sender, DataAddedEventArgs e) { var record = ((PSDataCollection)sender)[e.Index]; - DebugHandler.OnOutput?.Invoke(LogLevel.Debug, record.Message); + DebugHandler.Output(LogLevel.Debug, record.Message); } private void Verbose_DataAdded(object sender, DataAddedEventArgs e) { var record = ((PSDataCollection)sender)[e.Index]; - DebugHandler.OnOutput?.Invoke(LogLevel.Trace, record.Message); + DebugHandler.Output(LogLevel.Trace, record.Message); } private void Information_DataAdded(object sender, DataAddedEventArgs e) @@ -276,7 +274,7 @@ namespace LANCommander.SDK.PowerShell var record = ((PSDataCollection)sender)[e.Index]; if (record.MessageData != null && record.MessageData is HostInformationMessage) - DebugHandler.OnOutput?.Invoke(LogLevel.Information, (record.MessageData as HostInformationMessage).Message); + DebugHandler.Output(LogLevel.Information, (record.MessageData as HostInformationMessage).Message); } public static string Serialize(T input) diff --git a/LANCommander.SDK/Rpc/Interfaces/Client/Log.cs b/LANCommander.SDK/Rpc/Interfaces/Client/Log.cs new file mode 100644 index 00000000..b3c2eaa3 --- /dev/null +++ b/LANCommander.SDK/Rpc/Interfaces/Client/Log.cs @@ -0,0 +1,9 @@ +using System; +using System.Threading.Tasks; + +namespace LANCommander.SDK.Rpc.Client; + +public partial interface IRpcClient +{ + Task Log_ConsoleOutputAsync(Guid sessionId, string content); +} \ No newline at end of file diff --git a/LANCommander.SDK/Rpc/Interfaces/Client/Script.cs b/LANCommander.SDK/Rpc/Interfaces/Client/Script.cs new file mode 100644 index 00000000..bcbf188f --- /dev/null +++ b/LANCommander.SDK/Rpc/Interfaces/Client/Script.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; + +namespace LANCommander.SDK.Rpc.Client; + +public partial interface IRpcClient +{ + Task Script_DebugOutputAsync(Guid sessionId, LogLevel level, string message); + Task Script_DebugStartAsync(Guid sessionId); + Task Script_DebugBreakAsync(Guid sessionId); +} \ No newline at end of file diff --git a/LANCommander.SDK/Rpc/Interfaces/Server/Script.cs b/LANCommander.SDK/Rpc/Interfaces/Server/Script.cs new file mode 100644 index 00000000..58e699f5 --- /dev/null +++ b/LANCommander.SDK/Rpc/Interfaces/Server/Script.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace LANCommander.SDK.Rpc.Server; + +public partial interface IRpcHub +{ + Task Script_ExecuteAsync(Guid scriptId); + Task Script_ConsoleInputAsync(Guid sessionId, string input); +} \ No newline at end of file diff --git a/LANCommander.SDK/Rpc/Log.cs b/LANCommander.SDK/Rpc/Log.cs new file mode 100644 index 00000000..4fae36c3 --- /dev/null +++ b/LANCommander.SDK/Rpc/Log.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; + +namespace LANCommander.SDK.Rpc; + +public partial class RpcClient +{ + public async Task Log_ConsoleOutputAsync(Guid sessionId, string content) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/LANCommander.SDK/Rpc/Script.cs b/LANCommander.SDK/Rpc/Script.cs new file mode 100644 index 00000000..72d56e88 --- /dev/null +++ b/LANCommander.SDK/Rpc/Script.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using LANCommander.SDK.PowerShell; +using Microsoft.Extensions.Logging; + +namespace LANCommander.SDK.Rpc; + +public partial class RpcClient +{ + private Dictionary _debugHandlers = new(); + + public void AddDebugHandler(Guid sessionId, PowerShellDebugHandler handler) + { + _debugHandlers[sessionId] = handler; + } + + public async Task Script_DebugOutputAsync(Guid sessionId, LogLevel level, string message) + { + if (_debugHandlers.ContainsKey(sessionId)) + _debugHandlers[sessionId].Output(level, message); + } + + public async Task Script_DebugStartAsync(Guid sessionId) + { + if (_debugHandlers.ContainsKey(sessionId)) + _debugHandlers[sessionId].Start(); + } + + public async Task Script_DebugBreakAsync(Guid sessionId) + { + if (_debugHandlers.ContainsKey(sessionId)) + _debugHandlers[sessionId].Break(); + } +} \ No newline at end of file diff --git a/LANCommander.SDK/Services/SaveService.cs b/LANCommander.SDK/Services/SaveService.cs index 658e2bdb..d39b3fb9 100644 --- a/LANCommander.SDK/Services/SaveService.cs +++ b/LANCommander.SDK/Services/SaveService.cs @@ -208,14 +208,9 @@ namespace LANCommander.SDK.Services } script.UseInline($"Start-Process regedit.exe {adminArgument} -ArgumentList \"/s\", \"{registryImportFilePath}\""); - + if (_client.Scripts.Debug) - { script.EnableDebug(); - script.DebugHandler.OnDebugStart = _client.Scripts.OnDebugStart; - script.DebugHandler.OnDebugBreak = _client.Scripts.OnDebugBreak; - script.DebugHandler.OnOutput = _client.Scripts.OnOutput; - } await script.ExecuteAsync(); } diff --git a/LANCommander.SDK/Services/ScriptService.cs b/LANCommander.SDK/Services/ScriptService.cs index 5f986973..4c4b341b 100644 --- a/LANCommander.SDK/Services/ScriptService.cs +++ b/LANCommander.SDK/Services/ScriptService.cs @@ -22,10 +22,6 @@ namespace LANCommander.SDK.Services public bool Debug { get; set; } = false; - public Func OnDebugStart; - public Func OnDebugBreak; - public Func OnOutput; - public ScriptService(Client client) { _client = client; @@ -38,7 +34,7 @@ namespace LANCommander.SDK.Services } #region Authentication Scripts - public async Task RunUserLoginScript(Script loginScript, User user) + public async Task RunUserLoginScript(Script loginScript, User user, PowerShellDebugHandler debugHandler = null) { try { @@ -48,6 +44,9 @@ namespace LANCommander.SDK.Services script.AddVariable("User", user); + if (Debug) + script.EnableDebug(debugHandler); + script.UseInline(loginScript.Contents); try @@ -72,7 +71,7 @@ namespace LANCommander.SDK.Services } } - public async Task RunUserRegistrationScript(Script registrationScript, User user) + public async Task RunUserRegistrationScript(Script registrationScript, User user, PowerShellDebugHandler debugHandler = null) { try { @@ -81,6 +80,9 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.UserRegistration); script.AddVariable("User", user); + + if (Debug) + script.EnableDebug(debugHandler); script.UseInline(registrationScript.Contents); @@ -108,7 +110,7 @@ namespace LANCommander.SDK.Services #endregion #region Redistributables - public async Task RunDetectInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId) + public async Task RunDetectInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null) { bool result = default; @@ -126,7 +128,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.DetectInstall); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", gameManifest); @@ -161,20 +163,6 @@ namespace LANCommander.SDK.Services script.UseWorkingDirectory(Path.Combine(GameService.GetMetadataDirectoryPath(installDirectory, redistributableId))); script.UseFile(path); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -194,7 +182,8 @@ namespace LANCommander.SDK.Services } } } - result = await script.ExecuteAsync(); + + result = await script.ExecuteAsync(); op.Complete(); } @@ -208,7 +197,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId) + public async Task RunInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -226,7 +215,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.Install); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", gameManifest); @@ -264,20 +253,6 @@ namespace LANCommander.SDK.Services script.UseWorkingDirectory(extractionPath); script.UseFile(path); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -298,7 +273,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunBeforeStartScriptAsync(string installDirectory, Guid gameId, Guid redistributableId) + public async Task RunBeforeStartScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -317,7 +292,7 @@ namespace LANCommander.SDK.Services var playerAlias = await GameService.GetPlayerAliasAsync(installDirectory, gameId); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", gameManifest); @@ -357,20 +332,6 @@ namespace LANCommander.SDK.Services script.UseWorkingDirectory(extractionPath); script.UseFile(path); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -395,7 +356,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunAfterStopScriptAsync(string installDirectory, Guid gameId, Guid redistributableId) + public async Task RunAfterStopScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -413,7 +374,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.AfterStop); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", gameManifest); @@ -453,20 +414,6 @@ namespace LANCommander.SDK.Services script.UseWorkingDirectory(extractionPath); script.UseFile(path); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -492,7 +439,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunNameChangeScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, string newName) + public async Task RunNameChangeScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, string newName, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -520,7 +467,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.NameChange); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", gameManifest); @@ -561,20 +508,6 @@ namespace LANCommander.SDK.Services script.UseWorkingDirectory(extractionPath); script.UseFile(path); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -602,7 +535,7 @@ namespace LANCommander.SDK.Services #endregion #region Games - public async Task RunInstallScriptAsync(string installDirectory, Guid gameId) + public async Task RunInstallScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -618,7 +551,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.Install); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -648,20 +581,6 @@ namespace LANCommander.SDK.Services _logger?.LogError(ex, "Could not enrich logs"); } - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -686,7 +605,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunUninstallScriptAsync(string installDirectory, Guid gameId) + public async Task RunUninstallScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -702,7 +621,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.Uninstall); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -732,20 +651,6 @@ namespace LANCommander.SDK.Services _logger?.LogError(ex, "Could not enrich logs"); } - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -770,7 +675,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunBeforeStartScriptAsync(string installDirectory, Guid gameId) + public async Task RunBeforeStartScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -787,7 +692,7 @@ namespace LANCommander.SDK.Services var playerAlias = GameService.GetPlayerAlias(installDirectory, gameId); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -819,20 +724,6 @@ namespace LANCommander.SDK.Services _logger?.LogError(ex, "Could not enrich logs"); } - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -857,7 +748,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunAfterStopScriptAsync(string installDirectory, Guid gameId) + public async Task RunAfterStopScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -873,7 +764,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.AfterStop); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -904,20 +795,6 @@ namespace LANCommander.SDK.Services _logger?.LogError(ex, "Could not enrich logs"); } - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -943,7 +820,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunNameChangeScriptAsync(string installDirectory, Guid gameId, string newName) + public async Task RunNameChangeScriptAsync(string installDirectory, Guid gameId, string newName, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -969,7 +846,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.NameChange); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -1005,20 +882,6 @@ namespace LANCommander.SDK.Services GameService.UpdatePlayerAlias(installDirectory, gameId, newName); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -1043,7 +906,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunKeyChangeScriptAsync(string installDirectory, Guid gameId, string key) + public async Task RunKeyChangeScriptAsync(string installDirectory, Guid gameId, string key, PowerShellDebugHandler debugHandler = null) { int result = default; @@ -1059,7 +922,7 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.KeyChange); if (Debug) - script.DebugHandler.OnDebugStart = OnDebugStart; + script.EnableDebug(debugHandler); _logger?.LogTrace("New key is {Key}", key); @@ -1095,20 +958,6 @@ namespace LANCommander.SDK.Services GameService.UpdateCurrentKey(installDirectory, gameId, key); - try - { - if (Debug) - { - script.EnableDebug(); - script.DebugHandler.OnDebugBreak = OnDebugBreak; - script.DebugHandler.OnOutput = OnOutput; - } - } - catch (Exception ex) - { - _logger?.LogError(ex, "Could not debug script"); - } - bool handled = false; if (ExternalScriptRunner != null) @@ -1133,7 +982,7 @@ namespace LANCommander.SDK.Services return result; } - public async Task RunPackageScriptAsync(Script packageScript, Game game) + public async Task RunPackageScriptAsync(Script packageScript, Game game, PowerShellDebugHandler debugHandler = null) { try { @@ -1142,8 +991,11 @@ namespace LANCommander.SDK.Services var script = new PowerShellScript(Enums.ScriptType.Package); script.AddVariable("Game", game); - + script.UseInline(packageScript.Contents); + + if (Debug) + script.EnableDebug(debugHandler); try { diff --git a/LANCommander.Server/Hubs/Log.cs b/LANCommander.Server/Hubs/Log.cs new file mode 100644 index 00000000..51212fe3 --- /dev/null +++ b/LANCommander.Server/Hubs/Log.cs @@ -0,0 +1,6 @@ +namespace LANCommander.Server.Hubs; + +public partial class RpcHub +{ + +} \ No newline at end of file diff --git a/LANCommander.Server/Hubs/Script.cs b/LANCommander.Server/Hubs/Script.cs new file mode 100644 index 00000000..0aead178 --- /dev/null +++ b/LANCommander.Server/Hubs/Script.cs @@ -0,0 +1,49 @@ +using System.Management.Automation; +using LANCommander.SDK.Enums; +using LANCommander.SDK.Models; +using LANCommander.SDK.PowerShell; + +namespace LANCommander.Server.Hubs; + +public partial class RpcHub +{ + private readonly Dictionary _scripts = new Dictionary(); + private string GetScriptSessionGroupName(Guid sessionId) => $"Script/Sessions/{sessionId}"; + + public async Task Script_ExecuteAsync(Guid scriptId) + { + var script = await scriptService.GetAsync(scriptId); + var game = await gameService.GetAsync(script.GameId ?? Guid.Empty); + + if (script.Type == ScriptType.Package) + { + var debugHandler = new PowerShellDebugHandler(); + + debugHandler.OnDebugOutput += DebugOutput; + debugHandler.OnDebugBreak += DebugBreak; + debugHandler.OnDebugStart += DebugStart; + + await Groups.AddToGroupAsync(Context.ConnectionId, GetScriptSessionGroupName(debugHandler.SessionId)); + + await client.Scripts.RunPackageScriptAsync(mapper.Map(script), mapper.Map(game)); + } + } + + private void DebugStart(object? sender, OnDebugStartEventArgs args) + { + if (sender is PowerShellDebugHandler debugHandler) + Clients.Caller.Script_DebugStartAsync(debugHandler.SessionId); + } + + private void DebugBreak(object? sender, OnDebugBreakEventArgs args) + { + if (sender is PowerShellDebugHandler debugHandler) + Clients.Caller.Script_DebugBreakAsync(debugHandler.SessionId); + } + + private void DebugOutput(object? sender, OnDebugOutputEventArgs args) + { + if (sender is PowerShellDebugHandler debugHandler) + Clients.Caller.Script_DebugOutputAsync(debugHandler.SessionId, args.LogLevel, args.Message); + } +} \ No newline at end of file diff --git a/LANCommander.Server/Hubs/_RpcHub.cs b/LANCommander.Server/Hubs/_RpcHub.cs index a0473d3e..4568b299 100644 --- a/LANCommander.Server/Hubs/_RpcHub.cs +++ b/LANCommander.Server/Hubs/_RpcHub.cs @@ -10,7 +10,10 @@ namespace LANCommander.Server.Hubs; public partial class RpcHub( IFusionCache cache, IMapper mapper, + SDK.Client client, ChatService chatService, + GameService gameService, + ScriptService scriptService, ServerService serverService) : Hub, IRpcHub { From 45f1b7b267601c6c3b97c7980214a6b3980c00de Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Mon, 15 Sep 2025 20:48:56 -0500 Subject: [PATCH 2/4] Handle debug input --- .../UI/Components/PowerShellConsole.razor | 8 +------- .../PowerShell/PowerShellDebugHandler.cs | 13 ++++++++++++- .../PowerShell/PowerShellScript.cs | 12 +++++++++++- LANCommander.SDK/Services/ScriptService.cs | 18 ++++++++++++++++++ LANCommander.Server/Hubs/Script.cs | 10 +++++++++- 5 files changed, 51 insertions(+), 10 deletions(-) diff --git a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor b/LANCommander.Launcher/UI/Components/PowerShellConsole.razor index ee7ee022..7c814fa8 100644 --- a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor +++ b/LANCommander.Launcher/UI/Components/PowerShellConsole.razor @@ -51,13 +51,7 @@ if (input.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) break; - if (input.StartsWith('$')) - input = "Write-Host " + input; - - e.PowerShell.Commands.Clear(); - e.PowerShell.AddScript(input); - - await e.PowerShell.InvokeAsync(); + await DebugHandler.ExecuteAsync(input); } Visible = false; diff --git a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs index 49ddef05..b9ecabf2 100644 --- a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs +++ b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs @@ -43,7 +43,18 @@ public class PowerShellDebugHandler LogLevel = level, Message = message }); - } + } + + public async Task ExecuteAsync(string input) + { + if (input.StartsWith('$')) + input = $"Write-Host {input}"; + + _powerShell.Commands.Clear(); + _powerShell.AddScript(input); + + await _powerShell.InvokeAsync(); + } } public class OnDebugStartEventArgs : EventArgs diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index cff64d4b..cb68de41 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -13,6 +13,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using LANCommander.SDK.Services; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; @@ -35,6 +36,8 @@ namespace LANCommander.SDK.PowerShell private TaskCompletionSource Input { get; set; } public PowerShellDebugHandler DebugHandler { get; private set; } + + private ScriptService ScriptService { get; set; } private const string Logo = @" __ ___ _ _______ __ @@ -144,9 +147,13 @@ namespace LANCommander.SDK.PowerShell public PowerShellScript EnableDebug(PowerShellDebugHandler debugHandler = null) { Debug = true; - + if (debugHandler != null) + { DebugHandler = debugHandler; + + ScriptService.RegisterDebugHandler(debugHandler); + } return this; } @@ -237,6 +244,9 @@ namespace LANCommander.SDK.PowerShell if (IgnoreWow64) Wow64RevertWow64FsRedirection(ref wow64Value); + if (DebugHandler != null) + ScriptService.DeregisterDebugHandler(DebugHandler); + return result; } diff --git a/LANCommander.SDK/Services/ScriptService.cs b/LANCommander.SDK/Services/ScriptService.cs index 4c4b341b..64fe1c9a 100644 --- a/LANCommander.SDK/Services/ScriptService.cs +++ b/LANCommander.SDK/Services/ScriptService.cs @@ -4,6 +4,7 @@ using LANCommander.SDK.Models; using LANCommander.SDK.PowerShell; using Microsoft.Extensions.Logging; using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -17,6 +18,8 @@ namespace LANCommander.SDK.Services private readonly Client _client; + private List _debugHandlers = new(); + public delegate Task ExternalScriptRunnerHandler(PowerShellScript script); public event ExternalScriptRunnerHandler ExternalScriptRunner; @@ -32,6 +35,21 @@ namespace LANCommander.SDK.Services _client = client; _logger = logger; } + + public void RegisterDebugHandler(PowerShellDebugHandler debugHandler) + { + _debugHandlers.Add(debugHandler); + } + + public void DeregisterDebugHandler(PowerShellDebugHandler debugHandler) + { + _debugHandlers.Remove(debugHandler); + } + + public PowerShellDebugHandler GetDebugHandler(Guid sessionId) + { + return _debugHandlers.FirstOrDefault(h => h.SessionId == sessionId); + } #region Authentication Scripts public async Task RunUserLoginScript(Script loginScript, User user, PowerShellDebugHandler debugHandler = null) diff --git a/LANCommander.Server/Hubs/Script.cs b/LANCommander.Server/Hubs/Script.cs index 0aead178..a12d2f62 100644 --- a/LANCommander.Server/Hubs/Script.cs +++ b/LANCommander.Server/Hubs/Script.cs @@ -25,10 +25,18 @@ public partial class RpcHub await Groups.AddToGroupAsync(Context.ConnectionId, GetScriptSessionGroupName(debugHandler.SessionId)); - await client.Scripts.RunPackageScriptAsync(mapper.Map(script), mapper.Map(game)); + await client.Scripts.RunPackageScriptAsync(mapper.Map(script), mapper.Map(game), debugHandler); } } + public async Task Script_ConsoleInputAsync(Guid sessionId, string input) + { + var debugHandler = client.Scripts.GetDebugHandler(sessionId); + + if (debugHandler != null) + await debugHandler.ExecuteAsync(input); + } + private void DebugStart(object? sender, OnDebugStartEventArgs args) { if (sender is PowerShellDebugHandler debugHandler) From 1f7db5c5df263144eed39c2c47c7ca362846369d Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Tue, 16 Sep 2025 23:51:11 -0500 Subject: [PATCH 3/4] Make DebugConsole component for remote and local script execution --- .../Extensions/IServiceProviderExtensions.cs | 5 ++++ .../Extensions/ServiceCollectionExtensions.cs | 8 ++++++ .../Components/LibraryItemContextMenu.razor | 10 ++++--- LANCommander.Launcher/UI/MainLayout.razor | 2 +- LANCommander.SDK/Factories/ScriptFactory.cs | 13 +++++++++ .../PowerShell/PowerShellScript.cs | 4 ++- LANCommander.SDK/Services/SaveService.cs | 2 +- LANCommander.SDK/Services/ScriptService.cs | 28 +++++++++---------- .../ServerEngines/DockerServerEngine.cs | 4 +-- .../ServerEngines/LocalServerEngine.cs | 4 +-- LANCommander.Server.Services/ServerService.cs | 7 +++-- LANCommander.Server/Hubs/Script.cs | 2 ++ .../DebugConsole/DebugConsole.razor | 10 +++++-- 13 files changed, 69 insertions(+), 30 deletions(-) create mode 100644 LANCommander.SDK/Factories/ScriptFactory.cs rename LANCommander.Launcher/UI/Components/PowerShellConsole.razor => LANCommander.UI/Components/DebugConsole/DebugConsole.razor (89%) diff --git a/LANCommander.Launcher.Services/Extensions/IServiceProviderExtensions.cs b/LANCommander.Launcher.Services/Extensions/IServiceProviderExtensions.cs index f7cba313..3f91824f 100644 --- a/LANCommander.Launcher.Services/Extensions/IServiceProviderExtensions.cs +++ b/LANCommander.Launcher.Services/Extensions/IServiceProviderExtensions.cs @@ -7,6 +7,7 @@ using System.Data.Common; using System.Diagnostics; using System.Runtime.InteropServices; using LANCommander.SDK; +using LANCommander.SDK.PowerShell; namespace LANCommander.Launcher.Services.Extensions { @@ -24,6 +25,10 @@ namespace LANCommander.Launcher.Services.Extensions var logger = scope.ServiceProvider.GetService(); var authenticationService = scope.ServiceProvider.GetService(); var keepAliveService = scope.ServiceProvider.GetService(); + var client = scope.ServiceProvider.GetService(); + var debugHandler = scope.ServiceProvider.GetService(); + + client.Scripts.RegisterDebugHandler(debugHandler); #region Scaffold Required Directories try diff --git a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs index 04cd0b56..5e9ed91d 100644 --- a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs +++ b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs @@ -14,6 +14,7 @@ using System.Runtime.InteropServices; using System.Security.Principal; using System.Text; using System.Threading.Tasks; +using LANCommander.SDK.PowerShell; namespace LANCommander.Launcher.Services.Extensions { @@ -81,6 +82,13 @@ namespace LANCommander.Launcher.Services.Extensions services.AddScoped(); services.AddScoped(); + var debugHandler = new PowerShellDebugHandler + { + SessionId = Guid.Empty, + }; + + services.AddSingleton(debugHandler); + return services; } diff --git a/LANCommander.Launcher/UI/Components/LibraryItemContextMenu.razor b/LANCommander.Launcher/UI/Components/LibraryItemContextMenu.razor index a5d8ae40..1eab244c 100644 --- a/LANCommander.Launcher/UI/Components/LibraryItemContextMenu.razor +++ b/LANCommander.Launcher/UI/Components/LibraryItemContextMenu.razor @@ -3,6 +3,7 @@ @using System.Diagnostics @using LANCommander.SDK @using LANCommander.SDK.Helpers +@using LANCommander.SDK.PowerShell @inject GameService GameService @inject UserService UserService @inject LibraryService LibraryService @@ -10,6 +11,7 @@ @inject ModalService ModalService @inject MessageBusService MessageBusService @inject SDK.Client Client +@inject PowerShellDebugHandler DebugHandler @inject LocalizationService LocalizationService @if (GameActions != null && GameActions.Count() > 0) @@ -278,7 +280,7 @@ else foreach (var manifest in manifests) { - await Client.Scripts.RunInstallScriptAsync(Game.InstallDirectory, manifest.Id); + await Client.Scripts.RunInstallScriptAsync(Game.InstallDirectory, manifest.Id, DebugHandler); } } @@ -288,7 +290,7 @@ else foreach (var manifest in manifests) { - await Client.Scripts.RunUninstallScriptAsync(Game.InstallDirectory, manifest.Id); + await Client.Scripts.RunUninstallScriptAsync(Game.InstallDirectory, manifest.Id, DebugHandler); } } @@ -299,7 +301,7 @@ else foreach (var manifest in manifests) { - await Client.Scripts.RunNameChangeScriptAsync(Game.InstallDirectory, Game.Id, user.GetUserNameSafe ?? Settings.DEFAULT_GAME_USERNAME); + await Client.Scripts.RunNameChangeScriptAsync(Game.InstallDirectory, Game.Id, user.GetUserNameSafe ?? Settings.DEFAULT_GAME_USERNAME, DebugHandler); } } @@ -311,7 +313,7 @@ else { var key = Client.Games.GetAllocatedKey(manifest.Id); - await Client.Scripts.RunKeyChangeScriptAsync(Game.InstallDirectory, Game.Id, key); + await Client.Scripts.RunKeyChangeScriptAsync(Game.InstallDirectory, Game.Id, key, DebugHandler); } } diff --git a/LANCommander.Launcher/UI/MainLayout.razor b/LANCommander.Launcher/UI/MainLayout.razor index bfcfb1f8..14baa83b 100644 --- a/LANCommander.Launcher/UI/MainLayout.razor +++ b/LANCommander.Launcher/UI/MainLayout.razor @@ -74,7 +74,7 @@ @if (Settings.Debug.EnableScriptDebugging) { - + } diff --git a/LANCommander.SDK/Factories/ScriptFactory.cs b/LANCommander.SDK/Factories/ScriptFactory.cs new file mode 100644 index 00000000..3329a6b9 --- /dev/null +++ b/LANCommander.SDK/Factories/ScriptFactory.cs @@ -0,0 +1,13 @@ +using LANCommander.SDK.Enums; +using LANCommander.SDK.PowerShell; +using LANCommander.SDK.Services; + +namespace LANCommander.SDK.Factories; + +public class ScriptFactory(ScriptService scriptService) +{ + public PowerShellScript Create(ScriptType type) + { + return new PowerShellScript(type, scriptService); + } +} \ No newline at end of file diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index cb68de41..6fcc741c 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -47,9 +47,11 @@ namespace LANCommander.SDK.PowerShell "; - public PowerShellScript(ScriptType type) + public PowerShellScript(ScriptType type, ScriptService scriptService) { Type = type; + ScriptService = scriptService; + Variables = new PowerShellVariableList(); Arguments = new Dictionary(); DebugHandler = new PowerShellDebugHandler(); diff --git a/LANCommander.SDK/Services/SaveService.cs b/LANCommander.SDK/Services/SaveService.cs index d39b3fb9..8cbde974 100644 --- a/LANCommander.SDK/Services/SaveService.cs +++ b/LANCommander.SDK/Services/SaveService.cs @@ -198,7 +198,7 @@ namespace LANCommander.SDK.Services { var registryImportFileContents = File.ReadAllText(registryImportFilePath); - var script = new PowerShellScript(Enums.ScriptType.SaveDownload); + var script = new PowerShellScript(Enums.ScriptType.SaveDownload, _client.Scripts); string adminArgument = string.Empty; if (registryImportFileContents.Contains("HKEY_LOCAL_MACHINE")) diff --git a/LANCommander.SDK/Services/ScriptService.cs b/LANCommander.SDK/Services/ScriptService.cs index 64fe1c9a..2540d271 100644 --- a/LANCommander.SDK/Services/ScriptService.cs +++ b/LANCommander.SDK/Services/ScriptService.cs @@ -58,7 +58,7 @@ namespace LANCommander.SDK.Services { using (var op = _logger.BeginOperation("Executing user login script")) { - var script = new PowerShellScript(Enums.ScriptType.UserLogin); + var script = new PowerShellScript(Enums.ScriptType.UserLogin, _client.Scripts); script.AddVariable("User", user); @@ -95,7 +95,7 @@ namespace LANCommander.SDK.Services { using (var op = _logger.BeginOperation("Executing user registration script")) { - var script = new PowerShellScript(Enums.ScriptType.UserRegistration); + var script = new PowerShellScript(Enums.ScriptType.UserRegistration, _client.Scripts); script.AddVariable("User", user); @@ -143,7 +143,7 @@ namespace LANCommander.SDK.Services { using (var op = _logger.BeginOperation("Executing install detection script")) { - var script = new PowerShellScript(Enums.ScriptType.DetectInstall); + var script = new PowerShellScript(Enums.ScriptType.DetectInstall, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -230,7 +230,7 @@ namespace LANCommander.SDK.Services { using (var op = _logger.BeginOperation("Executing install detection script")) { - var script = new PowerShellScript(Enums.ScriptType.Install); + var script = new PowerShellScript(Enums.ScriptType.Install, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -306,7 +306,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.BeforeStart); + var script = new PowerShellScript(Enums.ScriptType.BeforeStart, _client.Scripts); var playerAlias = await GameService.GetPlayerAliasAsync(installDirectory, gameId); if (Debug) @@ -389,7 +389,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.AfterStop); + var script = new PowerShellScript(Enums.ScriptType.AfterStop, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -482,7 +482,7 @@ namespace LANCommander.SDK.Services _logger?.LogTrace("New Name: {NewName}", newName); - var script = new PowerShellScript(Enums.ScriptType.NameChange); + var script = new PowerShellScript(Enums.ScriptType.NameChange, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -566,7 +566,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.Install); + var script = new PowerShellScript(Enums.ScriptType.Install, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -636,7 +636,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.Uninstall); + var script = new PowerShellScript(Enums.ScriptType.Uninstall, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -706,7 +706,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.BeforeStart); + var script = new PowerShellScript(Enums.ScriptType.BeforeStart, _client.Scripts); var playerAlias = GameService.GetPlayerAlias(installDirectory, gameId); if (Debug) @@ -779,7 +779,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.AfterStop); + var script = new PowerShellScript(Enums.ScriptType.AfterStop, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -861,7 +861,7 @@ namespace LANCommander.SDK.Services _logger?.LogTrace("New Name: {NewName}", newName); - var script = new PowerShellScript(Enums.ScriptType.NameChange); + var script = new PowerShellScript(Enums.ScriptType.NameChange, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -937,7 +937,7 @@ namespace LANCommander.SDK.Services { if (File.Exists(path)) { - var script = new PowerShellScript(Enums.ScriptType.KeyChange); + var script = new PowerShellScript(Enums.ScriptType.KeyChange, _client.Scripts); if (Debug) script.EnableDebug(debugHandler); @@ -1006,7 +1006,7 @@ namespace LANCommander.SDK.Services { using (var op = _logger.BeginOperation("Executing game package script")) { - var script = new PowerShellScript(Enums.ScriptType.Package); + var script = new PowerShellScript(Enums.ScriptType.Package, _client.Scripts); script.AddVariable("Game", game); diff --git a/LANCommander.Server.Services/ServerEngines/DockerServerEngine.cs b/LANCommander.Server.Services/ServerEngines/DockerServerEngine.cs index 3426edbe..f3d7b64c 100644 --- a/LANCommander.Server.Services/ServerEngines/DockerServerEngine.cs +++ b/LANCommander.Server.Services/ServerEngines/DockerServerEngine.cs @@ -106,7 +106,7 @@ public class DockerServerEngine( { try { - var script = new PowerShellScript(SDK.Enums.ScriptType.BeforeStart); + var script = new PowerShellScript(SDK.Enums.ScriptType.BeforeStart, client.Scripts); script.AddVariable("Server", mapper.Map(server)); @@ -159,7 +159,7 @@ public class DockerServerEngine( { try { - var script = new PowerShellScript(SDK.Enums.ScriptType.AfterStop); + var script = new PowerShellScript(SDK.Enums.ScriptType.AfterStop, client.Scripts); script.AddVariable("Server", mapper.Map(server)); diff --git a/LANCommander.Server.Services/ServerEngines/LocalServerEngine.cs b/LANCommander.Server.Services/ServerEngines/LocalServerEngine.cs index 4a83671c..c4e3fa22 100644 --- a/LANCommander.Server.Services/ServerEngines/LocalServerEngine.cs +++ b/LANCommander.Server.Services/ServerEngines/LocalServerEngine.cs @@ -81,7 +81,7 @@ public class LocalServerEngine( { try { - var script = new PowerShellScript(SDK.Enums.ScriptType.BeforeStart); + var script = new PowerShellScript(SDK.Enums.ScriptType.BeforeStart, client.Scripts); script.AddVariable("Server", mapper.Map(server)); @@ -179,7 +179,7 @@ public class LocalServerEngine( { try { - var script = new PowerShellScript(SDK.Enums.ScriptType.AfterStop); + var script = new PowerShellScript(SDK.Enums.ScriptType.AfterStop, client.Scripts); script.AddVariable("Server", mapper.Map(server)); diff --git a/LANCommander.Server.Services/ServerService.cs b/LANCommander.Server.Services/ServerService.cs index 97c43321..2a8d9910 100644 --- a/LANCommander.Server.Services/ServerService.cs +++ b/LANCommander.Server.Services/ServerService.cs @@ -21,7 +21,8 @@ namespace LANCommander.Server.Services IHttpContextAccessor httpContextAccessor, IDbContextFactory contextFactory, IServiceProvider serviceProvider, - UserService userService) : BaseDatabaseService(logger, cache, mapper, httpContextAccessor, contextFactory) + UserService userService, + SDK.Client client) : BaseDatabaseService(logger, cache, mapper, httpContextAccessor, contextFactory) { public override async Task AddAsync(Data.Models.Server entity) { @@ -118,7 +119,7 @@ namespace LANCommander.Server.Services { try { - var scriptContext = new PowerShellScript(ScriptType.GameStarted); + var scriptContext = new PowerShellScript(ScriptType.GameStarted, client.Scripts); scriptContext.AddVariable("Server", mapper.Map(server)); scriptContext.AddVariable("Game", mapper.Map(server.Game)); @@ -151,7 +152,7 @@ namespace LANCommander.Server.Services { try { - var scriptContext = new PowerShellScript(ScriptType.GameStopped); + var scriptContext = new PowerShellScript(ScriptType.GameStopped, client.Scripts); scriptContext.AddVariable("Server", mapper.Map(server)); scriptContext.AddVariable("Game", mapper.Map(server.Game)); diff --git a/LANCommander.Server/Hubs/Script.cs b/LANCommander.Server/Hubs/Script.cs index a12d2f62..ec718bc8 100644 --- a/LANCommander.Server/Hubs/Script.cs +++ b/LANCommander.Server/Hubs/Script.cs @@ -24,6 +24,8 @@ public partial class RpcHub debugHandler.OnDebugStart += DebugStart; await Groups.AddToGroupAsync(Context.ConnectionId, GetScriptSessionGroupName(debugHandler.SessionId)); + await Clients.Group(GetScriptSessionGroupName(debugHandler.SessionId)) + .Script_DebugStartAsync(debugHandler.SessionId); await client.Scripts.RunPackageScriptAsync(mapper.Map(script), mapper.Map(game), debugHandler); } diff --git a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor b/LANCommander.UI/Components/DebugConsole/DebugConsole.razor similarity index 89% rename from LANCommander.Launcher/UI/Components/PowerShellConsole.razor rename to LANCommander.UI/Components/DebugConsole/DebugConsole.razor index 7c814fa8..a74d5727 100644 --- a/LANCommander.Launcher/UI/Components/PowerShellConsole.razor +++ b/LANCommander.UI/Components/DebugConsole/DebugConsole.razor @@ -1,7 +1,9 @@ -@using LANCommander.SDK.PowerShell +@using LANCommander.SDK +@using LANCommander.SDK.PowerShell @using XtermBlazor @using LogLevel = Microsoft.Extensions.Logging.LogLevel -@inject SDK.Client Client +@inject Client Client +@namespace LANCommander.UI.Components
@@ -9,6 +11,7 @@ @code { [Parameter] public PowerShellDebugHandler DebugHandler { get; set; } + [Parameter] public Guid? SessionId { get; set; } private bool Visible { get; set; } = false; private Xterm Terminal; @@ -28,6 +31,9 @@ protected override void OnInitialized() { + if (SessionId != null) + DebugHandler = Client.Scripts.GetDebugHandler(SessionId.Value); + DebugHandler.OnDebugStart += OnDebugStart; DebugHandler.OnDebugBreak += OnDebugBreak; DebugHandler.OnDebugOutput += OnDebugOutput; From 3094b03746ac7b1a3ec41087157ac9dcf4f6531f Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Wed, 17 Sep 2025 00:39:10 -0500 Subject: [PATCH 4/4] Add event when debug stops --- .../PowerShell/PowerShellDebugHandler.cs | 17 +++++++++++++++++ LANCommander.SDK/PowerShell/PowerShellScript.cs | 1 - .../Components/DebugConsole/DebugConsole.razor | 11 ++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs index b9ecabf2..9b091b7b 100644 --- a/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs +++ b/LANCommander.SDK/PowerShell/PowerShellDebugHandler.cs @@ -11,6 +11,7 @@ public class PowerShellDebugHandler public event EventHandler OnDebugStart; public event EventHandler OnDebugBreak; public event EventHandler OnDebugOutput; + public event EventHandler OnDebugStop; private System.Management.Automation.PowerShell _powerShell; @@ -45,6 +46,17 @@ public class PowerShellDebugHandler }); } + internal void Stop(System.Management.Automation.PowerShell ps = null) + { + if (_powerShell == null) + _powerShell = ps; + + OnDebugStop?.Invoke(this, new OnDebugStopEventArgs + { + PowerShell = _powerShell, + }); + } + public async Task ExecuteAsync(string input) { if (input.StartsWith('$')) @@ -71,4 +83,9 @@ public class OnDebugOutputEventArgs : EventArgs { public LogLevel LogLevel { get; set; } public string Message { get; set; } +} + +public class OnDebugStopEventArgs : EventArgs +{ + public System.Management.Automation.PowerShell PowerShell { get; set; } } \ No newline at end of file diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index 6fcc741c..4c6af9f5 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -193,7 +193,6 @@ namespace LANCommander.SDK.PowerShell using (var ps = System.Management.Automation.PowerShell.Create()) { ps.Runspace = runspace; - if (Debug) DebugHandler.Start(ps); diff --git a/LANCommander.UI/Components/DebugConsole/DebugConsole.razor b/LANCommander.UI/Components/DebugConsole/DebugConsole.razor index a74d5727..1fc4c3cc 100644 --- a/LANCommander.UI/Components/DebugConsole/DebugConsole.razor +++ b/LANCommander.UI/Components/DebugConsole/DebugConsole.razor @@ -37,11 +37,11 @@ DebugHandler.OnDebugStart += OnDebugStart; DebugHandler.OnDebugBreak += OnDebugBreak; DebugHandler.OnDebugOutput += OnDebugOutput; + DebugHandler.OnDebugStop += OnDebugStop; } private async void OnDebugStart(object? sender, OnDebugStartEventArgs e) { - Terminal?.Clear(); Visible = true; await InvokeAsync(StateHasChanged); @@ -55,7 +55,10 @@ var input = await ReadLine(); if (input.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase)) + { + await Terminal.Clear(); break; + } await DebugHandler.ExecuteAsync(input); } @@ -90,6 +93,12 @@ } } + private async void OnDebugStop(object? sender, OnDebugStopEventArgs e) + { + await Terminal.Clear(); + Visible = false; + } + private async Task OnFirstRender() { await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");