There is the following situation:
1. We work under OS supporting UAC.
2. The user starts the main application without elevated privileges (UAC).
Task:
From the main application running with “no elevated privileges”, it is required to start / stop services. This already requires an increase in the authority of the UAC. I would like to implement a temporary increase in authority for the operation to start the service stop. At the same time, of course, it is not necessary to increase the powers of the main process “forever”.
The following solutions have been tried:
1) Using standard WinAPI service control functions: OpenService, ControlService, StartService.
If you do "in the forehead", then OpenService does not show the UAC boost dialog and returns code 5 - no access. (and does not inform in any way that this is due to a non-elevated UAC).
It turns out that for further work on this path you need to do:
1. Find a WinAPI method that finds out that for the current operator, the service control functions are, in principle, allowed, but “downgraded” by the UAC.
2. Somehow to find a method to programmatically launch a user permission request to raise UAC.
3. Get elevated details and using them already call service management functions.
2) Option number 2 use external console utilities such as net, sc
(Via CreateProcess or ShellExecuteEx)
What happens:
- if done in the forehead, the utilities themselves do not have a manifest with a request for increasing authority, so they run, but return the Access denied error code (again without specifying that this is due to a non-elevated UAC).
What can be done:
- the “workaround” option: to make your own utility, in the manifest of which there will be a request for increasing authority, and the utility itself will in fact receive and, for example, start scroll MyService or net start MyService from its account. So it happened. But the idea itself with an intermediate exe-nick looks "not beautiful"
If, however, modifying this option “beautifully”, then again it boils down to the tasks listed above:
1. Find out that it makes sense for us to request UAC enhancements to manage this service.
2. Somehow request the UAC enhancement dialog and get a handle with which already:
3. Execute createProcess ShellExecute from under the received account
Can someone suggest points one and two?
Just in case some additional restrictions:
1. I would not like to use any external solutions / utilities other than those built into the OS (or proprietary).
2. In the task we do not need to ask the user to enter some other login / password from the current one. Only increase authority.
What is implemented (ServiceManager - exe-file requiring elevated privileges - executes sc start MyService from the command line):
int nRes = -1; // сначала попробуем запустить "по старом". Если будут ошибки "элевации", то // будем пытаться по другому: { STARTUPINFO si; PROCESS_INFORMATION pi; memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFlags = STARTF_USESHOWWINDOW; si.wShowWindow = SW_SHOW; AnsiString strProcess = "ServiceManager.exe sc start MyService"; BOOL fApiRes = ::CreateProcess( NULL, strProcess.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); if(!fApiRes) { // Были ошибки запуска: DWORD dwError = ::GetLastError(); if(dwError != ERROR_ELEVATION_REQUIRED /*740*/) { ShowMessage("Оperation failed. Code: " + IntToStr(dwError)); return dwError; } else { // Тут провалимся ниже, там обработка... } } else { // Всё запустилось без ошибок. return 0; } } // Если мы здесь, то будем повышать права SHELLEXECUTEINFO sei; memset(&sei, 0, sizeof(SHELLEXECUTEINFO)); sei.cbSize = sizeof(SHELLEXECUTEINFO); sei.fMask = SEE_MASK_NOCLOSEPROCESS; sei.lpVerb = "runas"; sei.lpFile = "ServiceManager.exe"; sei.lpParameters = "sc start MyService" sei.nShow = SW_SHOWNORMAL; bool fResult = ShellExecuteEx(&sei); DWORD dwResult = ::WaitForSingleObject(sei.hProcess, INFINITE); // dwResult = 0 nRes = 0; // На случай успешного выполнения. Если ошибка - рез-т поменяется ниже if(fResult) { DWORD dwCode = -1; fResult = ::GetExitCodeProcess(sei.hProcess, &dwCode); nRes = (int)dwCode; DWORD dwError = ::GetLastError(); if(!fResult) { DWORD dwError = ::GetLastError(); __L_BAD(m_pLog, "::GetExitCodeProcess return error: " + AnsiString(dwError)); nRes = dwError; } else if(dwCode) { __L_BAD(m_pLog, "Application return error code: " + AnsiString(nRes)); } } else { __L_BAD(m_pLog, "Error in WaitForSingleObject"); ::TerminateProcess(sei.hProcess, -1); nRes = -11; } ::CloseHandle(sei.hProcess); return nRes; Thanks a lot in advance