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