10 KiB
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:
- Automatisch beim erfolgreichen RDP-Verbindungsaufbau.
- Gezielt durch
PlandentRdpClientTrigger.exeinnerhalb 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.exegeladen 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
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 STARTwird 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:
HKCU\Software\Microsoft\Terminal Server Client\Default\AddIns\PlandentMstscScriptHook
Name = C:\Users\<Benutzer>\AppData\Local\Plandent\MstscScriptHook\PlandentMstscScriptHook.dll
Konfiguration:
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:
Local\Plandent.MstscScriptHook.<Hash>
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:
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:
.\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
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:
%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:
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:
.\build-release.ps1 -Clean
Ergebnis:
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
PlandentMstscScriptHook.exestarten.- Gewünschten automatischen RDP-Start konfigurieren oder deaktivieren.
- Optional unter Programmstart durch Terminalserver das lokale Trigger-Programm einschließlich Argumenten konfigurieren.
- Start durch Terminalserver-Trigger erlauben aktivieren.
- Optional beide Programme mit den Testbuttons lokal testen.
- Plugin installieren anklicken.
- Alle bereits laufenden
mstsc.exe-Instanzen vollständig schließen. - 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:
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:
C:\Tools\PlandentRdpClientTrigger.exe
Beispiel PowerShell mit Exitcode-Prüfung:
& '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_SESSIONverwendet wird. - Das lokale Programm läuft mit den Rechten des Benutzers, der
mstsc.exeauf dem Client gestartet hat. - Der Server kann über dieses Protokoll keine beliebigen Programme oder Parameter vorgeben.
StopOnDisconnectgilt 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.