UAC awareness.
authorIain Patterson <me@iain.cx>
Thu, 6 Feb 2014 21:30:06 +0000 (21:30 +0000)
committerIain Patterson <me@iain.cx>
Thu, 6 Feb 2014 21:38:51 +0000 (21:38 +0000)
Relaunch NSSM with administrator privileges if an attempt to install,
edit or remove a service fails.

The new process won't inherit the original console so its output won't
be visible.  For that reason we don't attempt to elevate for operations
which need to be able to display messages, such as controlling a service
or editing parameters.

Thanks to various people for requesting the feature but mainly to Marco
Certelli for advice on the implementation.

nssm.cpp

index e29ead6..7eef69d 100644 (file)
--- a/nssm.cpp
+++ b/nssm.cpp
@@ -55,6 +55,44 @@ void check_admin() {
   FreeSid(AdministratorsGroup);\r
 }\r
 \r
+static int elevate(int argc, TCHAR **argv, unsigned long message) {\r
+  print_message(stderr, message);\r
+\r
+  SHELLEXECUTEINFO sei;\r
+  ZeroMemory(&sei, sizeof(sei));\r
+  sei.cbSize = sizeof(sei);\r
+  sei.lpVerb = _T("runas");\r
+  sei.lpFile = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, PATH_LENGTH);\r
+  if (! sei.lpFile) {\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetModuleFileName()"), _T("elevate()"));\r
+    return 111;\r
+  }\r
+  GetModuleFileName(0, (TCHAR *) sei.lpFile, PATH_LENGTH);\r
+\r
+  TCHAR *args = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EXE_LENGTH * sizeof(TCHAR));\r
+  if (! args) {\r
+    HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile);\r
+    print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("GetCommandLine()"), _T("elevate()"));\r
+    return 111;\r
+  }\r
+\r
+  /* Get command line, which includes the path to NSSM, and skip that part. */\r
+  _sntprintf_s(args, EXE_LENGTH, _TRUNCATE, _T("%s"), GetCommandLine());\r
+  size_t s = _tcslen(argv[0]) + 1;\r
+  if (args[0] == _T('"')) s += 2;\r
+  while (isspace(args[s])) s++;\r
+\r
+  sei.lpParameters = args + s;\r
+  sei.nShow = SW_SHOW;\r
+\r
+  unsigned long exitcode = 0;\r
+  if (! ShellExecuteEx(&sei)) exitcode = 100;\r
+\r
+  HeapFree(GetProcessHeap(), 0, (void *) sei.lpFile);\r
+  HeapFree(GetProcessHeap(), 0, (void *) args);\r
+  return exitcode;\r
+}\r
+\r
 int num_cpus() {\r
   DWORD_PTR i, affinity, system_affinity;\r
   if (! GetProcessAffinityMask(GetCurrentProcess(), &affinity, &system_affinity)) return 64;\r
@@ -98,27 +136,18 @@ int _tmain(int argc, TCHAR **argv) {
     if (str_equiv(argv[1], _T("status"))) exit(control_service(SERVICE_CONTROL_INTERROGATE, argc - 2, argv + 2));\r
     if (str_equiv(argv[1], _T("rotate"))) exit(control_service(NSSM_SERVICE_CONTROL_ROTATE, argc - 2, argv + 2));\r
     if (str_equiv(argv[1], _T("install"))) {\r
-      if (! is_admin) {\r
-        print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL);\r
-        exit(100);\r
-      }\r
+      if (! is_admin) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_INSTALL));\r
       exit(pre_install_service(argc - 2, argv + 2));\r
     }\r
     if (str_equiv(argv[1], _T("edit")) || str_equiv(argv[1], _T("get")) || str_equiv(argv[1], _T("set")) || str_equiv(argv[1], _T("reset")) || str_equiv(argv[1], _T("unset"))) {\r
-      if (! is_admin) {\r
-        print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT);\r
-        exit(100);\r
-      }\r
       int ret = pre_edit_service(argc - 1, argv + 1);\r
+      if (ret == 3 && ! is_admin && argc == 3) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_EDIT));\r
       /* There might be a password here. */\r
       for (int i = 0; i < argc; i++) SecureZeroMemory(argv[i], _tcslen(argv[i]) * sizeof(TCHAR));\r
       exit(ret);\r
     }\r
     if (str_equiv(argv[1], _T("remove"))) {\r
-      if (! is_admin) {\r
-        print_message(stderr, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE);\r
-        exit(100);\r
-      }\r
+      if (! is_admin) exit(elevate(argc, argv, NSSM_MESSAGE_NOT_ADMINISTRATOR_CANNOT_REMOVE));\r
       exit(pre_remove_service(argc - 2, argv + 2));\r
     }\r
   }\r