Allow appending to the service environment.
authorIain Patterson <me@iain.cx>
Thu, 21 Nov 2013 11:55:31 +0000 (11:55 +0000)
committerIain Patterson <me@iain.cx>
Thu, 21 Nov 2013 11:55:31 +0000 (11:55 +0000)
The new registry value AppEnvironmentExtra allows defining a list of
environment variables which will be appended to the environment, as
opposed to those defined by the srvany-compatible AppEnvironment, which
replaces the environment.

Though it would serve no useful purpose beyond verifying whether or not
I went to the effort of writing a code path for it, it is possible to
define bother lists and have them behave intuitively.

README.txt
messages.mc
registry.cpp
registry.h
service.cpp
service.h

index f5e6ed8..3bf6ab9 100644 (file)
@@ -49,6 +49,10 @@ of time for the application to exit when shutting down.
 Since version 2.19, many more service options can be configured with the\r
 GUI installer as well as via the registry.\r
 \r
 Since version 2.19, many more service options can be configured with the\r
 GUI installer as well as via the registry.\r
 \r
+Since version 2.19, NSSM can add to the service's environment by setting\r
+AppEnvironmentExtra in place of or in addition to the srvany-compatible\r
+AppEnvironment.\r
+\r
 \r
 Usage\r
 -----\r
 \r
 Usage\r
 -----\r
@@ -230,6 +234,22 @@ work.  Remember, however, that the path must be accessible to the user
 running the service.\r
 \r
 \r
 running the service.\r
 \r
 \r
+Environment variables\r
+---------------------\r
+NSSM can replace or append to the managed application's environment.  Two\r
+multi-valued string (REG_MULTI_SZ) registry values are recognised under\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters.\r
+\r
+AppEnvironment defines a list of environment variables which will override\r
+the service's environment.  AppEnvironmentExtra defines a list of\r
+environment variables which will be added to the service's environment.\r
+\r
+Each entry in the list should be of the form KEY=VALUE.  It is possible to\r
+omit the VALUE but the = symbol is mandatory.\r
+\r
+srvany only supports AppEnvironment.\r
+\r
+\r
 Removing services using the GUI\r
 -------------------------------\r
 NSSM can also remove services.  Run\r
 Removing services using the GUI\r
 -------------------------------\r
 NSSM can also remove services.  Run\r
index f88da49..eb893db 100644 (file)
@@ -1493,3 +1493,18 @@ The minimum number of milliseconds which must pass before service %1 is consider
 Language = Italian
 The minimum number of milliseconds which must pass before service %1 is considered to have been started successfully is set to %2.  Access to the Windows service control manager is blocked until the service updates its status, therefore %3 will wait a maximum of %4 milliseconds before reporting the service's state as running.  Service restart throttling will be enforced if the service runs for less than the full %2 milliseconds.
 .
 Language = Italian
 The minimum number of milliseconds which must pass before service %1 is considered to have been started successfully is set to %2.  Access to the Windows service control manager is blocked until the service updates its status, therefore %3 will wait a maximum of %4 milliseconds before reporting the service's state as running.  Service restart throttling will be enforced if the service runs for less than the full %2 milliseconds.
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_SETENVIRONMENTVARIABLE_FAILED
+Severity = Warning
+Language = English
+SetEnvironmentVariable(%1=%2) failed:
+%3
+.
+Language = French
+SetEnvironmentVariable(%1=%2) a échoué:
+%3
+.
+Language = Italian
+Chiamata a SetEnvironmentVariable(%1=%2) fallita:
+.
index 169cd08..382cb0f 100644 (file)
@@ -113,42 +113,43 @@ int create_exit_action(char *service_name, const char *action_string) {
   return 0;\r
 }\r
 \r
   return 0;\r
 }\r
 \r
-int set_environment(char *service_name, HKEY key, char **env) {\r
+int set_environment(char *service_name, HKEY key, char *value, char **env, unsigned long *envlen) {\r
   unsigned long type = REG_MULTI_SZ;\r
   unsigned long type = REG_MULTI_SZ;\r
-  unsigned long envlen = 0;\r
 \r
   /* Dummy test to find buffer size */\r
 \r
   /* Dummy test to find buffer size */\r
-  unsigned long ret = RegQueryValueEx(key, NSSM_REG_ENV, 0, &type, NULL, &envlen);\r
+  unsigned long ret = RegQueryValueEx(key, value, 0, &type, NULL, envlen);\r
   if (ret != ERROR_SUCCESS) {\r
   if (ret != ERROR_SUCCESS) {\r
+    *envlen = 0;\r
     /* The service probably doesn't have any environment configured */\r
     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
     /* The service probably doesn't have any environment configured */\r
     if (ret == ERROR_FILE_NOT_FOUND) return 0;\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
     return 1;\r
   }\r
 \r
   if (type != REG_MULTI_SZ) {\r
     return 1;\r
   }\r
 \r
   if (type != REG_MULTI_SZ) {\r
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, NSSM_REG_ENV, service_name, 0);\r
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_INVALID_ENVIRONMENT_STRING_TYPE, value, service_name, 0);\r
     return 2;\r
   }\r
 \r
   /* Probably not possible */\r
     return 2;\r
   }\r
 \r
   /* Probably not possible */\r
-  if (! envlen) return 0;\r
+  if (! *envlen) return 0;\r
 \r
   /* Previously initialised? */\r
   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
 \r
 \r
   /* Previously initialised? */\r
   if (*env) HeapFree(GetProcessHeap(), 0, *env);\r
 \r
-  *env = (char *) HeapAlloc(GetProcessHeap(), 0, envlen);\r
+  *env = (char *) HeapAlloc(GetProcessHeap(), 0, *envlen);\r
   if (! *env) {\r
   if (! *env) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "environment registry", "set_environment()", 0);\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, value, "set_environment()", 0);\r
     return 3;\r
   }\r
 \r
   /* Actually get the strings */\r
     return 3;\r
   }\r
 \r
   /* Actually get the strings */\r
-  ret = RegQueryValueEx(key, NSSM_REG_ENV, 0, &type, (unsigned char *) *env, &envlen);\r
+  ret = RegQueryValueEx(key, value, 0, &type, (unsigned char *) *env, envlen);\r
   if (ret != ERROR_SUCCESS) {\r
   if (ret != ERROR_SUCCESS) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, value, error_string(GetLastError()), 0);\r
     HeapFree(GetProcessHeap(), 0, *env);\r
     *env = 0;\r
     HeapFree(GetProcessHeap(), 0, *env);\r
     *env = 0;\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);\r
+    *envlen = 0;\r
     return 4;\r
   }\r
 \r
     return 4;\r
   }\r
 \r
@@ -326,7 +327,42 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   }\r
 \r
   /* Try to get environment variables - may fail */\r
   }\r
 \r
   /* Try to get environment variables - may fail */\r
-  set_environment(service->name, key, &service->env);\r
+  set_environment(service->name, key, NSSM_REG_ENV, &service->env, &service->envlen);\r
+  /* Environment variables to add to existing rather than replace - may fail. */\r
+  set_environment(service->name, key, NSSM_REG_ENV_EXTRA, &service->env_extra, &service->env_extralen);\r
+\r
+  if (service->env_extra) {\r
+    /* Append these to any other environment variables set. */\r
+    if (service->env) {\r
+      /* Append extra variables to configured variables. */\r
+      unsigned long envlen = service->envlen + service->env_extralen - 1;\r
+      char *env = (char *) HeapAlloc(GetProcessHeap(), 0, envlen);\r
+      if (env) {\r
+        memmove(env, service->env, service->envlen - 1);\r
+        memmove(env + service->envlen - 1, service->env_extra, service->env_extralen);\r
+\r
+        HeapFree(GetProcessHeap(), 0, service->env);\r
+        service->env = env;\r
+        service->envlen = envlen;\r
+      }\r
+      else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "environment", "get_parameters()", 0);\r
+    }\r
+    else {\r
+      /* Append extra variables to our environment. */\r
+      char *env, *s;\r
+      size_t envlen, len;\r
+\r
+      env = service->env_extra;\r
+      len = 0;\r
+      while (*env) {\r
+        envlen = strlen(env) + 1;\r
+        for (s = env; *s && *s != '='; s++);\r
+        if (*s == '=') *s++ = '\0';\r
+        if (! SetEnvironmentVariable(env, s)) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETENVIRONMENTVARIABLE_FAILED, env, s, error_string(GetLastError()));\r
+        env += envlen;\r
+      }\r
+    }\r
+  }\r
 \r
   /* Try to get stdout and stderr */\r
   if (get_output_handles(key, si)) {\r
 \r
   /* Try to get stdout and stderr */\r
   if (get_output_handles(key, si)) {\r
index 0661b73..3c64b5d 100644 (file)
@@ -6,6 +6,7 @@
 #define NSSM_REG_FLAGS "AppParameters"\r
 #define NSSM_REG_DIR "AppDirectory"\r
 #define NSSM_REG_ENV "AppEnvironment"\r
 #define NSSM_REG_FLAGS "AppParameters"\r
 #define NSSM_REG_DIR "AppDirectory"\r
 #define NSSM_REG_ENV "AppEnvironment"\r
+#define NSSM_REG_ENV_EXTRA "AppEnvironmentExtra"\r
 #define NSSM_REG_EXIT "AppExit"\r
 #define NSSM_REG_THROTTLE "AppThrottle"\r
 #define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip"\r
 #define NSSM_REG_EXIT "AppExit"\r
 #define NSSM_REG_THROTTLE "AppThrottle"\r
 #define NSSM_REG_STOP_METHOD_SKIP "AppStopMethodSkip"\r
@@ -23,7 +24,7 @@
 int create_messages();\r
 int create_parameters(nssm_service_t *);\r
 int create_exit_action(char *, const char *);\r
 int create_messages();\r
 int create_parameters(nssm_service_t *);\r
 int create_exit_action(char *, const char *);\r
-int set_environment(char *, HKEY, char **);\r
+int set_environment(char *, HKEY, char *, char **, unsigned long *);\r
 int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool);\r
 int expand_parameter(HKEY, char *, char *, unsigned long, bool);\r
 int set_expand_string(HKEY, char *, char *);\r
 int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool);\r
 int expand_parameter(HKEY, char *, char *, unsigned long, bool);\r
 int set_expand_string(HKEY, char *, char *);\r
index 906ac64..ea5a460 100644 (file)
@@ -43,6 +43,7 @@ nssm_service_t *alloc_nssm_service() {
 void cleanup_nssm_service(nssm_service_t *service) {\r
   if (! service) return;\r
   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
 void cleanup_nssm_service(nssm_service_t *service) {\r
   if (! service) return;\r
   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
+  if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
   if (service->handle) CloseServiceHandle(service->handle);\r
   if (service->process_handle) CloseHandle(service->process_handle);\r
   if (service->wait_handle) UnregisterWait(service->process_handle);\r
   if (service->handle) CloseServiceHandle(service->handle);\r
   if (service->process_handle) CloseHandle(service->process_handle);\r
   if (service->wait_handle) UnregisterWait(service->process_handle);\r
index e9f36ba..5c56ed5 100644 (file)
--- a/service.h
+++ b/service.h
@@ -22,6 +22,9 @@ typedef struct {
   char flags[VALUE_LENGTH];\r
   char dir[MAX_PATH];\r
   char *env;\r
   char flags[VALUE_LENGTH];\r
   char dir[MAX_PATH];\r
   char *env;\r
+  unsigned long envlen;\r
+  char *env_extra;\r
+  unsigned long env_extralen;\r
   char stdin_path[MAX_PATH];\r
   char stdout_path[MAX_PATH];\r
   char stderr_path[MAX_PATH];\r
   char stdin_path[MAX_PATH];\r
   char stdout_path[MAX_PATH];\r
   char stderr_path[MAX_PATH];\r