Browse Source

Allocate new console by default.

Since the whole purpose of NSSM is to be able to make services out of
applications which expect to have a standard desktop environment, let's
simply allocate a new console for each one rather than do so only when
we try to redirect I/O.

A funky ASCII art banner identifies the console as belonging to the
service.  NSSM itself will detach from the console as soon as possible,
so if the service runs a graphical application the console may never be
seen.

The console can be suppressed by setting the REG_DWORD value
AppNoConsole to 1.

Note that services running console applications on Windows 2000 may
fail if the console is disabled.
Iain Patterson 7 years ago
parent
commit
c03d51cbbf
14 changed files with 416 additions and 255 deletions
  1. 230 226
      ChangeLog.txt
  2. 11 0
      README.txt
  3. 9 0
      gui.cpp
  4. 24 26
      io.cpp
  5. 129 0
      nssm.cpp
  6. 1 0
      nssm.h
  7. BIN
      nssm.rc
  8. 0 2
      process.cpp
  9. 5 0
      registry.cpp
  10. 1 0
      registry.h
  11. 2 1
      resource.h
  12. 2 0
      service.cpp
  13. 1 0
      service.h
  14. 1 0
      settings.cpp

+ 230 - 226
ChangeLog.txt

@@ -1,226 +1,230 @@
-Changes since 2.21
-------------------
-  * Existing services can now be managed using the GUI
-    or on the command line.
-
-  * NSSM can now set the priority class and processor
-    affinity of the managed application.
-
-  * NSSM can now apply an unconditional delay before
-    restarting the application.
-
-  * NSSM can now optionally rotate existing files when
-    redirecting I/O.
-
-  * Unqualified path names are now relative to the
-    application startup directory when redirecting I/O.
-
-  * NSSM can now set the service display name, description,
-    startup type and log on details.
-
-Changes since 2.20
-------------------
-  * Services installed from the GUI no longer have incorrect
-    AppParameters set in the registry.
-
-Changes since 2.19
-------------------
-  * Services installed from the commandline without using the
-    GUI no longer have incorrect AppStopMethod* registry
-    entries set.
-
-Changes since 2.18
-------------------
-  * Support AppEnvironmentExtra to append to the environment
-    instead of replacing it.
-
-  * The GUI is significantly less sucky.
-
-Changes since 2.17
-------------------
-  * Timeouts for each shutdown method can be configured in
-    the registry.
-
-  * The GUI is slightly less sucky.
-
-Changes since 2.16
-------------------
-  * NSSM can now redirect the service's I/O streams to any path
-    capable of being opened by CreateFile().
-
-  * Allow building on Visual Studio Express.
-
-  * Silently ignore INTERROGATE control.
-
-  * Try to send Control-C events to console applications when
-    shutting them down.
-
-Changes since 2.15
-------------------
-  * Fixed case where NSSM could kill unrelated processes when
-    shutting down.
-
-Changes since 2.14
-------------------
-  * NSSM is now translated into Italian.
-
-  * Fixed GUI not allowing paths longer than 256 characters.
-
-Changes since 2.13
-------------------
-  * Fixed default GUI language being French not English.
-
-Changes since 2.12
-------------------
-  * Fixed failure to run on Windows 2000.
-
-Changes since 2.11
-------------------
-  * NSSM is now translated into French.
-
-  * Really ensure systems recovery actions can happen.
-
-    The change supposedly introduced in v2.4 to allow service recovery
-    actions to be activated when the application exits gracefully with
-    a non-zero error code didn't actually work.
-
-Changes since 2.10
-------------------
-  * Support AppEnvironment for compatibility with srvany.
-
-Changes since 2.9
------------------
-  * Fixed failure to compile messages.mc in paths containing spaces.
-
-  * Fixed edge case with CreateProcess().
-
-    Correctly handle the case where the application executable is under
-    a path which contains space and an executable sharing the initial
-    part of that path (up to a space) exists.
-
-Changes since 2.8
------------------
-  * Fixed failure to run on Windows versions prior to Vista.
-
-Changes since 2.7
------------------
-  * Read Application, AppDirectory and AppParameters before each restart so
-    a change to any one doesn't require restarting NSSM itself.
-
-  * Fixed messages not being sent to the event log correctly in some
-    cases.
-
-  * Try to handle (strictly incorrect) quotes in AppDirectory.
-
-    Windows directories aren't allowed to contain quotes so CreateProcess()
-    will fail if the AppDirectory is quoted.  Note that it succeeds even if
-    Application itself is quoted as the application plus parameters are
-    interpreted as a command line.
-
-  * Fixed failed to write full arguments to AppParameters when
-    installing a service.
-
-  * Throttle restarts.
-
-    Back off from restarting the application immediately if it starts
-    successfully but exits too soon.  The default value of "too soon" is
-    1500 milliseconds.  This can be configured by adding a DWORD value
-    AppThrottle to the registry.
-    
-    Handle resume messages from the service console to restart the
-    application immediately even if it is throttled.
-
-  * Try to kill the process tree gracefully.
-
-    Before calling TerminateProcess() on all processes assocatiated with
-    the monitored application, enumerate all windows and threads and
-    post appropriate messages to them.  If the application bothers to
-    listen for such messages it has a chance to shut itself down gracefully.
-
-Changes since 2.6
------------------
-  * Handle missing registry values.
-
-    Warn if AppParameters is missing.  Warn if AppDirectory is missing or
-    unset and choose a fallback directory.
-    First try to find the parent directory of the application.  If that
-    fails, eg because the application path is just "notepad" or something,
-    start in the Windows directory.
-
-  * Kill process tree when stopping service.
-
-    Ensure that all child processes of the monitored application are
-    killed when the service stops by recursing through all running
-    processes and terminating those whose parent is the application
-    or one of its descendents.
-
-Changes since 2.5
------------------
-  * Removed incorrect ExpandEnvironmentStrings() error.
-
-    A log_event() call was inadvertently left in the code causing an error
-    to be set to the eventlog saying that ExpandEnvironmentStrings() had
-    failed when it had actually succeeded.
-
-Changes since 2.4
------------------
-  * Allow use of REG_EXPAND_SZ values in the registry.
-
-  * Don't suicide on exit status 0 by default.
-
-    Suiciding when the application exits 0 will cause recovery actions to be
-    taken.  Usually this is inappropriate.  Only suicide if there is an
-    explicit AppExit value for 0 in the registry.
-    
-    Technically such behaviour could be abused to do something like run a
-    script after successful completion of a service but in most cases a
-    suicide is undesirable when no actual failure occurred.
-
-  * Don't hang if startup parameters couldn't be determined.
-    Instead, signal that the service entered the STOPPED state.
-    Set START_PENDING state prior to actual startup.
-
-Changes since 2.3
------------------
-  * Ensure systems recovery actions can happen.
-
-    In Windows versions earlier than Vista the service manager would only
-    consider a service failed (and hence eligible for recovery action) if
-    the service exited without setting its state to SERVICE_STOPPED, even if
-    it signalled an error exit code.
-    In Vista and later the service manager can be configured to treat a
-    graceful shutdown with error code as a failure but this is not the
-    default behaviour.
-
-    Try to configure the service manager to use the new behaviour when
-    starting the service so users who set AppExit to Exit can use recovery
-    actions as expected.
-
-    Also recognise the new AppExit option Suicide for use on pre-Vista
-    systems.  When AppExit is Suicide don't stop the service but exit
-    inelegantly, which should be seen as a failure.
-
-Changes since 2.2
------------------
-  * Send properly formatted messages to the event log.
-
-  * Fixed truncation of very long path lengths in the registry.
-
-Changes since 2.1
------------------
- *  Decide how to handle application exit.
-
-    When the service exits with exit code n look in
-    HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit\<n>,
-    falling back to the unnamed value if no such code is listed.  Parse the
-    (string) value of this entry as follows:
-
-        Restart: Start the application again (NSSM default).
-        Ignore:  Do nothing (srvany default).
-        Exit:    Stop the service.
-
-Changes since 2.0
------------------
-  * Added support for building a 64-bit executable. 
-
-  * Added project files for newer versions of Visual Studio.
+Changes since 2.21
+------------------
+  * Existing services can now be managed using the GUI
+    or on the command line.
+
+  * NSSM can now set the priority class and processor
+    affinity of the managed application.
+
+  * NSSM can now apply an unconditional delay before
+    restarting the application.
+
+  * NSSM can now optionally rotate existing files when
+    redirecting I/O.
+
+  * Unqualified path names are now relative to the
+    application startup directory when redirecting I/O.
+
+  * NSSM can now set the service display name, description,
+    startup type and log on details.
+
+  * All services now receive a standard console window,
+    allowing them to read input correctly (if running in
+    interactive mode).
+
+Changes since 2.20
+------------------
+  * Services installed from the GUI no longer have incorrect
+    AppParameters set in the registry.
+
+Changes since 2.19
+------------------
+  * Services installed from the commandline without using the
+    GUI no longer have incorrect AppStopMethod* registry
+    entries set.
+
+Changes since 2.18
+------------------
+  * Support AppEnvironmentExtra to append to the environment
+    instead of replacing it.
+
+  * The GUI is significantly less sucky.
+
+Changes since 2.17
+------------------
+  * Timeouts for each shutdown method can be configured in
+    the registry.
+
+  * The GUI is slightly less sucky.
+
+Changes since 2.16
+------------------
+  * NSSM can now redirect the service's I/O streams to any path
+    capable of being opened by CreateFile().
+
+  * Allow building on Visual Studio Express.
+
+  * Silently ignore INTERROGATE control.
+
+  * Try to send Control-C events to console applications when
+    shutting them down.
+
+Changes since 2.15
+------------------
+  * Fixed case where NSSM could kill unrelated processes when
+    shutting down.
+
+Changes since 2.14
+------------------
+  * NSSM is now translated into Italian.
+
+  * Fixed GUI not allowing paths longer than 256 characters.
+
+Changes since 2.13
+------------------
+  * Fixed default GUI language being French not English.
+
+Changes since 2.12
+------------------
+  * Fixed failure to run on Windows 2000.
+
+Changes since 2.11
+------------------
+  * NSSM is now translated into French.
+
+  * Really ensure systems recovery actions can happen.
+
+    The change supposedly introduced in v2.4 to allow service recovery
+    actions to be activated when the application exits gracefully with
+    a non-zero error code didn't actually work.
+
+Changes since 2.10
+------------------
+  * Support AppEnvironment for compatibility with srvany.
+
+Changes since 2.9
+-----------------
+  * Fixed failure to compile messages.mc in paths containing spaces.
+
+  * Fixed edge case with CreateProcess().
+
+    Correctly handle the case where the application executable is under
+    a path which contains space and an executable sharing the initial
+    part of that path (up to a space) exists.
+
+Changes since 2.8
+-----------------
+  * Fixed failure to run on Windows versions prior to Vista.
+
+Changes since 2.7
+-----------------
+  * Read Application, AppDirectory and AppParameters before each restart so
+    a change to any one doesn't require restarting NSSM itself.
+
+  * Fixed messages not being sent to the event log correctly in some
+    cases.
+
+  * Try to handle (strictly incorrect) quotes in AppDirectory.
+
+    Windows directories aren't allowed to contain quotes so CreateProcess()
+    will fail if the AppDirectory is quoted.  Note that it succeeds even if
+    Application itself is quoted as the application plus parameters are
+    interpreted as a command line.
+
+  * Fixed failed to write full arguments to AppParameters when
+    installing a service.
+
+  * Throttle restarts.
+
+    Back off from restarting the application immediately if it starts
+    successfully but exits too soon.  The default value of "too soon" is
+    1500 milliseconds.  This can be configured by adding a DWORD value
+    AppThrottle to the registry.
+    
+    Handle resume messages from the service console to restart the
+    application immediately even if it is throttled.
+
+  * Try to kill the process tree gracefully.
+
+    Before calling TerminateProcess() on all processes assocatiated with
+    the monitored application, enumerate all windows and threads and
+    post appropriate messages to them.  If the application bothers to
+    listen for such messages it has a chance to shut itself down gracefully.
+
+Changes since 2.6
+-----------------
+  * Handle missing registry values.
+
+    Warn if AppParameters is missing.  Warn if AppDirectory is missing or
+    unset and choose a fallback directory.
+    First try to find the parent directory of the application.  If that
+    fails, eg because the application path is just "notepad" or something,
+    start in the Windows directory.
+
+  * Kill process tree when stopping service.
+
+    Ensure that all child processes of the monitored application are
+    killed when the service stops by recursing through all running
+    processes and terminating those whose parent is the application
+    or one of its descendents.
+
+Changes since 2.5
+-----------------
+  * Removed incorrect ExpandEnvironmentStrings() error.
+
+    A log_event() call was inadvertently left in the code causing an error
+    to be set to the eventlog saying that ExpandEnvironmentStrings() had
+    failed when it had actually succeeded.
+
+Changes since 2.4
+-----------------
+  * Allow use of REG_EXPAND_SZ values in the registry.
+
+  * Don't suicide on exit status 0 by default.
+
+    Suiciding when the application exits 0 will cause recovery actions to be
+    taken.  Usually this is inappropriate.  Only suicide if there is an
+    explicit AppExit value for 0 in the registry.
+    
+    Technically such behaviour could be abused to do something like run a
+    script after successful completion of a service but in most cases a
+    suicide is undesirable when no actual failure occurred.
+
+  * Don't hang if startup parameters couldn't be determined.
+    Instead, signal that the service entered the STOPPED state.
+    Set START_PENDING state prior to actual startup.
+
+Changes since 2.3
+-----------------
+  * Ensure systems recovery actions can happen.
+
+    In Windows versions earlier than Vista the service manager would only
+    consider a service failed (and hence eligible for recovery action) if
+    the service exited without setting its state to SERVICE_STOPPED, even if
+    it signalled an error exit code.
+    In Vista and later the service manager can be configured to treat a
+    graceful shutdown with error code as a failure but this is not the
+    default behaviour.
+
+    Try to configure the service manager to use the new behaviour when
+    starting the service so users who set AppExit to Exit can use recovery
+    actions as expected.
+
+    Also recognise the new AppExit option Suicide for use on pre-Vista
+    systems.  When AppExit is Suicide don't stop the service but exit
+    inelegantly, which should be seen as a failure.
+
+Changes since 2.2
+-----------------
+  * Send properly formatted messages to the event log.
+
+  * Fixed truncation of very long path lengths in the registry.
+
+Changes since 2.1
+-----------------
+ *  Decide how to handle application exit.
+
+    When the service exits with exit code n look in
+    HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppExit\<n>,
+    falling back to the unnamed value if no such code is listed.  Parse the
+    (string) value of this entry as follows:
+
+        Restart: Start the application again (NSSM default).
+        Ignore:  Do nothing (srvany default).
+        Exit:    Stop the service.
+
+Changes since 2.0
+-----------------
+  * Added support for building a 64-bit executable.
+
+  * Added project files for newer versions of Visual Studio.

+ 11 - 0
README.txt

@@ -262,6 +262,17 @@ so the actual time to shutdown may be longer than the sum of all configured
 timeouts if the application spawns multiple subprocesses.
 
 
+Console window
+--------------
+By default, NSSM will create a console window so that applications which
+are capable of reading user input can do so - subject to the service being
+allowed to interact with the desktop.
+
+Creation of the console can be suppressed by setting the integer (REG_DWORD)
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters\AppNoConsole
+registry value to 1.
+
+
 I/O redirection
 ---------------
 NSSM can redirect the managed application's I/O to any path capable of being

+ 9 - 0
gui.cpp

@@ -117,6 +117,10 @@ int nssm_gui(int resource, nssm_service_t *service) {
       }
     }
 
+    if (service->no_console) {
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);
+    }
+
     /* Shutdown tab. */
     if (! (service->stop_method & NSSM_STOP_METHOD_CONSOLE)) {
       SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);
@@ -478,6 +482,9 @@ int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service
     }
   }
 
+  if (SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->no_console = 0;
+  else service->no_console = 1;
+
   /* 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);
@@ -956,6 +963,8 @@ INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {
       SendMessage(combo, CB_INSERTSTRING, NSSM_IDLE_PRIORITY, (LPARAM) message_string(NSSM_GUI_IDLE_PRIORITY_CLASS));
       SendMessage(combo, CB_SETCURSEL, NSSM_NORMAL_PRIORITY, 0);
 
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_CHECKED, 0);
+
       list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);
       n = num_cpus();
       SendMessage(list, LB_SETCOLUMNWIDTH, 16, 0);

+ 24 - 26
io.cpp

@@ -231,7 +231,26 @@ void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsign
 }
 
 int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
-  bool redirect = false;
+  /* Allocate a new console so we get a fresh stdin, stdout and stderr. */
+  if (si && ! service->no_console) {
+    FreeConsole();
+    AllocConsole();
+    banner();
+
+    /* Set a title like "[NSSM] Jenkins" */
+    TCHAR displayname[SERVICE_NAME_LENGTH];
+    unsigned long len = _countof(displayname);
+    SC_HANDLE services = open_service_manager();
+    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);
+  }
 
   /* stdin */
   if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) {
@@ -245,8 +264,6 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0);
       return 2;
     }
-
-    redirect = true;
   }
 
   /* stdout */
@@ -277,8 +294,6 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
       }
       service->rotate_stdout_online = NSSM_ROTATE_OFFLINE;
     }
-
-    redirect = true;
   }
 
   /* stderr */
@@ -325,35 +340,18 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) {
         }
         service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;
       }
-
-      redirect = true;
     }
   }
 
-  if (! redirect || ! si) return 0;
-
-  /* Allocate a new console so we get a fresh stdin, stdout and stderr. */
-  FreeConsole();
-  AllocConsole();
-
-  /* Set a title like "[NSSM] Jenkins" */
-  TCHAR displayname[SERVICE_NAME_LENGTH];
-  unsigned long len = _countof(displayname);
-  SC_HANDLE services = open_service_manager();
-  if (services) {
-    if (! GetServiceDisplayName(services, service->name, displayname, &len)) _sntprintf_s(displayname, _countof(displayname), _TRUNCATE, _T("%s"), service->name);
-    CloseServiceHandle(services);
-  }
-
-  TCHAR title[65535];
-  _sntprintf_s(title, _countof(title), _TRUNCATE, _T("[%s] %s\n"), NSSM, displayname);
-  SetConsoleTitle(title);
+  if (! si) return 0;
 
   /*
     We need to set the startup_info flags to make the new handles
     inheritable by the new process.
   */
-  if (si) si->dwFlags |= STARTF_USESTDHANDLES;
+  si->dwFlags |= STARTF_USESTDHANDLES;
+
+  if (service->no_console) return 0;
 
   /* Redirect other handles. */
   if (! si->hStdInput) {

+ 129 - 0
nssm.cpp

@@ -82,6 +82,135 @@ int num_cpus() {
   return (int) i;
 }
 
+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 banner() {
+  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++;
+}
+
 int _tmain(int argc, TCHAR **argv) {
   check_console();
 

+ 1 - 0
nssm.h

@@ -57,6 +57,7 @@ int str_number(const TCHAR *, unsigned long *, TCHAR **);
 int str_number(const TCHAR *, unsigned long *);
 int num_cpus();
 int usage(int);
+void banner();
 
 #define NSSM _T("NSSM")
 #ifdef _WIN64

BIN
nssm.rc


+ 0 - 2
process.cpp

@@ -208,8 +208,6 @@ int kill_console(nssm_service_t *service) {
         return 2;
 
       case ERROR_ACCESS_DENIED:
-        /* Maybe we already allocated a console for output. */
-        if (service->stdin_path[0] || service->stdout_path[0] || service->stderr_path[0]) break;
       default:
         /* We already have a console. */
         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service->name, error_string(ret), 0);

+ 5 - 0
registry.cpp

@@ -119,6 +119,8 @@ int create_parameters(nssm_service_t *service, bool editing) {
   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);
+  if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);
+  else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);
 
   /* Environment */
   if (service->env) {
@@ -489,6 +491,9 @@ int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;
   if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;
 
+  /* Try to get force new console setting - may fail. */
+  if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;
+
   /* Change to startup directory in case stdout/stderr are relative paths. */
   TCHAR cwd[PATH_LENGTH];
   GetCurrentDirectory(_countof(cwd), cwd);

+ 1 - 0
registry.h

@@ -27,6 +27,7 @@
 #define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")
 #define NSSM_REG_PRIORITY _T("AppPriority")
 #define NSSM_REG_AFFINITY _T("AppAffinity")
+#define NSSM_REG_NO_CONSOLE _T("AppNoConsole")
 #define NSSM_STDIO_LENGTH 29
 
 HKEY open_registry(const TCHAR *, const TCHAR *, REGSAM sam);

+ 2 - 1
resource.h

@@ -61,6 +61,7 @@
 #define IDC_PRIORITY                    1042
 #define IDC_AFFINITY_ALL                1043
 #define IDC_AFFINITY                    1044
+#define IDC_CONSOLE                     1045
 
 // Next default values for new objects
 // 
@@ -68,7 +69,7 @@
 #ifndef APSTUDIO_READONLY_SYMBOLS
 #define _APS_NEXT_RESOURCE_VALUE        115
 #define _APS_NEXT_COMMAND_VALUE         40001
-#define _APS_NEXT_CONTROL_VALUE         1045
+#define _APS_NEXT_CONTROL_VALUE         1046
 #define _APS_NEXT_SYMED_VALUE           101
 #endif
 #endif

+ 2 - 0
service.cpp

@@ -1697,6 +1697,8 @@ void CALLBACK end_service(void *arg, unsigned char why) {
   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
   service->pid = 0;
 
+  if (! service->no_console) FreeConsole();
+
   /*
     The why argument is true if our wait timed out or false otherwise.
     Our wait is infinite so why will never be true when called by the system.

+ 1 - 0
service.h

@@ -53,6 +53,7 @@ typedef struct {
   TCHAR *env_extra;
   unsigned long env_extralen;
   unsigned long priority;
+  unsigned long no_console;
   TCHAR stdin_path[PATH_LENGTH];
   unsigned long stdin_sharing;
   unsigned long stdin_disposition;

+ 1 - 0
settings.cpp

@@ -810,6 +810,7 @@ settings_t settings[] = {
   { 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 },