Since Windows 7, security of Windows Services has been strengthened. Services now run isolated in Session 0, so there can be no interaction between the user (typically session 1, 2, 3, etc.) with Windows Services. See:
this article[
^]
I read an old article which yet addresses this new change
this[
^] and I am trying to find out why my when I create a process as a user, from a Windows Service, I see no UI when I run it on Windows Server 2012, while it works perfectly on Windows 10.
The article says:
"In Windows Server 2012, when bringing online the same Cluster Resource, the UI application does not become visible. This happens, because the Cluster Service is also a Windows Service, and the Cluster Resources launched by the Cluster Service are run in Session 0, which does not have user interaction. The workaround is to create a Windows Service that launches the UI application, and make this Windows Service a Cluster Resource. "
The following function I wrote is supposed to be invoked by a Windows Service in order to start a child process (as a user), so the child process can interact with the user, i.e. have UI. It works perfectly on Windows desktop but fails to work properly on Windows Server 2012. The UI isn't shown (even not a Console window) and a hot key we register for uninstalling the service, doesn't work as well.
Can someone guide me how to make this workaround?
What I have tried:
See
my code here[
^].
I followed the guidelines of
this article.[
^] Invoke my process as a user from a Windows Service, as Windows Services are isolated in
session 0. So since Services can't have their own UI, nor any interactions with the user, this child process which is a Win32 program, does such interaction and is invoked by the Service using:
bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0], NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_DEFAULT_ERROR_MODE, pEnv, NULL, &startupInfo, &processInfo);
The following code:
void WINAPI Run(DWORD dwTargetSessionId, int desktop, LPTSTR lpszCmdLine)
{
wprintf(_T("Run client start"));
if (hPrevAppProcess != NULL)
{
TerminateProcess(hPrevAppProcess, 0);
WaitForSingleObject(hPrevAppProcess, INFINITE);
}
HANDLE hToken = 0;
WTS_SESSION_INFO *si;
DWORD cnt = 0;
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &si, &cnt);
for (int i = 0; i < (int)cnt; i++)
{
if (si[i].SessionId == 0)continue;
wprintf(_T("Trying session id %i (%s) user admin token"), si[i].SessionId, si[i].pWinStationName);
HANDLE userToken;
if (WTSQueryUserToken(si[i].SessionId, &userToken))
{
wprintf(_T("WTSQueryUserToken succeced"));
TOKEN_LINKED_TOKEN admin;
DWORD len;
if (GetTokenInformation(userToken, TokenLinkedToken, &admin, sizeof(TOKEN_LINKED_TOKEN), &len))
{
wprintf(_T("Success using user admin token"));
hToken = admin.LinkedToken;
break;
}
else
wprintf(L"GetTokenInformation() failed");
CloseHandle(userToken);
}
else
wprintf(L"WTSQueryUserToken() failed");
}
if (hToken == 0)
{
HANDLE systemToken;
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &systemToken);
DuplicateTokenEx(systemToken, TOKEN_ALL_ACCESS, NULL, SecurityImpersonation, TokenPrimary, &hToken);
CloseHandle(systemToken);
int i;
for (i = 0; i < (int)cnt; i++)
{
if (si[i].SessionId == 0)continue;
if (SetTokenInformation(hToken, TokenSessionId, &si[i].SessionId, sizeof(DWORD)))
{
wprintf(_T("Success using system token with set user session id %i"), si[i].SessionId);
break;
}
}
if (i == cnt)
wprintf(_T("No success to get user admin token nor system token with set user session id"));
}
WTSFreeMemory(si);
STARTUPINFO startupInfo = {};
startupInfo.cb = sizeof(STARTUPINFO);
startupInfo.lpDesktop = _T("winsta0\\default");
LPVOID pEnv = NULL;
CreateEnvironmentBlock(&pEnv, hToken, TRUE);
PROCESS_INFORMATION processInfo = {};
PROCESS_INFORMATION processInfo32 = {};
TCHAR szCurModule[MAX_PATH] = { 0 };
GetModuleFileName(NULL, szCurModule, MAX_PATH);
BOOL bRes = FALSE;
std::wstring commandLine;
commandLine.reserve(1024);
commandLine += L"\"";
commandLine += szCurModule;
commandLine += L"\" \"";
commandLine += SERVICE_COMMAND_LUNCHER;
commandLine += L"\"";
wprintf(_T("launch SG_WinService with CreateProcessAsUser ... %s"), commandLine.c_str());
bRes = CreateProcessAsUserW(hToken, NULL, &commandLine[0], NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS |
CREATE_UNICODE_ENVIRONMENT | CREATE_NEW_CONSOLE | CREATE_DEFAULT_ERROR_MODE, pEnv,
NULL, &startupInfo, &processInfo);
if (bRes == FALSE)
{
DWORD dwLastError = ::GetLastError();
TCHAR lpBuffer[256] = _T("?");
if (dwLastError != 0) ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), lpBuffer, 255, NULL);
(_T("CreateProcessAsUser(SG_WinService) failed - Error : %s (%i)"), lpBuffer, dwLastError);
}
else
{
wprintf(_T("CreateProcessAsUser(SG_WinService) success. New process ID: %i"), processInfo.dwProcessId);
}
}
returns the following output:
Quote:
05.06.2019 04:25 5856: Trying session id 1 (Console) user admin token
05.06.2019 04:25 5856: Trying session id 2 (RDP-Tcp#27) user admin token
05.06.2019 04:25 5856: Trying session id 65536 (RDP-Tcp) user admin token
05.06.2019 04:25 5856: Success using system token with set user session id 1
05.06.2019 04:25 5856: launch SG_WinService with CreateProcessAsUser ... "C:\myservice\SG_WinService.exe" "ServiceIsLuncher"
05.06.2019 04:25 5856: CreateProcessAsUser(SG_WinService) success. New process ID: 5580
So instead of running the child process on Session 1, it runs as "SYSTEM".