Initialversion

This commit is contained in:
2026-08-09 13:27:53 +02:00
commit eec4f52b5a
25 changed files with 2123 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>PlandentMstscScriptHook</AssemblyName>
<RootNamespace>Plandent.MstscScriptHook.Configurator</RootNamespace>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>false</PublishReadyToRun>
<DebugType>embedded</DebugType>
<PluginPayloadPath>$(MSBuildProjectDirectory)\..\artifacts\x64\$(Configuration)\PlandentMstscScriptHook.dll</PluginPayloadPath>
</PropertyGroup>
<ItemGroup>
<EmbeddedResource Include="$(PluginPayloadPath)" LogicalName="PlandentMstscScriptHook.Native.PlandentMstscScriptHook.dll" />
</ItemGroup>
<Target Name="CheckNativePlugin" BeforeTargets="CreateManifestResourceNames">
<Error Condition="!Exists('$(PluginPayloadPath)')" Text="Native Plugin-DLL fehlt: $(PluginPayloadPath). Zuerst Plugin bauen oder build-release.ps1 verwenden." />
</Target>
</Project>
+14
View File
@@ -0,0 +1,14 @@
namespace Plandent.MstscScriptHook.Configurator;
internal sealed class HookConfig
{
public bool Enabled { get; set; } = true;
public string ScriptPath { get; set; } = string.Empty;
public string Arguments { get; set; } = string.Empty;
public string WorkingDirectory { get; set; } = string.Empty;
public bool StartOnConnect { get; set; } = true;
public bool StopOnDisconnect { get; set; }
public bool Hidden { get; set; } = true;
public bool PreventDuplicates { get; set; } = true;
public bool EnableLogging { get; set; } = true;
}
+468
View File
@@ -0,0 +1,468 @@
using System.Drawing;
using System.Windows.Forms;
using System.Diagnostics;
namespace Plandent.MstscScriptHook.Configurator;
internal sealed class MainForm : Form
{
private readonly TextBox scriptPathTextBox = new() { Dock = DockStyle.Fill };
private readonly TextBox argumentsTextBox = new() { Dock = DockStyle.Fill };
private readonly TextBox workingDirectoryTextBox = new() { Dock = DockStyle.Fill };
private readonly CheckBox enabledCheckBox = new() { Text = "Plugin-Funktion aktiviert", AutoSize = true };
private readonly CheckBox startOnConnectCheckBox = new() { Text = "Script bei erfolgreicher RDP-Verbindung starten", AutoSize = true };
private readonly CheckBox stopOnDisconnectCheckBox = new() { Text = "Script bei RDP-Trennung beenden", AutoSize = true };
private readonly CheckBox hiddenCheckBox = new() { Text = "Script unsichtbar starten", AutoSize = true };
private readonly CheckBox preventDuplicatesCheckBox = new() { Text = "Mehrfachstart verhindern", AutoSize = true };
private readonly CheckBox loggingCheckBox = new() { Text = "Plugin-Logging aktivieren", AutoSize = true };
private readonly Label statusLabel = new()
{
AutoSize = false,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft,
Padding = new Padding(8)
};
private readonly Button stopTestButton = new() { Text = "Test beenden", AutoSize = true, Enabled = false };
private Process? testProcess;
public MainForm()
{
Text = "Plandent MSTSC Script Hook";
StartPosition = FormStartPosition.CenterScreen;
MinimumSize = new Size(760, 590);
Size = new Size(820, 640);
Font = new Font("Segoe UI", 9F);
Controls.Add(BuildMainLayout());
Load += (_, _) => LoadConfiguration();
FormClosed += (_, _) => DisposeTestProcessHandle();
}
private Control BuildMainLayout()
{
var root = new TableLayoutPanel
{
Dock = DockStyle.Fill,
Padding = new Padding(18),
ColumnCount = 1,
RowCount = 6
};
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var title = new Label
{
Text = "MSTSC Script Hook",
Font = new Font(Font.FontFamily, 16F, FontStyle.Bold),
AutoSize = true,
Margin = new Padding(0, 0, 0, 4)
};
root.Controls.Add(title, 0, 0);
var description = new Label
{
Text = "Startet bei einer erfolgreichen klassischen Remotedesktop-Verbindung lokal ein konfiguriertes Script im Hintergrund.",
AutoSize = true,
MaximumSize = new Size(740, 0),
Margin = new Padding(0, 0, 0, 16)
};
root.Controls.Add(description, 0, 1);
root.Controls.Add(BuildFileGrid(), 0, 2);
root.Controls.Add(BuildOptionsGroup(), 0, 3);
root.Controls.Add(BuildStatusPanel(), 0, 4);
root.Controls.Add(BuildButtons(), 0, 5);
return root;
}
private Control BuildFileGrid()
{
var grid = new TableLayoutPanel
{
Dock = DockStyle.Top,
AutoSize = true,
ColumnCount = 3,
RowCount = 3,
Margin = new Padding(0, 0, 0, 14)
};
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
grid.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
grid.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
grid.Controls.Add(CreateFieldLabel("Script:"), 0, 0);
grid.Controls.Add(scriptPathTextBox, 1, 0);
var browseScriptButton = new Button { Text = "…", Width = 42, Height = scriptPathTextBox.PreferredHeight + 4 };
browseScriptButton.Click += (_, _) => BrowseScript();
grid.Controls.Add(browseScriptButton, 2, 0);
grid.Controls.Add(CreateFieldLabel("Argumente:"), 0, 1);
grid.Controls.Add(argumentsTextBox, 1, 1);
grid.SetColumnSpan(argumentsTextBox, 2);
grid.Controls.Add(CreateFieldLabel("Arbeitsverzeichnis:"), 0, 2);
grid.Controls.Add(workingDirectoryTextBox, 1, 2);
var browseWorkingButton = new Button { Text = "…", Width = 42, Height = workingDirectoryTextBox.PreferredHeight + 4 };
browseWorkingButton.Click += (_, _) => BrowseWorkingDirectory();
grid.Controls.Add(browseWorkingButton, 2, 2);
return grid;
}
private static Label CreateFieldLabel(string text)
{
return new Label
{
Text = text,
AutoSize = true,
Anchor = AnchorStyles.Left,
Margin = new Padding(0, 6, 12, 6)
};
}
private Control BuildOptionsGroup()
{
var group = new GroupBox
{
Text = "Verhalten",
Dock = DockStyle.Top,
AutoSize = true,
Padding = new Padding(12),
Margin = new Padding(0, 0, 0, 14)
};
var options = new FlowLayoutPanel
{
Dock = DockStyle.Top,
FlowDirection = FlowDirection.TopDown,
WrapContents = false,
AutoSize = true
};
options.Controls.Add(enabledCheckBox);
options.Controls.Add(startOnConnectCheckBox);
options.Controls.Add(stopOnDisconnectCheckBox);
options.Controls.Add(hiddenCheckBox);
options.Controls.Add(preventDuplicatesCheckBox);
options.Controls.Add(loggingCheckBox);
group.Controls.Add(options);
return group;
}
private Control BuildStatusPanel()
{
var panel = new Panel
{
Dock = DockStyle.Top,
Height = 58,
BorderStyle = BorderStyle.FixedSingle,
Margin = new Padding(0, 0, 0, 14)
};
panel.Controls.Add(statusLabel);
return panel;
}
private Control BuildButtons()
{
var buttons = new FlowLayoutPanel
{
Dock = DockStyle.Top,
AutoSize = true,
FlowDirection = FlowDirection.LeftToRight,
WrapContents = true,
Margin = new Padding(0)
};
var testButton = new Button { Text = "Script testen", AutoSize = true };
testButton.Click += (_, _) => StartTest();
stopTestButton.Click += (_, _) => StopTest();
var saveButton = new Button { Text = "Konfiguration speichern", AutoSize = true };
saveButton.Click += (_, _) => SaveConfiguration(showConfirmation: true);
var installButton = new Button { Text = "Plugin installieren", AutoSize = true };
installButton.Click += (_, _) => InstallPlugin();
var removeButton = new Button { Text = "Plugin entfernen", AutoSize = true };
removeButton.Click += (_, _) => RemovePlugin();
var logButton = new Button { Text = "Log öffnen", AutoSize = true };
logButton.Click += (_, _) => OpenLog();
buttons.Controls.Add(testButton);
buttons.Controls.Add(stopTestButton);
buttons.Controls.Add(saveButton);
buttons.Controls.Add(installButton);
buttons.Controls.Add(removeButton);
buttons.Controls.Add(logButton);
return buttons;
}
private void BrowseScript()
{
using var dialog = new OpenFileDialog
{
Title = "Script oder Programm auswählen",
Filter = "Unterstützte Dateien (*.bat;*.cmd;*.ps1;*.exe)|*.bat;*.cmd;*.ps1;*.exe|Alle Dateien (*.*)|*.*",
CheckFileExists = true
};
if (!string.IsNullOrWhiteSpace(scriptPathTextBox.Text))
{
string expanded = Environment.ExpandEnvironmentVariables(scriptPathTextBox.Text);
if (File.Exists(expanded))
{
dialog.InitialDirectory = Path.GetDirectoryName(expanded);
dialog.FileName = Path.GetFileName(expanded);
}
}
if (dialog.ShowDialog(this) != DialogResult.OK)
{
return;
}
scriptPathTextBox.Text = dialog.FileName;
if (string.IsNullOrWhiteSpace(workingDirectoryTextBox.Text))
{
workingDirectoryTextBox.Text = Path.GetDirectoryName(dialog.FileName) ?? string.Empty;
}
if (string.IsNullOrWhiteSpace(argumentsTextBox.Text) &&
Path.GetFileName(dialog.FileName).Contains("RemoteVDDS_Receiver_TS", StringComparison.OrdinalIgnoreCase))
{
argumentsTextBox.Text = "MIN";
}
}
private void BrowseWorkingDirectory()
{
using var dialog = new FolderBrowserDialog
{
Description = "Arbeitsverzeichnis auswählen",
UseDescriptionForTitle = true,
ShowNewFolderButton = true
};
string expanded = Environment.ExpandEnvironmentVariables(workingDirectoryTextBox.Text);
if (Directory.Exists(expanded))
{
dialog.SelectedPath = expanded;
}
if (dialog.ShowDialog(this) == DialogResult.OK)
{
workingDirectoryTextBox.Text = dialog.SelectedPath;
}
}
private HookConfig ReadForm()
{
return new HookConfig
{
Enabled = enabledCheckBox.Checked,
ScriptPath = scriptPathTextBox.Text.Trim(),
Arguments = argumentsTextBox.Text.Trim(),
WorkingDirectory = workingDirectoryTextBox.Text.Trim(),
StartOnConnect = startOnConnectCheckBox.Checked,
StopOnDisconnect = stopOnDisconnectCheckBox.Checked,
Hidden = hiddenCheckBox.Checked,
PreventDuplicates = preventDuplicatesCheckBox.Checked,
EnableLogging = loggingCheckBox.Checked
};
}
private void LoadConfiguration()
{
HookConfig config = RegistryConfig.Load();
scriptPathTextBox.Text = config.ScriptPath;
argumentsTextBox.Text = config.Arguments;
workingDirectoryTextBox.Text = config.WorkingDirectory;
enabledCheckBox.Checked = config.Enabled;
startOnConnectCheckBox.Checked = config.StartOnConnect;
stopOnDisconnectCheckBox.Checked = config.StopOnDisconnect;
hiddenCheckBox.Checked = config.Hidden;
preventDuplicatesCheckBox.Checked = config.PreventDuplicates;
loggingCheckBox.Checked = config.EnableLogging;
RefreshStatus();
}
private bool SaveConfiguration(bool showConfirmation)
{
try
{
HookConfig config = ReadForm();
ValidateConfig(config, requireScript: false);
RegistryConfig.Save(config);
if (showConfirmation)
{
MessageBox.Show(this, "Konfiguration wurde gespeichert.", "MSTSC Script Hook", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
RefreshStatus();
return true;
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "Konfiguration konnte nicht gespeichert werden", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
private void InstallPlugin()
{
try
{
HookConfig config = ReadForm();
ValidateConfig(config, requireScript: true);
RegistryConfig.Save(config);
PluginInstaller.Install();
RefreshStatus();
MessageBox.Show(
this,
"Plugin wurde für den aktuellen Benutzer installiert und die Konfiguration gespeichert.\n\n" +
"Bereits geöffnete mstsc.exe-Fenster vollständig schließen und anschließend neu starten, damit die DLL geladen wird.",
"Installation abgeschlossen",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "Installation fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void RemovePlugin()
{
UninstallResult result = PluginInstaller.Uninstall();
RefreshStatus();
MessageBox.Show(
this,
result.Message,
"Plugin entfernen",
MessageBoxButtons.OK,
result.FullyRemoved ? MessageBoxIcon.Information : MessageBoxIcon.Warning);
}
private void StartTest()
{
try
{
StopTest(silent: true);
HookConfig config = ReadForm();
ValidateConfig(config, requireScript: true);
testProcess = ScriptTestLauncher.Start(config);
stopTestButton.Enabled = true;
statusLabel.Text = $"Testprozess gestartet: PID {testProcess.Id}. Der Test läuft unabhängig von MSTSC.";
}
catch (Exception ex)
{
MessageBox.Show(this, ex.Message, "Script-Test fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private void StopTest() => StopTest(silent: false);
private void StopTest(bool silent)
{
if (testProcess is null)
{
stopTestButton.Enabled = false;
return;
}
try
{
if (!testProcess.HasExited)
{
testProcess.Kill(entireProcessTree: true);
testProcess.WaitForExit(3000);
}
if (!silent)
{
statusLabel.Text = "Testprozess wurde beendet.";
}
}
catch (Exception ex)
{
if (!silent)
{
MessageBox.Show(this, ex.Message, "Testprozess konnte nicht beendet werden", MessageBoxButtons.OK, MessageBoxIcon.Warning);
}
}
finally
{
DisposeTestProcessHandle();
}
}
private void DisposeTestProcessHandle()
{
testProcess?.Dispose();
testProcess = null;
stopTestButton.Enabled = false;
}
private void OpenLog()
{
string logPath = Path.Combine(PluginInstaller.InstallDirectory, "plugin.log");
if (!File.Exists(logPath))
{
MessageBox.Show(this, "Es existiert noch kein Plugin-Log.", "MSTSC Script Hook", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
Process.Start(new ProcessStartInfo
{
FileName = logPath,
UseShellExecute = true
});
}
private void RefreshStatus()
{
bool installed = PluginInstaller.IsInstalled(out string pluginStatus);
string configStatus = string.IsNullOrWhiteSpace(scriptPathTextBox.Text)
? " Kein Script konfiguriert."
: $" Script: {scriptPathTextBox.Text}";
statusLabel.Text = (installed ? "✓ " : "○ ") + pluginStatus + configStatus;
}
private static void ValidateConfig(HookConfig config, bool requireScript)
{
if (!requireScript && string.IsNullOrWhiteSpace(config.ScriptPath))
{
return;
}
string scriptPath = Environment.ExpandEnvironmentVariables(config.ScriptPath);
if (string.IsNullOrWhiteSpace(scriptPath))
{
throw new InvalidOperationException("Bitte ein Script auswählen.");
}
if (!File.Exists(scriptPath))
{
throw new FileNotFoundException("Das ausgewählte Script wurde nicht gefunden.", scriptPath);
}
string extension = Path.GetExtension(scriptPath).ToLowerInvariant();
if (extension is not (".bat" or ".cmd" or ".ps1" or ".exe"))
{
throw new NotSupportedException("Unterstützt werden .bat, .cmd, .ps1 und .exe.");
}
if (!string.IsNullOrWhiteSpace(config.WorkingDirectory))
{
string workDir = Environment.ExpandEnvironmentVariables(config.WorkingDirectory);
if (!Directory.Exists(workDir))
{
throw new DirectoryNotFoundException("Das Arbeitsverzeichnis wurde nicht gefunden: " + workDir);
}
}
}
}
+113
View File
@@ -0,0 +1,113 @@
using System.Reflection;
namespace Plandent.MstscScriptHook.Configurator;
internal static class PluginInstaller
{
private const string ResourceName = "PlandentMstscScriptHook.Native.PlandentMstscScriptHook.dll";
public const string DllFileName = "PlandentMstscScriptHook.dll";
public static string InstallDirectory => Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"Plandent",
"MstscScriptHook");
public static string InstalledDllPath => Path.Combine(InstallDirectory, DllFileName);
public static void Install()
{
Directory.CreateDirectory(InstallDirectory);
ExtractPluginBinary(InstalledDllPath);
RegistryConfig.RegisterPlugin(InstalledDllPath);
}
public static UninstallResult Uninstall()
{
RegistryConfig.UnregisterPlugin();
if (!File.Exists(InstalledDllPath))
{
return new UninstallResult(true, "Plugin-Registrierung entfernt.");
}
try
{
File.Delete(InstalledDllPath);
TryDeleteEmptyInstallDirectory();
return new UninstallResult(true, "Plugin-Registrierung und DLL wurden entfernt.");
}
catch (IOException)
{
return new UninstallResult(
false,
"Die Registrierung wurde entfernt. Die DLL wird noch von mstsc.exe verwendet. " +
"Alle Remote-Desktop-Clients schließen und die Datei anschließend löschen bzw. erneut auf „Plugin entfernen“ klicken.");
}
catch (UnauthorizedAccessException ex)
{
return new UninstallResult(false, "Registrierung entfernt, DLL konnte aber nicht gelöscht werden: " + ex.Message);
}
}
public static bool IsInstalled(out string status)
{
string? registered = RegistryConfig.GetRegisteredPluginPath();
if (string.IsNullOrWhiteSpace(registered))
{
status = "Plugin ist nicht registriert.";
return false;
}
string expanded = Environment.ExpandEnvironmentVariables(registered);
if (!File.Exists(expanded))
{
status = $"Plugin ist registriert, DLL fehlt jedoch: {expanded}";
return false;
}
status = $"Plugin ist registriert: {expanded}";
return true;
}
private static void ExtractPluginBinary(string destination)
{
Assembly assembly = Assembly.GetExecutingAssembly();
using Stream? resource = assembly.GetManifestResourceStream(ResourceName);
if (resource is not null)
{
using FileStream output = new(destination, FileMode.Create, FileAccess.Write, FileShare.None);
resource.CopyTo(output);
return;
}
// Praktischer Fallback für Debugging/Entwicklung: DLL direkt neben der EXE akzeptieren.
string adjacent = Path.Combine(AppContext.BaseDirectory, DllFileName);
if (File.Exists(adjacent))
{
File.Copy(adjacent, destination, overwrite: true);
return;
}
throw new FileNotFoundException(
"Die native Plugin-DLL ist weder als Resource eingebettet noch neben der Konfigurations-EXE vorhanden. " +
"Bitte zuerst build-release.ps1 ausführen.",
DllFileName);
}
private static void TryDeleteEmptyInstallDirectory()
{
try
{
if (Directory.Exists(InstallDirectory) && !Directory.EnumerateFileSystemEntries(InstallDirectory).Any())
{
Directory.Delete(InstallDirectory);
}
}
catch
{
// Unkritisch: ein leeres Verzeichnis darf bestehen bleiben.
}
}
}
internal sealed record UninstallResult(bool FullyRemoved, string Message);
+13
View File
@@ -0,0 +1,13 @@
using System.Windows.Forms;
namespace Plandent.MstscScriptHook.Configurator;
internal static class Program
{
[STAThread]
private static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
+81
View File
@@ -0,0 +1,81 @@
using Microsoft.Win32;
namespace Plandent.MstscScriptHook.Configurator;
internal static class RegistryConfig
{
public const string ConfigKeyPath = @"Software\Plandent\MstscScriptHook";
public const string AddInKeyPath = @"Software\Microsoft\Terminal Server Client\Default\AddIns\PlandentMstscScriptHook";
public static HookConfig Load()
{
var config = new HookConfig();
using RegistryKey? key = Registry.CurrentUser.OpenSubKey(ConfigKeyPath, writable: false);
if (key is null)
{
return config;
}
config.Enabled = ReadBool(key, "Enabled", true);
config.ScriptPath = ReadString(key, "ScriptPath");
config.Arguments = ReadString(key, "Arguments");
config.WorkingDirectory = ReadString(key, "WorkingDirectory");
config.StartOnConnect = ReadBool(key, "StartOnConnect", true);
config.StopOnDisconnect = ReadBool(key, "StopOnDisconnect", false);
config.Hidden = ReadBool(key, "Hidden", true);
config.PreventDuplicates = ReadBool(key, "PreventDuplicates", true);
config.EnableLogging = ReadBool(key, "EnableLogging", true);
return config;
}
public static void Save(HookConfig config)
{
using RegistryKey key = Registry.CurrentUser.CreateSubKey(ConfigKeyPath, writable: true)
?? throw new InvalidOperationException("Registry-Schlüssel konnte nicht erstellt werden.");
key.SetValue("Enabled", config.Enabled ? 1 : 0, RegistryValueKind.DWord);
key.SetValue("ScriptPath", config.ScriptPath ?? string.Empty, RegistryValueKind.ExpandString);
key.SetValue("Arguments", config.Arguments ?? string.Empty, RegistryValueKind.String);
key.SetValue("WorkingDirectory", config.WorkingDirectory ?? string.Empty, RegistryValueKind.ExpandString);
key.SetValue("StartOnConnect", config.StartOnConnect ? 1 : 0, RegistryValueKind.DWord);
key.SetValue("StopOnDisconnect", config.StopOnDisconnect ? 1 : 0, RegistryValueKind.DWord);
key.SetValue("Hidden", config.Hidden ? 1 : 0, RegistryValueKind.DWord);
key.SetValue("PreventDuplicates", config.PreventDuplicates ? 1 : 0, RegistryValueKind.DWord);
key.SetValue("EnableLogging", config.EnableLogging ? 1 : 0, RegistryValueKind.DWord);
}
public static string? GetRegisteredPluginPath()
{
using RegistryKey? key = Registry.CurrentUser.OpenSubKey(AddInKeyPath, writable: false);
return key?.GetValue("Name", null, RegistryValueOptions.DoNotExpandEnvironmentNames) as string;
}
public static void RegisterPlugin(string dllPath)
{
using RegistryKey key = Registry.CurrentUser.CreateSubKey(AddInKeyPath, writable: true)
?? throw new InvalidOperationException("MSTSC AddIn-Registry-Schlüssel konnte nicht erstellt werden.");
key.SetValue("Name", dllPath, RegistryValueKind.String);
}
public static void UnregisterPlugin()
{
Registry.CurrentUser.DeleteSubKeyTree(AddInKeyPath, throwOnMissingSubKey: false);
}
private static bool ReadBool(RegistryKey key, string name, bool defaultValue)
{
object? value = key.GetValue(name);
return value switch
{
int i => i != 0,
long l => l != 0,
_ => defaultValue
};
}
private static string ReadString(RegistryKey key, string name, string defaultValue = "")
{
return key.GetValue(name, defaultValue, RegistryValueOptions.DoNotExpandEnvironmentNames) as string
?? defaultValue;
}
}
+160
View File
@@ -0,0 +1,160 @@
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<string> Split(string commandLine)
{
var result = new List<string>();
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;
}
}