- 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
300 lines
8.6 KiB
C++
300 lines
8.6 KiB
C++
#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;
|
|
}
|
|
}
|