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
+299
View File
@@ -0,0 +1,299 @@
#include "TestLauncher.h"
#include "Config.h"
#include <algorithm>
#include <cwctype>
#include <vector>
namespace
{
std::wstring QuoteWindowsArgument(const std::wstring& arg)
{
if (arg.empty())
{
return L"\"\"";
}
if (arg.find_first_of(L" \t\n\v\"") == std::wstring::npos)
{
return arg;
}
std::wstring result = L"\"";
size_t backslashes = 0;
for (wchar_t ch : arg)
{
if (ch == L'\\')
{
++backslashes;
continue;
}
if (ch == L'\"')
{
result.append(backslashes * 2 + 1, L'\\');
result.push_back(L'\"');
backslashes = 0;
continue;
}
result.append(backslashes, L'\\');
backslashes = 0;
result.push_back(ch);
}
result.append(backslashes * 2, L'\\');
result.push_back(L'\"');
return result;
}
std::wstring ExtensionLower(const std::wstring& path)
{
const size_t dot = path.find_last_of(L'.');
const size_t slash = path.find_last_of(L"\\/");
if (dot == std::wstring::npos || (slash != std::wstring::npos && dot < slash))
{
return {};
}
std::wstring ext = path.substr(dot);
std::transform(ext.begin(), ext.end(), ext.begin(), [](wchar_t c) { return static_cast<wchar_t>(std::towlower(c)); });
return ext;
}
std::wstring ParentDirectory(const std::wstring& path)
{
const size_t slash = path.find_last_of(L"\\/");
if (slash == std::wstring::npos)
{
return {};
}
if (slash == 2 && path.size() >= 3 && path[1] == L':')
{
return path.substr(0, 3);
}
return path.substr(0, slash);
}
std::wstring EnvironmentValue(const wchar_t* name)
{
DWORD size = GetEnvironmentVariableW(name, nullptr, 0);
if (size == 0)
{
return {};
}
std::vector<wchar_t> buffer(size, L'\0');
if (GetEnvironmentVariableW(name, buffer.data(), size) == 0)
{
return {};
}
return std::wstring(buffer.data());
}
struct LaunchCommand
{
std::wstring application;
std::wstring commandLine;
};
bool BuildCommand(const testlaunch::LaunchSettings& settings, LaunchCommand& command, std::wstring& error)
{
const std::wstring extension = ExtensionLower(settings.path);
if (extension == L".bat" || extension == L".cmd")
{
std::wstring comspec = EnvironmentValue(L"ComSpec");
if (comspec.empty())
{
std::wstring windows = EnvironmentValue(L"SystemRoot");
if (windows.empty())
{
error = L"ComSpec/SystemRoot konnte nicht ermittelt werden.";
return false;
}
comspec = windows + L"\\System32\\cmd.exe";
}
std::wstring inner = QuoteWindowsArgument(settings.path);
if (!settings.arguments.empty())
{
inner += L" " + settings.arguments;
}
command.application = comspec;
command.commandLine = QuoteWindowsArgument(comspec) + L" /D /S /C \"" + inner + L"\"";
return true;
}
if (extension == L".ps1")
{
std::wstring windows = EnvironmentValue(L"SystemRoot");
if (windows.empty())
{
error = L"SystemRoot konnte nicht ermittelt werden.";
return false;
}
std::wstring powershell = windows + L"\\System32\\WindowsPowerShell\\v1.0\\powershell.exe";
command.application = powershell;
command.commandLine = QuoteWindowsArgument(powershell) +
L" -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -File " +
QuoteWindowsArgument(settings.path);
if (!settings.arguments.empty())
{
command.commandLine += L" " + settings.arguments;
}
return true;
}
if (extension == L".exe")
{
command.application = settings.path;
command.commandLine = QuoteWindowsArgument(settings.path);
if (!settings.arguments.empty())
{
command.commandLine += L" " + settings.arguments;
}
return true;
}
error = L"Unterstützt werden .bat, .cmd, .ps1 und .exe.";
return false;
}
std::wstring Win32ErrorMessage(DWORD error)
{
wchar_t* text = nullptr;
DWORD size = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
error,
0,
reinterpret_cast<wchar_t*>(&text),
0,
nullptr);
if (size == 0 || text == nullptr)
{
return L"Win32-Fehler " + std::to_wstring(error);
}
std::wstring result(text, size);
LocalFree(text);
while (!result.empty() && (result.back() == L'\r' || result.back() == L'\n' || result.back() == L' '))
{
result.pop_back();
}
return result + L" (" + std::to_wstring(error) + L")";
}
}
namespace testlaunch
{
TestProcess::~TestProcess()
{
Detach();
}
void TestProcess::CleanupExited()
{
if (process_ != nullptr && WaitForSingleObject(process_, 0) != WAIT_TIMEOUT)
{
CloseHandle(process_);
process_ = nullptr;
processId_ = 0;
}
}
bool TestProcess::IsRunning()
{
CleanupExited();
return process_ != nullptr;
}
void TestProcess::Detach()
{
if (process_ != nullptr)
{
CloseHandle(process_);
process_ = nullptr;
processId_ = 0;
}
}
bool TestProcess::Start(const LaunchSettings& rawSettings, std::wstring& error)
{
CleanupExited();
if (process_ != nullptr)
{
error = L"Der Testprozess läuft bereits.";
return false;
}
LaunchSettings settings = rawSettings;
settings.path = config::ExpandEnvironment(settings.path);
settings.workingDirectory = config::ExpandEnvironment(settings.workingDirectory);
DWORD attr = GetFileAttributesW(settings.path.c_str());
if (settings.path.empty() || attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
{
error = L"Das ausgewählte Programm/Script wurde nicht gefunden: " + settings.path;
return false;
}
LaunchCommand command;
if (!BuildCommand(settings, command, error))
{
return false;
}
std::wstring workingDirectory = settings.workingDirectory.empty()
? ParentDirectory(settings.path)
: settings.workingDirectory;
std::vector<wchar_t> mutableCommand(command.commandLine.begin(), command.commandLine.end());
mutableCommand.push_back(L'\0');
STARTUPINFOW startup{};
startup.cb = sizeof(startup);
if (settings.hidden)
{
startup.dwFlags = STARTF_USESHOWWINDOW;
startup.wShowWindow = SW_HIDE;
}
PROCESS_INFORMATION pi{};
DWORD flags = settings.hidden ? CREATE_NO_WINDOW : 0;
BOOL created = CreateProcessW(
command.application.c_str(),
mutableCommand.data(),
nullptr,
nullptr,
FALSE,
flags,
nullptr,
workingDirectory.empty() ? nullptr : workingDirectory.c_str(),
&startup,
&pi);
if (!created)
{
error = L"Testprozess konnte nicht gestartet werden: " + Win32ErrorMessage(GetLastError());
return false;
}
CloseHandle(pi.hThread);
process_ = pi.hProcess;
processId_ = pi.dwProcessId;
return true;
}
bool TestProcess::Stop(std::wstring& error)
{
CleanupExited();
if (process_ == nullptr)
{
return true;
}
if (!TerminateProcess(process_, 0))
{
error = L"Testprozess konnte nicht beendet werden: " + Win32ErrorMessage(GetLastError());
return false;
}
WaitForSingleObject(process_, 3000);
CloseHandle(process_);
process_ = nullptr;
processId_ = 0;
return true;
}
}