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
This commit is contained in:
@@ -53,6 +53,7 @@
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@@ -71,6 +72,7 @@
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;NOMINMAX;UNICODE;_UNICODE;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<LanguageStandard>stdcpp17</LanguageStandard>
|
||||
<AdditionalOptions>/utf-8 %(AdditionalOptions)</AdditionalOptions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
|
||||
+171
-16
@@ -10,10 +10,80 @@
|
||||
|
||||
namespace
|
||||
{
|
||||
std::wstring GetLocalClientHostname()
|
||||
{
|
||||
// PhysicalDnsHostname liefert den Hostteil ohne DNS-Domaene und ist nicht von
|
||||
// Variablen in der Remotesitzung abhaengig. Auf normalen Workstations entspricht
|
||||
// das dem lokalen Rechnernamen. GetComputerNameW dient als Rueckfall.
|
||||
wchar_t hostname[256]{};
|
||||
DWORD hostnameChars = static_cast<DWORD>(sizeof(hostname) / sizeof(hostname[0]));
|
||||
if (GetComputerNameExW(ComputerNamePhysicalDnsHostname, hostname, &hostnameChars))
|
||||
{
|
||||
return std::wstring(hostname, hostnameChars);
|
||||
}
|
||||
|
||||
wchar_t fallback[MAX_COMPUTERNAME_LENGTH + 1]{};
|
||||
DWORD fallbackChars = static_cast<DWORD>(sizeof(fallback) / sizeof(fallback[0]));
|
||||
if (GetComputerNameW(fallback, &fallbackChars))
|
||||
{
|
||||
return std::wstring(fallback, fallbackChars);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string WideToUtf8(const std::wstring& value)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const int required = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
value.data(),
|
||||
static_cast<int>(value.size()),
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
if (required <= 0)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string result(static_cast<std::size_t>(required), '\0');
|
||||
const int written = WideCharToMultiByte(
|
||||
CP_UTF8,
|
||||
WC_ERR_INVALID_CHARS,
|
||||
value.data(),
|
||||
static_cast<int>(value.size()),
|
||||
result.data(),
|
||||
required,
|
||||
nullptr,
|
||||
nullptr);
|
||||
|
||||
if (written != required)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class TriggerChannelCallback final : public IWTSVirtualChannelCallback
|
||||
{
|
||||
public:
|
||||
TriggerChannelCallback() = default;
|
||||
explicit TriggerChannelCallback(IWTSVirtualChannel* channel)
|
||||
: channel_(channel)
|
||||
{
|
||||
if (channel_ != nullptr)
|
||||
{
|
||||
channel_->AddRef();
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override
|
||||
{
|
||||
@@ -57,32 +127,111 @@ namespace
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (cbSize == hook::protocol::kGetClientHostnameMessageLength &&
|
||||
std::memcmp(buffer,
|
||||
hook::protocol::kGetClientHostnameMessage,
|
||||
hook::protocol::kGetClientHostnameMessageLength) == 0)
|
||||
{
|
||||
const std::wstring hostname = GetLocalClientHostname();
|
||||
const std::string hostnameUtf8 = WideToUtf8(hostname);
|
||||
|
||||
if (channel_ == nullptr || hostnameUtf8.empty())
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Hostname: Lokaler Client-Hostname konnte nicht ermittelt oder beantwortet werden.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::string response;
|
||||
response.reserve(hook::protocol::kClientHostnameResponsePrefixLength + hostnameUtf8.size());
|
||||
response.append(hook::protocol::kClientHostnameResponsePrefix,
|
||||
hook::protocol::kClientHostnameResponsePrefixLength);
|
||||
response.append(hostnameUtf8);
|
||||
|
||||
const HRESULT hr = channel_->Write(
|
||||
static_cast<ULONG>(response.size()),
|
||||
reinterpret_cast<BYTE*>(response.data()),
|
||||
nullptr);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Hostname: Antwort konnte nicht geschrieben werden. HRESULT=" +
|
||||
std::to_wstring(static_cast<unsigned long>(hr)) + L".");
|
||||
}
|
||||
else
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Hostname: Client-Hostname an Terminalserver beantwortet: " + hostname);
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (cbSize != hook::protocol::kStartMessageLength ||
|
||||
std::memcmp(buffer, hook::protocol::kStartMessage, hook::protocol::kStartMessageLength) != 0)
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Trigger: Unbekannte Nachricht empfangen; aus Sicherheitsgründen ignoriert.");
|
||||
L"DVC-Trigger: Unbekannte Nachricht empfangen; aus Sicherheitsgruenden ignoriert.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
hook::Log(config.enableLogging, L"DVC-Trigger: START vom Terminalserver empfangen.");
|
||||
|
||||
bool startSucceeded = false;
|
||||
|
||||
if (!config.enabled)
|
||||
{
|
||||
hook::Log(config.enableLogging, L"DVC-Trigger: Plugin-Funktion ist deaktiviert; Start ignoriert.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
if (!config.triggerEnabled)
|
||||
else if (!config.triggerEnabled)
|
||||
{
|
||||
hook::Log(config.enableLogging, L"DVC-Trigger: Server-Trigger ist in der Client-Konfiguration deaktiviert.");
|
||||
return S_OK;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Der Terminalserver liefert absichtlich weder Pfad noch Argumente.
|
||||
// Beides wird ausschliesslich aus HKCU auf dem lokalen Client gelesen.
|
||||
hook::ProcessLauncher launcher;
|
||||
startSucceeded = launcher.Start(
|
||||
config.triggerProgram,
|
||||
config.enableLogging,
|
||||
L"Server-Trigger-Programm");
|
||||
}
|
||||
|
||||
// Der Server wartet auf diese Quittung, bevor er den kurzlebigen DVC
|
||||
// schliesst. Das verhindert die beobachtete Race Condition, bei der
|
||||
// WTSVirtualChannelWrite erfolgreich war, der Kanal aber geschlossen
|
||||
// wurde, bevor OnDataReceived die START-Nachricht erhalten hatte.
|
||||
if (channel_ != nullptr)
|
||||
{
|
||||
const char* response = startSucceeded
|
||||
? hook::protocol::kStartOkResponse
|
||||
: hook::protocol::kStartFailedResponse;
|
||||
const std::size_t responseLength = startSucceeded
|
||||
? hook::protocol::kStartOkResponseLength
|
||||
: hook::protocol::kStartFailedResponseLength;
|
||||
|
||||
const HRESULT hr = channel_->Write(
|
||||
static_cast<ULONG>(responseLength),
|
||||
reinterpret_cast<BYTE*>(const_cast<char*>(response)),
|
||||
nullptr);
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Trigger: START-Quittung konnte nicht geschrieben werden. HRESULT=" +
|
||||
std::to_wstring(static_cast<unsigned long>(hr)) + L".");
|
||||
}
|
||||
else
|
||||
{
|
||||
hook::Log(config.enableLogging,
|
||||
startSucceeded
|
||||
? L"DVC-Trigger: START_OK an Terminalserver gesendet."
|
||||
: L"DVC-Trigger: START_FAILED an Terminalserver gesendet.");
|
||||
}
|
||||
}
|
||||
|
||||
// Der Terminalserver liefert absichtlich weder Pfad noch Argumente.
|
||||
// Beides wird ausschließlich aus HKCU auf dem lokalen Client gelesen.
|
||||
hook::ProcessLauncher launcher;
|
||||
launcher.Start(config.triggerProgram, config.enableLogging, L"Server-Trigger-Programm");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -94,8 +243,17 @@ namespace
|
||||
}
|
||||
|
||||
private:
|
||||
~TriggerChannelCallback() = default;
|
||||
~TriggerChannelCallback()
|
||||
{
|
||||
if (channel_ != nullptr)
|
||||
{
|
||||
channel_->Release();
|
||||
channel_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LONG refCount_ = 1;
|
||||
IWTSVirtualChannel* channel_ = nullptr;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -176,13 +334,11 @@ HRESULT STDMETHODCALLTYPE ScriptHookPlugin::Initialize(IWTSVirtualChannelManager
|
||||
L". Der automatische Start bei RDP-Verbindung bleibt trotzdem aktiv.");
|
||||
ReleaseDvcObjects();
|
||||
|
||||
// Der DVC-Trigger ist eine optionale Zusatzfunktion. Ein nicht verfügbarer
|
||||
// DVC darf den bestehenden Connected()/Disconnected()-Hook nicht deaktivieren.
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
hook::Log(config.enableLogging,
|
||||
L"DVC-Listener für 'plandent::mstsc-script-hook' wurde registriert.");
|
||||
L"DVC-Listener fuer 'plandent::mstsc-script-hook' wurde registriert.");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -197,7 +353,6 @@ HRESULT STDMETHODCALLTYPE ScriptHookPlugin::Connected()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Nur Registry lesen + CreateProcess aufrufen. Es wird niemals auf das Programm gewartet.
|
||||
connectLauncher_.Start(config.connectProgram, config.enableLogging, L"RDP-Verbindungsprogramm");
|
||||
return S_OK;
|
||||
}
|
||||
@@ -256,7 +411,7 @@ HRESULT STDMETHODCALLTYPE ScriptHookPlugin::OnNewChannelConnection(
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
auto* channelCallback = new (std::nothrow) TriggerChannelCallback();
|
||||
auto* channelCallback = new (std::nothrow) TriggerChannelCallback(channel);
|
||||
if (channelCallback == nullptr)
|
||||
{
|
||||
hook::Log(config.enableLogging, L"DVC-Trigger: Callback konnte nicht angelegt werden.");
|
||||
|
||||
Reference in New Issue
Block a user