Hinzufügen der Möglichkeit ein Programmstart auf dem Client durch die RDP Sitzung zu triggern

This commit is contained in:
2026-08-11 09:52:49 +02:00
parent eec4f52b5a
commit ee88f1ff30
22 changed files with 1162 additions and 263 deletions
+208 -42
View File
@@ -1,22 +1,64 @@
# Plandent MSTSC Script Hook
Kleines Windows-Projekt, das beim erfolgreichen Aufbau einer klassischen RDP-Verbindung mit `mstsc.exe` lokal ein konfiguriertes Script startet.
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
- **PlandentMstscScriptHook.dll** – native x64 DVC-Plugin-DLL für den Remote Desktop Connection Client.
### 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.
## Verhalten
### Terminalserver
Die DLL implementiert `IWTSPlugin` und exportiert `VirtualChannelGetInstance`.
- **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.
- `Initialize()` – Plugin wird von MSTSC initialisiert.
- `Connected()` – liest die Konfiguration und startet das lokale Script.
- `Disconnected()` – beendet das von dieser Plugin-Instanz gestartete Script, wenn `StopOnDisconnect=1` gesetzt ist.
- `Terminated()` – bereinigt Prozesshandles; bei `StopOnDisconnect=0` läuft das Script weiter.
## Architektur
Der Plugin-Callback wartet **nicht** auf das Script. Es wird nur mit `CreateProcessW` gestartet und der Callback kehrt direkt zurück.
```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
@@ -31,30 +73,110 @@ Konfiguration:
```text
HKCU\Software\Plandent\MstscScriptHook
Enabled REG_DWORD 1
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
EnableLogging REG_DWORD 1
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
```
## Unterstützte Starttypen
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 `Hidden=1` wird `CREATE_NO_WINDOW` verwendet und `SW_HIDE` gesetzt.
Bei aktivierter Option **Unsichtbar starten** werden Konsolenprogramme ohne sichtbares Konsolenfenster gestartet.
## Mehrfachstartschutz
Bei `PreventDuplicates=1` wird aus dem expandierten Scriptpfad ein Named Mutex erzeugt. Das Mutex-Handle wird gezielt an den gestarteten Prozess vererbt. Damit bleibt der Schutz bestehen, solange der Receiver läuft – auch wenn die ursprüngliche `mstsc.exe`-Instanz bereits beendet wurde.
Für beide Programme existiert eine getrennt konfigurierbare Option **Mehrfachstart verhindern**.
Der Schutz ist bewusst `Local\\...`, also auf die lokale Windows-Sitzung begrenzt.
Der Schutz wird aus dem expandierten Programmpfad abgeleitet und verwendet einen Named Mutex unter:
```text
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:
```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
@@ -64,16 +186,28 @@ Bei aktiviertem Logging:
%LOCALAPPDATA%\Plandent\MstscScriptHook\plugin.log
```
Die GUI enthält einen Button **Log öffnen**.
Dort werden sowohl der normale RDP-Lifecycle als auch DVC-Verbindungen und Triggerstarts protokolliert.
## Voraussetzungen zum Bauen
- Windows 10/11 x64
- Visual Studio 2022 oder Visual Studio Build Tools 2022
- 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:
@@ -85,33 +219,65 @@ PowerShell im Projektverzeichnis:
Ergebnis:
```text
dist\PlandentMstscScriptHook.exe
dist\
├── PlandentMstscScriptHook.exe
└── PlandentRdpClientTrigger.exe
```
Die veröffentlichte EXE ist x64, self-contained und enthält die native Plugin-DLL als Resource.
- `PlandentMstscScriptHook.exe` → auf dem **lokalen RDP-Client** verwenden.
- `PlandentRdpClientTrigger.exe` → auf den **Terminalserver** kopieren.
## Installation
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. Script über `...` auswählen.
3. Für `RemoteVDDS_Receiver_TS.bat` als Argument `MIN` setzen.
4. Optional Arbeitsverzeichnis auswählen; standardmäßig sollte es dem Scriptverzeichnis entsprechen.
5. **Script testen** und anschließend **Test beenden**.
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 schließen.
8. MSTSC neu starten und eine RDP-Verbindung herstellen.
7. Alle bereits laufenden `mstsc.exe`-Instanzen vollständig schließen.
8. MSTSC neu starten und die RDP-Verbindung herstellen.
Die Installation erfolgt nur unter `HKCU` und `%LOCALAPPDATA%`; dafür sind normalerweise keine Administratorrechte nötig.
Die Client-Installation erfolgt unter `HKCU` und `%LOCALAPPDATA%`; dafür sind normalerweise keine Administratorrechte nötig.
## Deinstallation
## Installation auf dem Terminalserver
**Plugin entfernen** löscht den MSTSC-Registryeintrag und die installierte DLL. Ist die DLL noch in einer laufenden `mstsc.exe` geladen, wird zumindest die Registrierung entfernt; nach dem Schließen aller MSTSC-Instanzen kann die DLL erneut über den Button entfernt werden.
Es ist keine Registrierung erforderlich. Lediglich:
Die Script-Konfiguration unter `HKCU\Software\Plandent\MstscScriptHook` bleibt absichtlich erhalten.
```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 ebenfalls unterstützen, sonst wird die DLL nicht geladen.
- `StopOnDisconnect` beendet nur den vom Plugin selbst gestarteten Root-Prozess. Beim RemoteVDDS-BAT ist das der dauerhaft laufende `cmd.exe`-Receiver.
- Das Script läuft mit den Rechten des lokalen Benutzers, der `mstsc.exe` gestartet hat.
- AppLocker/WDAC oder andere Application-Control-Richtlinien können das Laden einer nicht signierten DLL bzw. das Starten des Scripts blockieren.
- 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.