Browse Source

Use CRLF consistently.

The mismatch of line endings has long been an annoyance.

Thanks Mathias Breiner for advocating finally doing something about it.
Iain Patterson 9 years ago
parent
commit
b6f7fe3b11
15 changed files with 2905 additions and 2905 deletions
  1. 346 346
      account.cpp
  2. 24 24
      account.h
  3. 175 175
      console.cpp
  4. 7 7
      console.h
  5. 181 181
      env.cpp
  6. 14 14
      env.h
  7. 402 402
      hook.cpp
  8. 75 75
      hook.h
  9. 92 92
      imports.cpp
  10. 25 25
      imports.h
  11. BIN
      messages.mc
  12. 350 350
      process.cpp
  13. 36 36
      process.h
  14. 1130 1130
      settings.cpp
  15. 48 48
      settings.h

+ 346 - 346
account.cpp

@@ -1,346 +1,346 @@
-#include "nssm.h"
-
-#include <sddl.h>
-
-extern imports_t imports;
-
-/* Open Policy object. */
-int open_lsa_policy(LSA_HANDLE *policy) {
-  LSA_OBJECT_ATTRIBUTES attributes;
-  ZeroMemory(&attributes, sizeof(attributes));
-
-  NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy);
-  if (status) {
-    print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
-    return 1;
-  }
-
-  return 0;
-}
-
-/* Look up SID for an account. */
-int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
-  LSA_HANDLE handle;
-  if (! policy) {
-    policy = &handle;
-    if (open_lsa_policy(policy)) return 1;
-  }
-
-  /*
-    LsaLookupNames() can't look up .\username but can look up
-    %COMPUTERNAME%\username.  ChangeServiceConfig() writes .\username to the
-    registry when %COMPUTERNAME%\username is a passed as a parameter.  We
-    need to preserve .\username when calling ChangeServiceConfig() without
-    changing the username, but expand to %COMPUTERNAME%\username when calling
-    LsaLookupNames().
-  */
-  TCHAR *expanded;
-  unsigned long expandedlen;
-  if (_tcsnicmp(_T(".\\"), username, 2)) {
-    expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR);
-    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen);
-    if (! expanded) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
-      if (policy == &handle) LsaClose(handle);
-      return 2;
-    }
-    memmove(expanded, username, expandedlen);
-  }
-  else {
-    TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1];
-    expandedlen = _countof(computername);
-    GetComputerName(computername, &expandedlen);
-    expandedlen += (unsigned long) _tcslen(username);
-
-    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR));
-    if (! expanded) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
-      if (policy == &handle) LsaClose(handle);
-      return 2;
-    }
-    _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2);
-  }
-
-  LSA_UNICODE_STRING lsa_username;
-#ifdef UNICODE
-  lsa_username.Buffer = (wchar_t *) expanded;
-  lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR);
-  lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
-#else
-  size_t buflen;
-  mbstowcs_s(&buflen, NULL, 0, expanded, _TRUNCATE);
-  lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
-  lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
-  lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
-  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE);
-  else {
-    if (policy == &handle) LsaClose(handle);
-    HeapFree(GetProcessHeap(), 0, expanded);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
-    return 4;
-  }
-#endif
-
-  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
-  LSA_TRANSLATED_SID *translated_sid;
-  NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);
-#ifndef UNICODE
-  HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
-#endif
-  HeapFree(GetProcessHeap(), 0, expanded);
-  if (policy == &handle) LsaClose(handle);
-  if (status) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
-    return 5;
-  }
-
-  if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 6;
-  }
-
-  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
-  if (! trust || ! IsValidSid(trust->Sid)) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    return 7;
-  }
-
-  /* GetSidSubAuthority*() return pointers! */
-  unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
-
-  /* Convert translated SID to SID. */
-  *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
-  if (! *sid) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));
-    return 8;
-  }
-
-  unsigned long error;
-  if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
-    error = GetLastError();
-    HeapFree(GetProcessHeap(), 0, *sid);
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_sid);
-    print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
-    return 9;
-  }
-
-  for (unsigned char i = 0; i <= *n; i++) {
-    unsigned long *sub = GetSidSubAuthority(*sid, i);
-    if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
-    else *sub = translated_sid->RelativeId;
-  }
-
-  int ret = 0;
-  if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {
-    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
-    ret = 10;
-  }
-
-  LsaFreeMemory(translated_domains);
-  LsaFreeMemory(translated_sid);
-
-  return ret;
-}
-
-int username_sid(const TCHAR *username, SID **sid) {
-  return username_sid(username, sid, 0);
-}
-
-int canonicalise_username(const TCHAR *username, TCHAR **canon) {
-  LSA_HANDLE policy;
-  if (open_lsa_policy(&policy)) return 1;
-
-  SID *sid;
-  if (username_sid(username, &sid, &policy)) return 2;
-  PSID sids = { sid };
-
-  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
-  LSA_TRANSLATED_NAME *translated_name;
-  NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);
-  if (status) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_name);
-    print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));
-    return 3;
-  }
-
-  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
-  LSA_UNICODE_STRING lsa_canon;
-  lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);
-  lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);
-  lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);
-  if (! lsa_canon.Buffer) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_name);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));
-    return 9;
-  }
-
-  /* Buffer is wchar_t but Length is in bytes. */
-  memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);
-  memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));
-  memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);
-
-#ifdef UNICODE
-  *canon = lsa_canon.Buffer;
-#else
-  size_t buflen;
-  wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE);
-  *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
-  if (! *canon) {
-    LsaFreeMemory(translated_domains);
-    LsaFreeMemory(translated_name);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));
-    return 10;
-  }
-  wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE);
-  HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);
-#endif
-
-  LsaFreeMemory(translated_domains);
-  LsaFreeMemory(translated_name);
-
-  return 0;
-}
-
-/* Do two usernames map to the same SID? */
-int username_equiv(const TCHAR *a, const TCHAR *b) {
-  SID *sid_a, *sid_b;
-  if (username_sid(a, &sid_a)) return 0;
-
-  if (username_sid(b, &sid_b)) {
-    FreeSid(sid_a);
-    return 0;
-  }
-
-  int ret = 0;
-  if (EqualSid(sid_a, sid_b)) ret = 1;
-
-  FreeSid(sid_a);
-  FreeSid(sid_b);
-
-  return ret;
-}
-
-/* Does the username represent the LocalSystem account? */
-int is_localsystem(const TCHAR *username) {
-  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;
-  if (! imports.IsWellKnownSid) return 0;
-
-  SID *sid;
-  if (username_sid(username, &sid)) return 0;
-
-  int ret = 0;
-  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;
-
-  FreeSid(sid);
-
-  return ret;
-}
-
-/*
-  Get well-known alias for LocalSystem and friends.
-  Returns a pointer to a static string.  DO NOT try to free it.
-*/
-const TCHAR *well_known_sid(SID *sid) {
-  if (! imports.IsWellKnownSid) return 0;
-  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;
-  if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;
-  if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;
-  return 0;
-}
-
-const TCHAR *well_known_username(const TCHAR *username) {
-  if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;
-  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;
-  SID *sid;
-  if (username_sid(username, &sid)) return 0;
-
-  const TCHAR *well_known = well_known_sid(sid);
-  FreeSid(sid);
-
-  return well_known;
-}
-
-int grant_logon_as_service(const TCHAR *username) {
-  if (! username) return 0;
-
-  /* Open Policy object. */
-  LSA_OBJECT_ATTRIBUTES attributes;
-  ZeroMemory(&attributes, sizeof(attributes));
-
-  LSA_HANDLE policy;
-  NTSTATUS status;
-
-  if (open_lsa_policy(&policy)) return 1;
-
-  /* Look up SID for the account. */
-  SID *sid;
-  if (username_sid(username, &sid, &policy)) {
-    LsaClose(policy);
-    return 2;
-  }
-
-  /*
-    Shouldn't happen because it should have been checked before callling this function.
-  */
-  if (well_known_sid(sid)) {
-    LsaClose(policy);
-    return 3;
-  }
-
-  /* Check if the SID has the "Log on as a service" right. */
-  LSA_UNICODE_STRING lsa_right;
-  lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
-  lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
-  lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
-
-  LSA_UNICODE_STRING *rights;
-  unsigned long count = ~0;
-  status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
-  if (status) {
-    /*
-      If the account has no rights set LsaEnumerateAccountRights() will return
-      STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
-    */
-    unsigned long error = LsaNtStatusToWinError(status);
-    if (error != ERROR_FILE_NOT_FOUND) {
-      FreeSid(sid);
-      LsaClose(policy);
-      print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
-      return 4;
-    }
-  }
-
-  for (unsigned long i = 0; i < count; i++) {
-    if (rights[i].Length != lsa_right.Length) continue;
-    if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
-    /* The SID has the right. */
-    FreeSid(sid);
-    LsaFreeMemory(rights);
-    LsaClose(policy);
-    return 0;
-  }
-  LsaFreeMemory(rights);
-
-  /* Add the right. */
-  status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
-  FreeSid(sid);
-  LsaClose(policy);
-  if (status) {
-    print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
-    return 5;
-  }
-
-  print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
-  return 0;
-}
+#include "nssm.h"
+
+#include <sddl.h>
+
+extern imports_t imports;
+
+/* Open Policy object. */
+int open_lsa_policy(LSA_HANDLE *policy) {
+  LSA_OBJECT_ATTRIBUTES attributes;
+  ZeroMemory(&attributes, sizeof(attributes));
+
+  NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, policy);
+  if (status) {
+    print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 1;
+  }
+
+  return 0;
+}
+
+/* Look up SID for an account. */
+int username_sid(const TCHAR *username, SID **sid, LSA_HANDLE *policy) {
+  LSA_HANDLE handle;
+  if (! policy) {
+    policy = &handle;
+    if (open_lsa_policy(policy)) return 1;
+  }
+
+  /*
+    LsaLookupNames() can't look up .\username but can look up
+    %COMPUTERNAME%\username.  ChangeServiceConfig() writes .\username to the
+    registry when %COMPUTERNAME%\username is a passed as a parameter.  We
+    need to preserve .\username when calling ChangeServiceConfig() without
+    changing the username, but expand to %COMPUTERNAME%\username when calling
+    LsaLookupNames().
+  */
+  TCHAR *expanded;
+  unsigned long expandedlen;
+  if (_tcsnicmp(_T(".\\"), username, 2)) {
+    expandedlen = (unsigned long) (_tcslen(username) + 1) * sizeof(TCHAR);
+    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen);
+    if (! expanded) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
+      if (policy == &handle) LsaClose(handle);
+      return 2;
+    }
+    memmove(expanded, username, expandedlen);
+  }
+  else {
+    TCHAR computername[MAX_COMPUTERNAME_LENGTH + 1];
+    expandedlen = _countof(computername);
+    GetComputerName(computername, &expandedlen);
+    expandedlen += (unsigned long) _tcslen(username);
+
+    expanded = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, expandedlen * sizeof(TCHAR));
+    if (! expanded) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("expanded"), _T("username_sid"));
+      if (policy == &handle) LsaClose(handle);
+      return 2;
+    }
+    _sntprintf_s(expanded, expandedlen, _TRUNCATE, _T("%s\\%s"), computername, username + 2);
+  }
+
+  LSA_UNICODE_STRING lsa_username;
+#ifdef UNICODE
+  lsa_username.Buffer = (wchar_t *) expanded;
+  lsa_username.Length = (unsigned short) _tcslen(expanded) * sizeof(TCHAR);
+  lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
+#else
+  size_t buflen;
+  mbstowcs_s(&buflen, NULL, 0, expanded, _TRUNCATE);
+  lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);
+  lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
+  lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
+  if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, expanded, _TRUNCATE);
+  else {
+    if (policy == &handle) LsaClose(handle);
+    HeapFree(GetProcessHeap(), 0, expanded);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("username_sid()"));
+    return 4;
+  }
+#endif
+
+  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
+  LSA_TRANSLATED_SID *translated_sid;
+  NTSTATUS status = LsaLookupNames(*policy, 1, &lsa_username, &translated_domains, &translated_sid);
+#ifndef UNICODE
+  HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
+#endif
+  HeapFree(GetProcessHeap(), 0, expanded);
+  if (policy == &handle) LsaClose(handle);
+  if (status) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
+    return 5;
+  }
+
+  if (translated_sid->Use != SidTypeUser && translated_sid->Use != SidTypeWellKnownGroup) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    return 6;
+  }
+
+  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
+  if (! trust || ! IsValidSid(trust->Sid)) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    return 7;
+  }
+
+  /* GetSidSubAuthority*() return pointers! */
+  unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
+
+  /* Convert translated SID to SID. */
+  *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
+  if (! *sid) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("username_sid"));
+    return 8;
+  }
+
+  unsigned long error;
+  if (! InitializeSid(*sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
+    error = GetLastError();
+    HeapFree(GetProcessHeap(), 0, *sid);
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_sid);
+    print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
+    return 9;
+  }
+
+  for (unsigned char i = 0; i <= *n; i++) {
+    unsigned long *sub = GetSidSubAuthority(*sid, i);
+    if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
+    else *sub = translated_sid->RelativeId;
+  }
+
+  int ret = 0;
+  if (translated_sid->Use == SidTypeWellKnownGroup && ! well_known_sid(*sid)) {
+    print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
+    ret = 10;
+  }
+
+  LsaFreeMemory(translated_domains);
+  LsaFreeMemory(translated_sid);
+
+  return ret;
+}
+
+int username_sid(const TCHAR *username, SID **sid) {
+  return username_sid(username, sid, 0);
+}
+
+int canonicalise_username(const TCHAR *username, TCHAR **canon) {
+  LSA_HANDLE policy;
+  if (open_lsa_policy(&policy)) return 1;
+
+  SID *sid;
+  if (username_sid(username, &sid, &policy)) return 2;
+  PSID sids = { sid };
+
+  LSA_REFERENCED_DOMAIN_LIST *translated_domains;
+  LSA_TRANSLATED_NAME *translated_name;
+  NTSTATUS status = LsaLookupSids(policy, 1, &sids, &translated_domains, &translated_name);
+  if (status) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_LSALOOKUPSIDS_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 3;
+  }
+
+  LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_name->DomainIndex];
+  LSA_UNICODE_STRING lsa_canon;
+  lsa_canon.Length = translated_name->Name.Length + trust->Name.Length + sizeof(wchar_t);
+  lsa_canon.MaximumLength = lsa_canon.Length + sizeof(wchar_t);
+  lsa_canon.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, lsa_canon.MaximumLength);
+  if (! lsa_canon.Buffer) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lsa_canon"), _T("username_sid"));
+    return 9;
+  }
+
+  /* Buffer is wchar_t but Length is in bytes. */
+  memmove((char *) lsa_canon.Buffer, trust->Name.Buffer, trust->Name.Length);
+  memmove((char *) lsa_canon.Buffer + trust->Name.Length, L"\\", sizeof(wchar_t));
+  memmove((char *) lsa_canon.Buffer + trust->Name.Length + sizeof(wchar_t), translated_name->Name.Buffer, translated_name->Name.Length);
+
+#ifdef UNICODE
+  *canon = lsa_canon.Buffer;
+#else
+  size_t buflen;
+  wcstombs_s(&buflen, NULL, 0, lsa_canon.Buffer, _TRUNCATE);
+  *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
+  if (! *canon) {
+    LsaFreeMemory(translated_domains);
+    LsaFreeMemory(translated_name);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("username_sid"));
+    return 10;
+  }
+  wcstombs_s(&buflen, *canon, buflen, lsa_canon.Buffer, _TRUNCATE);
+  HeapFree(GetProcessHeap(), 0, lsa_canon.Buffer);
+#endif
+
+  LsaFreeMemory(translated_domains);
+  LsaFreeMemory(translated_name);
+
+  return 0;
+}
+
+/* Do two usernames map to the same SID? */
+int username_equiv(const TCHAR *a, const TCHAR *b) {
+  SID *sid_a, *sid_b;
+  if (username_sid(a, &sid_a)) return 0;
+
+  if (username_sid(b, &sid_b)) {
+    FreeSid(sid_a);
+    return 0;
+  }
+
+  int ret = 0;
+  if (EqualSid(sid_a, sid_b)) ret = 1;
+
+  FreeSid(sid_a);
+  FreeSid(sid_b);
+
+  return ret;
+}
+
+/* Does the username represent the LocalSystem account? */
+int is_localsystem(const TCHAR *username) {
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 1;
+  if (! imports.IsWellKnownSid) return 0;
+
+  SID *sid;
+  if (username_sid(username, &sid)) return 0;
+
+  int ret = 0;
+  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) ret = 1;
+
+  FreeSid(sid);
+
+  return ret;
+}
+
+/*
+  Get well-known alias for LocalSystem and friends.
+  Returns a pointer to a static string.  DO NOT try to free it.
+*/
+const TCHAR *well_known_sid(SID *sid) {
+  if (! imports.IsWellKnownSid) return 0;
+  if (imports.IsWellKnownSid(sid, WinLocalSystemSid)) return NSSM_LOCALSYSTEM_ACCOUNT;
+  if (imports.IsWellKnownSid(sid, WinLocalServiceSid)) return NSSM_LOCALSERVICE_ACCOUNT;
+  if (imports.IsWellKnownSid(sid, WinNetworkServiceSid)) return NSSM_NETWORKSERVICE_ACCOUNT;
+  return 0;
+}
+
+const TCHAR *well_known_username(const TCHAR *username) {
+  if (! username) return NSSM_LOCALSYSTEM_ACCOUNT;
+  if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return NSSM_LOCALSYSTEM_ACCOUNT;
+  SID *sid;
+  if (username_sid(username, &sid)) return 0;
+
+  const TCHAR *well_known = well_known_sid(sid);
+  FreeSid(sid);
+
+  return well_known;
+}
+
+int grant_logon_as_service(const TCHAR *username) {
+  if (! username) return 0;
+
+  /* Open Policy object. */
+  LSA_OBJECT_ATTRIBUTES attributes;
+  ZeroMemory(&attributes, sizeof(attributes));
+
+  LSA_HANDLE policy;
+  NTSTATUS status;
+
+  if (open_lsa_policy(&policy)) return 1;
+
+  /* Look up SID for the account. */
+  SID *sid;
+  if (username_sid(username, &sid, &policy)) {
+    LsaClose(policy);
+    return 2;
+  }
+
+  /*
+    Shouldn't happen because it should have been checked before callling this function.
+  */
+  if (well_known_sid(sid)) {
+    LsaClose(policy);
+    return 3;
+  }
+
+  /* Check if the SID has the "Log on as a service" right. */
+  LSA_UNICODE_STRING lsa_right;
+  lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
+  lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
+  lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
+
+  LSA_UNICODE_STRING *rights;
+  unsigned long count = ~0;
+  status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
+  if (status) {
+    /*
+      If the account has no rights set LsaEnumerateAccountRights() will return
+      STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
+    */
+    unsigned long error = LsaNtStatusToWinError(status);
+    if (error != ERROR_FILE_NOT_FOUND) {
+      FreeSid(sid);
+      LsaClose(policy);
+      print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
+      return 4;
+    }
+  }
+
+  for (unsigned long i = 0; i < count; i++) {
+    if (rights[i].Length != lsa_right.Length) continue;
+    if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
+    /* The SID has the right. */
+    FreeSid(sid);
+    LsaFreeMemory(rights);
+    LsaClose(policy);
+    return 0;
+  }
+  LsaFreeMemory(rights);
+
+  /* Add the right. */
+  status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
+  FreeSid(sid);
+  LsaClose(policy);
+  if (status) {
+    print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
+    return 5;
+  }
+
+  print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
+  return 0;
+}

+ 24 - 24
account.h

@@ -1,24 +1,24 @@
-#ifndef ACCOUNT_H
-#define ACCOUNT_H
-
-#include <ntsecapi.h>
-
-/* Not really an account.  The canonical name is NT Authority\System. */
-#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
-/* Other well-known accounts which can start a service without a password. */
-#define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService")
-#define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService")
-/* This is explicitly a wide string. */
-#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
-
-int open_lsa_policy(LSA_HANDLE *);
-int username_sid(const TCHAR *, SID **, LSA_HANDLE *);
-int username_sid(const TCHAR *, SID **);
-int username_equiv(const TCHAR *, const TCHAR *);
-int canonicalise_username(const TCHAR *, TCHAR **);
-int is_localsystem(const TCHAR *);
-const TCHAR *well_known_sid(SID *);
-const TCHAR *well_known_username(const TCHAR *);
-int grant_logon_as_service(const TCHAR *);
-
-#endif
+#ifndef ACCOUNT_H
+#define ACCOUNT_H
+
+#include <ntsecapi.h>
+
+/* Not really an account.  The canonical name is NT Authority\System. */
+#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
+/* Other well-known accounts which can start a service without a password. */
+#define NSSM_LOCALSERVICE_ACCOUNT _T("NT Authority\\LocalService")
+#define NSSM_NETWORKSERVICE_ACCOUNT _T("NT Authority\\NetworkService")
+/* This is explicitly a wide string. */
+#define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
+
+int open_lsa_policy(LSA_HANDLE *);
+int username_sid(const TCHAR *, SID **, LSA_HANDLE *);
+int username_sid(const TCHAR *, SID **);
+int username_equiv(const TCHAR *, const TCHAR *);
+int canonicalise_username(const TCHAR *, TCHAR **);
+int is_localsystem(const TCHAR *);
+const TCHAR *well_known_sid(SID *);
+const TCHAR *well_known_username(const TCHAR *);
+int grant_logon_as_service(const TCHAR *);
+
+#endif

+ 175 - 175
console.cpp

@@ -1,175 +1,175 @@
-#include "nssm.h"
-
-/* See if we were launched from a console window. */
-void check_console() {
-  /* If we're running in a service context there will be no console window. */
-  HWND console = GetConsoleWindow();
-  if (! console) return;
-
-  unsigned long pid;
-  if (! GetWindowThreadProcessId(console, &pid)) return;
-
-  /*
-    If the process associated with the console window handle is the same as
-    this process, we were not launched from an existing console.  The user
-    probably double-clicked our executable.
-  */
-  if (GetCurrentProcessId() != pid) return;
-
-  /* We close our new console so that subsequent messages appear in a popup. */
-  FreeConsole();
-}
-
-/* Helpers for drawing the banner. */
-static inline void block(unsigned int a, short x, short y, unsigned long n) {
-  HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
-  TCHAR s = _T(' ');
-
-  unsigned long out;
-  COORD c = { x, y };
-  FillConsoleOutputAttribute(h, a, n, c, &out);
-  FillConsoleOutputCharacter(h, s, n, c, &out);
-}
-
-static inline void R(short x, short y, unsigned long n) {
-  block(BACKGROUND_RED | BACKGROUND_INTENSITY, x, y, n);
-}
-
-static inline void r(short x, short y, unsigned long n) {
-  block(BACKGROUND_RED, x, y, n);
-}
-
-static inline void b(short x, short y, unsigned long n) {
-  block(0, x, y, n);
-}
-
-void alloc_console(nssm_service_t *service) {
-  if (service->no_console) return;
-
-  AllocConsole();
-
-  /* Disable accidental closure. */
-  HWND window = GetConsoleWindow();
-  HMENU menu = GetSystemMenu(window, false);
-  EnableMenuItem(menu, SC_CLOSE, MF_GRAYED);
-
-  /* Set a title like "[NSSM] Jenkins" */
-  TCHAR displayname[SERVICE_NAME_LENGTH];
-  unsigned long len = _countof(displayname);
-  SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);
-  if (services) {
-    if (! GetServiceDisplayName(services, service->name, displayname, &len)) ZeroMemory(displayname, sizeof(displayname));
-    CloseServiceHandle(services);
-  }
-  if (! displayname[0]) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name);
-
-  TCHAR title[65535];
-  _sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s"), NSSM, displayname);
-  SetConsoleTitle(title);
-
-  /* Draw the NSSM logo on the console window. */
-  short y = 0;
-
-  b(0, y, 80);
-  y++;
-
-  b(0, y, 80);
-  y++;
-
-  b(0, y, 80);
-  y++;
-
-  b(0, y, 80);
-  y++;
-
-  b(0, y, 80);
-  r(18, y, 5); r(28, y, 4); r(41, y, 4); r(68, y, 1);
-  R(6, y, 5); R(19, y, 4); R(29, y, 1); R(32, y, 3); R(42, y, 1); R(45, y, 3); R(52, y, 5); R(69, y, 4);
-  y++;
-
-  b(0, y, 80);
-  r(8, y, 4); r(20, y, 1); r(28, y, 1); r(33, y, 3); r(41, y, 1); r(46, y, 3); r (57, y, 1);
-  R(9, y, 2); R(21, y, 1); R(27, y, 1); R(34, y, 1); R(40, y, 1); R(47, y, 1); R(54, y, 3); R(68, y, 3);
-  y++;
-
-  b(0, y, 80);
-  r(12, y, 1); r(20, y, 1); r(26, y, 1); r(34, y, 2); r(39, y, 1); r(47, y, 2); r(67, y, 2);
-  R(9, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(20, y, 1); r(26, y, 1); r (35, y, 1); r(39, y, 1); r(48, y, 1); r(58, y, 1);
-  R(10, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(56, y, 1); r(66, y, 2);
-  R(11, y, 3); R(21, y, 1); R(26, y, 2); R(39, y, 2); R(54, y, 1); R(57, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(26, y, 1); r(39, y, 1); r(59, y, 1);
-  R(12, y, 3); R(21, y, 1); R(27, y, 2); R(40, y, 2); R(54, y, 1); R(57, y, 2); R(66, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(12, y, 4); r(30, y, 1); r(43, y, 1); r(57, y, 1); r(65, y, 2);
-  R(13, y, 2); R(21, y, 1); R(27, y, 3); R(40, y, 3); R(54, y, 1); R(58, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(13, y, 4); r(27, y, 7); r(40, y, 7);
-  R(14, y, 2); R(21, y, 1); R(28, y, 5); R(41, y, 5); R(54, y, 1); R(58, y, 2); R(65, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(60, y, 1); r(65, y, 1);
-  R(14, y, 3); R(21, y, 1); R(29, y, 6); R(42, y, 6); R(54, y, 1); R(58, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(31, y, 1); r(44, y, 1); r(58, y, 1); r(64, y, 1);
-  R(15, y, 3); R(21, y, 1); R(32, y, 4); R(45, y, 4); R(54, y, 1); R(59, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(33, y, 1); r(46, y, 1); r(61, y, 1); r(64, y, 1);
-  R(16, y, 3); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(59, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(16, y, 4); r(36, y, 1); r(49, y, 1); r(59, y, 1); r(63, y, 1);
-  R(17, y, 2); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(60, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(9, y, 1); r(17, y, 4); r(26, y, 1); r(36, y, 1); r(39, y, 1); r(49, y, 1);
-  R(18, y, 2); R(21, y, 1); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 2); R(63, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(26, y, 2); r(39, y, 2); r(63, y, 1);
-  R(9, y, 1); R(18, y, 4); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 3); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(34, y, 1); r(47, y, 1); r(60, y, 1);
-  R(9, y, 1); R(19, y, 3); R(26, y, 2); R(35, y, 1); R(39, y, 2); R(48, y, 1); R(54, y, 1); R(61, y, 2); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(8, y, 1); r(35, y, 1); r(48, y, 1); r(62, y, 1); r(71, y, 1);
-  R(9, y, 1); R(20, y, 2); R(26, y, 3); R(34, y, 1); R(39, y, 3); R(47, y, 1); R(54, y, 1); R(61, y, 1); R(69, y, 2);
-  y++;
-
-  b(0, y, 80);
-  r(11, y, 1); r(26, y, 1); r(28, y, 5); r(39, y, 1); r(41, y, 5); r(51, y, 7); r(61, y, 1); r(66, y, 8);
-  R(7, y, 4); R(21, y, 1); R(29, y, 1); R(33, y, 1); R(42, y, 1); R(46, y, 1); R(52, y, 5); R(67, y, 7);
-  y++;
-
-  b(0, y, 80);
-  y++;
-
-  b(0, y, 80);
-  y++;
-}
+#include "nssm.h"
+
+/* See if we were launched from a console window. */
+void check_console() {
+  /* If we're running in a service context there will be no console window. */
+  HWND console = GetConsoleWindow();
+  if (! console) return;
+
+  unsigned long pid;
+  if (! GetWindowThreadProcessId(console, &pid)) return;
+
+  /*
+    If the process associated with the console window handle is the same as
+    this process, we were not launched from an existing console.  The user
+    probably double-clicked our executable.
+  */
+  if (GetCurrentProcessId() != pid) return;
+
+  /* We close our new console so that subsequent messages appear in a popup. */
+  FreeConsole();
+}
+
+/* Helpers for drawing the banner. */
+static inline void block(unsigned int a, short x, short y, unsigned long n) {
+  HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
+  TCHAR s = _T(' ');
+
+  unsigned long out;
+  COORD c = { x, y };
+  FillConsoleOutputAttribute(h, a, n, c, &out);
+  FillConsoleOutputCharacter(h, s, n, c, &out);
+}
+
+static inline void R(short x, short y, unsigned long n) {
+  block(BACKGROUND_RED | BACKGROUND_INTENSITY, x, y, n);
+}
+
+static inline void r(short x, short y, unsigned long n) {
+  block(BACKGROUND_RED, x, y, n);
+}
+
+static inline void b(short x, short y, unsigned long n) {
+  block(0, x, y, n);
+}
+
+void alloc_console(nssm_service_t *service) {
+  if (service->no_console) return;
+
+  AllocConsole();
+
+  /* Disable accidental closure. */
+  HWND window = GetConsoleWindow();
+  HMENU menu = GetSystemMenu(window, false);
+  EnableMenuItem(menu, SC_CLOSE, MF_GRAYED);
+
+  /* Set a title like "[NSSM] Jenkins" */
+  TCHAR displayname[SERVICE_NAME_LENGTH];
+  unsigned long len = _countof(displayname);
+  SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);
+  if (services) {
+    if (! GetServiceDisplayName(services, service->name, displayname, &len)) ZeroMemory(displayname, sizeof(displayname));
+    CloseServiceHandle(services);
+  }
+  if (! displayname[0]) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name);
+
+  TCHAR title[65535];
+  _sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s"), NSSM, displayname);
+  SetConsoleTitle(title);
+
+  /* Draw the NSSM logo on the console window. */
+  short y = 0;
+
+  b(0, y, 80);
+  y++;
+
+  b(0, y, 80);
+  y++;
+
+  b(0, y, 80);
+  y++;
+
+  b(0, y, 80);
+  y++;
+
+  b(0, y, 80);
+  r(18, y, 5); r(28, y, 4); r(41, y, 4); r(68, y, 1);
+  R(6, y, 5); R(19, y, 4); R(29, y, 1); R(32, y, 3); R(42, y, 1); R(45, y, 3); R(52, y, 5); R(69, y, 4);
+  y++;
+
+  b(0, y, 80);
+  r(8, y, 4); r(20, y, 1); r(28, y, 1); r(33, y, 3); r(41, y, 1); r(46, y, 3); r (57, y, 1);
+  R(9, y, 2); R(21, y, 1); R(27, y, 1); R(34, y, 1); R(40, y, 1); R(47, y, 1); R(54, y, 3); R(68, y, 3);
+  y++;
+
+  b(0, y, 80);
+  r(12, y, 1); r(20, y, 1); r(26, y, 1); r(34, y, 2); r(39, y, 1); r(47, y, 2); r(67, y, 2);
+  R(9, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(20, y, 1); r(26, y, 1); r (35, y, 1); r(39, y, 1); r(48, y, 1); r(58, y, 1);
+  R(10, y, 3); R(21, y, 1); R(27, y, 1); R(40, y, 1); R(54, y, 1); R(56, y, 2); R(67, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(56, y, 1); r(66, y, 2);
+  R(11, y, 3); R(21, y, 1); R(26, y, 2); R(39, y, 2); R(54, y, 1); R(57, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(26, y, 1); r(39, y, 1); r(59, y, 1);
+  R(12, y, 3); R(21, y, 1); R(27, y, 2); R(40, y, 2); R(54, y, 1); R(57, y, 2); R(66, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(12, y, 4); r(30, y, 1); r(43, y, 1); r(57, y, 1); r(65, y, 2);
+  R(13, y, 2); R(21, y, 1); R(27, y, 3); R(40, y, 3); R(54, y, 1); R(58, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(13, y, 4); r(27, y, 7); r(40, y, 7);
+  R(14, y, 2); R(21, y, 1); R(28, y, 5); R(41, y, 5); R(54, y, 1); R(58, y, 2); R(65, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(60, y, 1); r(65, y, 1);
+  R(14, y, 3); R(21, y, 1); R(29, y, 6); R(42, y, 6); R(54, y, 1); R(58, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(31, y, 1); r(44, y, 1); r(58, y, 1); r(64, y, 1);
+  R(15, y, 3); R(21, y, 1); R(32, y, 4); R(45, y, 4); R(54, y, 1); R(59, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(33, y, 1); r(46, y, 1); r(61, y, 1); r(64, y, 1);
+  R(16, y, 3); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(59, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(16, y, 4); r(36, y, 1); r(49, y, 1); r(59, y, 1); r(63, y, 1);
+  R(17, y, 2); R(21, y, 1); R(34, y, 2); R(47, y, 2); R(54, y, 1); R(60, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(9, y, 1); r(17, y, 4); r(26, y, 1); r(36, y, 1); r(39, y, 1); r(49, y, 1);
+  R(18, y, 2); R(21, y, 1); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 2); R(63, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(26, y, 2); r(39, y, 2); r(63, y, 1);
+  R(9, y, 1); R(18, y, 4); R(35, y, 1); R(48, y, 1); R(54, y, 1); R(60, y, 3); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(34, y, 1); r(47, y, 1); r(60, y, 1);
+  R(9, y, 1); R(19, y, 3); R(26, y, 2); R(35, y, 1); R(39, y, 2); R(48, y, 1); R(54, y, 1); R(61, y, 2); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(8, y, 1); r(35, y, 1); r(48, y, 1); r(62, y, 1); r(71, y, 1);
+  R(9, y, 1); R(20, y, 2); R(26, y, 3); R(34, y, 1); R(39, y, 3); R(47, y, 1); R(54, y, 1); R(61, y, 1); R(69, y, 2);
+  y++;
+
+  b(0, y, 80);
+  r(11, y, 1); r(26, y, 1); r(28, y, 5); r(39, y, 1); r(41, y, 5); r(51, y, 7); r(61, y, 1); r(66, y, 8);
+  R(7, y, 4); R(21, y, 1); R(29, y, 1); R(33, y, 1); R(42, y, 1); R(46, y, 1); R(52, y, 5); R(67, y, 7);
+  y++;
+
+  b(0, y, 80);
+  y++;
+
+  b(0, y, 80);
+  y++;
+}

+ 7 - 7
console.h

@@ -1,7 +1,7 @@
-#ifndef CONSOLE_H
-#define CONSOLE_H
-
-void check_console();
-void alloc_console(nssm_service_t *);
-
-#endif
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+void check_console();
+void alloc_console(nssm_service_t *);
+
+#endif

+ 181 - 181
env.cpp

@@ -1,181 +1,181 @@
-#include "nssm.h"
-
-/* Copy an environment block. */
-TCHAR *copy_environment_block(TCHAR *env) {
-  unsigned long len;
-
-  if (! env) return 0;
-  for (len = 0; env[len]; len++) while (env[len]) len++;
-  if (! len++) return 0;
-
-  TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
-  if (! newenv) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("copy_environment_block()"), 0);
-    return 0;
-  }
-
-  memmove(newenv, env, len * sizeof(TCHAR));
-  return newenv;
-}
-
-/*
-  The environment block starts with variables of the form
-  =C:=C:\Windows\System32 which we ignore.
-*/
-TCHAR *useful_environment(TCHAR *rawenv) {
-  TCHAR *env = rawenv;
-
-  if (env) {
-    while (*env == _T('=')) {
-      for ( ; *env; env++);
-      env++;
-    }
-  }
-
-  return env;
-}
-
-/* Expand an environment variable.  Must call HeapFree() on the result. */
-TCHAR *expand_environment_string(TCHAR *string) {
-  unsigned long len;
-
-  len = ExpandEnvironmentStrings(string, 0, 0);
-  if (! len) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
-    return 0;
-  }
-
-  TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
-  if (! ret) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0);
-    return 0;
-  }
-
-  if (! ExpandEnvironmentStrings(string, ret, len)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
-    HeapFree(GetProcessHeap(), 0, ret);
-    return 0;
-  }
-
-  return ret;
-}
-
-/*
-  Set all the environment variables from an environment block in the current
-  environment or remove all the variables in the block from the current
-  environment.
-*/
-static int set_environment_block(TCHAR *env, bool set) {
-  int ret = 0;
-
-  TCHAR *s, *t;
-  for (s = env; *s; s++) {
-    for (t = s; *t && *t != _T('='); t++);
-    if (*t == _T('=')) {
-      *t = _T('\0');
-      if (set) {
-        TCHAR *expanded = expand_environment_string(++t);
-        if (expanded) {
-          if (! SetEnvironmentVariable(s, expanded)) ret++;
-          HeapFree(GetProcessHeap(), 0, expanded);
-        }
-        else {
-          if (! SetEnvironmentVariable(s, t)) ret++;
-        }
-      }
-      else {
-        if (! SetEnvironmentVariable(s, NULL)) ret++;
-      }
-      for (t++; *t; t++);
-    }
-    s = t;
-  }
-
-  return ret;
-}
-
-int set_environment_block(TCHAR *env) {
-  return set_environment_block(env, true);
-}
-
-static int unset_environment_block(TCHAR *env) {
-  return set_environment_block(env, false);
-}
-
-/* Remove all variables from the process environment. */
-int clear_environment() {
-  TCHAR *rawenv = GetEnvironmentStrings();
-  TCHAR *env = useful_environment(rawenv);
-
-  int ret = unset_environment_block(env);
-
-  if (rawenv) FreeEnvironmentStrings(rawenv);
-
-  return ret;
-}
-
-/* Set the current environment to exactly duplicate an environment block. */
-int duplicate_environment(TCHAR *rawenv) {
-  int ret = clear_environment();
-  TCHAR *env = useful_environment(rawenv);
-  ret += set_environment_block(env);
-  return ret;
-}
-
-/*
-  Verify an environment block.
-  Returns:  1 if environment is invalid.
-            0 if environment is OK.
-           -1 on error.
-*/
-int test_environment(TCHAR *env) {
-  TCHAR *path = (TCHAR *) nssm_imagepath();
-  STARTUPINFO si;
-  ZeroMemory(&si, sizeof(si));
-  si.cb = sizeof(si);
-  PROCESS_INFORMATION pi;
-  ZeroMemory(&pi, sizeof(pi));
-  unsigned long flags = CREATE_SUSPENDED;
-#ifdef UNICODE
-  flags |= CREATE_UNICODE_ENVIRONMENT;
-#endif
-
-  /*
-    Try to relaunch ourselves but with the candidate environment set.
-    Assuming no solar flare activity, the only reason this would fail is if
-    the environment were invalid.
-  */
-  if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) {
-    TerminateProcess(pi.hProcess, 0);
-  }
-  else {
-    unsigned long error = GetLastError();
-    if (error == ERROR_INVALID_PARAMETER) return 1;
-    else return -1;
-  }
-
-  return 0;
-}
-
-/*
-  Duplicate an environment block returned by GetEnvironmentStrings().
-  Since such a block is by definition readonly, and duplicate_environment()
-  modifies its inputs, this function takes a copy of the input and operates
-  on that.
-*/
-void duplicate_environment_strings(TCHAR *env) {
-  TCHAR *newenv = copy_environment_block(env);
-  if (! newenv) return;
-
-  duplicate_environment(newenv);
-  HeapFree(GetProcessHeap(), 0, newenv);
-}
-
-/* Safely get a copy of the current environment. */
-TCHAR *copy_environment() {
-  TCHAR *rawenv = GetEnvironmentStrings();
-  if (! rawenv) return NULL;
-  TCHAR *env = copy_environment_block(rawenv);
-  FreeEnvironmentStrings(rawenv);
-  return env;
-}
+#include "nssm.h"
+
+/* Copy an environment block. */
+TCHAR *copy_environment_block(TCHAR *env) {
+  unsigned long len;
+
+  if (! env) return 0;
+  for (len = 0; env[len]; len++) while (env[len]) len++;
+  if (! len++) return 0;
+
+  TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
+  if (! newenv) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("copy_environment_block()"), 0);
+    return 0;
+  }
+
+  memmove(newenv, env, len * sizeof(TCHAR));
+  return newenv;
+}
+
+/*
+  The environment block starts with variables of the form
+  =C:=C:\Windows\System32 which we ignore.
+*/
+TCHAR *useful_environment(TCHAR *rawenv) {
+  TCHAR *env = rawenv;
+
+  if (env) {
+    while (*env == _T('=')) {
+      for ( ; *env; env++);
+      env++;
+    }
+  }
+
+  return env;
+}
+
+/* Expand an environment variable.  Must call HeapFree() on the result. */
+TCHAR *expand_environment_string(TCHAR *string) {
+  unsigned long len;
+
+  len = ExpandEnvironmentStrings(string, 0, 0);
+  if (! len) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
+    return 0;
+  }
+
+  TCHAR *ret = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
+  if (! ret) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("ExpandEnvironmentStrings()"), _T("expand_environment_string"), 0);
+    return 0;
+  }
+
+  if (! ExpandEnvironmentStrings(string, ret, len)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_EXPANDENVIRONMENTSTRINGS_FAILED, string, error_string(GetLastError()), 0);
+    HeapFree(GetProcessHeap(), 0, ret);
+    return 0;
+  }
+
+  return ret;
+}
+
+/*
+  Set all the environment variables from an environment block in the current
+  environment or remove all the variables in the block from the current
+  environment.
+*/
+static int set_environment_block(TCHAR *env, bool set) {
+  int ret = 0;
+
+  TCHAR *s, *t;
+  for (s = env; *s; s++) {
+    for (t = s; *t && *t != _T('='); t++);
+    if (*t == _T('=')) {
+      *t = _T('\0');
+      if (set) {
+        TCHAR *expanded = expand_environment_string(++t);
+        if (expanded) {
+          if (! SetEnvironmentVariable(s, expanded)) ret++;
+          HeapFree(GetProcessHeap(), 0, expanded);
+        }
+        else {
+          if (! SetEnvironmentVariable(s, t)) ret++;
+        }
+      }
+      else {
+        if (! SetEnvironmentVariable(s, NULL)) ret++;
+      }
+      for (t++; *t; t++);
+    }
+    s = t;
+  }
+
+  return ret;
+}
+
+int set_environment_block(TCHAR *env) {
+  return set_environment_block(env, true);
+}
+
+static int unset_environment_block(TCHAR *env) {
+  return set_environment_block(env, false);
+}
+
+/* Remove all variables from the process environment. */
+int clear_environment() {
+  TCHAR *rawenv = GetEnvironmentStrings();
+  TCHAR *env = useful_environment(rawenv);
+
+  int ret = unset_environment_block(env);
+
+  if (rawenv) FreeEnvironmentStrings(rawenv);
+
+  return ret;
+}
+
+/* Set the current environment to exactly duplicate an environment block. */
+int duplicate_environment(TCHAR *rawenv) {
+  int ret = clear_environment();
+  TCHAR *env = useful_environment(rawenv);
+  ret += set_environment_block(env);
+  return ret;
+}
+
+/*
+  Verify an environment block.
+  Returns:  1 if environment is invalid.
+            0 if environment is OK.
+           -1 on error.
+*/
+int test_environment(TCHAR *env) {
+  TCHAR *path = (TCHAR *) nssm_imagepath();
+  STARTUPINFO si;
+  ZeroMemory(&si, sizeof(si));
+  si.cb = sizeof(si);
+  PROCESS_INFORMATION pi;
+  ZeroMemory(&pi, sizeof(pi));
+  unsigned long flags = CREATE_SUSPENDED;
+#ifdef UNICODE
+  flags |= CREATE_UNICODE_ENVIRONMENT;
+#endif
+
+  /*
+    Try to relaunch ourselves but with the candidate environment set.
+    Assuming no solar flare activity, the only reason this would fail is if
+    the environment were invalid.
+  */
+  if (CreateProcess(0, path, 0, 0, 0, flags, env, 0, &si, &pi)) {
+    TerminateProcess(pi.hProcess, 0);
+  }
+  else {
+    unsigned long error = GetLastError();
+    if (error == ERROR_INVALID_PARAMETER) return 1;
+    else return -1;
+  }
+
+  return 0;
+}
+
+/*
+  Duplicate an environment block returned by GetEnvironmentStrings().
+  Since such a block is by definition readonly, and duplicate_environment()
+  modifies its inputs, this function takes a copy of the input and operates
+  on that.
+*/
+void duplicate_environment_strings(TCHAR *env) {
+  TCHAR *newenv = copy_environment_block(env);
+  if (! newenv) return;
+
+  duplicate_environment(newenv);
+  HeapFree(GetProcessHeap(), 0, newenv);
+}
+
+/* Safely get a copy of the current environment. */
+TCHAR *copy_environment() {
+  TCHAR *rawenv = GetEnvironmentStrings();
+  if (! rawenv) return NULL;
+  TCHAR *env = copy_environment_block(rawenv);
+  FreeEnvironmentStrings(rawenv);
+  return env;
+}

+ 14 - 14
env.h

@@ -1,14 +1,14 @@
-#ifndef ENV_H
-#define ENV_H
-
-TCHAR *copy_environment_block(TCHAR *);
-TCHAR *useful_environment(TCHAR *);
-TCHAR *expand_environment_string(TCHAR *);
-int set_environment_block(TCHAR *);
-int clear_environment();
-int duplicate_environment(TCHAR *);
-int test_environment(TCHAR *);
-void duplicate_environment_strings(TCHAR *);
-TCHAR *copy_environment();
-
-#endif
+#ifndef ENV_H
+#define ENV_H
+
+TCHAR *copy_environment_block(TCHAR *);
+TCHAR *useful_environment(TCHAR *);
+TCHAR *expand_environment_string(TCHAR *);
+int set_environment_block(TCHAR *);
+int clear_environment();
+int duplicate_environment(TCHAR *);
+int test_environment(TCHAR *);
+void duplicate_environment_strings(TCHAR *);
+TCHAR *copy_environment();
+
+#endif

+ 402 - 402
hook.cpp

@@ -1,402 +1,402 @@
-#include "nssm.h"
-
-typedef struct {
-  TCHAR *name;
-  HANDLE process_handle;
-  unsigned long pid;
-  unsigned long deadline;
-  FILETIME creation_time;
-  kill_t k;
-} hook_t;
-
-static unsigned long WINAPI await_hook(void *arg) {
-  hook_t *hook = (hook_t *) arg;
-  if (! hook) return NSSM_HOOK_STATUS_ERROR;
-
-  int ret = 0;
-  if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT;
-
-  /* Tidy up hook process tree. */
-  if (hook->name) hook->k.name = hook->name;
-  else hook->k.name = _T("hook");
-  hook->k.process_handle = hook->process_handle;
-  hook->k.pid = hook->pid;
-  hook->k.stop_method = ~0;
-  hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
-  hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
-  hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
-  hook->k.creation_time = hook->creation_time;
-  GetSystemTimeAsFileTime(&hook->k.exit_time);
-  kill_process_tree(&hook->k, hook->pid);
-
-  if (ret) {
-    CloseHandle(hook->process_handle);
-    if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
-    HeapFree(GetProcessHeap(), 0, hook);
-    return ret;
-  }
-
-  unsigned long exitcode;
-  GetExitCodeProcess(hook->process_handle, &exitcode);
-  CloseHandle(hook->process_handle);
-
-  if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
-  HeapFree(GetProcessHeap(), 0, hook);
-
-  if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT;
-  if (exitcode) return NSSM_HOOK_STATUS_FAILED;
-
-  return NSSM_HOOK_STATUS_SUCCESS;
-}
-
-static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) {
-  if (start && now) {
-    ULARGE_INTEGER s;
-    s.LowPart = start->dwLowDateTime;
-    s.HighPart = start->dwHighDateTime;
-    if (s.QuadPart) {
-      ULARGE_INTEGER t;
-      t.LowPart = now->dwLowDateTime;
-      t.HighPart = now->dwHighDateTime;
-      if (t.QuadPart && t.QuadPart >= s.QuadPart) {
-        t.QuadPart -= s.QuadPart;
-        t.QuadPart /= 10000LL;
-        TCHAR number[16];
-        _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart);
-        SetEnvironmentVariable(v, number);
-        return;
-      }
-    }
-  }
-  SetEnvironmentVariable(v, _T(""));
-}
-
-static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) {
-  if (! hook_threads) return;
-
-  int num_threads = hook_threads->num_threads + 1;
-  hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
-  if (! data) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0);
-    return;
-  }
-
-  int i;
-  for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i]));
-  memmove(data[i].name, name, sizeof(data[i].name));
-  data[i].thread_handle = thread_handle;
-
-  if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data);
-  hook_threads->data = data;
-  hook_threads->num_threads = num_threads;
-}
-
-bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) {
-  bool valid_event = false;
-  bool valid_action = false;
-
-  /* Exit/Post */
-  if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) {
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
-    if (quiet) return false;
-    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
-    return false;
-  }
-
-  /* Power/{Change,Resume} */
-  if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) {
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true;
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true;
-    if (quiet) return false;
-    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME);
-    return false;
-  }
-
-  /* Rotate/{Pre,Post} */
-  if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) {
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
-    if (quiet) return false;
-    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
-    return false;
-  }
-
-  /* Start/{Pre,Post} */
-  if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) {
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
-    if (quiet) return false;
-    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
-    return false;
-  }
-
-  /* Stop/Pre */
-  if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) {
-    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
-    if (quiet) return false;
-    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
-    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
-    return false;
-  }
-
-  if (quiet) return false;
-  print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT);
-  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT);
-  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER);
-  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE);
-  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START);
-  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP);
-  return false;
-}
-
-void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) {
-  if (! hook_threads) return;
-  if (! hook_threads->num_threads) return;
-
-  int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int));
-  if (! retain) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0);
-    return;
-  }
-
-  /*
-    We could use WaitForMultipleObjects() but await_single_object() can update
-    the service status as well.
-  */
-  int num_threads = 0;
-  int i;
-  for (i = 0; i < hook_threads->num_threads; i++) {
-    if (deadline) {
-      if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) {
-        CloseHandle(hook_threads->data[i].thread_handle);
-        continue;
-      }
-    }
-    else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) {
-      CloseHandle(hook_threads->data[i].thread_handle);
-      continue;
-    }
-
-    retain[num_threads++]= i;
-  }
-
-  if (num_threads) {
-    hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
-    if (! data) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0);
-      HeapFree(GetProcessHeap(), 0, retain);
-      return;
-    }
-
-    for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i]));
-
-    HeapFree(GetProcessHeap(), 0, hook_threads->data);
-    hook_threads->data = data;
-    hook_threads->num_threads = num_threads;
-  }
-  else {
-    HeapFree(GetProcessHeap(), 0, hook_threads->data);
-    ZeroMemory(hook_threads, sizeof(*hook_threads));
-  }
-
-  HeapFree(GetProcessHeap(), 0, retain);
-}
-
-/*
-   Returns:
-   NSSM_HOOK_STATUS_SUCCESS  if the hook ran successfully.
-   NSSM_HOOK_STATUS_NOTFOUND if no hook was found.
-   NSSM_HOOK_STATUS_ABORT    if the hook failed and we should cancel service start.
-   NSSM_HOOK_STATUS_ERROR    on error.
-   NSSM_HOOK_STATUS_NOTRUN   if the hook didn't run.
-   NSSM_HOOK_STATUS_TIMEOUT  if the hook timed out.
-   NSSM_HOOK_STATUS_FAILED   if the hook failed.
-*/
-int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) {
-  int ret = 0;
-
-  hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t));
-  if (! hook) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0);
-    return NSSM_HOOK_STATUS_ERROR;
-  }
-
-  FILETIME now;
-  GetSystemTimeAsFileTime(&now);
-
-  EnterCriticalSection(&service->hook_section);
-
-  /* Set the environment. */
-  set_service_environment(service);
-
-  /* ABI version. */
-  TCHAR number[16];
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number);
-
-  /* Event triggering this action. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event);
-
-  /* Hook action. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action);
-
-  /* Control triggering this action.  May be empty. */
-  if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control));
-  else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T(""));
-
-  /* Last control handled. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control));
-
-  /* Path to NSSM, unquoted for the environment. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath());
-
-  /* NSSM version. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE);
-
-  /* NSSM PID. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId());
-  SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number);
-
-  /* NSSM runtime. */
-  set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now);
-
-  /* Application PID. */
-  if (service->pid) {
-    _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid);
-    SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number);
-    /* Application runtime. */
-    set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now);
-    /* Exit code. */
-    SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
-  }
-  else {
-    SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T(""));
-    if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) {
-      SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T(""));
-      SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
-    }
-    else {
-      set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time);
-      /* Exit code. */
-      _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode);
-      SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number);
-    }
-  }
-
-  /* Deadline for this script. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number);
-
-  /* Service name. */
-  SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname);
-
-  /* Times the service was asked to start. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number);
-
-  /* Times the service actually did start. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number);
-
-  /* Times the service exited. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number);
-
-  /* Throttled count. */
-  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number);
-
-  /* Command line. */
-  TCHAR app[CMD_LENGTH];
-  _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags);
-  SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app);
-
-  TCHAR cmd[CMD_LENGTH];
-  if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0);
-    unset_service_environment(service);
-    LeaveCriticalSection(&service->hook_section);
-    HeapFree(GetProcessHeap(), 0, hook);
-    return NSSM_HOOK_STATUS_ERROR;
-  }
-
-  /* No hook. */
-  if (! _tcslen(cmd)) {
-    unset_service_environment(service);
-    LeaveCriticalSection(&service->hook_section);
-    HeapFree(GetProcessHeap(), 0, hook);
-    return NSSM_HOOK_STATUS_NOTFOUND;
-  }
-
-  /* Run the command. */
-  STARTUPINFO si;
-  ZeroMemory(&si, sizeof(si));
-  si.cb = sizeof(si);
-  PROCESS_INFORMATION pi;
-  ZeroMemory(&pi, sizeof(pi));
-  unsigned long flags = 0;
-#ifdef UNICODE
-  flags |= CREATE_UNICODE_ENVIRONMENT;
-#endif
-  ret = NSSM_HOOK_STATUS_NOTRUN;
-  if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) {
-    hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR));
-    if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action);
-    hook->process_handle = pi.hProcess;
-    hook->pid = pi.dwProcessId;
-    hook->deadline = deadline;
-    if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time);
-
-    unsigned long tid;
-    HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid);
-    if (thread_handle) {
-      if (async) {
-        ret = 0;
-        await_hook_threads(hook_threads, service->status_handle, &service->status, 0);
-        add_thread_handle(hook_threads, thread_handle, hook->name);
-      }
-      else {
-        await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE);
-        unsigned long exitcode;
-        GetExitCodeThread(thread_handle, &exitcode);
-        ret = (int) exitcode;
-        CloseHandle(thread_handle);
-      }
-    }
-    else {
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
-      await_hook(hook);
-      if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
-      HeapFree(GetProcessHeap(), 0, hook);
-    }
-  }
-  else {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0);
-    HeapFree(GetProcessHeap(), 0, hook);
-  }
-
-  /* Restore our environment. */
-  unset_service_environment(service);
-
-  LeaveCriticalSection(&service->hook_section);
-
-  return ret;
-}
-
-int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) {
-  return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true);
-}
-
-int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) {
-  return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE);
-}
+#include "nssm.h"
+
+typedef struct {
+  TCHAR *name;
+  HANDLE process_handle;
+  unsigned long pid;
+  unsigned long deadline;
+  FILETIME creation_time;
+  kill_t k;
+} hook_t;
+
+static unsigned long WINAPI await_hook(void *arg) {
+  hook_t *hook = (hook_t *) arg;
+  if (! hook) return NSSM_HOOK_STATUS_ERROR;
+
+  int ret = 0;
+  if (WaitForSingleObject(hook->process_handle, hook->deadline) == WAIT_TIMEOUT) ret = NSSM_HOOK_STATUS_TIMEOUT;
+
+  /* Tidy up hook process tree. */
+  if (hook->name) hook->k.name = hook->name;
+  else hook->k.name = _T("hook");
+  hook->k.process_handle = hook->process_handle;
+  hook->k.pid = hook->pid;
+  hook->k.stop_method = ~0;
+  hook->k.kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
+  hook->k.kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
+  hook->k.kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
+  hook->k.creation_time = hook->creation_time;
+  GetSystemTimeAsFileTime(&hook->k.exit_time);
+  kill_process_tree(&hook->k, hook->pid);
+
+  if (ret) {
+    CloseHandle(hook->process_handle);
+    if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
+    HeapFree(GetProcessHeap(), 0, hook);
+    return ret;
+  }
+
+  unsigned long exitcode;
+  GetExitCodeProcess(hook->process_handle, &exitcode);
+  CloseHandle(hook->process_handle);
+
+  if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
+  HeapFree(GetProcessHeap(), 0, hook);
+
+  if (exitcode == NSSM_HOOK_STATUS_ABORT) return NSSM_HOOK_STATUS_ABORT;
+  if (exitcode) return NSSM_HOOK_STATUS_FAILED;
+
+  return NSSM_HOOK_STATUS_SUCCESS;
+}
+
+static void set_hook_runtime(TCHAR *v, FILETIME *start, FILETIME *now) {
+  if (start && now) {
+    ULARGE_INTEGER s;
+    s.LowPart = start->dwLowDateTime;
+    s.HighPart = start->dwHighDateTime;
+    if (s.QuadPart) {
+      ULARGE_INTEGER t;
+      t.LowPart = now->dwLowDateTime;
+      t.HighPart = now->dwHighDateTime;
+      if (t.QuadPart && t.QuadPart >= s.QuadPart) {
+        t.QuadPart -= s.QuadPart;
+        t.QuadPart /= 10000LL;
+        TCHAR number[16];
+        _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%llu"), t.QuadPart);
+        SetEnvironmentVariable(v, number);
+        return;
+      }
+    }
+  }
+  SetEnvironmentVariable(v, _T(""));
+}
+
+static void add_thread_handle(hook_thread_t *hook_threads, HANDLE thread_handle, TCHAR *name) {
+  if (! hook_threads) return;
+
+  int num_threads = hook_threads->num_threads + 1;
+  hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
+  if (! data) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook_thread_t"), _T("add_thread_handle()"), 0);
+    return;
+  }
+
+  int i;
+  for (i = 0; i < hook_threads->num_threads; i++) memmove(&data[i], &hook_threads->data[i], sizeof(data[i]));
+  memmove(data[i].name, name, sizeof(data[i].name));
+  data[i].thread_handle = thread_handle;
+
+  if (hook_threads->data) HeapFree(GetProcessHeap(), 0, hook_threads->data);
+  hook_threads->data = data;
+  hook_threads->num_threads = num_threads;
+}
+
+bool valid_hook_name(const TCHAR *hook_event, const TCHAR *hook_action, bool quiet) {
+  bool valid_event = false;
+  bool valid_action = false;
+
+  /* Exit/Post */
+  if (str_equiv(hook_event, NSSM_HOOK_EVENT_EXIT)) {
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
+    if (quiet) return false;
+    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
+    return false;
+  }
+
+  /* Power/{Change,Resume} */
+  if (str_equiv(hook_event, NSSM_HOOK_EVENT_POWER)) {
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_CHANGE)) return true;
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_RESUME)) return true;
+    if (quiet) return false;
+    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_CHANGE);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_RESUME);
+    return false;
+  }
+
+  /* Rotate/{Pre,Post} */
+  if (str_equiv(hook_event, NSSM_HOOK_EVENT_ROTATE)) {
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
+    if (quiet) return false;
+    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
+    return false;
+  }
+
+  /* Start/{Pre,Post} */
+  if (str_equiv(hook_event, NSSM_HOOK_EVENT_START)) {
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_POST)) return true;
+    if (quiet) return false;
+    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_POST);
+    return false;
+  }
+
+  /* Stop/Pre */
+  if (str_equiv(hook_event, NSSM_HOOK_EVENT_STOP)) {
+    if (str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) return true;
+    if (quiet) return false;
+    print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_ACTION, hook_event);
+    _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_ACTION_PRE);
+    return false;
+  }
+
+  if (quiet) return false;
+  print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_EVENT);
+  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_EXIT);
+  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_POWER);
+  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_ROTATE);
+  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_START);
+  _ftprintf(stderr, _T("%s\n"), NSSM_HOOK_EVENT_STOP);
+  return false;
+}
+
+void await_hook_threads(hook_thread_t *hook_threads, SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, unsigned long deadline) {
+  if (! hook_threads) return;
+  if (! hook_threads->num_threads) return;
+
+  int *retain = (int *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, hook_threads->num_threads * sizeof(int));
+  if (! retain) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("retain"), _T("await_hook_threads()"), 0);
+    return;
+  }
+
+  /*
+    We could use WaitForMultipleObjects() but await_single_object() can update
+    the service status as well.
+  */
+  int num_threads = 0;
+  int i;
+  for (i = 0; i < hook_threads->num_threads; i++) {
+    if (deadline) {
+      if (await_single_handle(status_handle, status, hook_threads->data[i].thread_handle, hook_threads->data[i].name, _T(__FUNCTION__), deadline) != 1) {
+        CloseHandle(hook_threads->data[i].thread_handle);
+        continue;
+      }
+    }
+    else if (WaitForSingleObject(hook_threads->data[i].thread_handle, 0) != WAIT_TIMEOUT) {
+      CloseHandle(hook_threads->data[i].thread_handle);
+      continue;
+    }
+
+    retain[num_threads++]= i;
+  }
+
+  if (num_threads) {
+    hook_thread_data_t *data = (hook_thread_data_t *) HeapAlloc(GetProcessHeap(), 0, num_threads * sizeof(hook_thread_data_t));
+    if (! data) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("data"), _T("await_hook_threads()"), 0);
+      HeapFree(GetProcessHeap(), 0, retain);
+      return;
+    }
+
+    for (i = 0; i < num_threads; i++) memmove(&data[i], &hook_threads->data[retain[i]], sizeof(data[i]));
+
+    HeapFree(GetProcessHeap(), 0, hook_threads->data);
+    hook_threads->data = data;
+    hook_threads->num_threads = num_threads;
+  }
+  else {
+    HeapFree(GetProcessHeap(), 0, hook_threads->data);
+    ZeroMemory(hook_threads, sizeof(*hook_threads));
+  }
+
+  HeapFree(GetProcessHeap(), 0, retain);
+}
+
+/*
+   Returns:
+   NSSM_HOOK_STATUS_SUCCESS  if the hook ran successfully.
+   NSSM_HOOK_STATUS_NOTFOUND if no hook was found.
+   NSSM_HOOK_STATUS_ABORT    if the hook failed and we should cancel service start.
+   NSSM_HOOK_STATUS_ERROR    on error.
+   NSSM_HOOK_STATUS_NOTRUN   if the hook didn't run.
+   NSSM_HOOK_STATUS_TIMEOUT  if the hook timed out.
+   NSSM_HOOK_STATUS_FAILED   if the hook failed.
+*/
+int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline, bool async) {
+  int ret = 0;
+
+  hook_t *hook = (hook_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(hook_t));
+  if (! hook) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("hook"), _T("nssm_hook()"), 0);
+    return NSSM_HOOK_STATUS_ERROR;
+  }
+
+  FILETIME now;
+  GetSystemTimeAsFileTime(&now);
+
+  EnterCriticalSection(&service->hook_section);
+
+  /* Set the environment. */
+  set_service_environment(service);
+
+  /* ABI version. */
+  TCHAR number[16];
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), NSSM_HOOK_VERSION);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_VERSION, number);
+
+  /* Event triggering this action. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_EVENT, hook_event);
+
+  /* Hook action. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_ACTION, hook_action);
+
+  /* Control triggering this action.  May be empty. */
+  if (hook_control) SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, service_control_text(*hook_control));
+  else SetEnvironmentVariable(NSSM_HOOK_ENV_TRIGGER, _T(""));
+
+  /* Last control handled. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_LAST_CONTROL, service_control_text(service->last_control));
+
+  /* Path to NSSM, unquoted for the environment. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_IMAGE_PATH, nssm_unquoted_imagepath());
+
+  /* NSSM version. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_CONFIGURATION, NSSM_CONFIGURATION);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_NSSM_VERSION, NSSM_VERSION);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_BUILD_DATE, NSSM_DATE);
+
+  /* NSSM PID. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), GetCurrentProcessId());
+  SetEnvironmentVariable(NSSM_HOOK_ENV_PID, number);
+
+  /* NSSM runtime. */
+  set_hook_runtime(NSSM_HOOK_ENV_RUNTIME, &service->nssm_creation_time, &now);
+
+  /* Application PID. */
+  if (service->pid) {
+    _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->pid);
+    SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, number);
+    /* Application runtime. */
+    set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &now);
+    /* Exit code. */
+    SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
+  }
+  else {
+    SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_PID, _T(""));
+    if (str_equiv(hook_event, NSSM_HOOK_EVENT_START) && str_equiv(hook_action, NSSM_HOOK_ACTION_PRE)) {
+      SetEnvironmentVariable(NSSM_HOOK_ENV_APPLICATION_RUNTIME, _T(""));
+      SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, _T(""));
+    }
+    else {
+      set_hook_runtime(NSSM_HOOK_ENV_APPLICATION_RUNTIME, &service->creation_time, &service->exit_time);
+      /* Exit code. */
+      _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exitcode);
+      SetEnvironmentVariable(NSSM_HOOK_ENV_EXITCODE, number);
+    }
+  }
+
+  /* Deadline for this script. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), deadline);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_DEADLINE, number);
+
+  /* Service name. */
+  SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_NAME, service->name);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_SERVICE_DISPLAYNAME, service->displayname);
+
+  /* Times the service was asked to start. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_requested_count);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_START_REQUESTED_COUNT, number);
+
+  /* Times the service actually did start. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->start_count);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_START_COUNT, number);
+
+  /* Times the service exited. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->exit_count);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_EXIT_COUNT, number);
+
+  /* Throttled count. */
+  _sntprintf_s(number, _countof(number), _TRUNCATE, _T("%lu"), service->throttle);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_THROTTLE_COUNT, number);
+
+  /* Command line. */
+  TCHAR app[CMD_LENGTH];
+  _sntprintf_s(app, _countof(app), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags);
+  SetEnvironmentVariable(NSSM_HOOK_ENV_COMMAND_LINE, app);
+
+  TCHAR cmd[CMD_LENGTH];
+  if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_HOOK_FAILED, hook_event, hook_action, service->name, 0);
+    unset_service_environment(service);
+    LeaveCriticalSection(&service->hook_section);
+    HeapFree(GetProcessHeap(), 0, hook);
+    return NSSM_HOOK_STATUS_ERROR;
+  }
+
+  /* No hook. */
+  if (! _tcslen(cmd)) {
+    unset_service_environment(service);
+    LeaveCriticalSection(&service->hook_section);
+    HeapFree(GetProcessHeap(), 0, hook);
+    return NSSM_HOOK_STATUS_NOTFOUND;
+  }
+
+  /* Run the command. */
+  STARTUPINFO si;
+  ZeroMemory(&si, sizeof(si));
+  si.cb = sizeof(si);
+  PROCESS_INFORMATION pi;
+  ZeroMemory(&pi, sizeof(pi));
+  unsigned long flags = 0;
+#ifdef UNICODE
+  flags |= CREATE_UNICODE_ENVIRONMENT;
+#endif
+  ret = NSSM_HOOK_STATUS_NOTRUN;
+  if (CreateProcess(0, cmd, 0, 0, false, flags, 0, service->dir, &si, &pi)) {
+    hook->name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, HOOK_NAME_LENGTH * sizeof(TCHAR));
+    if (hook->name) _sntprintf_s(hook->name, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s (%s/%s)"), service->name, hook_event, hook_action);
+    hook->process_handle = pi.hProcess;
+    hook->pid = pi.dwProcessId;
+    hook->deadline = deadline;
+    if (get_process_creation_time(hook->process_handle, &hook->creation_time)) GetSystemTimeAsFileTime(&hook->creation_time);
+
+    unsigned long tid;
+    HANDLE thread_handle = CreateThread(NULL, 0, await_hook, (void *) hook, 0, &tid);
+    if (thread_handle) {
+      if (async) {
+        ret = 0;
+        await_hook_threads(hook_threads, service->status_handle, &service->status, 0);
+        add_thread_handle(hook_threads, thread_handle, hook->name);
+      }
+      else {
+        await_single_handle(service->status_handle, &service->status, thread_handle, hook->name, _T(__FUNCTION__), deadline + NSSM_SERVICE_STATUS_DEADLINE);
+        unsigned long exitcode;
+        GetExitCodeThread(thread_handle, &exitcode);
+        ret = (int) exitcode;
+        CloseHandle(thread_handle);
+      }
+    }
+    else {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
+      await_hook(hook);
+      if (hook->name) HeapFree(GetProcessHeap(), 0, hook->name);
+      HeapFree(GetProcessHeap(), 0, hook);
+    }
+  }
+  else {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_HOOK_CREATEPROCESS_FAILED, hook_event, hook_action, service->name, cmd, error_string(GetLastError()), 0);
+    HeapFree(GetProcessHeap(), 0, hook);
+  }
+
+  /* Restore our environment. */
+  unset_service_environment(service);
+
+  LeaveCriticalSection(&service->hook_section);
+
+  return ret;
+}
+
+int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control, unsigned long deadline) {
+  return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, deadline, true);
+}
+
+int nssm_hook(hook_thread_t *hook_threads, nssm_service_t *service, TCHAR *hook_event, TCHAR *hook_action, unsigned long *hook_control) {
+  return nssm_hook(hook_threads, service, hook_event, hook_action, hook_control, NSSM_HOOK_DEADLINE);
+}

+ 75 - 75
hook.h

@@ -1,75 +1,75 @@
-#ifndef HOOK_H
-#define HOOK_H
-
-#define NSSM_HOOK_EVENT_START _T("Start")
-#define NSSM_HOOK_EVENT_STOP _T("Stop")
-#define NSSM_HOOK_EVENT_EXIT _T("Exit")
-#define NSSM_HOOK_EVENT_POWER _T("Power")
-#define NSSM_HOOK_EVENT_ROTATE _T("Rotate")
-
-#define NSSM_HOOK_ACTION_PRE _T("Pre")
-#define NSSM_HOOK_ACTION_POST _T("Post")
-#define NSSM_HOOK_ACTION_CHANGE _T("Change")
-#define NSSM_HOOK_ACTION_RESUME _T("Resume")
-
-/* Hook name will be "<service> (<event>/<action>)" */
-#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2
-
-#define NSSM_HOOK_VERSION 1
-
-/* Hook ran successfully. */
-#define NSSM_HOOK_STATUS_SUCCESS 0
-/* No hook configured. */
-#define NSSM_HOOK_STATUS_NOTFOUND 1
-/* Hook requested abort. */
-#define NSSM_HOOK_STATUS_ABORT 99
-/* Internal error launching hook. */
-#define NSSM_HOOK_STATUS_ERROR 100
-/* Hook was not run. */
-#define NSSM_HOOK_STATUS_NOTRUN 101
-/* Hook timed out. */
-#define NSSM_HOOK_STATUS_TIMEOUT 102
-/* Hook returned non-zero. */
-#define NSSM_HOOK_STATUS_FAILED 111
-
-/* Version 1. */
-#define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION")
-#define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE")
-#define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION")
-#define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION")
-#define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE")
-#define NSSM_HOOK_ENV_PID _T("NSSM_PID")
-#define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE")
-#define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME")
-#define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME")
-#define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE")
-#define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID")
-#define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT")
-#define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION")
-#define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER")
-#define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL")
-#define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT")
-#define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT")
-#define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT")
-#define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT")
-#define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE")
-#define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME")
-#define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME")
-
-typedef struct {
-  TCHAR name[HOOK_NAME_LENGTH];
-  HANDLE thread_handle;
-} hook_thread_data_t;
-
-typedef struct {
-  hook_thread_data_t *data;
-  int num_threads;
-} hook_thread_t;
-
-bool valid_hook_name(const TCHAR *, const TCHAR *, bool);
-void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long);
-int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool);
-int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long);
-int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *);
-
-#endif
+#ifndef HOOK_H
+#define HOOK_H
+
+#define NSSM_HOOK_EVENT_START _T("Start")
+#define NSSM_HOOK_EVENT_STOP _T("Stop")
+#define NSSM_HOOK_EVENT_EXIT _T("Exit")
+#define NSSM_HOOK_EVENT_POWER _T("Power")
+#define NSSM_HOOK_EVENT_ROTATE _T("Rotate")
+
+#define NSSM_HOOK_ACTION_PRE _T("Pre")
+#define NSSM_HOOK_ACTION_POST _T("Post")
+#define NSSM_HOOK_ACTION_CHANGE _T("Change")
+#define NSSM_HOOK_ACTION_RESUME _T("Resume")
+
+/* Hook name will be "<service> (<event>/<action>)" */
+#define HOOK_NAME_LENGTH SERVICE_NAME_LENGTH * 2
+
+#define NSSM_HOOK_VERSION 1
+
+/* Hook ran successfully. */
+#define NSSM_HOOK_STATUS_SUCCESS 0
+/* No hook configured. */
+#define NSSM_HOOK_STATUS_NOTFOUND 1
+/* Hook requested abort. */
+#define NSSM_HOOK_STATUS_ABORT 99
+/* Internal error launching hook. */
+#define NSSM_HOOK_STATUS_ERROR 100
+/* Hook was not run. */
+#define NSSM_HOOK_STATUS_NOTRUN 101
+/* Hook timed out. */
+#define NSSM_HOOK_STATUS_TIMEOUT 102
+/* Hook returned non-zero. */
+#define NSSM_HOOK_STATUS_FAILED 111
+
+/* Version 1. */
+#define NSSM_HOOK_ENV_VERSION _T("NSSM_HOOK_VERSION")
+#define NSSM_HOOK_ENV_IMAGE_PATH _T("NSSM_EXE")
+#define NSSM_HOOK_ENV_NSSM_CONFIGURATION _T("NSSM_CONFIGURATION")
+#define NSSM_HOOK_ENV_NSSM_VERSION _T("NSSM_VERSION")
+#define NSSM_HOOK_ENV_BUILD_DATE _T("NSSM_BUILD_DATE")
+#define NSSM_HOOK_ENV_PID _T("NSSM_PID")
+#define NSSM_HOOK_ENV_DEADLINE _T("NSSM_DEADLINE")
+#define NSSM_HOOK_ENV_SERVICE_NAME _T("NSSM_SERVICE_NAME")
+#define NSSM_HOOK_ENV_SERVICE_DISPLAYNAME _T("NSSM_SERVICE_DISPLAYNAME")
+#define NSSM_HOOK_ENV_COMMAND_LINE _T("NSSM_COMMAND_LINE")
+#define NSSM_HOOK_ENV_APPLICATION_PID _T("NSSM_APPLICATION_PID")
+#define NSSM_HOOK_ENV_EVENT _T("NSSM_EVENT")
+#define NSSM_HOOK_ENV_ACTION _T("NSSM_ACTION")
+#define NSSM_HOOK_ENV_TRIGGER _T("NSSM_TRIGGER")
+#define NSSM_HOOK_ENV_LAST_CONTROL _T("NSSM_LAST_CONTROL")
+#define NSSM_HOOK_ENV_START_REQUESTED_COUNT _T("NSSM_START_REQUESTED_COUNT")
+#define NSSM_HOOK_ENV_START_COUNT _T("NSSM_START_COUNT")
+#define NSSM_HOOK_ENV_THROTTLE_COUNT _T("NSSM_THROTTLE_COUNT")
+#define NSSM_HOOK_ENV_EXIT_COUNT _T("NSSM_EXIT_COUNT")
+#define NSSM_HOOK_ENV_EXITCODE _T("NSSM_EXITCODE")
+#define NSSM_HOOK_ENV_RUNTIME _T("NSSM_RUNTIME")
+#define NSSM_HOOK_ENV_APPLICATION_RUNTIME _T("NSSM_APPLICATION_RUNTIME")
+
+typedef struct {
+  TCHAR name[HOOK_NAME_LENGTH];
+  HANDLE thread_handle;
+} hook_thread_data_t;
+
+typedef struct {
+  hook_thread_data_t *data;
+  int num_threads;
+} hook_thread_t;
+
+bool valid_hook_name(const TCHAR *, const TCHAR *, bool);
+void await_hook_threads(hook_thread_t *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long, bool);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *, unsigned long);
+int nssm_hook(hook_thread_t *, nssm_service_t *, TCHAR *, TCHAR *, unsigned long *);
+
+#endif

+ 92 - 92
imports.cpp

@@ -1,92 +1,92 @@
-#include "nssm.h"
-
-imports_t imports;
-
-/*
-  Try to set up function pointers.
-  In this first implementation it is not an error if we can't load them
-  because we aren't currently trying to load any functions which we
-  absolutely need.  If we later add some indispensible imports we can
-  return non-zero here to force an application exit.
-*/
-HMODULE get_dll(const TCHAR *dll, unsigned long *error) {
-  *error = 0;
-
-  HMODULE ret = LoadLibrary(dll);
-  if (! ret) {
-    *error = GetLastError();
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0);
-  }
-
-  return ret;
-}
-
-FARPROC get_import(HMODULE library, const char *function, unsigned long *error) {
-  *error = 0;
-
-  FARPROC ret = GetProcAddress(library, function);
-  if (! ret) {
-    *error = GetLastError();
-    TCHAR *function_name;
-#ifdef UNICODE
-    size_t buflen;
-    mbstowcs_s(&buflen, NULL, 0, function, _TRUNCATE);
-    function_name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen * sizeof(TCHAR));
-    if (function_name) mbstowcs_s(&buflen, function_name, buflen * sizeof(TCHAR), function, _TRUNCATE);
-#else
-    function_name = (TCHAR *) function;
-#endif
-    if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0);
-#ifdef UNICODE
-    if (function_name) HeapFree(GetProcessHeap(), 0, function_name);
-#endif
-  }
-
-  return ret;
-}
-
-int get_imports() {
-  unsigned long error;
-
-  ZeroMemory(&imports, sizeof(imports));
-
-  imports.kernel32 = get_dll(_T("kernel32.dll"), &error);
-  if (imports.kernel32) {
-    imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error);
-    if (! imports.AttachConsole) {
-      if (error != ERROR_PROC_NOT_FOUND) return 2;
-    }
-
-    imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error);
-    if (! imports.SleepConditionVariableCS) {
-      if (error != ERROR_PROC_NOT_FOUND) return 3;
-    }
-
-    imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error);
-    if (! imports.WakeConditionVariable) {
-      if (error != ERROR_PROC_NOT_FOUND) return 4;
-    }
-  }
-  else if (error != ERROR_MOD_NOT_FOUND) return 1;
-
-  imports.advapi32 = get_dll(_T("advapi32.dll"), &error);
-  if (imports.advapi32) {
-    imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error);
-    if (! imports.CreateWellKnownSid) {
-      if (error != ERROR_PROC_NOT_FOUND) return 6;
-    }
-    imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error);
-    if (! imports.IsWellKnownSid) {
-      if (error != ERROR_PROC_NOT_FOUND) return 7;
-    }
-  }
-  else if (error != ERROR_MOD_NOT_FOUND) return 5;
-
-  return 0;
-}
-
-void free_imports() {
-  if (imports.kernel32) FreeLibrary(imports.kernel32);
-  if (imports.advapi32) FreeLibrary(imports.advapi32);
-  ZeroMemory(&imports, sizeof(imports));
-}
+#include "nssm.h"
+
+imports_t imports;
+
+/*
+  Try to set up function pointers.
+  In this first implementation it is not an error if we can't load them
+  because we aren't currently trying to load any functions which we
+  absolutely need.  If we later add some indispensible imports we can
+  return non-zero here to force an application exit.
+*/
+HMODULE get_dll(const TCHAR *dll, unsigned long *error) {
+  *error = 0;
+
+  HMODULE ret = LoadLibrary(dll);
+  if (! ret) {
+    *error = GetLastError();
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_LOADLIBRARY_FAILED, dll, error_string(*error), 0);
+  }
+
+  return ret;
+}
+
+FARPROC get_import(HMODULE library, const char *function, unsigned long *error) {
+  *error = 0;
+
+  FARPROC ret = GetProcAddress(library, function);
+  if (! ret) {
+    *error = GetLastError();
+    TCHAR *function_name;
+#ifdef UNICODE
+    size_t buflen;
+    mbstowcs_s(&buflen, NULL, 0, function, _TRUNCATE);
+    function_name = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen * sizeof(TCHAR));
+    if (function_name) mbstowcs_s(&buflen, function_name, buflen * sizeof(TCHAR), function, _TRUNCATE);
+#else
+    function_name = (TCHAR *) function;
+#endif
+    if (*error != ERROR_PROC_NOT_FOUND) log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_GETPROCADDRESS_FAILED, function_name, error_string(*error), 0);
+#ifdef UNICODE
+    if (function_name) HeapFree(GetProcessHeap(), 0, function_name);
+#endif
+  }
+
+  return ret;
+}
+
+int get_imports() {
+  unsigned long error;
+
+  ZeroMemory(&imports, sizeof(imports));
+
+  imports.kernel32 = get_dll(_T("kernel32.dll"), &error);
+  if (imports.kernel32) {
+    imports.AttachConsole = (AttachConsole_ptr) get_import(imports.kernel32, "AttachConsole", &error);
+    if (! imports.AttachConsole) {
+      if (error != ERROR_PROC_NOT_FOUND) return 2;
+    }
+
+    imports.SleepConditionVariableCS = (SleepConditionVariableCS_ptr) get_import(imports.kernel32, "SleepConditionVariableCS", &error);
+    if (! imports.SleepConditionVariableCS) {
+      if (error != ERROR_PROC_NOT_FOUND) return 3;
+    }
+
+    imports.WakeConditionVariable = (WakeConditionVariable_ptr) get_import(imports.kernel32, "WakeConditionVariable", &error);
+    if (! imports.WakeConditionVariable) {
+      if (error != ERROR_PROC_NOT_FOUND) return 4;
+    }
+  }
+  else if (error != ERROR_MOD_NOT_FOUND) return 1;
+
+  imports.advapi32 = get_dll(_T("advapi32.dll"), &error);
+  if (imports.advapi32) {
+    imports.CreateWellKnownSid = (CreateWellKnownSid_ptr) get_import(imports.advapi32, "CreateWellKnownSid", &error);
+    if (! imports.CreateWellKnownSid) {
+      if (error != ERROR_PROC_NOT_FOUND) return 6;
+    }
+    imports.IsWellKnownSid = (IsWellKnownSid_ptr) get_import(imports.advapi32, "IsWellKnownSid", &error);
+    if (! imports.IsWellKnownSid) {
+      if (error != ERROR_PROC_NOT_FOUND) return 7;
+    }
+  }
+  else if (error != ERROR_MOD_NOT_FOUND) return 5;
+
+  return 0;
+}
+
+void free_imports() {
+  if (imports.kernel32) FreeLibrary(imports.kernel32);
+  if (imports.advapi32) FreeLibrary(imports.advapi32);
+  ZeroMemory(&imports, sizeof(imports));
+}

+ 25 - 25
imports.h

@@ -1,25 +1,25 @@
-#ifndef IMPORTS_H
-#define IMPORTS_H
-
-typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD);
-typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD);
-typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE);
-typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *);
-typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE);
-
-typedef struct {
-  HMODULE kernel32;
-  HMODULE advapi32;
-  AttachConsole_ptr AttachConsole;
-  SleepConditionVariableCS_ptr SleepConditionVariableCS;
-  WakeConditionVariable_ptr WakeConditionVariable;
-  CreateWellKnownSid_ptr CreateWellKnownSid;
-  IsWellKnownSid_ptr IsWellKnownSid;
-} imports_t;
-
-HMODULE get_dll(const TCHAR *, unsigned long *);
-FARPROC get_import(HMODULE, const char *, unsigned long *);
-int get_imports();
-void free_imports();
-
-#endif
+#ifndef IMPORTS_H
+#define IMPORTS_H
+
+typedef BOOL (WINAPI *AttachConsole_ptr)(DWORD);
+typedef BOOL (WINAPI *SleepConditionVariableCS_ptr)(PCONDITION_VARIABLE, PCRITICAL_SECTION, DWORD);
+typedef void (WINAPI *WakeConditionVariable_ptr)(PCONDITION_VARIABLE);
+typedef BOOL (WINAPI *CreateWellKnownSid_ptr)(WELL_KNOWN_SID_TYPE, SID *, SID *, unsigned long *);
+typedef BOOL (WINAPI *IsWellKnownSid_ptr)(SID *, WELL_KNOWN_SID_TYPE);
+
+typedef struct {
+  HMODULE kernel32;
+  HMODULE advapi32;
+  AttachConsole_ptr AttachConsole;
+  SleepConditionVariableCS_ptr SleepConditionVariableCS;
+  WakeConditionVariable_ptr WakeConditionVariable;
+  CreateWellKnownSid_ptr CreateWellKnownSid;
+  IsWellKnownSid_ptr IsWellKnownSid;
+} imports_t;
+
+HMODULE get_dll(const TCHAR *, unsigned long *);
+FARPROC get_import(HMODULE, const char *, unsigned long *);
+int get_imports();
+void free_imports();
+
+#endif

BIN
messages.mc


+ 350 - 350
process.cpp

@@ -1,350 +1,350 @@
-#include "nssm.h"
-
-extern imports_t imports;
-
-void service_kill_t(nssm_service_t *service, kill_t *k) {
-  if (! service) return;
-  if (! k) return;
-
-  ZeroMemory(k, sizeof(*k));
-  k->name = service->name;
-  k->process_handle = service->process_handle;
-  k->pid = service->pid;
-  k->exitcode = service->exitcode;
-  k->stop_method = service->stop_method;
-  k->kill_console_delay = service->kill_console_delay;
-  k->kill_window_delay = service->kill_window_delay;
-  k->kill_threads_delay = service->kill_threads_delay;
-  k->status_handle = service->status_handle;
-  k->status = &service->status;
-  k->creation_time = service->creation_time;
-  k->exit_time = service->exit_time;
-}
-
-int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
-  FILETIME creation_time, exit_time, kernel_time, user_time;
-
-  if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
-    return 1;
-  }
-
-  memmove(ft, &creation_time, sizeof(creation_time));
-
-  return 0;
-}
-
-int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
-  FILETIME creation_time, exit_time, kernel_time, user_time;
-
-  if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
-    return 1;
-  }
-
-  if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
-  memmove(ft, &exit_time, sizeof(exit_time));
-
-  return 0;
-}
-
-int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {
-  /* Check parent process ID matches. */
-  if (pe->th32ParentProcessID != ppid) return 1;
-
-  /*
-    Process IDs can be reused so do a sanity check by making sure the child
-    has been running for less time than the parent.
-    Though unlikely, it's possible that the parent exited and its process ID
-    was already reused, so we'll also compare against its exit time.
-  */
-  HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
-  if (! process_handle) {
-    TCHAR pid_string[16];
-    _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
-    return 2;
-  }
-
-  FILETIME ft;
-  if (get_process_creation_time(process_handle, &ft)) {
-    CloseHandle(process_handle);
-    return 3;
-  }
-
-  CloseHandle(process_handle);
-
-  /* Verify that the parent's creation time is not later. */
-  if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;
-
-  /* Verify that the parent's exit time is not earlier. */
-  if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;
-
-  return 0;
-}
-
-/* Send some window messages and hope the window respects one or more. */
-int CALLBACK kill_window(HWND window, LPARAM arg) {
-  kill_t *k = (kill_t *) arg;
-
-  unsigned long pid;
-  if (! GetWindowThreadProcessId(window, &pid)) return 1;
-  if (pid != k->pid) return 1;
-
-  /* First try sending WM_CLOSE to request that the window close. */
-  k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
-
-  /*
-    Then tell the window that the user is logging off and it should exit
-    without worrying about saving any data.
-  */
-  k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
-
-  return 1;
-}
-
-/*
-  Try to post a message to the message queues of threads associated with the
-  given process ID.  Not all threads have message queues so there's no
-  guarantee of success, and we don't want to be left waiting for unsignalled
-  processes so this function returns only true if at least one thread was
-  successfully prodded.
-*/
-int kill_threads(nssm_service_t *service, kill_t *k) {
-  int ret = 0;
-
-  /* Get a snapshot of all threads in the system. */
-  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
-  if (! snapshot) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);
-    return 0;
-  }
-
-  THREADENTRY32 te;
-  ZeroMemory(&te, sizeof(te));
-  te.dwSize = sizeof(te);
-
-  if (! Thread32First(snapshot, &te)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
-    CloseHandle(snapshot);
-    return 0;
-  }
-
-  /* This thread belongs to the doomed process so signal it. */
-  if (te.th32OwnerProcessID == k->pid) {
-    ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
-  }
-
-  while (true) {
-    /* Try to get the next thread. */
-    if (! Thread32Next(snapshot, &te)) {
-      unsigned long error = GetLastError();
-      if (error == ERROR_NO_MORE_FILES) break;
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
-      CloseHandle(snapshot);
-      return ret;
-    }
-
-    if (te.th32OwnerProcessID == k->pid) {
-      ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
-    }
-  }
-
-  CloseHandle(snapshot);
-
-  return ret;
-}
-
-int kill_threads(kill_t *k) {
-  return kill_threads(NULL, k);
-}
-
-/* Give the process a chance to die gracefully. */
-int kill_process(nssm_service_t *service, kill_t *k) {
-  if (! k) return 1;
-
-  unsigned long ret;
-  if (GetExitCodeProcess(k->process_handle, &ret)) {
-    if (ret != STILL_ACTIVE) return 1;
-  }
-
-  /* Try to send a Control-C event to the console. */
-  if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {
-    if (! kill_console(k)) return 1;
-  }
-
-  /*
-    Try to post messages to the windows belonging to the given process ID.
-    If the process is a console application it won't have any windows so there's
-    no guarantee of success.
-  */
-  if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {
-    EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);
-    if (k->signalled) {
-      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;
-      k->signalled = 0;
-    }
-  }
-
-  /*
-    Try to post messages to any thread message queues associated with the
-    process.  Console applications might have them (but probably won't) so
-    there's still no guarantee of success.
-  */
-  if (k->stop_method & NSSM_STOP_METHOD_THREADS) {
-    if (kill_threads(k)) {
-      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;
-    }
-  }
-
-  /* We tried being nice.  Time for extreme prejudice. */
-  if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {
-    return TerminateProcess(k->process_handle, k->exitcode);
-  }
-
-  return 0;
-}
-
-int kill_process(kill_t *k) {
-  return kill_process(NULL, k);
-}
-
-/* Simulate a Control-C event to our console (shared with the app). */
-int kill_console(nssm_service_t *service, kill_t *k) {
-  unsigned long ret;
-
-  if (! k) return 1;
-
-  /* Check we loaded AttachConsole(). */
-  if (! imports.AttachConsole) return 4;
-
-  /* Try to attach to the process's console. */
-  if (! imports.AttachConsole(k->pid)) {
-    ret = GetLastError();
-
-    switch (ret) {
-      case ERROR_INVALID_HANDLE:
-        /* The app doesn't have a console. */
-        return 1;
-
-      case ERROR_GEN_FAILURE:
-        /* The app already exited. */
-        return 2;
-
-      case ERROR_ACCESS_DENIED:
-      default:
-        /* We already have a console. */
-        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);
-        return 3;
-    }
-  }
-
-  /* Ignore the event ourselves. */
-  ret = 0;
-  if (! SetConsoleCtrlHandler(0, TRUE)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
-    ret = 4;
-  }
-
-  /* Send the event. */
-  if (! ret) {
-    if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);
-      ret = 5;
-    }
-  }
-
-  /* Detach from the console. */
-  if (! FreeConsole()) {
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);
-  }
-
-  /* Wait for process to exit. */
-  if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;
-
-  return ret;
-}
-
-int kill_console(kill_t *k) {
-  return kill_console(NULL, k);
-}
-
-void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) {
-  if (! k) return;
-  /* Shouldn't happen unless the service failed to start. */
-  if (! k->pid) return; /* XXX: needed? */
-  unsigned long pid = k->pid;
-
-  TCHAR pid_string[16], code[16];
-  _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
-  _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
-
-  /* We will need a process handle in order to call TerminateProcess() later. */
-  HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
-  if (process_handle) {
-    /* Kill this process first, then its descendents. */
-    TCHAR ppid_string[16];
-    _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
-    k->process_handle = process_handle; /* XXX: open directly? */
-    if (! kill_process(k)) {
-      /* Maybe it already died. */
-      unsigned long ret;
-      if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
-        if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
-        else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
-      }
-    }
-
-    CloseHandle(process_handle);
-  }
-  else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
-
-  /* Get a snapshot of all processes in the system. */
-  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
-  if (! snapshot) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
-    return;
-  }
-
-  PROCESSENTRY32 pe;
-  ZeroMemory(&pe, sizeof(pe));
-  pe.dwSize = sizeof(pe);
-
-  if (! Process32First(snapshot, &pe)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
-    CloseHandle(snapshot);
-    return;
-  }
-
-  /* This is a child of the doomed process so kill it. */
-  if (! check_parent(k, &pe, pid)) {
-    k->pid = pe.th32ProcessID;
-    kill_process_tree(k, ppid);
-  }
-  k->pid = pid;
-
-  while (true) {
-    /* Try to get the next process. */
-    if (! Process32Next(snapshot, &pe)) {
-      unsigned long ret = GetLastError();
-      if (ret == ERROR_NO_MORE_FILES) break;
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
-      CloseHandle(snapshot);
-      return;
-    }
-
-    if (! check_parent(k, &pe, pid)) {
-      k->pid = pe.th32ProcessID;
-      kill_process_tree(k, ppid);
-    }
-    k->pid = pid;
-  }
-
-  CloseHandle(snapshot);
-}
-
-void kill_process_tree(kill_t *k, unsigned long ppid) {
-  return kill_process_tree(NULL, k, ppid);
-}
+#include "nssm.h"
+
+extern imports_t imports;
+
+void service_kill_t(nssm_service_t *service, kill_t *k) {
+  if (! service) return;
+  if (! k) return;
+
+  ZeroMemory(k, sizeof(*k));
+  k->name = service->name;
+  k->process_handle = service->process_handle;
+  k->pid = service->pid;
+  k->exitcode = service->exitcode;
+  k->stop_method = service->stop_method;
+  k->kill_console_delay = service->kill_console_delay;
+  k->kill_window_delay = service->kill_window_delay;
+  k->kill_threads_delay = service->kill_threads_delay;
+  k->status_handle = service->status_handle;
+  k->status = &service->status;
+  k->creation_time = service->creation_time;
+  k->exit_time = service->exit_time;
+}
+
+int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
+  FILETIME creation_time, exit_time, kernel_time, user_time;
+
+  if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
+    return 1;
+  }
+
+  memmove(ft, &creation_time, sizeof(creation_time));
+
+  return 0;
+}
+
+int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
+  FILETIME creation_time, exit_time, kernel_time, user_time;
+
+  if (! GetProcessTimes(process_handle, &creation_time, &exit_time, &kernel_time, &user_time)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSTIMES_FAILED, error_string(GetLastError()), 0);
+    return 1;
+  }
+
+  if (! (exit_time.dwLowDateTime || exit_time.dwHighDateTime)) return 2;
+  memmove(ft, &exit_time, sizeof(exit_time));
+
+  return 0;
+}
+
+int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {
+  /* Check parent process ID matches. */
+  if (pe->th32ParentProcessID != ppid) return 1;
+
+  /*
+    Process IDs can be reused so do a sanity check by making sure the child
+    has been running for less time than the parent.
+    Though unlikely, it's possible that the parent exited and its process ID
+    was already reused, so we'll also compare against its exit time.
+  */
+  HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION, false, pe->th32ProcessID);
+  if (! process_handle) {
+    TCHAR pid_string[16];
+    _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
+    return 2;
+  }
+
+  FILETIME ft;
+  if (get_process_creation_time(process_handle, &ft)) {
+    CloseHandle(process_handle);
+    return 3;
+  }
+
+  CloseHandle(process_handle);
+
+  /* Verify that the parent's creation time is not later. */
+  if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;
+
+  /* Verify that the parent's exit time is not earlier. */
+  if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;
+
+  return 0;
+}
+
+/* Send some window messages and hope the window respects one or more. */
+int CALLBACK kill_window(HWND window, LPARAM arg) {
+  kill_t *k = (kill_t *) arg;
+
+  unsigned long pid;
+  if (! GetWindowThreadProcessId(window, &pid)) return 1;
+  if (pid != k->pid) return 1;
+
+  /* First try sending WM_CLOSE to request that the window close. */
+  k->signalled |= PostMessage(window, WM_CLOSE, k->exitcode, 0);
+
+  /*
+    Then tell the window that the user is logging off and it should exit
+    without worrying about saving any data.
+  */
+  k->signalled |= PostMessage(window, WM_ENDSESSION, 1, ENDSESSION_CLOSEAPP | ENDSESSION_CRITICAL | ENDSESSION_LOGOFF);
+
+  return 1;
+}
+
+/*
+  Try to post a message to the message queues of threads associated with the
+  given process ID.  Not all threads have message queues so there's no
+  guarantee of success, and we don't want to be left waiting for unsignalled
+  processes so this function returns only true if at least one thread was
+  successfully prodded.
+*/
+int kill_threads(nssm_service_t *service, kill_t *k) {
+  int ret = 0;
+
+  /* Get a snapshot of all threads in the system. */
+  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+  if (! snapshot) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);
+    return 0;
+  }
+
+  THREADENTRY32 te;
+  ZeroMemory(&te, sizeof(te));
+  te.dwSize = sizeof(te);
+
+  if (! Thread32First(snapshot, &te)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
+    CloseHandle(snapshot);
+    return 0;
+  }
+
+  /* This thread belongs to the doomed process so signal it. */
+  if (te.th32OwnerProcessID == k->pid) {
+    ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
+  }
+
+  while (true) {
+    /* Try to get the next thread. */
+    if (! Thread32Next(snapshot, &te)) {
+      unsigned long error = GetLastError();
+      if (error == ERROR_NO_MORE_FILES) break;
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
+      CloseHandle(snapshot);
+      return ret;
+    }
+
+    if (te.th32OwnerProcessID == k->pid) {
+      ret |= PostThreadMessage(te.th32ThreadID, WM_QUIT, k->exitcode, 0);
+    }
+  }
+
+  CloseHandle(snapshot);
+
+  return ret;
+}
+
+int kill_threads(kill_t *k) {
+  return kill_threads(NULL, k);
+}
+
+/* Give the process a chance to die gracefully. */
+int kill_process(nssm_service_t *service, kill_t *k) {
+  if (! k) return 1;
+
+  unsigned long ret;
+  if (GetExitCodeProcess(k->process_handle, &ret)) {
+    if (ret != STILL_ACTIVE) return 1;
+  }
+
+  /* Try to send a Control-C event to the console. */
+  if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {
+    if (! kill_console(k)) return 1;
+  }
+
+  /*
+    Try to post messages to the windows belonging to the given process ID.
+    If the process is a console application it won't have any windows so there's
+    no guarantee of success.
+  */
+  if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {
+    EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);
+    if (k->signalled) {
+      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;
+      k->signalled = 0;
+    }
+  }
+
+  /*
+    Try to post messages to any thread message queues associated with the
+    process.  Console applications might have them (but probably won't) so
+    there's still no guarantee of success.
+  */
+  if (k->stop_method & NSSM_STOP_METHOD_THREADS) {
+    if (kill_threads(k)) {
+      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;
+    }
+  }
+
+  /* We tried being nice.  Time for extreme prejudice. */
+  if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {
+    return TerminateProcess(k->process_handle, k->exitcode);
+  }
+
+  return 0;
+}
+
+int kill_process(kill_t *k) {
+  return kill_process(NULL, k);
+}
+
+/* Simulate a Control-C event to our console (shared with the app). */
+int kill_console(nssm_service_t *service, kill_t *k) {
+  unsigned long ret;
+
+  if (! k) return 1;
+
+  /* Check we loaded AttachConsole(). */
+  if (! imports.AttachConsole) return 4;
+
+  /* Try to attach to the process's console. */
+  if (! imports.AttachConsole(k->pid)) {
+    ret = GetLastError();
+
+    switch (ret) {
+      case ERROR_INVALID_HANDLE:
+        /* The app doesn't have a console. */
+        return 1;
+
+      case ERROR_GEN_FAILURE:
+        /* The app already exited. */
+        return 2;
+
+      case ERROR_ACCESS_DENIED:
+      default:
+        /* We already have a console. */
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);
+        return 3;
+    }
+  }
+
+  /* Ignore the event ourselves. */
+  ret = 0;
+  if (! SetConsoleCtrlHandler(0, TRUE)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
+    ret = 4;
+  }
+
+  /* Send the event. */
+  if (! ret) {
+    if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);
+      ret = 5;
+    }
+  }
+
+  /* Detach from the console. */
+  if (! FreeConsole()) {
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);
+  }
+
+  /* Wait for process to exit. */
+  if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;
+
+  return ret;
+}
+
+int kill_console(kill_t *k) {
+  return kill_console(NULL, k);
+}
+
+void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) {
+  if (! k) return;
+  /* Shouldn't happen unless the service failed to start. */
+  if (! k->pid) return; /* XXX: needed? */
+  unsigned long pid = k->pid;
+
+  TCHAR pid_string[16], code[16];
+  _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
+  _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
+
+  /* We will need a process handle in order to call TerminateProcess() later. */
+  HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
+  if (process_handle) {
+    /* Kill this process first, then its descendents. */
+    TCHAR ppid_string[16];
+    _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
+    k->process_handle = process_handle; /* XXX: open directly? */
+    if (! kill_process(k)) {
+      /* Maybe it already died. */
+      unsigned long ret;
+      if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
+        if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
+        else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
+      }
+    }
+
+    CloseHandle(process_handle);
+  }
+  else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
+
+  /* Get a snapshot of all processes in the system. */
+  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+  if (! snapshot) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
+    return;
+  }
+
+  PROCESSENTRY32 pe;
+  ZeroMemory(&pe, sizeof(pe));
+  pe.dwSize = sizeof(pe);
+
+  if (! Process32First(snapshot, &pe)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
+    CloseHandle(snapshot);
+    return;
+  }
+
+  /* This is a child of the doomed process so kill it. */
+  if (! check_parent(k, &pe, pid)) {
+    k->pid = pe.th32ProcessID;
+    kill_process_tree(k, ppid);
+  }
+  k->pid = pid;
+
+  while (true) {
+    /* Try to get the next process. */
+    if (! Process32Next(snapshot, &pe)) {
+      unsigned long ret = GetLastError();
+      if (ret == ERROR_NO_MORE_FILES) break;
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
+      CloseHandle(snapshot);
+      return;
+    }
+
+    if (! check_parent(k, &pe, pid)) {
+      k->pid = pe.th32ProcessID;
+      kill_process_tree(k, ppid);
+    }
+    k->pid = pid;
+  }
+
+  CloseHandle(snapshot);
+}
+
+void kill_process_tree(kill_t *k, unsigned long ppid) {
+  return kill_process_tree(NULL, k, ppid);
+}

+ 36 - 36
process.h

@@ -1,36 +1,36 @@
-#ifndef PROCESS_H
-#define PROCESS_H
-
-#include <tlhelp32.h>
-
-typedef struct {
-  TCHAR *name;
-  HANDLE process_handle;
-  unsigned long pid;
-  unsigned long exitcode;
-  unsigned long stop_method;
-  unsigned long kill_console_delay;
-  unsigned long kill_window_delay;
-  unsigned long kill_threads_delay;
-  SERVICE_STATUS_HANDLE status_handle;
-  SERVICE_STATUS *status;
-  FILETIME creation_time;
-  FILETIME exit_time;
-  int signalled;
-} kill_t;
-
-void service_kill_t(nssm_service_t *, kill_t *);
-int get_process_creation_time(HANDLE, FILETIME *);
-int get_process_exit_time(HANDLE, FILETIME *);
-int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long);
-int CALLBACK kill_window(HWND, LPARAM);
-int kill_threads(nssm_service_t *, kill_t *);
-int kill_threads(kill_t *);
-int kill_console(nssm_service_t *, kill_t *);
-int kill_console(kill_t *);
-int kill_process(nssm_service_t *, kill_t *);
-int kill_process(kill_t *);
-void kill_process_tree(nssm_service_t *, kill_t *, unsigned long);
-void kill_process_tree(kill_t *, unsigned long);
-
-#endif
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include <tlhelp32.h>
+
+typedef struct {
+  TCHAR *name;
+  HANDLE process_handle;
+  unsigned long pid;
+  unsigned long exitcode;
+  unsigned long stop_method;
+  unsigned long kill_console_delay;
+  unsigned long kill_window_delay;
+  unsigned long kill_threads_delay;
+  SERVICE_STATUS_HANDLE status_handle;
+  SERVICE_STATUS *status;
+  FILETIME creation_time;
+  FILETIME exit_time;
+  int signalled;
+} kill_t;
+
+void service_kill_t(nssm_service_t *, kill_t *);
+int get_process_creation_time(HANDLE, FILETIME *);
+int get_process_exit_time(HANDLE, FILETIME *);
+int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long);
+int CALLBACK kill_window(HWND, LPARAM);
+int kill_threads(nssm_service_t *, kill_t *);
+int kill_threads(kill_t *);
+int kill_console(nssm_service_t *, kill_t *);
+int kill_console(kill_t *);
+int kill_process(nssm_service_t *, kill_t *);
+int kill_process(kill_t *);
+void kill_process_tree(nssm_service_t *, kill_t *, unsigned long);
+void kill_process_tree(kill_t *, unsigned long);
+
+#endif

+ 1130 - 1130
settings.cpp

@@ -1,1130 +1,1130 @@
-#include "nssm.h"
-/* XXX: (value && value->string) is probably bogus because value is probably never null */
-
-/* Affinity. */
-#define NSSM_AFFINITY_ALL _T("All")
-
-extern const TCHAR *exit_action_strings[];
-extern const TCHAR *startup_strings[];
-extern const TCHAR *priority_strings[];
-
-/* Does the parameter refer to the default value of the setting? */
-static inline int is_default(const TCHAR *value) {
-  return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]);
-}
-
-static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) {
-  size_t len = _tcslen(string);
-  if (! len++) {
-    value->string = 0;
-    return 0;
-  }
-
-  value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
-  if (! value->string) {
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
-    return -1;
-  }
-
-  if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) {
-    HeapFree(GetProcessHeap(), 0, value->string);
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
-    return -1;
-  }
-
-  return 1;
-}
-
-/* Functions to manage NSSM-specific settings in the registry. */
-static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! key) return -1;
-
-  unsigned long number;
-  long error;
-
-  /* Resetting to default? */
-  if (! value || ! value->string) {
-    error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-  if (str_number(value->string, &number)) return -1;
-
-  if (default_value && number == PtrToUlong(default_value)) {
-    error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-
-  if (set_number(key, (TCHAR *) name, number)) return -1;
-
-  return 1;
-}
-
-static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  return get_number(key, (TCHAR *) name, &value->numeric, false);
-}
-
-static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! key) return -1;
-
-  long error;
-
-  /* Resetting to default? */
-  if (! value || ! value->string) {
-    if (default_value) value->string = (TCHAR *) default_value;
-    else {
-      error = RegDeleteValue(key, name);
-      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-      return -1;
-    }
-  }
-  if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) {
-    error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-
-  if (set_expand_string(key, (TCHAR *) name, value->string)) return -1;
-
-  return 1;
-}
-
-static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  TCHAR buffer[VALUE_LENGTH];
-
-  if (get_string(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false, false)) return -1;
-
-  return value_from_string(name, value, buffer);
-}
-
-static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  unsigned long exitcode;
-  TCHAR *code;
-  TCHAR action_string[ACTION_LEN];
-
-  if (additional) {
-    /* Default action? */
-    if (is_default(additional)) code = 0;
-    else {
-      if (str_number(additional, &exitcode)) return -1;
-      code = (TCHAR *) additional;
-    }
-  }
-
-  HKEY key = open_registry(service_name, name, KEY_WRITE);
-  if (! key) return -1;
-
-  long error;
-  int ret = 1;
-
-  /* Resetting to default? */
-  if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string);
-  else {
-    if (code) {
-      /* Delete explicit action. */
-      error = RegDeleteValue(key, code);
-      RegCloseKey(key);
-      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error));
-      return -1;
-    }
-    else {
-      /* Explicitly keep the default action. */
-      if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value);
-      ret = 0;
-    }
-  }
-
-  /* Validate the string. */
-  for (int i = 0; exit_action_strings[i]; i++) {
-    if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
-      if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0;
-      if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) exit_action_strings[i], (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
-        print_message(stderr, NSSM_MESSAGE_SETVALUE_FAILED, code, service_name, error_string(GetLastError()));
-        RegCloseKey(key);
-        return -1;
-      }
-
-      RegCloseKey(key);
-      return ret;
-    }
-  }
-
-  print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string);
-  for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]);
-
-  return -1;
-}
-
-static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  unsigned long exitcode = 0;
-  unsigned long *code = 0;
-
-  if (additional) {
-    if (! is_default(additional)) {
-      if (str_number(additional, &exitcode)) return -1;
-      code = &exitcode;
-    }
-  }
-
-  TCHAR action_string[ACTION_LEN];
-  bool default_action;
-  if (get_exit_action(service_name, code, action_string, &default_action)) return -1;
-
-  value_from_string(name, value, action_string);
-
-  if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0;
-  return 1;
-}
-
-static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) {
-  TCHAR *s;
-
-  for (s = (TCHAR *) hook_name; *s; s++) {
-    if (*s == _T('/')) {
-      *s = _T('\0');
-      _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name);
-      _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s);
-      return valid_hook_name(hook_event, hook_action, false);
-    }
-  }
-
-  print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name);
-  return false;
-}
-
-static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  TCHAR hook_event[HOOK_NAME_LENGTH];
-  TCHAR hook_action[HOOK_NAME_LENGTH];
-  if (! split_hook_name(additional, hook_event, hook_action)) return -1;
-
-  TCHAR *cmd;
-  if (value && value->string) cmd = value->string;
-  else cmd = _T("");
-
-  if (set_hook(service_name, hook_event, hook_action, cmd)) return -1;
-  if (! _tcslen(cmd)) return 0;
-  return 1;
-}
-
-static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  TCHAR hook_event[HOOK_NAME_LENGTH];
-  TCHAR hook_action[HOOK_NAME_LENGTH];
-  if (! split_hook_name(additional, hook_event, hook_action)) return -1;
-
-  TCHAR cmd[CMD_LENGTH];
-  if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1;
-
-  value_from_string(name, value, cmd);
-
-  if (! _tcslen(cmd)) return 0;
-  return 1;
-}
-
-static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! key) return -1;
-
-  long error;
-  __int64 mask;
-  __int64 system_affinity = 0LL;
-
-  if (value && value->string) {
-    DWORD_PTR affinity;
-    if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0;
-
-    if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL;
-    else if (affinity_string_to_mask(value->string, &mask)) {
-      print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1);
-      return -1;
-    }
-  }
-  else mask = 0LL;
-
-  if (! mask) {
-    error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-
-  /* Canonicalise. */
-  TCHAR *canon = 0;
-  if (affinity_mask_to_string(mask, &canon)) canon = value->string;
-
-  __int64 effective_affinity = mask & system_affinity;
-  if (effective_affinity != mask) {
-    /* Requested CPUs did not intersect with available CPUs? */
-    if (! effective_affinity) mask = effective_affinity = system_affinity;
-
-    TCHAR *system = 0;
-    if (! affinity_mask_to_string(system_affinity, &system)) {
-      TCHAR *effective = 0;
-      if (! affinity_mask_to_string(effective_affinity, &effective)) {
-        print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective);
-        HeapFree(GetProcessHeap(), 0, effective);
-      }
-      HeapFree(GetProcessHeap(), 0, system);
-    }
-  }
-
-  if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
-    if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0);
-    return -1;
-  }
-
-  if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
-  return 1;
-}
-
-static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! key) return -1;
-
-  unsigned long type;
-  TCHAR *buffer = 0;
-  unsigned long buflen = 0;
-
-  int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen);
-  if (ret == ERROR_FILE_NOT_FOUND) {
-    if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0;
-    return -1;
-  }
-  if (ret != ERROR_SUCCESS) return -1;
-
-  if (type != REG_SZ) return -1;
-
-  buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
-  if (! buffer) {
-    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity"));
-    return -1;
-  }
-
-  if (get_string(key, (TCHAR *) name, buffer, buflen, false, false, true)) {
-    HeapFree(GetProcessHeap(), 0, buffer);
-    return -1;
-  }
-
-  __int64 affinity;
-  if (affinity_string_to_mask(buffer, &affinity)) {
-    print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1);
-    HeapFree(GetProcessHeap(), 0, buffer);
-    return -1;
-  }
-
-  HeapFree(GetProcessHeap(), 0, buffer);
-
-  /* Canonicalise. */
-  if (affinity_mask_to_string(affinity, &buffer)) {
-    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-    return -1;
-  }
-
-  ret = value_from_string(name, value, buffer);
-  HeapFree(GetProcessHeap(), 0, buffer);
-  return ret;
-}
-
-static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! param) return -1;
-
-  if (! value || ! value->string || ! value->string[0]) {
-    long error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-
-  unsigned long envlen = (unsigned long) _tcslen(value->string) + 1;
-  TCHAR *unformatted = 0;
-  unsigned long newlen;
-  if (unformat_double_null(value->string, envlen, &unformatted, &newlen)) return -1;
-
-  if (test_environment(unformatted)) {
-    HeapFree(GetProcessHeap(), 0, unformatted);
-    print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT);
-    return -1;
-  }
-
-  if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) {
-    if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
-    return -1;
-  }
-
-  if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
-  return 1;
-}
-
-static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! param) return -1;
-
-  TCHAR *env = 0;
-  unsigned long envlen;
-  if (get_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1;
-  if (! envlen) return 0;
-
-  TCHAR *formatted;
-  unsigned long newlen;
-  if (format_double_null(env, envlen, &formatted, &newlen)) return -1;
-
-  int ret;
-  if (additional) {
-    /* Find named environment variable. */
-    TCHAR *s;
-    size_t len = _tcslen(additional);
-    for (s = env; *s; s++) {
-      /* Look for <additional>=<string> NULL NULL */
-      if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) {
-        /* Strip <key>= */
-        s += len + 1;
-        ret = value_from_string(name, value, s);
-        HeapFree(GetProcessHeap(), 0, env);
-        return ret;
-      }
-
-      /* Skip this string. */
-      for ( ; *s; s++);
-    }
-    HeapFree(GetProcessHeap(), 0, env);
-    return 0;
-  }
-
-  HeapFree(GetProcessHeap(), 0, env);
-
-  ret = value_from_string(name, value, formatted);
-  if (newlen) HeapFree(GetProcessHeap(), 0, formatted);
-  return ret;
-}
-
-static int setting_set_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! param) return -1;
-
-  TCHAR *priority_string;
-  int i;
-  long error;
-
-  if (value && value->string) priority_string = value->string;
-  else if (default_value) priority_string = (TCHAR *) default_value;
-  else {
-    error = RegDeleteValue(key, name);
-    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-    return -1;
-  }
-
-  for (i = 0; priority_strings[i]; i++) {
-    if (! str_equiv(priority_strings[i], priority_string)) continue;
-
-    if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) {
-      error = RegDeleteValue(key, name);
-      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
-      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
-      return -1;
-    }
-
-    if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1;
-    return 1;
-  }
-
-  print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string);
-  for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]);
-
-  return -1;
-}
-
-static int setting_get_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = (HKEY) param;
-  if (! param) return -1;
-
-  unsigned long constant;
-  switch (get_number(key, (TCHAR *) name, &constant, false)) {
-    case 0: return value_from_string(name, value, (const TCHAR *) default_value);
-    case -1: return -1;
-  }
-
-  return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]);
-}
-
-/* Functions to manage native service settings. */
-static int native_set_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /*
-    Get existing service dependencies because we must set both types together.
-  */
-  TCHAR *buffer;
-  unsigned long buflen;
-  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
-
-  if (! value || ! value->string || ! value->string[0]) {
-    if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
-      print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-    return 0;
-  }
-
-  unsigned long len = (unsigned long) _tcslen(value->string) + 1;
-  TCHAR *unformatted = 0;
-  unsigned long newlen;
-  if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
-    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-    return -1;
-  }
-
-  /* Prepend group identifier. */
-  unsigned long missing = 0;
-  TCHAR *canon = unformatted;
-  size_t canonlen = 0;
-  TCHAR *s;
-  for (s = unformatted; *s; s++) {
-    if (*s != SC_GROUP_IDENTIFIER) missing++;
-    size_t len = _tcslen(s);
-    canonlen += len + 1;
-    s += len;
-  }
-
-  if (missing) {
-    /* Missing identifiers plus double NULL terminator. */
-    canonlen += missing + 1;
-    newlen = (unsigned long) canonlen;
-
-    canon = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, canonlen * sizeof(TCHAR));
-    if (! canon) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("native_set_dependongroup"));
-      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
-      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    size_t i = 0;
-    for (s = unformatted; *s; s++) {
-      if (*s != SC_GROUP_IDENTIFIER) canon[i++] = SC_GROUP_IDENTIFIER;
-      size_t len = _tcslen(s);
-      memmove(canon + i, s, (len + 1) * sizeof(TCHAR));
-      i += len + 1;
-      s += len;
-    }
-  }
-
-  TCHAR *dependencies;
-  if (buflen > 2) {
-    dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
-    if (! dependencies) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependongroup"));
-      if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
-      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
-      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    memmove(dependencies, buffer, buflen * sizeof(TCHAR));
-    memmove(dependencies + buflen - 1, canon, newlen * sizeof(TCHAR));
-  }
-  else dependencies = canon;
-
-  int ret = 1;
-  if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
-  if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
-  if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
-  if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
-  if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-
-  return ret;
-}
-
-static int native_get_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  TCHAR *buffer;
-  unsigned long buflen;
-  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
-
-  int ret;
-  if (buflen) {
-    TCHAR *formatted;
-    unsigned long newlen;
-    if (format_double_null(buffer, buflen, &formatted, &newlen)) {
-      HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    ret = value_from_string(name, value, formatted);
-    HeapFree(GetProcessHeap(), 0, formatted);
-    HeapFree(GetProcessHeap(), 0, buffer);
-  }
-  else {
-    value->string = 0;
-    ret = 0;
-  }
-
-  return ret;
-}
-
-static int native_set_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /*
-    Get existing group dependencies because we must set both types together.
-  */
-  TCHAR *buffer;
-  unsigned long buflen;
-  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
-
-  if (! value || ! value->string || ! value->string[0]) {
-    if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
-      print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-    return 0;
-  }
-
-  unsigned long len = (unsigned long) _tcslen(value->string) + 1;
-  TCHAR *unformatted = 0;
-  unsigned long newlen;
-  if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
-    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-    return -1;
-  }
-
-  TCHAR *dependencies;
-  if (buflen > 2) {
-    dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
-    if (! dependencies) {
-      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependonservice"));
-      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
-      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    memmove(dependencies, buffer, buflen * sizeof(TCHAR));
-    memmove(dependencies + buflen - 1, unformatted, newlen * sizeof(TCHAR));
-  }
-  else dependencies = unformatted;
-
-  int ret = 1;
-  if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
-  if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
-  if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
-  if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
-
-  return ret;
-}
-
-static int native_get_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  TCHAR *buffer;
-  unsigned long buflen;
-  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
-
-  int ret;
-  if (buflen) {
-    TCHAR *formatted;
-    unsigned long newlen;
-    if (format_double_null(buffer, buflen, &formatted, &newlen)) {
-      HeapFree(GetProcessHeap(), 0, buffer);
-      return -1;
-    }
-
-    ret = value_from_string(name, value, formatted);
-    HeapFree(GetProcessHeap(), 0, formatted);
-    HeapFree(GetProcessHeap(), 0, buffer);
-  }
-  else {
-    value->string = 0;
-    ret = 0;
-  }
-
-  return ret;
-}
-
-int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  TCHAR *description = 0;
-  if (value) description = value->string;
-  if (set_service_description(service_name, service_handle, description)) return -1;
-
-  if (description && description[0]) return 1;
-
-  return 0;
-}
-
-int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  TCHAR buffer[VALUE_LENGTH];
-  if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1;
-
-  if (buffer[0]) return value_from_string(name, value, buffer);
-  value->string = 0;
-
-  return 0;
-}
-
-int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  TCHAR *displayname = 0;
-  if (value && value->string) displayname = value->string;
-  else displayname = (TCHAR *) service_name;
-
-  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) {
-    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-    return -1;
-  }
-
-  /*
-    If the display name and service name differ only in case,
-    ChangeServiceConfig() will return success but the display name will be
-    set to the service name, NOT the value passed to the function.
-    This appears to be a quirk of Windows rather than a bug here.
-  */
-  if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1;
-
-  return 0;
-}
-
-int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-  if (! qsc) return -1;
-
-  int ret = value_from_string(name, value, qsc->lpDisplayName);
-  HeapFree(GetProcessHeap(), 0, qsc);
-
-  return ret;
-}
-
-int native_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = open_service_registry(service_name, KEY_SET_VALUE, false);
-  if (! key) return -1;
-
-  int ret = setting_set_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional);
-  RegCloseKey(key);
-  return ret;
-}
-
-int native_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  HKEY key = open_service_registry(service_name, KEY_READ, false);
-  if (! key) return -1;
-
-  ZeroMemory(value, sizeof(value_t));
-  int ret = setting_get_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional);
-  RegCloseKey(key);
-  return ret;
-}
-
-int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /* It makes no sense to try to reset the image path. */
-  if (! value || ! value->string) {
-    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
-    return -1;
-  }
-
-  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) {
-    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-    return -1;
-  }
-
-  return 1;
-}
-
-int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-  if (! qsc) return -1;
-
-  int ret = value_from_string(name, value, qsc->lpBinaryPathName);
-  HeapFree(GetProcessHeap(), 0, qsc);
-
-  return ret;
-}
-
-int native_set_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  print_message(stderr, NSSM_MESSAGE_CANNOT_RENAME_SERVICE);
-  return -1;
-}
-
-int native_get_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  return value_from_string(name, value, service_name);
-}
-
-int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /*
-    Logical syntax is: nssm set <service> ObjectName <username> <password>
-    That means the username is actually passed in the additional parameter.
-  */
-  bool localsystem = false;
-  TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
-  TCHAR *password = 0;
-  if (additional) {
-    username = (TCHAR *) additional;
-    if (value && value->string) password = value->string;
-  }
-  else if (value && value->string) username = value->string;
-
-  const TCHAR *well_known = well_known_username(username);
-  size_t passwordsize = 0;
-  if (well_known) {
-    if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true;
-    username = (TCHAR *) well_known;
-    password = _T("");
-  }
-  else if (! password) {
-    /* We need a password if the account requires it. */
-    print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
-    return -1;
-  }
-  else passwordsize = _tcslen(password) * sizeof(TCHAR);
-
-  /*
-    ChangeServiceConfig() will fail to set the username if the service is set
-    to interact with the desktop.
-  */
-  unsigned long type = SERVICE_NO_CHANGE;
-  if (! localsystem) {
-    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-    if (! qsc) {
-      if (passwordsize) SecureZeroMemory(password, passwordsize);
-      return -1;
-    }
-
-    type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS;
-    HeapFree(GetProcessHeap(), 0, qsc);
-  }
-
-  if (! well_known) {
-    if (grant_logon_as_service(username)) {
-      if (passwordsize) SecureZeroMemory(password, passwordsize);
-      print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
-      return -1;
-    }
-  }
-
-  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) {
-    if (passwordsize) SecureZeroMemory(password, passwordsize);
-    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-    return -1;
-  }
-
-  if (passwordsize) SecureZeroMemory(password, passwordsize);
-
-  if (localsystem) return 0;
-
-  return 1;
-}
-
-int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-  if (! qsc) return -1;
-
-  int ret = value_from_string(name, value, qsc->lpServiceStartName);
-  HeapFree(GetProcessHeap(), 0, qsc);
-
-  return ret;
-}
-
-int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /* It makes no sense to try to reset the startup type. */
-  if (! value || ! value->string) {
-    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
-    return -1;
-  }
-
-  /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */
-  int service_startup = -1;
-  int i;
-  for (i = 0; startup_strings[i]; i++) {
-    if (str_equiv(value->string, startup_strings[i])) {
-      service_startup = i;
-      break;
-    }
-  }
-
-  if (service_startup < 0) {
-    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string);
-    for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]);
-    return -1;
-  }
-
-  unsigned long startup;
-  switch (service_startup) {
-    case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
-    case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
-    default: startup = SERVICE_AUTO_START;
-  }
-
-  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
-    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-    return -1;
-  }
-
-  SERVICE_DELAYED_AUTO_START_INFO delayed;
-  ZeroMemory(&delayed, sizeof(delayed));
-  if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
-  else delayed.fDelayedAutostart = 0;
-  if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
-    unsigned long error = GetLastError();
-    /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
-    if (error != ERROR_INVALID_LEVEL) {
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0);
-    }
-  }
-
-  return 1;
-}
-
-int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-  if (! qsc) return -1;
-
-  unsigned long startup;
-  int ret = get_service_startup(service_name, service_handle, qsc, &startup);
-  HeapFree(GetProcessHeap(), 0, qsc);
-
-  if (ret) return -1;
-
-  unsigned long i;
-  for (i = 0; startup_strings[i]; i++);
-  if (startup >= i) return -1;
-
-  return value_from_string(name, value, startup_strings[startup]);
-}
-
-int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  /* It makes no sense to try to reset the service type. */
-  if (! value || ! value->string) {
-    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
-    return -1;
-  }
-
-  /*
-    We can only manage services of type SERVICE_WIN32_OWN_PROCESS
-    and SERVICE_INTERACTIVE_PROCESS.
-  */
-  unsigned long type = SERVICE_WIN32_OWN_PROCESS;
-  if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS;
-  else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) {
-    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string);
-    _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS);
-    _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS);
-    return -1;
-  }
-
-  /*
-    ChangeServiceConfig() will fail if the service runs under an account
-    other than LOCALSYSTEM and we try to make it interactive.
-  */
-  if (type & SERVICE_INTERACTIVE_PROCESS) {
-    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-    if (! qsc) return -1;
-
-    if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
-      HeapFree(GetProcessHeap(), 0, qsc);
-      print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT);
-      return -1;
-    }
-
-    HeapFree(GetProcessHeap(), 0, qsc);
-  }
-
-  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
-    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
-    return -1;
-  }
-
-  return 1;
-}
-
-int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
-  SC_HANDLE service_handle = (SC_HANDLE) param;
-  if (! service_handle) return -1;
-
-  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
-  if (! qsc) return -1;
-
-  value->numeric = qsc->dwServiceType;
-  HeapFree(GetProcessHeap(), 0, qsc);
-
-  const TCHAR *string;
-  switch (value->numeric) {
-    case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break;
-    case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break;
-    case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break;
-    case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break;
-    case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break;
-    case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break;
-    default: string = NSSM_UNKNOWN;
-  }
-
-  return value_from_string(name, value, string);
-}
-
-int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
-  if (! key) return -1;
-  int ret;
-
-  if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional);
-  else ret = -1;
-
-  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
-  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
-  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
-
-  return ret;
-}
-
-int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
-  if (! service_handle) return -1;
-
-  int ret;
-  if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional);
-  else ret = -1;
-
-  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
-  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
-  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
-
-  return ret;
-}
-
-/*
-  Returns:  1 if the value was retrieved.
-            0 if the default value was retrieved.
-           -1 on error.
-*/
-int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
-  if (! key) return -1;
-  int ret;
-
-  switch (setting->type) {
-    case REG_EXPAND_SZ:
-    case REG_MULTI_SZ:
-    case REG_SZ:
-      value->string = (TCHAR *) setting->default_value;
-      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
-      else ret = -1;
-      break;
-
-    case REG_DWORD:
-      value->numeric = PtrToUlong(setting->default_value);
-      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
-      else ret = -1;
-      break;
-
-    default:
-      ret = -1;
-      break;
-  }
-
-  if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name);
-
-  return ret;
-}
-
-int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
-  if (! service_handle) return -1;
-  return setting->get(service_name, service_handle, setting->name, 0, value, additional);
-}
-
-settings_t settings[] = {
-  { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
-  { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook },
-  { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity },
-  { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
-  { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
-  { NSSM_REG_NO_CONSOLE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority },
-  { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
-  { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
-  { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number },
-  { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup },
-  { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice },
-  { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },
-  { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname },
-  { NSSM_NATIVE_ENVIRONMENT, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_environment, native_get_environment },
-  { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath },
-  { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname },
-  { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name },
-  { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup },
-  { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type },
-  { NULL, NULL, NULL, NULL, NULL }
-};
+#include "nssm.h"
+/* XXX: (value && value->string) is probably bogus because value is probably never null */
+
+/* Affinity. */
+#define NSSM_AFFINITY_ALL _T("All")
+
+extern const TCHAR *exit_action_strings[];
+extern const TCHAR *startup_strings[];
+extern const TCHAR *priority_strings[];
+
+/* Does the parameter refer to the default value of the setting? */
+static inline int is_default(const TCHAR *value) {
+  return (str_equiv(value, _T("default")) || str_equiv(value, _T("*")) || ! value[0]);
+}
+
+static int value_from_string(const TCHAR *name, value_t *value, const TCHAR *string) {
+  size_t len = _tcslen(string);
+  if (! len++) {
+    value->string = 0;
+    return 0;
+  }
+
+  value->string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
+  if (! value->string) {
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
+    return -1;
+  }
+
+  if (_sntprintf_s(value->string, len, _TRUNCATE, _T("%s"), string) < 0) {
+    HeapFree(GetProcessHeap(), 0, value->string);
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, name, _T("value_from_string()"));
+    return -1;
+  }
+
+  return 1;
+}
+
+/* Functions to manage NSSM-specific settings in the registry. */
+static int setting_set_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  unsigned long number;
+  long error;
+
+  /* Resetting to default? */
+  if (! value || ! value->string) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+  if (str_number(value->string, &number)) return -1;
+
+  if (default_value && number == PtrToUlong(default_value)) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  if (set_number(key, (TCHAR *) name, number)) return -1;
+
+  return 1;
+}
+
+static int setting_get_number(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  return get_number(key, (TCHAR *) name, &value->numeric, false);
+}
+
+static int setting_set_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  long error;
+
+  /* Resetting to default? */
+  if (! value || ! value->string) {
+    if (default_value) value->string = (TCHAR *) default_value;
+    else {
+      error = RegDeleteValue(key, name);
+      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+      return -1;
+    }
+  }
+  if (default_value && _tcslen((TCHAR *) default_value) && str_equiv(value->string, (TCHAR *) default_value)) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  if (set_expand_string(key, (TCHAR *) name, value->string)) return -1;
+
+  return 1;
+}
+
+static int setting_get_string(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  TCHAR buffer[VALUE_LENGTH];
+
+  if (get_string(key, (TCHAR *) name, (TCHAR *) buffer, (unsigned long) sizeof(buffer), false, false, false)) return -1;
+
+  return value_from_string(name, value, buffer);
+}
+
+static int setting_set_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  unsigned long exitcode;
+  TCHAR *code;
+  TCHAR action_string[ACTION_LEN];
+
+  if (additional) {
+    /* Default action? */
+    if (is_default(additional)) code = 0;
+    else {
+      if (str_number(additional, &exitcode)) return -1;
+      code = (TCHAR *) additional;
+    }
+  }
+
+  HKEY key = open_registry(service_name, name, KEY_WRITE);
+  if (! key) return -1;
+
+  long error;
+  int ret = 1;
+
+  /* Resetting to default? */
+  if (value && value->string) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), value->string);
+  else {
+    if (code) {
+      /* Delete explicit action. */
+      error = RegDeleteValue(key, code);
+      RegCloseKey(key);
+      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, code, service_name, error_string(error));
+      return -1;
+    }
+    else {
+      /* Explicitly keep the default action. */
+      if (default_value) _sntprintf_s(action_string, _countof(action_string), _TRUNCATE, _T("%s"), (TCHAR *) default_value);
+      ret = 0;
+    }
+  }
+
+  /* Validate the string. */
+  for (int i = 0; exit_action_strings[i]; i++) {
+    if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
+      if (default_value && str_equiv(action_string, (TCHAR *) default_value)) ret = 0;
+      if (RegSetValueEx(key, code, 0, REG_SZ, (const unsigned char *) exit_action_strings[i], (unsigned long) (_tcslen(action_string) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
+        print_message(stderr, NSSM_MESSAGE_SETVALUE_FAILED, code, service_name, error_string(GetLastError()));
+        RegCloseKey(key);
+        return -1;
+      }
+
+      RegCloseKey(key);
+      return ret;
+    }
+  }
+
+  print_message(stderr, NSSM_MESSAGE_INVALID_EXIT_ACTION, action_string);
+  for (int i = 0; exit_action_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), exit_action_strings[i]);
+
+  return -1;
+}
+
+static int setting_get_exit_action(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  unsigned long exitcode = 0;
+  unsigned long *code = 0;
+
+  if (additional) {
+    if (! is_default(additional)) {
+      if (str_number(additional, &exitcode)) return -1;
+      code = &exitcode;
+    }
+  }
+
+  TCHAR action_string[ACTION_LEN];
+  bool default_action;
+  if (get_exit_action(service_name, code, action_string, &default_action)) return -1;
+
+  value_from_string(name, value, action_string);
+
+  if (default_action && ! _tcsnicmp((const TCHAR *) action_string, (TCHAR *) default_value, ACTION_LEN)) return 0;
+  return 1;
+}
+
+static inline bool split_hook_name(const TCHAR *hook_name, TCHAR *hook_event, TCHAR *hook_action) {
+  TCHAR *s;
+
+  for (s = (TCHAR *) hook_name; *s; s++) {
+    if (*s == _T('/')) {
+      *s = _T('\0');
+      _sntprintf_s(hook_event, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), hook_name);
+      _sntprintf_s(hook_action, HOOK_NAME_LENGTH, _TRUNCATE, _T("%s"), ++s);
+      return valid_hook_name(hook_event, hook_action, false);
+    }
+  }
+
+  print_message(stderr, NSSM_MESSAGE_INVALID_HOOK_NAME, hook_name);
+  return false;
+}
+
+static int setting_set_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  TCHAR hook_event[HOOK_NAME_LENGTH];
+  TCHAR hook_action[HOOK_NAME_LENGTH];
+  if (! split_hook_name(additional, hook_event, hook_action)) return -1;
+
+  TCHAR *cmd;
+  if (value && value->string) cmd = value->string;
+  else cmd = _T("");
+
+  if (set_hook(service_name, hook_event, hook_action, cmd)) return -1;
+  if (! _tcslen(cmd)) return 0;
+  return 1;
+}
+
+static int setting_get_hook(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  TCHAR hook_event[HOOK_NAME_LENGTH];
+  TCHAR hook_action[HOOK_NAME_LENGTH];
+  if (! split_hook_name(additional, hook_event, hook_action)) return -1;
+
+  TCHAR cmd[CMD_LENGTH];
+  if (get_hook(service_name, hook_event, hook_action, cmd, sizeof(cmd))) return -1;
+
+  value_from_string(name, value, cmd);
+
+  if (! _tcslen(cmd)) return 0;
+  return 1;
+}
+
+static int setting_set_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  long error;
+  __int64 mask;
+  __int64 system_affinity = 0LL;
+
+  if (value && value->string) {
+    DWORD_PTR affinity;
+    if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, (DWORD_PTR *) &system_affinity)) system_affinity = ~0;
+
+    if (is_default(value->string) || str_equiv(value->string, NSSM_AFFINITY_ALL)) mask = 0LL;
+    else if (affinity_string_to_mask(value->string, &mask)) {
+      print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, value->string, num_cpus() - 1);
+      return -1;
+    }
+  }
+  else mask = 0LL;
+
+  if (! mask) {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  /* Canonicalise. */
+  TCHAR *canon = 0;
+  if (affinity_mask_to_string(mask, &canon)) canon = value->string;
+
+  __int64 effective_affinity = mask & system_affinity;
+  if (effective_affinity != mask) {
+    /* Requested CPUs did not intersect with available CPUs? */
+    if (! effective_affinity) mask = effective_affinity = system_affinity;
+
+    TCHAR *system = 0;
+    if (! affinity_mask_to_string(system_affinity, &system)) {
+      TCHAR *effective = 0;
+      if (! affinity_mask_to_string(effective_affinity, &effective)) {
+        print_message(stderr, NSSM_MESSAGE_EFFECTIVE_AFFINITY_MASK, value->string, system, effective);
+        HeapFree(GetProcessHeap(), 0, effective);
+      }
+      HeapFree(GetProcessHeap(), 0, system);
+    }
+  }
+
+  if (RegSetValueEx(key, name, 0, REG_SZ, (const unsigned char *) canon, (unsigned long) (_tcslen(canon) + 1) * sizeof(TCHAR)) != ERROR_SUCCESS) {
+    if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, name, error_string(GetLastError()), 0);
+    return -1;
+  }
+
+  if (canon != value->string) HeapFree(GetProcessHeap(), 0, canon);
+  return 1;
+}
+
+static int setting_get_affinity(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! key) return -1;
+
+  unsigned long type;
+  TCHAR *buffer = 0;
+  unsigned long buflen = 0;
+
+  int ret = RegQueryValueEx(key, name, 0, &type, 0, &buflen);
+  if (ret == ERROR_FILE_NOT_FOUND) {
+    if (value_from_string(name, value, NSSM_AFFINITY_ALL) == 1) return 0;
+    return -1;
+  }
+  if (ret != ERROR_SUCCESS) return -1;
+
+  if (type != REG_SZ) return -1;
+
+  buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, buflen);
+  if (! buffer) {
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("affinity"), _T("setting_get_affinity"));
+    return -1;
+  }
+
+  if (get_string(key, (TCHAR *) name, buffer, buflen, false, false, true)) {
+    HeapFree(GetProcessHeap(), 0, buffer);
+    return -1;
+  }
+
+  __int64 affinity;
+  if (affinity_string_to_mask(buffer, &affinity)) {
+    print_message(stderr, NSSM_MESSAGE_BOGUS_AFFINITY_MASK, buffer, num_cpus() - 1);
+    HeapFree(GetProcessHeap(), 0, buffer);
+    return -1;
+  }
+
+  HeapFree(GetProcessHeap(), 0, buffer);
+
+  /* Canonicalise. */
+  if (affinity_mask_to_string(affinity, &buffer)) {
+    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+    return -1;
+  }
+
+  ret = value_from_string(name, value, buffer);
+  HeapFree(GetProcessHeap(), 0, buffer);
+  return ret;
+}
+
+static int setting_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  if (! value || ! value->string || ! value->string[0]) {
+    long error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  unsigned long envlen = (unsigned long) _tcslen(value->string) + 1;
+  TCHAR *unformatted = 0;
+  unsigned long newlen;
+  if (unformat_double_null(value->string, envlen, &unformatted, &newlen)) return -1;
+
+  if (test_environment(unformatted)) {
+    HeapFree(GetProcessHeap(), 0, unformatted);
+    print_message(stderr, NSSM_GUI_INVALID_ENVIRONMENT);
+    return -1;
+  }
+
+  if (RegSetValueEx(key, name, 0, REG_MULTI_SZ, (const unsigned char *) unformatted, (unsigned long) newlen * sizeof(TCHAR)) != ERROR_SUCCESS) {
+    if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
+    return -1;
+  }
+
+  if (newlen) HeapFree(GetProcessHeap(), 0, unformatted);
+  return 1;
+}
+
+static int setting_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  TCHAR *env = 0;
+  unsigned long envlen;
+  if (get_environment((TCHAR *) service_name, key, (TCHAR *) name, &env, &envlen)) return -1;
+  if (! envlen) return 0;
+
+  TCHAR *formatted;
+  unsigned long newlen;
+  if (format_double_null(env, envlen, &formatted, &newlen)) return -1;
+
+  int ret;
+  if (additional) {
+    /* Find named environment variable. */
+    TCHAR *s;
+    size_t len = _tcslen(additional);
+    for (s = env; *s; s++) {
+      /* Look for <additional>=<string> NULL NULL */
+      if (! _tcsnicmp(s, additional, len) && s[len] == _T('=')) {
+        /* Strip <key>= */
+        s += len + 1;
+        ret = value_from_string(name, value, s);
+        HeapFree(GetProcessHeap(), 0, env);
+        return ret;
+      }
+
+      /* Skip this string. */
+      for ( ; *s; s++);
+    }
+    HeapFree(GetProcessHeap(), 0, env);
+    return 0;
+  }
+
+  HeapFree(GetProcessHeap(), 0, env);
+
+  ret = value_from_string(name, value, formatted);
+  if (newlen) HeapFree(GetProcessHeap(), 0, formatted);
+  return ret;
+}
+
+static int setting_set_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  TCHAR *priority_string;
+  int i;
+  long error;
+
+  if (value && value->string) priority_string = value->string;
+  else if (default_value) priority_string = (TCHAR *) default_value;
+  else {
+    error = RegDeleteValue(key, name);
+    if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+    print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+    return -1;
+  }
+
+  for (i = 0; priority_strings[i]; i++) {
+    if (! str_equiv(priority_strings[i], priority_string)) continue;
+
+    if (default_value && str_equiv(priority_string, (TCHAR *) default_value)) {
+      error = RegDeleteValue(key, name);
+      if (error == ERROR_SUCCESS || error == ERROR_FILE_NOT_FOUND) return 0;
+      print_message(stderr, NSSM_MESSAGE_REGDELETEVALUE_FAILED, name, service_name, error_string(error));
+      return -1;
+    }
+
+    if (set_number(key, (TCHAR *) name, priority_index_to_constant(i))) return -1;
+    return 1;
+  }
+
+  print_message(stderr, NSSM_MESSAGE_INVALID_PRIORITY, priority_string);
+  for (i = 0; priority_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), priority_strings[i]);
+
+  return -1;
+}
+
+static int setting_get_priority(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = (HKEY) param;
+  if (! param) return -1;
+
+  unsigned long constant;
+  switch (get_number(key, (TCHAR *) name, &constant, false)) {
+    case 0: return value_from_string(name, value, (const TCHAR *) default_value);
+    case -1: return -1;
+  }
+
+  return value_from_string(name, value, priority_strings[priority_constant_to_index(constant)]);
+}
+
+/* Functions to manage native service settings. */
+static int native_set_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /*
+    Get existing service dependencies because we must set both types together.
+  */
+  TCHAR *buffer;
+  unsigned long buflen;
+  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
+
+  if (! value || ! value->string || ! value->string[0]) {
+    if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
+      print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+    return 0;
+  }
+
+  unsigned long len = (unsigned long) _tcslen(value->string) + 1;
+  TCHAR *unformatted = 0;
+  unsigned long newlen;
+  if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
+    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+    return -1;
+  }
+
+  /* Prepend group identifier. */
+  unsigned long missing = 0;
+  TCHAR *canon = unformatted;
+  size_t canonlen = 0;
+  TCHAR *s;
+  for (s = unformatted; *s; s++) {
+    if (*s != SC_GROUP_IDENTIFIER) missing++;
+    size_t len = _tcslen(s);
+    canonlen += len + 1;
+    s += len;
+  }
+
+  if (missing) {
+    /* Missing identifiers plus double NULL terminator. */
+    canonlen += missing + 1;
+    newlen = (unsigned long) canonlen;
+
+    canon = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, canonlen * sizeof(TCHAR));
+    if (! canon) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("native_set_dependongroup"));
+      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
+      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    size_t i = 0;
+    for (s = unformatted; *s; s++) {
+      if (*s != SC_GROUP_IDENTIFIER) canon[i++] = SC_GROUP_IDENTIFIER;
+      size_t len = _tcslen(s);
+      memmove(canon + i, s, (len + 1) * sizeof(TCHAR));
+      i += len + 1;
+      s += len;
+    }
+  }
+
+  TCHAR *dependencies;
+  if (buflen > 2) {
+    dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
+    if (! dependencies) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependongroup"));
+      if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
+      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
+      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    memmove(dependencies, buffer, buflen * sizeof(TCHAR));
+    memmove(dependencies + buflen - 1, canon, newlen * sizeof(TCHAR));
+  }
+  else dependencies = canon;
+
+  int ret = 1;
+  if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
+  if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
+  if (canon != unformatted) HeapFree(GetProcessHeap(), 0, canon);
+  if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
+  if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+
+  return ret;
+}
+
+static int native_get_dependongroup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *buffer;
+  unsigned long buflen;
+  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
+
+  int ret;
+  if (buflen) {
+    TCHAR *formatted;
+    unsigned long newlen;
+    if (format_double_null(buffer, buflen, &formatted, &newlen)) {
+      HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    ret = value_from_string(name, value, formatted);
+    HeapFree(GetProcessHeap(), 0, formatted);
+    HeapFree(GetProcessHeap(), 0, buffer);
+  }
+  else {
+    value->string = 0;
+    ret = 0;
+  }
+
+  return ret;
+}
+
+static int native_set_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /*
+    Get existing group dependencies because we must set both types together.
+  */
+  TCHAR *buffer;
+  unsigned long buflen;
+  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_GROUPS)) return -1;
+
+  if (! value || ! value->string || ! value->string[0]) {
+    if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, buffer, 0, 0, 0)) {
+      print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+    return 0;
+  }
+
+  unsigned long len = (unsigned long) _tcslen(value->string) + 1;
+  TCHAR *unformatted = 0;
+  unsigned long newlen;
+  if (unformat_double_null(value->string, len, &unformatted, &newlen)) {
+    if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+    return -1;
+  }
+
+  TCHAR *dependencies;
+  if (buflen > 2) {
+    dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (newlen + buflen) * sizeof(TCHAR));
+    if (! dependencies) {
+      print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("dependencies"), _T("native_set_dependonservice"));
+      if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
+      if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    memmove(dependencies, buffer, buflen * sizeof(TCHAR));
+    memmove(dependencies + buflen - 1, unformatted, newlen * sizeof(TCHAR));
+  }
+  else dependencies = unformatted;
+
+  int ret = 1;
+  if (set_service_dependencies(service_name, service_handle, dependencies)) ret = -1;
+  if (dependencies != unformatted) HeapFree(GetProcessHeap(), 0, dependencies);
+  if (unformatted) HeapFree(GetProcessHeap(), 0, unformatted);
+  if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
+
+  return ret;
+}
+
+static int native_get_dependonservice(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *buffer;
+  unsigned long buflen;
+  if (get_service_dependencies(service_name, service_handle, &buffer, &buflen, DEPENDENCY_SERVICES)) return -1;
+
+  int ret;
+  if (buflen) {
+    TCHAR *formatted;
+    unsigned long newlen;
+    if (format_double_null(buffer, buflen, &formatted, &newlen)) {
+      HeapFree(GetProcessHeap(), 0, buffer);
+      return -1;
+    }
+
+    ret = value_from_string(name, value, formatted);
+    HeapFree(GetProcessHeap(), 0, formatted);
+    HeapFree(GetProcessHeap(), 0, buffer);
+  }
+  else {
+    value->string = 0;
+    ret = 0;
+  }
+
+  return ret;
+}
+
+int native_set_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *description = 0;
+  if (value) description = value->string;
+  if (set_service_description(service_name, service_handle, description)) return -1;
+
+  if (description && description[0]) return 1;
+
+  return 0;
+}
+
+int native_get_description(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR buffer[VALUE_LENGTH];
+  if (get_service_description(service_name, service_handle, _countof(buffer), buffer)) return -1;
+
+  if (buffer[0]) return value_from_string(name, value, buffer);
+  value->string = 0;
+
+  return 0;
+}
+
+int native_set_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  TCHAR *displayname = 0;
+  if (value && value->string) displayname = value->string;
+  else displayname = (TCHAR *) service_name;
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, displayname)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  /*
+    If the display name and service name differ only in case,
+    ChangeServiceConfig() will return success but the display name will be
+    set to the service name, NOT the value passed to the function.
+    This appears to be a quirk of Windows rather than a bug here.
+  */
+  if (displayname != service_name && ! str_equiv(displayname, service_name)) return 1;
+
+  return 0;
+}
+
+int native_get_displayname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpDisplayName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = open_service_registry(service_name, KEY_SET_VALUE, false);
+  if (! key) return -1;
+
+  int ret = setting_set_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional);
+  RegCloseKey(key);
+  return ret;
+}
+
+int native_get_environment(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  HKEY key = open_service_registry(service_name, KEY_READ, false);
+  if (! key) return -1;
+
+  ZeroMemory(value, sizeof(value_t));
+  int ret = setting_get_environment(service_name, (void *) key, NSSM_NATIVE_ENVIRONMENT, default_value, value, additional);
+  RegCloseKey(key);
+  return ret;
+}
+
+int native_set_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the image path. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, value->string, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  return 1;
+}
+
+int native_get_imagepath(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpBinaryPathName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  print_message(stderr, NSSM_MESSAGE_CANNOT_RENAME_SERVICE);
+  return -1;
+}
+
+int native_get_name(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  return value_from_string(name, value, service_name);
+}
+
+int native_set_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /*
+    Logical syntax is: nssm set <service> ObjectName <username> <password>
+    That means the username is actually passed in the additional parameter.
+  */
+  bool localsystem = false;
+  TCHAR *username = NSSM_LOCALSYSTEM_ACCOUNT;
+  TCHAR *password = 0;
+  if (additional) {
+    username = (TCHAR *) additional;
+    if (value && value->string) password = value->string;
+  }
+  else if (value && value->string) username = value->string;
+
+  const TCHAR *well_known = well_known_username(username);
+  size_t passwordsize = 0;
+  if (well_known) {
+    if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) localsystem = true;
+    username = (TCHAR *) well_known;
+    password = _T("");
+  }
+  else if (! password) {
+    /* We need a password if the account requires it. */
+    print_message(stderr, NSSM_MESSAGE_MISSING_PASSWORD, name);
+    return -1;
+  }
+  else passwordsize = _tcslen(password) * sizeof(TCHAR);
+
+  /*
+    ChangeServiceConfig() will fail to set the username if the service is set
+    to interact with the desktop.
+  */
+  unsigned long type = SERVICE_NO_CHANGE;
+  if (! localsystem) {
+    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+    if (! qsc) {
+      if (passwordsize) SecureZeroMemory(password, passwordsize);
+      return -1;
+    }
+
+    type = qsc->dwServiceType & ~SERVICE_INTERACTIVE_PROCESS;
+    HeapFree(GetProcessHeap(), 0, qsc);
+  }
+
+  if (! well_known) {
+    if (grant_logon_as_service(username)) {
+      if (passwordsize) SecureZeroMemory(password, passwordsize);
+      print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
+      return -1;
+    }
+  }
+
+  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, 0)) {
+    if (passwordsize) SecureZeroMemory(password, passwordsize);
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  if (passwordsize) SecureZeroMemory(password, passwordsize);
+
+  if (localsystem) return 0;
+
+  return 1;
+}
+
+int native_get_objectname(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  int ret = value_from_string(name, value, qsc->lpServiceStartName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  return ret;
+}
+
+int native_set_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the startup type. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  /* Map NSSM_STARTUP_* constant to Windows SERVICE_*_START constant. */
+  int service_startup = -1;
+  int i;
+  for (i = 0; startup_strings[i]; i++) {
+    if (str_equiv(value->string, startup_strings[i])) {
+      service_startup = i;
+      break;
+    }
+  }
+
+  if (service_startup < 0) {
+    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_STARTUP, value->string);
+    for (i = 0; startup_strings[i]; i++) _ftprintf(stderr, _T("%s\n"), startup_strings[i]);
+    return -1;
+  }
+
+  unsigned long startup;
+  switch (service_startup) {
+    case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
+    case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
+    default: startup = SERVICE_AUTO_START;
+  }
+
+  if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  SERVICE_DELAYED_AUTO_START_INFO delayed;
+  ZeroMemory(&delayed, sizeof(delayed));
+  if (service_startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
+  else delayed.fDelayedAutostart = 0;
+  if (! ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
+    unsigned long error = GetLastError();
+    /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
+    if (error != ERROR_INVALID_LEVEL) {
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_MESSAGE_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service_name, error_string(error), 0);
+    }
+  }
+
+  return 1;
+}
+
+int native_get_startup(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  unsigned long startup;
+  int ret = get_service_startup(service_name, service_handle, qsc, &startup);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  if (ret) return -1;
+
+  unsigned long i;
+  for (i = 0; startup_strings[i]; i++);
+  if (startup >= i) return -1;
+
+  return value_from_string(name, value, startup_strings[startup]);
+}
+
+int native_set_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  /* It makes no sense to try to reset the service type. */
+  if (! value || ! value->string) {
+    print_message(stderr, NSSM_MESSAGE_NO_DEFAULT_VALUE, name);
+    return -1;
+  }
+
+  /*
+    We can only manage services of type SERVICE_WIN32_OWN_PROCESS
+    and SERVICE_INTERACTIVE_PROCESS.
+  */
+  unsigned long type = SERVICE_WIN32_OWN_PROCESS;
+  if (str_equiv(value->string, NSSM_INTERACTIVE_PROCESS)) type |= SERVICE_INTERACTIVE_PROCESS;
+  else if (! str_equiv(value->string, NSSM_WIN32_OWN_PROCESS)) {
+    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE_TYPE, value->string);
+    _ftprintf(stderr, _T("%s\n"), NSSM_WIN32_OWN_PROCESS);
+    _ftprintf(stderr, _T("%s\n"), NSSM_INTERACTIVE_PROCESS);
+    return -1;
+  }
+
+  /*
+    ChangeServiceConfig() will fail if the service runs under an account
+    other than LOCALSYSTEM and we try to make it interactive.
+  */
+  if (type & SERVICE_INTERACTIVE_PROCESS) {
+    QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+    if (! qsc) return -1;
+
+    if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
+      HeapFree(GetProcessHeap(), 0, qsc);
+      print_message(stderr, NSSM_MESSAGE_INTERACTIVE_NOT_LOCALSYSTEM, value->string, service_name, NSSM_LOCALSYSTEM_ACCOUNT);
+      return -1;
+    }
+
+    HeapFree(GetProcessHeap(), 0, qsc);
+  }
+
+  if (! ChangeServiceConfig(service_handle, type, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, 0, 0, 0, 0)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
+    return -1;
+  }
+
+  return 1;
+}
+
+int native_get_type(const TCHAR *service_name, void *param, const TCHAR *name, void *default_value, value_t *value, const TCHAR *additional) {
+  SC_HANDLE service_handle = (SC_HANDLE) param;
+  if (! service_handle) return -1;
+
+  QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);
+  if (! qsc) return -1;
+
+  value->numeric = qsc->dwServiceType;
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  const TCHAR *string;
+  switch (value->numeric) {
+    case SERVICE_KERNEL_DRIVER: string = NSSM_KERNEL_DRIVER; break;
+    case SERVICE_FILE_SYSTEM_DRIVER: string = NSSM_FILE_SYSTEM_DRIVER; break;
+    case SERVICE_WIN32_OWN_PROCESS: string = NSSM_WIN32_OWN_PROCESS; break;
+    case SERVICE_WIN32_SHARE_PROCESS: string = NSSM_WIN32_SHARE_PROCESS; break;
+    case SERVICE_WIN32_OWN_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_INTERACTIVE_PROCESS; break;
+    case SERVICE_WIN32_SHARE_PROCESS|SERVICE_INTERACTIVE_PROCESS: string = NSSM_SHARE_INTERACTIVE_PROCESS; break;
+    default: string = NSSM_UNKNOWN;
+  }
+
+  return value_from_string(name, value, string);
+}
+
+int set_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! key) return -1;
+  int ret;
+
+  if (setting->set) ret = setting->set(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+  else ret = -1;
+
+  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
+  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
+  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+int set_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! service_handle) return -1;
+
+  int ret;
+  if (setting->set) ret = setting->set(service_name, service_handle, setting->name, setting->default_value, value, additional);
+  else ret = -1;
+
+  if (! ret) print_message(stdout, NSSM_MESSAGE_RESET_SETTING, setting->name, service_name);
+  else if (ret > 0) print_message(stdout, NSSM_MESSAGE_SET_SETTING, setting->name, service_name);
+  else print_message(stderr, NSSM_MESSAGE_SET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+/*
+  Returns:  1 if the value was retrieved.
+            0 if the default value was retrieved.
+           -1 on error.
+*/
+int get_setting(const TCHAR *service_name, HKEY key, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! key) return -1;
+  int ret;
+
+  switch (setting->type) {
+    case REG_EXPAND_SZ:
+    case REG_MULTI_SZ:
+    case REG_SZ:
+      value->string = (TCHAR *) setting->default_value;
+      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+      else ret = -1;
+      break;
+
+    case REG_DWORD:
+      value->numeric = PtrToUlong(setting->default_value);
+      if (setting->get) ret = setting->get(service_name, (void *) key, setting->name, setting->default_value, value, additional);
+      else ret = -1;
+      break;
+
+    default:
+      ret = -1;
+      break;
+  }
+
+  if (ret < 0) print_message(stderr, NSSM_MESSAGE_GET_SETTING_FAILED, setting->name, service_name);
+
+  return ret;
+}
+
+int get_setting(const TCHAR *service_name, SC_HANDLE service_handle, settings_t *setting, value_t *value, const TCHAR *additional) {
+  if (! service_handle) return -1;
+  return setting->get(service_name, service_handle, setting->name, 0, value, additional);
+}
+
+settings_t settings[] = {
+  { NSSM_REG_EXE, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_FLAGS, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_DIR, REG_EXPAND_SZ, (void *) _T(""), false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_EXIT, REG_SZ, (void *) exit_action_strings[NSSM_EXIT_RESTART], false, ADDITIONAL_MANDATORY, setting_set_exit_action, setting_get_exit_action },
+  { NSSM_REG_HOOK, REG_SZ, (void *) _T(""), false, ADDITIONAL_MANDATORY, setting_set_hook, setting_get_hook },
+  { NSSM_REG_AFFINITY, REG_SZ, 0, false, 0, setting_set_affinity, setting_get_affinity },
+  { NSSM_REG_ENV, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
+  { NSSM_REG_ENV_EXTRA, REG_MULTI_SZ, NULL, false, ADDITIONAL_CRLF, setting_set_environment, setting_get_environment },
+  { NSSM_REG_NO_CONSOLE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_PRIORITY, REG_SZ, (void *) priority_strings[NSSM_NORMAL_PRIORITY], false, 0, setting_set_priority, setting_get_priority },
+  { NSSM_REG_RESTART_DELAY, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDIN, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDIN_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDIN_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDIN NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDIN_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_THREADS_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_THREADS_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_KILL_PROCESS_TREE, REG_DWORD, (void *) 1, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_THROTTLE, REG_DWORD, (void *) NSSM_RESET_THROTTLE_RESTART, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_ONLINE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+  { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number },
+  { NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup },
+  { NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice },
+  { NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },
+  { NSSM_NATIVE_DISPLAYNAME, REG_SZ, NULL, true, 0, native_set_displayname, native_get_displayname },
+  { NSSM_NATIVE_ENVIRONMENT, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_environment, native_get_environment },
+  { NSSM_NATIVE_IMAGEPATH, REG_EXPAND_SZ, NULL, true, 0, native_set_imagepath, native_get_imagepath },
+  { NSSM_NATIVE_OBJECTNAME, REG_SZ, NSSM_LOCALSYSTEM_ACCOUNT, true, 0, native_set_objectname, native_get_objectname },
+  { NSSM_NATIVE_NAME, REG_SZ, NULL, true, 0, native_set_name, native_get_name },
+  { NSSM_NATIVE_STARTUP, REG_SZ, NULL, true, 0, native_set_startup, native_get_startup },
+  { NSSM_NATIVE_TYPE, REG_SZ, NULL, true, 0, native_set_type, native_get_type },
+  { NULL, NULL, NULL, NULL, NULL }
+};

+ 48 - 48
settings.h

@@ -1,48 +1,48 @@
-#ifndef SETTINGS_H
-#define SETTINGS_H
-
-#define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup")
-#define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService")
-#define NSSM_NATIVE_DESCRIPTION _T("Description")
-#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName")
-#define NSSM_NATIVE_ENVIRONMENT _T("Environment")
-#define NSSM_NATIVE_IMAGEPATH _T("ImagePath")
-#define NSSM_NATIVE_NAME _T("Name")
-#define NSSM_NATIVE_OBJECTNAME _T("ObjectName")
-#define NSSM_NATIVE_STARTUP _T("Start")
-#define NSSM_NATIVE_TYPE _T("Type")
-
-/* Are additional arguments needed? */
-#define ADDITIONAL_GETTING (1 << 0)
-#define ADDITIONAL_SETTING (1 << 1)
-#define ADDITIONAL_RESETTING (1 << 2)
-#define ADDITIONAL_CRLF (1 << 3)
-#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING
-
-#define DEPENDENCY_SERVICES (1 << 0)
-#define DEPENDENCY_GROUPS (1 << 1)
-#define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS)
-
-typedef union {
-  unsigned long numeric;
-  TCHAR *string;
-} value_t;
-
-typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *);
-
-typedef struct {
-  const TCHAR *name;
-  unsigned long type;
-  void *default_value;
-  bool native;
-  int additional;
-  setting_function_t set;
-  setting_function_t get;
-} settings_t;
-
-int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
-int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
-int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
-int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
-
-#endif
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#define NSSM_NATIVE_DEPENDONGROUP _T("DependOnGroup")
+#define NSSM_NATIVE_DEPENDONSERVICE _T("DependOnService")
+#define NSSM_NATIVE_DESCRIPTION _T("Description")
+#define NSSM_NATIVE_DISPLAYNAME _T("DisplayName")
+#define NSSM_NATIVE_ENVIRONMENT _T("Environment")
+#define NSSM_NATIVE_IMAGEPATH _T("ImagePath")
+#define NSSM_NATIVE_NAME _T("Name")
+#define NSSM_NATIVE_OBJECTNAME _T("ObjectName")
+#define NSSM_NATIVE_STARTUP _T("Start")
+#define NSSM_NATIVE_TYPE _T("Type")
+
+/* Are additional arguments needed? */
+#define ADDITIONAL_GETTING (1 << 0)
+#define ADDITIONAL_SETTING (1 << 1)
+#define ADDITIONAL_RESETTING (1 << 2)
+#define ADDITIONAL_CRLF (1 << 3)
+#define ADDITIONAL_MANDATORY ADDITIONAL_GETTING|ADDITIONAL_SETTING|ADDITIONAL_RESETTING
+
+#define DEPENDENCY_SERVICES (1 << 0)
+#define DEPENDENCY_GROUPS (1 << 1)
+#define DEPENDENCY_ALL (DEPENDENCY_SERVICES|DEPENDENCY_GROUPS)
+
+typedef union {
+  unsigned long numeric;
+  TCHAR *string;
+} value_t;
+
+typedef int (*setting_function_t)(const TCHAR *, void *, const TCHAR *, void *, value_t *, const TCHAR *);
+
+typedef struct {
+  const TCHAR *name;
+  unsigned long type;
+  void *default_value;
+  bool native;
+  int additional;
+  setting_function_t set;
+  setting_function_t get;
+} settings_t;
+
+int set_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
+int set_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
+int get_setting(const TCHAR *, HKEY, settings_t *, value_t *, const TCHAR *);
+int get_setting(const TCHAR *, SC_HANDLE, settings_t *, value_t *, const TCHAR *);
+
+#endif