CreateProcessAsUser generating error 5 - c++

I've tried mixing the code here and here to run an GUI exe from a service that was initialized through QtService, but whenever I run the code bellow I get an error 5 from the CreateProcessAsUser.
Also, I saw the answer to a similar question here on StackOverflow but couldn't figure out how the DACL is related with the problem, and can't use the answer by Harry Johnson because I wouldn't have the logon info from the users.
So, can someone help me understand why am I receiving the error 5 (Acess Denied) from the code below?
if(initUiWin())
log->write("InitUiWin executed.");
else {
QString errorNumber = QString::number(GetLastError());
log->write("InitUiWin error: " + errorNumber);
}
-
bool initUiWin()
{
// obtain the currently active session id; every logged on
// User in the system has a unique session id
uint dwSessionId = WTSGetActiveConsoleSessionId();
// obtain the process id of the winlogon process that
// is running within the currently active session
QString processName("winlogon.exe");
DWORD winlogonPID = FindProcessId(processName.toStdWString(),dwSessionId);
if( winlogonPID != 0 ) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, winlogonPID);
HANDLE hToken;
OpenProcessToken(hProcess,TOKEN_READ,&hToken);
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
SECURITY_ATTRIBUTES sa;
sa.nLength = static_cast<DWORD>(sizeof(SECURITY_ATTRIBUTES));
// copy the access token of the winlogon process;
// the newly created token will be a primary token
HANDLE hUserTokenDup;
if (!DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,&sa,
SecurityIdentification,TokenPrimary,&hUserTokenDup)) {
CloseHandle(hProcess);
CloseHandle(hToken);
return false;
}
// Get Handle to the interactive window station
HWINSTA hwinsta = NULL;
hwinsta = OpenWindowStation(
_T(L"winsta0"), // the interactive window station
FALSE, // handle is not inheritable
READ_CONTROL | WRITE_DAC); // rights to read/write the DACL
if(hwinsta == NULL)
return false;
// To get the correct default desktop, set the caller's
// window station to the interactive window station.
if (!SetProcessWindowStation(hwinsta))
return false;
// Get a handle to the interactive desktop.
HDESK hdesk = NULL;
hdesk = OpenDesktop(
_T(L"default"), // the interactive window station
0, // no interaction with other desktop processes
FALSE, // handle is not inheritable
READ_CONTROL | // request the rights to read and write the DACL
WRITE_DAC |
DESKTOP_WRITEOBJECTS |
DESKTOP_READOBJECTS);
if(hdesk == NULL)
return false;
// Get the SID for the client's logon session.
PSID pSid = NULL;
if (!GetLogonSID(hUserTokenDup, &pSid))
return false;
// Allow logon SID full access to interactive window station.
if (!AddAceToWindowStation(hwinsta, pSid) )
return false;
// Allow logon SID full access to interactive desktop.
if (!AddAceToDesktop(hdesk, pSid) )
return false;
// Impersonate client to ensure access to executable file.
if (!ImpersonateLoggedOnUser(hUserTokenDup) )
return false;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = static_cast<DWORD>(sizeof(STARTUPINFO));
// interactive window station parameter; basically this indicates
// that the process created can display a GUI on the desktop
wchar_t auxBuffer[16] = L"winsta0\\default";
si.lpDesktop = auxBuffer;
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_BREAKAWAY_FROM_JOB;
// create a new process in the current User's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
L"test-ui-systray.exe", // file to execute
NULL, // command line
&sa, // pointer to process SECURITY_ATTRIBUTES
&sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
NULL, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
if (pSid)
FreeLogonSID(&pSid);
if (hdesk)
CloseDesktop(hdesk);
if (hwinsta)
CloseWindowStation(hwinsta);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return result;
}
return false;
}

Putting it here for visibility. HarryJohnston answered in the comments. The problem was the flags in the OpenProcessToken, I just changed
OpenProcessToken(hProcess,TOKEN_READ,&hToken)
to
OpenProcessToken(hProcess,TOKEN_READ|TOKEN_QUERY|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY,&hToken)

Related

windows service start a interactive process(MFC application) on Windows XP

now I have a windows service, and I want the service create a GUI process(MFC application). And I referenced the code:Launch your application in Vista under the local system account without the UAC popup .
And the important is :
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bResult = FALSE;
DWORD dwSessionId,winlogonPid;
HANDLE hUserToken,hUserTokenDup,hPToken,hProcess;
DWORD dwCreationFlags;
// Log the client on to the local computer.
dwSessionId = WTSGetActiveConsoleSessionId();
WTSQueryUserToken(dwSessionId,&hUserToken);
dwCreationFlags = NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb= sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\default";
ZeroMemory(&pi, sizeof(pi));
TOKEN_PRIVILEGES tp;
LUID luid;
hProcess = OpenProcess(MAXIMUM_ALLOWED,FALSE,GetCurrentProcessId();
if(!::OpenProcessToken(hProcess,TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY
|TOKEN_DUPLICATE|TOKEN_ASSIGN_PRIMARY|TOKEN_ADJUST_SESSIONID
|TOKEN_READ|TOKEN_WRITE,&hPToken))
{
int abcd = GetLastError();
printf("Process token open Error: %u\n",GetLastError());
}
if (!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid))
{
printf("Lookup Privilege value Error: %u\n",GetLastError());
}
tp.PrivilegeCount =1;
tp.Privileges[0].Luid =luid;
tp.Privileges[0].Attributes =SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hUserTokenDup);
int dup = GetLastError();
//Adjust Token privilege
SetTokenInformation(hUserTokenDup,TokenSessionId,(void*)dwSessionId,sizeof(DWORD));
if (!AdjustTokenPrivileges(hUserTokenDup,FALSE,&tp,sizeof(TOKEN_PRIVILEGES),(PTOKEN_PRIVILEGES)NULL,NULL))
{
int abc =GetLastError();
printf("Adjust Privilege value Error: %u\n",GetLastError());
}
if (GetLastError()== ERROR_NOT_ALL_ASSIGNED)
{
printf("Token does not have the provilege\n");
}
LPVOID pEnv =NULL;
if(CreateEnvironmentBlock(&pEnv,hUserTokenDup,TRUE))
{
dwCreationFlags|=CREATE_UNICODE_ENVIRONMENT;
}
else
pEnv=NULL;
// Launch the process in the client's logon session.
bResult = CreateProcessAsUser(
hUserTokenDup, // client's access token
_T("TestDialog.exe"), // file to execute
NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
// End impersonation of client.
//GetLastError Shud be 0
int iResultOfCreateProcessAsUser = GetLastError();
//Perform All the Close Handles task
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return 0;
Now the MFC application can be started after the service stared, it works well. And the process user name is System.
But when I logoff my account(the GUI application is running while click the windows system logoff button), the MFC application have a VC++ crash, the crash point the MFC internal function afxactivationwndproc .
My MFC application is just created by the VS2010 IDE without modify.
My test environment is on Windows XP.
If I double click the application to run it, and logoff with the application running, no error occurs.
So I want to know:
When user logoff, will system force kill my MFC application process? (Note: The process user name is System)
As mentioned, if I double click the application to run it, it is OK. So is there any problem with the service starting method? Or we should add addition necessary operation to the MFC application.
Is there any other possible reason for this error?
Thank you very much!

CreateProcessAsUser fails with ERROR_ELEVATION_REQUIRED for a process with UIAccess="true"

I'm trying to use the following code to run a user-mode process from my service application (running as a local system.)
The requirement for the user-mode process is to run without elevatation, but to have UIAccess="true" in its manifest to be able to display top-most windows correctly under Windows 8.
So I do this (from my service) to run my user-mode process:
//NOTE: Error checking is omitted for readability
//'dwSessionID' = user session ID to run user-mode process in
//'pUserProcPath' = L"C:\\Program Files (x86)\\Company\\Software\\user_process.exe"
HANDLE hToken = NULL;
WTSQueryUserToken(dwSessionID, &hToken);
HANDLE hToken2;
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
LPVOID pEnvBlock = NULL;
CreateEnvironmentBlock(&pEnvBlock, hToken2, FALSE);
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = L"winsta0\\default";
PROCESS_INFORMATION pi;
ZeroMemory(&pi, sizeof(pi));
//ImpersonateLoggedOnUser(hToken2); //Not necessary as suggested below
PVOID OldRedir;
Wow64DisableWow64FsRedirection(&OldRedir);
BOOL bSuccess = CreateProcessAsUser(
hToken2, // client's access token
pUserProcPath, // file to execute
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
);
int nOSError = ::GetLastError();
Wow64RevertWow64FsRedirection(OldRedir);
//RevertToSelf(); //Not necessary as suggested below
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
DestroyEnvironmentBlock(pEnvBlock);
CloseHandle(hToken2);
CloseHandle(hToken);
This runs fine, if UIAccess="false" in the manifest for the user_process.exe.
But if I do the following:
Enable UIAccess="true" for the user_process.exe:
Sign it with my code-signing certificate and
Place it in the "C:\Program Files (x86)\Company\Software" folder.
What I get is that CreateProcessAsUser fails with the error ERROR_ELEVATION_REQUIRED.
Can someone suggest how to make it work?
PS. I tried adjusting my service's privileges to enable SE_DEBUG_NAME and SE_TCB_NAME, but neither helped.
PS2. If I simply double-click my user_process.exe process compiled with UIAccess="true", code-signed, and placed in the C:\Program Files (x86)\Company\Software folder, it starts & runs just fine (not elevated.).
I found the answer. Here it is for whoever runs into it as well:
Add this after DuplicateTokenEx call:
HANDLE hToken2;
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hToken2);
//ONLY if requiring UIAccess!
DWORD dwUIAccess = 1;
::SetTokenInformation(hToken2, TokenUIAccess, &dwUIAccess, sizeof(dwUIAccess));

Duplicate Windows Handle of a local process

Use Case
I have a 64 bit server process which through IPC (COM+ RPC) gains access to the PID of a 32 bit Client Process. The Server Process Creates a new Window with a parent Window Handler. I need to display the New Window inside the Client's Window instead of a standalone popup on top of the Desktop Window.
Process Adopted
In order to Display the New Window on the parent Window, I first tried to
Get the Handle to the Top Window
Duplicate the Window Handle using DuplicateHandle
Create the new Window with the new Duplicate Window Handle
Code
In Order to Duplicate the Window Handle I adopted the following Code. Note this is not the actual code, but for brevity changed the non relevant parts. Also Note, the SetPriviledge Function was adopted from Enabling and Disabling Privileges in C++
bool Duplicate(HWND hWnd)
{
HANDLE pToken = NULL;
HANDLE hProcess = NULL;
HANDLE hDuplicateHandle = NULL;
DWORD pid = 0;
bool bReturn = true;
GetWindowThreadProcessId(hWnd, &pid);
if(!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid)))
{
std::cout<<"Cannot Open Process:"<<GetLastError()<<std::endl;
bReturn = false;
}
if(bReturn && !OpenProcessToken(
hProcess,
TOKEN_ALL_ACCESS,
&pToken ))
{
std::cout<<"Cannot Open Token:"<<GetLastError()<<std::endl;
bReturn = false;
}
//The SetPriviledge function was adopted from
//http://msdn.microsoft.com/en-us/library/windows/desktop/aa446619(v=vs.85).aspx
if (bReturn && !SetPrivilege(
pToken,
SE_DEBUG_NAME,
true ))
{
std::cout<<"Error Setting Priveledge:error"<<GetLastError()<<std::endl;
bReturn = false;
}
if(bReturn && !DuplicateHandle(
hProcess,
hWnd,
GetCurrentProcess(),
&hDuplicateHandle,
NULL,
NULL,
DUPLICATE_SAME_ACCESS))
{
std::cout<<"Error Duplicating Handle: "<<GetLastError()<<std::endl;
std::cout<<"Source Handle is "<<hWnd<<" And the Duplicate Handle is "<<hDuplicateHandle<<std::endl;
bReturn = false;
}
if (hProcess)
{
CloseHandle(hProcess);
}
return bReturn;
}
O/P From the Above Code
Error Duplicating Handle: 6
Source Handle is 00150C1C And the Duplicate Handle is 00000000
Press any key to continue . . .
i.e. The Code fails with Error Code 6: ERROR_INVALID_HANDLE
Goal
To Make the above code work so that I can Duplicate a Remote Windows handle of a Local Process. Alternatively, determine if the above is the correct process.

CreateEnvironmentBlock crashes service

I'm trying to start GUI application from windows service. But when I call CreateEnvironmentBlock() function, It hangs there for a while then crashes displaying dialog box "SampleService.exe stopped working and was closed. A problem caused the application to stop working correctly. windows will notify you if a solution is available." Following is my code.
DWORD dwSessionId = 0; // Session ID
HANDLE hToken = NULL; // Active session token
HANDLE hDupToken = NULL; // Duplicate session token
WCHAR szErr[1024] = {0};
STARTUPINFO* startupInfo;
PROCESS_INFORMATION processInformation;
PWTS_SESSION_INFO pSessionInfo = 0;
DWORD dwCount = 0;
LPVOID lpEnvironment = NULL; // Environtment block
OutputDebugString(_T("My Sample Service: startApplication: Entry"));
// Get the list of all terminal sessions
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSessionInfo, &dwCount);
int dataSize = sizeof(WTS_SESSION_INFO);
// look over obtained list in search of the active session
for (DWORD i = 0; i < dwCount; ++i)
{
WTS_SESSION_INFO si = pSessionInfo[i];
if (WTSActive == si.State)
{
// If the current session is active – store its ID
dwSessionId = si.SessionId;
break;
}
}
OutputDebugString(_T("My Sample Service: startApplication: freewtsmemory"));
WTSFreeMemory(pSessionInfo);
OutputDebugString(_T("My Sample Service: startApplication: WTSQueryUserToken"));
// Get token of the logged in user by the active session ID
BOOL bRet = WTSQueryUserToken(dwSessionId, &hToken);
if (!bRet)
{
swprintf(szErr, _T("WTSQueryUserToken Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
OutputDebugString(_T("My Sample Service: startApplication: duplicatetokenex"));
// Get duplicate token from the active logged in user's token
bRet = DuplicateTokenEx(hToken, // Active session token
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS, // Desired access
NULL, // Token attributes
SecurityImpersonation, // Impersonation level
TokenPrimary, // Token type
&hDupToken); // New/Duplicate token
if (!bRet)
{
swprintf(szErr, _T("DuplicateTokenEx Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
// Get all necessary environment variables of logged in user
// to pass them to the process
OutputDebugString(_T("My Sample Service: startApplication: createenvironmentblock"));
try{
bRet = CreateEnvironmentBlock(&lpEnvironment, hDupToken, FALSE);
}
catch( const exception &e)
{
swprintf(szErr, _T("CreateEnvironmentBlock Exception: %s"), e);
OutputDebugString(szErr);
return false;
}
if(!bRet)
{
swprintf(szErr, _T("CreateEnvironmentBlock Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
// Initialize Startup and Process info
startupInfo->cb = sizeof(STARTUPINFO);
OutputDebugString(_T("My Sample Service: startApplication: createprocess"));
// Start the process on behalf of the current user
BOOL returnCode = CreateProcessAsUser(hDupToken,
NULL,
L"C:\\KM\\TEST.exe",
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT,
lpEnvironment,
NULL,
startupInfo,
&processInformation);
if( !returnCode)
{
swprintf(szErr, _T("CreateProcessAsUser Error: %d"), GetLastError());
OutputDebugString(szErr);
return false;
}
CloseHandle(hDupToken);
return true;
It shows "My Sample Service: startApplication: createenvironmentblock" in debugview and stopped service. please help me out regarding this issue. please note i m using windows vista.
Regards,
KM.
You need to initialise pointers before you can use them in a defined fashion.
STARTUPINFO* startupInfo;
...
startupInfo->cb = sizeof(STARTUPINFO);
This mistake might have been more obvious to spot if your variables were declared closer to where they are used. If you follow some rule that variables can only be declared at the start of a function, you might want to consider making more functions.
And, for what it's worth, when troubleshooting these sorts of issues you can always attach Visual Studio's debugger to the service process instead of relying on OutputDebugString. Just make sure the service process is the last thing built by Visual Studio and process, symbol files and source code should all be aligned.

How to get the active user when multiple users are logged on in Windows?

Suppose there are multiples users currently logged on on Windows. Say, user1 logs on, then switch user and user2 logs on, (without making user1 log off). Suppose there is an app which runs when user logs on. There are two users user1 and user2 logged on, with user2 as the active user, and there are two apps.
My question is: How does the app know whether its corresponding user is active or not? I.e., app in user2 domain determines that its user is active, while app in user1 domain determines its user is currently inactive. Thanks!
You can call WTSGetActiveConsoleSessionId to get the terminal services (aka "fast user switching" aka "remote desktop") session ID that is currently active on the physical console.
You can call WTSQuerySessionInformation with WTS_CURRENT_SESSION for the session identifier and WTSSessionId for WTSInfoClass to get the terminal services session ID for the current process.
If the active session ID and the current process session ID are the same, the user corresponding to the current process has the active session on the physical console.
If what you want to know is whether the session that the current process is running in is active (but not necessarily on the physical console) you can instead use the WTSConnectState option to WTSQuerySessionInformation.
WTSGetActiveConsoleSessionId() may actually return session 0 when run from a windows service. If you need to do this from a Windows service you will need to enumerate all sessions and find the connected session then get the user from that.
The code below does much more than that, including impersonation of that user and running a process as that user all from a windows service, but if you are just interested in the user name please look for the second instance the WTSQuerySessionInformation() function is called.
//Function to run a process as active user from windows service
void ImpersonateActiveUserAndRun(WCHAR* path, WCHAR* args)
{
DWORD session_id = -1;
DWORD session_count = 0;
WTS_SESSION_INFOA *pSession = NULL;
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pSession, &session_count))
{
//log success
}
else
{
//log error
return;
}
for (int i = 0; i < session_count; i++)
{
session_id = pSession[i].SessionId;
WTS_CONNECTSTATE_CLASS wts_connect_state = WTSDisconnected;
WTS_CONNECTSTATE_CLASS* ptr_wts_connect_state = NULL;
DWORD bytes_returned = 0;
if (::WTSQuerySessionInformation(
WTS_CURRENT_SERVER_HANDLE,
session_id,
WTSConnectState,
reinterpret_cast<LPTSTR*>(&ptr_wts_connect_state),
&bytes_returned))
{
wts_connect_state = *ptr_wts_connect_state;
::WTSFreeMemory(ptr_wts_connect_state);
if (wts_connect_state != WTSActive) continue;
}
else
{
//log error
continue;
}
HANDLE hImpersonationToken;
if (!WTSQueryUserToken(session_id, &hImpersonationToken))
{
//log error
continue;
}
//Get real token from impersonation token
DWORD neededSize1 = 0;
HANDLE *realToken = new HANDLE;
if (GetTokenInformation(hImpersonationToken, (::TOKEN_INFORMATION_CLASS) TokenLinkedToken, realToken, sizeof(HANDLE), &neededSize1))
{
CloseHandle(hImpersonationToken);
hImpersonationToken = *realToken;
}
else
{
//log error
continue;
}
HANDLE hUserToken;
if (!DuplicateTokenEx(hImpersonationToken,
//0,
//MAXIMUM_ALLOWED,
TOKEN_ASSIGN_PRIMARY | TOKEN_ALL_ACCESS | MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary,
&hUserToken))
{
//log error
continue;
}
// Get user name of this process
//LPTSTR pUserName = NULL;
WCHAR* pUserName;
DWORD user_name_len = 0;
if (WTSQuerySessionInformationW(WTS_CURRENT_SERVER_HANDLE, session_id, WTSUserName, &pUserName, &user_name_len))
{
//log username contained in pUserName WCHAR string
}
//Free memory
if (pUserName) WTSFreeMemory(pUserName);
ImpersonateLoggedOnUser(hUserToken);
STARTUPINFOW StartupInfo;
GetStartupInfoW(&StartupInfo);
StartupInfo.cb = sizeof(STARTUPINFOW);
//StartupInfo.lpDesktop = "winsta0\\default";
PROCESS_INFORMATION processInfo;
SECURITY_ATTRIBUTES Security1;
Security1.nLength = sizeof SECURITY_ATTRIBUTES;
SECURITY_ATTRIBUTES Security2;
Security2.nLength = sizeof SECURITY_ATTRIBUTES;
void* lpEnvironment = NULL;
// Get all necessary environment variables of logged in user
// to pass them to the new process
BOOL resultEnv = CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE);
if (!resultEnv)
{
//log error
continue;
}
WCHAR PP[1024]; //path and parameters
ZeroMemory(PP, 1024 * sizeof WCHAR);
wcscpy(PP, path);
wcscat(PP, L" ");
wcscat(PP, args);
// Start the process on behalf of the current user
BOOL result = CreateProcessAsUserW(hUserToken,
NULL,
PP,
//&Security1,
//&Security2,
NULL,
NULL,
FALSE,
NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,
//lpEnvironment,
NULL,
//"C:\\ProgramData\\some_dir",
NULL,
&StartupInfo,
&processInfo);
if (!result)
{
//log error
}
else
{
//log success
}
DestroyEnvironmentBlock(lpEnvironment);
CloseHandle(hImpersonationToken);
CloseHandle(hUserToken);
CloseHandle(realToken);
RevertToSelf();
}
WTSFreeMemory(pSession);
}