aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDan Engelbrecht <[email protected]>2025-01-08 13:49:56 +0100
committerDan Engelbrecht <[email protected]>2025-01-08 13:49:56 +0100
commit995aec217bbb26c9c2a701cc77edb067ffbf8d36 (patch)
tree2da2d3fd806547bd9f38bc190514abbf9fdb6361 /src
parentcheck if service is already installed before attempting install (diff)
downloadzen-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.cpp129
-rw-r--r--src/zen/cmds/service_cmd.h1
-rw-r--r--src/zencore/include/zencore/process.h1
-rw-r--r--src/zencore/process.cpp8
-rw-r--r--src/zenutil/include/zenutil/service.h17
-rw-r--r--src/zenutil/service.cpp391
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