# Plandent MSTSC Script Hook Windows-Projekt für den klassischen Microsoft Remote Desktop Connection Client (`mstsc.exe`). Das Client-Plugin kann lokale Programme auf zwei voneinander getrennten Wegen starten: 1. **Automatisch beim erfolgreichen RDP-Verbindungsaufbau**. 2. **Gezielt durch `PlandentRdpClientTrigger.exe` innerhalb der Terminalserver-Sitzung**. Der Terminalserver überträgt dabei **keinen Programmpfad und keine Argumente**. Die Server-EXE sendet ausschließlich einen festen `START`-Befehl über einen RDP Dynamic Virtual Channel (DVC). Welches lokale Programm mit welchen Parametern gestartet wird, wird ausschließlich auf dem Client in der Registry konfiguriert. ## Komponenten ### Client - **PlandentMstscScriptHook.dll** – native x64 DVC-Plugin-DLL, die von `mstsc.exe` geladen wird. - **PlandentMstscScriptHook.exe** – WinForms-Konfigurator/Installer. Die Release-EXE enthält die DLL als Resource und extrahiert sie bei der Installation. ### Terminalserver - **PlandentRdpClientTrigger.exe** – kleine native x64 Windows-EXE ohne GUI und ohne Parameter. Sie wird **innerhalb der betreffenden RDP-Benutzersitzung** ausgeführt und sendet den festen Start-Trigger an den Client dieser Sitzung. ## Architektur ```text LOKALER CLIENT TERMINALSERVER ================ ============== mstsc.exe │ └── PlandentMstscScriptHook.dll │ ├── Connected() │ └── optional: lokales RDP-Verbindungsprogramm │ └── DVC Listener plandent::mstsc-script-hook ▲ │ fester Befehl: │ PLANDENT_MSTSC_HOOK/1 START │ └──────────────────── PlandentRdpClientTrigger.exe │ └── läuft in der RDP-Sitzung Nach START liest die DLL ausschließlich lokal: HKCU\Software\Plandent\MstscScriptHook TriggerProgramPath TriggerArguments TriggerWorkingDirectory ``` ## Sicherheit des Triggers Die Server-Komponente ist absichtlich minimal gehalten: - keine Kommandozeilenparameter für das Client-Programm, - kein Pfad wird vom Terminalserver übertragen, - keine beliebigen Befehle werden akzeptiert, - ausschließlich die exakte Protokollnachricht `PLANDENT_MSTSC_HOOK/1 START` wird verarbeitet, - Programmpfad und Argumente stammen nur aus der lokalen HKCU-Konfiguration des angemeldeten Client-Benutzers. Damit kann `PlandentRdpClientTrigger.exe` nur den vorher auf dem Client festgelegten Start auslösen. ## Registry MSTSC-Registrierung pro Benutzer: ```text HKCU\Software\Microsoft\Terminal Server Client\Default\AddIns\PlandentMstscScriptHook Name = C:\Users\\AppData\Local\Plandent\MstscScriptHook\PlandentMstscScriptHook.dll ``` Konfiguration: ```text HKCU\Software\Plandent\MstscScriptHook Enabled REG_DWORD 1 EnableLogging REG_DWORD 1 ; Automatischer Start bei RDP-Verbindung ScriptPath REG_EXPAND_SZ C:\RemoteVDDS\RemoteVDDS_Receiver_TS.bat Arguments REG_SZ MIN WorkingDirectory REG_EXPAND_SZ C:\RemoteVDDS StartOnConnect REG_DWORD 1 StopOnDisconnect REG_DWORD 0 Hidden REG_DWORD 1 PreventDuplicates REG_DWORD 1 ; Separates Programm für den Terminalserver-Trigger TriggerEnabled REG_DWORD 1 TriggerProgramPath REG_EXPAND_SZ C:\Program Files\Plandent\ClientTool.exe TriggerArguments REG_SZ --receive --silent TriggerWorkingDirectory REG_EXPAND_SZ C:\Program Files\Plandent TriggerHidden REG_DWORD 1 TriggerPreventDuplicates REG_DWORD 1 ``` Die bisherigen Registry-Werte für den automatischen RDP-Start bleiben kompatibel. ## Unterstützte lokale Starttypen Für beide Startarten werden unterstützt: - `.bat` / `.cmd` → `cmd.exe /D /S /C ...` - `.ps1` → Windows PowerShell mit `-NoProfile -NonInteractive -ExecutionPolicy Bypass -File ...` - `.exe` → direkter Start Bei aktivierter Option **Unsichtbar starten** werden Konsolenprogramme ohne sichtbares Konsolenfenster gestartet. ## Mehrfachstartschutz Für beide Programme existiert eine getrennt konfigurierbare Option **Mehrfachstart verhindern**. Der Schutz wird aus dem expandierten Programmpfad abgeleitet und verwendet einen Named Mutex unter: ```text Local\Plandent.MstscScriptHook. ``` Das Mutex-Handle wird an den gestarteten Root-Prozess vererbt. Solange dieser Prozess läuft, wird ein erneuter Start desselben Pfades verhindert. ## Dynamic Virtual Channel Das Client-Plugin registriert in `IWTSPlugin::Initialize()` den DVC-Listener: ```text plandent::mstsc-script-hook ``` Wenn `PlandentRdpClientTrigger.exe` innerhalb der RDP-Sitzung läuft, öffnet sie mit `WTSVirtualChannelOpenEx(WTS_CURRENT_SESSION, ..., WTS_CHANNEL_OPTION_DYNAMIC)` genau diesen Kanal und sendet den festen Startbefehl. Auf dem Client nimmt `IWTSListenerCallback::OnNewChannelConnection()` den Kanal an. `IWTSVirtualChannelCallback::OnDataReceived()` akzeptiert ausschließlich die definierte `START`-Nachricht und startet anschließend das lokal konfigurierte Trigger-Programm. ## Terminalserver-Trigger verwenden `PlandentRdpClientTrigger.exe` benötigt keine Argumente: ```powershell .\PlandentRdpClientTrigger.exe ``` Sie sollte von einem Prozess innerhalb der Benutzer-RDP-Sitzung gestartet werden, deren Client angesprochen werden soll. Die EXE öffnet **keinen Netzwerkport**, benötigt keine zusätzliche Firewallfreigabe und kommuniziert ausschließlich über den bestehenden RDP-DVC. ### Exitcodes ```text 0 Trigger wurde an den DVC geschrieben 10 DVC konnte nicht geöffnet werden 11 Schreiben auf den DVC fehlgeschlagen 12 Trigger-Nachricht wurde nicht vollständig geschrieben ``` Ein Exitcode `10` tritt beispielsweise auf, wenn die EXE nicht innerhalb einer passenden RDP-Sitzung läuft, das Client-Plugin nicht geladen wurde oder der DVC nicht verfügbar ist. ## Client-Konfigurator Die GUI enthält jetzt zwei getrennte Bereiche: ### Automatischer Start bei RDP-Verbindung - Programm/Script - Argumente - Arbeitsverzeichnis - Bei erfolgreicher RDP-Verbindung starten - Bei RDP-Trennung beenden - Unsichtbar starten - Mehrfachstart verhindern - lokaler Test ### Programmstart durch Terminalserver - Trigger aktivieren/deaktivieren - separates Programm/Script - separate Argumente - separates Arbeitsverzeichnis - Unsichtbar starten - Mehrfachstart verhindern - lokaler Test ## Logging Bei aktiviertem Logging: ```text %LOCALAPPDATA%\Plandent\MstscScriptHook\plugin.log ``` Dort werden sowohl der normale RDP-Lifecycle als auch DVC-Verbindungen und Triggerstarts protokolliert. ## Voraussetzungen zum Bauen - Windows 10/11 x64 als Entwicklungsrechner - Visual Studio 2022 oder entsprechende Build Tools - Workload **Desktopentwicklung mit C++** - Windows 10/11 SDK - .NET 8 SDK ## Debug-Build in Visual Studio `Configurator.csproj` ist für **Debug** jetzt absichtlich framework-dependent konfiguriert. Dadurch werden beim normalen F5-Build keine `Microsoft.*.Runtime.win-x64`-Runtime-Pakete benötigt. Als Startprojekt in Visual Studio: ```text PlandentMstscScriptHook.Configurator ``` Die Plugin-DLL wird über die Projektabhängigkeit zuerst gebaut und anschließend als Resource in den Configurator eingebettet. ## Release bauen PowerShell im Projektverzeichnis: ```powershell .\build-release.ps1 -Clean ``` Ergebnis: ```text dist\ ├── PlandentMstscScriptHook.exe └── PlandentRdpClientTrigger.exe ``` - `PlandentMstscScriptHook.exe` → auf dem **lokalen RDP-Client** verwenden. - `PlandentRdpClientTrigger.exe` → auf den **Terminalserver** kopieren. Die Client-EXE ist im Release x64, self-contained und Single-File. Die native Plugin-DLL ist darin eingebettet. ## Installation auf dem Client 1. `PlandentMstscScriptHook.exe` starten. 2. Gewünschten automatischen RDP-Start konfigurieren oder deaktivieren. 3. Optional unter **Programmstart durch Terminalserver** das lokale Trigger-Programm einschließlich Argumenten konfigurieren. 4. **Start durch Terminalserver-Trigger erlauben** aktivieren. 5. Optional beide Programme mit den Testbuttons lokal testen. 6. **Plugin installieren** anklicken. 7. Alle bereits laufenden `mstsc.exe`-Instanzen vollständig schließen. 8. MSTSC neu starten und die RDP-Verbindung herstellen. Die Client-Installation erfolgt unter `HKCU` und `%LOCALAPPDATA%`; dafür sind normalerweise keine Administratorrechte nötig. ## Installation auf dem Terminalserver Es ist keine Registrierung erforderlich. Lediglich: ```text PlandentRdpClientTrigger.exe ``` an einen geeigneten Ort kopieren und von der Anwendung ausführen, die den lokalen Start anfordern soll. Beispiel aus einer Batchdatei innerhalb der RDP-Sitzung: ```bat C:\Tools\PlandentRdpClientTrigger.exe ``` Beispiel PowerShell mit Exitcode-Prüfung: ```powershell & 'C:\Tools\PlandentRdpClientTrigger.exe' if ($LASTEXITCODE -ne 0) { Write-Warning "Client-Trigger fehlgeschlagen. ExitCode: $LASTEXITCODE" } ``` ## Deinstallation auf dem Client **Plugin entfernen** löscht den MSTSC-Registryeintrag und die installierte DLL. Ist die DLL noch in einer laufenden `mstsc.exe` geladen, wird die Registrierung entfernt und die DLL kann nach dem Schließen aller MSTSC-Instanzen erneut entfernt werden. Die eigentliche Programmkonfiguration unter `HKCU\Software\Plandent\MstscScriptHook` bleibt absichtlich erhalten. ## Wichtige Grenzen - Das Plugin ist für den klassischen Microsoft-RDC-Pluginmechanismus ausgelegt. Andere RDP-Clients müssen diesen Mechanismus unterstützen. - Der Server-Trigger muss innerhalb einer aktiven RDP-Sitzung ausgeführt werden, wenn `WTS_CURRENT_SESSION` verwendet wird. - Das lokale Programm läuft mit den Rechten des Benutzers, der `mstsc.exe` auf dem Client gestartet hat. - Der Server kann über dieses Protokoll **keine** beliebigen Programme oder Parameter vorgeben. - `StopOnDisconnect` gilt ausschließlich für das automatische RDP-Verbindungsprogramm, nicht für das per Server ausgelöste Programm. - AppLocker/WDAC oder andere Application-Control-Richtlinien können das Laden einer nicht signierten DLL bzw. den Programmstart blockieren.