mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-11-11 02:06:38 +08:00
[fix] repair portable service paths after upgrades, refs #146
This commit is contained in:
+29
-10
@@ -43,8 +43,9 @@ bool InstallServiceWithElevation() {
|
||||
executable_path.parent_path() / L"crossdesk_service.exe";
|
||||
const std::filesystem::path helper_path =
|
||||
executable_path.parent_path() / L"crossdesk_session_helper.exe";
|
||||
if (!std::filesystem::exists(service_path) ||
|
||||
!std::filesystem::exists(helper_path)) {
|
||||
std::error_code error;
|
||||
if (!std::filesystem::is_regular_file(service_path, error) ||
|
||||
!std::filesystem::is_regular_file(helper_path, error)) {
|
||||
LOG_ERROR("Portable service install failed: service binaries missing, "
|
||||
"service={}, helper={}",
|
||||
service_path.string(), helper_path.string());
|
||||
@@ -53,7 +54,7 @@ bool InstallServiceWithElevation() {
|
||||
|
||||
const std::wstring executable = executable_path.wstring();
|
||||
const std::wstring working_dir = executable_path.parent_path().wstring();
|
||||
constexpr wchar_t parameters[] = L"--service-install";
|
||||
constexpr wchar_t parameters[] = L"--service-repair";
|
||||
|
||||
SHELLEXECUTEINFOW execute_info{};
|
||||
execute_info.cbSize = sizeof(execute_info);
|
||||
@@ -86,11 +87,8 @@ bool InstallServiceWithElevation() {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool started = StartCrossDeskService();
|
||||
if (!started) {
|
||||
LOG_WARN("Portable service installed but start failed");
|
||||
}
|
||||
return IsCrossDeskServiceInstalled() && started;
|
||||
return QueryCrossDeskServiceRegistration(service_path.wstring()) ==
|
||||
ServiceRegistrationState::current;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -100,14 +98,34 @@ void GuiApplication::CheckPortableWindowsService() {
|
||||
return;
|
||||
}
|
||||
portable_service_prompt_checked_ = true;
|
||||
portable_service_installed_ = IsCrossDeskServiceInstalled();
|
||||
if (portable_service_installed_ || portable_service_prompt_suppressed_) {
|
||||
const auto executable_path = GetCurrentExecutablePath();
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
const auto registration = QueryCrossDeskServiceRegistration(
|
||||
executable_path.empty() ? std::wstring{}
|
||||
: (executable_path.parent_path() /
|
||||
L"crossdesk_service.exe").wstring(),
|
||||
&error);
|
||||
portable_service_installed_ = registration == ServiceRegistrationState::current;
|
||||
portable_service_needs_repair_ =
|
||||
registration == ServiceRegistrationState::repair_required ||
|
||||
registration == ServiceRegistrationState::query_failed;
|
||||
if (registration == ServiceRegistrationState::query_failed) {
|
||||
LOG_WARN("Could not validate portable service registration, error={}", error);
|
||||
}
|
||||
// Suppressing an optional first-install prompt must never hide a broken
|
||||
// registration left behind by a portable upgrade.
|
||||
if (portable_service_installed_ ||
|
||||
(!portable_service_needs_repair_ && portable_service_prompt_suppressed_)) {
|
||||
return;
|
||||
}
|
||||
|
||||
portable_service_install_state_.store(PortableServiceInstallState::idle,
|
||||
std::memory_order_relaxed);
|
||||
show_portable_service_install_window_ = true;
|
||||
if (portable_service_needs_repair_) {
|
||||
portable_service_do_not_remind_ = false;
|
||||
ShowMainWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void GuiApplication::StartPortableWindowsServiceInstall() {
|
||||
@@ -124,6 +142,7 @@ void GuiApplication::StartPortableWindowsServiceInstall() {
|
||||
}
|
||||
|
||||
JoinPortableWindowsServiceInstallThread();
|
||||
portable_service_installed_ = false;
|
||||
portable_service_install_thread_ = std::thread([this]() {
|
||||
const bool installed = InstallServiceWithElevation();
|
||||
portable_service_install_state_.store(
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <Userenv.h>
|
||||
#include <WtsApi32.h>
|
||||
#include <sddl.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
@@ -242,7 +243,8 @@ bool GrantCrossDeskServiceStartAccessToAuthenticatedUsers(SC_HANDLE service) {
|
||||
}
|
||||
|
||||
EXPLICIT_ACCESSW access{};
|
||||
access.grfAccessPermissions = SERVICE_START | SERVICE_QUERY_STATUS;
|
||||
access.grfAccessPermissions =
|
||||
SERVICE_START | SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG;
|
||||
access.grfAccessMode = SET_ACCESS;
|
||||
access.grfInheritance = NO_INHERITANCE;
|
||||
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
@@ -2637,6 +2639,75 @@ std::string CrossDeskServiceHost::SendSecureDesktopTextInput(
|
||||
BuildSecureTextInputCommand(text, target.stage, target.desktop), 1000);
|
||||
}
|
||||
|
||||
ServiceRegistrationState QueryCrossDeskServiceRegistration(
|
||||
const std::wstring& binary_path, DWORD* error_code) {
|
||||
if (error_code) *error_code = ERROR_SUCCESS;
|
||||
const auto failed = [error_code](DWORD error) {
|
||||
if (error_code) *error_code = error;
|
||||
return ServiceRegistrationState::query_failed;
|
||||
};
|
||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||
if (!manager) return failed(GetLastError());
|
||||
SC_HANDLE service =
|
||||
OpenServiceW(manager, kCrossDeskServiceName, SERVICE_QUERY_CONFIG);
|
||||
const DWORD open_error = service ? ERROR_SUCCESS : GetLastError();
|
||||
CloseServiceHandle(manager);
|
||||
if (!service) {
|
||||
return open_error == ERROR_SERVICE_DOES_NOT_EXIST
|
||||
? ServiceRegistrationState::not_installed
|
||||
: failed(open_error);
|
||||
}
|
||||
|
||||
// QueryServiceConfig documents an 8 KiB maximum. A single query also avoids
|
||||
// a size-query race with an installer changing the registration.
|
||||
alignas(QUERY_SERVICE_CONFIGW) BYTE buffer[8192]{};
|
||||
auto* config = reinterpret_cast<QUERY_SERVICE_CONFIGW*>(buffer);
|
||||
DWORD needed = 0;
|
||||
const bool queried = QueryServiceConfigW(service, config, sizeof(buffer),
|
||||
&needed) != FALSE;
|
||||
const DWORD query_error = queried ? ERROR_SUCCESS : GetLastError();
|
||||
CloseServiceHandle(service);
|
||||
if (!queried) return failed(query_error);
|
||||
|
||||
if (!config->lpBinaryPathName || binary_path.empty() ||
|
||||
config->dwStartType == SERVICE_DISABLED) {
|
||||
return ServiceRegistrationState::repair_required;
|
||||
}
|
||||
const DWORD expanded_size =
|
||||
ExpandEnvironmentStringsW(config->lpBinaryPathName, nullptr, 0);
|
||||
if (!expanded_size) return failed(GetLastError());
|
||||
std::vector<wchar_t> expanded(expanded_size);
|
||||
const DWORD expanded_length = ExpandEnvironmentStringsW(
|
||||
config->lpBinaryPathName, expanded.data(), expanded_size);
|
||||
if (!expanded_length) return failed(GetLastError());
|
||||
if (expanded_length > expanded_size) return failed(ERROR_INSUFFICIENT_BUFFER);
|
||||
const std::wstring command(expanded.data());
|
||||
const auto first = command.find_first_not_of(L" \t");
|
||||
if (first == std::wstring::npos)
|
||||
return ServiceRegistrationState::repair_required;
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(command.c_str() + first, &argc);
|
||||
if (!argv) return failed(GetLastError());
|
||||
const bool valid_command = argc == 2 && std::wstring(argv[1]) == L"--service";
|
||||
const std::filesystem::path registered = argc > 0 ? argv[0] : L"";
|
||||
LocalFree(argv);
|
||||
const std::filesystem::path expected(binary_path);
|
||||
const auto registered_path = registered.lexically_normal().make_preferred().wstring();
|
||||
const auto expected_path = expected.lexically_normal().make_preferred().wstring();
|
||||
if (!valid_command ||
|
||||
CompareStringOrdinal(registered_path.c_str(), -1, expected_path.c_str(),
|
||||
-1, TRUE) != CSTR_EQUAL) {
|
||||
return ServiceRegistrationState::repair_required;
|
||||
}
|
||||
std::error_code error;
|
||||
if (!std::filesystem::is_regular_file(expected, error) ||
|
||||
!std::filesystem::is_regular_file(
|
||||
expected.parent_path() / L"crossdesk_session_helper.exe", error)) {
|
||||
return ServiceRegistrationState::repair_required;
|
||||
}
|
||||
return ServiceRegistrationState::current;
|
||||
}
|
||||
|
||||
bool InstallCrossDeskService(const std::wstring& binary_path) {
|
||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (manager == nullptr) {
|
||||
@@ -2734,6 +2805,37 @@ bool IsCrossDeskServiceInstalled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RepairCrossDeskService(const std::wstring& binary_path) {
|
||||
const std::filesystem::path service_path(binary_path);
|
||||
std::error_code error;
|
||||
if (!std::filesystem::is_regular_file(service_path, error) ||
|
||||
!std::filesystem::is_regular_file(
|
||||
service_path.parent_path() / L"crossdesk_session_helper.exe", error)) {
|
||||
LOG_ERROR("Service repair failed: service binaries missing");
|
||||
return false;
|
||||
}
|
||||
// Updating ImagePath does not replace an already running process. Keep the
|
||||
// existing service identity and unattended profile, then restart it so the
|
||||
// service and its helpers all come from the current directory.
|
||||
if (!InstallCrossDeskService(binary_path) || !StopCrossDeskService() ||
|
||||
!StartCrossDeskService()) {
|
||||
return false;
|
||||
}
|
||||
const ULONGLONG deadline = GetTickCount64() + 10000;
|
||||
while (GetTickCount64() < deadline) {
|
||||
const auto response = Json::parse(QueryCrossDeskService("ping", 500),
|
||||
nullptr, false);
|
||||
if (response.is_object() && response.value("ok", false) &&
|
||||
response.value("reply", std::string{}) == "pong") {
|
||||
return QueryCrossDeskServiceRegistration(binary_path) ==
|
||||
ServiceRegistrationState::current;
|
||||
}
|
||||
Sleep(100);
|
||||
}
|
||||
LOG_ERROR("Service repair failed: service did not become ready");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StartCrossDeskService() {
|
||||
SC_HANDLE manager = OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT);
|
||||
if (manager == nullptr) {
|
||||
|
||||
@@ -186,7 +186,16 @@ class CrossDeskServiceHost {
|
||||
};
|
||||
|
||||
bool IsCrossDeskServiceInstalled();
|
||||
enum class ServiceRegistrationState {
|
||||
not_installed,
|
||||
current,
|
||||
repair_required,
|
||||
query_failed,
|
||||
};
|
||||
ServiceRegistrationState QueryCrossDeskServiceRegistration(
|
||||
const std::wstring& binary_path, DWORD* error_code = nullptr);
|
||||
bool InstallCrossDeskService(const std::wstring& binary_path);
|
||||
bool RepairCrossDeskService(const std::wstring& binary_path);
|
||||
bool UninstallCrossDeskService();
|
||||
bool StartCrossDeskService();
|
||||
bool StopCrossDeskService(DWORD timeout_ms = 20000);
|
||||
|
||||
Reference in New Issue
Block a user