diff --git a/schema/v1/ScriptRunnerSchema.json b/schema/v1/ScriptRunnerSchema.json index 2db5e25..6fad487 100644 --- a/schema/v1/ScriptRunnerSchema.json +++ b/schema/v1/ScriptRunnerSchema.json @@ -28,6 +28,9 @@ "command": { "type": "string" }, + "useSystemShell": { + "type": "boolean" + }, "runCommandAsAdmin":{ "type":"boolean" }, diff --git a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs index b80e029..fce31e5 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ScriptConfigs/ScriptConfig.cs @@ -14,6 +14,7 @@ public class ScriptConfig public string? Description { get; set; } public string Command { get; set; } public bool RunCommandAsAdmin { get; set; } + public bool UseSystemShell { get; set; } public string Docs { get; set; } public string DocsContent { get; set; } = string.Empty; public string DocAssetPath { get; set; } diff --git a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs index 560a76c..2a93b58 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/MainWindowViewModel.cs @@ -503,7 +503,7 @@ private void RenderParameterForm(ScriptConfig action, Dictionary var taskCompletionSource = new TaskCompletionSource(); try { - ExecuteCommand(command, this.SelectedAction, title, s => + ExecuteCommand(command, this.SelectedAction, useSystemShell:false, title, s => { taskCompletionSource.SetResult(s); }); @@ -858,7 +858,7 @@ public void RunScript() var selectedActionCommand = selectedAction.Command; - ExecuteCommand(selectedActionCommand, selectedAction); + ExecuteCommand(selectedActionCommand, selectedAction, selectedAction.UseSystemShell); // Some audit staff var usedParams = HarvestCurrentParameters(vaultPrefixForNewEntries: $"{selectedAction.Name}_{Guid.NewGuid():N}"); @@ -870,7 +870,7 @@ public void RunScript() } - private void ExecuteCommand(string command, ScriptConfig selectedAction, string? title = null, Action? onComplete = null) + private void ExecuteCommand(string command, ScriptConfig selectedAction, bool useSystemShell, string? title = null, Action? onComplete = null) { var (commandPath, args) = SplitCommandAndArgs(command); var envVariables = new Dictionary(selectedAction.EnvironmentVariables); @@ -902,7 +902,7 @@ private void ExecuteCommand(string command, ScriptConfig selectedAction, string? job.ExecutionCompleted += (sender, args) => onComplete(job.RawOutput); } - job.RunJob(commandPath, args, selectedAction.WorkingDirectory, selectedAction.InteractiveInputs, selectedAction.Troubleshooting); + job.RunJob(commandPath, args, selectedAction.WorkingDirectory, selectedAction.InteractiveInputs, selectedAction.Troubleshooting, useSystemShell); } public ObservableCollection ExecutionLog { get; set; } = new (); diff --git a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/RunningJobViewModel.cs b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/RunningJobViewModel.cs index 017821b..34a4e44 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/ViewModels/RunningJobViewModel.cs +++ b/src/ScriptRunner/ScriptRunner.GUI/ViewModels/RunningJobViewModel.cs @@ -94,7 +94,9 @@ public void Dispose() public void RaiseExecutionCompleted() => ExecutionCompleted?.Invoke(this, EventArgs.Empty); private IReadOnlyList _inputs = new List(); public void RunJob(string commandPath, string args, string? workingDirectory, - IReadOnlyList interactiveInputs, IReadOnlyList troubleshooting) + IReadOnlyList interactiveInputs, + IReadOnlyList troubleshooting, + bool useSystemShell = false) { _inputs = interactiveInputs; @@ -115,24 +117,63 @@ public void RunJob(string commandPath, string args, string? workingDirectory, GracefulCancellation = new CancellationTokenSource(); KillCancellation = new CancellationTokenSource(); ChangeStatus(RunningJobStatus.Running); - await Cli.Wrap(commandPath) - .WithArguments(args) - //TODO: Working dir should be read from the config with the fallback set to the config file dir - .WithWorkingDirectory(workingDirectory ?? "Scripts/") - .WithStandardInputPipe(PipeSource.FromStream(inputStream,autoFlush:true)) - .WithStandardOutputPipe(PipeTarget.ToDelegate(s => + + if (useSystemShell) + { + var processStartInfo = new ProcessStartInfo() { - rawOutput.AppendLine(s); - AppendToOutput(s, ConsoleOutputLevel.Normal); - })) - .WithStandardErrorPipe(PipeTarget.ToDelegate(s => + FileName = commandPath, + Arguments = args, + WorkingDirectory = workingDirectory, + UseShellExecute = true, + RedirectStandardInput = false, + RedirectStandardOutput = false, + RedirectStandardError = false + }; + + if (EnvironmentVariables != null) { - rawErrorOutput.Append(s); - AppendToOutput(s, ConsoleOutputLevel.Error); - })) - .WithValidation(CommandResultValidation.None) - .WithEnvironmentVariables(EnvironmentVariables ?? new()) - .ExecuteAsync(KillCancellation.Token, GracefulCancellation.Token); + foreach (var o in EnvironmentVariables) + { + processStartInfo.EnvironmentVariables[o.Key] = o.Value; + } + } + var p = Process.Start(processStartInfo); + try + { + if (p != null) + { + await p.WaitForExitAsync(GracefulCancellation.Token); + } + } + finally + { + if(p.HasExited == false) + p.Kill(); + } + } + else + { + await Cli.Wrap(commandPath) + .WithArguments(args) + //TODO: Working dir should be read from the config with the fallback set to the config file dir + .WithWorkingDirectory(workingDirectory ?? "Scripts/") + .WithStandardInputPipe(PipeSource.FromStream(inputStream,autoFlush:true)) + .WithStandardOutputPipe(PipeTarget.ToDelegate(s => + { + rawOutput.AppendLine(s); + AppendToOutput(s, ConsoleOutputLevel.Normal); + })) + .WithStandardErrorPipe(PipeTarget.ToDelegate(s => + { + rawErrorOutput.Append(s); + AppendToOutput(s, ConsoleOutputLevel.Error); + })) + .WithValidation(CommandResultValidation.None) + .WithEnvironmentVariables(EnvironmentVariables ?? new()) + .ExecuteAsync(KillCancellation.Token, GracefulCancellation.Token); + } + ChangeStatus(RunningJobStatus.Finished); } catch (Exception e) @@ -424,13 +465,13 @@ private void AppendToUiOutput(IList s) foreach (var part in s.SelectMany(x=>x.Split("\r\n")).TakeLast(OutputBufferSize)) { var subParts = ConsoleSpecialCharsPattern.Split(part); - if (part.Contains("http://") || part.Contains("https://")) + if (part.Contains("http://", StringComparison.OrdinalIgnoreCase) || part.Contains("https://", StringComparison.OrdinalIgnoreCase)) { subParts = subParts.SelectMany(x => urlPattern.Split(x)).ToArray(); } foreach (var chunk in subParts.Where(x=> x != string.Empty)) { - if (chunk.StartsWith("http://") || chunk.StartsWith("https://")) + if (chunk.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || chunk.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { _outputElements.Add(new Link(chunk)); continue; @@ -443,7 +484,7 @@ private void AppendToUiOutput(IList s) subPart = subPart.Replace(";3m", "m"); } - if (subPart.StartsWith("\u001b[")) + if (subPart.StartsWith("\u001b[", StringComparison.Ordinal)) { var foreground = subPart switch { diff --git a/src/ScriptRunner/ScriptRunner.GUI/Views/ActionDetailsSection.axaml b/src/ScriptRunner/ScriptRunner.GUI/Views/ActionDetailsSection.axaml index 777b014..5862f09 100644 --- a/src/ScriptRunner/ScriptRunner.GUI/Views/ActionDetailsSection.axaml +++ b/src/ScriptRunner/ScriptRunner.GUI/Views/ActionDetailsSection.axaml @@ -185,7 +185,7 @@ Compacted - +