using System.Diagnostics; namespace Plandent.MstscScriptHook.Configurator; internal static class ScriptTestLauncher { public static Process Start(HookConfig config) { string scriptPath = Environment.ExpandEnvironmentVariables(config.ScriptPath ?? string.Empty); if (string.IsNullOrWhiteSpace(scriptPath) || !File.Exists(scriptPath)) { throw new FileNotFoundException("Das ausgewählte Script wurde nicht gefunden.", scriptPath); } string extension = Path.GetExtension(scriptPath).ToLowerInvariant(); string workingDirectory = string.IsNullOrWhiteSpace(config.WorkingDirectory) ? Path.GetDirectoryName(scriptPath) ?? Environment.CurrentDirectory : Environment.ExpandEnvironmentVariables(config.WorkingDirectory); var startInfo = new ProcessStartInfo { UseShellExecute = false, CreateNoWindow = config.Hidden, WindowStyle = config.Hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal, WorkingDirectory = workingDirectory }; switch (extension) { case ".bat": case ".cmd": startInfo.FileName = Environment.GetEnvironmentVariable("ComSpec") ?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "cmd.exe"); startInfo.ArgumentList.Add("/D"); startInfo.ArgumentList.Add("/S"); startInfo.ArgumentList.Add("/C"); startInfo.ArgumentList.Add(BuildCmdCommand(scriptPath, config.Arguments)); break; case ".ps1": startInfo.FileName = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.System), "WindowsPowerShell", "v1.0", "powershell.exe"); startInfo.ArgumentList.Add("-NoLogo"); startInfo.ArgumentList.Add("-NoProfile"); startInfo.ArgumentList.Add("-NonInteractive"); startInfo.ArgumentList.Add("-ExecutionPolicy"); startInfo.ArgumentList.Add("Bypass"); startInfo.ArgumentList.Add("-File"); startInfo.ArgumentList.Add(scriptPath); AddRawArguments(startInfo, config.Arguments); break; case ".exe": startInfo.FileName = scriptPath; AddRawArguments(startInfo, config.Arguments); break; default: throw new NotSupportedException("Unterstützt werden .bat, .cmd, .ps1 und .exe."); } return Process.Start(startInfo) ?? throw new InvalidOperationException("Der Testprozess konnte nicht gestartet werden."); } private static string BuildCmdCommand(string scriptPath, string? arguments) { string command = QuoteForCmd(scriptPath); if (!string.IsNullOrWhiteSpace(arguments)) { command += " " + arguments; } return command; } private static string QuoteForCmd(string value) { return "\"" + value.Replace("\"", "\\\"") + "\""; } // Die GUI behandelt das Argumentfeld bewusst als Kommandozeilen-Argumentstring. // Für den Test wird eine einfache Windows-Argumentzerlegung verwendet. Das Plugin selbst // übergibt den String unverändert an cmd.exe/PowerShell bzw. an die EXE-Kommandozeile. private static void AddRawArguments(ProcessStartInfo startInfo, string? raw) { if (string.IsNullOrWhiteSpace(raw)) { return; } foreach (string argument in CommandLineSplitter.Split(raw)) { startInfo.ArgumentList.Add(argument); } } } internal static class CommandLineSplitter { public static IReadOnlyList Split(string commandLine) { var result = new List(); var current = new System.Text.StringBuilder(); bool inQuotes = false; int backslashes = 0; for (int i = 0; i < commandLine.Length; i++) { char ch = commandLine[i]; if (ch == '\\') { backslashes++; continue; } if (ch == '"') { current.Append('\\', backslashes / 2); if (backslashes % 2 == 0) { inQuotes = !inQuotes; } else { current.Append('"'); } backslashes = 0; continue; } current.Append('\\', backslashes); backslashes = 0; if (char.IsWhiteSpace(ch) && !inQuotes) { if (current.Length > 0) { result.Add(current.ToString()); current.Clear(); } } else { current.Append(ch); } } current.Append('\\', backslashes); if (current.Length > 0) { result.Add(current.ToString()); } return result; } }