#include "TestLauncher.h" #include "Config.h" #include #include #include 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(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 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(&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 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; } }