------------------\r
* Allow skipping kill_process_tree().\r
\r
+ * NSSM can now sleep a configurable amount of time after\r
+ rotating output files.\r
+\r
+ * NSSM can now rotate log files by calling CopyFile()\r
+ followed by SetEndOfFile(), allowing it to rotate files\r
+ which other processes hold open.\r
+\r
Changes since 2.23\r
------------------\r
* NSSM once again calls TerminateProcess() correctly.\r
than the given number of bytes. 64-bit file sizes can be handled by setting\r
a non-zero value of AppRotateBytesHigh.\r
\r
+If AppRotateDelay is non-zero, NSSM will pause for the given number of\r
+milliseconds after rotation.\r
+\r
+If AppStdoutCopyAndTruncate or AppStdErrCopyAndTruncate are non-zero, the\r
+stdout (or stderr respectively) file will be rotated by first taking a copy\r
+of the file then truncating the original file to zero size. This allows\r
+NSSM to rotate files which are held open by other processes, preventing the\r
+usual MoveFile() from succeeding. Note that the copy process may take some\r
+time if the file is large, and will temporarily consume twice as much disk\r
+space as the original file. Note also that applications reading the log file\r
+may not notice that the file size changed. Using this option in conjunction\r
+with AppRotateDelay may help in that case.\r
+\r
Rotation is independent of the CreateFile() parameters used to open the files.\r
They will be rotated regardless of whether NSSM would otherwise have appended\r
or replaced them.\r
Thanks to Sam Townsend for noticing a regression with TerminateProcess().\r
Thanks to Barrett Lewis for suggesting the option to skip terminating the\r
application's child processes.\r
+Thanks to Miguel Angel Terrón for suggesting copy/truncate rotation.\r
\r
Licence\r
-------\r
#define COMPLAINED_WRITE (1 << 1)\r
#define COMPLAINED_ROTATE (1 << 2)\r
\r
-static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long *tid_ptr, unsigned long *rotate_online) {\r
+static HANDLE create_logging_thread(TCHAR *service_name, TCHAR *path, unsigned long sharing, unsigned long disposition, unsigned long flags, HANDLE *read_handle_ptr, HANDLE *pipe_handle_ptr, HANDLE *write_handle_ptr, unsigned long rotate_bytes_low, unsigned long rotate_bytes_high, unsigned long rotate_delay, unsigned long *tid_ptr, unsigned long *rotate_online, bool copy_and_truncate) {\r
*tid_ptr = 0;\r
\r
/* Pipe between application's stdout/stderr and our logging handle. */\r
logger->size = (__int64) size.QuadPart;\r
logger->tid_ptr = tid_ptr;\r
logger->rotate_online = rotate_online;\r
+ logger->rotate_delay = rotate_delay;\r
+ logger->copy_and_truncate = copy_and_truncate;\r
\r
HANDLE thread_handle = CreateThread(NULL, 0, log_and_rotate, (void *) logger, 0, logger->tid_ptr);\r
if (! thread_handle) {\r
}\r
\r
/* Get path, share mode, creation disposition and flags for a stream. */\r
-int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags) {\r
+int get_createfile_parameters(HKEY key, TCHAR *prefix, TCHAR *path, unsigned long *sharing, unsigned long default_sharing, unsigned long *disposition, unsigned long default_disposition, unsigned long *flags, unsigned long default_flags, bool *copy_and_truncate) {\r
TCHAR value[NSSM_STDIO_LENGTH];\r
\r
/* Path. */\r
case -2: return 8; break; /* Error. */\r
}\r
\r
+ /* Rotate with CopyFile() and SetEndOfFile(). */\r
+ if (copy_and_truncate) {\r
+ unsigned long data;\r
+ if (_sntprintf_s(value, _countof(value), _TRUNCATE, _T("%s%s"), prefix, NSSM_REG_STDIO_COPY_AND_TRUNCATE) < 0) {\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, NSSM_REG_STDIO_COPY_AND_TRUNCATE, _T("get_createfile_parameters()"), 0);\r
+ return 9;\r
+ }\r
+ switch (get_number(key, value, &data, false)) {\r
+ case 0: *copy_and_truncate = false; break; /* Missing. */\r
+ case 1: /* Found. */\r
+ if (data) *copy_and_truncate = true;\r
+ else *copy_and_truncate = false;\r
+ break;\r
+ case -2: return 9; break; /* Error. */\r
+ }\r
+ }\r
+\r
return 0;\r
}\r
\r
_sntprintf_s(rotated, rotated_len, _TRUNCATE, _T("%s%s"), buffer, extension);\r
}\r
\r
-void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long low, unsigned long high) {\r
+void rotate_file(TCHAR *service_name, TCHAR *path, unsigned long seconds, unsigned long delay, unsigned long low, unsigned long high, bool copy_and_truncate) {\r
unsigned long error;\r
\r
/* Now. */\r
rotated_filename(path, rotated, _countof(rotated), &st);\r
\r
/* Rotate. */\r
- if (MoveFile(path, rotated)) {\r
+ bool ok = true;\r
+ TCHAR *function;\r
+ if (copy_and_truncate) {\r
+ function = _T("CopyFile()");\r
+ if (CopyFile(path, rotated, TRUE)) {\r
+ file = write_to_file(path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS);\r
+ Sleep(delay);\r
+ SetFilePointer(file, 0, 0, FILE_BEGIN);\r
+ SetEndOfFile(file);\r
+ CloseHandle(file);\r
+ }\r
+ else ok = false;\r
+ }\r
+ else {\r
+ function = _T("MoveFile()");\r
+ if (! MoveFile(path, rotated)) ok = false;\r
+ }\r
+ if (ok) {\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, service_name, path, rotated, 0);\r
return;\r
}\r
error = GetLastError();\r
\r
if (error == ERROR_FILE_NOT_FOUND) return;\r
- log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, _T("MoveFile()"), rotated, error_string(error), 0);\r
+ log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, service_name, path, function, rotated, error_string(error), 0);\r
return;\r
}\r
\r
\r
/* stdout */\r
if (service->stdout_path[0]) {\r
- if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
+ if (service->rotate_files) rotate_file(service->name, service->stdout_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stdout_copy_and_truncate);\r
HANDLE stdout_handle = write_to_file(service->stdout_path, service->stdout_sharing, 0, service->stdout_disposition, service->stdout_flags);\r
if (! stdout_handle) return 4;\r
\r
if (service->rotate_files && service->rotate_stdout_online) {\r
service->stdout_pipe = si->hStdOutput = 0;\r
- service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &si->hStdOutput, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, &service->stdout_tid, &service->rotate_stdout_online);\r
+ service->stdout_thread = create_logging_thread(service->name, service->stdout_path, service->stdout_sharing, service->stdout_disposition, service->stdout_flags, &service->stdout_pipe, &si->hStdOutput, &stdout_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stdout_tid, &service->rotate_stdout_online, service->stdout_copy_and_truncate);\r
if (! service->stdout_thread) {\r
CloseHandle(service->stdout_pipe);\r
CloseHandle(si->hStdOutput);\r
}\r
}\r
else {\r
- if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high);\r
+ if (service->rotate_files) rotate_file(service->name, service->stderr_path, service->rotate_seconds, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, service->stderr_copy_and_truncate);\r
HANDLE stderr_handle = write_to_file(service->stderr_path, service->stderr_sharing, 0, service->stderr_disposition, service->stderr_flags);\r
if (! stderr_handle) return 7;\r
\r
if (service->rotate_files && service->rotate_stderr_online) {\r
service->stderr_pipe = si->hStdError = 0;\r
- service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &si->hStdError, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, &service->stderr_tid, &service->rotate_stderr_online);\r
+ service->stderr_thread = create_logging_thread(service->name, service->stderr_path, service->stderr_sharing, service->stderr_disposition, service->stderr_flags, &service->stderr_pipe, &si->hStdError, &stderr_handle, service->rotate_bytes_low, service->rotate_bytes_high, service->rotate_delay, &service->stderr_tid, &service->rotate_stderr_online, service->stderr_copy_and_truncate);\r
if (! service->stderr_thread) {\r
CloseHandle(service->stderr_pipe);\r
CloseHandle(si->hStdError);\r
MoveFile() will fail if the handle is still open so we must\r
risk losing everything.\r
*/\r
+ if (logger->copy_and_truncate) FlushFileBuffers(logger->write_handle);\r
CloseHandle(logger->write_handle);\r
- if (MoveFile(logger->path, rotated)) {\r
+ bool ok = true;\r
+ TCHAR *function;\r
+ if (logger->copy_and_truncate) {\r
+ function = _T("CopyFile()");\r
+ if (CopyFile(logger->path, rotated, TRUE)) {\r
+ HANDLE file = write_to_file(logger->path, NSSM_STDOUT_SHARING, 0, NSSM_STDOUT_DISPOSITION, NSSM_STDOUT_FLAGS);\r
+ Sleep(logger->rotate_delay);\r
+ SetFilePointer(file, 0, 0, FILE_BEGIN);\r
+ SetEndOfFile(file);\r
+ CloseHandle(file);\r
+ }\r
+ else ok = false;\r
+ }\r
+ else {\r
+ function = _T("MoveFile()");\r
+ if (! MoveFile(logger->path, rotated)) ok = false;\r
+ }\r
+ if (ok) {\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ROTATED, logger->service_name, logger->path, rotated, 0);\r
size = 0LL;\r
}\r
else {\r
error = GetLastError();\r
if (error != ERROR_FILE_NOT_FOUND) {\r
- if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, _T("MoveFile()"), rotated, error_string(error), 0);\r
+ if (! (complained & COMPLAINED_ROTATE)) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ROTATE_FILE_FAILED, logger->service_name, logger->path, function, rotated, error_string(error), 0);\r
complained |= COMPLAINED_ROTATE;\r
/* We can at least try to re-open the existing file. */\r
logger->disposition = OPEN_ALWAYS;\r
__int64 size;\r
unsigned long *tid_ptr;\r
unsigned long *rotate_online;\r
+ bool copy_and_truncate;\r
+ unsigned long rotate_delay;\r
} logger_t;\r
\r
-int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long);\r
+int get_createfile_parameters(HKEY, TCHAR *, TCHAR *, unsigned long *, unsigned long, unsigned long *, unsigned long, unsigned long *, unsigned long, bool *);\r
int set_createfile_parameter(HKEY, TCHAR *, TCHAR *, unsigned long);\r
int delete_createfile_parameter(HKEY, TCHAR *, TCHAR *);\r
HANDLE write_to_file(TCHAR *, unsigned long, SECURITY_ATTRIBUTES *, unsigned long, unsigned long);\r
-void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long);\r
+void rotate_file(TCHAR *, TCHAR *, unsigned long, unsigned long, unsigned long, unsigned long, bool);\r
int get_output_handles(nssm_service_t *, STARTUPINFO *);\r
void close_output_handles(STARTUPINFO *);\r
unsigned long WINAPI log_and_rotate(void *);\r
*/\r
#define NSSM_KILL_THREADS_GRACE_PERIOD 1500\r
\r
+/* How many milliseconds to pause after rotating logs. */\r
+#define NSSM_ROTATE_DELAY 0\r
+\r
/* Margin of error for service status wait hints in milliseconds. */\r
#define NSSM_WAITHINT_MARGIN 2000\r
\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_DISPOSITION);\r
if (service->stdout_flags != NSSM_STDOUT_FLAGS) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS, service->stdout_flags);\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_FLAGS);\r
+ if (service->stdout_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
+ else if (editing) delete_createfile_parameter(key, NSSM_REG_STDOUT, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
}\r
if (service->stderr_path[0] || editing) {\r
if (service->stderr_path[0]) set_expand_string(key, NSSM_REG_STDERR, service->stderr_path);\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_DISPOSITION);\r
if (service->stderr_flags != NSSM_STDERR_FLAGS) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS, service->stderr_flags);\r
else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_FLAGS);\r
+ if (service->stderr_copy_and_truncate) set_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE, 1);\r
+ else if (editing) delete_createfile_parameter(key, NSSM_REG_STDERR, NSSM_REG_STDIO_COPY_AND_TRUNCATE);\r
}\r
if (service->rotate_files) set_number(key, NSSM_REG_ROTATE, 1);\r
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE);\r
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_LOW);\r
if (service->rotate_bytes_high) set_number(key, NSSM_REG_ROTATE_BYTES_HIGH, service->rotate_bytes_high);\r
else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_BYTES_HIGH);\r
+ if (service->rotate_delay != NSSM_ROTATE_DELAY) set_number(key, NSSM_REG_ROTATE_DELAY, service->rotate_delay);\r
+ else if (editing) RegDeleteValue(key, NSSM_REG_ROTATE_DELAY);\r
if (service->no_console) set_number(key, NSSM_REG_NO_CONSOLE, 1);\r
else if (editing) RegDeleteValue(key, NSSM_REG_NO_CONSOLE);\r
\r
\r
int get_io_parameters(nssm_service_t *service, HKEY key) {\r
/* stdin */\r
- 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)) {\r
+ 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, 0)) {\r
service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0;\r
ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR));\r
return 1;\r
}\r
\r
/* stdout */\r
- if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS)) {\r
+ if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS, &service->stdout_copy_and_truncate)) {\r
service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0;\r
ZeroMemory(service->stdout_path, _countof(service->stdout_path) * sizeof(TCHAR));\r
return 2;\r
}\r
\r
/* stderr */\r
- if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS)) {\r
+ if (get_createfile_parameters(key, NSSM_REG_STDERR, service->stderr_path, &service->stderr_sharing, NSSM_STDERR_SHARING, &service->stderr_disposition, NSSM_STDERR_DISPOSITION, &service->stderr_flags, NSSM_STDERR_FLAGS, &service->stderr_copy_and_truncate)) {\r
service->stderr_sharing = service->stderr_disposition = service->stderr_flags = 0;\r
ZeroMemory(service->stderr_path, _countof(service->stderr_path) * sizeof(TCHAR));\r
return 3;\r
if (get_number(key, NSSM_REG_ROTATE_SECONDS, &service->rotate_seconds, false) != 1) service->rotate_seconds = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_LOW, &service->rotate_bytes_low, false) != 1) service->rotate_bytes_low = 0;\r
if (get_number(key, NSSM_REG_ROTATE_BYTES_HIGH, &service->rotate_bytes_high, false) != 1) service->rotate_bytes_high = 0;\r
+ override_milliseconds(service->name, key, NSSM_REG_ROTATE_DELAY, &service->rotate_delay, NSSM_ROTATE_DELAY, NSSM_EVENT_BOGUS_THROTTLE);\r
\r
/* Try to get force new console setting - may fail. */\r
if (get_number(key, NSSM_REG_NO_CONSOLE, &service->no_console, false) != 1) service->no_console = 0;\r
#define NSSM_REG_STDIO_SHARING _T("ShareMode")\r
#define NSSM_REG_STDIO_DISPOSITION _T("CreationDisposition")\r
#define NSSM_REG_STDIO_FLAGS _T("FlagsAndAttributes")\r
+#define NSSM_REG_STDIO_COPY_AND_TRUNCATE _T("CopyAndTruncate")\r
#define NSSM_REG_ROTATE _T("AppRotateFiles")\r
#define NSSM_REG_ROTATE_ONLINE _T("AppRotateOnline")\r
#define NSSM_REG_ROTATE_SECONDS _T("AppRotateSeconds")\r
#define NSSM_REG_ROTATE_BYTES_LOW _T("AppRotateBytes")\r
#define NSSM_REG_ROTATE_BYTES_HIGH _T("AppRotateBytesHigh")\r
+#define NSSM_REG_ROTATE_DELAY _T("AppRotateDelay")\r
#define NSSM_REG_PRIORITY _T("AppPriority")\r
#define NSSM_REG_AFFINITY _T("AppAffinity")\r
#define NSSM_REG_NO_CONSOLE _T("AppNoConsole")\r
HANDLE stderr_thread;\r
unsigned long stderr_tid;\r
bool rotate_files;\r
+ bool stdout_copy_and_truncate;\r
+ bool stderr_copy_and_truncate;\r
unsigned long rotate_stdout_online;\r
unsigned long rotate_stderr_online;\r
unsigned long rotate_seconds;\r
unsigned long rotate_bytes_low;\r
unsigned long rotate_bytes_high;\r
+ unsigned long rotate_delay;\r
unsigned long default_exit_action;\r
unsigned long restart_delay;\r
unsigned long throttle_delay;\r
{ NSSM_REG_STDOUT NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDOUT_SHARING, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDOUT NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDOUT_DISPOSITION, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDOUT NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDOUT_FLAGS, false, 0, setting_set_number, setting_get_number },
+ { NSSM_REG_STDOUT NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDERR, REG_EXPAND_SZ, NULL, false, 0, setting_set_string, setting_get_string },
{ NSSM_REG_STDERR NSSM_REG_STDIO_SHARING, REG_DWORD, (void *) NSSM_STDERR_SHARING, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDERR NSSM_REG_STDIO_DISPOSITION, REG_DWORD, (void *) NSSM_STDERR_DISPOSITION, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STDERR NSSM_REG_STDIO_FLAGS, REG_DWORD, (void *) NSSM_STDERR_FLAGS, false, 0, setting_set_number, setting_get_number },
+ { NSSM_REG_STDERR NSSM_REG_STDIO_COPY_AND_TRUNCATE, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_STOP_METHOD_SKIP, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_CONSOLE_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_KILL_WINDOW_GRACE_PERIOD, REG_DWORD, (void *) NSSM_KILL_WINDOW_GRACE_PERIOD, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_ROTATE_SECONDS, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_ROTATE_BYTES_LOW, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
{ NSSM_REG_ROTATE_BYTES_HIGH, REG_DWORD, 0, false, 0, setting_set_number, setting_get_number },
+ { NSSM_REG_ROTATE_DELAY, REG_DWORD, (void *) NSSM_ROTATE_DELAY, false, 0, setting_set_number, setting_get_number },
{ NSSM_NATIVE_DEPENDONGROUP, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependongroup, native_get_dependongroup },
{ NSSM_NATIVE_DEPENDONSERVICE, REG_MULTI_SZ, NULL, true, ADDITIONAL_CRLF, native_set_dependonservice, native_get_dependonservice },
{ NSSM_NATIVE_DESCRIPTION, REG_SZ, _T(""), true, 0, native_set_description, native_get_description },