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:
2026-08-11 16:32:33 +02:00
parent ee88f1ff30
commit 4c51989600
37 changed files with 4885 additions and 1475 deletions
+227
View File
@@ -0,0 +1,227 @@
#include "PluginInstaller.h"
#include "Config.h"
#include "resource.h"
#include <windows.h>
#include <shlobj.h>
#include <vector>
namespace
{
constexpr wchar_t kDllFileName[] = L"PlandentMstscScriptHook.dll";
std::wstring JoinPath(const std::wstring& left, const std::wstring& right)
{
if (left.empty())
{
return right;
}
if (left.back() == L'\\' || left.back() == L'/')
{
return left + right;
}
return left + L"\\" + right;
}
std::wstring ModuleDirectory()
{
std::vector<wchar_t> buffer(32768, L'\0');
DWORD len = GetModuleFileNameW(nullptr, buffer.data(), static_cast<DWORD>(buffer.size()));
if (len == 0 || len >= buffer.size())
{
return {};
}
std::wstring path(buffer.data(), len);
size_t slash = path.find_last_of(L"\\/");
return slash == std::wstring::npos ? std::wstring{} : path.substr(0, slash);
}
bool EnsureDirectory(const std::wstring& path, std::wstring& error)
{
if (CreateDirectoryW(path.c_str(), nullptr) || GetLastError() == ERROR_ALREADY_EXISTS)
{
return true;
}
error = L"Verzeichnis konnte nicht erstellt werden: " + path + L" (Fehler " + std::to_wstring(GetLastError()) + L")";
return false;
}
bool ExtractResourceToFile(const std::wstring& destination, std::wstring& error)
{
HMODULE module = GetModuleHandleW(nullptr);
HRSRC resource = FindResourceW(module, MAKEINTRESOURCEW(IDR_PLUGIN_DLL), RT_RCDATA);
if (resource == nullptr)
{
// Fallback für Entwicklung: Plugin-DLL direkt neben der EXE akzeptieren.
std::wstring adjacent = JoinPath(ModuleDirectory(), kDllFileName);
DWORD attr = GetFileAttributesW(adjacent.c_str());
if (attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0)
{
if (CopyFileW(adjacent.c_str(), destination.c_str(), FALSE))
{
return true;
}
error = L"Plugin-DLL konnte nicht kopiert werden (Fehler " + std::to_wstring(GetLastError()) + L").";
return false;
}
error = L"Die native Plugin-DLL ist weder eingebettet noch neben der Konfigurations-EXE vorhanden.";
return false;
}
HGLOBAL loaded = LoadResource(module, resource);
DWORD size = SizeofResource(module, resource);
const void* data = loaded != nullptr ? LockResource(loaded) : nullptr;
if (data == nullptr || size == 0)
{
error = L"Eingebettete Plugin-DLL konnte nicht gelesen werden.";
return false;
}
HANDLE file = CreateFileW(
destination.c_str(),
GENERIC_WRITE,
0,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr);
if (file == INVALID_HANDLE_VALUE)
{
error = L"Plugin-DLL konnte nicht geschrieben werden: " + destination + L" (Fehler " + std::to_wstring(GetLastError()) + L")";
return false;
}
DWORD written = 0;
BOOL ok = WriteFile(file, data, size, &written, nullptr);
DWORD writeError = ok ? ERROR_SUCCESS : GetLastError();
CloseHandle(file);
if (!ok || written != size)
{
DeleteFileW(destination.c_str());
error = L"Plugin-DLL konnte nicht vollständig geschrieben werden (Fehler " + std::to_wstring(writeError) + L").";
return false;
}
return true;
}
}
namespace installer
{
std::wstring InstallDirectory()
{
PWSTR localAppData = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &localAppData);
if (FAILED(hr) || localAppData == nullptr)
{
return {};
}
std::wstring result = std::wstring(localAppData) + L"\\Plandent\\MstscScriptHook";
CoTaskMemFree(localAppData);
return result;
}
std::wstring InstalledDllPath()
{
return JoinPath(InstallDirectory(), kDllFileName);
}
std::wstring LogPath()
{
return JoinPath(InstallDirectory(), L"plugin.log");
}
bool Install(std::wstring& error)
{
PWSTR localAppData = nullptr;
HRESULT hr = SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &localAppData);
if (FAILED(hr) || localAppData == nullptr)
{
error = L"%LOCALAPPDATA% konnte nicht ermittelt werden.";
return false;
}
std::wstring base(localAppData);
CoTaskMemFree(localAppData);
std::wstring plandent = JoinPath(base, L"Plandent");
std::wstring targetDir = JoinPath(plandent, L"MstscScriptHook");
if (!EnsureDirectory(plandent, error) || !EnsureDirectory(targetDir, error))
{
return false;
}
const std::wstring targetDll = JoinPath(targetDir, kDllFileName);
if (!ExtractResourceToFile(targetDll, error))
{
return false;
}
if (!config::RegisterPlugin(targetDll, error))
{
return false;
}
return true;
}
bool Uninstall(std::wstring& message, bool& fullyRemoved)
{
fullyRemoved = false;
std::wstring error;
if (!config::UnregisterPlugin(error))
{
message = error;
return false;
}
std::wstring dll = InstalledDllPath();
DWORD attr = GetFileAttributesW(dll.c_str());
if (attr == INVALID_FILE_ATTRIBUTES)
{
fullyRemoved = true;
message = L"Plugin-Registrierung wurde entfernt.";
return true;
}
if (DeleteFileW(dll.c_str()))
{
RemoveDirectoryW(InstallDirectory().c_str()); // Darf fehlschlagen, z. B. wenn plugin.log existiert.
fullyRemoved = true;
message = L"Plugin-Registrierung und DLL wurden entfernt.";
return true;
}
DWORD lastError = GetLastError();
if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_ACCESS_DENIED)
{
message = L"Die Registrierung wurde entfernt. Die DLL wird wahrscheinlich noch von mstsc.exe verwendet. "
L"Alle Remote-Desktop-Clients schließen und anschließend erneut auf „Plugin entfernen“ klicken.";
return true;
}
message = L"Registrierung wurde entfernt, die DLL konnte aber nicht gelöscht werden (Fehler " + std::to_wstring(lastError) + L").";
return true;
}
Status GetStatus()
{
Status status;
std::wstring registered = config::GetRegisteredPluginPath();
if (registered.empty())
{
status.text = L"Plugin ist nicht registriert.";
return status;
}
std::wstring expanded = config::ExpandEnvironment(registered);
DWORD attr = GetFileAttributesW(expanded.c_str());
if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
status.text = L"Plugin ist registriert, DLL fehlt jedoch: " + expanded;
return status;
}
status.installed = true;
status.text = L"Plugin ist registriert: " + expanded;
return status;
}
}