Allow service editing on the command line.
authorIain Patterson <me@iain.cx>
Wed, 1 Jan 2014 12:41:02 +0000 (12:41 +0000)
committerIain Patterson <me@iain.cx>
Wed, 1 Jan 2014 21:27:58 +0000 (21:27 +0000)
Individual service parameters can now be queried or edited on the
command line.  The syntax is designed to be intuitive and is
described in some detail in the README.  Some examples follow:

    nssm get Selenium Application

    nssm get "My Batch Job" AppExit 0

    nssm get Jenkins AppEnvironmentExtra CLASSPATH

    nssm get Netlogon ImagePath

    nssm set UT2004 Application C:\Games\UT2004\System\UCC.exe

    nssm set UT2004 AppParameters server

    nssm set UT2004 AppExit Default Exit

    nssm set Jenkins ObjectName DOMAIN\ci correct horse battery staple

    nssm set "My Batch Job" AppExit 0 Exit

    nssm set WowzaMediaServer362 Start SERVICE_DELAYED_START

    nssm set UT2004 Type SERVICE_INTERACTIVE_PROCESS

    nssm reset UT2004 AppEnvironment

    nssm reset UT2004 ObjectName

ChangeLog.txt
README.txt
messages.mc
nssm.cpp
nssm.dsp
nssm.h
nssm.vcproj
service.cpp
service.h
settings.cpp [new file with mode: 0644]
settings.h [new file with mode: 0644]

index 004f597..bdc1aed 100644 (file)
@@ -1,6 +1,7 @@
 Changes since 2.21
 ------------------
-  * Existing services can now be edited using the GUI.
+  * Existing services can now be edited using the GUI
+    or on the command line.
 
   * NSSM can now optionally rotate existing files when
     redirecting I/O.
index 0a0c47c..a7655d9 100644 (file)
@@ -58,7 +58,7 @@ Since version 2.22, NSSM can rotate existing output files when redirecting I/O.
 Since version 2.22, NSSM can set service display name, description, startup\r
 type and log on details.\r
 \r
-Since version 2.22, NSSM can edit existing services with the GUI.\r
+Since version 2.22, NSSM can edit existing services.\r
 \r
 \r
 Usage\r
@@ -298,6 +298,131 @@ the App* registry settings described above, the GUI will allow editing only
 system settings such as the service display name and description.\r
 \r
 \r
+Managing services using the command line\r
+----------------------------------------\r
+NSSM can retrieve or set individual service parameters from the command line.\r
+In general the syntax is as follows, though see below for exceptions.\r
+\r
+    nssm get <servicename> <parameter>\r
+\r
+    nssm set <servicename> <parameter> <value>\r
+\r
+Parameters can also be reset to their default values.\r
+\r
+    nssm reset <servicename> <parameter>\r
+\r
+The parameter names recognised by NSSM are the same as the registry entry\r
+names described above, eg AppDirectory.\r
+\r
+NSSM offers limited editing capabilities for Services other than those which\r
+run NSSM itself.  The parameters recognised are as follows:\r
+\r
+  Description: Service description.\r
+  DisplayName: Service display name.\r
+  ImagePath: Path to the service executable.\r
+  ObjectName: User account which runs the service.\r
+  Start: Service startup type.\r
+  Type: Service type.\r
+\r
+These correspond to the registry values under the service's key\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>.\r
+\r
+\r
+Note that NSSM will concatenate all arguments passed on the command line\r
+with spaces to form the value to set.  Thus the following two invocations\r
+would have the same effect.\r
+\r
+    nssm set <servicename> Description "NSSM managed service"\r
+\r
+    nssm set <servicename> Description NSSM managed service\r
+\r
+\r
+Non-standard parameters\r
+-----------------------\r
+The AppEnvironment and AppEnvironmentExtra parameters recognise an\r
+additional argument when querying the environment.  The following syntax\r
+will print all extra environment variables configured for a service\r
+\r
+    nssm get <servicename> AppEnvironmentExtra\r
+\r
+whereas the syntax below will print only the value of the CLASSPATH\r
+variable if it is configured in the environment block, or the empty string\r
+if it is not configured.\r
+\r
+    nssm get <servicename> AppEnvironmentExtra CLASSPATH\r
+\r
+When setting an environment block, each variable should be specified as a\r
+KEY=VALUE pair in separate command line arguments.  For example:\r
+\r
+    nssm set <servicename> AppEnvironment CLASSPATH=C:\Classes TEMP=C:\Temp\r
+\r
+\r
+The AppExit parameter requires an additional argument specifying the exit\r
+code to get or set.  The default action can be specified with the string\r
+Default.\r
+\r
+For example, to get the default exit action for a service you should run\r
+\r
+    nssm get <servicename> AppExit Default\r
+\r
+To get the exit action when the application exits with exit code 2, run\r
+\r
+    nssm get <servicename> AppExit 2\r
+\r
+Note that if no explicit action is configured for a specified exit code,\r
+NSSM will print the default exit action.\r
+\r
+To set configure the service to stop when the application exits with an\r
+exit code of 2, run\r
+\r
+    nssm set <servicename> AppExit 2 Exit\r
+\r
+\r
+The ObjectName parameter requires an additional argument only when setting\r
+a username.  The additional argument is the password of the user.\r
+\r
+To retrieve the username, run\r
+\r
+    nssm get <servicename> ObjectName\r
+\r
+To set the username and password, run\r
+\r
+    nssm set <servicename> ObjectName <username> <password>\r
+\r
+Note that the rules of argument concatenation still apply.  The following\r
+invocation is valid and will have the expected effect.\r
+\r
+    nssm set <servicename> ObjectName <username> correct horse battery staple\r
+\r
+\r
+The Start parameter is used to query or set the startup type of the service.\r
+Valid service startup types are as follows:\r
+\r
+  SERVICE_AUTO_START: Automatic startup at boot.\r
+  SERVICE_DELAYED_START: Delayed startup at boot.\r
+  SERVICE_DEMAND_START: Manual service startup.\r
+  SERVICE_DISABLED: The service is disabled.\r
+\r
+Note that SERVICE_DELAYED_START is not supported on versions of Windows prior\r
+to Vista.  NSSM will set the service to automatic startup if delayed start is\r
+unavailable.\r
+\r
+\r
+The Type parameter is used to query or set the service type.  NSSM recognises\r
+all currently documented service types but will only allow setting one of two\r
+types:\r
+\r
+  SERVICE_WIN32_OWN_PROCESS: A standalone service.  This is the default.\r
+  SERVICE_INTERACTIVE_PROCESS: A service which can interact with the desktop.\r
+\r
+Note that a service may only be configured as interactive if it runs under\r
+the LocalSystem account.  The safe way to configure an interactive service\r
+is in two stages as follows.\r
+\r
+    nssm reset <servicename> ObjectName\r
+    nssm set <servicename> Type SERVICE_INTERACTIVE_PROCESS\r
+\r
+\r
 Removing services using the GUI\r
 -------------------------------\r
 NSSM can also remove services.  Run\r
@@ -335,6 +460,14 @@ To install an Unreal Tournament server:
 \r
     nssm install UT2004 c:\games\ut2004\system\ucc.exe server\r
 \r
+To run the server as the "games" user:\r
+\r
+    nssm set UT2004 ObjectName games password\r
+\r
+To configure the server to log to a file:\r
+\r
+    nssm set UT2004 AppStdout c:\games\ut2004\service.log\r
+\r
 To remove the server:\r
 \r
     nssm remove UT2004 confirm\r
index 40a7f1c..aaaeb8c 100644 (file)
Binary files a/messages.mc and b/messages.mc differ
index 781a228..43faa41 100644 (file)
--- a/nssm.cpp
+++ b/nssm.cpp
@@ -79,7 +79,7 @@ int _tmain(int argc, TCHAR **argv) {
 \r
   /* Elevate */\r
   if (argc > 1) {\r
-    /* Valid commands are install, edit or remove */\r
+    /* Valid commands are install, edit, get, set, reset, unset or remove */\r
     if (str_equiv(argv[1], _T("install"))) {\r
       if (! is_admin) {\r
         print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL);\r
@@ -87,12 +87,15 @@ int _tmain(int argc, TCHAR **argv) {
       }\r
       exit(pre_install_service(argc - 2, argv + 2));\r
     }\r
-    if (str_equiv(argv[1], _T("edit"))) {\r
+    if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset"))) {\r
       if (! is_admin) {\r
         print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT);\r
         exit(100);\r
       }\r
-      exit(pre_edit_service(argc - 2, argv + 2));\r
+      int ret = pre_edit_service(argc - 1, argv + 1);\r
+      /* There might be a password here. */\r
+      for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR));\r
+      exit(ret);\r
     }\r
     if (str_equiv(argv[1], _T("remove"))) {\r
       if (! is_admin) {\r
index 2ac2b34..05efb15 100644 (file)
--- a/nssm.dsp
+++ b/nssm.dsp
@@ -118,6 +118,10 @@ SOURCE=.\registry.cpp
 \r
 SOURCE=.\service.cpp\r
 # End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\settings.cpp\r
+# End Source File\r
 # End Group\r
 # Begin Group "Header Files"\r
 \r
@@ -154,6 +158,10 @@ SOURCE=.\registry.h
 \r
 SOURCE=.\service.h\r
 # End Source File\r
+# Begin Source File\r
+\r
+SOURCE=.\settings.h\r
+# End Source File\r
 # End Group\r
 # Begin Group "Resource Files"\r
 \r
diff --git a/nssm.h b/nssm.h
index cf6133e..b83844a 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -13,6 +13,7 @@
 #include "messages.h"\r
 #include "process.h"\r
 #include "registry.h"\r
+#include "settings.h"\r
 #include "io.h"\r
 #include "gui.h"\r
 \r
index dfac3e5..324e28f 100755 (executable)
                                        />\r
                                </FileConfiguration>\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\settings.cpp"\r
+                               >\r
+                       </File>\r
                </Filter>\r
                <Filter\r
                        Name="Header Files"\r
                                RelativePath="service.h"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\settings.h"\r
+                               >\r
+                       </File>\r
                </Filter>\r
                <Filter\r
                        Name="Resource Files"\r
index d402ab3..296d416 100644 (file)
@@ -3,14 +3,14 @@
 /* This is explicitly a wide string. */\r
 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"\r
 \r
-extern const TCHAR *exit_action_strings[];\r
-\r
 bool is_admin;\r
 bool use_critical_section;\r
 \r
 extern imports_t imports;\r
+extern settings_t settings[];\r
 \r
 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
+const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
 \r
 static inline int throttle_milliseconds(unsigned long throttle) {\r
   /* pow() operates on doubles. */\r
@@ -182,7 +182,7 @@ int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *
   return 0;\r
 }\r
 \r
-static int grant_logon_as_service(const TCHAR *username) {\r
+int grant_logon_as_service(const TCHAR *username) {\r
   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
 \r
   /* Open Policy object. */\r
@@ -419,10 +419,69 @@ int pre_install_service(int argc, TCHAR **argv) {
 /* About to edit the service. */\r
 int pre_edit_service(int argc, TCHAR **argv) {\r
   /* Require service name. */\r
-  if (argc < 1) return usage(1);\r
+  if (argc < 2) return usage(1);\r
+\r
+  /* Are we editing on the command line? */\r
+  enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;\r
+  const TCHAR *verb = argv[0];\r
+  const TCHAR *service_name = argv[1];\r
+  bool getting = false;\r
+  bool unsetting = false;\r
+\r
+  /* Minimum number of arguments. */\r
+  int mandatory = 2;\r
+  /* Index of first value. */\r
+  int remainder = 3;\r
+  int i;\r
+  if (str_equiv(verb, _T("get"))) {\r
+    mandatory = 3;\r
+    mode = MODE_GETTING;\r
+  }\r
+  else if (str_equiv(verb, _T("set"))) {\r
+    mandatory = 4;\r
+    mode = MODE_SETTING;\r
+  }\r
+  else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {\r
+    mandatory = 3;\r
+    mode = MODE_RESETTING;\r
+  }\r
+  if (argc < mandatory) return usage(1);\r
+\r
+  const TCHAR *parameter = 0;\r
+  settings_t *setting = 0;\r
+  TCHAR *additional;\r
+\r
+  /* Validate the parameter. */\r
+  if (mandatory > 2) {\r
+    bool additional_mandatory = false;\r
+\r
+    parameter = argv[2];\r
+    for (i = 0; settings[i].name; i++) {\r
+      setting = &settings[i];\r
+      if (! str_equiv(setting->name, parameter)) continue;\r
+      if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
+        additional_mandatory = true;\r
+        mandatory++;\r
+      }\r
+      break;\r
+    }\r
+    if (! settings[i].name) {\r
+      print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
+      for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
+      return 1;\r
+    }\r
+    if (argc < mandatory) return usage(1);\r
+\r
+    additional = 0;\r
+    if (additional_mandatory) {\r
+      additional = argv[3];\r
+      remainder = 4;\r
+    }\r
+    else additional = argv[remainder];\r
+  }\r
 \r
   nssm_service_t *service = alloc_nssm_service();\r
-  _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
+  _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
 \r
   /* Open service manager */\r
   SC_HANDLE services = open_service_manager();\r
@@ -440,7 +499,6 @@ int pre_edit_service(int argc, TCHAR **argv) {
   }\r
 \r
   /* Get system details. */\r
-  unsigned long bufsize;\r
   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
   if (! qsc) {\r
     CloseHandle(service->handle);\r
@@ -450,31 +508,37 @@ int pre_edit_service(int argc, TCHAR **argv) {
 \r
   service->type = qsc->dwServiceType;\r
   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
-    HeapFree(GetProcessHeap(), 0, qsc);\r
-    CloseHandle(service->handle);\r
-    CloseServiceHandle(services);\r
-    print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);\r
-    return 3;\r
+    if (mode != MODE_GETTING) {\r
+      HeapFree(GetProcessHeap(), 0, qsc);\r
+      CloseHandle(service->handle);\r
+      CloseServiceHandle(services);\r
+      print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
+      return 3;\r
+    }\r
   }\r
 \r
   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
-    HeapFree(GetProcessHeap(), 0, qsc);\r
-    CloseHandle(service->handle);\r
-    CloseServiceHandle(services);\r
-    return 4;\r
+    if (mode != MODE_GETTING) {\r
+      HeapFree(GetProcessHeap(), 0, qsc);\r
+      CloseHandle(service->handle);\r
+      CloseServiceHandle(services);\r
+      return 4;\r
+    }\r
   }\r
 \r
   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
-    HeapFree(GetProcessHeap(), 0, qsc);\r
-    CloseHandle(service->handle);\r
-    CloseServiceHandle(services);\r
-    return 5;\r
+    if (mode != MODE_GETTING) {\r
+      HeapFree(GetProcessHeap(), 0, qsc);\r
+      CloseHandle(service->handle);\r
+      CloseServiceHandle(services);\r
+      return 5;\r
+    }\r
   }\r
 \r
   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
 \r
   /* Get the canonical service name. We open it case insensitively. */\r
-  bufsize = _countof(service->name);\r
+  unsigned long bufsize = _countof(service->name);\r
   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
 \r
   /* Remember the executable in case it isn't NSSM. */\r
@@ -483,9 +547,11 @@ int pre_edit_service(int argc, TCHAR **argv) {
 \r
   /* Get extended system details. */\r
   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
-    CloseHandle(service->handle);\r
-    CloseServiceHandle(services);\r
-    return 6;\r
+    if (mode != MODE_GETTING) {\r
+      CloseHandle(service->handle);\r
+      CloseServiceHandle(services);\r
+      return 6;\r
+    }\r
   }\r
 \r
   /* Get NSSM details. */\r
@@ -494,11 +560,112 @@ int pre_edit_service(int argc, TCHAR **argv) {
   CloseServiceHandle(services);\r
 \r
   if (! service->exe[0]) {\r
-    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
     service->native = true;\r
+    if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
+  }\r
+\r
+  /* Editing with the GUI. */\r
+  if (mode == MODE_EDITING) {\r
+    nssm_gui(IDD_EDIT, service);\r
+    return 0;\r
+  }\r
+\r
+  /* Trying to manage App* parameters for a non-NSSM service. */\r
+  if (! setting->native && service->native) {\r
+    CloseHandle(service->handle);\r
+    print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
+    return 1;\r
+  }\r
+\r
+  HKEY key;\r
+  value_t value;\r
+  int ret;\r
+\r
+  if (mode == MODE_GETTING) {\r
+    if (! service->native) {\r
+      key = open_registry(service->name, KEY_READ);\r
+      if (! key) return 4;\r
+    }\r
+\r
+    if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
+    else ret = get_setting(service->name, key, setting, &value, additional);\r
+    if (ret < 0) {\r
+      CloseHandle(service->handle);\r
+      return 5;\r
+    }\r
+\r
+    switch (setting->type) {\r
+      case REG_EXPAND_SZ:\r
+      case REG_MULTI_SZ:\r
+      case REG_SZ:\r
+        _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
+        HeapFree(GetProcessHeap(), 0, value.string);\r
+        break;\r
+\r
+      case REG_DWORD:\r
+        _tprintf(_T("%u\n"), value.numeric);\r
+        break;\r
+    }\r
+\r
+    if (! service->native) RegCloseKey(key);\r
+    CloseHandle(service->handle);\r
+    return 0;\r
+  }\r
+\r
+  /* Build the value. */\r
+  if (mode == MODE_RESETTING) {\r
+    /* Unset the parameter. */\r
+    value.string = 0;\r
+  }\r
+  else {\r
+    /* Set the parameter. */\r
+    size_t len = 0;\r
+    size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
+    for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
+    len++;\r
+\r
+    value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
+    if (! value.string) {\r
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
+      CloseHandle(service->handle);\r
+      return 2;\r
+    }\r
+\r
+    size_t s = 0;\r
+    for (i = remainder; i < argc; i++) {\r
+      size_t len = _tcslen(argv[i]);\r
+      memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
+      s += len;\r
+      if (i < argc - 1) {\r
+        if (setting->additional & ADDITIONAL_CRLF) {\r
+          value.string[s++] = _T('\r');\r
+          value.string[s++] = _T('\n');\r
+        }\r
+        else value.string[s++] = _T(' ');\r
+      }\r
+    }\r
+    value.string[s] = _T('\0');\r
+  }\r
+\r
+  if (! service->native) {\r
+    key = open_registry(service->name, KEY_WRITE);\r
+    if (! key) {\r
+      if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
+      return 4;\r
+    }\r
+  }\r
+\r
+  if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
+  else ret = set_setting(service->name, key, setting, &value, additional);\r
+  if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
+  if (ret < 0) {\r
+    if (! service->native) RegCloseKey(key);\r
+    CloseHandle(service->handle);\r
+    return 6;\r
   }\r
 \r
-  nssm_gui(IDD_EDIT, service);\r
+  if (! service->native) RegCloseKey(key);\r
+  CloseHandle(service->handle);\r
 \r
   return 0;\r
 }\r
@@ -581,7 +748,7 @@ int edit_service(nssm_service_t *service, bool editing) {
 \r
   /*\r
     Username must be NULL if we aren't changing or an account name.\r
-    We must explicitly user LOCALSYSTEM to change it when we are editing.\r
+    We must explicitly use LOCALSYSTEM to change it when we are editing.\r
     Password must be NULL if we aren't changing, a password or "".\r
     Empty passwords are valid but we won't allow them in the GUI.\r
   */\r
index a7914a1..515b108 100644 (file)
--- a/service.h
+++ b/service.h
 #define ACTION_LEN 16\r
 \r
 #define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")\r
+#define NSSM_KERNEL_DRIVER _T("SERVICE_KERNEL_DRIVER")\r
+#define NSSM_FILE_SYSTEM_DRIVER _T("SERVICE_FILE_SYSTEM_DRIVER")\r
+#define NSSM_WIN32_OWN_PROCESS _T("SERVICE_WIN32_OWN_PROCESS")\r
+#define NSSM_WIN32_SHARE_PROCESS _T("SERVICE_WIN32_SHARE_PROCESS")\r
+#define NSSM_INTERACTIVE_PROCESS _T("SERVICE_INTERACTIVE_PROCESS")\r
+#define NSSM_SHARE_INTERACTIVE_PROCESS NSSM_WIN32_SHARE_PROCESS _T("|") NSSM_INTERACTIVE_PROCESS\r
+#define NSSM_UNKNOWN _T("?")\r
 \r
 typedef struct {\r
   bool native;\r
@@ -94,6 +101,7 @@ int set_service_description(const TCHAR *, SC_HANDLE, TCHAR *);
 int get_service_description(const TCHAR *, SC_HANDLE, unsigned long, TCHAR *);\r
 int get_service_startup(const TCHAR *, SC_HANDLE, const QUERY_SERVICE_CONFIG *, unsigned long *);\r
 int get_service_username(const TCHAR *, const QUERY_SERVICE_CONFIG *, TCHAR **, size_t *);\r
+int grant_logon_as_service(const TCHAR *);\r
 int pre_install_service(int, TCHAR **);\r
 int pre_remove_service(int, TCHAR **);\r
 int pre_edit_service(int, TCHAR **);\r
diff --git a/settings.cpp b/settings.cpp
new file mode 100644 (file)
index 0000000..e64fdc5
--- /dev/null
@@ -0,0 +1,672 @@
+#include "nssm.h"
+/* XXX: (value && value->string) is probably bogus because value is probably never null */
+
+extern const TCHAR *exit_action_strings[];
+extern const TCHAR *startup_strings[];
+
+/* Does the parameter refer to the default value of the AppExit setting? */
+static inline int is_default_exit_action(const TCHAR *value) {
+  return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]);
+}
+
+static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) {
+  size_t len = _tcslen(string);
+  if (! len++) {
+    value->string = 0;
+    return 0;
+  }
+
+  value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
+  if (! value->string) {
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
+    return -1;
+  }
+
+  if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) {
+    HeapFree(GetProcessHeap(), 0, value->string);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
+    return -1;
+  }
+
+  return 1;
+}
+
+/* Functions to manage NSSM-specific settings in the registry. */
+static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  unsigned long number;
+  long error;
+
+  /* Resetting to default? */
+  if (! value || ! value->string) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+  if (str_number(value->string, &number)) return -1;
+
+  if (default_value && number == (unsigned long) default_value) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  if (set_number(key, (TCHAR *) name, number)) return -1;
+
+  return 1;
+}
+
+static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  return get_number(key, (TCHAR *) name, &value->numeric, false);
+}
+
+static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  long error;
+
+  /* Resetting to default? */
+  if (! value || ! value->string) {
+    if (default_value) value->string = (TCHAR *) default_value;
+    else {
+      error = RegDeleteValue(key, name);
+      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+      return -1;
+    }
+  }
+  if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  if (set_expand_string(key, (TCHAR *) name, value->string)) return -1;
+
+  return 1;
+}
+
+static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  TCHAR buffer[VALUE_LENGTH];
+
+  if (expand_parameter(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false)) return -1;
+
+  return value_from_string(name, value, buffer);
+}
+
+static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  unsigned long exitcode;
+  TCHAR *code;
+  TCHAR action_string[ACTION_LEN];
+
+  if (additional) {
+    /* Default action? */
+    if (is_default_exit_action(additional)) code = 0;
+    else {
+      if (str_number(additional, &exitcode)) return -1;
+      code = (TCHAR *) additional;
+    }
+  }
+
+  HKEY key = open_registry(service_name, name, KEY_WRITE);
+  if (! key) return -1;
+
+  long error;
+  int ret = 1;
+
+  /* Resetting to default? */
+  if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string);
+  else {
+    if (code) {
+      /* Delete explicit action. */
+      error = RegDeleteValue(key, code);
+      RegCloseKey(key);
+      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error));
+      return -1;
+    }
+    else {
+      /* Explicitly keep the default action. */
+      if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value);
+      ret = 0;
+    }
+  }
+
+  /* Validate the string. */
+  for (int i = 0; exit_action_strings[i]; i++) {
+    if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
+      if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0;
+      if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) action_string, (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
+        print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(GetLastError()));
+        RegCloseKey(key);
+        return -1;
+      }
+
+      RegCloseKey(key);
+      return ret;
+    }
+  }
+
+  print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string);
+  for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]);
+
+  return -1;
+}
+
+static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  unsigned long exitcode = 0;
+  unsigned long *code = 0;
+
+  if (additional) {
+    if (! is_default_exit_action(additional)) {
+      if (str_number(additional, &exitcode)) return -1;
+      code = &exitcode;
+    }
+  }
+
+  TCHAR action_string[ACTION_LEN];
+  bool default_action;
+  if (get_exit_action(service_name, code, action_string, &default_action)) return -1;
+
+  value_from_string(name, value, action_string);
+
+  if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0;
+  return 1;
+}
+
+static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  if (! value || ! value->string || ! value->string[0]) {
+    long error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  unsigned long envlen = (unsigned long) _tcslen(value->string) + 1;
+  TCHAR *unformatted = 0;
+  unsigned long newlen;
+  if (unformat_environment(value->string, envlen, &unformatted, &newlen)) return -1;
+
+  if (test_environment(unformatted)) {
+    HeapFree(GetProcessHeap(), 0, unformatted);
+    print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT);
+    return -1;
+  }
+
+  if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) {
+    if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
+    return -1;
+  }
+
+  if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
+  return 1;
+}
+
+static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  TCHAR *env = 0;
+  unsigned long envlen;
+  if (set_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1;
+  if (! envlen) return 0;
+
+  TCHAR *formatted;
+  unsigned long newlen;
+  if (format_environment(env, envlen, &formatted, &newlen)) return -1;
+
+  int ret;
+  if (additional) {
+    /* Find named environment variable. */
+    TCHAR *s;
+    size_t len = _tcslen(additional);
+    for (s = env; *s; s++) {
+      /* Look for <additional>=<string> NULL NULL */
+      if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) {
+        /* Strip <key>= */
+        s += len + 1;
+        ret = value_from_string(name, value, s);
+        HeapFree(GetProcessHeap(), 0, env);
+        return ret;
+      }
+
+      /* Skip this string. */
+      for ( ; *s; s++);
+    }
+    HeapFree(GetProcessHeap(), 0, env);
+    return 0;
+  }
+
+  HeapFree(GetProcessHeap(), 0, env);
+
+  ret = value_from_string(name, value, formatted);
+  if (newlen) HeapFree(GetProcessHeap(), 0, formatted);
+  return ret;
+}
+
+/* Functions to manage native service settings. */
+int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *description = 0;
+  if (value) description = value->string;
+  if (set_service_description(service_name, service_handle, description)) return -1;
+
+  if (description && description[0]) return 1;
+
+  return 0;
+}
+
+int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR buffer[VALUE_LENGTH];
+  if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1;
+
+  if (buffer[0]) return value_from_string(name, value, buffer);
+  value->string = 0;
+
+  return 0;
+}
+
+int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *displayname = 0;
+  if (value && value->string) displayname = value->string;
+  else displayname = (TCHAR *) service_name;
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  /*
+    If the display name and service name differ only in case,
+    ChangeServiceConfig() will return success but the display name will be
+    set to the service name, NOT the value passed to the function.
+    This appears to be a quirk of Windows rather than a bug here.
+  */
+  if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1;
+
+  return 0;
+}
+
+int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpDisplayName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the image path. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  return 1;
+}
+
+int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpBinaryPathName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /*
+    Logical syntax is: nssm set <service> ObjectName <username> <password>
+    That means the username is actually passed in the additional parameter.
+  */
+  bool localsystem = true;
+  TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
+  TCHAR *password = 0;
+  if (additional) {
+    if (! str_equiv(additional, NSSM_LOCALSYSTEM_ACCOUNT)) {
+      localsystem = false;
+      username = (TCHAR *) additional;
+      if (value && value->string) password = value->string;
+      else {
+        /* We need a password if the account is not LOCALSYSTEM. */
+        print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
+        return -1;
+      }
+    }
+  }
+
+  /*
+    ChangeServiceConfig() will fail to set the username if the service is set
+    to interact with the desktop.
+  */
+  unsigned long type = SERVICE_NO_CHANGE;
+  if (! localsystem) {
+    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+    if (! qsc) {
+      if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
+      return -1;
+    }
+
+    type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS;
+    HeapFree(GetProcessHeap(), 0, qsc);
+  }
+
+  if (grant_logon_as_service(username)) {
+    if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
+    print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
+    return -1;
+  }
+
+  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) {
+    if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+  if (password) SecureZeroMemory(password, _tcslen(password) * sizeof(TCHAR));
+
+  if (localsystem) return 0;
+
+  return 1;
+}
+
+int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpServiceStartName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the startup type. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */
+  int service_startup = -1;
+  int i;
+  for (i = 0; startup_strings[i]; i++) {
+    if (str_equiv(value->string, startup_strings[i])) {
+      service_startup = i;
+      break;
+    }
+  }
+
+  if (service_startup < 0) {
+    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string);
+    for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]);
+    return -1;
+  }
+
+  unsigned long startup;
+  switch (service_startup) {
+    case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
+    case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
+    default: startup = SERVICE_AUTO_START;
+  }
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  SERVICE_DELAYED_AUTO_START_INFO delayed;
+  ZeroMemory(&delayed, sizeof(delayed));
+  if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
+  else delayed.fDelayedAutostart = 0;
+  if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
+    unsigned long error = GetLastError();
+    /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
+    if (error != ERROR_INVALID_LEVEL) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0);
+    }
+  }
+
+  return 1;
+}
+
+int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  unsigned long startup;
+  int ret = get_service_startup(service_name, service_handle, qsc, &startup);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  if (ret) return -1;
+
+  unsigned long i;
+  for (i = 0; startup_strings[i]; i++);
+  if (startup >= i) return -1;
+
+  return value_from_string(name, value, startup_strings[startup]);
+}
+
+int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the service type. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  /*
+    We can only manage services of type SERVICE_WIN32_OWN_PROCESS
+    and SERVICE_INTERACTIVE_PROCESS.
+  */
+  unsigned long type = SERVICE_WIN32_OWN_PROCESS;
+  if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS;
+  else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) {
+    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string);
+    _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS);
+    _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS);
+    return -1;
+  }
+
+  /*
+    ChangeServiceConfig() will fail if the service runs under an account
+    other than LOCALSYSTEM and we try to make it interactive.
+  */
+  if (type & SERVICE_INTERACTIVE_PROCESS) {
+    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+    if (! qsc) return -1;
+
+    if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
+      HeapFree(GetProcessHeap(), 0, qsc);
+      print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT);
+      return -1;
+    }
+
+    HeapFree(GetProcessHeap(), 0, qsc);
+  }
+
+  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  return 1;
+}
+
+int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  value->numeric = qsc->dwServiceType;
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  const TCHAR *string;
+  switch (value->numeric) {
+    case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break;
+    case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break;
+    case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break;
+    case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break;
+    case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break;
+    case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break;
+    default: string = NSSM_UNKNOWN;
+  }
+
+  return value_from_string(name, value, string);
+}
+
+int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! key) return -1;
+  int ret;
+
+  if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+  else ret = -1;
+
+  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
+  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
+  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! service_handle) return -1;
+
+  int ret;
+  if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional);
+  else ret = -1;
+
+  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
+  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
+  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+/*
+  Returns:  1 if the value was retrieved.
+            0 if the default value was retrieved.
+           -1 on error.
+*/
+int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! key) return -1;
+  int ret;
+
+  switch (setting->type) {
+    case REG_EXPAND_SZ:
+    case REG_MULTI_SZ:
+    case REG_SZ:
+      value->string = (TCHAR *) setting->default_value;
+      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+      else ret = -1;
+      break;
+
+    case REG_DWORD:
+      value->numeric = (unsigned long) setting->default_value;
+      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+      else ret = -1;
+      break;
+
+    default:
+      ret = -1;
+      break;
+  }
+
+  if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! service_handle) return -1;
+  return setting->get(service_name, service_handle, setting->name, 0, value, additional);
+}
+
+settings_t settings[] = {
+  { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
+  { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
+  { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
+  { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },
+  { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname },
+  { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath },
+  { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, ADDITIONAL_SETTING, native_set_objectname, native_get_objectname },
+  { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup },
+  { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type },
+  { NULL, NULL, NULL, NULL, NULL }
+};
diff --git a/settings.h b/settings.h
new file mode 100644 (file)
index 0000000..71e31d1
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#define NSSM_NATIVE_DESCRIPTION _T("Description")
+#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName")
+#define NSSM_NATIVE_IMAGEPATH _T("ImagePath")
+#define NSSM_NATIVE_OBJECTNAME _T("ObjectName")
+#define NSSM_NATIVE_STARTUP _T("Start")
+#define NSSM_NATIVE_TYPE _T("Type")
+
+/* Are additional arguments needed? */
+#define ADDITIONAL_GETTING (1 << 0)
+#define ADDITIONAL_SETTING (1 << 1)
+#define ADDITIONAL_RESETTING (1 << 2)
+#define ADDITIONAL_CRLF (1 << 3)
+#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING
+
+typedef union {
+  unsigned long numeric;
+  TCHAR *string;
+} value_t;
+
+typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *);
+
+typedef struct {
+  const TCHAR *name;
+  unsigned long type;
+  void *default_value;
+  bool native;
+  int additional;
+  setting_function_t set;
+  setting_function_t get;
+} settings_t;
+
+int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
+int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
+int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
+int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
+
+#endif