diff options
| author | Dan Engelbrecht <[email protected]> | 2025-01-08 13:49:56 +0100 |
|---|---|---|
| committer | Dan Engelbrecht <[email protected]> | 2025-01-08 13:49:56 +0100 |
| commit | 995aec217bbb26c9c2a701cc77edb067ffbf8d36 (patch) | |
| tree | 2da2d3fd806547bd9f38bc190514abbf9fdb6361 /src | |
| parent | check if service is already installed before attempting install (diff) | |
| download | zen-995aec217bbb26c9c2a701cc77edb067ffbf8d36.tar.xz zen-995aec217bbb26c9c2a701cc77edb067ffbf8d36.zip | |
add ServiceLevel for service processes: User, AllUsers and Service
Diffstat (limited to 'src')
| -rw-r--r-- | src/zen/cmds/service_cmd.cpp | 129 | ||||
| -rw-r--r-- | src/zen/cmds/service_cmd.h | 1 | ||||
| -rw-r--r-- | src/zencore/include/zencore/process.h | 1 | ||||
| -rw-r--r-- | src/zencore/process.cpp | 8 | ||||
| -rw-r--r-- | src/zenutil/include/zenutil/service.h | 17 | ||||
| -rw-r--r-- | src/zenutil/service.cpp | 391 |
6 files changed, 483 insertions, 64 deletions
diff --git a/src/zen/cmds/service_cmd.cpp b/src/zen/cmds/service_cmd.cpp index aede68573..7727ed8a2 100644 --- a/src/zen/cmds/service_cmd.cpp +++ b/src/zen/cmds/service_cmd.cpp @@ -120,6 +120,12 @@ ServiceCommand::ServiceCommand() m_StatusOptions.positional_help("name"); m_InstallOptions.add_options()("h,help", "Print help"); + m_InstallOptions.add_option("", + "l", + "service-level", + "Service level: CurrentUser, AllUsers, System. Default is CurrentUser", + cxxopts::value(m_ServiceLevel), + "<level>"); m_InstallOptions.add_option("", "s", "executable", "Path to server executable", cxxopts::value(m_ServerExecutable), "<path>"); m_InstallOptions.add_option("", "n", @@ -209,19 +215,29 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE("Can't get information about service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); return 1; } - ZEN_CONSOLE( - "Service '{}':\n" - " Status: {}\n" - " Executable: {}\n" - " CommandLineOptions: {}\n" - " Display Name: {}\n" - " Description: {}", - m_ServiceName, - ToString(Info.Status), - Info.Spec.ExecutablePath, - Info.Spec.CommandLineOptions, - Info.Spec.DisplayName, - Info.Spec.Description); + if (Info.Status == ServiceStatus::NotInstalled) + { + ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName); + return 0; + } + else + { + ZEN_CONSOLE( + "Service '{}':\n" + " Status: {}\n" + " Level: {}\n" + " Executable: {}\n" + " CommandLineOptions: {}\n" + " Display Name: {}\n" + " Description: {}", + m_ServiceName, + ToString(Info.Status), + ToString(Info.Spec.ServiceLevel), + Info.Spec.ExecutablePath, + Info.Spec.CommandLineOptions, + Info.Spec.DisplayName, + Info.Spec.Description); + } } if (SubOption == &m_InstallOptions) @@ -233,12 +249,14 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) ZEN_CONSOLE( "Service '{}' already installed:\n" " Status: {}\n" + " Level: {}\n" " Executable: {}\n" " CommandLineOptions: {}\n" " Display Name: {}\n" " Description: {}", m_ServiceName, ToString(Info.Status), + ToString(Info.Spec.ServiceLevel), Info.Spec.ExecutablePath, Info.Spec.CommandLineOptions, Info.Spec.DisplayName, @@ -246,8 +264,18 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) return 1; } + std::optional<ServiceLevel> Level = ServiceLevel::CurrentUser; + if (!m_ServiceLevel.empty()) + { + Level = FromString(m_ServiceLevel); + } + if (!Level.has_value()) + { + throw zen::OptionParseException("invalid service level"); + } + #if ZEN_PLATFORM_WINDOWS - if (!WinIsElevated()) + if (!WinIsElevated() && (Level.value() != ServiceLevel::CurrentUser)) { return WinRelaunchElevated(); } @@ -259,12 +287,12 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) m_ServerExecutable = ExePath; } m_ServerExecutable = std::filesystem::absolute(m_ServerExecutable); - - Ec = InstallService(m_ServiceName, - ServiceSpec{.ExecutablePath = m_ServerExecutable, - .CommandLineOptions = GlobalOptions.PassthroughCommandLine, - .DisplayName = m_ServiceDisplayName, - .Description = m_ServiceDescription}); + Ec = InstallService(m_ServiceName, + ServiceSpec{.ServiceLevel = Level.value(), + .ExecutablePath = m_ServerExecutable, + .CommandLineOptions = GlobalOptions.PassthroughCommandLine, + .DisplayName = m_ServiceDisplayName, + .Description = m_ServiceDescription}); if (Ec) { ZEN_CONSOLE("Failed to install service '{}' using '{}' . Reason: '{}'", m_ServiceName, m_ServerExecutable, Ec.message()); @@ -275,13 +303,26 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (SubOption == &m_UninstallOptions) { + ServiceInfo Info; + std::error_code Ec = QueryInstalledService(m_ServiceName, Info); + if (Ec) + { + ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); + return 1; + } + if (Info.Status == ServiceStatus::NotInstalled) + { + ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName); + return 0; + } + #if ZEN_PLATFORM_WINDOWS - if (!WinIsElevated()) + if (!WinIsElevated() && (Info.Spec.ServiceLevel != ServiceLevel::CurrentUser)) { return WinRelaunchElevated(); } #endif // ZEN_PLATFORM_WINDOWS - std::error_code Ec = UninstallService(m_ServiceName); + Ec = UninstallService(m_ServiceName, Info.Spec.ServiceLevel); if (Ec) { ZEN_CONSOLE("Failed to uninstall service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); @@ -292,13 +333,31 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (SubOption == &m_StartOptions) { + ServiceInfo Info; + std::error_code Ec = QueryInstalledService(m_ServiceName, Info); + if (Ec) + { + ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); + return 1; + } + if (Info.Status == ServiceStatus::NotInstalled) + { + ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName); + return 1; + } + if (Info.Status != ServiceStatus::Stopped) + { + ZEN_CONSOLE("Service '{}' is already running", m_ServiceName); + return 1; + } + #if ZEN_PLATFORM_WINDOWS - if (!WinIsElevated()) + if (!WinIsElevated() && (Info.Spec.ServiceLevel != ServiceLevel::CurrentUser)) { return WinRelaunchElevated(); } #endif // ZEN_PLATFORM_WINDOWS - std::error_code Ec = StartService(m_ServiceName); + Ec = StartService(m_ServiceName, Info.Spec.ServiceLevel); if (Ec) { ZEN_CONSOLE("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); @@ -309,13 +368,31 @@ ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) if (SubOption == &m_StopOptions) { + ServiceInfo Info; + std::error_code Ec = QueryInstalledService(m_ServiceName, Info); + if (Ec) + { + ZEN_CONSOLE("Failed to inspect installed service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); + return 1; + } + if (Info.Status == ServiceStatus::NotInstalled) + { + ZEN_CONSOLE("Service '{}' is not installed", m_ServiceName); + return 1; + } + if (Info.Status != ServiceStatus::Running) + { + ZEN_CONSOLE("Service '{}' is not running", m_ServiceName); + return 1; + } + #if ZEN_PLATFORM_WINDOWS - if (!WinIsElevated()) + if (!WinIsElevated() && (Info.Spec.ServiceLevel != ServiceLevel::CurrentUser)) { return WinRelaunchElevated(); } #endif // ZEN_PLATFORM_WINDOWS - std::error_code Ec = StopService(m_ServiceName); + Ec = StopService(m_ServiceName, Info.Spec.ServiceLevel); if (Ec) { ZEN_CONSOLE("Failed to stop service '{}'. Reason: '{}'", m_ServiceName, Ec.message()); diff --git a/src/zen/cmds/service_cmd.h b/src/zen/cmds/service_cmd.h index 15273e952..e058915e5 100644 --- a/src/zen/cmds/service_cmd.h +++ b/src/zen/cmds/service_cmd.h @@ -32,6 +32,7 @@ private: cxxopts::Options m_InstallOptions{ "install", "Install zenserver as a service. Arguments following \" -- \" will be added as parameters to the installed service."}; + std::string m_ServiceLevel; std::filesystem::path m_ServerExecutable; std::string m_ServiceDisplayName = "Unreal Zen Storage Service"; std::string m_ServiceDescription; diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h index d1394cd9a..42b997c39 100644 --- a/src/zencore/include/zencore/process.h +++ b/src/zencore/include/zencore/process.h @@ -51,6 +51,7 @@ struct CreateProcOptions Flag_NewConsole = 1 << 0, Flag_Elevated = 1 << 1, Flag_Unelevated = 1 << 2, + Flag_NoConsole = 1 << 3, }; const std::filesystem::path* WorkingDirectory = nullptr; diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp index b1da034d2..11273a2b9 100644 --- a/src/zencore/process.cpp +++ b/src/zencore/process.cpp @@ -443,6 +443,10 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma { CreationFlags |= CREATE_NEW_CONSOLE; } + if (Options.Flags & CreateProcOptions::Flag_NoConsole) + { + CreationFlags |= CREATE_NO_WINDOW; + } const wchar_t* WorkingDir = nullptr; if (Options.WorkingDirectory != nullptr) @@ -588,6 +592,10 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C { CreateProcFlags |= CREATE_NEW_CONSOLE; } + if (Options.Flags & CreateProcOptions::Flag_NoConsole) + { + CreateProcFlags |= CREATE_NO_WINDOW; + } ExtendableWideStringBuilder<256> CommandLineZ; CommandLineZ << CommandLine; diff --git a/src/zenutil/include/zenutil/service.h b/src/zenutil/include/zenutil/service.h index cd769b67b..1a0ebc235 100644 --- a/src/zenutil/include/zenutil/service.h +++ b/src/zenutil/include/zenutil/service.h @@ -6,8 +6,16 @@ namespace zen { +enum class ServiceLevel +{ + CurrentUser, + AllUsers, + SystemService +}; + struct ServiceSpec { + ServiceLevel ServiceLevel = ServiceLevel::CurrentUser; std::filesystem::path ExecutablePath; std::string CommandLineOptions; std::string DisplayName; @@ -26,6 +34,9 @@ enum class ServiceStatus Resuming }; +std::string_view ToString(ServiceLevel Level); +std::optional<ServiceLevel> FromString(const std::string& Level); + struct ServiceInfo { ServiceStatus Status; @@ -35,8 +46,8 @@ struct ServiceInfo std::string_view ToString(ServiceStatus Status); std::error_code InstallService(std::string_view ServiceName, const ServiceSpec& Spec); -std::error_code UninstallService(std::string_view ServiceName); +std::error_code UninstallService(std::string_view ServiceName, ServiceLevel Level); std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo); -std::error_code StartService(std::string_view ServiceName); -std::error_code StopService(std::string_view ServiceName); +std::error_code StartService(std::string_view ServiceName, ServiceLevel Level); +std::error_code StopService(std::string_view ServiceName, ServiceLevel Level); } // namespace zen diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp index fd96af0c8..44aa50494 100644 --- a/src/zenutil/service.cpp +++ b/src/zenutil/service.cpp @@ -3,8 +3,14 @@ #include <zenutil/service.h> #include <zencore/except.h> +#include <zencore/process.h> #include <zencore/scopeguard.h> #include <zencore/zencore.h> +#include <zenutil/zenserverprocess.h> + +ZEN_THIRD_PARTY_INCLUDES_START +#include <gsl/gsl-lite.hpp> +ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include <zencore/windows.h> @@ -19,6 +25,46 @@ namespace zen { using namespace std::literals; +namespace { + bool SplitExecutableAndArgs(const std::wstring& ExeAndArgs, std::filesystem::path& OutExecutablePath, std::string& OutArguments) + { + if (ExeAndArgs.size()) + { + if (ExeAndArgs[0] == '"') + { + std::wstring::size_type ExecutableEnd = ExeAndArgs.find('"', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutExecutablePath = ExeAndArgs; + return true; + } + else + { + OutExecutablePath = ExeAndArgs.substr(0, ExecutableEnd + 1); + OutArguments = WideToUtf8(ExeAndArgs.substr(ExecutableEnd + 1 + ExeAndArgs[ExecutableEnd + 1] == ' ' ? 1 : 0)); + return true; + } + } + else + { + std::wstring::size_type ExecutableEnd = ExeAndArgs.find(' ', 1); + if (ExecutableEnd == std::wstring::npos) + { + OutExecutablePath = ExeAndArgs; + return true; + } + else + { + OutExecutablePath = ExeAndArgs.substr(0, ExecutableEnd); + OutArguments = WideToUtf8(ExeAndArgs.substr(ExecutableEnd + 1)); + return true; + } + } + } + return false; + } +} // namespace + #if ZEN_PLATFORM_MAC namespace { @@ -202,6 +248,44 @@ namespace { #endif // ZEN_PLATFORM_MAC std::string_view +ToString(ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return "Current User"sv; + case ServiceLevel::AllUsers: + return "All Users"sv; + case ServiceLevel::SystemService: + return "System Service"sv; + default: + ZEN_ASSERT(false); + return ""sv; + } +} + +std::optional<ServiceLevel> +FromString(const std::string& Level) +{ + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::CurrentUser)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "CurrentUser") == 0) + { + return ServiceLevel::CurrentUser; + } + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::AllUsers)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "AllUsers") == 0) + { + return ServiceLevel::AllUsers; + } + if (StrCaseCompare(Level.c_str(), std::string(ToString(ServiceLevel::SystemService)).c_str()) == 0 || + StrCaseCompare(Level.c_str(), "SystemService") == 0) + { + return ServiceLevel::SystemService; + } + return {}; +} + +std::string_view ToString(ServiceStatus Status) { switch (Status) @@ -231,7 +315,36 @@ ToString(ServiceStatus Status) #if ZEN_PLATFORM_WINDOWS std::error_code -InstallService(std::string_view ServiceName, const ServiceSpec& Spec) +InstallRunService(std::string_view ServiceName, const ServiceSpec& Spec) +{ + HKEY RegKey = NULL; + if (LSTATUS Status = RegOpenKey(Spec.ServiceLevel == ServiceLevel::AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", + &RegKey); + Status == ERROR_SUCCESS) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + std::wstring PathIncludingArgs = Spec.ExecutablePath.wstring(); + if (!Spec.CommandLineOptions.empty()) + { + PathIncludingArgs += (L" " + Utf8ToWide(Spec.CommandLineOptions)); + } + if (Status = RegSetValueEx(RegKey, + Utf8ToWide(ServiceName).c_str(), + 0, + REG_SZ, + (BYTE*)PathIncludingArgs.c_str(), + DWORD((PathIncludingArgs.length() * 2) + 1)); + Status == ERROR_SUCCESS) + { + return {}; + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +InstallSystemService(std::string_view ServiceName, const ServiceSpec& Spec) { // Get a handle to the SCM database. @@ -300,7 +413,40 @@ InstallService(std::string_view ServiceName, const ServiceSpec& Spec) } std::error_code -UninstallService(std::string_view ServiceName) +InstallService(std::string_view ServiceName, const ServiceSpec& Spec) +{ + if (Spec.ServiceLevel == ServiceLevel::SystemService) + { + return InstallSystemService(ServiceName, Spec); + } + return InstallRunService(ServiceName, Spec); +} + +std::error_code +UninstallRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (LSTATUS Status = + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey); + Status == ERROR_SUCCESS) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + if (Status = RegDeleteValue(RegKey, Utf8ToWide(ServiceName).c_str()); Status == ERROR_SUCCESS) + { + return std::error_code{}; + } + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +UninstallSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -345,7 +491,57 @@ UninstallService(std::string_view ServiceName) } std::error_code -QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +UninstallService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return UninstallRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return UninstallRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return UninstallSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + +bool +QueryRunServiceStatus(bool AllUsers, std::string_view ServiceName, ServiceInfo& OutInfo) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + OutInfo.Spec.ServiceLevel = AllUsers ? ServiceLevel::AllUsers : ServiceLevel::CurrentUser; + std::wstring PathIncludingArgs(Value); + + (void)SplitExecutableAndArgs(PathIncludingArgs, OutInfo.Spec.ExecutablePath, OutInfo.Spec.CommandLineOptions); + + OutInfo.Spec.DisplayName = ServiceName; + OutInfo.Spec.Description = ""; + OutInfo.Status = ServiceStatus::Stopped; + ProcessHandle Process; + std::error_code Ec = FindProcess(OutInfo.Spec.ExecutablePath, Process); + if (!Ec) + { + OutInfo.Status = ServiceStatus::Running; + } + return true; + } + } + return false; +} + +std::error_code +QuerySystemServiceStatus(std::string_view ServiceName, ServiceInfo& OutInfo) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -389,36 +585,7 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::wstring BinaryWithArguments(ServiceConfig->lpBinaryPathName); - if (BinaryWithArguments.size()) - { - if (BinaryWithArguments[0] == '"') - { - std::wstring::size_type ExecutableEnd = BinaryWithArguments.find('"', 1); - if (ExecutableEnd == std::wstring::npos) - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments; - } - else - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd + 1); - OutInfo.Spec.CommandLineOptions = - WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1 + BinaryWithArguments[ExecutableEnd + 1] == ' ' ? 1 : 0)); - } - } - else - { - std::wstring::size_type ExecutableEnd = BinaryWithArguments.find(' ', 1); - if (ExecutableEnd == std::wstring::npos) - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments; - } - else - { - OutInfo.Spec.ExecutablePath = BinaryWithArguments.substr(0, ExecutableEnd); - OutInfo.Spec.CommandLineOptions = WideToUtf8(BinaryWithArguments.substr(ExecutableEnd + 1)); - } - } - } + (void)SplitExecutableAndArgs(BinaryWithArguments, OutInfo.Spec.ExecutablePath, OutInfo.Spec.CommandLineOptions); OutInfo.Spec.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName); SERVICE_STATUS ServiceStatus; @@ -480,7 +647,54 @@ QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) } std::error_code -StartService(std::string_view ServiceName) +QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo) +{ + if (QueryRunServiceStatus(/*AllUsers*/ false, ServiceName, OutInfo)) + { + return {}; + } + if (QueryRunServiceStatus(/*AllUsers*/ true, ServiceName, OutInfo)) + { + return {}; + } + return QuerySystemServiceStatus(ServiceName, OutInfo); +} + +std::error_code +StartRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + std::wstring PathIncludingArgs(Value); + + std::filesystem::path ExecutablePath; + std::string CommandLineOptions; + if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) + { + ProcessHandle Proc; + Proc.Initialize(CreateProc(ExecutablePath, WideToUtf8(PathIncludingArgs), {.Flags = CreateProcOptions::Flag_NoConsole})); + if (Proc.IsValid()) + { + return {}; + } + MakeErrorCode(ERROR_PATH_NOT_FOUND); + } + MakeErrorCode(ERROR_INVALID_PARAMETER); + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +StartSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -520,7 +734,96 @@ StartService(std::string_view ServiceName) } std::error_code -StopService(std::string_view ServiceName) +StartService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return StartRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return StartRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return StartSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + +std::error_code +StopRunService(bool AllUsers, std::string_view ServiceName) +{ + HKEY RegKey = NULL; + if (ERROR_SUCCESS == + RegOpenKey(AllUsers ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, L"Software\\Microsoft\\Windows\\CurrentVersion\\Run", &RegKey)) + { + auto _ = MakeGuard([&]() { RegCloseKey(RegKey); }); + TCHAR Value[4096]; + DWORD Type; + DWORD Size = sizeof(Value); + if (ERROR_SUCCESS == RegQueryValueEx(RegKey, Utf8ToWide(ServiceName).c_str(), 0, &Type, (BYTE*)Value, &Size)) + { + std::wstring PathIncludingArgs(Value); + + std::filesystem::path ExecutablePath; + std::string CommandLineOptions; + if (SplitExecutableAndArgs(PathIncludingArgs, ExecutablePath, CommandLineOptions)) + { + ProcessHandle Proc; + std::error_code Ec = FindProcess(ExecutablePath, Proc); + if (Ec) + { + return Ec; + } + else + { + // This is hacky and checks if the running service is a zenserver instance and tries to shut down using the shutdown + // event + ExtendableStringBuilder<32> ChildShutdownEventName; + ZenServerState State; + if (State.InitializeReadOnly()) + { + State.Snapshot([&](const ZenServerState::ZenServerEntry& Entry) { + if (Entry.Pid == gsl::narrow<uint32_t>(Proc.Pid())) + { + ChildShutdownEventName << "Zen_" << Entry.EffectiveListenPort; + ChildShutdownEventName << "_Shutdown"; + } + }); + if (ChildShutdownEventName.Size() > 0) + { + NamedEvent Event(ChildShutdownEventName); + Ec = Event.Set(); + if (Ec) + { + return Ec; + } + return {}; + } + } + // This only works for a running process that does not already have a console attached - zenserver does have one + // hence the attempt to shut down using event above + if (AttachConsole(Proc.Pid())) + { + if (SetConsoleCtrlHandler(NULL, TRUE)) + { + if (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) + { + return {}; + } + } + } + return MakeErrorCodeFromLastError(); + } + } + return MakeErrorCode(ERROR_INVALID_PARAMETER); + } + } + return MakeErrorCodeFromLastError(); +} + +std::error_code +StopSystemService(std::string_view ServiceName) { // Get a handle to the SCM database. SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer @@ -558,6 +861,24 @@ StopService(std::string_view ServiceName) return {}; } + +std::error_code +StopService(std::string_view ServiceName, ServiceLevel Level) +{ + switch (Level) + { + case ServiceLevel::CurrentUser: + return StopRunService(/*AllUsers*/ false, ServiceName); + case ServiceLevel::AllUsers: + return StopRunService(/*AllUsers*/ true, ServiceName); + case ServiceLevel::SystemService: + return StopSystemService(ServiceName); + default: + ZEN_ASSERT(false); + return {}; + } +} + #else # if 0 |