Try to build PDB files even for releases.
[nssm.git] / gui.cpp
diff --git a/gui.cpp b/gui.cpp
index 5041acd..5aaa932 100644 (file)
--- a/gui.cpp
+++ b/gui.cpp
@@ -1,23 +1,62 @@
 #include "nssm.h"\r
 \r
-int nssm_gui(int resource, char *name) {\r
-  char blurb[256];\r
+extern const TCHAR *hook_event_strings[];\r
+extern const TCHAR *hook_action_strings[];\r
 \r
+static enum { NSSM_TAB_APPLICATION, NSSM_TAB_DETAILS, NSSM_TAB_LOGON, NSSM_TAB_DEPENDENCIES, NSSM_TAB_PROCESS, NSSM_TAB_SHUTDOWN, NSSM_TAB_EXIT, NSSM_TAB_IO, NSSM_TAB_ROTATION, NSSM_TAB_ENVIRONMENT, NSSM_TAB_HOOKS, NSSM_NUM_TABS } nssm_tabs;\r
+static HWND tablist[NSSM_NUM_TABS];\r
+static int selected_tab;\r
+\r
+static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function, LPARAM l) {\r
+  /* The caller will deal with GetLastError()... */\r
+  HRSRC resource = FindResourceEx(0, RT_DIALOG, templ, GetUserDefaultLangID());\r
+  if (! resource) {\r
+    if (GetLastError() != ERROR_RESOURCE_LANG_NOT_FOUND) return 0;\r
+    resource = FindResourceEx(0, RT_DIALOG, templ, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL));\r
+    if (! resource) return 0;\r
+  }\r
+\r
+  HGLOBAL ret = LoadResource(0, resource);\r
+  if (! ret) return 0;\r
+\r
+  return CreateDialogIndirectParam(0, (DLGTEMPLATE *) ret, parent, function, l);\r
+}\r
+\r
+static HWND dialog(const TCHAR *templ, HWND parent, DLGPROC function) {\r
+  return dialog(templ, parent, function, 0);\r
+}\r
+\r
+static inline void set_logon_enabled(unsigned char interact_enabled, unsigned char credentials_enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_INTERACT), interact_enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), credentials_enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), credentials_enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), credentials_enabled);\r
+}\r
+\r
+int nssm_gui(int resource, nssm_service_t *service) {\r
   /* Create window */\r
-  HWND dlg = CreateDialog(0, MAKEINTRESOURCE(resource), 0, install_dlg);\r
+  HWND dlg = dialog(MAKEINTRESOURCE(resource), 0, nssm_dlg, (LPARAM) service);\r
   if (! dlg) {\r
-    snprintf(blurb, sizeof(blurb), "CreateDialog() failed with error code %d", GetLastError());\r
-    MessageBox(0, blurb, NSSM, MB_OK);\r
+    popup_message(0, MB_OK, NSSM_GUI_CREATEDIALOG_FAILED, error_string(GetLastError()));\r
     return 1;\r
   }\r
 \r
+  /* Load the icon. */\r
+  HANDLE icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_NSSM), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);\r
+  if (icon) SendMessage(dlg, WM_SETICON, ICON_SMALL, (LPARAM) icon);\r
+  icon = LoadImage(GetModuleHandle(0), MAKEINTRESOURCE(IDI_NSSM), IMAGE_ICON, GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), 0);\r
+  if (icon) SendMessage(dlg, WM_SETICON, ICON_BIG, (LPARAM) icon);\r
+\r
+  /* Remember what the window is for. */\r
+  SetWindowLongPtr(dlg, GWLP_USERDATA, (LONG_PTR) resource);\r
+\r
   /* Display the window */\r
   centre_window(dlg);\r
   ShowWindow(dlg, SW_SHOW);\r
 \r
   /* Set service name if given */\r
-  if (name) {\r
-    SetDlgItemText(dlg, IDC_NAME, name);\r
+  if (service->name[0]) {\r
+    SetDlgItemText(dlg, IDC_NAME, service->name);\r
     /* No point making user click remove if the name is already entered */\r
     if (resource == IDD_REMOVE) {\r
       HWND button = GetDlgItem(dlg, IDC_REMOVE);\r
@@ -28,14 +67,176 @@ int nssm_gui(int resource, char *name) {
     }\r
   }\r
 \r
+  if (resource == IDD_EDIT) {\r
+    /* We'll need the service handle later. */\r
+    SetWindowLongPtr(dlg, DWLP_USER, (LONG_PTR) service);\r
+\r
+    /* Service name can't be edited. */\r
+    EnableWindow(GetDlgItem(dlg, IDC_NAME), 0);\r
+    SetFocus(GetDlgItem(dlg, IDOK));\r
+\r
+    /* Set existing details. */\r
+    HWND combo;\r
+    HWND list;\r
+\r
+    /* Application tab. */\r
+    if (service->native) SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->image);\r
+    else SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe);\r
+    SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_DIR, service->dir);\r
+    SetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS, service->flags);\r
+\r
+    /* Details tab. */\r
+    SetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname);\r
+    SetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description);\r
+    combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);\r
+    SendMessage(combo, CB_SETCURSEL, service->startup, 0);\r
+\r
+    /* Log on tab. */\r
+    if (service->username) {\r
+      if (is_virtual_account(service->name, service->username)) {\r
+        CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_VIRTUAL_SERVICE);\r
+        set_logon_enabled(0, 0);\r
+      }\r
+      else {\r
+        CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_ACCOUNT);\r
+        SetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username);\r
+        set_logon_enabled(0, 1);\r
+      }\r
+    }\r
+    else {\r
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_VIRTUAL_SERVICE, IDC_LOCALSYSTEM);\r
+      if (service->type & SERVICE_INTERACTIVE_PROCESS) SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_SETCHECK, BST_CHECKED, 0);\r
+    }\r
+\r
+    /* Dependencies tab. */\r
+    if (service->dependencieslen) {\r
+      TCHAR *formatted;\r
+      unsigned long newlen;\r
+      if (format_double_null(service->dependencies, service->dependencieslen, &formatted, &newlen)) {\r
+        popup_message(dlg, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("nssm_dlg()"));\r
+      }\r
+      else {\r
+        SetDlgItemText(tablist[NSSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES, formatted);\r
+        HeapFree(GetProcessHeap(), 0, formatted);\r
+      }\r
+    }\r
+\r
+    /* Process tab. */\r
+    if (service->priority) {\r
+      int priority = priority_constant_to_index(service->priority);\r
+      combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+      SendMessage(combo, CB_SETCURSEL, priority, 0);\r
+    }\r
+\r
+    if (service->affinity) {\r
+      list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), 1);\r
+\r
+      DWORD_PTR affinity, system_affinity;\r
+      if (GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) {\r
+        if ((service->affinity & (__int64) system_affinity) != service->affinity) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_AFFINITY);\r
+      }\r
+\r
+      for (int i = 0; i < num_cpus(); i++) {\r
+        if (! (service->affinity & (1LL << (__int64) i))) SendMessage(list, LB_SETSEL, 0, i);\r
+      }\r
+    }\r
+\r
+    if (service->no_console) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);\r
+    }\r
+\r
+    /* Shutdown tab. */\r
+    if (! (service->stop_method & NSSM_STOP_METHOD_CONSOLE)) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE), 0);\r
+    }\r
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, service->kill_console_delay, 0);\r
+    if (! (service->stop_method & NSSM_STOP_METHOD_WINDOW)) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW), 0);\r
+    }\r
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, service->kill_window_delay, 0);\r
+    if (! (service->stop_method & NSSM_STOP_METHOD_THREADS)) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS), 0);\r
+    }\r
+    SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, service->kill_threads_delay, 0);\r
+    if (! (service->stop_method & NSSM_STOP_METHOD_TERMINATE)) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_UNCHECKED, 0);\r
+    }\r
+    if (! service->kill_process_tree) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_SETCHECK, BST_UNCHECKED, 0);\r
+    }\r
+\r
+    /* Restart tab. */\r
+    SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, service->throttle_delay, 0);\r
+    combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
+    SendMessage(combo, CB_SETCURSEL, service->default_exit_action, 0);\r
+    SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, service->restart_delay, 0);\r
+\r
+    /* I/O tab. */\r
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDIN, service->stdin_path);\r
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDOUT, service->stdout_path);\r
+    SetDlgItemText(tablist[NSSM_TAB_IO], IDC_STDERR, service->stderr_path);\r
+    if (service->timestamp_log) SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_CHECKED, 0);\r
+\r
+    /* Rotation tab. */\r
+    if (service->stdout_disposition == CREATE_ALWAYS) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_SETCHECK, BST_CHECKED, 0);\r
+    if (service->rotate_files) {\r
+      SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_SETCHECK, BST_CHECKED, 0);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), 1);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), 1);\r
+      EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), 1);\r
+    }\r
+    if (service->rotate_stdout_online || service->rotate_stderr_online) SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_CHECKED, 0);\r
+    SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, service->rotate_seconds, 0);\r
+    if (! service->rotate_bytes_high) SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, service->rotate_bytes_low, 0);\r
+\r
+    /* Hooks tab. */\r
+    if (service->hook_share_output_handles) SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_CHECKED, 0);\r
+\r
+    /* Check if advanced settings are in use. */\r
+    if (service->stdout_disposition != service->stderr_disposition || (service->stdout_disposition && service->stdout_disposition != NSSM_STDOUT_DISPOSITION && service->stdout_disposition != CREATE_ALWAYS) || (service->stderr_disposition && service->stderr_disposition != NSSM_STDERR_DISPOSITION && service->stderr_disposition != CREATE_ALWAYS)) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_STDIO);\r
+    if (service->rotate_bytes_high) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ROTATE_BYTES);\r
+\r
+    /* Environment tab. */\r
+    TCHAR *env;\r
+    unsigned long envlen;\r
+    if (service->env_extralen) {\r
+      env = service->env_extra;\r
+      envlen = service->env_extralen;\r
+    }\r
+    else {\r
+      env = service->env;\r
+      envlen = service->envlen;\r
+      if (envlen) SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_SETCHECK, BST_CHECKED, 0);\r
+    }\r
+\r
+    if (envlen) {\r
+      TCHAR *formatted;\r
+      unsigned long newlen;\r
+      if (format_double_null(env, envlen, &formatted, &newlen)) {\r
+        popup_message(dlg, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("nssm_dlg()"));\r
+      }\r
+      else {\r
+        SetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, formatted);\r
+        HeapFree(GetProcessHeap(), 0, formatted);\r
+      }\r
+    }\r
+    if (service->envlen && service->env_extralen) popup_message(dlg, MB_OK | MB_ICONWARNING, NSSM_GUI_WARN_ENVIRONMENT);\r
+  }\r
+\r
   /* Go! */\r
   MSG message;\r
   while (GetMessage(&message, 0, 0, 0)) {\r
+    if (IsDialogMessage(dlg, &message)) continue;\r
     TranslateMessage(&message);\r
     DispatchMessage(&message);\r
   }\r
 \r
-  return message.wParam;\r
+  return (int) message.wParam;\r
 }\r
 \r
 void centre_window(HWND window) {\r
@@ -47,7 +248,7 @@ void centre_window(HWND window) {
 \r
   /* Find window size */\r
   if (! GetWindowRect(window, &size)) return;\r
-  \r
+\r
   /* Find desktop window */\r
   desktop = GetDesktopWindow();\r
   if (! desktop) return;\r
@@ -58,63 +259,542 @@ void centre_window(HWND window) {
   /* Centre window */\r
   x = (desktop_size.right - size.right) / 2;\r
   y = (desktop_size.bottom - size.bottom) / 2;\r
-  MoveWindow(window, x, y, size.right, size.bottom, 0);\r
+  MoveWindow(window, x, y, size.right - size.left, size.bottom - size.top, 0);\r
 }\r
 \r
-/* Install the service */\r
-int install(HWND window) {\r
-  if (! window) return 1;\r
+static inline void check_stop_method(nssm_service_t *service, unsigned long method, unsigned long control) {\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) return;\r
+  service->stop_method &= ~method;\r
+}\r
+\r
+static inline void check_number(HWND tab, unsigned long control, unsigned long *timeout) {\r
+  BOOL translated;\r
+  unsigned long configured = GetDlgItemInt(tab, control, &translated, 0);\r
+  if (translated) *timeout = configured;\r
+}\r
+\r
+static inline void set_timeout_enabled(unsigned long control, unsigned long dependent) {\r
+  unsigned char enabled = 0;\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], control, BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1;\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_SHUTDOWN], dependent), enabled);\r
+}\r
+\r
+static inline void set_affinity_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY), enabled);\r
+}\r
+\r
+static inline void set_rotation_enabled(unsigned char enabled) {\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE), enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS), enabled);\r
+  EnableWindow(GetDlgItem(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW), enabled);\r
+}\r
+\r
+static inline int hook_env(const TCHAR *hook_event, const TCHAR *hook_action, TCHAR *buffer, unsigned long buflen) {\r
+  return _sntprintf_s(buffer, buflen, _TRUNCATE, _T("NSSM_HOOK_%s_%s"), hook_event, hook_action);\r
+}\r
+\r
+static inline void set_hook_tab(int event_index, int action_index, bool changed) {\r
+  int first_event = NSSM_GUI_HOOK_EVENT_START;\r
+  HWND combo;\r
+  combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_EVENT);\r
+  SendMessage(combo, CB_SETCURSEL, event_index, 0);\r
+  combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_ACTION);\r
+  SendMessage(combo, CB_RESETCONTENT, 0, 0);\r
+\r
+  const TCHAR *hook_event = hook_event_strings[event_index];\r
+  TCHAR *hook_action;\r
+  int i;\r
+  switch (event_index + first_event) {\r
+    case NSSM_GUI_HOOK_EVENT_ROTATE:\r
+      i = 0;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_ROTATE_PRE));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_ROTATE_POST));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+      break;\r
+\r
+    case NSSM_GUI_HOOK_EVENT_START:\r
+      i = 0;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_START_PRE));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_START_POST));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+      break;\r
+\r
+    case NSSM_GUI_HOOK_EVENT_STOP:\r
+      i = 0;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_STOP_PRE));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_PRE;\r
+      break;\r
+\r
+    case NSSM_GUI_HOOK_EVENT_EXIT:\r
+      i = 0;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_EXIT_POST));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_POST;\r
+      break;\r
+\r
+    case NSSM_GUI_HOOK_EVENT_POWER:\r
+      i = 0;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_POWER_CHANGE));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_CHANGE;\r
+      SendMessage(combo, CB_INSERTSTRING, i, (LPARAM) message_string(NSSM_GUI_HOOK_ACTION_POWER_RESUME));\r
+      if (action_index == i++) hook_action = NSSM_HOOK_ACTION_RESUME;\r
+      break;\r
+  }\r
+\r
+  SendMessage(combo, CB_SETCURSEL, action_index, 0);\r
+\r
+  TCHAR hook_name[HOOK_NAME_LENGTH];\r
+  hook_env(hook_event, hook_action, hook_name, _countof(hook_name));\r
+\r
+  if (! *hook_name) return;\r
+\r
+  TCHAR cmd[CMD_LENGTH];\r
+  if (changed) {\r
+    GetDlgItemText(tablist[NSSM_TAB_HOOKS], IDC_HOOK, cmd, _countof(cmd));\r
+    SetEnvironmentVariable(hook_name, cmd);\r
+  }\r
+  else {\r
+    if (! GetEnvironmentVariable(hook_name, cmd, _countof(cmd))) cmd[0] = _T('\0');\r
+    SetDlgItemText(tablist[NSSM_TAB_HOOKS], IDC_HOOK, cmd);\r
+  }\r
+}\r
+\r
+static inline int update_hook(TCHAR *service_name, const TCHAR *hook_event, const TCHAR *hook_action) {\r
+  TCHAR hook_name[HOOK_NAME_LENGTH];\r
+  if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) return 1;\r
+  TCHAR cmd[CMD_LENGTH];\r
+  ZeroMemory(cmd, sizeof(cmd));\r
+  GetEnvironmentVariable(hook_name, cmd, _countof(cmd));\r
+  if (set_hook(service_name, hook_event, hook_action, cmd)) return 2;\r
+  return 0;\r
+}\r
+\r
+static inline int update_hooks(TCHAR *service_name) {\r
+  int ret = 0;\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE);\r
+  ret += update_hook(service_name, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST);\r
+  return ret;\r
+}\r
 \r
-  /* Check parameters in the window */\r
-  char name[STRING_SIZE];\r
-  char exe[MAX_PATH];\r
-  char flags[STRING_SIZE];\r
+static inline void check_io(HWND owner, TCHAR *name, TCHAR *buffer, unsigned long len, unsigned long control) {\r
+  if (! SendMessage(GetDlgItem(tablist[NSSM_TAB_IO], control), WM_GETTEXTLENGTH, 0, 0)) return;\r
+  if (GetDlgItemText(tablist[NSSM_TAB_IO], control, buffer, (int) len)) return;\r
+  popup_message(owner, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, name);\r
+  ZeroMemory(buffer, len * sizeof(TCHAR));\r
+}\r
+\r
+/* Set service parameters. */\r
+int configure(HWND window, nssm_service_t *service, nssm_service_t *orig_service) {\r
+  if (! service) return 1;\r
+\r
+  set_nssm_service_defaults(service);\r
+\r
+  if (orig_service) {\r
+    service->native = orig_service->native;\r
+    service->handle = orig_service->handle;\r
+  }\r
 \r
-  /* Get service name */\r
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {\r
-    MessageBox(0, "No valid service name was specified!", NSSM, MB_OK);\r
+  /* Get service name. */\r
+  if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) {\r
+    popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
+    cleanup_nssm_service(service);\r
     return 2;\r
   }\r
 \r
   /* Get executable name */\r
-  if (! GetDlgItemText(window, IDC_PATH, exe, sizeof(exe))) {\r
-    MessageBox(0, "No valid executable path was specified!", NSSM, MB_OK);\r
-    return 3;\r
+  if (! service->native) {\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_PATH, service->exe, _countof(service->exe))) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);\r
+      return 3;\r
+    }\r
+\r
+    /* Get startup directory. */\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_DIR, service->dir, _countof(service->dir))) {\r
+      _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
+      strip_basename(service->dir);\r
+    }\r
+\r
+    /* Get flags. */\r
+    if (SendMessage(GetDlgItem(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {\r
+      if (! GetDlgItemText(tablist[NSSM_TAB_APPLICATION], IDC_FLAGS, service->flags, _countof(service->flags))) {\r
+        popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);\r
+        return 4;\r
+      }\r
+    }\r
   }\r
 \r
-  /* Get flags */\r
-  if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {\r
-    if (! GetDlgItemText(window, IDC_FLAGS, flags, sizeof(flags))) {\r
-      MessageBox(0, "No valid options were specified!", NSSM, MB_OK);\r
-      return 4;\r
+  /* Get details. */\r
+  if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME), WM_GETTEXTLENGTH, 0, 0)) {\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DISPLAYNAME, service->displayname, _countof(service->displayname))) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DISPLAYNAME);\r
+      return 5;\r
     }\r
   }\r
-  else ZeroMemory(&flags, sizeof(flags));\r
 \r
-  /* See if it works */\r
-  switch (install_service(name, exe, flags)) {\r
+  if (SendMessage(GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION), WM_GETTEXTLENGTH, 0, 0)) {\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_DETAILS], IDC_DESCRIPTION, service->description, _countof(service->description))) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DESCRIPTION);\r
+      return 5;\r
+    }\r
+  }\r
+\r
+  HWND combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);\r
+  service->startup = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);\r
+  if (service->startup == CB_ERR) service->startup = 0;\r
+\r
+  /* Get logon stuff. */\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_INTERACT, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+      service->type |= SERVICE_INTERACTIVE_PROCESS;\r
+    }\r
+    if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
+    service->username = 0;\r
+    service->usernamelen = 0;\r
+    if (service->password) {\r
+      SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
+      HeapFree(GetProcessHeap(), 0, service->password);\r
+    }\r
+    service->password = 0;\r
+    service->passwordlen = 0;\r
+  }\r
+  else if (SendDlgItemMessage(tablist[NSSM_TAB_LOGON], IDC_VIRTUAL_SERVICE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+    if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
+    service->username = virtual_account(service->name);\r
+    if (! service->username) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));\r
+      return 6;\r
+    }\r
+    service->usernamelen = _tcslen(service->username) + 1;\r
+    service->password = 0;\r
+    service->passwordlen = 0;\r
+  }\r
+  else {\r
+    /* Username. */\r
+    service->usernamelen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_USERNAME), WM_GETTEXTLENGTH, 0, 0);\r
+    if (! service->usernamelen) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_USERNAME);\r
+      return 6;\r
+    }\r
+    service->usernamelen++;\r
+\r
+    service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));\r
+    if (! service->username) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("account name"), _T("install()"));\r
+      return 6;\r
+    }\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_USERNAME, service->username, (int) service->usernamelen)) {\r
+      HeapFree(GetProcessHeap(), 0, service->username);\r
+      service->username = 0;\r
+      service->usernamelen = 0;\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_USERNAME);\r
+      return 6;\r
+    }\r
+\r
+    /*\r
+      Special case for well-known accounts.\r
+      Ignore the password if we're editing and the username hasn't changed.\r
+    */\r
+    const TCHAR *well_known = well_known_username(service->username);\r
+    if (well_known) {\r
+      if (str_equiv(well_known, NSSM_LOCALSYSTEM_ACCOUNT)) {\r
+        HeapFree(GetProcessHeap(), 0, service->username);\r
+        service->username = 0;\r
+        service->usernamelen = 0;\r
+      }\r
+      else {\r
+        service->usernamelen = _tcslen(well_known) + 1;\r
+        service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->usernamelen * sizeof(TCHAR));\r
+        if (! service->username) {\r
+          print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("install()"));\r
+          return 6;\r
+        }\r
+        memmove(service->username, well_known, service->usernamelen * sizeof(TCHAR));\r
+      }\r
+    }\r
+    else {\r
+      /* Password. */\r
+      service->passwordlen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1), WM_GETTEXTLENGTH, 0, 0);\r
+      size_t passwordlen = SendMessage(GetDlgItem(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2), WM_GETTEXTLENGTH, 0, 0);\r
+\r
+      if (! orig_service || ! orig_service->username || ! str_equiv(service->username, orig_service->username) || service->passwordlen || passwordlen) {\r
+        if (! service->passwordlen) {\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+          return 6;\r
+        }\r
+        if (passwordlen != service->passwordlen) {\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+          return 6;\r
+        }\r
+        service->passwordlen++;\r
+\r
+        /* Temporary buffer for password validation. */\r
+        TCHAR *password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR));\r
+        if (! password) {\r
+          HeapFree(GetProcessHeap(), 0, service->username);\r
+          service->username = 0;\r
+          service->usernamelen = 0;\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password confirmation"), _T("install()"));\r
+          return 6;\r
+        }\r
+\r
+        /* Actual password buffer. */\r
+        service->password = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, service->passwordlen * sizeof(TCHAR));\r
+        if (! service->password) {\r
+          HeapFree(GetProcessHeap(), 0, password);\r
+          HeapFree(GetProcessHeap(), 0, service->username);\r
+          service->username = 0;\r
+          service->usernamelen = 0;\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("password"), _T("install()"));\r
+          return 6;\r
+        }\r
+\r
+        /* Get first password. */\r
+        if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_PASSWORD1, service->password, (int) service->passwordlen)) {\r
+          HeapFree(GetProcessHeap(), 0, password);\r
+          SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
+          HeapFree(GetProcessHeap(), 0, service->password);\r
+          service->password = 0;\r
+          service->passwordlen = 0;\r
+          HeapFree(GetProcessHeap(), 0, service->username);\r
+          service->username = 0;\r
+          service->usernamelen = 0;\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_PASSWORD);\r
+          return 6;\r
+        }\r
+\r
+        /* Get confirmation. */\r
+        if (! GetDlgItemText(tablist[NSSM_TAB_LOGON], IDC_PASSWORD2, password, (int) service->passwordlen)) {\r
+          SecureZeroMemory(password, service->passwordlen * sizeof(TCHAR));\r
+          HeapFree(GetProcessHeap(), 0, password);\r
+          SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
+          HeapFree(GetProcessHeap(), 0, service->password);\r
+          service->password = 0;\r
+          service->passwordlen = 0;\r
+          HeapFree(GetProcessHeap(), 0, service->username);\r
+          service->username = 0;\r
+          service->usernamelen = 0;\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_PASSWORD);\r
+          return 6;\r
+        }\r
+\r
+        /* Compare. */\r
+        if (_tcsncmp(password, service->password, service->passwordlen)) {\r
+          popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PASSWORD);\r
+          SecureZeroMemory(password, service->passwordlen * sizeof(TCHAR));\r
+          HeapFree(GetProcessHeap(), 0, password);\r
+          SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
+          HeapFree(GetProcessHeap(), 0, service->password);\r
+          service->password = 0;\r
+          service->passwordlen = 0;\r
+          HeapFree(GetProcessHeap(), 0, service->username);\r
+          service->username = 0;\r
+          service->usernamelen = 0;\r
+          return 6;\r
+        }\r
+      }\r
+    }\r
+  }\r
+\r
+  /* Get dependencies. */\r
+  unsigned long dependencieslen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES), WM_GETTEXTLENGTH, 0, 0);\r
+  if (dependencieslen) {\r
+    TCHAR *dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (dependencieslen + 2) * sizeof(TCHAR));\r
+    if (! dependencies) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("install()"));\r
+      cleanup_nssm_service(service);\r
+      return 6;\r
+    }\r
+\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_DEPENDENCIES], IDC_DEPENDENCIES, dependencies, dependencieslen + 1)) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_DEPENDENCIES);\r
+      HeapFree(GetProcessHeap(), 0, dependencies);\r
+      cleanup_nssm_service(service);\r
+      return 6;\r
+    }\r
+\r
+    if (unformat_double_null(dependencies, dependencieslen, &service->dependencies, &service->dependencieslen)) {\r
+      HeapFree(GetProcessHeap(), 0, dependencies);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("dependencies"), _T("install()"));\r
+      cleanup_nssm_service(service);\r
+      return 6;\r
+    }\r
+\r
+    HeapFree(GetProcessHeap(), 0, dependencies);\r
+  }\r
+\r
+  /* Remaining tabs are only for services we manage. */\r
+  if (service->native) return 0;\r
+\r
+  /* Get process stuff. */\r
+  combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+  service->priority = priority_index_to_constant((unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0));\r
+\r
+  service->affinity = 0LL;\r
+  if (! (SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_GETCHECK, 0, 0) & BST_CHECKED)) {\r
+    HWND list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+    int selected = (int) SendMessage(list, LB_GETSELCOUNT, 0, 0);\r
+    int count = (int) SendMessage(list, LB_GETCOUNT, 0, 0);\r
+    if (! selected) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_WARN_AFFINITY_NONE);\r
+      return 5;\r
+    }\r
+    else if (selected < count) {\r
+      for (int i = 0; i < count; i++) {\r
+        if (SendMessage(list, LB_GETSEL, i, 0)) service->affinity |= (1LL << (__int64) i);\r
+      }\r
+    }\r
+  }\r
+\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->no_console = 0;\r
+  else service->no_console = 1;\r
+\r
+  /* Get stop method stuff. */\r
+  check_stop_method(service, NSSM_STOP_METHOD_CONSOLE, IDC_METHOD_CONSOLE);\r
+  check_stop_method(service, NSSM_STOP_METHOD_WINDOW, IDC_METHOD_WINDOW);\r
+  check_stop_method(service, NSSM_STOP_METHOD_THREADS, IDC_METHOD_THREADS);\r
+  check_stop_method(service, NSSM_STOP_METHOD_TERMINATE, IDC_METHOD_TERMINATE);\r
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, &service->kill_console_delay);\r
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, &service->kill_window_delay);\r
+  check_number(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, &service->kill_threads_delay);\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->kill_process_tree = 1;\r
+  else service->kill_process_tree = 0;\r
+\r
+  /* Get exit action stuff. */\r
+  check_number(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, &service->throttle_delay);\r
+  combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
+  service->default_exit_action = (unsigned long) SendMessage(combo, CB_GETCURSEL, 0, 0);\r
+  if (service->default_exit_action == CB_ERR) service->default_exit_action = 0;\r
+  check_number(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, &service->restart_delay);\r
+\r
+  /* Get I/O stuff. */\r
+  check_io(window, _T("stdin"), service->stdin_path, _countof(service->stdin_path), IDC_STDIN);\r
+  check_io(window, _T("stdout"), service->stdout_path, _countof(service->stdout_path), IDC_STDOUT);\r
+  check_io(window, _T("stderr"), service->stderr_path, _countof(service->stderr_path), IDC_STDERR);\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_GETCHECK, 0, 0) & BST_CHECKED) service->timestamp_log = true;\r
+  else service->timestamp_log = false;\r
+\r
+  /* Override stdout and/or stderr. */\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_TRUNCATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+    if (service->stdout_path[0]) service->stdout_disposition = CREATE_ALWAYS;\r
+    if (service->stderr_path[0]) service->stderr_disposition = CREATE_ALWAYS;\r
+  }\r
+\r
+  /* Get rotation stuff. */\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+    service->rotate_files = true;\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_GETCHECK, 0, 0) & BST_CHECKED) service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_ONLINE;\r
+    check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, &service->rotate_seconds);\r
+    check_number(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, &service->rotate_bytes_low);\r
+  }\r
+\r
+  /* Get hook stuff. */\r
+  if (SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_GETCHECK, 0, 0) & BST_CHECKED) service->hook_share_output_handles = true;\r
+\r
+  /* Get environment. */\r
+  unsigned long envlen = (unsigned long) SendMessage(GetDlgItem(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT), WM_GETTEXTLENGTH, 0, 0);\r
+  if (envlen) {\r
+    TCHAR *env = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (envlen + 2) * sizeof(TCHAR));\r
+    if (! env) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));\r
+      cleanup_nssm_service(service);\r
+      return 5;\r
+    }\r
+\r
+    if (! GetDlgItemText(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT, env, envlen + 1)) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);\r
+      HeapFree(GetProcessHeap(), 0, env);\r
+      cleanup_nssm_service(service);\r
+      return 5;\r
+    }\r
+\r
+    TCHAR *newenv;\r
+    unsigned long newlen;\r
+    if (unformat_double_null(env, envlen, &newenv, &newlen)) {\r
+      HeapFree(GetProcessHeap(), 0, env);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("environment"), _T("install()"));\r
+      cleanup_nssm_service(service);\r
+      return 5;\r
+    }\r
+\r
+    HeapFree(GetProcessHeap(), 0, env);\r
+    env = newenv;\r
+    envlen = newlen;\r
+\r
+    /* Test the environment is valid. */\r
+    if (test_environment(env)) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_ENVIRONMENT);\r
+      HeapFree(GetProcessHeap(), 0, env);\r
+      cleanup_nssm_service(service);\r
+      return 5;\r
+    }\r
+\r
+    if (SendDlgItemMessage(tablist[NSSM_TAB_ENVIRONMENT], IDC_ENVIRONMENT_REPLACE, BM_GETCHECK, 0, 0) & BST_CHECKED) {\r
+      service->env = env;\r
+      service->envlen = envlen;\r
+    }\r
+    else {\r
+      service->env_extra = env;\r
+      service->env_extralen = envlen;\r
+    }\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+/* Install the service. */\r
+int install(HWND window) {\r
+  if (! window) return 1;\r
+\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (service) {\r
+    int ret = configure(window, service, 0);\r
+    if (ret) return ret;\r
+  }\r
+\r
+  /* See if it works. */\r
+  switch (install_service(service)) {\r
+    case 1:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("install()"));\r
+      cleanup_nssm_service(service);\r
+      return 1;\r
+\r
     case 2:\r
-      MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      MessageBox(0, "Path too long!\nThe full path to " NSSM " is too long.\nPlease install " NSSM " somewhere else...\n", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
+      cleanup_nssm_service(service);\r
       return 3;\r
 \r
     case 4:\r
-      MessageBox(0, "Error constructing ImagePath!\nThis really shouldn't happen.  You could be out of memory\nor the world may be about to end or something equally bad.", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
+      cleanup_nssm_service(service);\r
       return 4;\r
 \r
     case 5:\r
-      MessageBox(0, "Couldn't create service!\nPerhaps it is already installed...", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INSTALL_SERVICE_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 5;\r
 \r
     case 6:\r
-      MessageBox(0, "Couldn't set startup parameters for the service!\nDeleting the service...", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_CREATE_PARAMETERS_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 6;\r
   }\r
 \r
-  MessageBox(0, "Service successfully installed!", NSSM, MB_OK);\r
+  update_hooks(service->name);\r
+\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
+  cleanup_nssm_service(service);\r
   return 0;\r
 }\r
 \r
@@ -122,90 +802,556 @@ int install(HWND window) {
 int remove(HWND window) {\r
   if (! window) return 1;\r
 \r
-  /* Check parameters in the window */\r
-  char name[STRING_SIZE];\r
+  /* See if it works */\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (service) {\r
+    /* Get service name */\r
+    if (! GetDlgItemText(window, IDC_NAME, service->name, _countof(service->name))) {\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);\r
+      cleanup_nssm_service(service);\r
+      return 2;\r
+    }\r
 \r
-  /* Get service name */\r
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {\r
-    MessageBox(0, "No valid service name was specified!", NSSM, MB_OK);\r
-    return 2;\r
+    /* Confirm */\r
+    if (popup_message(window, MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) {\r
+      cleanup_nssm_service(service);\r
+      return 0;\r
+    }\r
   }\r
 \r
-  /* Confirm */\r
-  char blurb[MAX_PATH];\r
-  if (snprintf(blurb, sizeof(blurb), "Remove the \"%s\" service?", name) < 0) {\r
-    if (MessageBox(0, "Remove the service?", NSSM, MB_YESNO) != IDYES) return 0;\r
-  }\r
-  else if (MessageBox(0, blurb, NSSM, MB_YESNO) != IDYES) return 0;\r
+  switch (remove_service(service)) {\r
+    case 1:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("remove()"));\r
+      cleanup_nssm_service(service);\r
+      return 1;\r
 \r
-  /* See if it works */\r
-  switch (remove_service(name)) {\r
     case 2:\r
-      MessageBox(0, "Can't open service manager!\nPerhaps you need to be an administrator...", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 2;\r
 \r
     case 3:\r
-      MessageBox(0, "Can't open service!\nPerhaps it isn't installed...", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_SERVICE_NOT_INSTALLED);\r
+      cleanup_nssm_service(service);\r
       return 3;\r
 \r
     case 4:\r
-      MessageBox(0, "Can't delete service!  Make sure the service is stopped and try again.\nIf this error persists, you may need to set the service NOT to start\nautomatically, reboot your computer and try removing it again.", NSSM, MB_OK);\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_REMOVE_SERVICE_FAILED);\r
+      cleanup_nssm_service(service);\r
       return 4;\r
   }\r
 \r
-  MessageBox(0, "Service successfully removed!", NSSM, MB_OK);\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
+  cleanup_nssm_service(service);\r
   return 0;\r
 }\r
 \r
-/* Browse for game */\r
-void browse(HWND window) {\r
+int edit(HWND window, nssm_service_t *orig_service) {\r
+  if (! window) return 1;\r
+\r
+  nssm_service_t *service = alloc_nssm_service();\r
+  if (service) {\r
+    int ret = configure(window, service, orig_service);\r
+    if (ret) return ret;\r
+  }\r
+\r
+  switch (edit_service(service, true)) {\r
+    case 1:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("edit()"));\r
+      cleanup_nssm_service(service);\r
+      return 1;\r
+\r
+    case 3:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);\r
+      cleanup_nssm_service(service);\r
+      return 3;\r
+\r
+    case 4:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);\r
+      cleanup_nssm_service(service);\r
+      return 4;\r
+\r
+    case 5:\r
+    case 6:\r
+      popup_message(window, MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_EDIT_PARAMETERS_FAILED);\r
+      cleanup_nssm_service(service);\r
+      return 6;\r
+  }\r
+\r
+  update_hooks(service->name);\r
+\r
+  popup_message(window, MB_OK, NSSM_MESSAGE_SERVICE_EDITED, service->name);\r
+  cleanup_nssm_service(service);\r
+  return 0;\r
+}\r
+\r
+static TCHAR *browse_filter(int message) {\r
+  switch (message) {\r
+    case NSSM_GUI_BROWSE_FILTER_APPLICATIONS: return _T("*.exe;*.bat;*.cmd");\r
+    case NSSM_GUI_BROWSE_FILTER_DIRECTORIES: return _T(".");\r
+    case NSSM_GUI_BROWSE_FILTER_ALL_FILES: /* Fall through. */\r
+    default: return _T("*.*");\r
+  }\r
+}\r
+\r
+UINT_PTR CALLBACK browse_hook(HWND dlg, UINT message, WPARAM w, LPARAM l) {\r
+  switch (message) {\r
+    case WM_INITDIALOG:\r
+      return 1;\r
+  }\r
+\r
+  return 0;\r
+}\r
+\r
+/* Browse for application */\r
+void browse(HWND window, TCHAR *current, unsigned long flags, ...) {\r
   if (! window) return;\r
 \r
+  va_list arg;\r
+  size_t bufsize = 256;\r
+  size_t len = bufsize;\r
+  int i;\r
+\r
   OPENFILENAME ofn;\r
   ZeroMemory(&ofn, sizeof(ofn));\r
   ofn.lStructSize = sizeof(ofn);\r
-  ofn.lpstrFilter = "Applications\0*.exe\0All files\0*.*\0\0";\r
-  ofn.lpstrFile = new char[MAX_PATH];\r
-  ofn.lpstrFile[0] = '\0';\r
-  ofn.lpstrTitle = "Locate application file";\r
-  ofn.nMaxFile = MAX_PATH;\r
-  ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;\r
+  ofn.lpstrFilter = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, bufsize * sizeof(TCHAR));\r
+  /* XXX: Escaping nulls with FormatMessage is tricky */\r
+  if (ofn.lpstrFilter) {\r
+    ZeroMemory((void *) ofn.lpstrFilter, bufsize);\r
+    len = 0;\r
+    /* "Applications" + NULL + "*.exe" + NULL */\r
+    va_start(arg, flags);\r
+    while (i = va_arg(arg, int)) {\r
+      TCHAR *localised = message_string(i);\r
+      _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, localised);\r
+      len += _tcslen(localised) + 1;\r
+      LocalFree(localised);\r
+      TCHAR *filter = browse_filter(i);\r
+      _sntprintf_s((TCHAR *) ofn.lpstrFilter + len, bufsize - len, _TRUNCATE, _T("%s"), filter);\r
+      len += _tcslen(filter) + 1;\r
+    }\r
+    va_end(arg);\r
+    /* Remainder of the buffer is already zeroed */\r
+  }\r
+  ofn.lpstrFile = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, PATH_LENGTH * sizeof(TCHAR));\r
+  if (ofn.lpstrFile) {\r
+    if (flags & OFN_NOVALIDATE) {\r
+      /* Directory hack. */\r
+      _sntprintf_s(ofn.lpstrFile, PATH_LENGTH, _TRUNCATE, _T(":%s:"), message_string(NSSM_GUI_BROWSE_FILTER_DIRECTORIES));\r
+      ofn.nMaxFile = DIR_LENGTH;\r
+    }\r
+    else {\r
+      _sntprintf_s(ofn.lpstrFile, PATH_LENGTH, _TRUNCATE, _T("%s"), current);\r
+      ofn.nMaxFile = PATH_LENGTH;\r
+    }\r
+  }\r
+  ofn.lpstrTitle = message_string(NSSM_GUI_BROWSE_TITLE);\r
+  ofn.Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | flags;\r
 \r
   if (GetOpenFileName(&ofn)) {\r
+    /* Directory hack. */\r
+    if (flags & OFN_NOVALIDATE) strip_basename(ofn.lpstrFile);\r
     SendMessage(window, WM_SETTEXT, 0, (LPARAM) ofn.lpstrFile);\r
   }\r
+  if (ofn.lpstrFilter) HeapFree(GetProcessHeap(), 0, (void *) ofn.lpstrFilter);\r
+  if (ofn.lpstrFile) HeapFree(GetProcessHeap(), 0, ofn.lpstrFile);\r
+}\r
+\r
+INT_PTR CALLBACK tab_dlg(HWND tab, UINT message, WPARAM w, LPARAM l) {\r
+  switch (message) {\r
+    case WM_INITDIALOG:\r
+      return 1;\r
+\r
+    /* Button was pressed or control was controlled. */\r
+    case WM_COMMAND:\r
+      HWND dlg;\r
+      TCHAR buffer[PATH_LENGTH];\r
+      unsigned char enabled;\r
 \r
-  delete[] ofn.lpstrFile;\r
+      switch (LOWORD(w)) {\r
+        /* Browse for application. */\r
+        case IDC_BROWSE:\r
+          dlg = GetDlgItem(tab, IDC_PATH);\r
+          GetDlgItemText(tab, IDC_PATH, buffer, _countof(buffer));\r
+          browse(dlg, buffer, OFN_FILEMUSTEXIST, NSSM_GUI_BROWSE_FILTER_APPLICATIONS, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          /* Fill in startup directory if it wasn't already specified. */\r
+          GetDlgItemText(tab, IDC_DIR, buffer, _countof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_PATH, buffer, _countof(buffer));\r
+            strip_basename(buffer);\r
+            SetDlgItemText(tab, IDC_DIR, buffer);\r
+          }\r
+          break;\r
+\r
+        /* Browse for startup directory. */\r
+        case IDC_BROWSE_DIR:\r
+          dlg = GetDlgItem(tab, IDC_DIR);\r
+          GetDlgItemText(tab, IDC_DIR, buffer, _countof(buffer));\r
+          browse(dlg, buffer, OFN_NOVALIDATE, NSSM_GUI_BROWSE_FILTER_DIRECTORIES, 0);\r
+          break;\r
+\r
+        /* Log on. */\r
+        case IDC_LOCALSYSTEM:\r
+          set_logon_enabled(1, 0);\r
+          break;\r
+\r
+        case IDC_VIRTUAL_SERVICE:\r
+          set_logon_enabled(0, 0);\r
+          break;\r
+\r
+        case IDC_ACCOUNT:\r
+          set_logon_enabled(0, 1);\r
+          break;\r
+\r
+        /* Affinity. */\r
+        case IDC_AFFINITY_ALL:\r
+          if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 0;\r
+          else enabled = 1;\r
+          set_affinity_enabled(enabled);\r
+          break;\r
+\r
+        /* Shutdown methods. */\r
+        case IDC_METHOD_CONSOLE:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_CONSOLE);\r
+          break;\r
+\r
+        case IDC_METHOD_WINDOW:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_WINDOW);\r
+          break;\r
+\r
+        case IDC_METHOD_THREADS:\r
+          set_timeout_enabled(LOWORD(w), IDC_KILL_THREADS);\r
+          break;\r
+\r
+        /* Browse for stdin. */\r
+        case IDC_BROWSE_STDIN:\r
+          dlg = GetDlgItem(tab, IDC_STDIN);\r
+          GetDlgItemText(tab, IDC_STDIN, buffer, _countof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+\r
+        /* Browse for stdout. */\r
+        case IDC_BROWSE_STDOUT:\r
+          dlg = GetDlgItem(tab, IDC_STDOUT);\r
+          GetDlgItemText(tab, IDC_STDOUT, buffer, _countof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          /* Fill in stderr if it wasn't already specified. */\r
+          GetDlgItemText(tab, IDC_STDERR, buffer, _countof(buffer));\r
+          if (! buffer[0]) {\r
+            GetDlgItemText(tab, IDC_STDOUT, buffer, _countof(buffer));\r
+            SetDlgItemText(tab, IDC_STDERR, buffer);\r
+          }\r
+          break;\r
+\r
+        /* Browse for stderr. */\r
+        case IDC_BROWSE_STDERR:\r
+          dlg = GetDlgItem(tab, IDC_STDERR);\r
+          GetDlgItemText(tab, IDC_STDERR, buffer, _countof(buffer));\r
+          browse(dlg, buffer, 0, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+\r
+        /* Rotation. */\r
+        case IDC_ROTATE:\r
+          if (SendDlgItemMessage(tab, LOWORD(w), BM_GETCHECK, 0, 0) & BST_CHECKED) enabled = 1;\r
+          else enabled = 0;\r
+          set_rotation_enabled(enabled);\r
+          break;\r
+\r
+        /* Hook event. */\r
+        case IDC_HOOK_EVENT:\r
+          if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), 0, false);\r
+          break;\r
+\r
+        /* Hook action. */\r
+        case IDC_HOOK_ACTION:\r
+          if (HIWORD(w) == CBN_SELCHANGE) set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), false);\r
+          break;\r
+\r
+        /* Browse for hook. */\r
+        case IDC_BROWSE_HOOK:\r
+          dlg = GetDlgItem(tab, IDC_HOOK);\r
+          GetDlgItemText(tab, IDC_HOOK, buffer, _countof(buffer));\r
+          browse(dlg, _T(""), OFN_FILEMUSTEXIST, NSSM_GUI_BROWSE_FILTER_ALL_FILES, 0);\r
+          break;\r
+\r
+        /* Hook. */\r
+        case IDC_HOOK:\r
+          set_hook_tab((int) SendMessage(GetDlgItem(tab, IDC_HOOK_EVENT), CB_GETCURSEL, 0, 0), (int) SendMessage(GetDlgItem(tab, IDC_HOOK_ACTION), CB_GETCURSEL, 0, 0), true);\r
+          break;\r
+      }\r
+      return 1;\r
+  }\r
+\r
+  return 0;\r
 }\r
 \r
 /* Install/remove dialogue callback */\r
-int CALLBACK install_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {\r
+INT_PTR CALLBACK nssm_dlg(HWND window, UINT message, WPARAM w, LPARAM l) {\r
+  nssm_service_t *service;\r
+\r
   switch (message) {\r
     /* Creating the dialogue */\r
     case WM_INITDIALOG:\r
+      service = (nssm_service_t *) l;\r
+\r
+      SetFocus(GetDlgItem(window, IDC_NAME));\r
+\r
+      HWND tabs;\r
+      HWND combo;\r
+      HWND list;\r
+      int i, n;\r
+      tabs = GetDlgItem(window, IDC_TAB1);\r
+      if (! tabs) return 0;\r
+\r
+      /* Set up tabs. */\r
+      TCITEM tab;\r
+      ZeroMemory(&tab, sizeof(tab));\r
+      tab.mask = TCIF_TEXT;\r
+\r
+      selected_tab = 0;\r
+\r
+      /* Application tab. */\r
+      if (service->native) tab.pszText = message_string(NSSM_GUI_TAB_NATIVE);\r
+      else tab.pszText = message_string(NSSM_GUI_TAB_APPLICATION);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_APPLICATION, (LPARAM) &tab);\r
+      if (service->native) {\r
+        tablist[NSSM_TAB_APPLICATION] = dialog(MAKEINTRESOURCE(IDD_NATIVE), window, tab_dlg);\r
+        EnableWindow(tablist[NSSM_TAB_APPLICATION], 0);\r
+        EnableWindow(GetDlgItem(tablist[NSSM_TAB_APPLICATION], IDC_PATH), 0);\r
+      }\r
+      else tablist[NSSM_TAB_APPLICATION] = dialog(MAKEINTRESOURCE(IDD_APPLICATION), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_APPLICATION], SW_SHOW);\r
+\r
+      /* Details tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_DETAILS);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_DETAILS, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_DETAILS] = dialog(MAKEINTRESOURCE(IDD_DETAILS), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_DETAILS], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      combo = GetDlgItem(tablist[NSSM_TAB_DETAILS], IDC_STARTUP);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_AUTOMATIC, (LPARAM) message_string(NSSM_GUI_STARTUP_AUTOMATIC));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_DELAYED, (LPARAM) message_string(NSSM_GUI_STARTUP_DELAYED));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_MANUAL, (LPARAM) message_string(NSSM_GUI_STARTUP_MANUAL));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_STARTUP_DISABLED, (LPARAM) message_string(NSSM_GUI_STARTUP_DISABLED));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_STARTUP_AUTOMATIC, 0);\r
+\r
+      /* Logon tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_LOGON);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_LOGON, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_LOGON] = dialog(MAKEINTRESOURCE(IDD_LOGON), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_LOGON], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      CheckRadioButton(tablist[NSSM_TAB_LOGON], IDC_LOCALSYSTEM, IDC_ACCOUNT, IDC_LOCALSYSTEM);\r
+      set_logon_enabled(1, 0);\r
+\r
+      /* Dependencies tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_DEPENDENCIES);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_DEPENDENCIES, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_DEPENDENCIES] = dialog(MAKEINTRESOURCE(IDD_DEPENDENCIES), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_DEPENDENCIES], SW_HIDE);\r
+\r
+      /* Remaining tabs are only for services we manage. */\r
+      if (service->native) return 1;\r
+\r
+      /* Process tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_PROCESS);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_PROCESS, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_PROCESS] = dialog(MAKEINTRESOURCE(IDD_PROCESS), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_PROCESS], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      combo = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_PRIORITY);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_REALTIME_PRIORITY, (LPARAM) message_string(NSSM_GUI_REALTIME_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_HIGH_PRIORITY, (LPARAM) message_string(NSSM_GUI_HIGH_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_ABOVE_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_ABOVE_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_BELOW_NORMAL_PRIORITY, (LPARAM) message_string(NSSM_GUI_BELOW_NORMAL_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_IDLE_PRIORITY, (LPARAM) message_string(NSSM_GUI_IDLE_PRIORITY_CLASS));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_NORMAL_PRIORITY, 0);\r
+\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_CONSOLE, BM_SETCHECK, BST_CHECKED, 0);\r
+\r
+      list = GetDlgItem(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY);\r
+      n = num_cpus();\r
+      SendMessage(list, LB_SETCOLUMNWIDTH, 16, 0);\r
+      for (i = 0; i < n; i++) {\r
+        TCHAR buffer[3];\r
+        _sntprintf_s(buffer, _countof(buffer), _TRUNCATE, _T("%d"), i);\r
+        SendMessage(list, LB_ADDSTRING, 0, (LPARAM) buffer);\r
+      }\r
+\r
+      /*\r
+        Size to fit.\r
+        The box is high enough for four rows.  It is wide enough for eight\r
+        columns without scrolling.  With scrollbars it shrinks to two rows.\r
+        Note that the above only holds if we set the column width BEFORE\r
+        adding the strings.\r
+      */\r
+      if (n < 32) {\r
+        int columns = (n - 1) / 4;\r
+        RECT rect;\r
+        GetWindowRect(list, &rect);\r
+        int width = rect.right - rect.left;\r
+        width -= (7 - columns) * 16;\r
+        int height = rect.bottom - rect.top;\r
+        if (n < 4) height -= (int) SendMessage(list, LB_GETITEMHEIGHT, 0, 0) * (4 - n);\r
+        SetWindowPos(list, 0, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER);\r
+      }\r
+      SendMessage(list, LB_SELITEMRANGE, 1, MAKELPARAM(0, n));\r
+\r
+      SendDlgItemMessage(tablist[NSSM_TAB_PROCESS], IDC_AFFINITY_ALL, BM_SETCHECK, BST_CHECKED, 0);\r
+      set_affinity_enabled(0);\r
+\r
+      /* Shutdown tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_SHUTDOWN);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_SHUTDOWN, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_SHUTDOWN] = dialog(MAKEINTRESOURCE(IDD_SHUTDOWN), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_SHUTDOWN], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_CONSOLE, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_CONSOLE, NSSM_KILL_CONSOLE_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_WINDOW, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_WINDOW, NSSM_KILL_WINDOW_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_THREADS, BM_SETCHECK, BST_CHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_THREADS, NSSM_KILL_THREADS_GRACE_PERIOD, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_METHOD_TERMINATE, BM_SETCHECK, BST_CHECKED, 0);\r
+      SendDlgItemMessage(tablist[NSSM_TAB_SHUTDOWN], IDC_KILL_PROCESS_TREE, BM_SETCHECK, BST_CHECKED, 1);\r
+\r
+      /* Restart tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_EXIT);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText);\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_EXIT, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_EXIT] = dialog(MAKEINTRESOURCE(IDD_APPEXIT), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_EXIT], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_THROTTLE, NSSM_RESET_THROTTLE_RESTART, 0);\r
+      combo = GetDlgItem(tablist[NSSM_TAB_EXIT], IDC_APPEXIT);\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_RESTART, (LPARAM) message_string(NSSM_GUI_EXIT_RESTART));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_IGNORE, (LPARAM) message_string(NSSM_GUI_EXIT_IGNORE));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_REALLY, (LPARAM) message_string(NSSM_GUI_EXIT_REALLY));\r
+      SendMessage(combo, CB_INSERTSTRING, NSSM_EXIT_UNCLEAN, (LPARAM) message_string(NSSM_GUI_EXIT_UNCLEAN));\r
+      SendMessage(combo, CB_SETCURSEL, NSSM_EXIT_RESTART, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_EXIT], IDC_RESTART_DELAY, 0, 0);\r
+\r
+      /* I/O tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_IO);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_IO, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_IO] = dialog(MAKEINTRESOURCE(IDD_IO), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_IO], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SendDlgItemMessage(tablist[NSSM_TAB_IO], IDC_TIMESTAMP, BM_SETCHECK, BST_UNCHECKED, 0);\r
+\r
+      /* Rotation tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_ROTATION);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ROTATION, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_ROTATION] = dialog(MAKEINTRESOURCE(IDD_ROTATION), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_ROTATION], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      SendDlgItemMessage(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_ONLINE, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_SECONDS, 0, 0);\r
+      SetDlgItemInt(tablist[NSSM_TAB_ROTATION], IDC_ROTATE_BYTES_LOW, 0, 0);\r
+      set_rotation_enabled(0);\r
+\r
+      /* Environment tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_ENVIRONMENT);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_ENVIRONMENT, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_ENVIRONMENT] = dialog(MAKEINTRESOURCE(IDD_ENVIRONMENT), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_ENVIRONMENT], SW_HIDE);\r
+\r
+      /* Hooks tab. */\r
+      tab.pszText = message_string(NSSM_GUI_TAB_HOOKS);\r
+      tab.cchTextMax = (int) _tcslen(tab.pszText) + 1;\r
+      SendMessage(tabs, TCM_INSERTITEM, NSSM_TAB_HOOKS, (LPARAM) &tab);\r
+      tablist[NSSM_TAB_HOOKS] = dialog(MAKEINTRESOURCE(IDD_HOOKS), window, tab_dlg);\r
+      ShowWindow(tablist[NSSM_TAB_HOOKS], SW_HIDE);\r
+\r
+      /* Set defaults. */\r
+      combo = GetDlgItem(tablist[NSSM_TAB_HOOKS], IDC_HOOK_EVENT);\r
+      SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_START));\r
+      SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_STOP));\r
+      SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_EXIT));\r
+      SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_POWER));\r
+      SendMessage(combo, CB_INSERTSTRING, -1, (LPARAM) message_string(NSSM_GUI_HOOK_EVENT_ROTATE));\r
+      SendDlgItemMessage(tablist[NSSM_TAB_HOOKS], IDC_REDIRECT_HOOK, BM_SETCHECK, BST_UNCHECKED, 0);\r
+      if (_tcslen(service->name)) {\r
+        TCHAR hook_name[HOOK_NAME_LENGTH];\r
+        TCHAR cmd[CMD_LENGTH];\r
+        for (i = 0; hook_event_strings[i]; i++) {\r
+          const TCHAR *hook_event = hook_event_strings[i];\r
+          int j;\r
+          for (j = 0; hook_action_strings[j]; j++) {\r
+            const TCHAR *hook_action = hook_action_strings[j];\r
+            if (! valid_hook_name(hook_event, hook_action, true)) continue;\r
+            if (get_hook(service->name, hook_event, hook_action, cmd, sizeof(cmd))) continue;\r
+            if (hook_env(hook_event, hook_action, hook_name, _countof(hook_name)) < 0) continue;\r
+            SetEnvironmentVariable(hook_name, cmd);\r
+          }\r
+        }\r
+      }\r
+      set_hook_tab(0, 0, false);\r
+\r
       return 1;\r
 \r
+    /* Tab change. */\r
+    case WM_NOTIFY:\r
+      NMHDR *notification;\r
+\r
+      notification = (NMHDR *) l;\r
+      switch (notification->code) {\r
+        case TCN_SELCHANGE:\r
+          HWND tabs;\r
+          int selection;\r
+\r
+          tabs = GetDlgItem(window, IDC_TAB1);\r
+          if (! tabs) return 0;\r
+\r
+          selection = (int) SendMessage(tabs, TCM_GETCURSEL, 0, 0);\r
+          if (selection != selected_tab) {\r
+            ShowWindow(tablist[selected_tab], SW_HIDE);\r
+            ShowWindow(tablist[selection], SW_SHOWDEFAULT);\r
+            SetFocus(GetDlgItem(window, IDOK));\r
+            selected_tab = selection;\r
+          }\r
+          return 1;\r
+      }\r
+\r
+      return 0;\r
+\r
     /* Button was pressed or control was controlled */\r
     case WM_COMMAND:\r
       switch (LOWORD(w)) {\r
         /* OK button */\r
-        case IDC_OK:\r
-          PostQuitMessage(install(window));\r
+        case IDOK:\r
+          if ((int) GetWindowLongPtr(window, GWLP_USERDATA) == IDD_EDIT) {\r
+            if (! edit(window, (nssm_service_t *) GetWindowLongPtr(window, DWLP_USER))) PostQuitMessage(0);\r
+          }\r
+          else if (! install(window)) PostQuitMessage(0);\r
           break;\r
 \r
         /* Cancel button */\r
-        case IDC_CANCEL:\r
+        case IDCANCEL:\r
           DestroyWindow(window);\r
           break;\r
 \r
-        /* Browse button */\r
-        case IDC_BROWSE:\r
-          browse(GetDlgItem(window, IDC_PATH));\r
-          break;\r
-\r
         /* Remove button */\r
         case IDC_REMOVE:\r
-          PostQuitMessage(remove(window));\r
+          if (! remove(window)) PostQuitMessage(0);\r
           break;\r
       }\r
       return 1;\r