From 85e47f5d3a5836fcda0bb0945eda2ea82919f22e Mon Sep 17 00:00:00 2001 From: Pat Hartl Date: Thu, 8 Aug 2024 18:00:41 -0500 Subject: [PATCH] Run scripts as admin using CLI elevation --- .../Extensions/ServiceCollectionExtensions.cs | 53 +++++++++++++- .../PowerShell/Cmdlets/Install-Game.cs | 6 +- .../PowerShell/Cmdlets/Uninstall-Game.cs | 2 +- .../PowerShell/PowerShellScript.cs | 40 +++++----- .../PowerShell/PowerShellVariable.cs | 11 +++ LANCommander.SDK/RedistributableService.cs | 5 +- LANCommander.SDK/SaveService.cs | 6 +- LANCommander.SDK/ScriptService.cs | 73 ++++++++++++++----- 8 files changed, 150 insertions(+), 46 deletions(-) diff --git a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs index c0e02834..a7e2918f 100644 --- a/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs +++ b/LANCommander.Launcher.Services/Extensions/ServiceCollectionExtensions.cs @@ -1,8 +1,13 @@ -using LANCommander.Launcher.Data; +using CommandLine; +using LANCommander.Launcher.Data; +using LANCommander.Launcher.Models; +using LANCommander.SDK; +using LANCommander.SDK.Enums; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -26,6 +31,8 @@ namespace LANCommander.Launcher.Services.Extensions RefreshToken = settings.Authentication.RefreshToken, }); + client.Scripts.ExternalScriptRunner += Scripts_ExternalScriptRunner; + services.AddSingleton(client); services.AddSingleton(); #endregion @@ -50,5 +57,49 @@ namespace LANCommander.Launcher.Services.Extensions return services; } + + private static async Task Scripts_ExternalScriptRunner(SDK.PowerShell.PowerShellScript script) + { + if (script.RunAsAdmin) + { + var manifest = script.Variables.GetValue("GameManifest"); + + var options = new RunScriptCommandLineOptions + { + InstallDirectory = script.Variables.GetValue("InstallDirectory"), + GameId = manifest.Id, + Type = script.Type + }; + + if (script.Type == ScriptType.KeyChange) + options.AllocatedKey = script.Variables.GetValue("AllocatedKey"); + + if (script.Type == ScriptType.NameChange) + { + options.OldPlayerAlias = script.Variables.GetValue("OldPlayerAlias"); + options.NewPlayerAlias = script.Variables.GetValue("NewPlayerAlias"); + } + + var arguments = Parser.Default.FormatCommandLine(options); + + var path = Process.GetCurrentProcess().MainModule.FileName; + + var process = new Process(); + + process.StartInfo.FileName = path; + process.StartInfo.Verb = "runas"; + process.StartInfo.UseShellExecute = true; + process.StartInfo.WorkingDirectory = script.WorkingDirectory; + process.StartInfo.Arguments = arguments; + + process.Start(); + + await process.WaitForExitAsync(); + + return true; + } + + return false; + } } } diff --git a/LANCommander.SDK/PowerShell/Cmdlets/Install-Game.cs b/LANCommander.SDK/PowerShell/Cmdlets/Install-Game.cs index 44d57624..d3b134fe 100644 --- a/LANCommander.SDK/PowerShell/Cmdlets/Install-Game.cs +++ b/LANCommander.SDK/PowerShell/Cmdlets/Install-Game.cs @@ -63,7 +63,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets if (File.Exists(path)) { - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.Install); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -86,7 +86,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets if (File.Exists(path)) { - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.NameChange); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -110,7 +110,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets if (File.Exists(path)) { - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.KeyChange); var key = Client.Games.GetAllocatedKey(manifest.Id); diff --git a/LANCommander.SDK/PowerShell/Cmdlets/Uninstall-Game.cs b/LANCommander.SDK/PowerShell/Cmdlets/Uninstall-Game.cs index d19a8ee4..e8946ffb 100644 --- a/LANCommander.SDK/PowerShell/Cmdlets/Uninstall-Game.cs +++ b/LANCommander.SDK/PowerShell/Cmdlets/Uninstall-Game.cs @@ -29,7 +29,7 @@ namespace LANCommander.SDK.PowerShell.Cmdlets if (!String.IsNullOrEmpty(scriptPath) && File.Exists(scriptPath)) { var manifest = ManifestHelper.Read(InstallDirectory, Id); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.Uninstall); script.AddVariable("InstallDirectory", InstallDirectory); script.AddVariable("GameManifest", manifest); diff --git a/LANCommander.SDK/PowerShell/PowerShellScript.cs b/LANCommander.SDK/PowerShell/PowerShellScript.cs index 5ec3eab3..b3ef6d9b 100644 --- a/LANCommander.SDK/PowerShell/PowerShellScript.cs +++ b/LANCommander.SDK/PowerShell/PowerShellScript.cs @@ -1,4 +1,5 @@ -using LANCommander.SDK.Helpers; +using LANCommander.SDK.Enums; +using LANCommander.SDK.Helpers; using LANCommander.SDK.PowerShell.Cmdlets; using System; using System.Collections.Generic; @@ -17,20 +18,22 @@ namespace LANCommander.SDK.PowerShell { public class PowerShellScript { + public ScriptType Type { get; private set; } private string Contents { get; set; } = ""; - private string WorkingDirectory { get; set; } = ""; - private bool AsAdmin { get; set; } = false; + public string WorkingDirectory { get; private set; } = ""; private bool ShellExecute { get; set; } = false; + public bool RunAsAdmin { get; private set; } = false; private bool IgnoreWow64 { get; set; } = false; private bool Debug { get; set; } = false; - private ICollection Variables { get; set; } - private Dictionary Arguments { get; set; } + public PowerShellVariableList Variables { get; private set; } + public Dictionary Arguments { get; private set; } private InitialSessionState InitialSessionState { get; set; } - public PowerShellScript() + public PowerShellScript(ScriptType type) { - Variables = new List(); + Type = type; + Variables = new PowerShellVariableList(); Arguments = new Dictionary(); InitialSessionState = InitialSessionState.CreateDefault(); @@ -55,6 +58,9 @@ namespace LANCommander.SDK.PowerShell { Contents = File.ReadAllText(path); + if (Contents.StartsWith("# Requires Admin")) + RunAsAdmin = true; + return this; } @@ -62,6 +68,9 @@ namespace LANCommander.SDK.PowerShell { Contents = contents; + if (Contents.StartsWith("# Requires Admin")) + RunAsAdmin = true; + return this; } @@ -107,13 +116,6 @@ namespace LANCommander.SDK.PowerShell return this; } - public PowerShellScript RunAsAdmin() - { - AsAdmin = true; - - return this; - } - public PowerShellScript IgnoreWow64Redirection() { IgnoreWow64 = true; @@ -128,15 +130,19 @@ namespace LANCommander.SDK.PowerShell return this; } + public PowerShellScript AsAdmin() + { + RunAsAdmin = true; + + return this; + } + public async Task ExecuteAsync() { var scriptBuilder = new StringBuilder(); var wow64Value = IntPtr.Zero; - if (Contents.StartsWith("# Requires Admin")) - RunAsAdmin(); - foreach (var variable in Variables) { scriptBuilder.AppendLine($"${variable.Name} = ConvertFrom-SerializedBase64 \"{Serialize(variable.Value)}\""); diff --git a/LANCommander.SDK/PowerShell/PowerShellVariable.cs b/LANCommander.SDK/PowerShell/PowerShellVariable.cs index a7f78b8d..eafe2d55 100644 --- a/LANCommander.SDK/PowerShell/PowerShellVariable.cs +++ b/LANCommander.SDK/PowerShell/PowerShellVariable.cs @@ -1,9 +1,20 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace LANCommander.SDK.PowerShell { + public class PowerShellVariableList : List + { + public T GetValue(string variableName) + { + var variable = this.FirstOrDefault(v => v.Name == variableName); + + return (T)variable.Value; + } + } + public class PowerShellVariable { public string Name { get; set; } diff --git a/LANCommander.SDK/RedistributableService.cs b/LANCommander.SDK/RedistributableService.cs index 42f7e9a8..93c566b5 100644 --- a/LANCommander.SDK/RedistributableService.cs +++ b/LANCommander.SDK/RedistributableService.cs @@ -191,16 +191,13 @@ namespace LANCommander.SDK private async Task RunScriptAsync(string path, Redistributable redistributable, bool requiresAdmin = false, string workingDirectory = "") { - var script = new PowerShellScript(); + var script = new PowerShellScript(ScriptType.Install); script.AddVariable("Redistributable", redistributable); script.UseWorkingDirectory(workingDirectory); script.UseFile(path); - if (requiresAdmin) - script.RunAsAdmin(); - return await script.ExecuteAsync(); } } diff --git a/LANCommander.SDK/SaveService.cs b/LANCommander.SDK/SaveService.cs index cb30e3e5..185c7fbf 100644 --- a/LANCommander.SDK/SaveService.cs +++ b/LANCommander.SDK/SaveService.cs @@ -195,12 +195,12 @@ namespace LANCommander.SDK { var registryImportFileContents = File.ReadAllText(registryImportFilePath); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.Install); script.UseInline($"regedit.exe /s \"{registryImportFilePath}\""); if (registryImportFileContents.Contains("HKEY_LOCAL_MACHINE")) - script.RunAsAdmin(); + script.AsAdmin(); await script.ExecuteAsync(); } @@ -267,7 +267,7 @@ namespace LANCommander.SDK tempRegFiles.Add(tempRegFile); } - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.SaveUpload); script.UseInline(exportCommand.ToString()); diff --git a/LANCommander.SDK/ScriptService.cs b/LANCommander.SDK/ScriptService.cs index 953dd2a7..4a813d59 100644 --- a/LANCommander.SDK/ScriptService.cs +++ b/LANCommander.SDK/ScriptService.cs @@ -19,6 +19,9 @@ namespace LANCommander.SDK private readonly Client Client; + public delegate Task ExternalScriptRunnerHandler(PowerShellScript script); + public event ExternalScriptRunnerHandler ExternalScriptRunner; + public ScriptService(Client client) { Client = client; @@ -41,7 +44,7 @@ namespace LANCommander.SDK { Logger?.LogTrace("Running install script for game {GameTitle} ({gameId})", manifest.Title, gameId); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.Install); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -53,7 +56,13 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - return await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } Logger?.LogTrace("No install script found for game {GameTitle} ({gameId})", manifest.Title, gameId); @@ -66,16 +75,18 @@ namespace LANCommander.SDK return 0; } - public async Task RunUninstallScriptAsync(string installDirectory, Guid gameId, bool debug = false) + public async Task RunUninstallScriptAsync(string installDirectory, Guid gameId, bool debug = false) { try { var manifest = ManifestHelper.Read(installDirectory, gameId); var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, Enums.ScriptType.Uninstall); + var contents = await File.ReadAllTextAsync(path); + if (File.Exists(path)) { - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.Uninstall); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -86,17 +97,21 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - return await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } Logger?.LogTrace("No uninstall script found for game {GameTitle} ({gameId})", manifest.Title, gameId); } catch (Exception ex) { - Logger?.LogError(ex, "Ran into an unexpected error when attempting to run an Uninstall script"); + Logger?.LogError(ex, "Ran into an unexpected error when attempting to get an Uninstall script"); } - - return 0; } public async Task RunBeforeStartScriptAsync(string installDirectory, Guid gameId, bool debug = false) @@ -110,7 +125,7 @@ namespace LANCommander.SDK { var manifest = ManifestHelper.Read(installDirectory, gameId); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.BeforeStart); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -123,7 +138,13 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } } catch (Exception ex) @@ -142,7 +163,7 @@ namespace LANCommander.SDK { var manifest = ManifestHelper.Read(installDirectory, gameId); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.AfterStop); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -155,7 +176,13 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } } catch (Exception ex) @@ -182,7 +209,7 @@ namespace LANCommander.SDK Logger?.LogTrace("New Name: {NewName}", newName); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.NameChange); script.AddVariable("InstallDirectory", installDirectory); script.AddVariable("GameManifest", manifest); @@ -198,7 +225,13 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } } catch (Exception ex) @@ -211,14 +244,14 @@ namespace LANCommander.SDK { try { - var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, SDK.Enums.ScriptType.NameChange); + var path = ScriptHelper.GetScriptFilePath(installDirectory, gameId, SDK.Enums.ScriptType.KeyChange); var manifest = ManifestHelper.Read(installDirectory, gameId); if (File.Exists(path)) { Logger?.LogTrace("Running key change script for game {GameTitle} ({gameId})", manifest.Title, gameId); - var script = new PowerShellScript(); + var script = new PowerShellScript(Enums.ScriptType.KeyChange); Logger?.LogTrace("New key is {Key}", key); @@ -235,7 +268,13 @@ namespace LANCommander.SDK if (debug) script.EnableDebug(); - await script.ExecuteAsync(); + bool handled = false; + + if (ExternalScriptRunner != null) + handled = await ExternalScriptRunner.Invoke(script); + + if (!handled) + await script.ExecuteAsync(); } } catch (Exception ex)