Browse Source

Allow editing services.

"nssm edit <service>" brings up a GUI to edit an existing service.
Services not managed by NSSM can also be edited but only system
properties such as the display name will appear in the GUI.
Iain Patterson 8 years ago
parent
commit
2c60e5334f
13 changed files with 945 additions and 198 deletions
  1. 2 0
      ChangeLog.txt
  2. 17 0
      README.txt
  3. 353 142
      gui.cpp
  4. 2 0
      gui.h
  5. 193 0
      messages.mc
  6. 8 1
      nssm.cpp
  7. 1 0
      nssm.h
  8. 69 0
      nssm.rc
  9. 46 10
      registry.cpp
  10. 2 2
      registry.h
  11. 11 9
      resource.h
  12. 235 34
      service.cpp
  13. 6 0
      service.h

+ 2 - 0
ChangeLog.txt

@@ -1,5 +1,7 @@
 Changes since 2.21
 ------------------
+  * Existing services can now be edited using the GUI.
+
   * NSSM can now optionally rotate existing files when
     redirecting I/O.
 

+ 17 - 0
README.txt

@@ -58,6 +58,8 @@ Since version 2.22, NSSM can rotate existing output files when redirecting I/O.
 Since version 2.22, NSSM can set service display name, description, startup
 type and log on details.
 
+Since version 2.22, NSSM can edit existing services with the GUI.
+
 
 Usage
 -----
@@ -281,6 +283,21 @@ omit the VALUE but the = symbol is mandatory.
 srvany only supports AppEnvironment.
 
 
+Managing services using the GUI
+-------------------------------
+NSSM can edit the settings of existing services with the same GUI that is
+used to install them.  Run
+
+    nssm edit <servicename>
+
+to bring up the GUI.
+
+NSSM offers limited editing capabilities for services other than those which
+run NSSM itself.  When NSSM is asked to edit a service which does not have
+the App* registry settings described above, the GUI will allow editing only
+system settings such as the service display name and description.
+
+
 Removing services using the GUI
 -------------------------------
 NSSM can also remove services.  Run

+ 353 - 142
gui.cpp

@@ -12,6 +12,9 @@ int nssm_gui(int resource, nssm_service_t *service) {
     return 1;
   }
 
+  /* Remember what the window is for. */
+  SetWindowLongPtr(dlg, GWLP_USERDATA, (LONG_PTR) resource);
+
   /* Display the window */
   centre_window(dlg);
   ShowWindow(dlg, SW_SHOW);
@@ -29,6 +32,126 @@ int nssm_gui(int resource, nssm_service_t *service) {
     }
   }
 
+  if (resource == IDD_EDIT) {
+    /* We'll need the service handle later. */
+    SetWindowLongPtr(dlg, DWLP_USER, (LONG_PTR) service);
+
+    /* Service name can't be edited. */
+    EnableWindow(GetDlgItem(dlg, IDC_NAME), 0);
+    SetFocus(GetDlgItem(dlg, IDOK));
+
+    /* Set existing details. */
+    HWND combo;
+
+    /* Application tab. */
+    if (service->native) SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->image);
+    else SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe);
+    SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_DIR, service->dir);
+    SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS, service->flags);
+
+    /* Details tab. */
+    SetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname);
+    SetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description);
+    combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);
+    SendMessage(combo, CB_SETCURSEL, service->startup, 0);
+
+    /* Log on tab. */
+    if (service->username) {
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_ACCOUNT);
+      SetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_INTERACT), 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), 1);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), 1);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), 1);
+    }
+    else {
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);
+      if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0);
+    }
+
+    /* Shutdown tab. */
+    if (! (service->stop_method & NSSM_STOP_METHOD_CONSOLE)) {
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE), 0);
+    }
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, service->kill_console_delay, 0);
+    if (! (service->stop_method & NSSM_STOP_METHOD_WINDOW)) {
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_UNCHECKED, 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW), 0);
+    }
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, service->kill_window_delay, 0);
+    if (! (service->stop_method & NSSM_STOP_METHOD_THREADS)) {
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_UNCHECKED, 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS), 0);
+    }
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, service->kill_threads_delay, 0);
+    if (! (service->stop_method & NSSM_STOP_METHOD_TERMINATE)) {
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_UNCHECKED, 0);
+    }
+
+    /* Restart tab. */
+    SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, service->throttle_delay, 0);
+    combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
+    SendMessage(combo, CB_SETCURSEL, service->default_exit_action, 0);
+
+    /* I/O tab. */
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path);
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDOUT, service->stdout_path);
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDERR, service->stderr_path);
+
+    /* Rotation tab. */
+    if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0);
+    if (service->rotate_files) {
+      SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, BST_CHECKED, 0);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), 1);
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), 1);
+    }
+    SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0);
+    if (! service->rotate_bytes_high) SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0);
+
+    /* Check if advanced settings are in use. */
+    if (service->stdout_disposition ^ service->stderr_disposition || service->stdout_disposition & ~CREATE_ALWAYS || service->stderr_disposition & ~CREATE_ALWAYS) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_STDIO);
+    if (service->rotate_bytes_high) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES);
+
+    /* Environment tab. */
+    TCHAR *env;
+    unsigned long envlen;
+    if (service->env_extralen) {
+      SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_SETCHECK, BST_CHECKED, 0);
+      env = service->env_extra;
+      envlen = service->env_extralen;
+    }
+    else {
+      env = service->env;
+      envlen = service->envlen;
+    }
+
+    if (envlen) {
+      /* Replace NULL with CRLF. Leave NULL NULL as the end marker. */
+      unsigned long i, j;
+      unsigned long newlen = envlen;
+      for (i = 0; i < envlen; i++) if (! env[i] && env[i + 1]) newlen++;
+
+      TCHAR *formatted = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen * sizeof(TCHAR));
+      if (formatted) {
+        for (i = 0, j = 0; i < envlen; i++) {
+          formatted[j] = env[i];
+          if (! env[i]) {
+            if (env[i + 1]) {
+              formatted[j] = _T('\r');
+              formatted[++j] = _T('\n');
+            }
+          }
+          j++;
+        }
+        SetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, formatted);
+        HeapFree(GetProcessHeap(), 0, formatted);
+      }
+      else popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("nssm_dlg()"));
+    }
+    if (service->envlen && service->env_extralen) popup_message(MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ENVIRONMENT);
+  }
+
   /* Go! */
   MSG message;
   while (GetMessage(&message, 0, 0, 0)) {
@@ -99,22 +222,26 @@ static inline void check_io(TCHAR *name, TCHAR *buffer, unsigned long len, unsig
   ZeroMemory(buffer, len * sizeof(TCHAR));
 }
 
-/* Install the service. */
-int install(HWND window) {
-  if (! window) return 1;
+/* Set service parameters. */
+int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service) {
+  if (! service) return 1;
 
-  nssm_service_t *service = alloc_nssm_service();
-  if (service) {
-    set_nssm_service_defaults(service);
+  set_nssm_service_defaults(service);
 
-    /* Get service name. */
-    if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) {
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
-      cleanup_nssm_service(service);
-      return 2;
-    }
+  if (orig_service) {
+    service->native = orig_service->native;
+    service->handle = orig_service->handle;
+  }
 
-    /* Get executable name */
+  /* Get service name. */
+  if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) {
+    popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
+    cleanup_nssm_service(service);
+    return 2;
+  }
+
+  /* Get executable name */
+  if (! service->native) {
     if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe, _countof(service->exe))) {
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);
       return 3;
@@ -133,54 +260,74 @@ int install(HWND window) {
         return 4;
       }
     }
+  }
 
-    /* Get details. */
-    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME), WM_GETTEXTLENGTH, 0, 0)) {
-      if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname, _countof(service->displayname))) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DISPLAYNAME);
-        return 5;
-      }
+  /* Get details. */
+  if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME), WM_GETTEXTLENGTH, 0, 0)) {
+    if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname, _countof(service->displayname))) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DISPLAYNAME);
+      return 5;
     }
+  }
 
-    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION), WM_GETTEXTLENGTH, 0, 0)) {
-      if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description, _countof(service->description))) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DESCRIPTION);
-        return 5;
-      }
+  if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION), WM_GETTEXTLENGTH, 0, 0)) {
+    if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description, _countof(service->description))) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DESCRIPTION);
+      return 5;
     }
+  }
 
-    HWND combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);
-    service->startup = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
-    if (service->startup == CB_ERR) service->startup = 0;
+  HWND combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);
+  service->startup = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
+  if (service->startup == CB_ERR) service->startup = 0;
 
-    /* Get logon stuff. */
-    if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, BM_GETCHECK, 0, 0) & BST_CHECKED) {
-      if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_GETCHECK, 0, 0) & BST_CHECKED) {
-        service->type |= SERVICE_INTERACTIVE_PROCESS;
-      }
+  /* Get logon stuff. */
+  if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+    if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+      service->type |= SERVICE_INTERACTIVE_PROCESS;
     }
-    else {
-      /* Username. */
-      service->usernamelen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0);
-      if (! service->usernamelen) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_USERNAME);
-        return 6;
-      }
-      service->usernamelen++;
+    if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
+    service->username = 0;
+    service->usernamelen = 0;
+    if (service->password) {
+      SecureZeroMemory(service->password, service->passwordlen);
+      HeapFree(GetProcessHeap(), 0, service->password);
+    }
+    service->password = 0;
+    service->passwordlen = 0;
+  }
+  else {
+    /* Username. */
+    service->usernamelen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0);
+    if (! service->usernamelen) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_USERNAME);
+      return 6;
+    }
+    service->usernamelen++;
 
-      service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));
-      if (! service->username) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));
-        return 6;
-      }
-      if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username, (int) service->usernamelen)) {
-        HeapFree(GetProcessHeap(), 0, service->username);
-        service->username = 0;
-        service->usernamelen = 0;
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_USERNAME);
-        return 6;
-      }
+    service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));
+    if (! service->username) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));
+      return 6;
+    }
+    if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username, (int) service->usernamelen)) {
+      HeapFree(GetProcessHeap(), 0, service->username);
+      service->username = 0;
+      service->usernamelen = 0;
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_USERNAME);
+      return 6;
+    }
 
+    /*
+      Special case LOCALSYSTEM.
+      Ignore the password if we're editing and the username hasn't changed.
+    */
+    if (str_equiv(service->username, NSSM_LOCALSYSTEM_ACCOUNT)) {
+      HeapFree(GetProcessHeap(), 0, service->username);
+      service->username = 0;
+      service->usernamelen = 0;
+    }
+    else if (! orig_service || ! orig_service->username || ! str_equiv(service->username, orig_service->username)) {
       /* Password. */
       service->passwordlen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), WM_GETTEXTLENGTH, 0, 0);
       if (! service->passwordlen) {
@@ -258,100 +405,116 @@ int install(HWND window) {
         return 6;
       }
     }
+  }
 
-    /* Get stop method stuff. */
-    check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);
-    check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);
-    check_stop_method(service, NSSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS);
-    check_stop_method(service, NSSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE);
-    check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);
-    check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);
-    check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);
-
-    /* Get exit action stuff. */
-    check_number(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);
-    combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
-    service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
-    if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;
+  /* Remaining tabs are only for services we manage. */
+  if (service->native) return 0;
+
+  /* Get stop method stuff. */
+  check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);
+  check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);
+  check_stop_method(service, NSSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS);
+  check_stop_method(service, NSSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE);
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);
+
+  /* Get exit action stuff. */
+  check_number(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);
+  combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);
+  service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);
+  if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;
+
+  /* Get I/O stuff. */
+  check_io(_T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);
+  check_io(_T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);
+  check_io(_T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR);
+
+  /* Override stdout and/or stderr. */
+  if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+    if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS;
+    if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS;
+  }
 
-    /* Get I/O stuff. */
-    check_io(_T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);
-    check_io(_T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);
-    check_io(_T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR);
+  /* Get rotation stuff. */
+  if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+    service->rotate_files = true;
+    check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);
+    check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);
+  }
 
-    /* Override stdout and/or stderr. */
-    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
-      if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS;
-      if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS;
+  /* Get environment. */
+  unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);
+  if (envlen) {
+    TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (envlen + 2) * sizeof(TCHAR));
+    if (! env) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));
+      cleanup_nssm_service(service);
+      return 5;
     }
 
-    /* Get rotation stuff. */
-    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
-      service->rotate_files = true;
-      check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);
-      check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);
+    if (! GetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, env, envlen + 1)) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);
+      HeapFree(GetProcessHeap(), 0, env);
+      cleanup_nssm_service(service);
+      return 5;
     }
 
-    /* Get environment. */
-    unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);
-    if (envlen) {
-      TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (envlen + 2) * sizeof(TCHAR));
-      if (! env) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));
-        cleanup_nssm_service(service);
-        return 5;
-      }
+    /* Strip CR and replace LF with NULL. */
+    unsigned long newlen = 0;
+    unsigned long i, j;
+    for (i = 0; i < envlen; i++) if (env[i] != _T('\r')) newlen++;
+    /* Must end with two NULLs. */
+    newlen += 2;
 
-      if (! GetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, env, envlen + 1)) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);
-        HeapFree(GetProcessHeap(), 0, env);
-        cleanup_nssm_service(service);
-        return 5;
-      }
+    TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen * sizeof(TCHAR));
+    if (! newenv) {
+      HeapFree(GetProcessHeap(), 0, env);
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));
+      cleanup_nssm_service(service);
+      return 5;
+    }
 
-      /* Strip CR and replace LF with NULL. */
-      unsigned long newlen = 0;
-      unsigned long i, j;
-      for (i = 0; i < envlen; i++) if (env[i] != _T('\r')) newlen++;
-      /* Must end with two NULLs. */
-      newlen += 2;
-
-      TCHAR *newenv = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newlen * sizeof(TCHAR));
-      if (! newenv) {
-        HeapFree(GetProcessHeap(), 0, env);
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));
-        cleanup_nssm_service(service);
-        return 5;
-      }
+    for (i = 0, j = 0; i < envlen; i++) {
+      if (env[i] == _T('\r')) continue;
+      if (env[i] == _T('\n')) newenv[j] = _T('\0');
+      else newenv[j] = env[i];
+      j++;
+    }
 
-      for (i = 0, j = 0; i < envlen; i++) {
-        if (env[i] == _T('\r')) continue;
-        if (env[i] == _T('\n')) newenv[j] = _T('\0');
-        else newenv[j] = env[i];
-        j++;
-      }
+    HeapFree(GetProcessHeap(), 0, env);
+    env = newenv;
+    envlen = newlen;
 
+    /* Test the environment is valid. */
+    if (test_environment(env)) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);
       HeapFree(GetProcessHeap(), 0, env);
-      env = newenv;
-      envlen = newlen;
-
-      /* Test the environment is valid. */
-      if (test_environment(env)) {
-        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);
-        HeapFree(GetProcessHeap(), 0, env);
-        cleanup_nssm_service(service);
-        return 5;
-      }
+      cleanup_nssm_service(service);
+      return 5;
+    }
 
-      if (SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
-        service->env = env;
-        service->envlen = envlen;
-      }
-      else {
-        service->env_extra = env;
-        service->env_extralen = envlen;
-      }
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_GETCHECK, 0, 0) & BST_CHECKED) {
+      service->env = env;
+      service->envlen = envlen;
     }
+    else {
+      service->env_extra = env;
+      service->env_extralen = envlen;
+    }
+  }
+
+  return 0;
+}
+
+/* Install the service. */
+int install(HWND window) {
+  if (! window) return 1;
+
+  nssm_service_t *service = alloc_nssm_service();
+  if (service) {
+    int ret = configure(window, service, 0);
+    if (ret) return ret;
   }
 
   /* See if it works. */
@@ -440,6 +603,42 @@ int remove(HWND window) {
   return 0;
 }
 
+int edit(HWND window, nssm_service_t *orig_service) {
+  if (! window) return 1;
+
+  nssm_service_t *service = alloc_nssm_service();
+  if (service) {
+    int ret = configure(window, service, orig_service);
+    if (ret) return ret;
+  }
+
+  switch (edit_service(service, true)) {
+    case 1:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("edit()"));
+      cleanup_nssm_service(service);
+      return 1;
+
+    case 3:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
+      cleanup_nssm_service(service);
+      return 3;
+
+    case 4:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);
+      cleanup_nssm_service(service);
+      return 4;
+
+    case 6:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_EDIT_PARAMETERS_FAILED);
+      cleanup_nssm_service(service);
+      return 6;
+  }
+
+  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_EDITED, service->name);
+  cleanup_nssm_service(service);
+  return 0;
+}
+
 static TCHAR *browse_filter(int message) {
   switch (message) {
     case NSSM_GUI_BROWSE_FILTER_APPLICATIONS: return _T("*.exe;*.bat;*.cmd");
@@ -606,9 +805,13 @@ INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {
 
 /* Install/remove dialogue callback */
 INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
+  nssm_service_t *service;
+
   switch (message) {
     /* Creating the dialogue */
     case WM_INITDIALOG:
+      service = (nssm_service_t *) l;
+
       SetFocus(GetDlgItem(window, IDC_NAME));
 
       HWND tabs;
@@ -621,11 +824,19 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       ZeroMemory(&tab, sizeof(tab));
       tab.mask = TCIF_TEXT;
 
+      selected_tab = 0;
+
       /* Application tab. */
-      tab.pszText = message_string(NSSM_GUI_TAB_APPLICATION);
+      if (service->native) tab.pszText = message_string(NSSM_GUI_TAB_NATIVE);
+      else tab.pszText = message_string(NSSM_GUI_TAB_APPLICATION);
       tab.cchTextMax = (int) _tcslen(tab.pszText);
       SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_APPLICATION, (LPARAM) &tab);
-      tablist[NSSM_TAB_APPLICATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg);
+      if (service->native) {
+        tablist[NSSM_TAB_APPLICATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_NATIVE), window, tab_dlg);
+        EnableWindow(tablist[NSSM_TAB_APPLICATION], 0);
+        EnableWindow(GetDlgItem(tablist[NSSM_TAB_APPLICATION], IDC_PATH), 0);
+      }
+      else tablist[NSSM_TAB_APPLICATION] = CreateDialog(0, MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg);
       ShowWindow(tablist[NSSM_TAB_APPLICATION], SW_SHOW);
 
       /* Details tab. */
@@ -654,6 +865,9 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);
       set_logon_enabled(0);
 
+      /* Remaining tabs are only for services we manage. */
+      if (service->native) return 1;
+
       /* Shutdown tab. */
       tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);
       tab.cchTextMax = (int) _tcslen(tab.pszText);
@@ -712,8 +926,6 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       tablist[NSSM_TAB_ENVIRONMENT] = CreateDialog(0, MAKEINTRESOURCE(IDD_ENVIRONMENT), window, tab_dlg);
       ShowWindow(tablist[NSSM_TAB_ENVIRONMENT], SW_HIDE);
 
-      selected_tab = 0;
-
       return 1;
 
     /* Tab change. */
@@ -732,12 +944,8 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
           selection = (int) SendMessage(tabs, TCM_GETCURSEL, 0, 0);
           if (selection != selected_tab) {
             ShowWindow(tablist[selected_tab], SW_HIDE);
-            /*
-              XXX: Sets focus to the service name which isn't ideal but is
-                   better than leaving it in another tab.
-            */
             ShowWindow(tablist[selection], SW_SHOWDEFAULT);
-            SetFocus(tablist[selection]);
+            SetFocus(GetDlgItem(window, IDOK));
             selected_tab = selection;
           }
           return 1;
@@ -750,7 +958,10 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       switch (LOWORD(w)) {
         /* OK button */
         case IDOK:
-          if (! install(window)) PostQuitMessage(0);
+          if ((int) GetWindowLongPtr(window, GWLP_USERDATA) == IDD_EDIT) {
+            if (! edit(window, (nssm_service_t *) GetWindowLongPtr(window, DWLP_USER))) PostQuitMessage(0);
+          }
+          else if (! install(window)) PostQuitMessage(0);
           break;
 
         /* Cancel button */

+ 2 - 0
gui.h

@@ -8,8 +8,10 @@
 
 int nssm_gui(int, nssm_service_t *);
 void centre_window(HWND);
+int configure(HWND, nssm_service_t *, nssm_service_t *);
 int install(HWND);
 int remove(HWND);
+int edit(HWND, nssm_service_t *);
 void browse(HWND);
 INT_PTR CALLBACK nssm_dlg(HWND, UINT, WPARAM, LPARAM);
 

+ 193 - 0
messages.mc

@@ -21,6 +21,10 @@ To install a service without confirmation:
 
         nssm install <servicename> <app> [<args>]
 
+To show service editing GUI:
+
+        nssm edit <servicename>
+
 To show service removal GUI:
 
         nssm remove [<servicename>]
@@ -85,6 +89,19 @@ Language = Italian
 L'installazione di un servizio richiede privilegi di amministratore.
 .
 
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT
+Severity = Informational
+Language = English
+Administrator access is needed to edit a service.
+.
+Language = French
+Les droits d'administrateur sont requis pour éditer un service.
+.
+Language = Italian
+L'edizione di un servizio richiede privilegi di amministratore.
+.
+
 MessageId = +1
 SymbolicName = NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE
 Severity = Informational
@@ -124,6 +141,67 @@ Language = Italian
 Errore apertura Service Manager!
 .
 
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED
+Severity = Informational
+Language = English
+Error querying service %s!
+QueryServiceConfig(): %s%0
+.
+Language = French
+Error querying service %s!
+QueryServiceConfig(): %s%0
+.
+Language = Italian
+Error querying service %s!
+QueryServiceConfig(): %s%0
+.
+
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED
+Severity = Informational
+Language = English
+Error querying service %s!
+QueryServiceConfig2(%s): %s%0
+.
+Language = French
+Error querying service %s!
+QueryServiceConfig2(%s): %s%0
+.
+Language = Italian
+Error querying service %s!
+QueryServiceConfig2(%s): %s%0
+.
+
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_INVALID_SERVICE
+Severity = Informational
+Language = English
+Service "%s" is not a valid %s service!
+Executable is %s%0
+.
+Language = French
+Service "%s" is not a valid %s service!
+Executable is %s%0
+.
+Language = Italian
+Service "%s" is not a valid %s service!
+Executable is %s%0
+.
+
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_CANNOT_EDIT
+Severity = Informational
+Language = English
+Service "%s" is not a %s service!
+.
+Language = French
+Service "%s" is not a %s service!
+.
+Language = Italian
+Service "%s" is not a %s service!
+.
+
 MessageId = +1
 SymbolicName = NSSM_MESSAGE_PATH_TOO_LONG
 Severity = Informational
@@ -176,6 +254,22 @@ Language = Italian
 Errore creazione servizio!
 .
 
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED
+Severity = Informational
+Language = English
+Error editing service!
+ChangeServiceConfig(): %s%0
+.
+Language = French
+Erreur à l'édition du service!
+ChangeServiceConfig(): %s%0
+.
+Language = Italian
+Errore edizione servizio!
+ChangeServiceConfig(): %s%0
+.
+
 MessageId = +1
 SymbolicName = NSSM_MESSAGE_CREATE_PARAMETERS_FAILED
 Severity = Informational
@@ -241,6 +335,19 @@ Language = Italian
 Servizio "%s" rimosso correttamente!
 .
 
+MessageId = +1
+SymbolicName = NSSM_MESSAGE_SERVICE_EDITED
+Severity = Informational
+Language = English
+Service "%s" edited successfully!
+.
+Language = French
+Le service "%s" a été édité avec succès!
+.
+Language = Italian
+Servizio "%s" edizione correttamente!
+.
+
 MessageId = +1
 SymbolicName = NSSM_GUI_CREATEDIALOG_FAILED
 Severity = Informational
@@ -436,6 +543,19 @@ Impossibile impostare i parametri di avvio per il servizio!
 Eliminazione servizio in corso...
 .
 
+MessageId = +1
+SymbolicName = NSSM_GUI_EDIT_PARAMETERS_FAILED
+Severity = Informational
+Language = English
+Couldn't set startup parameters for the service!
+.
+Language = French
+Impossible de régler les paramètres de démarrage pour le service!
+.
+Language = Italian
+Impossibile impostare i parametri di avvio per il servizio!
+.
+
 MessageId = +1
 SymbolicName = NSSM_GUI_ASK_REMOVE_SERVICE
 Severity = Informational
@@ -549,6 +669,19 @@ Language = Italian
 Applicazione%0
 .
 
+MessageId = +1
+SymbolicName = NSSM_GUI_TAB_NATIVE
+Severity = Informational
+Language = English
+Service%0
+.
+Language = French
+Service%0
+.
+Language = Italian
+Servizio%0
+.
+
 MessageId = +1
 SymbolicName = NSSM_GUI_TAB_DETAILS
 Severity = Informational
@@ -744,6 +877,66 @@ Language = Italian
 Fake crash (pre-Vista)%0
 .
 
+MessageId = +1
+SymbolicName = NSSM_GUI_WARN_STDIO
+Severity = Informational
+Language = English
+The service is configured with I/O redirection settings which cannot be
+represented by this GUI's simplified set of options.  Check the registry
+after editing the service to confirm its I/O redirection settings.
+.
+Language = French
+The service is configured with I/O redirection settings which cannot be
+represented by this GUI's simplified set of options.  Check the registry
+after editing the service to confirm its I/O redirection settings.
+.
+Language = Italian
+The service is configured with I/O redirection settings which cannot be
+represented by this GUI's simplified set of options.  Check the registry
+after editing the service to confirm its I/O redirection settings.
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_WARN_ROTATE_BYTES
+Severity = Informational
+Language = English
+The service is configured with a 64-bit file size threshold for file
+rotation.  This GUI can only display 32-bit settings.  Check the registry
+after editing the service to confirm its file rotation settings.
+.
+Language = French
+The service is configured with a 64-bit file size threshold for file
+rotation.  This GUI can only display 32-bit settings.  Check the registry
+after editing the service to confirm its file rotation settings.
+.
+Language = Italian
+The service is configured with a 64-bit file size threshold for file
+rotation.  This GUI can only display 32-bit settings.  Check the registry
+after editing the service to confirm its file rotation settings.
+.
+
+MessageId = +1
+SymbolicName = NSSM_GUI_WARN_ENVIRONMENT
+Severity = Informational
+Language = English
+The service is configured with a srvany-compatible environment block
+for the application as well as an extra environment block.  This GUI
+can only display one such block.  Editing the service will result in
+one of the environment blocks being deleted.
+.
+Language = French
+The service is configured with a srvany-compatible environment block
+for the application as well as an extra environment block.  This GUI
+can only display one such block.  Editing the service will result in
+one of the environment blocks being deleted.
+.
+Language = Italian
+The service is configured with a srvany-compatible environment block
+for the application as well as an extra environment block.  This GUI
+can only display one such block.  Editing the service will result in
+one of the environment blocks being deleted.
+.
+
 MessageId = 1001
 SymbolicName = NSSM_EVENT_DISPATCHER_FAILED
 Severity = Error

+ 8 - 1
nssm.cpp

@@ -45,7 +45,7 @@ int _tmain(int argc, TCHAR **argv) {
 
   /* Elevate */
   if (argc > 1) {
-    /* Valid commands are install or remove */
+    /* Valid commands are install, edit or remove */
     if (str_equiv(argv[1], _T("install"))) {
       if (! is_admin) {
         print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL);
@@ -53,6 +53,13 @@ int _tmain(int argc, TCHAR **argv) {
       }
       exit(pre_install_service(argc - 2, argv + 2));
     }
+    if (str_equiv(argv[1], _T("edit"))) {
+      if (! is_admin) {
+        print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT);
+        exit(100);
+      }
+      exit(pre_edit_service(argc - 2, argv + 2));
+    }
     if (str_equiv(argv[1], _T("remove"))) {
       if (! is_admin) {
         print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE);

+ 1 - 0
nssm.h

@@ -18,6 +18,7 @@
 
 int str_equiv(const TCHAR *, const TCHAR *);
 void strip_basename(TCHAR *);
+int usage(int);
 
 #define NSSM _T("nssm")
 #define NSSM_VERSION _T("2.21")

+ 69 - 0
nssm.rc

@@ -86,6 +86,19 @@ BEGIN
     EDITTEXT        IDC_NAME,59,7,87,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+IDD_EDIT DIALOG 0, 0, 286, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "NSSM service editor"
+FONT 8, "MS Sans Serif"
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Service name:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Edit service", IDOK, 172, 104, 50, 14
+    PUSHBUTTON      "Cancel", IDCANCEL, 227, 104, 50, 14
+}
+
 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
 IDD_APPLICATION DIALOG 9, 20, 261, 75
 STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
@@ -102,6 +115,16 @@ FONT 8, "MS Sans Serif"
     EDITTEXT        IDC_FLAGS, 70, 48, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
 }
 
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+IDD_NATIVE DIALOG 9, 20, 261, 75
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Service", IDC_STATIC, 7, 7, 251, 68
+    LTEXT           "Image path:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 70, 16, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
 
 
 LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
@@ -308,6 +331,19 @@ BEGIN
     EDITTEXT        IDC_NAME,43,7,90,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
+IDD_EDIT DIALOG 0, 0, 282, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "Edition d'un service NSSM"
+FONT 8, "MS Sans Serif"
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Nom du service:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Editer le service", IDOK, 172, 106, 50, 14
+    PUSHBUTTON      "Anuller", IDCANCEL, 227, 106, 50, 14
+}
+
 LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
 IDD_APPLICATION DIALOG 9, 20, 261, 75
 STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
@@ -324,6 +360,16 @@ FONT 8, "MS Sans Serif"
     EDITTEXT        IDC_FLAGS, 80, 45, 174, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
 }
 
+LANGUAGE LANG_FRENCH, SUBLANG_FRENCH
+IDD_NATIVE DIALOG 9, 20, 261, 75
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Service", IDC_STATIC, 7, 7, 251, 68
+    LTEXT           "Chemin:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 70, 16, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
 
 /////////////////////////////////////////////////////////////////////////////
 //
@@ -433,6 +479,19 @@ BEGIN
     EDITTEXT        IDC_NAME,59,7,87,14,ES_AUTOHSCROLL
 END
 
+LANGUAGE LANG_ITALIAN, SUBLANG_ITALIAN
+IDD_EDIT DIALOG 0, 0, 282, 126
+STYLE DS_MODALFRAME | DS_SETFONT | WS_CAPTION | WS_POPUP | WS_SYSMENU
+CAPTION "NSSM - Edizione Servizio"
+FONT 8, "MS Sans Serif"
+{
+    CONTROL         "", IDC_TAB1, WC_TABCONTROL, 0, 7, 7, 269, 93
+    LTEXT           "Nome servizio:", IDC_STATIC, 7, 106, 52, 8, SS_LEFT
+    EDITTEXT        IDC_NAME, 64, 104, 98, 12, ES_AUTOHSCROLL
+    DEFPUSHBUTTON   "Edita servizio", IDOK, 172, 106, 50, 14
+    PUSHBUTTON      "Anulla", IDCANCEL, 227, 106, 50, 14
+}
+
 LANGUAGE LANG_ITALIAN, SUBLANG_ITALIAN
 IDD_APPLICATION DIALOG 9, 20, 261, 75
 STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
@@ -449,6 +508,16 @@ FONT 8, "MS Sans Serif"
     EDITTEXT        IDC_FLAGS, 70, 46, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
 }
 
+LANGUAGE LANG_ITALIAN, SUBLANG_ITALIAN
+IDD_NATIVE DIALOG 9, 20, 261, 75
+STYLE DS_SHELLFONT | WS_VISIBLE | WS_CHILD | DS_CONTROL
+FONT 8, "MS Sans Serif"
+{
+    GROUPBOX        "Servizio", IDC_STATIC, 7, 7, 251, 68
+    LTEXT           "Path:", IDC_STATIC, 13, 18, 53, 8, SS_LEFT
+    EDITTEXT        IDC_PATH, 70, 16, 184, 12, ES_AUTOHSCROLL, WS_EX_ACCEPTFILES
+}
+
 
 /////////////////////////////////////////////////////////////////////////////
 //

+ 46 - 10
registry.cpp

@@ -28,7 +28,7 @@ int create_messages() {
   return 0;
 }
 
-int create_parameters(nssm_service_t *service) {
+int create_parameters(nssm_service_t *service, bool editing) {
   /* Get registry */
   TCHAR registry[KEY_LENGTH];
   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY, service->name) < 0) {
@@ -63,33 +63,54 @@ int create_parameters(nssm_service_t *service) {
   /* Other non-default parameters. May fail. */
   unsigned long stop_method_skip = ~service->stop_method;
   if (stop_method_skip) set_number(key, NSSM_REG_STOP_METHOD_SKIP, stop_method_skip);
-  if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action]);
+  else if (editing) RegDeleteValue(key, NSSM_REG_STOP_METHOD_SKIP);
+  if (service->default_exit_action < NSSM_NUM_EXIT_ACTIONS) create_exit_action(service->name, exit_action_strings[service->default_exit_action], editing);
   if (service->throttle_delay != NSSM_RESET_THROTTLE_RESTART) set_number(key, NSSM_REG_THROTTLE, service->throttle_delay);
+  else if (editing) RegDeleteValue(key, NSSM_REG_THROTTLE);
   if (service->kill_console_delay != NSSM_KILL_CONSOLE_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, service->kill_console_delay);
+  else if (editing) RegDeleteValue(key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD);
   if (service->kill_window_delay != NSSM_KILL_WINDOW_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, service->kill_window_delay);
+  else if (editing) RegDeleteValue(key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD);
   if (service->kill_threads_delay != NSSM_KILL_THREADS_GRACE_PERIOD) set_number(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, service->kill_threads_delay);
-  if (service->stdin_path[0]) {
-    set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);
+  else if (editing) RegDeleteValue(key, NSSM_REG_KILL_THREADS_GRACE_PERIOD);
+  if (service->stdin_path[0] || editing) {
+    if (service->stdin_path[0]) set_expand_string(key, NSSM_REG_STDIN, service->stdin_path);
+    else if (editing) RegDeleteValue(key, NSSM_REG_STDIN);
     if (service->stdin_sharing != NSSM_STDIN_SHARING) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING, service->stdin_sharing);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_SHARING);
     if (service->stdin_disposition != NSSM_STDIN_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION, service->stdin_disposition);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_DISPOSITION);
     if (service->stdin_flags != NSSM_STDIN_FLAGS) set_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS, service->stdin_flags);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDIN, NSSM_REG_STDIO_FLAGS);
   }
-  if (service->stdout_path[0]) {
-    set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);
+  if (service->stdout_path[0] || editing) {
+    if (service->stdout_path[0]) set_expand_string(key, NSSM_REG_STDOUT, service->stdout_path);
+    else if (editing) RegDeleteValue(key, NSSM_REG_STDOUT);
     if (service->stdout_sharing != NSSM_STDOUT_SHARING) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING, service->stdout_sharing);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_SHARING);
     if (service->stdout_disposition != NSSM_STDOUT_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION, service->stdout_disposition);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);
     if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);
   }
-  if (service->stderr_path[0]) {
-    set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);
+  if (service->stderr_path[0] || editing) {
+    if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);
+    else if (editing) RegDeleteValue(key, NSSM_REG_STDERR);
     if (service->stderr_sharing != NSSM_STDERR_SHARING) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING, service->stderr_sharing);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_SHARING);
     if (service->stderr_disposition != NSSM_STDERR_DISPOSITION) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION, service->stderr_disposition);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);
     if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);
+    else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);
   }
   if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);
   if (service->rotate_seconds) set_number(key, NSSM_REG_ROTATE_SECONDS, service->rotate_seconds);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_SECONDS);
   if (service->rotate_bytes_low) set_number(key, NSSM_REG_ROTATE_BYTES_LOW, service->rotate_bytes_low);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);
   if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);
+  else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);
 
   /* Environment */
   if (service->env) {
@@ -97,11 +118,13 @@ int create_parameters(nssm_service_t *service) {
       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV, error_string(GetLastError()), 0);
     }
   }
+  else if (editing) RegDeleteValue(key, NSSM_REG_ENV);
   if (service->env_extra) {
     if (RegSetValueEx(key, NSSM_REG_ENV_EXTRA, 0, REG_MULTI_SZ, (const unsigned char *) service->env_extra, (unsigned long) service->env_extralen * sizeof(TCHAR)) != ERROR_SUCCESS) {
       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_ENV_EXTRA, error_string(GetLastError()), 0);
     }
   }
+  else if (editing) RegDeleteValue(key, NSSM_REG_ENV_EXTRA);
 
   /* Close registry. */
   RegCloseKey(key);
@@ -109,7 +132,7 @@ int create_parameters(nssm_service_t *service) {
   return 0;
 }
 
-int create_exit_action(TCHAR *service_name, const TCHAR *action_string) {
+int create_exit_action(TCHAR *service_name, const TCHAR *action_string, bool editing) {
   /* Get registry */
   TCHAR registry[KEY_LENGTH];
   if (_sntprintf_s(registry, _countof(registry), _TRUNCATE, NSSM_REGISTRY _T("\\%s"), service_name, NSSM_REG_EXIT) < 0) {
@@ -126,7 +149,7 @@ int create_exit_action(TCHAR *service_name, const TCHAR *action_string) {
   }
 
   /* Do nothing if the key already existed */
-  if (disposition == REG_OPENED_EXISTING_KEY) {
+  if (disposition == REG_OPENED_EXISTING_KEY && ! editing) {
     RegCloseKey(key);
     return 0;
   }
@@ -452,6 +475,19 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   override_milliseconds(service->name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, &service->kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);
   override_milliseconds(service->name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, &service->kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);
 
+  /* Try to get default exit action. */
+  bool default_action;
+  service->default_exit_action = NSSM_EXIT_RESTART;
+  TCHAR action_string[ACTION_LEN];
+  if (! get_exit_action(service->name, 0, action_string, &default_action)) {
+    for (int i = 0; exit_action_strings[i]; i++) {
+      if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
+        service->default_exit_action = i;
+        break;
+      }
+    }
+  }
+
   /* Close registry */
   RegCloseKey(key);
 

+ 2 - 2
registry.h

@@ -26,8 +26,8 @@
 #define NSSM_STDIO_LENGTH 29
 
 int create_messages();
-int create_parameters(nssm_service_t *);
-int create_exit_action(TCHAR *, const TCHAR *);
+int create_parameters(nssm_service_t *, bool);
+int create_exit_action(TCHAR *, const TCHAR *, bool);
 int set_environment(TCHAR *, HKEY, TCHAR *, TCHAR **, unsigned long *);
 int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool, bool);
 int expand_parameter(HKEY, TCHAR *, TCHAR *, unsigned long, bool);

+ 11 - 9
resource.h

@@ -6,14 +6,16 @@
 #define IDI_NSSM                        101
 #define IDD_INSTALL                     102
 #define IDD_REMOVE                      103
-#define IDD_APPLICATION                 104
-#define IDD_DETAILS                     105
-#define IDD_LOGON                       106
-#define IDD_IO                          107
-#define IDD_ROTATION                    108
-#define IDD_APPEXIT                     109
-#define IDD_SHUTDOWN                    110
-#define IDD_ENVIRONMENT                 111
+#define IDD_EDIT                        104
+#define IDD_APPLICATION                 105
+#define IDD_DETAILS                     106
+#define IDD_LOGON                       107
+#define IDD_IO                          108
+#define IDD_ROTATION                    109
+#define IDD_APPEXIT                     110
+#define IDD_SHUTDOWN                    111
+#define IDD_ENVIRONMENT                 112
+#define IDD_NATIVE                      113
 #define IDC_PATH                        1000
 #define IDC_TAB1                        1001
 #define IDC_CANCEL                      1002
@@ -58,7 +60,7 @@
 // 
 #ifdef APSTUDIO_INVOKED
 #ifndef APSTUDIO_READONLY_SYMBOLS
-#define _APS_NEXT_RESOURCE_VALUE        112
+#define _APS_NEXT_RESOURCE_VALUE        114
 #define _APS_NEXT_COMMAND_VALUE         40001
 #define _APS_NEXT_CONTROL_VALUE         1040
 #define _APS_NEXT_SYMED_VALUE           101

+ 235 - 34
service.cpp

@@ -1,5 +1,7 @@
 #include "nssm.h"
 
+extern const TCHAR *exit_action_strings[];
+
 bool is_admin;
 bool use_critical_section;
 
@@ -120,6 +122,172 @@ int pre_install_service(int argc, TCHAR **argv) {
   return ret;
 }
 
+/* About to edit the service. */
+int pre_edit_service(int argc, TCHAR **argv) {
+  /* Require service name. */
+  if (argc < 1) return usage(1);
+
+  nssm_service_t *service = alloc_nssm_service();
+  _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
+
+  /* Open service manager */
+  SC_HANDLE services = open_service_manager();
+  if (! services) {
+    print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
+    return 2;
+  }
+
+  /* Try to open the service */
+  service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
+  if (! service->handle) {
+    CloseServiceHandle(services);
+    print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
+    return 3;
+  }
+
+  /* Get system details. */
+  unsigned long bufsize;
+  unsigned long error;
+  QUERY_SERVICE_CONFIG *qsc;
+
+  QueryServiceConfig(service->handle, 0, 0, &bufsize);
+  error = GetLastError();
+  if (error == ERROR_INSUFFICIENT_BUFFER) {
+    qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);
+    if (! qsc) {
+      print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("pre_edit_service()"), 0);
+      return 4;
+    }
+  }
+  else {
+    CloseHandle(service->handle);
+    CloseServiceHandle(services);
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(error), 0);
+    return 4;
+  }
+
+  if (! QueryServiceConfig(service->handle, qsc, bufsize, &bufsize)) {
+    HeapFree(GetProcessHeap(), 0, qsc);
+    CloseHandle(service->handle);
+    CloseServiceHandle(services);
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(GetLastError()), 0);
+    return 4;
+  }
+
+  service->type = qsc->dwServiceType;
+  if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
+    HeapFree(GetProcessHeap(), 0, qsc);
+    CloseHandle(service->handle);
+    CloseServiceHandle(services);
+    print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
+    return 3;
+  }
+
+  switch (qsc->dwStartType) {
+    case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;
+    case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;
+    default: service->startup = NSSM_STARTUP_AUTOMATIC;
+  }
+  if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
+    size_t len = _tcslen(qsc->lpServiceStartName);
+    service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
+    if (service->username) {
+      memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
+      service->usernamelen = (unsigned long) len;
+    }
+    else {
+      HeapFree(GetProcessHeap(), 0, qsc);
+      CloseHandle(service->handle);
+      CloseServiceHandle(services);
+      print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));
+      return 4;
+    }
+  }
+  _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
+
+  /* Remember the executable in case it isn't NSSM. */
+  _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
+  HeapFree(GetProcessHeap(), 0, qsc);
+
+  /* Get extended system details. */
+  if (service->startup == NSSM_STARTUP_AUTOMATIC) {
+    QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
+    error = GetLastError();
+    if (error == ERROR_INSUFFICIENT_BUFFER) {
+      SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
+      if (! info) {
+        CloseHandle(service->handle);
+        CloseServiceHandle(services);
+        print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));
+        return 5;
+      }
+
+      if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
+        if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;
+      }
+      else {
+        error = GetLastError();
+        if (error != ERROR_INVALID_LEVEL) {
+          CloseHandle(service->handle);
+          CloseServiceHandle(services);
+          print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
+          return 5;
+        }
+      }
+    }
+    else if (error != ERROR_INVALID_LEVEL) {
+      CloseHandle(service->handle);
+      CloseServiceHandle(services);
+      print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
+      return 5;
+    }
+  }
+
+  QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
+  error = GetLastError();
+  if (error == ERROR_INSUFFICIENT_BUFFER) {
+    SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
+    if (! description) {
+      CloseHandle(service->handle);
+      CloseServiceHandle(services);
+      print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
+      return 6;
+    }
+
+    if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
+     if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
+      HeapFree(GetProcessHeap(), 0, description);
+    }
+    else {
+      HeapFree(GetProcessHeap(), 0, description);
+      CloseHandle(service->handle);
+      CloseServiceHandle(services);
+      print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
+      return 6;
+    }
+  }
+  else {
+    CloseHandle(service->handle);
+    CloseServiceHandle(services);
+    print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
+    return 6;
+  }
+
+  /* Get NSSM details. */
+  get_parameters(service, 0);
+
+  CloseServiceHandle(services);
+
+  if (! service->exe[0]) {
+    print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
+    service->native = true;
+  }
+
+  nssm_gui(IDD_EDIT, service);
+
+  return 0;
+}
+
 /* About to remove the service */
 int pre_remove_service(int argc, TCHAR **argv) {
   nssm_service_t *service = alloc_nssm_service();
@@ -150,8 +318,33 @@ int install_service(nssm_service_t *service) {
   }
 
   /* Get path of this program */
-  TCHAR command[MAX_PATH];
-  GetModuleFileName(0, command, _countof(command));
+  GetModuleFileName(0, service->image, _countof(service->image));
+
+  /* Create the service - settings will be changed in edit_service() */
+  service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);
+  if (! service->handle) {
+    print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
+    CloseServiceHandle(services);
+    return 5;
+  }
+
+  if (edit_service(service, false)) {
+    DeleteService(service->handle);
+    CloseServiceHandle(services);
+    return 6;
+  }
+
+  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
+
+  /* Cleanup */
+  CloseServiceHandle(services);
+
+  return 0;
+}
+
+/* Edit the service. */
+int edit_service(nssm_service_t *service, bool editing) {
+  if (! service) return 1;
 
   /*
     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
@@ -171,51 +364,59 @@ int install_service(nssm_service_t *service) {
   /* Display name. */
   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
 
-  /* Create the service */
-  service->handle = CreateService(services, service->name, service->displayname, SC_MANAGER_ALL_ACCESS, service->type, startup, SERVICE_ERROR_NORMAL, command, 0, 0, 0, service->username, service->password);
-  if (! service->handle) {
-    print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
-    CloseServiceHandle(services);
+  /*
+    Username must be NULL if we aren't changing or an account name.
+    We must explicitly user LOCALSYSTEM to change it when we are editing.
+    Password must be NULL if we aren't changing, a password or "".
+    Empty passwords are valid but we won't allow them in the GUI.
+  */
+  TCHAR *username = 0;
+  TCHAR *password = 0;
+  if (service->usernamelen) {
+    username = service->username;
+    if (service->passwordlen) password = service->password;
+    else password = _T("");
+  }
+  else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
+
+  if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
+    print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
     return 5;
   }
 
-  if (service->description[0]) {
+  if (service->description[0] || editing) {
     SERVICE_DESCRIPTION description;
     ZeroMemory(&description, sizeof(description));
-    description.lpDescription = service->description;
+    if (service->description[0]) description.lpDescription = service->description;
+    else description.lpDescription = 0;
     if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
     }
   }
 
-  if (service->startup == NSSM_STARTUP_DELAYED) {
-    SERVICE_DELAYED_AUTO_START_INFO delayed;
-    ZeroMemory(&delayed, sizeof(delayed));
-    delayed.fDelayedAutostart = 1;
-    /* Delayed startup isn't supported until Vista. */
-    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_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
-      }
+  SERVICE_DELAYED_AUTO_START_INFO delayed;
+  ZeroMemory(&delayed, sizeof(delayed));
+  if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
+  else delayed.fDelayedAutostart = 0;
+  /* Delayed startup isn't supported until Vista. */
+  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_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
     }
   }
 
-  /* Now we need to put the parameters into the registry */
-  if (create_parameters(service)) {
-    print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
-    DeleteService(service->handle);
-    CloseServiceHandle(services);
-    return 6;
-  }
-
-  set_service_recovery(service);
-
-  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
+  /* Don't mess with parameters which aren't ours. */
+  if (! service->native) {
+    /* Now we need to put the parameters into the registry */
+    if (create_parameters(service, editing)) {
+      print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
+      return 6;
+    }
 
-  /* Cleanup */
-  CloseServiceHandle(services);
+    set_service_recovery(service);
+  }
 
   return 0;
 }
@@ -295,7 +496,7 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
 
   if (is_admin) {
     /* Try to create the exit action parameters; we don't care if it fails */
-    create_exit_action(service->name, exit_action_strings[0]);
+    create_exit_action(service->name, exit_action_strings[0], false);
 
     SC_HANDLE services = open_service_manager();
     if (services) {

+ 6 - 0
service.h

@@ -17,7 +17,10 @@
 
 #define ACTION_LEN 16
 
+#define NSSM_LOCALSYSTEM_ACCOUNT _T("LocalSystem")
+
 typedef struct {
+  bool native;
   TCHAR name[SERVICE_NAME_LENGTH];
   TCHAR displayname[SERVICE_DISPLAYNAME_LENGTH];
   TCHAR description[VALUE_LENGTH];
@@ -27,6 +30,7 @@ typedef struct {
   TCHAR *password;
   size_t passwordlen;
   unsigned long type;
+  TCHAR image[MAX_PATH];
   TCHAR exe[EXE_LENGTH];
   TCHAR flags[VALUE_LENGTH];
   TCHAR dir[MAX_PATH];
@@ -85,8 +89,10 @@ void cleanup_nssm_service(nssm_service_t *);
 SC_HANDLE open_service_manager();
 int pre_install_service(int, TCHAR **);
 int pre_remove_service(int, TCHAR **);
+int pre_edit_service(int, TCHAR **);
 int install_service(nssm_service_t *);
 int remove_service(nssm_service_t *);
+int edit_service(nssm_service_t *, bool);
 void set_service_recovery(nssm_service_t *);
 int monitor_service(nssm_service_t *);
 int start_service(nssm_service_t *);