Compare commits

...
Sign in to create a new pull request.

4 commits

Author SHA1 Message Date
Pat Hartl
3094b03746 Add event when debug stops 2025-09-17 00:39:10 -05:00
Pat Hartl
1f7db5c5df Make DebugConsole component for remote and local script execution 2025-09-16 23:51:11 -05:00
Pat Hartl
45f1b7b267 Handle debug input 2025-09-15 20:49:06 -05:00
Pat Hartl
c28f47b632 Create debug handler class to manage script debugging events 2025-09-10 02:06:53 -05:00
22 changed files with 480 additions and 336 deletions

View file

@ -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<ILogger>();
var authenticationService = scope.ServiceProvider.GetService<AuthenticationService>();
var keepAliveService = scope.ServiceProvider.GetService<KeepAliveService>();
var client = scope.ServiceProvider.GetService<SDK.Client>();
var debugHandler = scope.ServiceProvider.GetService<PowerShellDebugHandler>();
client.Scripts.RegisterDebugHandler(debugHandler);
#region Scaffold Required Directories
try

View file

@ -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<TagService>();
services.AddScoped<UpdateService>();
var debugHandler = new PowerShellDebugHandler
{
SessionId = Guid.Empty,
};
services.AddSingleton(debugHandler);
return services;
}

View file

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

View file

@ -1,95 +0,0 @@
@using XtermBlazor
@using LogLevel = Microsoft.Extensions.Logging.LogLevel
@inject SDK.Client Client
<div class="terminal @(Visible ? "" : "hidden")">
<Xterm @ref="Terminal" Options="_options" OnFirstRender="@OnFirstRender" Addons="@Addons" />
</div>
@code {
private bool Visible { get; set; } = false;
private Xterm Terminal;
private TaskCompletionSource<string> InputTaskCompletionSource;
private TerminalOptions _options = new TerminalOptions
{
CursorBlink = true,
CursorStyle = CursorStyle.Bar,
};
private HashSet<string> Addons = new HashSet<string>()
{
"readline",
"addon-fit"
};
protected override async Task OnInitializedAsync()
{
Client.Scripts.OnDebugStart = async (ps) =>
{
Terminal?.Clear();
Visible = true;
await InvokeAsync(StateHasChanged);
await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");
};
Client.Scripts.OnOutput = async (level, message) =>
{
switch (level)
{
case LogLevel.Error:
await Terminal.WriteLine($"\x1b[0;31m{message}");
break;
case LogLevel.Warning:
await Terminal.WriteLine($"\x1b[0;33m{message}");
break;
case LogLevel.Debug:
await Terminal.WriteLine($"\x1b[0;37m{message}");
break;
case LogLevel.Trace:
await Terminal.WriteLine($"\x1b[0;36m{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);
};
}
private async Task OnFirstRender()
{
await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");
}
private async Task<string> ReadLine()
{
return await Terminal.Addon("readline").InvokeAsync<string>("read", "> ");
}
}

View file

@ -74,7 +74,7 @@
@if (Settings.Debug.EnableScriptDebugging)
{
<PowerShellConsole/>
<DebugConsole SessionId="Guid.Empty" />
}
<KeepAliveContainer/>

View file

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

View file

@ -6,7 +6,86 @@ namespace LANCommander.SDK.PowerShell;
public class PowerShellDebugHandler
{
public Func<System.Management.Automation.PowerShell, Task> OnDebugStart;
public Func<System.Management.Automation.PowerShell, Task> OnDebugBreak;
public Func<LogLevel, string, Task> OnOutput;
public Guid SessionId { get; set; } = Guid.NewGuid();
public event EventHandler<OnDebugStartEventArgs> OnDebugStart;
public event EventHandler<OnDebugBreakEventArgs> OnDebugBreak;
public event EventHandler<OnDebugOutputEventArgs> OnDebugOutput;
public event EventHandler<OnDebugStopEventArgs> OnDebugStop;
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
});
}
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('$'))
input = $"Write-Host {input}";
_powerShell.Commands.Clear();
_powerShell.AddScript(input);
await _powerShell.InvokeAsync();
}
}
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; }
}
public class OnDebugStopEventArgs : EventArgs
{
public System.Management.Automation.PowerShell PowerShell { get; set; }
}

View file

@ -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;
@ -36,6 +37,8 @@ namespace LANCommander.SDK.PowerShell
public PowerShellDebugHandler DebugHandler { get; private set; }
private ScriptService ScriptService { get; set; }
private const string Logo = @"
__ ___ _ _______ __
/ / / _ | / |/ / ___/__ __ _ __ _ ___ ____ ___/ /__ ____
@ -44,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<string, string>();
DebugHandler = new PowerShellDebugHandler();
@ -146,8 +151,12 @@ namespace LANCommander.SDK.PowerShell
Debug = true;
if (debugHandler != null)
{
DebugHandler = debugHandler;
ScriptService.RegisterDebugHandler(debugHandler);
}
return this;
}
@ -186,7 +195,7 @@ namespace LANCommander.SDK.PowerShell
ps.Runspace = runspace;
if (Debug)
await (DebugHandler.OnDebugStart?.Invoke(ps) ?? Task.CompletedTask);
DebugHandler.Start(ps);
ps.AddScript("Write-Host $Logo");
@ -207,20 +216,17 @@ 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
{
@ -239,6 +245,9 @@ namespace LANCommander.SDK.PowerShell
if (IgnoreWow64)
Wow64RevertWow64FsRedirection(ref wow64Value);
if (DebugHandler != null)
ScriptService.DeregisterDebugHandler(DebugHandler);
return result;
}
@ -246,29 +255,29 @@ namespace LANCommander.SDK.PowerShell
{
var record = ((PSDataCollection<ErrorRecord>)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<WarningRecord>)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<DebugRecord>)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<VerboseRecord>)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 +285,7 @@ namespace LANCommander.SDK.PowerShell
var record = ((PSDataCollection<InformationRecord>)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>(T input)

View file

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

View file

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

View file

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

View file

@ -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();
}
}

View file

@ -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<Guid, PowerShellDebugHandler> _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();
}
}

View file

@ -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"))
@ -210,12 +210,7 @@ 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<int>();
}

View file

@ -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,15 +18,13 @@ namespace LANCommander.SDK.Services
private readonly Client _client;
private List<PowerShellDebugHandler> _debugHandlers = new();
public delegate Task<bool> ExternalScriptRunnerHandler(PowerShellScript script);
public event ExternalScriptRunnerHandler ExternalScriptRunner;
public bool Debug { get; set; } = false;
public Func<System.Management.Automation.PowerShell, Task> OnDebugStart;
public Func<System.Management.Automation.PowerShell, Task> OnDebugBreak;
public Func<LogLevel, string, Task> OnOutput;
public ScriptService(Client client)
{
_client = client;
@ -37,17 +36,35 @@ namespace LANCommander.SDK.Services
_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)
public async Task RunUserLoginScript(Script loginScript, User user, PowerShellDebugHandler debugHandler = null)
{
try
{
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);
if (Debug)
script.EnableDebug(debugHandler);
script.UseInline(loginScript.Contents);
try
@ -72,16 +89,19 @@ namespace LANCommander.SDK.Services
}
}
public async Task RunUserRegistrationScript(Script registrationScript, User user)
public async Task RunUserRegistrationScript(Script registrationScript, User user, PowerShellDebugHandler debugHandler = null)
{
try
{
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);
if (Debug)
script.EnableDebug(debugHandler);
script.UseInline(registrationScript.Contents);
try
@ -108,7 +128,7 @@ namespace LANCommander.SDK.Services
#endregion
#region Redistributables
public async Task<bool> RunDetectInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId)
public async Task<bool> RunDetectInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null)
{
bool result = default;
@ -123,10 +143,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", gameManifest);
@ -161,20 +181,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 +200,8 @@ namespace LANCommander.SDK.Services
}
}
}
result = await script.ExecuteAsync<bool>();
result = await script.ExecuteAsync<bool>();
op.Complete();
}
@ -208,7 +215,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId)
public async Task<int> RunInstallScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -223,10 +230,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", gameManifest);
@ -264,20 +271,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 +291,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunBeforeStartScriptAsync(string installDirectory, Guid gameId, Guid redistributableId)
public async Task<int> RunBeforeStartScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -313,11 +306,11 @@ 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)
script.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", gameManifest);
@ -357,20 +350,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 +374,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunAfterStopScriptAsync(string installDirectory, Guid gameId, Guid redistributableId)
public async Task<int> RunAfterStopScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -410,10 +389,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", gameManifest);
@ -453,20 +432,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 +457,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunNameChangeScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, string newName)
public async Task<int> RunNameChangeScriptAsync(string installDirectory, Guid gameId, Guid redistributableId, string newName, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -517,10 +482,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", gameManifest);
@ -561,20 +526,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 +553,7 @@ namespace LANCommander.SDK.Services
#endregion
#region Games
public async Task<int> RunInstallScriptAsync(string installDirectory, Guid gameId)
public async Task<int> RunInstallScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -615,10 +566,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
@ -648,20 +599,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 +623,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunUninstallScriptAsync(string installDirectory, Guid gameId)
public async Task<int> RunUninstallScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -699,10 +636,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
@ -732,20 +669,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 +693,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunBeforeStartScriptAsync(string installDirectory, Guid gameId)
public async Task<int> RunBeforeStartScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -783,11 +706,11 @@ 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)
script.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
@ -819,20 +742,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 +766,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunAfterStopScriptAsync(string installDirectory, Guid gameId)
public async Task<int> RunAfterStopScriptAsync(string installDirectory, Guid gameId, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -870,10 +779,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
@ -904,20 +813,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 +838,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunNameChangeScriptAsync(string installDirectory, Guid gameId, string newName)
public async Task<int> RunNameChangeScriptAsync(string installDirectory, Guid gameId, string newName, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -966,10 +861,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
script.AddVariable("InstallDirectory", installDirectory);
script.AddVariable("GameManifest", manifest);
@ -1005,20 +900,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 +924,7 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<int> RunKeyChangeScriptAsync(string installDirectory, Guid gameId, string key)
public async Task<int> RunKeyChangeScriptAsync(string installDirectory, Guid gameId, string key, PowerShellDebugHandler debugHandler = null)
{
int result = default;
@ -1056,10 +937,10 @@ 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.DebugHandler.OnDebugStart = OnDebugStart;
script.EnableDebug(debugHandler);
_logger?.LogTrace("New key is {Key}", key);
@ -1095,20 +976,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,18 +1000,21 @@ namespace LANCommander.SDK.Services
return result;
}
public async Task<Package> RunPackageScriptAsync(Script packageScript, Game game)
public async Task<Package> RunPackageScriptAsync(Script packageScript, Game game, PowerShellDebugHandler debugHandler = null)
{
try
{
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);
script.UseInline(packageScript.Contents);
if (Debug)
script.EnableDebug(debugHandler);
try
{
op

View file

@ -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<SDK.Models.Server>(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<SDK.Models.Server>(server));

View file

@ -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<SDK.Models.Server>(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<SDK.Models.Server>(server));

View file

@ -21,7 +21,8 @@ namespace LANCommander.Server.Services
IHttpContextAccessor httpContextAccessor,
IDbContextFactory<DatabaseContext> contextFactory,
IServiceProvider serviceProvider,
UserService userService) : BaseDatabaseService<Data.Models.Server>(logger, cache, mapper, httpContextAccessor, contextFactory)
UserService userService,
SDK.Client client) : BaseDatabaseService<Data.Models.Server>(logger, cache, mapper, httpContextAccessor, contextFactory)
{
public override async Task<Data.Models.Server> 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<SDK.Models.Server>(server));
scriptContext.AddVariable("Game", mapper.Map<SDK.Models.Game>(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<SDK.Models.Server>(server));
scriptContext.AddVariable("Game", mapper.Map<SDK.Models.Game>(server.Game));

View file

@ -0,0 +1,6 @@
namespace LANCommander.Server.Hubs;
public partial class RpcHub
{
}

View file

@ -0,0 +1,59 @@
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<Guid, Script> _scripts = new Dictionary<Guid, Script>();
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 Clients.Group(GetScriptSessionGroupName(debugHandler.SessionId))
.Script_DebugStartAsync(debugHandler.SessionId);
await client.Scripts.RunPackageScriptAsync(mapper.Map<SDK.Models.Script>(script), mapper.Map<SDK.Models.Game>(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)
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);
}
}

View file

@ -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<IRpcClient>, IRpcHub
{

View file

@ -0,0 +1,111 @@
@using LANCommander.SDK
@using LANCommander.SDK.PowerShell
@using XtermBlazor
@using LogLevel = Microsoft.Extensions.Logging.LogLevel
@inject Client Client
@namespace LANCommander.UI.Components
<div class="terminal @(Visible ? "" : "hidden")">
<Xterm @ref="Terminal" Options="_options" OnFirstRender="@OnFirstRender" Addons="@Addons" />
</div>
@code {
[Parameter] public PowerShellDebugHandler DebugHandler { get; set; }
[Parameter] public Guid? SessionId { get; set; }
private bool Visible { get; set; } = false;
private Xterm Terminal;
private TaskCompletionSource<string> InputTaskCompletionSource;
private TerminalOptions _options = new()
{
CursorBlink = true,
CursorStyle = CursorStyle.Bar,
};
private HashSet<string> Addons = new()
{
"readline",
"addon-fit"
};
protected override void OnInitialized()
{
if (SessionId != null)
DebugHandler = Client.Scripts.GetDebugHandler(SessionId.Value);
DebugHandler.OnDebugStart += OnDebugStart;
DebugHandler.OnDebugBreak += OnDebugBreak;
DebugHandler.OnDebugOutput += OnDebugOutput;
DebugHandler.OnDebugStop += OnDebugStop;
}
private async void OnDebugStart(object? sender, OnDebugStartEventArgs e)
{
Visible = true;
await InvokeAsync(StateHasChanged);
await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");
}
private async void OnDebugBreak(object? sender, OnDebugBreakEventArgs e)
{
while (true)
{
var input = await ReadLine();
if (input.Trim().Equals("exit", StringComparison.OrdinalIgnoreCase))
{
await Terminal.Clear();
break;
}
await DebugHandler.ExecuteAsync(input);
}
Visible = false;
await InvokeAsync(StateHasChanged);
}
private async void OnDebugOutput(object? sender, OnDebugOutputEventArgs e)
{
switch (e.LogLevel)
{
case LogLevel.Error:
await Terminal.WriteLine($"\x1b[0;31m{e.Message}");
break;
case LogLevel.Warning:
await Terminal.WriteLine($"\x1b[0;33m{e.Message}");
break;
case LogLevel.Debug:
await Terminal.WriteLine($"\x1b[0;37m{e.Message}");
break;
case LogLevel.Trace:
await Terminal.WriteLine($"\x1b[0;36m{e.Message}");
break;
case LogLevel.Information:
await Terminal.WriteLine($"\x1b[0;37m{e.Message}");
break;
}
}
private async void OnDebugStop(object? sender, OnDebugStopEventArgs e)
{
await Terminal.Clear();
Visible = false;
}
private async Task OnFirstRender()
{
await Terminal.Addon("addon-fit").InvokeVoidAsync("fit");
}
private async Task<string> ReadLine()
{
return await Terminal.Addon("readline").InvokeAsync<string>("read", "> ");
}
}