aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2024-08-27 17:09:55 +0200
committerDan Engelbrecht <[email protected]>2025-01-08 10:01:23 +0100
commit1747a513dc9dccd5a60e76daebd59cfba4a536d9 (patch)
treef46af5db5c13cbca9c072030ff04e54ba0e1bede /src
parent5.5.17-pre1 (diff)
downloadzen-1747a513dc9dccd5a60e76daebd59cfba4a536d9.tar.xz
zen-1747a513dc9dccd5a60e76daebd59cfba4a536d9.zip
zen `service` command
Diffstat (limited to 'src')
-rw-r--r--src/zen/cmds/service_cmd.cpp308
-rw-r--r--src/zen/cmds/service_cmd.h48
-rw-r--r--src/zen/zen.cpp3
-rw-r--r--src/zenutil/include/zenutil/service.h38
-rw-r--r--src/zenutil/service.cpp393
5 files changed, 790 insertions, 0 deletions
diff --git a/src/zen/cmds/service_cmd.cpp b/src/zen/cmds/service_cmd.cpp
new file mode 100644
index 000000000..a93bac37c
--- /dev/null
+++ b/src/zen/cmds/service_cmd.cpp
@@ -0,0 +1,308 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "service_cmd.h"
+
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/logging.h>
+#include <zencore/zencore.h>
+#include <zenutil/service.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+# include <shellapi.h>
+#endif
+
+ZEN_THIRD_PARTY_INCLUDES_START
+#include <gsl/gsl-lite.hpp>
+ZEN_THIRD_PARTY_INCLUDES_END
+
+#include <memory>
+
+namespace zen {
+
+//////////////////////////////////////////////////////////////////////////
+#if ZEN_PLATFORM_WINDOWS
+BOOL
+WinIsElevated()
+{
+ BOOL fRet = FALSE;
+ HANDLE hToken = NULL;
+ if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken))
+ {
+ TOKEN_ELEVATION Elevation;
+ DWORD cbSize = sizeof(TOKEN_ELEVATION);
+ if (GetTokenInformation(hToken, TokenElevation, &Elevation, sizeof(Elevation), &cbSize))
+ {
+ fRet = Elevation.TokenIsElevated;
+ }
+ }
+ if (hToken)
+ {
+ CloseHandle(hToken);
+ }
+ return fRet;
+}
+
+int
+WinRelaunchElevated()
+{
+ TCHAR CurrentDir[4096];
+ GetCurrentDirectory(4096, CurrentDir);
+
+ ExtendableWideStringBuilder<256> Parameters;
+ std::filesystem::path ExecutablePath = GetRunningExecutablePath();
+ std::wstring CommandLine(GetCommandLine());
+ std::string::size_type ExtraLength = CommandLine[0] == '\"' ? 2 : 0;
+ std::wstring CommandArguments = CommandLine.substr(ExecutablePath.string().length() + ExtraLength + 1);
+
+ ZEN_CONSOLE("Attempting to run '{} {}' elevated...", ExecutablePath, WideToUtf8(CommandArguments));
+
+ SHELLEXECUTEINFO shExInfo = {0};
+ shExInfo.cbSize = sizeof(shExInfo);
+ shExInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE;
+ shExInfo.hwnd = 0;
+ shExInfo.lpVerb = TEXT("runas"); // Operation to perform
+ shExInfo.lpFile = ExecutablePath.c_str(); // Application to start
+ shExInfo.lpParameters = CommandArguments.c_str(); // Additional parameters
+ shExInfo.lpDirectory = CurrentDir;
+ shExInfo.nShow = SW_HIDE;
+ shExInfo.hInstApp = 0;
+
+ DWORD ReturnCode = 1;
+ if (ShellExecuteEx(&shExInfo))
+ {
+ WaitForSingleObject(shExInfo.hProcess, INFINITE);
+ GetExitCodeProcess(shExInfo.hProcess, &ReturnCode);
+ CloseHandle(shExInfo.hProcess);
+ if (ReturnCode == 0)
+ {
+ ZEN_CONSOLE("Elevated execution completed successfully.");
+ }
+ else
+ {
+ ZEN_CONSOLE("Elevated execution completed unsuccessfully, return code: '{}'.", ReturnCode);
+ }
+ }
+ else
+ {
+ ZEN_CONSOLE("Failed to elevated, operation did not complete.");
+ }
+ return (int)ReturnCode;
+}
+
+#endif
+
+ServiceCommand::ServiceCommand()
+{
+ m_Options.add_option("",
+ "v",
+ "verb",
+ fmt::format("Verb for service - {}, {}, {}, {}, {}. Use '--' ",
+ m_StatusOptions.program(),
+ m_InstallOptions.program(),
+ m_UninstallOptions.program(),
+ m_StartOptions.program(),
+ m_StopOptions.program()),
+ cxxopts::value(m_Verb),
+ "<verb>");
+ m_Options.parse_positional({"verb"});
+ m_Options.positional_help("verb");
+
+ m_StatusOptions.add_options()("h,help", "Print help");
+ m_StatusOptions.add_option("",
+ "n",
+ "name",
+ fmt::format("Service name, defaults to \"{}\"", m_ServiceName),
+ cxxopts::value(m_ServiceName),
+ "<name>");
+ m_StatusOptions.parse_positional({"name"});
+ m_StatusOptions.positional_help("name");
+
+ m_InstallOptions.add_options()("h,help", "Print help");
+ m_InstallOptions.add_option("", "s", "executable", "Path to server executable", cxxopts::value(m_ServerExecutable), "<path>");
+ m_InstallOptions.add_option("",
+ "n",
+ "name",
+ fmt::format("Service name, defaults to \"{}\"", m_ServiceName),
+ cxxopts::value(m_ServiceName),
+ "<name>");
+ m_InstallOptions.add_option("",
+ "d",
+ "display-name",
+ fmt::format("Service display, defaults to \"{}\"", m_ServiceDisplayName),
+ cxxopts::value(m_ServiceDisplayName),
+ "<display-name>");
+ m_InstallOptions.add_option("",
+ "",
+ "description",
+ fmt::format("Service description", m_ServiceDescription),
+ cxxopts::value(m_ServiceDescription),
+ "<description>");
+ m_InstallOptions.parse_positional({"executable", "name", "display-name"});
+ m_InstallOptions.positional_help("executable name display-name");
+
+ m_UninstallOptions.add_options()("h,help", "Print help");
+ m_UninstallOptions.add_option("",
+ "n",
+ "name",
+ fmt::format("Service name, defaults to \"{}\"", m_ServiceName),
+ cxxopts::value(m_ServiceName),
+ "<name>");
+ m_UninstallOptions.parse_positional({"name"});
+ m_UninstallOptions.positional_help("name");
+
+ m_StartOptions.add_options()("h,help", "Print help");
+ m_StartOptions.add_option("",
+ "n",
+ "name",
+ fmt::format("Service name, defaults to \"{}\"", m_ServiceName),
+ cxxopts::value(m_ServiceName),
+ "<name>");
+ m_StartOptions.parse_positional({"name"});
+ m_StartOptions.positional_help("name");
+
+ m_StopOptions.add_options()("h,help", "Print help");
+ m_StopOptions.add_option("",
+ "n",
+ "name",
+ fmt::format("Service name, defaults to \"{}\"", m_ServiceName),
+ cxxopts::value(m_ServiceName),
+ "<name>");
+ m_StopOptions.parse_positional({"name"});
+ m_StopOptions.positional_help("name");
+}
+
+ServiceCommand::~ServiceCommand() = default;
+
+int
+ServiceCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ using namespace std::literals;
+
+ std::vector<char*> SubCommandArguments;
+ cxxopts::Options* SubOption = nullptr;
+ int ParentCommandArgCount = GetSubCommand(m_Options, argc, argv, m_SubCommands, SubOption, SubCommandArguments);
+ if (!ParseOptions(ParentCommandArgCount, argv))
+ {
+ return 0;
+ }
+
+ if (SubOption == nullptr)
+ {
+ throw zen::OptionParseException("command verb is missing");
+ }
+
+ if (!ParseOptions(*SubOption, gsl::narrow<int>(SubCommandArguments.size()), SubCommandArguments.data()))
+ {
+ return 0;
+ }
+
+ if (SubOption == &m_StatusOptions)
+ {
+ ServiceInfo Info;
+ std::error_code Ec = QueryInstalledService(m_ServiceName, Info);
+ if (Ec)
+ {
+ ZEN_CONSOLE("Can't get information about service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
+ return 1;
+ }
+ ZEN_CONSOLE(
+ "Service '{}':\n"
+ " Status: {}\n"
+ " Executable: '{}'\n"
+ " Display Name: '{}'\n"
+ " Description: '{}'",
+ m_ServiceName,
+ ToString(Info.Status),
+ Info.ExecutablePath,
+ Info.DisplayName,
+ Info.Description);
+ }
+
+ if (SubOption == &m_InstallOptions)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ if (!WinIsElevated())
+ {
+ return WinRelaunchElevated();
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+ if (m_ServerExecutable.empty())
+ {
+ std::filesystem::path ExePath = zen::GetRunningExecutablePath();
+ ExePath.replace_filename("zenserver" + ExePath.extension().string());
+ m_ServerExecutable = ExePath;
+ }
+ m_ServerExecutable = std::filesystem::absolute(m_ServerExecutable);
+
+ std::error_code Ec = InstallService(m_ServerExecutable,
+ GlobalOptions.PassthroughCommandLine,
+ m_ServiceName,
+ m_ServiceDisplayName,
+ m_ServiceDescription);
+ if (Ec)
+ {
+ ZEN_CONSOLE("Failed to install service '{}' using '{}' . Reason: '{}'", m_ServiceName, m_ServerExecutable, Ec.message());
+ return 1;
+ }
+ ZEN_CONSOLE("Installed service '{}' using '{}' successfully", m_ServiceName, m_ServerExecutable);
+ }
+
+ if (SubOption == &m_UninstallOptions)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ if (!WinIsElevated())
+ {
+ return WinRelaunchElevated();
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+ std::error_code Ec = UninstallService(m_ServiceName);
+ if (Ec)
+ {
+ ZEN_CONSOLE("Failed to uninstall service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
+ return 1;
+ }
+ ZEN_CONSOLE("Uninstalled service {} successfully", m_ServiceName);
+ }
+
+ if (SubOption == &m_StartOptions)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ if (!WinIsElevated())
+ {
+ return WinRelaunchElevated();
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+ std::error_code Ec = StartService(m_ServiceName);
+ if (Ec)
+ {
+ ZEN_CONSOLE("Failed to start service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
+ return 1;
+ }
+ ZEN_CONSOLE("Started service '{}' successfully", m_ServiceName);
+ }
+
+ if (SubOption == &m_StopOptions)
+ {
+#if ZEN_PLATFORM_WINDOWS
+ if (!WinIsElevated())
+ {
+ return WinRelaunchElevated();
+ }
+#endif // ZEN_PLATFORM_WINDOWS
+ std::error_code Ec = StopService(m_ServiceName);
+ if (Ec)
+ {
+ ZEN_CONSOLE("Failed to stop service '{}'. Reason: '{}'", m_ServiceName, Ec.message());
+ return 1;
+ }
+ ZEN_CONSOLE("Stopped service '{}' successfully", m_ServiceName);
+ }
+
+ return 0;
+}
+
+} // namespace zen
diff --git a/src/zen/cmds/service_cmd.h b/src/zen/cmds/service_cmd.h
new file mode 100644
index 000000000..d61748421
--- /dev/null
+++ b/src/zen/cmds/service_cmd.h
@@ -0,0 +1,48 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "../zen.h"
+
+#include <filesystem>
+
+namespace zen {
+
+class ServiceCommand : public ZenCmdBase
+{
+public:
+ static constexpr char Name[] = "service";
+ static constexpr char Description[] = "Manage zenserver as a service - status, install, uninstall, start and stop";
+
+ ServiceCommand();
+ ~ServiceCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+
+private:
+ cxxopts::Options m_Options{Name, Description};
+
+ std::string m_Verb; // create, info, remove
+
+ std::string m_ServiceName = "Unreal Zen Storage";
+
+ cxxopts::Options m_StatusOptions{"status", "Show information about an installed zenserver service"};
+
+ cxxopts::Options m_InstallOptions{
+ "install",
+ "Install zenserver as a service. Arguments following \" -- \" will be added as parameters to the installed service."};
+ std::filesystem::path m_ServerExecutable;
+ std::string m_ServiceDisplayName = "Unreal Zen Storage Service";
+ std::string m_ServiceDescription;
+
+ cxxopts::Options m_UninstallOptions{"uninstall", "Uninstall zenserver as a service"};
+
+ cxxopts::Options m_StartOptions{"start", "Start an installed zenserver service"};
+
+ cxxopts::Options m_StopOptions{"stop", "Start an installed zenserver service"};
+
+ cxxopts::Options* m_SubCommands[5] = {&m_StatusOptions, &m_InstallOptions, &m_UninstallOptions, &m_StartOptions, &m_StopOptions};
+};
+
+} // namespace zen
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index fd58b024a..f64bc96a8 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -16,6 +16,7 @@
#include "cmds/rpcreplay_cmd.h"
#include "cmds/run_cmd.h"
#include "cmds/serve_cmd.h"
+#include "cmds/service_cmd.h"
#include "cmds/status_cmd.h"
#include "cmds/top_cmd.h"
#include "cmds/trace_cmd.h"
@@ -429,6 +430,7 @@ main(int argc, char** argv)
VfsCommand VfsCmd;
WorkspaceCommand WorkspaceCmd;
WorkspaceShareCommand WorkspaceShareCmd;
+ ServiceCommand ServiceCmd;
const struct CommandInfo
{
@@ -484,6 +486,7 @@ main(int argc, char** argv)
{"flush", &FlushCmd, "Flush storage"},
{WorkspaceCommand::Name, &WorkspaceCmd, WorkspaceCommand::Description},
{WorkspaceShareCommand::Name, &WorkspaceShareCmd, WorkspaceShareCommand::Description},
+ {ServiceCommand::Name, &ServiceCmd, ServiceCommand::Description},
// clang-format on
};
diff --git a/src/zenutil/include/zenutil/service.h b/src/zenutil/include/zenutil/service.h
new file mode 100644
index 000000000..79be16052
--- /dev/null
+++ b/src/zenutil/include/zenutil/service.h
@@ -0,0 +1,38 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include <filesystem>
+
+namespace zen {
+enum class ServiceStatus
+{
+ NotInstalled,
+ Starting,
+ Running,
+ Stopping,
+ Stopped,
+ Pausing,
+ Paused,
+ Resuming
+};
+
+std::string_view ToString(ServiceStatus Status);
+
+std::error_code InstallService(const std::filesystem::path& ExecutablePath,
+ std::string_view CommandLineOptions,
+ std::string_view ServiceName,
+ std::string_view ServiceDisplayName,
+ std::string_view ServiceDescription);
+std::error_code UninstallService(std::string_view ServiceName);
+struct ServiceInfo
+{
+ ServiceStatus Status;
+ std::filesystem::path ExecutablePath;
+ std::string DisplayName;
+ std::string Description;
+};
+std::error_code QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus);
+std::error_code StartService(std::string_view ServiceName);
+std::error_code StopService(std::string_view ServiceName);
+} // namespace zen
diff --git a/src/zenutil/service.cpp b/src/zenutil/service.cpp
new file mode 100644
index 000000000..840fe3104
--- /dev/null
+++ b/src/zenutil/service.cpp
@@ -0,0 +1,393 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include <zenutil/service.h>
+
+#include <zencore/except.h>
+#include <zencore/scopeguard.h>
+#include <zencore/zencore.h>
+
+#if ZEN_PLATFORM_WINDOWS
+# include <zencore/windows.h>
+#endif
+
+namespace zen {
+using namespace std::literals;
+
+std::string_view
+ToString(ServiceStatus Status)
+{
+ switch (Status)
+ {
+ case ServiceStatus::NotInstalled:
+ return "Not installed"sv;
+ case ServiceStatus::Starting:
+ return "Starting"sv;
+ case ServiceStatus::Running:
+ return "Running"sv;
+ case ServiceStatus::Stopping:
+ return "Stopping"sv;
+ case ServiceStatus::Stopped:
+ return "Stopped"sv;
+ case ServiceStatus::Pausing:
+ return "Pausing"sv;
+ case ServiceStatus::Paused:
+ return "Paused"sv;
+ case ServiceStatus::Resuming:
+ return "Resuming"sv;
+ default:
+ ZEN_ASSERT(false);
+ return ""sv;
+ }
+}
+
+#if ZEN_PLATFORM_WINDOWS
+
+std::error_code
+InstallService(const std::filesystem::path& ExecutablePath,
+ std::string_view CommandLineOptions,
+ std::string_view ServiceName,
+ std::string_view ServiceDisplayName,
+ std::string_view ServiceDescription)
+{
+ // Get a handle to the SCM database.
+
+ SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access rights
+
+ if (NULL == schSCManager)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); });
+
+ // Create the service
+
+ ExtendableWideStringBuilder<128> Name;
+ Utf8ToWide(ServiceName, Name);
+
+ ExtendableWideStringBuilder<128> DisplayName;
+ Utf8ToWide(ServiceDisplayName, DisplayName);
+
+ ExtendableWideStringBuilder<128> Path;
+ Path.Append(ExecutablePath.c_str());
+ if (!CommandLineOptions.empty())
+ {
+ Path.AppendAscii(" ");
+ Utf8ToWide(CommandLineOptions, Path);
+ }
+
+ SC_HANDLE schService = CreateService(schSCManager, // SCM database
+ Name.c_str(), // name of service
+ DisplayName.c_str(), // service name to display
+ SERVICE_ALL_ACCESS, // desired access
+ SERVICE_WIN32_OWN_PROCESS, // service type
+ SERVICE_DEMAND_START, // start type
+ SERVICE_ERROR_NORMAL, // error control type
+ Path.c_str(), // path to service's binary
+ NULL, // no load ordering group
+ NULL, // no tag identifier
+ NULL, // no dependencies
+ NULL, // LocalSystem account
+ NULL); // no password
+
+ if (schService == NULL)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ if (!ServiceDescription.empty())
+ {
+ ExtendableWideStringBuilder<128> DescriptionBuilder;
+ Utf8ToWide(ServiceDescription, DescriptionBuilder);
+
+ SERVICE_DESCRIPTION Description;
+ Description.lpDescription = const_cast<wchar_t*>(DescriptionBuilder.c_str());
+ if (!ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &Description))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+ }
+
+ CloseServiceHandle(schService);
+
+ return {};
+}
+
+std::error_code
+UninstallService(std::string_view ServiceName)
+{
+ // Get a handle to the SCM database.
+ SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // full access rights
+
+ if (NULL == schSCManager)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); });
+
+ // Get a handle to the service.
+
+ ExtendableWideStringBuilder<128> Name;
+ Utf8ToWide(ServiceName, Name);
+
+ SC_HANDLE schService = OpenService(schSCManager, // SCM database
+ Name.c_str(), // name of service
+ DELETE); // need delete access
+
+ if (schService == NULL)
+ {
+ DWORD Error = ::GetLastError();
+ if (Error == ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ return {};
+ }
+ return MakeErrorCode(Error);
+ }
+ auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); });
+
+ // Delete the service.
+
+ if (!DeleteService(schService))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ return {};
+}
+
+std::error_code
+QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutInfo)
+{
+ // Get a handle to the SCM database.
+ SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_CONNECT); // standard access rights
+
+ if (NULL == schSCManager)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); });
+
+ // Get a handle to the service.
+
+ ExtendableWideStringBuilder<128> Name;
+ Utf8ToWide(ServiceName, Name);
+
+ SC_HANDLE schService = OpenService(schSCManager, // SCM database
+ Name.c_str(), // name of service
+ SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG); // need delete access
+
+ if (schService == NULL)
+ {
+ DWORD Error = ::GetLastError();
+ if (Error == ERROR_SERVICE_DOES_NOT_EXIST)
+ {
+ OutInfo.Status = ServiceStatus::NotInstalled;
+ return {};
+ }
+ return MakeErrorCode(Error);
+ }
+ auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); });
+
+ std::vector<std::uint8_t> Buffer(8192);
+ QUERY_SERVICE_CONFIG* ServiceConfig = reinterpret_cast<QUERY_SERVICE_CONFIG*>(Buffer.data());
+ DWORD BytesNeeded = 0;
+ if (!QueryServiceConfig(schService, ServiceConfig, (DWORD)Buffer.size(), &BytesNeeded))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ OutInfo.ExecutablePath = std::filesystem::path(ServiceConfig->lpBinaryPathName);
+ OutInfo.DisplayName = WideToUtf8(ServiceConfig->lpDisplayName);
+
+ SERVICE_STATUS ServiceStatus;
+ if (!::QueryServiceStatus(schService, &ServiceStatus))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ switch (ServiceStatus.dwCurrentState)
+ {
+ case SERVICE_STOPPED:
+ OutInfo.Status = ServiceStatus::Stopped;
+ break;
+ case SERVICE_START_PENDING:
+ OutInfo.Status = ServiceStatus::Starting;
+ break;
+ case SERVICE_STOP_PENDING:
+ OutInfo.Status = ServiceStatus::Stopping;
+ break;
+ case SERVICE_RUNNING:
+ OutInfo.Status = ServiceStatus::Running;
+ break;
+ case SERVICE_CONTINUE_PENDING:
+ OutInfo.Status = ServiceStatus::Resuming;
+ break;
+ case SERVICE_PAUSE_PENDING:
+ OutInfo.Status = ServiceStatus::Pausing;
+ break;
+ case SERVICE_PAUSED:
+ OutInfo.Status = ServiceStatus::Paused;
+ break;
+ default:
+ throw std::runtime_error(fmt::format("Unknown service status for '{}': {}", ServiceName, ServiceStatus.dwCurrentState));
+ }
+
+ if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded))
+ {
+ DWORD Error = ::GetLastError();
+ if (Error == ERROR_INSUFFICIENT_BUFFER)
+ {
+ Buffer.resize((size_t)BytesNeeded);
+ if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, Buffer.data(), (DWORD)Buffer.size(), &BytesNeeded))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+ }
+ else
+ {
+ return MakeErrorCode(Error);
+ }
+ }
+ SERVICE_DESCRIPTION* Description = (SERVICE_DESCRIPTION*)Buffer.data();
+ if (Description->lpDescription != NULL)
+ {
+ OutInfo.Description = WideToUtf8(std::wstring(Description->lpDescription));
+ }
+
+ return {};
+}
+
+std::error_code
+StartService(std::string_view ServiceName)
+{
+ // Get a handle to the SCM database.
+ SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_CONNECT); // default access rights
+
+ if (NULL == schSCManager)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); });
+
+ // Get a handle to the service.
+
+ ExtendableWideStringBuilder<128> Name;
+ Utf8ToWide(ServiceName, Name);
+
+ SC_HANDLE schService = OpenService(schSCManager, // SCM database
+ Name.c_str(), // name of service
+ SERVICE_START); // need start access
+
+ if (schService == NULL)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+ auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); });
+
+ // Start the service.
+
+ if (!::StartService(schService, 0, NULL))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ return {};
+}
+
+std::error_code
+StopService(std::string_view ServiceName)
+{
+ // Get a handle to the SCM database.
+ SC_HANDLE schSCManager = OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_CONNECT); // default access rights
+
+ if (NULL == schSCManager)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ auto _ = MakeGuard([schSCManager]() { CloseServiceHandle(schSCManager); });
+
+ // Get a handle to the service.
+
+ ExtendableWideStringBuilder<128> Name;
+ Utf8ToWide(ServiceName, Name);
+
+ SC_HANDLE schService = OpenService(schSCManager, // SCM database
+ Name.c_str(), // name of service
+ SERVICE_STOP); // need start access
+
+ if (schService == NULL)
+ {
+ return MakeErrorCodeFromLastError();
+ }
+ auto __ = MakeGuard([schService]() { CloseServiceHandle(schService); });
+
+ // Stop the service.
+ SERVICE_STATUS ServiceStatus;
+ if (!::ControlService(schService, SERVICE_CONTROL_STOP, &ServiceStatus))
+ {
+ return MakeErrorCodeFromLastError();
+ }
+
+ return {};
+}
+#else
+std::error_code
+InstallService(const std::filesystem::path& ExecutablePath,
+ std::string_view CommandLineOptions,
+ std::string_view ServiceName,
+ std::string_view ServiceDisplayName,
+ std::string_view ServiceDescription)
+{
+ ZEN_UNUSED(ExecutablePath, CommandLineOptions, ServiceName, ServiceDisplayName, ServiceDescription);
+ ZEN_NOT_IMPLEMENTED("InstallService");
+ return {};
+}
+
+std::error_code
+UninstallService(std::string_view ServiceName)
+{
+ ZEN_UNUSED(ServiceName);
+ ZEN_NOT_IMPLEMENTED("UninstallService");
+ return {};
+}
+
+std::error_code
+QueryInstalledService(std::string_view ServiceName, ServiceInfo& OutStatus)
+{
+ ZEN_UNUSED(ServiceName, OutStatus);
+ ZEN_NOT_IMPLEMENTED("QueryInstalledService");
+ return {};
+}
+
+std::error_code
+StartService(std::string_view ServiceName)
+{
+ ZEN_UNUSED(ServiceName);
+ ZEN_NOT_IMPLEMENTED("StartService");
+ return {};
+}
+
+std::error_code
+StopService(std::string_view ServiceName)
+{
+ ZEN_UNUSED(ServiceName);
+ ZEN_NOT_IMPLEMENTED("StopService");
+ return {};
+}
+
+#endif
+} // namespace zen