Files
Mstsc-Script-Hook/README.md
T

10 KiB
Raw Blame History

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

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:

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

  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:

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_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.