Enable on-demand rotation.
[nssm.git] / service.cpp
index be70501..b8c73ef 100644 (file)
 #include "nssm.h"\r
 \r
+/* This is explicitly a wide string. */\r
+#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"\r
+\r
 bool is_admin;\r
-SERVICE_STATUS service_status;\r
-SERVICE_STATUS_HANDLE service_handle;\r
-HANDLE process_handle;\r
-HANDLE wait_handle;\r
-unsigned long pid;\r
-static char service_name[SERVICE_NAME_LENGTH];\r
-char exe[EXE_LENGTH];\r
-char flags[CMD_LENGTH];\r
-char dir[MAX_PATH];\r
-bool stopping;\r
-bool allow_restart;\r
-unsigned long throttle_delay;\r
-unsigned long stop_method;\r
-unsigned long kill_console_delay;\r
-unsigned long kill_window_delay;\r
-unsigned long kill_threads_delay;\r
-CRITICAL_SECTION throttle_section;\r
-CONDITION_VARIABLE throttle_condition;\r
-HANDLE throttle_timer;\r
-LARGE_INTEGER throttle_duetime;\r
 bool use_critical_section;\r
-FILETIME creation_time;\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
+const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };\r
+\r
+typedef struct {\r
+  int first;\r
+  int last;\r
+} list_t;\r
+\r
+int affinity_mask_to_string(__int64 mask, TCHAR **string) {\r
+  if (! string) return 1;\r
+  if (! mask) {\r
+    *string = 0;\r
+    return 0;\r
+  }\r
+\r
+  __int64 i, n;\r
+\r
+  /* SetProcessAffinityMask() accepts a mask of up to 64 processors. */\r
+  list_t set[64];\r
+  for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
+\r
+  for (i = 0, n = 0; i < _countof(set); i++) {\r
+    if (mask & (1LL << i)) {\r
+      if (set[n].first == -1) set[n].first = set[n].last = (int) i;\r
+      else if (set[n].last == (int) i - 1) set[n].last = (int) i;\r
+      else {\r
+        n++;\r
+        set[n].first = set[n].last = (int) i;\r
+      }\r
+    }\r
+  }\r
+\r
+  /* Worst case is 2x2 characters for first and last CPU plus - and/or , */\r
+  size_t len = (size_t) (n + 1) * 6;\r
+  *string = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));\r
+  if (! string) return 2;\r
+\r
+  size_t s = 0;\r
+  int ret;\r
+  for (i = 0; i <= n; i++) {\r
+    if (i) (*string)[s++] = _T(',');\r
+    ret = _sntprintf_s(*string + s, 3, _TRUNCATE, _T("%u"), set[i].first);\r
+    if (ret < 0) {\r
+      HeapFree(GetProcessHeap(), 0, *string);\r
+      *string = 0;\r
+      return 3;\r
+    }\r
+    else s += ret;\r
+    if (set[i].last != set[i].first) {\r
+      ret =_sntprintf_s(*string + s, 4, _TRUNCATE, _T("%c%u"), (set[i].last == set[i].first + 1) ? _T(',') : _T('-'), set[i].last);\r
+      if (ret < 0) {\r
+        HeapFree(GetProcessHeap(), 0, *string);\r
+        *string = 0;\r
+        return 4;\r
+      }\r
+      else s += ret;\r
+    }\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+int affinity_string_to_mask(TCHAR *string, __int64 *mask) {\r
+  if (! mask) return 1;\r
 \r
-static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
-static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
+  *mask = 0LL;\r
+  if (! string) return 0;\r
 \r
-static unsigned long throttle;\r
+  list_t set[64];\r
 \r
-static inline int throttle_milliseconds() {\r
+  TCHAR *s = string;\r
+  TCHAR *end;\r
+  int ret;\r
+  int i;\r
+  int n = 0;\r
+  unsigned long number;\r
+\r
+  for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
+  n = 0;\r
+\r
+  while (*s) {\r
+    ret = str_number(s, &number, &end);\r
+    s = end;\r
+    if (ret == 0 || ret == 2) {\r
+      if (number >= _countof(set)) return 2;\r
+      set[n].first = set[n].last = (int) number;\r
+\r
+      switch (*s) {\r
+        case 0:\r
+          break;\r
+\r
+        case _T(','):\r
+          n++;\r
+          s++;\r
+          break;\r
+\r
+        case _T('-'):\r
+          if (! *(++s)) return 3;\r
+          ret = str_number(s, &number, &end);\r
+          if (ret == 0 || ret == 2) {\r
+            s = end;\r
+            if (! *s || *s == _T(',')) {\r
+              set[n].last = (int) number;\r
+              if (! *s) break;\r
+              n++;\r
+              s++;\r
+            }\r
+            else return 3;\r
+          }\r
+          else return 3;\r
+          break;\r
+\r
+        default:\r
+          return 3;\r
+      }\r
+    }\r
+    else return 4;\r
+  }\r
+\r
+  for (i = 0; i <= n; i++) {\r
+    for (int j = set[i].first; j <= set[i].last; j++) (__int64) *mask |= (1LL << (__int64) j);\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+inline unsigned long priority_mask() {\r
+ return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
+}\r
+\r
+int priority_constant_to_index(unsigned long constant) {\r
+  switch (constant & priority_mask()) {\r
+    case REALTIME_PRIORITY_CLASS: return NSSM_REALTIME_PRIORITY;\r
+    case HIGH_PRIORITY_CLASS: return NSSM_HIGH_PRIORITY;\r
+    case ABOVE_NORMAL_PRIORITY_CLASS: return NSSM_ABOVE_NORMAL_PRIORITY;\r
+    case BELOW_NORMAL_PRIORITY_CLASS: return NSSM_BELOW_NORMAL_PRIORITY;\r
+    case IDLE_PRIORITY_CLASS: return NSSM_IDLE_PRIORITY;\r
+  }\r
+  return NSSM_NORMAL_PRIORITY;\r
+}\r
+\r
+unsigned long priority_index_to_constant(int index) {\r
+  switch (index) {\r
+    case NSSM_REALTIME_PRIORITY: return REALTIME_PRIORITY_CLASS;\r
+    case NSSM_HIGH_PRIORITY: return HIGH_PRIORITY_CLASS;\r
+    case NSSM_ABOVE_NORMAL_PRIORITY: return ABOVE_NORMAL_PRIORITY_CLASS;\r
+    case NSSM_BELOW_NORMAL_PRIORITY: return BELOW_NORMAL_PRIORITY_CLASS;\r
+    case NSSM_IDLE_PRIORITY: return IDLE_PRIORITY_CLASS;\r
+  }\r
+  return NORMAL_PRIORITY_CLASS;\r
+}\r
+\r
+static inline unsigned long throttle_milliseconds(unsigned long throttle) {\r
   /* pow() operates on doubles. */\r
-  int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
+  unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
   return ret * 1000;\r
 }\r
 \r
@@ -42,7 +173,7 @@ static inline int throttle_milliseconds() {
   control immediately.\r
 */\r
 static unsigned long WINAPI shutdown_service(void *arg) {\r
-  return stop_service(0, true, true);\r
+  return stop_service((nssm_service_t *) arg, 0, true, true);\r
 }\r
 \r
 /* Connect to the service manager */\r
@@ -56,112 +187,955 @@ SC_HANDLE open_service_manager() {
   return ret;\r
 }\r
 \r
+/* Open a service by name or display name. */\r
+SC_HANDLE open_service(SC_HANDLE services, TCHAR *service_name, TCHAR *canonical_name, unsigned long canonical_namelen) {\r
+  SC_HANDLE service_handle = OpenService(services, service_name, SERVICE_ALL_ACCESS);\r
+  if (service_handle) {\r
+    if (canonical_name && canonical_name != service_name) {\r
+      if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), service_name) < 0) {\r
+        print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
+        return 0;\r
+      }\r
+    }\r
+    return service_handle;\r
+  }\r
+\r
+  unsigned long error = GetLastError();\r
+  if (error != ERROR_SERVICE_DOES_NOT_EXIST) {\r
+    print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
+    return 0;\r
+  }\r
+\r
+  /* We can't look for a display name because there's no buffer to store it. */\r
+  if (! canonical_name) {\r
+    print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
+    return 0;\r
+  }\r
+\r
+  unsigned long bufsize, required, count, i;\r
+  unsigned long resume = 0;\r
+  EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
+  error = GetLastError();\r
+  if (error != ERROR_MORE_DATA) {\r
+    print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
+    return 0;\r
+  }\r
+\r
+  ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
+  if (! status) {\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("open_service()"));\r
+    return 0;\r
+  }\r
+\r
+  bufsize = required;\r
+  while (true) {\r
+    /*\r
+      EnumServicesStatus() returns:\r
+      1 when it retrieved data and there's no more data to come.\r
+      0 and sets last error to ERROR_MORE_DATA when it retrieved data and\r
+        there's more data to come.\r
+      0 and sets last error to something else on error.\r
+    */\r
+    int ret = EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
+    if (! ret) {\r
+      error = GetLastError();\r
+      if (error != ERROR_MORE_DATA) {\r
+        HeapFree(GetProcessHeap(), 0, status);\r
+        print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
+        return 0;\r
+      }\r
+    }\r
+\r
+    for (i = 0; i < count; i++) {\r
+      if (str_equiv(status[i].lpDisplayName, service_name)) {\r
+        if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), status[i].lpServiceName) < 0) {\r
+          HeapFree(GetProcessHeap(), 0, status);\r
+          print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
+          return 0;\r
+        }\r
+\r
+        HeapFree(GetProcessHeap(), 0, status);\r
+        return open_service(services, canonical_name, 0, 0);\r
+      }\r
+    }\r
+\r
+    if (ret) break;\r
+  }\r
+\r
+  /* Recurse so we can get an error message. */\r
+  return open_service(services, service_name, 0, 0);\r
+}\r
+\r
+QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
+  QUERY_SERVICE_CONFIG *qsc;\r
+  unsigned long bufsize;\r
+  unsigned long error;\r
+\r
+  QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
+  error = GetLastError();\r
+  if (error == ERROR_INSUFFICIENT_BUFFER) {\r
+    qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
+    if (! qsc) {\r
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
+      return 0;\r
+    }\r
+  }\r
+  else {\r
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
+    return 0;\r
+  }\r
+\r
+  if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
+    HeapFree(GetProcessHeap(), 0, qsc);\r
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
+    return 0;\r
+  }\r
+\r
+  return qsc;\r
+}\r
+\r
+int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
+  SERVICE_DESCRIPTION description;\r
+  ZeroMemory(&description, sizeof(description));\r
+  /*\r
+    lpDescription must be NULL if we aren't changing, the new description\r
+    or "".\r
+  */\r
+  if (buffer && buffer[0]) description.lpDescription = buffer;\r
+  else description.lpDescription = _T("");\r
+\r
+  if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;\r
+\r
+  log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);\r
+  return 1;\r
+}\r
+\r
+int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
+  if (! buffer) return 1;\r
+\r
+  unsigned long bufsize;\r
+  QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
+  unsigned long error = GetLastError();\r
+  if (error == ERROR_INSUFFICIENT_BUFFER) {\r
+    SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
+    if (! description) {\r
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
+      return 2;\r
+    }\r
+\r
+    if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
+      if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
+      else ZeroMemory(buffer, len * sizeof(TCHAR));\r
+      HeapFree(GetProcessHeap(), 0, description);\r
+      return 0;\r
+    }\r
+    else {\r
+      HeapFree(GetProcessHeap(), 0, description);\r
+      print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
+      return 3;\r
+    }\r
+  }\r
+  else {\r
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
+    return 4;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
+  if (! qsc) return 1;\r
+\r
+  switch (qsc->dwStartType) {\r
+    case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
+    case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
+    default: *startup = NSSM_STARTUP_AUTOMATIC;\r
+  }\r
+\r
+  if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
+\r
+  /* Check for delayed start. */\r
+  unsigned long bufsize;\r
+  unsigned long error;\r
+  QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
+  error = GetLastError();\r
+  if (error == ERROR_INSUFFICIENT_BUFFER) {\r
+    SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
+    if (! info) {\r
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
+      return 2;\r
+    }\r
+\r
+    if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
+      if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
+      HeapFree(GetProcessHeap(), 0, info);\r
+      return 0;\r
+    }\r
+    else {\r
+      error = GetLastError();\r
+      if (error != ERROR_INVALID_LEVEL) {\r
+        print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
+        return 3;\r
+      }\r
+    }\r
+  }\r
+  else if (error != ERROR_INVALID_LEVEL) {\r
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
+    return 3;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
+  if (! username) return 1;\r
+  if (! usernamelen) return 1;\r
+\r
+  *username = 0;\r
+  *usernamelen = 0;\r
+\r
+  if (! qsc) return 1;\r
+\r
+  if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
+\r
+  size_t len = _tcslen(qsc->lpServiceStartName);\r
+  *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
+  if (! *username) {\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
+    return 2;\r
+  }\r
+\r
+  memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
+  *usernamelen = len;\r
+\r
+  return 0;\r
+}\r
+\r
+int grant_logon_as_service(const TCHAR *username) {\r
+  if (! username) return 0;\r
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
+\r
+  /* Open Policy object. */\r
+  LSA_OBJECT_ATTRIBUTES attributes;\r
+  ZeroMemory(&attributes, sizeof(attributes));\r
+\r
+  LSA_HANDLE policy;\r
+\r
+  NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
+  if (status) {\r
+    print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
+    return 1;\r
+  }\r
+\r
+  /* Look up SID for the account. */\r
+  LSA_UNICODE_STRING lsa_username;\r
+#ifdef UNICODE\r
+  lsa_username.Buffer = (wchar_t *) username;\r
+  lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
+  lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
+#else\r
+  size_t buflen;\r
+  mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
+  lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);\r
+  lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
+  lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
+  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
+  else {\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
+    return 2;\r
+  }\r
+#endif\r
+\r
+  LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
+  LSA_TRANSLATED_SID *translated_sid;\r
+  status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
+#ifndef UNICODE\r
+  HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
+#endif\r
+  if (status) {\r
+    LsaFreeMemory(translated_domains);\r
+    LsaFreeMemory(translated_sid);\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
+    return 3;\r
+  }\r
+\r
+  if (translated_sid->Use != SidTypeUser) {\r
+    LsaFreeMemory(translated_domains);\r
+    LsaFreeMemory(translated_sid);\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
+    return 4;\r
+  }\r
+\r
+  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
+  if (! trust || ! IsValidSid(trust->Sid)) {\r
+    LsaFreeMemory(translated_domains);\r
+    LsaFreeMemory(translated_sid);\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
+    return 4;\r
+  }\r
+\r
+  /* GetSidSubAuthority*() return pointers! */\r
+  unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
+\r
+  /* Convert translated SID to SID. */\r
+  SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
+  if (! sid) {\r
+    LsaFreeMemory(translated_domains);\r
+    LsaFreeMemory(translated_sid);\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
+    return 4;\r
+  }\r
+\r
+  unsigned long error;\r
+  if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
+    error = GetLastError();\r
+    HeapFree(GetProcessHeap(), 0, sid);\r
+    LsaFreeMemory(translated_domains);\r
+    LsaFreeMemory(translated_sid);\r
+    LsaClose(policy);\r
+    print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
+    return 5;\r
+  }\r
+\r
+  for (unsigned char i = 0; i <= *n; i++) {\r
+    unsigned long *sub = GetSidSubAuthority(sid, i);\r
+    if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
+    else *sub = translated_sid->RelativeId;\r
+  }\r
+\r
+  LsaFreeMemory(translated_domains);\r
+  LsaFreeMemory(translated_sid);\r
+\r
+  /* Check if the SID has the "Log on as a service" right. */\r
+  LSA_UNICODE_STRING lsa_right;\r
+  lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
+  lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
+  lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
+\r
+  LSA_UNICODE_STRING *rights;\r
+  unsigned long count = ~0;\r
+  status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
+  if (status) {\r
+    /*\r
+      If the account has no rights set LsaEnumerateAccountRights() will return\r
+      STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
+    */\r
+    error = LsaNtStatusToWinError(status);\r
+    if (error != ERROR_FILE_NOT_FOUND) {\r
+      HeapFree(GetProcessHeap(), 0, sid);\r
+      LsaClose(policy);\r
+      print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
+      return 4;\r
+    }\r
+  }\r
+\r
+  for (unsigned long i = 0; i < count; i++) {\r
+    if (rights[i].Length != lsa_right.Length) continue;\r
+    if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
+    /* The SID has the right. */\r
+    HeapFree(GetProcessHeap(), 0, sid);\r
+    LsaFreeMemory(rights);\r
+    LsaClose(policy);\r
+    return 0;\r
+  }\r
+  LsaFreeMemory(rights);\r
+\r
+  /* Add the right. */\r
+  status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
+  HeapFree(GetProcessHeap(), 0, sid);\r
+  LsaClose(policy);\r
+  if (status) {\r
+    print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
+    return 5;\r
+  }\r
+\r
+  print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
+  return 0;\r
+}\r
+\r
+/* Set default values which aren't zero. */\r
+void set_nssm_service_defaults(nssm_service_t *service) {\r
+  if (! service) return;\r
+\r
+  service->type = SERVICE_WIN32_OWN_PROCESS;\r
+  service->priority = NORMAL_PRIORITY_CLASS;\r
+  service->stdin_sharing = NSSM_STDIN_SHARING;\r
+  service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
+  service->stdin_flags = NSSM_STDIN_FLAGS;\r
+  service->stdout_sharing = NSSM_STDOUT_SHARING;\r
+  service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
+  service->stdout_flags = NSSM_STDOUT_FLAGS;\r
+  service->stderr_sharing = NSSM_STDERR_SHARING;\r
+  service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
+  service->stderr_flags = NSSM_STDERR_FLAGS;\r
+  service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
+  service->stop_method = ~0;\r
+  service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
+  service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
+  service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
+}\r
+\r
+/* Allocate and zero memory for a service. */\r
+nssm_service_t *alloc_nssm_service() {\r
+  nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
+  if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
+  return service;\r
+}\r
+\r
+/* Free memory for a service. */\r
+void cleanup_nssm_service(nssm_service_t *service) {\r
+  if (! service) return;\r
+  if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
+  if (service->password) {\r
+    SecureZeroMemory(service->password, service->passwordlen);\r
+    HeapFree(GetProcessHeap(), 0, service->password);\r
+  }\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->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
+  if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
+  HeapFree(GetProcessHeap(), 0, service);\r
+}\r
+\r
 /* About to install the service */\r
-int pre_install_service(int argc, char **argv) {\r
+int pre_install_service(int argc, TCHAR **argv) {\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  set_nssm_service_defaults(service);\r
+  if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
+\r
   /* Show the dialogue box if we didn't give the service name and path */\r
-  if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);\r
+  if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
+\r
+  if (! service) {\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
+    return 1;\r
+  }\r
+  _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
 \r
   /* Arguments are optional */\r
-  char *flags;\r
   size_t flagslen = 0;\r
   size_t s = 0;\r
   int i;\r
-  for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
+  for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
   if (! flagslen) flagslen = 1;\r
-\r
-  flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
-  if (! flags) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
+  if (flagslen > _countof(service->flags)) {\r
+    print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
     return 2;\r
   }\r
-  ZeroMemory(flags, flagslen);\r
 \r
-  /*\r
-    This probably isn't UTF8-safe and should use std::string or something\r
-    but it's been broken for the best part of a decade and due for a rewrite\r
-    anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
-    the flags buffer but as the program exits that isn't a big problem.\r
-  */\r
   for (i = 2; i < argc; i++) {\r
-    size_t len = strlen(argv[i]);\r
-    memmove(flags + s, argv[i], len);\r
+    size_t len = _tcslen(argv[i]);\r
+    memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
     s += len;\r
-    if (i < argc - 1) flags[s++] = ' ';\r
+    if (i < argc - 1) service->flags[s++] = _T(' ');\r
+  }\r
+\r
+  /* Work out directory name */\r
+  _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
+  strip_basename(service->dir);\r
+\r
+  int ret = install_service(service);\r
+  cleanup_nssm_service(service);\r
+  return ret;\r
+}\r
+\r
+/* About to edit the service. */\r
+int pre_edit_service(int argc, TCHAR **argv) {\r
+  /* Require service name. */\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"), service_name);\r
+\r
+  /* Open service manager */\r
+  SC_HANDLE services = open_service_manager();\r
+  if (! services) {\r
+    print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+    return 2;\r
+  }\r
+\r
+  /* Try to open the service */\r
+  service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
+  if (! service->handle) {\r
+    CloseServiceHandle(services);\r
+    return 3;\r
+  }\r
+\r
+  /* Get system details. */\r
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
+  if (! qsc) {\r
+    CloseHandle(service->handle);\r
+    CloseServiceHandle(services);\r
+    return 4;\r
+  }\r
+\r
+  service->type = qsc->dwServiceType;\r
+  if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\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
+    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
+    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
+  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
+  _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
+  HeapFree(GetProcessHeap(), 0, qsc);\r
+\r
+  /* Get extended system details. */\r
+  if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
+    if (mode != MODE_GETTING) {\r
+      CloseHandle(service->handle);\r
+      CloseServiceHandle(services);\r
+      return 6;\r
+    }\r
+  }\r
+\r
+  /* Get NSSM details. */\r
+  get_parameters(service, 0);\r
+\r
+  CloseServiceHandle(services);\r
+\r
+  if (! service->exe[0]) {\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
-  return install_service(argv[0], argv[1], flags);\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
+  if (! service->native) RegCloseKey(key);\r
+  CloseHandle(service->handle);\r
+\r
+  return 0;\r
 }\r
 \r
 /* About to remove the service */\r
-int pre_remove_service(int argc, char **argv) {\r
+int pre_remove_service(int argc, TCHAR **argv) {\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  set_nssm_service_defaults(service);\r
+  if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
+\r
   /* Show dialogue box if we didn't pass service name and "confirm" */\r
-  if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
-  if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
+  if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
+  if (str_equiv(argv[1], _T("confirm"))) {\r
+    int ret = remove_service(service);\r
+    cleanup_nssm_service(service);\r
+    return ret;\r
+  }\r
   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
   return 100;\r
 }\r
 \r
 /* Install the service */\r
-int install_service(char *name, char *exe, char *flags) {\r
+int install_service(nssm_service_t *service) {\r
+  if (! service) return 1;\r
+\r
   /* Open service manager */\r
   SC_HANDLE services = open_service_manager();\r
   if (! services) {\r
     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+    cleanup_nssm_service(service);\r
     return 2;\r
   }\r
 \r
   /* Get path of this program */\r
-  char path[MAX_PATH];\r
-  GetModuleFileName(0, path, MAX_PATH);\r
-\r
-  /* Construct command */\r
-  char command[CMD_LENGTH];\r
-  size_t pathlen = strlen(path);\r
-  if (pathlen + 1 >= VALUE_LENGTH) {\r
-    print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
-    return 3;\r
-  }\r
-  if (_snprintf_s(command, sizeof(command), _TRUNCATE, "\"%s\"", path) < 0) {\r
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
-    return 4;\r
-  }\r
+  GetModuleFileName(0, service->image, _countof(service->image));\r
 \r
-  /* Work out directory name */\r
-  size_t len = strlen(exe);\r
-  size_t i;\r
-  for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);\r
-  char dir[MAX_PATH];\r
-  memmove(dir, exe, i);\r
-  dir[i] = '\0';\r
-\r
-  /* Create the service */\r
-  SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);\r
-  if (! service) {\r
-    print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
+  /* Create the service - settings will be changed in edit_service() */\r
+  service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
+  if (! service->handle) {\r
+    print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
     CloseServiceHandle(services);\r
     return 5;\r
   }\r
 \r
-  /* Now we need to put the parameters into the registry */\r
-  if (create_parameters(name, exe, flags, dir)) {\r
-    print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
-    DeleteService(service);\r
+  if (edit_service(service, false)) {\r
+    DeleteService(service->handle);\r
     CloseServiceHandle(services);\r
     return 6;\r
   }\r
 \r
-  set_service_recovery(service, name);\r
+  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
 \r
   /* Cleanup */\r
-  CloseServiceHandle(service);\r
   CloseServiceHandle(services);\r
 \r
-  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);\r
   return 0;\r
 }\r
 \r
+/* Edit the service. */\r
+int edit_service(nssm_service_t *service, bool editing) {\r
+  if (! service) return 1;\r
+\r
+  /*\r
+    The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
+    and SERVICE_INTERACTIVE_PROCESS.\r
+  */\r
+  service->type &= SERVICE_INTERACTIVE_PROCESS;\r
+  service->type |= SERVICE_WIN32_OWN_PROCESS;\r
+\r
+  /* Startup type. */\r
+  unsigned long startup;\r
+  switch (service->startup) {\r
+    case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
+    case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
+    default: startup = SERVICE_AUTO_START;\r
+  }\r
+\r
+  /* Display name. */\r
+  if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
+\r
+  /*\r
+    Username must be NULL if we aren't changing or an account name.\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
+  TCHAR *username = 0;\r
+  TCHAR *password = 0;\r
+  if (service->usernamelen) {\r
+    username = service->username;\r
+    if (service->passwordlen) password = service->password;\r
+    else password = _T("");\r
+  }\r
+  else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
+\r
+  if (grant_logon_as_service(username)) {\r
+    print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
+    return 5;\r
+  }\r
+\r
+  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
+    return 5;\r
+  }\r
+\r
+  if (service->description[0] || editing) {\r
+    set_service_description(service->name, service->handle, service->description);\r
+  }\r
+\r
+  SERVICE_DELAYED_AUTO_START_INFO delayed;\r
+  ZeroMemory(&delayed, sizeof(delayed));\r
+  if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
+  else delayed.fDelayedAutostart = 0;\r
+  /* Delayed startup isn't supported until Vista. */\r
+  if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
+    unsigned long error = GetLastError();\r
+    /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
+    if (error != ERROR_INVALID_LEVEL) {\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
+    }\r
+  }\r
+\r
+  /* Don't mess with parameters which aren't ours. */\r
+  if (! service->native) {\r
+    /* Now we need to put the parameters into the registry */\r
+    if (create_parameters(service, editing)) {\r
+      print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
+      return 6;\r
+    }\r
+\r
+    set_service_recovery(service);\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+/* Control a service. */\r
+int control_service(unsigned long control, int argc, TCHAR **argv) {\r
+  if (argc < 1) return usage(1);\r
+  TCHAR *service_name = argv[0];\r
+  TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
+\r
+  SC_HANDLE services = open_service_manager();\r
+  if (! services) {\r
+    print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+    return 2;\r
+  }\r
+\r
+  SC_HANDLE service_handle = open_service(services, service_name, canonical_name, _countof(canonical_name));\r
+  if (! service_handle) {\r
+    CloseServiceHandle(services);\r
+    return 3;\r
+  }\r
+\r
+  int ret;\r
+  unsigned long error;\r
+  SERVICE_STATUS service_status;\r
+  if (control == 0) {\r
+    ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
+    error = GetLastError();\r
+    CloseHandle(service_handle);\r
+    CloseServiceHandle(services);\r
+\r
+    if (error == ERROR_IO_PENDING) {\r
+      /*\r
+        Older versions of Windows return immediately with ERROR_IO_PENDING\r
+        indicate that the operation is still in progress.  Newer versions\r
+        will return it if there really is a delay.  As far as we're\r
+        concerned the operation is a success.  We don't claim to offer a\r
+        fully-feature service control method; it's just a quick 'n' dirty\r
+        interface.\r
+\r
+        In the future we may identify and handle this situation properly.\r
+      */\r
+      ret = 1;\r
+      error = ERROR_SUCCESS;\r
+    }\r
+\r
+    if (ret) {\r
+      _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
+      return 0;\r
+    }\r
+    else {\r
+      _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
+      return 1;\r
+    }\r
+  }\r
+  else if (control == SERVICE_CONTROL_INTERROGATE) {\r
+    /*\r
+      We could actually send an INTERROGATE control but that won't return\r
+      any information if the service is stopped and we don't care about\r
+      the extra details it might give us in any case.  So we'll fake it.\r
+    */\r
+    ret = QueryServiceStatus(service_handle, &service_status);\r
+    error = GetLastError();\r
+\r
+    if (ret) {\r
+      switch (service_status.dwCurrentState) {\r
+        case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break;\r
+        case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break;\r
+        case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break;\r
+        case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break;\r
+        case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break;\r
+        case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break;\r
+        case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break;\r
+        default: _tprintf(_T("?\n")); return 1;\r
+      }\r
+      return 0;\r
+    }\r
+    else {\r
+      _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
+      return 1;\r
+    }\r
+  }\r
+  else {\r
+    ret = ControlService(service_handle, control, &service_status);\r
+    error = GetLastError();\r
+    CloseHandle(service_handle);\r
+    CloseServiceHandle(services);\r
+\r
+    if (error == ERROR_IO_PENDING) {\r
+      ret = 1;\r
+      error = ERROR_SUCCESS;\r
+    }\r
+\r
+    if (ret) {\r
+      _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
+      return 0;\r
+    }\r
+    else {\r
+      _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
+      return 1;\r
+    }\r
+  }\r
+}\r
+\r
 /* Remove the service */\r
-int remove_service(char *name) {\r
+int remove_service(nssm_service_t *service) {\r
+  if (! service) return 1;\r
+\r
   /* Open service manager */\r
   SC_HANDLE services = open_service_manager();\r
   if (! services) {\r
@@ -170,33 +1144,39 @@ int remove_service(char *name) {
   }\r
 \r
   /* Try to open the service */\r
-  SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);\r
-  if (! service) {\r
-    print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
+  service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
+  if (! service->handle) {\r
     CloseServiceHandle(services);\r
     return 3;\r
   }\r
 \r
+  /* Get the canonical service name. We open it case insensitively. */\r
+  unsigned long bufsize = _countof(service->displayname);\r
+  GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
+  bufsize = _countof(service->name);\r
+  GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
+\r
   /* Try to delete the service */\r
-  if (! DeleteService(service)) {\r
+  if (! DeleteService(service->handle)) {\r
     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
-    CloseServiceHandle(service);\r
     CloseServiceHandle(services);\r
     return 4;\r
   }\r
 \r
   /* Cleanup */\r
-  CloseServiceHandle(service);\r
   CloseServiceHandle(services);\r
 \r
-  print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);\r
+  print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
   return 0;\r
 }\r
 \r
 /* Service initialisation */\r
-void WINAPI service_main(unsigned long argc, char **argv) {\r
-  if (_snprintf_s(service_name, sizeof(service_name), _TRUNCATE, "%s", argv[0]) < 0) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);\r
+void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (! service) return;\r
+\r
+  if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
     return;\r
   }\r
 \r
@@ -205,126 +1185,120 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   else use_critical_section = false;\r
 \r
   /* Initialise status */\r
-  ZeroMemory(&service_status, sizeof(service_status));\r
-  service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
-  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
-  service_status.dwWin32ExitCode = NO_ERROR;\r
-  service_status.dwServiceSpecificExitCode = 0;\r
-  service_status.dwCheckPoint = 0;\r
-  service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
+  ZeroMemory(&service->status, sizeof(service->status));\r
+  service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
+  service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
+  service->status.dwWin32ExitCode = NO_ERROR;\r
+  service->status.dwServiceSpecificExitCode = 0;\r
+  service->status.dwCheckPoint = 0;\r
+  service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
 \r
   /* Signal we AREN'T running the server */\r
-  process_handle = 0;\r
-  pid = 0;\r
+  service->process_handle = 0;\r
+  service->pid = 0;\r
 \r
   /* Register control handler */\r
-  service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);\r
-  if (! service_handle) {\r
+  service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
+  if (! service->status_handle) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
     return;\r
   }\r
 \r
-  log_service_control(service_name, 0, true);\r
+  log_service_control(service->name, 0, true);\r
 \r
-  service_status.dwCurrentState = SERVICE_START_PENDING;\r
-  service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;\r
-  SetServiceStatus(service_handle, &service_status);\r
+  service->status.dwCurrentState = SERVICE_START_PENDING;\r
+  service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
+  SetServiceStatus(service->status_handle, &service->status);\r
 \r
   if (is_admin) {\r
     /* Try to create the exit action parameters; we don't care if it fails */\r
-    create_exit_action(argv[0], exit_action_strings[0]);\r
+    create_exit_action(service->name, exit_action_strings[0], false);\r
 \r
-    set_service_recovery(0, service_name);\r
+    SC_HANDLE services = open_service_manager();\r
+    if (services) {\r
+      service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
+      set_service_recovery(service);\r
+      CloseServiceHandle(services);\r
+    }\r
   }\r
 \r
   /* Used for signalling a resume if the service pauses when throttled. */\r
-  if (use_critical_section) InitializeCriticalSection(&throttle_section);\r
+  if (use_critical_section) {\r
+    InitializeCriticalSection(&service->throttle_section);\r
+    service->throttle_section_initialised = true;\r
+  }\r
   else {\r
-    throttle_timer = CreateWaitableTimer(0, 1, 0);\r
-    if (! throttle_timer) {\r
-      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
+    service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
+    if (! service->throttle_timer) {\r
+      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
     }\r
   }\r
 \r
-  monitor_service();\r
+  monitor_service(service);\r
 }\r
 \r
 /* Make sure service recovery actions are taken where necessary */\r
-void set_service_recovery(SC_HANDLE service, char *service_name) {\r
-  SC_HANDLE services = 0;\r
-\r
-  if (! service) {\r
-    services = open_service_manager();\r
-    if (! services) return;\r
-\r
-    service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
-    if (! service) return;\r
-  }\r
-\r
+void set_service_recovery(nssm_service_t *service) {\r
   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
   ZeroMemory(&flag, sizeof(flag));\r
   flag.fFailureActionsOnNonCrashFailures = true;\r
 \r
   /* This functionality was added in Vista so the call may fail */\r
-  if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
+  if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
     unsigned long error = GetLastError();\r
     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
     if (error != ERROR_INVALID_LEVEL) {\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
     }\r
   }\r
-\r
-  if (services) {\r
-    CloseServiceHandle(service);\r
-    CloseServiceHandle(services);\r
-  }\r
 }\r
 \r
-int monitor_service() {\r
+int monitor_service(nssm_service_t *service) {\r
   /* Set service status to started */\r
-  int ret = start_service();\r
+  int ret = start_service(service);\r
   if (ret) {\r
-    char code[16];\r
-    _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
+    TCHAR code[16];\r
+    _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
     return ret;\r
   }\r
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
 \r
   /* Monitor service */\r
-  if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
+  if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
   }\r
 \r
   return 0;\r
 }\r
 \r
-char *service_control_text(unsigned long control) {\r
+TCHAR *service_control_text(unsigned long control) {\r
   switch (control) {\r
     /* HACK: there is no SERVICE_CONTROL_START constant */\r
-    case 0: return "START";\r
-    case SERVICE_CONTROL_STOP: return "STOP";\r
-    case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";\r
-    case SERVICE_CONTROL_PAUSE: return "PAUSE";\r
-    case SERVICE_CONTROL_CONTINUE: return "CONTINUE";\r
-    case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";\r
+    case 0: return _T("START");\r
+    case SERVICE_CONTROL_STOP: return _T("STOP");\r
+    case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
+    case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
+    case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
+    case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
+    case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");\r
     default: return 0;\r
   }\r
 }\r
 \r
-void log_service_control(char *service_name, unsigned long control, bool handled) {\r
-  char *text = service_control_text(control);\r
+void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
+  TCHAR *text = service_control_text(control);\r
   unsigned long event;\r
 \r
   if (! text) {\r
     /* "0x" + 8 x hex + NULL */\r
-    text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);\r
+    text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
     if (! text) {\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
       return;\r
     }\r
-    if (_snprintf_s(text, 11, _TRUNCATE, "0x%08x", control) < 0) {\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control()", 0);\r
+    if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
       HeapFree(GetProcessHeap(), 0, text);\r
       return;\r
     }\r
@@ -343,6 +1317,8 @@ void log_service_control(char *service_name, unsigned long control, bool handled
 \r
 /* Service control handler */\r
 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
+  nssm_service_t *service = (nssm_service_t *) context;\r
+\r
   switch (control) {\r
     case SERVICE_CONTROL_INTERROGATE:\r
       /* We always keep the service status up-to-date so this is a no-op. */\r
@@ -350,40 +1326,40 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 \r
     case SERVICE_CONTROL_SHUTDOWN:\r
     case SERVICE_CONTROL_STOP:\r
-      log_service_control(service_name, control, true);\r
+      log_service_control(service->name, control, true);\r
       /*\r
         We MUST acknowledge the stop request promptly but we're committed to\r
         waiting for the application to exit.  Spawn a new thread to wait\r
         while we acknowledge the request.\r
       */\r
-      if (! CreateThread(NULL, 0, shutdown_service, (void *) service_name, 0, NULL)) {\r
+      if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
 \r
         /*\r
           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
           to complete in time in this thread.\r
         */\r
-        kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
-        kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
-        kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
+        service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
+        service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
+        service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
 \r
-        stop_service(0, true, true);\r
+        stop_service(service, 0, true, true);\r
       }\r
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_CONTINUE:\r
-      log_service_control(service_name, control, true);\r
-      throttle = 0;\r
-      if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);\r
+      log_service_control(service->name, control, true);\r
+      service->throttle = 0;\r
+      if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
       else {\r
-        if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
-        ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
-        SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
+        if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
+        ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
+        SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
       }\r
-      service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
-      service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
-      SetServiceStatus(service_handle, &service_status);\r
+      service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
+      service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
+      SetServiceStatus(service->status_handle, &service->status);\r
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_PAUSE:\r
@@ -391,21 +1367,27 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
         We don't accept pause messages but it isn't possible to register\r
         only for continue messages so we have to handle this case.\r
       */\r
-      log_service_control(service_name, control, false);\r
+      log_service_control(service->name, control, false);\r
       return ERROR_CALL_NOT_IMPLEMENTED;\r
+\r
+    case NSSM_SERVICE_CONTROL_ROTATE:\r
+      log_service_control(service->name, control, true);\r
+      if (service->rotate_stdout_online) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
+      if (service->rotate_stdout_online) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
+      return NO_ERROR;\r
   }\r
 \r
   /* Unknown control */\r
-  log_service_control(service_name, control, false);\r
+  log_service_control(service->name, control, false);\r
   return ERROR_CALL_NOT_IMPLEMENTED;\r
 }\r
 \r
 /* Start the service */\r
-int start_service() {\r
-  stopping = false;\r
-  allow_restart = true;\r
+int start_service(nssm_service_t *service) {\r
+  service->stopping = false;\r
+  service->allow_restart = true;\r
 \r
-  if (process_handle) return 0;\r
+  if (service->process_handle) return 0;\r
 \r
   /* Allocate a STARTUPINFO structure for a new process */\r
   STARTUPINFO si;\r
@@ -417,109 +1399,152 @@ int start_service() {
   ZeroMemory(&pi, sizeof(pi));\r
 \r
   /* Get startup parameters */\r
-  char *env = 0;\r
-  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &kill_console_delay, &kill_window_delay, &kill_threads_delay, &si);\r
+  int ret = get_parameters(service, &si);\r
   if (ret) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
-    return stop_service(2, true, true);\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
+    return stop_service(service, 2, true, true);\r
   }\r
 \r
   /* Launch executable with arguments */\r
-  char cmd[CMD_LENGTH];\r
-  if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", exe, flags) < 0) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
+  TCHAR cmd[CMD_LENGTH];\r
+  if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
     close_output_handles(&si);\r
-    return stop_service(2, true, true);\r
+    return stop_service(service, 2, true, true);\r
   }\r
 \r
-  throttle_restart();\r
+  throttle_restart(service);\r
 \r
   bool inherit_handles = false;\r
   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
-  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {\r
+  unsigned long flags = service->priority & priority_mask();\r
+  if (service->affinity) flags |= CREATE_SUSPENDED;\r
+#ifdef UNICODE\r
+  flags |= CREATE_UNICODE_ENVIRONMENT;\r
+#endif\r
+  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
+    unsigned long exitcode = 3;\r
     unsigned long error = GetLastError();\r
-    if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
-    else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
+    if (error == ERROR_INVALID_PARAMETER && service->env) {\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
+      if (test_environment(service->env)) exitcode = 4;\r
+    }\r
+    else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
     close_output_handles(&si);\r
-    return stop_service(3, true, true);\r
+    return stop_service(service, exitcode, true, true);\r
   }\r
-  process_handle = pi.hProcess;\r
-  pid = pi.dwProcessId;\r
+  service->process_handle = pi.hProcess;\r
+  service->pid = pi.dwProcessId;\r
+\r
+  if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
+\r
+  close_output_handles(&si, ! service->rotate_stdout_online, ! service->rotate_stderr_online);\r
+\r
+  if (service->affinity) {\r
+    /*\r
+      We are explicitly storing service->affinity as a 64-bit unsigned integer\r
+      so that we can parse it regardless of whether we're running in 32-bit\r
+      or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
+      defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
+      (or when running the 32-bit NSSM).\r
+\r
+      The result is a lot of seemingly-unnecessary casting throughout the code\r
+      and potentially confusion when we actually try to start the service.\r
+      Having said that, however, it's unlikely that we're actually going to\r
+      run in 32-bit mode on a system which has more than 32 CPUs so the\r
+      likelihood of seeing a confusing situation is somewhat diminished.\r
+    */\r
+    DWORD_PTR affinity, system_affinity;\r
+\r
+    if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
+    else {\r
+      affinity = (DWORD_PTR) service->affinity;\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+    }\r
 \r
-  if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
+    if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
+      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+    }\r
 \r
-  close_output_handles(&si);\r
+    ResumeThread(pi.hThread);\r
+  }\r
 \r
   /*\r
     Wait for a clean startup before changing the service status to RUNNING\r
     but be mindful of the fact that we are blocking the service control manager\r
     so abandon the wait before too much time has elapsed.\r
   */\r
-  unsigned long delay = throttle_delay;\r
+  unsigned long delay = service->throttle_delay;\r
   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
-    char delay_milliseconds[16];\r
-    _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);\r
-    char deadline_milliseconds[16];\r
-    _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);\r
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
+    TCHAR delay_milliseconds[16];\r
+    _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
+    TCHAR deadline_milliseconds[16];\r
+    _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
   }\r
-  unsigned long deadline = WaitForSingleObject(process_handle, delay);\r
+  unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
 \r
   /* Signal successful start */\r
-  service_status.dwCurrentState = SERVICE_RUNNING;\r
-  SetServiceStatus(service_handle, &service_status);\r
+  service->status.dwCurrentState = SERVICE_RUNNING;\r
+  SetServiceStatus(service->status_handle, &service->status);\r
 \r
   /* Continue waiting for a clean startup. */\r
   if (deadline == WAIT_TIMEOUT) {\r
-    if (throttle_delay > delay) {\r
-      if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0;\r
+    if (service->throttle_delay > delay) {\r
+      if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
     }\r
-    else throttle = 0;\r
+    else service->throttle = 0;\r
   }\r
 \r
+  /* Ensure the restart delay is always applied. */\r
+  if (service->restart_delay && ! service->throttle) service->throttle++;\r
+\r
   return 0;\r
 }\r
 \r
 /* Stop the service */\r
-int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
-  allow_restart = false;\r
-  if (wait_handle) UnregisterWait(wait_handle);\r
+int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
+  service->allow_restart = false;\r
+  if (service->wait_handle) {\r
+    UnregisterWait(service->wait_handle);\r
+    service->wait_handle = 0;\r
+  }\r
 \r
   if (default_action && ! exitcode && ! graceful) {\r
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY] ,0);\r
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY], 0);\r
     graceful = true;\r
   }\r
 \r
   /* Signal we are stopping */\r
   if (graceful) {\r
-    service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
-    service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
-    SetServiceStatus(service_handle, &service_status);\r
+    service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+    service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
+    SetServiceStatus(service->status_handle, &service->status);\r
   }\r
 \r
   /* Nothing to do if service isn't running */\r
-  if (pid) {\r
+  if (service->pid) {\r
     /* Shut down service */\r
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
-    kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);\r
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
+    kill_process(service, service->process_handle, service->pid, 0);\r
   }\r
-  else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
+  else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
 \r
-  end_service((void *) pid, true);\r
+  end_service((void *) service, true);\r
 \r
   /* Signal we stopped */\r
   if (graceful) {\r
-    service_status.dwCurrentState = SERVICE_STOPPED;\r
+    service->status.dwCurrentState = SERVICE_STOPPED;\r
     if (exitcode) {\r
-      service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
-      service_status.dwServiceSpecificExitCode = exitcode;\r
+      service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
+      service->status.dwServiceSpecificExitCode = exitcode;\r
     }\r
     else {\r
-      service_status.dwWin32ExitCode = NO_ERROR;\r
-      service_status.dwServiceSpecificExitCode = 0;\r
+      service->status.dwWin32ExitCode = NO_ERROR;\r
+      service->status.dwServiceSpecificExitCode = 0;\r
     }\r
-    SetServiceStatus(service_handle, &service_status);\r
+    SetServiceStatus(service->status_handle, &service->status);\r
   }\r
 \r
   return exitcode;\r
@@ -527,32 +1552,37 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
 \r
 /* Callback function triggered when the server exits */\r
 void CALLBACK end_service(void *arg, unsigned char why) {\r
-  if (stopping) return;\r
+  nssm_service_t *service = (nssm_service_t *) arg;\r
 \r
-  stopping = true;\r
+  if (service->stopping) return;\r
 \r
-  pid = (unsigned long) arg;\r
+  service->stopping = true;\r
 \r
   /* Check exit code */\r
   unsigned long exitcode = 0;\r
-  char code[16];\r
-  FILETIME exit_time;\r
-  GetExitCodeProcess(process_handle, &exitcode);\r
-  if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);\r
-  CloseHandle(process_handle);\r
+  TCHAR code[16];\r
+  if (service->process_handle) {\r
+    GetExitCodeProcess(service->process_handle, &exitcode);\r
+    if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
+    CloseHandle(service->process_handle);\r
+  }\r
+  else GetSystemTimeAsFileTime(&service->exit_time);\r
+\r
+  service->process_handle = 0;\r
 \r
   /*\r
     Log that the service ended BEFORE logging about killing the process\r
     tree.  See below for the possible values of the why argument.\r
   */\r
   if (! why) {\r
-    _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);\r
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
+    _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
   }\r
 \r
   /* Clean up. */\r
   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
-  kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);\r
+  if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
+  service->pid = 0;\r
 \r
   /*\r
     The why argument is true if our wait timed out or false otherwise.\r
@@ -561,84 +1591,90 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     this is a controlled shutdown, and don't take any restart action.\r
   */\r
   if (why) return;\r
-  if (! allow_restart) return;\r
+  if (! service->allow_restart) return;\r
 \r
   /* What action should we take? */\r
   int action = NSSM_EXIT_RESTART;\r
-  unsigned char action_string[ACTION_LEN];\r
+  TCHAR action_string[ACTION_LEN];\r
   bool default_action;\r
-  if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
+  if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
     for (int i = 0; exit_action_strings[i]; i++) {\r
-      if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
+      if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
         action = i;\r
         break;\r
       }\r
     }\r
   }\r
 \r
-  process_handle = 0;\r
-  pid = 0;\r
   switch (action) {\r
     /* Try to restart the service or return failure code to service manager */\r
     case NSSM_EXIT_RESTART:\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
-      while (monitor_service()) {\r
-        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
+      while (monitor_service(service)) {\r
+        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
         Sleep(30000);\r
       }\r
     break;\r
 \r
     /* Do nothing, just like srvany would */\r
     case NSSM_EXIT_IGNORE:\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
       Sleep(INFINITE);\r
     break;\r
 \r
     /* Tell the service manager we are finished */\r
     case NSSM_EXIT_REALLY:\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
-      stop_service(exitcode, true, default_action);\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
+      stop_service(service, exitcode, true, default_action);\r
     break;\r
 \r
     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
     case NSSM_EXIT_UNCLEAN:\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
-      stop_service(exitcode, false, default_action);\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
+      stop_service(service, exitcode, false, default_action);\r
       free_imports();\r
       exit(exitcode);\r
     break;\r
   }\r
 }\r
 \r
-void throttle_restart() {\r
+void throttle_restart(nssm_service_t *service) {\r
   /* This can't be a restart if the service is already running. */\r
-  if (! throttle++) return;\r
+  if (! service->throttle++) return;\r
+\r
+  unsigned long ms;\r
+  unsigned long throttle_ms = throttle_milliseconds(service->throttle);\r
+  TCHAR threshold[8], milliseconds[8];\r
+\r
+  if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
+  else ms = throttle_ms;\r
 \r
-  int ms = throttle_milliseconds();\r
+  if (service->throttle > 7) service->throttle = 8;\r
 \r
-  if (throttle > 7) throttle = 8;\r
+  _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
 \r
-  char threshold[8], milliseconds[8];\r
-  _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);\r
-  _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);\r
-  log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
+  if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
+  else {\r
+    _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
+  }\r
 \r
-  if (use_critical_section) EnterCriticalSection(&throttle_section);\r
-  else if (throttle_timer) {\r
-    ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
-    throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
-    SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
+  if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
+  else if (service->throttle_timer) {\r
+    ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
+    service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
+    SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
   }\r
 \r
-  service_status.dwCurrentState = SERVICE_PAUSED;\r
-  SetServiceStatus(service_handle, &service_status);\r
+  service->status.dwCurrentState = SERVICE_PAUSED;\r
+  SetServiceStatus(service->status_handle, &service->status);\r
 \r
   if (use_critical_section) {\r
-    imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
-    LeaveCriticalSection(&throttle_section);\r
+    imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
+    LeaveCriticalSection(&service->throttle_section);\r
   }\r
   else {\r
-    if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
+    if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
     else Sleep(ms);\r
   }\r
 }\r
@@ -671,43 +1707,43 @@ void throttle_restart() {
            0 if the wait completed.\r
           -1 on error.\r
 */\r
-int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long timeout) {\r
+int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
   unsigned long interval;\r
   unsigned long waithint;\r
   unsigned long ret;\r
   unsigned long waited;\r
-  char interval_milliseconds[16];\r
-  char timeout_milliseconds[16];\r
-  char waited_milliseconds[16];\r
-  char *function = function_name;\r
+  TCHAR interval_milliseconds[16];\r
+  TCHAR timeout_milliseconds[16];\r
+  TCHAR waited_milliseconds[16];\r
+  TCHAR *function = function_name;\r
 \r
   /* Add brackets to function name. */\r
-  size_t funclen = strlen(function_name) + 3;\r
-  char *func = (char *) HeapAlloc(GetProcessHeap(), 0, funclen);\r
+  size_t funclen = _tcslen(function_name) + 3;\r
+  TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
   if (func) {\r
-    if (_snprintf_s(func, funclen, _TRUNCATE, "%s()", function_name) > -1) function = func;\r
+    if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
   }\r
 \r
-  _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);\r
+  _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
 \r
-  waithint = service_status->dwWaitHint;\r
+  waithint = service->status.dwWaitHint;\r
   waited = 0;\r
   while (waited < timeout) {\r
     interval = timeout - waited;\r
     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
 \r
-    service_status->dwCurrentState = SERVICE_STOP_PENDING;\r
-    service_status->dwWaitHint += interval;\r
-    service_status->dwCheckPoint++;\r
-    SetServiceStatus(service_handle, service_status);\r
+    service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+    service->status.dwWaitHint += interval;\r
+    service->status.dwCheckPoint++;\r
+    SetServiceStatus(service->status_handle, &service->status);\r
 \r
     if (waited) {\r
-      _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);\r
-      _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);\r
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
+      _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
+      _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
     }\r
 \r
-    switch (WaitForSingleObject(process_handle, interval)) {\r
+    switch (WaitForSingleObject(service->process_handle, interval)) {\r
       case WAIT_OBJECT_0:\r
         ret = 0;\r
         goto awaited;\r