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