Files
T
patrick 4c51989600 feat: initiale Version 0.1.0 des MSTSC Script Hooks
- Native x64 MSTSC-DVC-Erweiterung für lokalen Programmstart
- Automatischer Script-/Programmstart bei RDP-Verbindungsaufbau
- Separater Programmstart über Trigger innerhalb der RDP-Sitzung
- Bidirektionaler DVC zur Ermittlung des lokalen Client-Hostnamens
- SIOMIN-Trigger zur Anbindung lokaler Sidexis-Installationen
- SIOMIN-Datensatzlängen und Hostname-Felder bytegenau angepasst
- Temporäre SIOMIN-Einträge nach erfolgreicher Übernahme bereinigt
- Native Win32-Konfigurationsoberfläche ohne .NET-Abhängigkeit
- Mehrfachstartschutz und optionaler unsichtbarer Programmstart
- Debug- und Plugin-Logging ergänzt
- RemoteVDDS-Anwendungsfall integriert
- Installer/Registrierung des MSTSC-Plugins über Client-Konfigurator
- Techniker-, Entwickler- und Projektdokumentation ergänzt
- Proprietäre Lizenz für Copyright Patrick Gniza hinzugefügt
2026-08-11 16:32:33 +02:00

112 lines
3.5 KiB
C++

#include "../Shared/TriggerProtocol.h"
#include "../Shared/DvcServerIo.h"
#include <windows.h>
#include <wtsapi32.h>
#include <cstring>
#include <cwchar>
#include <vector>
namespace
{
constexpr DWORD kAckTimeoutMs = 5000;
enum class ExitCode : int
{
Success = 0,
ChannelOpenFailed = 10,
WriteFailed = 11,
PartialWrite = 12,
AckReadFailed = 13,
StartRejected = 14,
InvalidAck = 15
};
void DebugError(const wchar_t* step, DWORD error)
{
wchar_t buffer[256]{};
swprintf_s(
buffer,
(sizeof(buffer) / sizeof(buffer[0])),
L"PlandentRdpClientTrigger: %s fehlgeschlagen, Win32=%lu\n",
step,
error);
OutputDebugStringW(buffer);
}
}
int APIENTRY wWinMain(HINSTANCE, HINSTANCE, PWSTR, int)
{
// WTS_CURRENT_SESSION stellt sicher, dass ausschliesslich der Client der
// RDP-Sitzung angesprochen wird, in der diese EXE ausgefuehrt wird.
HANDLE channel = WTSVirtualChannelOpenEx(
WTS_CURRENT_SESSION,
const_cast<LPSTR>(hook::protocol::kChannelName),
WTS_CHANNEL_OPTION_DYNAMIC | WTS_CHANNEL_OPTION_DYNAMIC_PRI_MED);
if (channel == nullptr)
{
DebugError(L"WTSVirtualChannelOpenEx", GetLastError());
return static_cast<int>(ExitCode::ChannelOpenFailed);
}
ULONG bytesWritten = 0;
const BOOL written = WTSVirtualChannelWrite(
channel,
const_cast<PCHAR>(hook::protocol::kStartMessage),
static_cast<ULONG>(hook::protocol::kStartMessageLength),
&bytesWritten);
if (!written)
{
const DWORD error = GetLastError();
WTSVirtualChannelClose(channel);
DebugError(L"WTSVirtualChannelWrite", error);
return static_cast<int>(ExitCode::WriteFailed);
}
if (bytesWritten != static_cast<ULONG>(hook::protocol::kStartMessageLength))
{
WTSVirtualChannelClose(channel);
OutputDebugStringW(L"PlandentRdpClientTrigger: Trigger-Nachricht wurde nur teilweise geschrieben.\n");
return static_cast<int>(ExitCode::PartialWrite);
}
// Wichtig: den DVC nicht direkt nach WTSVirtualChannelWrite schliessen.
// Der Client quittiert START erst nach Verarbeitung. Dadurch bleibt der
// Kanal lange genug bestehen, bis OnDataReceived die Nachricht erhalten hat.
std::vector<char> response;
DWORD readError = ERROR_SUCCESS;
const bool readOk = hook::dvc::ReadMessage(channel, kAckTimeoutMs, response, readError);
WTSVirtualChannelClose(channel);
if (!readOk)
{
DebugError(L"START-Quittung lesen", readError);
return static_cast<int>(ExitCode::AckReadFailed);
}
if (response.size() == hook::protocol::kStartOkResponseLength &&
std::memcmp(
response.data(),
hook::protocol::kStartOkResponse,
hook::protocol::kStartOkResponseLength) == 0)
{
return static_cast<int>(ExitCode::Success);
}
if (response.size() == hook::protocol::kStartFailedResponseLength &&
std::memcmp(
response.data(),
hook::protocol::kStartFailedResponse,
hook::protocol::kStartFailedResponseLength) == 0)
{
OutputDebugStringW(L"PlandentRdpClientTrigger: Client hat den Programmstart abgelehnt/als fehlgeschlagen gemeldet.\n");
return static_cast<int>(ExitCode::StartRejected);
}
OutputDebugStringW(L"PlandentRdpClientTrigger: Ungueltige START-Quittung empfangen.\n");
return static_cast<int>(ExitCode::InvalidAck);
}