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:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user