Hi everyone:
I have a Windows service written in C++/WinAPI that runs in a local-system context. The service may perform some configurable power actions (such as put the system into sleep mode) depending on user inactivity and other parameters related to my app. But before performing these actions I would like to show a user warning in a form of a simple UI window with a countdown.
Displaying such warning UI when an interactive user is logged on to the workstation is not an issue and is outside of the scope of this question.
The issue comes up when I need to display my warning UI over a logon screen in 2 basic scenarios:
1. When there's no logged on interactive users (say, when the system just boots up and before any user had a chance to log in.)
2. When the workstation is locked by a user, either by selecting Start -> (Power Button) -> Lock workstation, or by hitting Ctrl+Alt+Delete on the keyboard, etc.
I was able to come up with the following code (that is called from within my service) that works well in the (1) scenario. Just as a proof of concept I'm using Windows calculator in place of my user-mode process that would normally display the warning UI. It produces something like this:
Here's the pseudo-code that I'm using:
Code:
//The following method can be used to launch a GUI process
//in a logon desktop from a local system service.
//INFO: Error checks are omitted for brevity!
DWORD dwActvSessID = WTSGetActiveConsoleSessionId();
HANDLE hSelfToken = NULL;
OpenProcessToken(::GetCurrentProcess(), TOKEN_ALL_ACCESS, &hSelfToken);
HANDLE hTokenNew = NULL;
DuplicateTokenEx(hSelfToken, TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, NULL, SecurityIdentification, TokenPrimary, &hTokenNew);
SetTokenInformation(hTokenNew, TokenSessionId, &dwActvSessID, sizeof(dwActvSessID));
//I'm not sure about the need of this duplication???
HANDLE hToken2 = NULL;
DuplicateHandle(::GetCurrentProcess(), hTokenNew, ::GetCurrentProcess(), &hToken2, 0, FALSE, DUPLICATE_SAME_ACCESS));
LPVOID pEnvBlock = NULL;
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = L"Winsta0\\Winlogon";
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
TCHAR pBuffCmdLine[MAX_PATH];
pBuffCmdLine[0] = 0;
StringCchCopy(pBuffCmdLine, MAX_PATH, L"calc.exe"); //As a test launch the calculator
ImpersonateLoggedOnUser(hToken2);
bResult = !!CreateProcessAsUser(
hToken2, // client's access token
L"calc.exe", // file to execute
pBuffCmdLine[0] != 0 ? pBuffCmdLine : NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, // creation flags
pEnvBlock, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
RevertToSelf();
if(bResult)
{
//Can wait on process handle in 'pi.hProcess' if needed...
}
if(pi.hProcess)
CloseHandle(pi.hProcess);
if(pi.hThread)
CloseHandle(pi.hThread);
if(pEnvBlock)
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hTokenNew);
CloseHandle(hSelfToken);
But the issue with the code above is that it does not fully work in the (2) scenario, or when my 'dwActvSessID' variable is 1 and up, or when there's a logged in interactive session. What happens in that case is that the calc.exe process starts up and runs but it's UI is not visible above the logon desktop UI. Here's the screenshot from the TaskManager, and as you see the calc.exe is running, in this case as a SYSTEM account:
There's a definitely some mix-up with the desktop that my UI process is started under.
Any idea what am I missing here?
PS. Just as an interesting observation, if I replace "calc.exe" with any non-GUI process, such as "cmd.exe" its window does show up above the logon screen, as I would expect it. Here's a screenshot:
![Name: 2.jpg
Views: 12
Size: 25.8 KB]()