locked
Calling CreateProcessAsUser() from service RRS feed

  • Question

  • According to some MS documents (e.g. http://www.microsoft.com/whdc/system/vista/services.mspx), it should be possible to use CreateProcessAsUser() API in a Vista service to create a process in user session. I am trying to achive this, but the function returns error code 1307 (ERROR_INVALID_OWNER) = "This security ID may not be assigned as the owner of this object."

    Does it work for somebody? Many thanks in advance.
    Tuesday, May 16, 2006 1:23 PM

All replies

  • The same code works for us on Vista as on XP, etc. The service is running as the Local System.

    1. use WTSGetActiveConsoleSessionId to get the ID of the current active Windows session at the console (i.e. the machine keyboard and display, as opposed to WTS sessions).

    2. use WTSQueryUserToken to get the token for that session.

    3. use DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary, &hTokenDup) to duplicate that token.

    4. use CreateEnvironmentBlock to create an environment that you will be passing to the process.

    5. use CreateProcessAsUser with the duplicated token and the created environment. Actually, we use CreateProcessAsUserW, since the A version had some sort of bug on some older systems.

    6. Don't forget to CloseHandle on the various tokens, etc, and to DestroyEnvironmentBlock the environment.

    Wednesday, May 24, 2006 3:07 PM
  • Is CreateEnvironmentBlock() necessary?
    Tuesday, July 11, 2006 3:27 AM
  • Only if you want the process to have an environment.
    Monday, July 24, 2006 12:34 PM
  • Mine was the same senerio, calling CreateProcessAsUser from service.

    I followed the steps given by you, and it really worked for me thanks

    HANDLE hTokenNew = NULL, hTokenDup = NULL;
     HMODULE  hmod = LoadLibrary("kernel32.dll");
     WTSGETACTIVECONSOLESESSIONID lpfnWTSGetActiveConsoleSessionId = (WTSGETACTIVECONSOLESESSIONID)GetProcAddress(hmod,"WTSGetActiveConsoleSessionId"); 
     DWORD dwSessionId = lpfnWTSGetActiveConsoleSessionId();
     WTSQueryUserToken(dwSessionId, &hToken);
     DuplicateTokenEx(hTokenNew,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hTokenDup);
     //
     WriteToLog("Calling lpfnCreateEnvironmentBlock");
     ZeroMemory( &si, sizeof( STARTUPINFO ) );
     si.cb = sizeof( STARTUPINFO );
     si.lpDesktop = "winsta0\\default";


     LPVOID  pEnv = NULL;
     DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
     HMODULE hModule = LoadLibrary("Userenv.dll");
     if(hModule )
     {
      LPFN_CreateEnvironmentBlock lpfnCreateEnvironmentBlock = (LPFN_CreateEnvironmentBlock)GetProcAddress( hModule, "CreateEnvironmentBlock" );
      if( lpfnCreateEnvironmentBlock != NULL )
      {
       if(lpfnCreateEnvironmentBlock(&pEnv, hTokenDup, FALSE))
       {
        WriteToLog("CreateEnvironmentBlock Ok");
        dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT;    
       }
       else
       {
        pEnv = NULL;
       }
      }
     }
      //
     ZeroMemory( &pi,sizeof(pi));
     
     if ( !CreateProcessAsUser(
      hTokenDup,
      NULL,
      ( char * )pszCmd,  
      NULL,
      NULL,
      FALSE,
      dwCreationFlag,
      pEnv,
      NULL,
      &si,
      &pi
      ) )
     {
      
      goto RESTORE;
     } 

    Tuesday, August 1, 2006 10:34 AM
  • I am doing basically the same thing, but I get "CreateProcessAsUser failed with 123"

    I have tried everything I cn think of with no luck

    Tuesday, October 3, 2006 11:40 PM
  • 123 is ERROR_INVALID_NAME ("The filename, directory name, or volume label syntax is incorrect"). Check the command line / exe path you are passing it. Also, keep in mind that Local System does not have privileges to access network paths, and does not have the mapped drives that a user might have.
    Wednesday, October 4, 2006 10:07 AM
  • Any way I am able to launch process (using CreateProcessAsUser) but it is getting launch in another desktop

    i.e., is Session0, this is the place where my service is running. I am not able to launch in Session1 (users desktop).

    I am trying to get some help from

    http://weblogs.asp.net/kennykerr/archive/2006/09/29/Windows-Vista-for-Developers-_1320_-Part-4-_1320_-User-Account-Control.aspx

    may this help you. If you could, let me know.

    Thanks

     

     

    Monday, October 9, 2006 11:21 AM
  • The TS session in which the process is started will be based on the session id of the token passed to CreateProcessAsUser. If the token is for an interactive logged on user, it should already have the correct session id.

    The windowstation/desktop can be specified in the STARTUPINFO structure. "Winsta0\Default" is the default user's desktop.

    Wednesday, October 18, 2006 9:56 PM
  • [Ganeshm wrote]

    Mine was the same senerio, calling CreateProcessAsUser from service.

    I followed the steps given by you, and it really worked for me thanks

    HANDLE hTokenNew = NULL, hTokenDup = NULL;
    HMODULE hmod = LoadLibrary("kernel32.dll");

    ...

    Hi !

    Could you tell me what sort of libraries did you included to make this piece of code working? When I pasted it in VS a lot of errors occured...

    For sure there were libraries:      WtsApi32.h, windows.h and variables  declarations     STARTUPINFO si;            PROCESS_INFORMATION pi;,

    but others I cannot guess...

    Creating process , using this function is very important for me, so I'd be grateful for an answer... :)



    Thursday, October 19, 2006 11:22 AM
  • There's a small mistake in this code:

    WTSQueryUserToken(dwSessionId, &hToken);
    DuplicateTokenEx(hTokenNew,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hTokenDup);

    The output of the WTSQueryUserToken (i.e. hToken) should become the first parameter to DuplicateTokenEx but it's not in this case.
    Note that the token duplication is not really necessary because WTSQueryUserToken yields a primary token already.

    Thursday, October 19, 2006 8:18 PM
  • Somehow on Vista, when I call WTSGetActiveConsoleSessionId(), I got session 0 and pass this session id to  WTSQueryUserToken() and I got error 1008 (An attempt was made to reference a token that does not exist). The above calls were from a localsystem and the login user was current on. Why I got the error message?
    Tuesday, October 31, 2006 5:24 PM
  • WTSQueryUserToken with 0 as the session id will always fail on Vista, because no user ever logs on in that session anymore.
    WTSGetActiveConsoleSessionId() returning 0 may happen under some rare circumstances.
    Is there anything special about the moment when you call that API?

    Tuesday, October 31, 2006 7:14 PM
  • The call was in windbg, I used DebugBreak() to get into step by step.  (Somehow I couldn't use OutputDebugStringA() to display using DbgView.exe).
    When I use Remote Desktop to debug, the session id is 3, however WTSQueryUserToken() failed with the same error 1008.
    Tuesday, October 31, 2006 8:21 PM
  • Did you use start windbg.exe in session 0 and make the service interactive?
    Dbgview has to run in the same session as the process being traced...
    Windbg doesn't have this restriction by the way. You can debug/attach accross session boundary and use it as dbgview.

    MSDN with regards to WTSQueryUserToken:

    ERROR_NO_TOKEN
    1008
    The token query is for a session in which no user is logged-on. This occurs, for example, when the session is in the idle state.

    Was a user logged on in session 3 at the time of the call?

    Tuesday, October 31, 2006 11:13 PM
  • When I use DebugReak() to force windbg starts, I got Interactive services dialog detection popup telling me "A program may need information or permission to complete a task." When I click "Show me the message", the whole screen switched to a complete different screen there only windbg is running.  I guess WTSGetActiveConsoleSessionId() gave me the wrong session id thus, I got 1008 error returned by WTSQueryUserToken().  I was the one logged on at the same time.  In this case, how do I get the correct session id? In other word, why WTSGetActiveConsoleSessionId() gave me the wrong session id?
    Thursday, November 2, 2006 2:55 AM
  • Hi,

    I have done a service - run installation as administrator under vista RC2 - build 5744
    it seems the process failed a creation 

    I enclose the code I have done based on what I read here - but somthing is wrong somewhere.

    Did already someone, was able to bypass Session 0 isolation on a service on vista ?

    (http://www.microsoft.com/whdc/system/vista/services.mspx)

    Or is there something wrong in the code I wrote ?

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    HANDLE hToken = NULL;

    HANDLE hTokenthis = NULL;

    PVOID lpEnvironment = NULL;

    ZeroMemory( &si, sizeof(STARTUPINFO) );

    si.cb = sizeof(STARTUPINFO);

    ZeroMemory( &pi, sizeof(PROCESS_INFORMATION) );

    si.dwFlags=STARTF_USESHOWWINDOW;

    si.wShowWindow = SW_SHOW;

    si.lpDesktop = L"Winsta0\\Default";

    //get Active UI session

    WTSQueryUserToken (WTSGetActiveConsoleSessionId(), &hTokenthis);

    DuplicateTokenEx(hTokenthis, MAXIMUM_ALLOWED,NULL, SecurityIdentification, TokenPrimary, &hToken);

    // Start the child process.

    if ( CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE) )

    {

    if (!CreateProcessAsUserW(hToken, // hToken

    NULL, // lpApplicationName

    L"c:\\windows\\notepad.exe", // lpCommandLine

    NULL, // lpProcessAttributes

    NULL, // lpThreadAttributes

    FALSE, // bInheritHandles

    CREATE_UNICODE_ENVIRONMENT |NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, // dwCreationFlags

    lpEnvironment, // lpEnvironment

    NULL, // lpDirectory

    &si, // lpStartupInfo

    &pi // lpProcessInfo

    ))

    {

    //error

    }

    }

     

    Thursday, November 2, 2006 3:53 PM
  • Jan Shao, when you debug your service this way, after the switch you describe, session 0 becomes the active console session!
    WTSGetConsoleSessionId() will return 0 while you're switched over.
    Your debugging environment is generating this condition.

    Thursday, November 2, 2006 6:03 PM
  • 2 things I would do differently:
    * Remove CREATE_NEW_CONSOLE (it's meant for console applications, and notepad isn't one).
    * Copy your command line in a local or allocated buffer because of this (excerpt from MSDN about CreateProcessAsUser):

    lpCommandLine
    [in] The command line to be executed. The maximum length of this string is 32,000 characters.
    Windows 2000:  The maximum length of this string is MAX_PATH characters.

    The Unicode version of this function, CreateProcessAsUserW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.

    Thursday, November 2, 2006 6:05 PM
  • Could you tell me are there any other ways that I can debug service so to avoid this situation? Thanks.
    Thursday, November 2, 2006 8:54 PM
  • The easiest method:
    If a user is logged on for the entire duration of the part you're interested in debugging, you could just start the debugger (elevated) from that session and attach to the service.

    The most powerful method:
    A kernel debugger and the service started under ntsd piped into kd (using image file execution options).
    ntsd allows some form of source level debugging...

    Alternate methods involve the creation of debugging servers (see the debuggers documentation).
    That debugging server can be controlled (even remotely) from other debuggers.

    None of these methods impact the behavior of the service, as opposed to making the service interactive and debugging it from session 0...

    Thursday, November 2, 2006 11:20 PM
  • I followed you first suggestion and it failed the same way with session id = 3 and 1008 from WTSQueryUserToken().  I gave up debug and dump the message to a file from the service, failed the same way.

    Here are the code  segment:

           dwSessionId = WTSGetActiveConsoleSessionId();
            if (dwSessionId == 0xFFFFFFFF) {
                DbgPrint("No session attached to the console (WTSGetActiveConsoleSessionId failed)\n");
                __leave;
            }
            DbgPrint("Got session id [%d]\n", dwSessionId);

            if (!WTSQueryUserToken(dwSessionId, &hToken)) {
                DbgPrint("WTSQueryUserToken failed %d\n", status = GetLastError());
                __leave;
            }

            if(!DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hTokenDup)) {
                DbgPrint("DuplicateTokenEx failed %d\n", status = GetLastError());
                __leave;
            }
    Friday, November 3, 2006 1:13 PM
  • Can you also output the session state before calling WTSQueryUserToken?
    That error indicates that no user is logged on in that session.

    Monday, November 6, 2006 10:34 PM
  • Dont use WTSQueryUserToken for getting logged in users session id

    use following logic

    1) Service will be running in Session0 ->ok, from your service find out interactive process i.e.,  "explorer.exe"(here user is logged in)  by using EnumProcessModules and GetModuleBaseName (psapi functions).

    2) Once you get handle to process ("explorer.exe") HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED,    FALSE, nProcessIDsIdea);

    to obtain access token assocated with process use

    OpenProcessToken ( hProcess, TOKEN_READ | TOKEN_DUPLICATE | TOKEN_QUERY|TOKEN_ASSIGN_PRIMARY , &hProcessToken )

    3) To get interactive session id use

    GetTokenInformation(hProcessToken, TokenSessionId, &idInterActiveSession, sizeof(idInterActiveSession), &len)
       

    4) If you use hProcessToken  in CreateProcessAsUser, process will launch in proper desktop.

    5) Further you can get SID of logged in user by

    ::GetTokenInformation ( hProcessToken, TokenUser, NULL, NULL, & bufSize );
         if ( ::GetLastError () != ERROR_INSUFFICIENT_BUFFER )
         {
                return FALSE;
         }
         
         
         buf.resize ( bufSize );
         if ( ! GetTokenInformation ( hProcessToken, TokenUser, &buf.front (), bufSize, & bufSize ))
         {
                return FALSE;
         }
         
         TOKEN_USER * tu = (TOKEN_USER*) &buf.front();
         if ( tu == 0 )
         {
                return FALSE;
         }

    DWORD dwLength = GetLengthSid( tu->User.Sid );
          *userSid1 = ( PSID )HeapAlloc(
           GetProcessHeap(),
           HEAP_ZERO_MEMORY,
           dwLength
           );
          CopySid( dwLength, *userSid1, tu->User.Sid ) ;

     //User name and domain can be verified by calling

    ::LookupAccountSid ( NULL, tu->User.Sid, namebuf, & cbNamebuf, domainbuf, & cbDomainbuf, & sidType );

    This will definately work, but this will be in loggin users contex not the required user context, this is I am trying to solve know.

    Thanks

     

    Tuesday, November 7, 2006 7:38 AM
  • This procedure is a rather expensive workaround that should only be used on platforms where the WTS APIs don't exist.
    And it won't work in some edge cases either (the shell is not explorer.exe, or is further restricted using SAFER policies).

    Tuesday, November 7, 2006 7:36 PM
  • My quick question is why WTSGetActiveCobnsoleSessionId() gave me the wrong session id? I am the one who currently log on the system.
    Tuesday, November 7, 2006 8:19 PM
  • There's no way for me to say whether it's the correct session number or not.
    The active console session id changes all the time as people logon and logoff, at the physical console or not.
    What's the output of qwinsta right before the service is going to make the call?

    Tuesday, November 7, 2006 9:45 PM
  • I am doing exactly the same thing as posted in previous messages and getting the same error message:

    WTSQueryUserToken failed: 1008 (SessID 1)

    This is the error logged by a service, running as Local System when called by a client on Vista RC2.

    The code works great on XP and 2003 but not on Vista.

    Has anyone managed to gain more understanding into this?

    Thanks,

    Adrian

    Saturday, November 11, 2006 5:26 PM
  • Someone with this issue has yet to provide the output of qwinsta (a built-in tool showing the state of each session) right before the call to WTSQueryUserToken is made.

    Tuesday, November 14, 2006 12:59 AM
  • Sorry, forgot about this - the issue is as follows:

    1. The code *does* on Vista also - just as well as XP, 2002. Have not tried 2000 yet (different code needed as functions are not present there).

    2. The problem is due to *Remote Desktop*! Myself - like the other person with this error from what I've read - were using Remote Desktop. This is why you get 1008 - there is no console session! Hmm, shoudn't Remote Desktop emulate that???

    Now the question is why WTSQueryUserToken does not work over Remote Desktop? How do you get the user's token then?

    1. One of the work-arounds - as posted - is to iterate through processes for "your session" and then get the token for that process. This does work but does not strike me as a good solution. Only do this when WTSQueryUserToken fails.

    2. You have to remember that Vista and XP/2003 sessions are different, i.e. in XP/2003 remote desktop session is made 0 for compatibility (anyone remebers the article that describes this, could not find it) while in Vista - as you know. Not sure what happens when 2 users Remote Desktop at the same time.

    The problem here - at least for me - is how is the service supposed to know which session to use for Remote Desktop for the call? As some calls need to run under SYSTEM you need to make them before you impersonate the user and then work out the caller's session to use to launch the process.

    Anyway, this is the stage I'm in. I hope this does help others out there.

    At the moment it seems I'll have to blow a MSDN call on this - considering they took 3 months to solve a simple RPC issue last time even after making a complaint I'm not very hopeful.

    Thanks,

    Adrian

    Tuesday, November 14, 2006 1:32 PM
  • Sorry, I was away for a few days.  Thank you Eric for this tools.  Now I know my problem  is.  Just like Addrian, I was working in Remote Desktop so WTSQueryUserToken failed.  I guess I have to  iterate through sessions  and  compare the owner of the session  to get the correct session id before calling WTSQueryUserToken.
    Tuesday, November 14, 2006 4:32 PM
  • Hmm, for me WTSQueryUserToken fails for any session through Remote Desktop.

    It's not the session ID that's wrong, the function seems to "think" there is no user for that session. It's easy to check, just change the session ID you pass to it and see what you get.

    On Vista (RC2) the ID is also "wrong" though, as my session is 2 but the active one is deemed 3:

    Current Session: 2
    Active  Session: 3

    C:\Temp>qwinsta
     SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
     services                                    0  Disc
                       administrator             1  Disc
    >rdp-tcp#0         adrians                   2  Active  rdpwd
     console                                     3  Conn
                                             65536  Down
     rdp-tcp                                 65537  Listen

    The fix that works for me is for the client to tell the service (server) what session to use thus the server does not need to work anything out and the client does know its session (from process ID).

    Hope this helps,

    Adrian

    Tuesday, November 14, 2006 5:46 PM
  • WTSGetActiveConsoleSessionId() will always return the id of the TS session bound to the physical console.
    That's its definition.

    It looks like it's not what you want in either of your cases.
    You need to know which session you want to target. The OS can't figure that one out for you.
    WTSQueryUserToken will only succeed for sessions where a user is logged on (1 & 2 in the output above).

    Here's what I believe happened here:
    THE administrator probably logged on locally (session 1).
    Then adrians used remote desktop (session 2). When he logged on, and since this is a client SKU where you can only have one active session at a time, session 1 was disconnected, and an empty session (3) was created at the physical console.

    In terms of figuring out which session to use, there are a few scenarios:

    1. You want to use the session corresponding to an RPC call.

    1a. you want to create the new process as the caller: impersonate, openthreadtoken, revert, duplicate the token as primary, CPAU (CreateProcessAsUser)

    1b. you want to create a new process as the user logged on in that session: impersonate, openthreadtoken, revert,  get session id out of the token, wtsqueryusertoken, CPAU

    2. From a service you need to alert a user: enumerate sessions, find the active one (there may not be one at that time), wtsqueryusertoken, CPAU

    Note that all calls to CPAU should be done while impersonating the same token (I skipped this above).

    Tuesday, November 14, 2006 6:25 PM
  • Eric, thank you very much for replying.

    You seem to understand what happened, while I seem to do not: why was the empty session 3 created? This would account for the function returning 3 while my session is 2 over TS/RD. I'm pretty sure WTSQUS failed for session 2 as well.

    1.a. From what I've seen, RPCImpersonate does not give you a primary token, only an impersonation token. You can see this by looking at the list of privileges; we run into this when we needed to turn on a privilege that turned out to be missing. Trying impersonate/ open thread token/ duplicate(primary,...)/ revert/ SetThreadToken did not work. Thus CPAU should fail.

    Is there another RPCImpersonate function that sets the thread with a primary token from the caller?

    When you mean "calls to CPAU should be done while impersonating the same token" - do you mean the new primary token made for CPAU rather than the original RPC token?

    Thanks,

    Adrian.

    Wednesday, November 15, 2006 12:14 AM
  • Session 3 is created to give the opportunity to anyone to reconnect to that machine locally (either administrator, adrians, or another user).
    Let's assume adrians disconnects from his RD session.
    After the authentication in session 3, if that user is already logged on (administrator, adrians), only a reconnect will happen (to either session 1 or 2, which will become active console session again), or a full logon of a 3rd user will go on.

    1.a. Yes RpcImpersonateClient yields an impersonation token. That's why you duplicate to a primary for the call to CPAU.
    In that case you can RpcImpersonateClient again (since it's the same user), or use ImpersonateLoggedOnUser with the primary token that you'll pass to CPAU.
    SetThreadToken only accepts an impersonation token.

    The impersonation while calling CPAU (same token with both calls) ensures that the access checks to the file itself are done in the context of the user...

    Wednesday, November 15, 2006 3:34 AM
  • OK,

    I've implemented 1.a. as per your instructions:

    - The good news is that it does work on a live console in XP, 2003, Vista

    - The bad news is that it does not work over Remote Desktop. CPAU returns error 2 - however further investigation shows this is not the true cause.

    Doing GetTokenInformation(SessionId...) returns Session 1!!!

    Now this is an XP machine over RD, here is qwinsta:

    C:\Temp>qwinsta
     SESSIONNAME       USERNAME                 ID  STATE   TYPE        DEVICE
    >rdp-tcp#1         adrians                   0  Active  rdpwd
     rdp-tcp                                 65536  Listen  rdpwd
     console                                     2  Conn    wdcon

    Changing the token's session to 0 makes it work, though now I'm at a loss to figure out why the RPC token's session is 1 when there is no such session at all.

    I haven't worked out a fix for Vista over RD since 0 won't work.

    Any more ideas? This does not seem easy at all.

    Thanks,

    Adrian

    Wednesday, November 15, 2006 5:17 PM
  • >> Doing GetTokenInformation(SessionId...) returns Session 1!!!
    Did you look at the return value or the content of TokenInformation ?
    Another way to verify this is to obtain the output of !token (from a MS debugger).

    The calling application (the one being RpcImpersonated) is running as the logged on user (local or remote), right?

    Wednesday, November 15, 2006 8:29 PM
  • I've got the content of TokenInformation. The server code is as follows:

    ...

    DWORD dwReturnLen;
    DWORD dwSessionId=0;

    if (GetTokenInformation(hThreadToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId), &dwReturnLen))
    {
     ...

     _stprintf(szStatus, TEXT("CreateProcessAsUser failed: %lu (%s, Sess %lu)"),
      GetLastError(), szCmdLine, dwSessionId);

     ...

    szStatus is returned by the RPC server (running as LSA) to the client. No debugger was used.

    The client application was run from the command line in RD on the same box as the server. Session for the process is 2 as per qwinsta.

    When I overwrote the session ID on the duplicated token

    dwSessionId=0;

    SetTokenInformation(hPrimaryToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId));

    then it worked.

    Note: The initial code did not do SetTokenInfo, nor anything else with dwSessionID, it was just logged just in case. Only when things did not work I've added SetTokenInfo and tried changing it.

    Note 2: Box was XP/SP2 with login from another XP/SP2.

    Adrian

     

     

    Thursday, November 16, 2006 12:06 AM
  • That could be a known bug actually. Please confirm the following:
    * Issue doesn't repro on Vista.
    * Issue doesn't repro if RD is used by the same user that's already logged on locally (reconnection case).

    Thursday, November 16, 2006 2:04 AM
  • 1. Sorry, I had no chance to try it on Vista RTM; I'm still trying to get the download to finish off MSDN.

    1.a. I had no chance to try it on 2003 / SP1 Server. It may be an XP issue.

    2. I had no chance to try RD reconnection.

    Would it be "better" to open an official MSDN support incident? If it may be a bug the call is credited back - still have some left.

    Thanks,

    Adrian

    Saturday, November 18, 2006 1:20 AM
  • If you're able to RD at all, reconnection (vs. full logon) is only a matter of being already logged on at the physical console before RD is initiated.
    If this issue is caused by the bug I am thinking of, it shouldn't happen in the reconnect case (only if RD when nobody is logged on yet).

    Saturday, November 18, 2006 1:53 AM
  • OK, here's what I've been able to test so far:

     

    A. System reboot, nobody physically logged on; RD log-on:

    Windows XP SP2 - 32-bit / 32-bit program - error 2

    Windows 2003 Server SP1 - 32-bit / 32-bit program  - works

    Windows 2003 Server SP1 - Itanium / 32-bit program - error 6

    Vista Ultimate RC2 - x64 / 32-bit program - works

     

    B. System reboot, log-on locally; then connect from another machine via RD:

    Windows XP SP2 - 32-bit - works, you're right.

    Cannot actually physically logon to the Itanium server; in any case that error seems to be something else - will compile IA64 client & server to test. Cannot reboot any x64 servers to test.

    Does that help?

    Adrian

     

    Saturday, November 18, 2006 2:56 AM
  • The variation in behavior on XPSP2 is a pretty good sign that my hunch is correct.
    In the fresh logon case, the actual logon occurs in temporary session 1.
    Then people familiar with GINAs will know that most of the logon generated data is then "transferred" to session 0.
    The bug is that there are remnants of the fact that the logon occurred in that session...

    This is irrelevant in the reconnection case where the token/logon session is destroyed shortly after the recoonection.

    There's a good chance there's a QFE for this. In any case, it's not your bug...

    Monday, November 20, 2006 2:13 AM
  • This works for me too!

    But,

    Is there some info on how to execute as the "System" account on the users desktop (VISTA) already done XP?

    Monday, February 19, 2007 9:46 PM
  • That's a questionable design decision. Do you mind sharing some details as to why you would want to do this?

    Tuesday, February 20, 2007 12:14 AM
  • Inhouse system administration and problem resolution of 23,000+ remote clients that don't have admin rights to their workstations. Being able to launch an application (Help Desk support) on to the users desktop either when their logged on or not and during business hours or off-hours to do maintenance it a benefit.

    I realize that it's not something we'd do very often, but I want the option.

     I figured it out ....

       Using the current process token then setting (token) SessionID of the current token to the ActiveConsoleSessionID;  

    Tuesday, February 20, 2007 2:34 PM
  • Why do you need the "application" to start on the user's desktop if it's not interactive?
    If it's driven remotely, won't it work equally well in session 0 (i.e. where services belong)?

    Tuesday, February 20, 2007 10:48 PM
  • Well when I mean interactive, I mean more along the lines of 'show window'.

    But if you do want to help;

    I'm getting an error "0xc00000142 Failed to initialize properly" when I try to launch an application on a users desktop as another user (member of local admin group),  I'm ;

    1. creating a token using LogonUser
    2. using that token to GrantSessionSID access to Winstation and desktop rights (working under XP)
    3. with the same token to ImpersonateLoggedOnUser to fill in lp_ProfileInfo
    4. Add privleges se_increase_qutoa_name and se_assignPrimarytoken_name (was getting privlege errors)
    5. LoadUserProfile using that token and lp_Profileinfo (needed under XP, not sure under Vista)
    6. Setting destopSessionID to the token with SetTokeInformation(token,TokenSessionID,dwSessionID,sizeof(DWord))7. calling CreateProcessAsUser with the token.

    (Borland Delphi)

    CreateProcessAsUser(hTokenDup,  pchar('cmd.exe /k'),  nil,nil,false,
                      CREATE_DEFAULT_ERROR_MODE or CREATE_NEW_CONSOLE or CREATE_UNICODE_ENVIRONMENT,
       nil, Pchar(StartupDir),   SI, PI));

    Seems close to working, and it worked under XP, something must be missing under Vista.

    I know this is not very detailed but just wondering if i'm missing an obvious step.

    Thanks.

     

    Wednesday, February 21, 2007 2:01 PM
  • I assume you run this code from a service.
    In step 2, you're modifying the SD of  the winsta and desktop of session 0...
    This is not very useful if you want to start the resulting process in an interactive session, which will never be 0.

    Also note that the token obtained from LogonUser is actually the non administrative token...

    I still question the motive here. A previous post indicated that this needed to work whether a user was logged on or not.
    Where would the window show up in this case?
    And if a user is logged on, and the administrative application shows on the desktop, the logged on user can interact with it too. Depending on what the application allows, this could be a security issue.
    If you just need another window alerting the user that something is being done on his machine, it's another matter (in this case, I'd suggest a small application started as the logged on user).

    Some of the issues in the above code sequence go away if the process is created in a non interactive desktop (like a task).
    Scheduling a task could be another option by the way...

    Thursday, February 22, 2007 1:58 AM
  • Hi,

    Getting session ID before CreateProcessAsUser stuff is very interesting. I have the similar situation. I  used it and it works fine when logged on user is an administrator. But unfortunately fails when logged on user is a standard user. Just to clarify I will briefly describe my picture.

    1. I wish to launch a process from service as an administrator without asking logged on user for admin credentials as I already know those crendentials.

    2. I used the method posted in this thread. It works fine when logged on user is an admin user. but fails when logged on user is a normal user.

    3. It fails at CreateProcessAsUser with error code 740 (ERROR_ELEVATION_REQUIRED). I have also tried LogonUser to get Token instead of fetching it from Session ID and use it.

     

    Is there any way I run admin process interactively when logged on user is a non-admin user?

     

    Thanks in advance,

     

    Wednesday, March 7, 2007 7:30 AM
  • This procedure works fine if the service is running a the Local System. Note that if the CreateProcessAsUser starts an exe for which elevation is required, CreateProcessAsUser will fail with code 740 (elevation required).

    But in our case we have a service that is running as Administrator and we would like to run, from the service, a process in a different session, eg session 1. On XP and 2003 this works fine becuase both session IDs are the same (both in session 0). Instead in Vista the service is running as Administrator in session 0 and the administrator is logged on in session 1 (the active session).

    How do I then run a process in session 1 (where the Administrator is logged on) from a service running is session 0 as Administrator?

    So far we could not succeed... is SeTcbPrivilege required for this? The administrator account does not have SeTcbPrivilege by default....

    Please help!  thanks

     

    Wednesday, March 14, 2007 3:14 PM
  • Read the whole thread, your questions have already been answered.
    Wednesday, March 14, 2007 4:29 PM
  • Thanks, I read the thread again but could not find the code we need. Could you point it out to me please? thanks! Our problem is that it works if the service is running as LocalSystem, but it fails with "Privilege not required" if the service is running under the administrator account.

     

    Under the administrator account:

    WTSQueryUserToken(dwSessionId, &hToken);

    fails with privilege not held.

    Also

    SetTokeInformation(token,TokenSessionID,dwSessionID,sizeof(DWord));

    fails with privilege not held.

     

    How can the administrator assign a different session ID to a token?

     

    thanks

     

    Thursday, March 15, 2007 3:22 AM
  • I used the WTSQueryUserToken() function to obtain the primary access token of the logged-on user whose session-id I had obtained from a call to WTSGetActiveConsoleSessionId().

     

    My issue is that after the call to WTSQueryUserToken(), GetLastError() returns error number 1314: A required privilege is not held by the client.

     

    In MSDN I see that what I'm getting is the error ERROR_PRIVILEGE_NOT_HELD which means: The caller does not have the SE_TCB_NAME privilege. How should I go about getting that previledge now?

     

    Here is my source:

     

    Code Snippet

     

       static STARTUPINFO si;
        static PROCESS_INFORMATION pi;
        HANDLE hTokenNew = NULL, hTokenDup = NULL;
       
        DWORD dwSessionId = WTSGetActiveConsoleSessionId();

     

       WTSQueryUserToken(dwSessionId, &hTokenNew);

     

       DuplicateTokenEx(hTokenNew, MAXIMUM_ALLOWED, NULL,

                        SecurityIdentification, TokenPrimary, &hTokenDup);

     

       ZeroMemory( &si, sizeof( STARTUPINFO ) );
        si.cb = sizeof( STARTUPINFO );
        si.lpDesktop = _T("winsta0\\default");

     

       LPVOID  pEnv = NULL;
        DWORD dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;

     

       CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE));

     

       dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT;


        pEnv = NULL;

       ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) );

     

       CreateProcessAsUser( hTokenDup, NULL, _T("c:\\windows\\notepad.exe"),
                             NULL, NULL, FALSE, dwCreationFlag,
                             pEnv, NULL, &si, &pi);

       CloseHandle(hTokenDup);

     

     

    The Notepad application also doesn't start. My DLL is running under the Print Spooler service. I am logged onto the Administrator's account. What must be going wrong in the code.????

    Tuesday, April 10, 2007 1:55 PM
  • Which account is running the service?
    Wednesday, April 11, 2007 12:45 AM
  • The service i'm concerned with is the Print Spooler (SPOOLSV.EXE) that runs under the SYSTEM user name account, i.e. the LocalSystem account.

     

    If you open the Windows Task Manager you'll see the Image Name SPOOLSV.EXE is running under the SYSTEM user name.

     

    My DLL is loaded by SPOOLSV.EXE under its own context.

     

    From there I want to start the Notepad application under the context of the user who is currently logged on. Any hints please.

     

    Note: As an aside, please note that the SPOOLSV.EXE runs from location C:\WINDOWS\system32\spoolsv.exe. It is the Windows Print Spooler service that loads files to memory for later printing.

    Wednesday, April 11, 2007 6:20 AM
  • I think

    WTSQueryUserToken(dwSessionId, &hToken);

    fails with "privilege not held" if the SeTCBPrivilege is not available.

     

    In my tests, the function would work if the service was running under the LocalSystem but would not work if the service was running under an admin account. Maybe you should check if the service has the SeTCBPrivilege enabled.

    Wednesday, April 11, 2007 6:51 AM
  • Hi.

    I tried it out, the way you said. But somehow my call to WTSQueryUserToken() does not succeed and returns the value 0 (zero).

     

    After that if I do a GetLastError(), I get Error No. 1314, with description "A required privilege is not held by the client". FYI - error 1314 is ERROR_PRIVILEGE_NOT_HELD.

     

    I'm running an Administrator A/C in WinXPSP2 and my DLL loads under the SPOOLSV.EXE whose user name is LocalSystem.

     

    Need help. Plz. DDAS.

     

     

     efratian wrote:

    The same code works for us on Vista as on XP, etc. The service is running as the Local System.

    1. use WTSGetActiveConsoleSessionId to get the ID of the current active Windows session at the console (i.e. the machine keyboard and display, as opposed to WTS sessions).

    2. use WTSQueryUserToken to get the token for that session.

    3. use DuplicateTokenEx(hToken,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary, &hTokenDup) to duplicate that token.

    4. use CreateEnvironmentBlock to create an environment that you will be passing to the process.

    5. use CreateProcessAsUser with the duplicated token and the created environment. Actually, we use CreateProcessAsUserW, since the A version had some sort of bug on some older systems.

    6. Don't forget to CloseHandle on the various tokens, etc, and to DestroyEnvironmentBlock the environment.

    Wednesday, April 11, 2007 11:52 AM
  • Hi Eric,

     

    Can you tell me why my call to WTSQueryUserToken(dwSessionId, &hToken) fails with

     

    Error Code 1314: "A required privilege is not held by the client".

     

    I'm running the Admin account in WinXPSP2 and my code is in a DLL that is loaded by the SPOOLSV.EXE Windows Service.

     

    Looking forward. Thanks in advance.

     

     Eric Perlin - MSFT wrote:

    There's a small mistake in this code:

    WTSQueryUserToken(dwSessionId, &hToken);
    DuplicateTokenEx(hTokenNew,MAXIMUM_ALLOWED,NULL,SecurityIdentification,TokenPrimary,&hTokenDup);

    The output of the WTSQueryUserToken (i.e. hToken) should become the first parameter to DuplicateTokenEx but it's not in this case.
    Note that the token duplication is not really necessary because WTSQueryUserToken yields a primary token already.

    Wednesday, April 11, 2007 11:59 AM
  • Add the paths to your project. In your VC++ IDE Menu, go to Project -> Settings.

     

    Say the PATH to your SDK is d:\devkits\sdk_3790.1830.

     

    In the C/C++ tab, Preprocessor category, add path d:\devkits\sdk_3790.1830\include in the textbox for Additional include directories.

    Go to the Link tab, in the General category, add path d:\devkits\sdk_3790.1830\Lib\WtsApi32.Lib in the textbox for Object/library modules.

     

    Similarly add the other ones also. I hope this will work. Bye.

     

     

     misiu_mietowy wrote:
    [Ganeshm wrote]

    Mine was the same senerio, calling CreateProcessAsUser from service.

    I followed the steps given by you, and it really worked for me thanks

    HANDLE hTokenNew = NULL, hTokenDup = NULL;
    HMODULE hmod = LoadLibrary("kernel32.dll");

    ...

    Hi !

    Could you tell me what sort of libraries did you included to make this piece of code working? When I pasted it in VS a lot of errors occured...

    For sure there were libraries:      WtsApi32.h, windows.h and variables  declarations     STARTUPINFO si;            PROCESS_INFORMATION pi;,

    but others I cannot guess...

    Creating process , using this function is very important for me, so I'd be grateful for an answer... Smile



    Wednesday, April 11, 2007 12:09 PM
  • Not all SYSTEM processes have TCB. Some services drop privileges on their own.

    If you run the service under debugger, you can verify this with !token.

    Another option is to use Process Explorer from sysinternals.

     

    WTSQueryUserToken is not the only way to get the token.

    If the caller allowed impersonation, the service can impersonate him. It can then open the thread token and proceed from step 3 (in this case a conversion to a primary token is required).

    Also, services like the spooler may offer some API to retrieve the token of the user who submitted the job.

    Sunday, April 22, 2007 10:26 PM
  • Yeah I got this stuff working. There was no need for the WTSGetActiveConsoleSessionId() and WTSQueryUserToken(). Just the CreateEnvironmentBlock() must work properly so that the environment for the process you are going to create is set correctly.

     

    Here is the source that worked for me. The sample below shows the Notepad application being launched. The DLL in which I used this code runs under the Print Spooler service - basically it is a Print Monitor. You need to add your own validity checks - what's listed below is bare-bones.

    Code Snippet
    #include "userenv.h"
    // Global Typedefs for function pointers in USERENV.DLL
    typedef BOOL (STDMETHODCALLTYPE FAR * LPFNCREATEENVIRONMENTBLOCK)
        ( LPVOID  *lpEnvironment,
          HANDLE  hToken,
          BOOL    bInherit );
    typedef BOOL (STDMETHODCALLTYPE FAR * LPFNDESTROYENVIRONMENTBLOCK)
        ( LPVOID lpEnvironment );
    void InvokeApp()
    {
        // Local Variable Declarations
        HANDLE hToken    = NULL;
        HANDLE hTokenDup = NULL;
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
        ZeroMemory( &si, sizeof( STARTUPINFO ) );
        ZeroMemory( &pi, sizeof( PROCESS_INFORMATION ) );
       
        si.cb = sizeof( STARTUPINFO );
        si.lpDesktop = _T("Winsta0\\Default");
       
        DWORD  dwCreationFlag = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
        LPVOID pEnvironment = NULL;
        LPFNCREATEENVIRONMENTBLOCK  lpfnCreateEnvironmentBlock  = NULL;
        LPFNDESTROYENVIRONMENTBLOCK lpfnDestroyEnvironmentBlock = NULL;
        HMODULE hUserEnvLib = NULL;
        hUserEnvLib = LoadLibrary( _T("userenv.dll") );
        if ( NULL != hUserEnvLib ) {
            lpfnCreateEnvironmentBlock  = (LPFNCREATEENVIRONMENTBLOCK)
            GetProcAddress( hUserEnvLib, "CreateEnvironmentBlock" );
           
            lpfnDestroyEnvironmentBlock = (LPFNDESTROYENVIRONMENTBLOCK)
            GetProcAddress( hUserEnvLib, "DestroyEnvironmentBlock" );
        }
        OpenThreadToken( GetCurrentThread(), TOKEN_DUPLICATE, TRUE, &hToken );
        DuplicateTokenEx( hToken,
                          TOKEN_IMPERSONATE|TOKEN_READ|TOKEN_ASSIGN_PRIMARY|TOKEN_DUPLICATE,
                          NULL, SecurityImpersonation, TokenPrimary, &hTokenDup );
        RevertToSelf( );
        CloseHandle( hToken );
        if ( NULL != lpfnCreateEnvironmentBlock ) {
            if ( lpfnCreateEnvironmentBlock( &pEnvironment, hTokenDup, FALSE ) ) {
                dwCreationFlag |= CREATE_UNICODE_ENVIRONMENT;   // must specify
            }
            else {
                pEnvironment = NULL;
                OutputDebugString( _T(" CreateEnvironmentBlock() -- FAILED") );
            }
        }
        else {
            OutputDebugString(_T(" FAILED - GetProcAddress(CreateEnvironmentBlock)"));
        }
        CreateProcessAsUser( hTokenDup, NULL, _T("c:\\windows\\notepad.exe"),
                             NULL, NULL, FALSE, dwCreationFlag,
                             pEnvironment, NULL, &si, &pi );
        if ( NULL != lpfnDestroyEnvironmentBlock )
            lpfnDestroyEnvironmentBlock( pEnvironment );
        if ( NULL != hUserEnvLib ) FreeLibrary( hUserEnvLib );
        CloseHandle( hTokenDup );
    }

    What I fixed? The issue I was facing was that the application I wanted my Print Monitor to launch using CreateProcessAsUser() was not getting the logged-on user's environment. The after-effect was that, because of this reason, when my application used to show the File Open common dialog box, it would behave strange while trying to browse to the Desktop in it. This was in Vista.

     

    In Windows XP, the File Open dialog would let you browse to the Desktop folder but the object icons on the Desktop would not appear right in it.

     

    Now it works. Thanks for your help, suggestions etc. Have a nice day.

    • Proposed as answer by ddaS-edEn Monday, July 27, 2009 11:36 AM
    Wednesday, April 25, 2007 9:02 AM
  • If you don't use CreateEnvironmentBlock(), and the application you launch uses things like the Windows Common Dialog boxes, you may find file dialog boxes working erratically.
    Wednesday, April 25, 2007 9:07 AM
  • Hi all,

    I have a service which should start an UI application on the
    users desktop who is pysically logged on the machine.

    Im programming in Delphi (Object Pascal) therefore i had to
    port the code posted here into Delphi.

    I´m logging the results of GetLastError to a text file.
    I always get an error "This security ID may not be assigned as the owner of this object".


    1.
    Because of having no PVOID type in i declared it that way.

    Code Snippet
    type PVOID = ^Pointer;

     


    2.
    Then i declared to get the functions needed out of the DLL´s.

    Code Snippet

    function WTSGetActiveConsoleSessionId(
      ): DWORD; stdcall; external 'Kernel32.dll';

    function WTSQueryUserToken(
      SessionId : Cardinal;
      phToken : PHandle
      ) : LongBool; stdcall; external 'wtsapi32.dll';

    function CreateEnvironmentBlock(
      var lpEnvironment: PVoid;
      hToken: THANDLE;
      bInherit: Boolean
      ): LongBool; stdcall; external 'userenv.dll';

    function DestroyEnvironmentBlock(
      lpEnvironment: PVoid
      ): LongBool; stdcall; external 'userenv.dll';

     

     

    3. Ported the source from C++ to Delphi

    Code Snippet

    function CreateProcessOnPysDesktop(ACmdLine: string): Boolean;
    var LToken: THandle;
        LDupToken: THandle;
        LSessionID: DWORD;
        LStartUpInfo: TStartupInfo;
        LPointer: PVoid;
        LProcessInfo: TProcessInformation;
    begin
      ZeroMemory(@LStartUpInfo, SizeOf(TStartupInfo));
      ZeroMemory(@LProcessInfo, SizeOf(TProcessInformation));
      try
        //getting session id  ---  e.g. 1
        LSessionID := WTSGetActiveConsoleSessionId;

        //getting user token from session   --- i got e.g. 388
        WTSQueryUserToken(LSessionID, @LToken);

        //duplicating the token ----  LDup e.g. 376
        DuplicateTokenEx(LToken, TOKEN_ASSIGN_PRIMARY or TOKEN_ALL_ACCESS,
                                  nil, SecurityIdentification, TokenPrimary, LDupToken);

        //setting up StartUpInfo
        LStartUpInfo.cb := SizeOf(TStartupInfo);
        LDesktop := 'winsta0\default';
        LStartUpInfo.lpDesktop := @LDesktop;
        LStartUpInfo.dwFlags := STARTF_USESHOWWINDOW;
        LStartUpInfo.wShowWindow := SW_SHOW;


        LPointer := nil;
        //creating environment block --- succeded
        if CreateEnvironmentBlock(LPointer, LDupToken, False) then
        begin

          //creating process  --- this throws the posted error message!!!
          result := CreateProcessAsUser(LDupToken,
                              nil,
                              PAnsiChar(ACmdLine),
                              nil,
                              nil,
                              False,
                              CREATE_UNICODE_ENVIRONMENT or DETACHED_PROCESS, //LCreationFlag,
                              LPointer,
                              nil,
                              LStartUpInfo,
                              LProcessInfo);

          if assigned(LPointer) then
            DestroyEnvironmentBlock(LPointer)
        end;

      finally
        CloseHandle(LToken);
        CloseHandle(LDupToken);
      end;
    end;

     

     

    Why i get this error message?
    Any ideas?


    Regards
    Levent Y.

    Thursday, April 26, 2007 3:32 PM
  • Hi Eric.
    I have a VB app that i runs from a LocalSystem service like you described
    the app runs under LocalSystem too.
    2 Problems:
    1)In my app i have:
    Private WithEvents winShell As SHDocVw.ShellWindows

    and then in one of the functions:
    set winShell = new SHDocVw.ShellWindows
    When it came to that set line, i get this error in a popup dialog:
    "automation error" -2147220990 (80040202)

    Some points:
    1)When i run the app not from the service, everything is working fine
    1)When i run the app not from the LocalSystem service, but the app run under Admin account everything is working fine (but i need the app to run under LocalSystem too)
    3)When i remove the WithEvents, i do not get that error (but as you can understand it worthless without the Events).

    The problem is related to the combination of the app that runs from a service (under localsystem), automation and the WithEvents keyword.

    I tried to make the service interactive by going to the service manager, right click properties, and checking that box - it did not solve it.

    Any suggestions?

    I run it on XP pro and Vista - same problem.

    2)  When i call in my app that as said run from the Localsystem service to:
    WNetAddConnection2, i get error 1312 (ERROR_NO_SUCH_LOGON_SESSION)
    In my opinion since a LocalSystem service does not have access to network.
    Can i make the app that run from the LocalSystem service to access Network so that
    WNetAddConnection2 will work?

    (The app and The service must keep running under LocalSystem - i do not want to change that)

    Thanks!

    Monday, April 30, 2007 1:21 AM
  • My problem is solved.

        LDesktop := 'winsta0\default';
        LStartUpInfo.lpDesktop := PChar(LDesktop);

    A cast to PChar was necessary.


    Regards.
    Monday, May 7, 2007 1:55 PM
  • What if instead of calling WTSGetActiveConsoleSessionId call WTSEnumerateSessions and check for WTSActive?
    How many active users there will be on Terminal Services?
    Monday, May 7, 2007 3:31 PM
  • That's the best case scenario (i.e. when the server has client context through impersonation).

    It's always better because it also handles cases where the caller is not the user logged on in that session (runas, elevation with alternate credentials).

    OpenThreadToken + DuplicateTokenEx + CPAU is indeed the correct sequence in this case.

    I've got couple comments about the code though:

    * why not link with userenv instead of the LoadLibrary+GetProcAddress (although it doesn't affect functionality)?

    * more importantly, the RevertToSelf() call is concerning.
    Functions shouldn't revert the impersonation unless it's expected by the caller. All code executed after that function returns is going to run in the context of the process token... in this specific case as SYSTEM if this is called from within the spooler. Is it even required here?
    Impersonating before calling CPAU is recommended.
    In case you must, I'd suggest you set the thread token back before exiting or even better as soon as you can.

     

    Wednesday, May 9, 2007 5:37 AM
  • There could be hundreds on a large server. Using the console should be last resort on a server. There may not be a user at the console for extended periods of time. A user context (through impersonation for example) is preferable in that case.

    Wednesday, May 9, 2007 5:52 AM
  • I'm a complete dummy when it comes to VB...

    On the other hand, that object seems very much Shell oriented.

    Each logged on user has its own Shell, SYSTEM doesn't. I'm not surprised it doesn't work in this context.

     

    With regards to the second question, here's an excerpt from MSDN about WNetAddConnection2:

    <<<

    Windows Server 2003 and Windows XP:  The WNet functions create and delete network drive letters in the MS-DOS device namespace associated with a logon session because MS-DOS devices are identified by AuthenticationID. (An AuthenticationID is the locally unique identifier, or LUID, associated with a logon session.)

    >>>

    SYSTEM is not logged on. It doesn't have a logon session... That error makes sense.

     

    Wednesday, May 9, 2007 6:05 AM
  • None of this works for me - it's error 1314 every time.

    Nice that MS disabled UI in services and gave us a 'hint' that CreateProcessAsUser might help us out of this bind - but no working example.

    Tuesday, June 19, 2007 6:38 PM
  • Doesn't work - or if it used to, no longer works...
    Tuesday, June 19, 2007 6:54 PM
  • This thread has grown to a point where it's difficult to find the replies. All I have is the excerpt in the notification mail.

    Here's a summary of the options:

    a/ The service already impersonates the caller (might be a logged on user, or a runas user, or an "over-the-shoulder" elevated user).

    This is the best case scenario because the service knows exactly who to run the application as.

    In this case the correct sequence is OpenThreadToken + DuplicateTokenEx + CPAU.

    TCB isn't required in this case.

    b/ The service needs to notify a user (the one at the physical console?) but doesn't have context.

    Using the user at the physical console or finding active users is an option but may not always be appropriate.

    In this case WTSQueryUserToken+ImpersonateLoggedOnUser+CPAU will work when the service has TCB (otherwise the first API will fail with error 1314).

     

    Interactive services had to run as SYSTEM anyway. I expect either method to cover most cases if not all.

     

    Wednesday, June 20, 2007 11:45 PM
  •  

    Hello,

     

    I'm in the case where the service is the spooler and I'm writting a print monitor that do some job and finaly run IE.
    Everything works fine except when the user is logged on a domain. In this case, the favorites in IE are not displayed while they are displayed if IE is ran manually from the desktop.

    It seems that the spooler impersonates the user who sent the print job but does not load its profile. We can see that by using SHGetSpecialFolderLocation command with CSIDL_LOCAL_APPDATA in the monitor code just before calling the CPAU function. it reports "C:\Windows\System32config\systemprofile\AppData\Local" instead of the user local directory. Then I tried to use LoadUserProfile but this fails because the user doesn't have the priviledge to do that.
    So I'm now in a blocked situation. The problem occurs on vista, XP and 2000.

     

    Can anybody help me ?

    Saturday, October 13, 2007 7:26 AM
  • Users that print are usually logged on (interactively) and their profile is typically loaded.

    The output of the API returns the default folder for the SYSTEM user (the service account).

    Are you sure you are impersonating at the time you're making the call?

     

    Tuesday, October 30, 2007 8:03 PM
  • I have been trying to get this to work for weeks. The problem is that the program is starting but it is closing immediately. I have been using process monitor to watch what happens, and I can see the process start and close, but no idea why. I have tried making it start notepad but the same thing occurs. I have also tried this with impersonating the user first but it's no different. This class is used from a service running in the context of NT AUTHORITY\SYSTEM on XPSP2 and Vista x64.  What am I doing wrong? Does someone have a complete example that works?

     

    P.S. I know I should be using SafeHandle, but it doesn't really matter if I can't get the code working anyway.

     

    Code Block

    using System;
    using System.Text;
    using System.Runtime.InteropServices;

    namespace Test
    {
        static public class Test
        {
            /* structs, enums, and external functions defined at end of code */

            public static System.Diagnostics.Process StartProcessInSession(int sessionID, String commandLine)
            {
                IntPtr userToken;
                if (WTSQueryUserToken(sessionID, out userToken))
                {
                    //note that WTSQueryUserToken only works when in context of local system account with SE_TCB_NAME
                    IntPtr lpEnvironment;
                    if (CreateEnvironmentBlock(out lpEnvironment, userToken, false))
                    {
                        StartupInfo si = new StartupInfo();
                        si.cb = Marshal.SizeOf(si);
                        si.lpDesktop = "winsta0\\default";
                        si.dwFlags = STARTF.STARTF_USESHOWWINDOW;
                        si.wShowWindow = ShowWindow.SW_SHOW;
                        ProcessInformation pi;
                        if (CreateProcessAsUser(userToken, null, new StringBuilder(commandLine), IntPtr.Zero, IntPtr.Zero, false, CreationFlags.DETACHED_PROCESS, lpEnvironment, null, ref si, out pi))
                        {
                            CloseHandle(pi.hThread);
                            CloseHandle(pi.hProcess);
                            //context.Undo();
                            try
                            {
                                return System.Diagnostics.Process.GetProcessById(pi.dwProcessId);
                            }
                            catch (ArgumentException e)
                            {
                                //The process ID couldn't be found - which is what always happens because it has closed
                                return null;
                            }
                        }
                        else
                        {
                            int err = Marshal.GetLastWin32Error();
                            throw new System.ComponentModel.Win32Exception(err, "Could not create process.\nWin32 error: " + err.ToString());
                        }
                    }
                    else
                    {
                        int err = Marshal.GetLastWin32Error();
                        throw new System.ComponentModel.Win32Exception(err, "Could not create environment block.\nWin32 error: " + err.ToString());
                    }
                }
                else
                {
                    int err = System.Runtime.InteropServices.Marshal.GetLastWin32Error();
                    if (err == 1008) return null; //There is no token
                    throw new System.ComponentModel.Win32Exception(err, "Could not get the user token from session " + sessionID.ToString() + " - Error: " + err.ToString());
                }
            }

            [DllImport("wtsapi32.dll", SetLastError = true)]
            static extern bool WTSQueryUserToken(Int32 sessionId, out IntPtr Token);

            [DllImport("userenv.dll", SetLastError = true)]
            static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

            [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
            static extern bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, [In] StringBuilder lpCommandLine, IntPtr /*to a SecurityAttributes struct or null*/ lpProcessAttributes, IntPtr /*to a SecurityAttributes struct or null*/ lpThreadAttributes, bool bInheritHandles, CreationFlags creationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref StartupInfo lpStartupInfo, out ProcessInformation lpProcessInformation);

            [DllImport("kernel32.dll", SetLastError = true)]
            static extern bool CloseHandle(IntPtr hHandle);

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            struct StartupInfo
            {
                public Int32 cb;
                public String lpReserved;
                public String lpDesktop;
                public String lpTitle;
                public Int32 dwX;
                public Int32 dwY;
                public Int32 dwXSize;
                public Int32 dwYSize;
                public Int32 dwXCountChars;
                public Int32 dwYCountChars;
                public Int32 dwFillAttribute;
                public STARTF dwFlags;
                public ShowWindow wShowWindow;
                public Int16 cbReserved2;
                public IntPtr lpReserved2;
                public IntPtr hStdInput;
                public IntPtr hStdOutput;
                public IntPtr hStdError;
            }

            [StructLayout(LayoutKind.Sequential)]
            internal struct ProcessInformation
            {
                public IntPtr hProcess;
                public IntPtr hThread;
                public int dwProcessId;
                public int dwThreadId;
            }


            /// <summary>
            /// The following process creation flags are used by the CreateProcess, CreateProcessAsUser, CreateProcessWithLogonW, and CreateProcessWithTokenW functions. They can be specified in any combination, except as noted.
            /// </summary>
            [Flags]
            enum CreationFlags : int
            {
                /// <summary>
                /// Not specified by MSDN???
                /// </summary>
                NONE = 0,

                /// <summary>
                /// The calling thread starts and debugs the new process and all child processes created by the new process. It can receive all related debug events using the WaitForDebugEvent function.
                /// A process that uses DEBUG_PROCESS becomes the root of a debugging chain. This continues until another process in the chain is created with DEBUG_PROCESS.
                /// If this flag is combined with DEBUG_ONLY_THIS_PROCESS, the caller debugs only the new process, not any child processes.
                /// </summary>
                DEBUG_PROCESS = 0x00000001,

                /// <summary>
                /// The calling thread starts and debugs the new process. It can receive all related debug events using the WaitForDebugEvent function.
                /// </summary>
                DEBUG_ONLY_THIS_PROCESS = 0x00000002,

                /// <summary>
                /// The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.
                /// </summary>
                CREATE_SUSPENDED = 0x00000004,

                /// <summary>
                /// For console processes, the new process does not inherit its parent's console (the default). The new process can call the AllocConsole function at a later time to create a console. For more information, see Creation of a Console.
                /// This value cannot be used with CREATE_NEW_CONSOLE.
                /// </summary>
                DETACHED_PROCESS = 0x00000008,

                /// <summary>
                /// The new process has a new console, instead of inheriting its parent's console (the default). For more information, see Creation of a Console.
                /// This flag cannot be used with DETACHED_PROCESS.
                /// </summary>
                CREATE_NEW_CONSOLE = 0x00000010,

                /// <summary>
                /// The new process is the root process of a new process group. The process group includes all processes that are descendants of this root process. The process identifier of the new process group is the same as the process identifier, which is returned in the lpProcessInformation parameter. Process groups are used by the GenerateConsoleCtrlEvent function to enable sending a CTRL+BREAK signal to a group of console processes.
                /// If this flag is specified, CTRL+C signals will be disabled for all processes within the new process group.
                /// This flag is ignored if specified with CREATE_NEW_CONSOLE.
                /// </summary>
                CREATE_NEW_PROCESS_GROUP = 0x00000200,

                /// <summary>
                /// If this flag is set, the environment block pointed to by lpEnvironment uses Unicode characters. Otherwise, the environment block uses ANSI characters.
                /// </summary>
                CREATE_UNICODE_ENVIRONMENT = 0x00000400,

                /// <summary>
                /// This flag is valid only when starting a 16-bit Windows-based application. If set, the new process runs in a private Virtual DOS Machine (VDM). By default, all 16-bit Windows-based applications run as threads in a single, shared VDM. The advantage of running separately is that a crash only terminates the single VDM; any other programs running in distinct VDMs continue to function normally. Also, 16-bit Windows-based applications that are run in separate VDMs have separate input queues. That means that if one application stops responding momentarily, applications in separate VDMs continue to receive input. The disadvantage of running separately is that it takes significantly more memory to do so. You should use this flag only if the user requests that 16-bit applications should run in their own VDM.
                /// </summary>
                CREATE_SEPARATE_WOW_VDM = 0x00000800,

                /// <summary>
                /// The flag is valid only when starting a 16-bit Windows-based application. If the DefaultSeparateVDM switch in the Windows section of WIN.INI is TRUE, this flag overrides the switch. The new process is run in the shared Virtual DOS Machine.
                /// </summary>
                CREATE_SHARED_WOW_VDM = 0x00001000,

                /// <summary>
                /// The process is to be run as a protected process. The system restricts access to protected processes and the threads of protected processes. For more information on how processes can interact with protected processes, see Process Security and Access Rights.
                /// To activate a protected process, the binary must have a special signature. This signature is provided by Microsoft but not currently available for non-Microsoft binaries. There are currently four protected processes: media foundation, audio engine, Windows error reporting, and system. Components that load into these binaries must also be signed. Multimedia companies can leverage the first two protected processes. For more information, see Overview of the Protected Media Path.
                /// Windows Server 2003 and Windows XP/2000:  This value is not supported.
                /// </summary>
                CREATE_PROTECTED_PROCESS = 0x00040000,

                /// <summary>
                /// The process is created with extended startup information; the lpStartupInfo parameter specifies a STARTUPINFOEX structure.
                /// Windows Server 2003 and Windows XP/2000:  This value is not supported.
                /// </summary>
                EXTENDED_STARTUPINFO_PRESENT = 0x00080000,

                /// <summary>
                /// The child processes of a process associated with a job are not associated with the job.
                /// If the calling process is not associated with a job, this constant has no effect. If the calling process is associated with a job, the job must set the JOB_OBJECT_LIMIT_BREAKAWAY_OK limit.
                /// </summary>
                CREATE_BREAKAWAY_FROM_JOB = 0x01000000,

                /// <summary>
                /// Allows the caller to execute a child process that bypasses the process restrictions that would normally be applied automatically to the process.
                /// Windows 2000:  This value is not supported.
                /// </summary>
                CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,

                /// <summary>
                /// The new process does not inherit the error mode of the calling process. Instead, the new process gets the default error mode.
                /// This feature is particularly useful for multi-threaded shell applications that run with hard errors disabled.
                /// The default behavior is for the new process to inherit the error mode of the caller. Setting this flag changes that default behavior.
                /// </summary>
                CREATE_DEFAULT_ERROR_MODE = 0x04000000,

                /// <summary>
                /// The process is a console application that is being run without a console window. Therefore, the console handle for the application is not set.
                /// This flag is ignored if the application is not a console application, or if it is used with either CREATE_NEW_CONSOLE or DETACHED_PROCESS.
                /// </summary>
                CREATE_NO_WINDOW = 0x08000000,
            }

            [Flags]
            public enum STARTF : uint
            {
                STARTF_USESHOWWINDOW = 0x00000001,
                STARTF_USESIZE = 0x00000002,
                STARTF_USEPOSITION = 0x00000004,
                STARTF_USECOUNTCHARS = 0x00000008,
                STARTF_USEFILLATTRIBUTE = 0x00000010,
                STARTF_RUNFULLSCREEN = 0x00000020,  // ignored for non-x86 platforms
                STARTF_FORCEONFEEDBACK = 0x00000040,
                STARTF_FORCEOFFFEEDBACK = 0x00000080,
                STARTF_USESTDHANDLES = 0x00000100,
            }

            public enum ShowWindow : short
            {
                SW_HIDE = 0,
                SW_SHOWNORMAL = 1,
                SW_NORMAL = 1,
                SW_SHOWMINIMIZED = 2,
                SW_SHOWMAXIMIZED = 3,
                SW_MAXIMIZE = 3,
                SW_SHOWNOACTIVATE = 4,
                SW_SHOW = 5,
                SW_MINIMIZE = 6,
                SW_SHOWMINNOACTIVE = 7,
                SW_SHOWNA = 8,
                SW_RESTORE = 9,
                SW_SHOWDEFAULT = 10,
                SW_FORCEMINIMIZE = 11,
                SW_MAX = 11
            }

        }
    }

     

     

    Saturday, January 19, 2008 6:01 PM
  • I don't see anything obviously wrong, unless you're building UNICODE, in which case you're missing the flag indicating this for the environment block. On the other hand, I'm not very familiar with the native interop code used here...

    It might help that you're getting the exit code of the app (see GetExitCodeProcess).

     

    Monday, January 21, 2008 8:00 PM
  • It seems the only change needed was to add CREATE_UNICODE_ENVIRONMENT, which means the code for manually creating the environment block can be done away with. You can see this is some of the other posts. I can FINALLY move on.

     

    Monday, January 21, 2008 10:18 PM
  • That's good news. I'm glad I was able to help.

    Unless you're pretty sure the application (and the dll it loads) doesn't use the environment string, I would *not* get rid of the call to CreateEnvironment block. If you pass null for the env block during the call to CreateProcessAsUser, I believe the app will inherit its block from the service (running as system). It could misbehave as a result (it will fail to write to well-known locations like USERPROFILE & [LOCAL]APPDATA).

    Passing the flag is the right thing to do in my opinion.

     

    Monday, January 21, 2008 10:36 PM
  • Hi Eric,

    I am trying to start an application from the a windows service it's working fine on XP but not on Vista. The application is always started in session 0,but I want it in session 1 because it can show a UI.

    Further more it needs to start when windows start and run either the interactive user is logged in or not, it does not need admin rights though.

    Here is the entire code I am using, I also tried "winlogon" desktop but I got the same result.

     

     

     

    int res = LogonUser(User,Dom,Pwd,LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, &hToken);

    DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityDelegation, TokenPrimary, &hDToken);

    hwinstaSave = GetProcessWindowStation();

    hwinsta = OpenWindowStation("winsta0", FALSE, READ_CONTROL | WRITE_DAC);

    res = SetProcessWindowStation(hwinsta);

    hdesk = OpenDesktop("default", 0, FALSE, READ_CONTROL | WRITE_DAC | DESKTOP_WRITEOBJECTS |

    DESKTOP_READOBJECTS);

    res = SetProcessWindowStation(hwinstaSave);

    res = GetLogonSID(hDToken, &pSid);

    res = AddAceToWindowStation(hwinsta, pSid);

    res = AddAceToDesktop(hdesk, pSid);

    res = ImpersonateLoggedOnUser(hDToken);

    ZeroMemory(&si, sizeof(STARTUPINFO));

    si.cb= sizeof(STARTUPINFO);

    si.lpDesktop = TEXT("winsta0\\default");

    bResult = CreateProcessAsUser(hDToken, NULL, lpCommandLine, NULL,NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, NULL, currentDirectory.c_str(), &si, &pi );

    Saturday, January 26, 2008 1:01 AM
  • That can't work for multiple reasons:

    * None of this code is changing the session ID in the token used in the call to CPAU.

    * Even if there was such code, the ACLs on the windowstation/desktop would not allow the code to run.

    The interactive windowstation of session 0 is the one that's reACLed here. You'd need to reACL the windowstation in the target session... which can't be done from the service.

     

    In any case, launching code as an alternate user is not recommended.

    It happens in the runas case (pure CreateProcessWithLogonW or run as administrator when the logged on user is a standard user) but a different technique is used.

    If you intend to run code before a user is logged on, it's even less recommended (you'd be reACLing the winlogon desktop!!!).

     

    I recommend you start your application as the logged on user when there's one, and as SYSTEM otherwise (I hope that the application in question is designed to handle this because otherwise it could allow the user to take over the machine).

    See this thread for details: http://forums.microsoft.com/Forums/ShowPost.aspx?PostID=1834742&SiteID=1

     

    Saturday, January 26, 2008 1:22 AM
  • BTW my code is heavily inspired from this msdn article

    http://msdn2.microsoft.com/en-us/library/aa379608(VS.85).aspx

     

    Saturday, January 26, 2008 1:27 AM
  • I'm afraid that article is rather outdated (not compatible with Vista at all, and more generally with Fast User Switching and Terminal Services)...

    I'll track down the author and get this fixed. Thanks for indicating the source of the information.

     

    Saturday, January 26, 2008 1:45 AM
  • Thanks Eric for you reply.

     Eric Perlin - MSFT wrote:

    If you intend to run code before a user is logged on, it's even less recommended (you'd be reACLing the winlogon desktop!!!).

     

    That's exactly what I want to do, run the application before a user is logged on, or when he is logged out, kind of task scheduler with the system startup trigger, I am not trying to run the application as an alternate user.

    But I want it in session 1 so when he loggs in he can use the UI. Can you please elaborate why it's not recommended to do so?

     

     

    Saturday, January 26, 2008 1:54 AM
  • I mentioned this in the context of running code under a user account.

    Doing so with an interactive application before logon (on the secure desktop is implied) is not safe.

    The secure desktop would not be so secure anymore...

    The intent behind the secure desktop is that the system is in control when you press CAD.

    If an application is running on that desktop as a different entity, the assumption is broken.

     

    As mentioned earlier too, making a random application run on the secure desktop (as SYSTEM) is unsafe as well.

    For example, if the application features an open/save file dialog or has printing capabilities, I would be able to take over this machine in no time. Code running on that desktop needs to obey very strict guidelines...

     

    In any case, once a user logs on, the best option is for the application to restart itself as the logged on user on the "Default" desktop.

     

    Saturday, January 26, 2008 10:34 PM
  •  

    Thanks Eric for your quick responses we really appreciate it, I am sure this thread is helping a lot of people.
    Monday, January 28, 2008 7:20 PM
  •  

    Thanks for your aswer

     

    This was a mistake from me in the call to CreateEnvironnementBlock.
    However, calling this fucntion ibefore CreateProcessAsUser is necessary to have the complete user environnement available.

    Tuesday, January 29, 2008 10:18 AM
  • Hello Eric,

     

    Did you find an updated example that works on Vista of this article(http://msdn2.microsoft.com/en-us/library/aa379608(VS.85).aspx)?

    I'm using this example too in my application but I have to update it because it doesn't work anymore on Vista.

     

    Your reply is greatly appreciated,

    Eric

    Monday, February 25, 2008 3:52 PM
  • It somehow depends on what you want to do end to end.

    a) The code posted above is a decent reference to start an application as the logged on user in a given session.

    b) It would have to be tweaked a little bit for the case where the service needs to start code based on an impersonation context (while processing a RPC call for example).

    c) Starting an application from a user session with alternate credentials is fairly trivial (see CreateProcessWithLogonW).

    d) Starting a non-interactive application given some credentials is equivalent to starting a task.

     

    I realize that this doesn't cover the article's case (starting an interactive application with alternate credentials on the desktop of the user who happens to be logged on), although it can be achieved by mixing a & c.

    On the other hand, and I've mentioned it before, it's a rather dangerous thing to do, and it's probably best that there aren't samples demonstrating how it can be done.

     

    Monday, March 3, 2008 11:54 PM
  • I am getting error following error on using WTSGetActiveConsoleSessionId():

    error C3861: 'WTSGetActiveConsoleSessionId': identifier not found, even with argument-dependent lookup

    I have included windows.h and tried including winbase.h specifically, but no luck.

    I am linking WtsApi32.lib and UserEnv.lib too.

     

    In winbase.h this API is enclosed by

    #if _WIN32_WINNT >= 0x0501

    That means, this API is supported only for NT 5.1 or higher.

     

    I tried to compile on XP (NT 5.1) and Vista too.

     

    What could be the reason for the error above?

     

    I want to start product's installer using SYSTEM account whether or not user is logged-in.

    Tuesday, May 6, 2008 7:16 AM
  • I added following in stdafx.h

    #define _WIN32_WINNT 0x0501

    and it worked.

     

    Friday, May 9, 2008 10:28 AM
  • Hi,

    Is there a way to differentiate between remote desktop sessions that has been started through "mstsc" and one that has been started through "mstsc /console" on Windows Server? I have an application running as a system service that can interact with the desktop by spawning another application, this application will be visible either in the session where someone is physically logged in at the console or if someone is logged in remotly using mstsc /console (with Windows Server >= 2003). I would like to start this second application in the context of the user that will see it but if two user have active session, unless one is the physical console one, I need to know if any is connected with the /console to know which token user to retrieve.
    Thanks!
    Monday, June 30, 2008 12:12 PM
  • Note that /console has become irrelevant with Vista and Server 2008.

    It only made sense before session 0 isolation.

     

    I'm not sure I understand what you are trying to achieve.

    I could understand trying the console first (WTSGetActiveConsoleSessionId) and remote sessions afterwards though (enumerate TS sessions).

    Why does it matter if the remote sessions were /console or not?

     

     

    Friday, July 4, 2008 12:28 AM
  • Hi

    Basically I have 2 machines, one one machine I have a service that is set so it can interact with the desktop and it act as server, on the other machine I have a client that talk with the server through TCP IP and request the server to launch an application. I'm not passing any special parameter in the STARTUPINFO structure to determine on which desktop the application should appear so by default it appear on the dekstop of the person that is physically logged in at the machine on that is logged in through mstsc /console.

    Now what I'm try to do is to be able to start this new application in the context of the person that will see it, I enumerate the active session, retrieve their token and then CPAU with this information. This is working find on WindowsXP (I haven't tested  in a Vista environment) because I can only have one active session at the time, but when I arrive in a server environment (my test machine has 2003 server) then there can be more than one terminal service session at the same time so I'm looking to find a way to determine which one was started through an mstsc /console because it's in this session that my new process window will be visiable when I will launch it from the service.

    Thanks for the help!


    Daniel
    Tuesday, July 8, 2008 11:21 PM
  • /console always lead to session 0 being remoted, (user in that session and services).

     

    But as I have mentioned it earlier, that flag becomes irrelevant starting with Vista and Server 2008.

    That's because no user ever logs on in session 0 anymore...

    As a result, interactive services are being deprecated (i.e. they probably won't be supported in a one or two releases).

     

    So you're going to have to resort to finding the right user to notify anyway.

    It's indeed easier on client machines because there's only one user active at a time...

    On servers, you could consider looking for admins, as I'm not really sure that there's a way to retrieve whether the connection was initiated with /admin or not.

     

    Tuesday, July 15, 2008 1:02 AM
  •  Eric Perlin - MSFT wrote:

    In any case, launching code as an alternate user is not recommended.

    It happens in the runas case (pure CreateProcessWithLogonW or run as administrator when the logged on user is a standard user) but a different technique is used.


    So how does it work in runas case?
    I am doing something similar to  runas like this
    Logonuser + settokeninformation (... TokenSessionId ...) + ImpersonateLoggedOnUser + CreateProcessAsUser

    Thursday, July 17, 2008 10:16 AM
  • I ran into this same problem and thought I would throw in my solution.

    For a Remote Desktop Connection, the
    WTSGetActiveConsoleSessionId returns a session ID associated with the RDP connection, not the physical connection.  When calling WTSQueryUserToken, the physical session ID needs to be passed, not the RDP Session ID.

    To fix the problem, I no longer call WTSGetActiveConsoleSessionID to get the active session ID.  I now use WTSEnumerateSessions.  I loop through the sessions until I find one with State WTS_CONNECTSTATE_CLASS::WTSActive.

    Using this session ID, WTSQueryUserToken and CreateProcessAsUser both work.   The application is launched under the correct user and can be confirmed by inspecting the Windows Task Manager from the RDP session.



    Wednesday, October 15, 2008 2:43 PM
  •  

    I tried the code of Ganeshm and it worked fine on Windows XP, but when I tried the same code on Windows Vista, the CreateProcessAsUser() Method always fails with the error invalid handle. Is there a way to find out which of the passed handles is invalid and the cause why it is invalid? I checked that none of the handles is NULL.

     

    Should I rather use CreateProcessWithTokenW on Vista?

    Wednesday, October 15, 2008 4:13 PM
  • Ron wrote:

    <<<

    For a Remote Desktop Connection, the WTSGetActiveConsoleSessionId returns a session ID associated with the RDP connection, not the physical connection.  When calling WTSQueryUserToken, the physical session ID needs to be passed, not the RDP Session ID.

    >>>

    I have to correct this. WTSGetActiveConsoleSessionId always returns the ID of the session bound to the physical machine (there's only one of these at a time). The important word here is console.

    WTSQueryUserToken returns the token of the session ID that's passed in, so if you want the token corresponding to a remote desktop session, it can't be the one WTSGetActiveConsoleSessionId returns...

    Since some SKUs support multiple remote desktop sessions (typically servers), there's no API returning *the* ID of the session in active state...

     

    Wednesday, October 15, 2008 9:30 PM
  • sbaeler,

    there's only one handle passed in (the token) so it doesn't leave a lot of room for choice.

    CreateProcessWithTokenW should almost never be used. It provides very limited benefits over CreateProcessAsUser and even fails in some scenarios where CreateProcessAsUser would have otherwise succeeded...

     

    Wednesday, October 15, 2008 9:44 PM
  • Ok, so I have to further analyze the token. But I wonder why this token is valid in XP but not in Vista.

     

    According to the documentation, the necessary access rights are TOKEN_QUERY, TOKEN_DUPLICATE, and TOKEN_ASSIGN_PRIMARY. How can I verify that these rights are set?

    GetKernelObjectSecurity() seems to go in that direction, but is there example code for that?

     

    Thursday, October 16, 2008 9:30 AM
  • If you didn't have the appropriate rights, you'd probably get "access denied" or something similar.

    If the handle is not null and is actually a valid handle to a token, it's possible another cause for error is that it is an impersonation token. A primary token is required for CPAU.

     

    Thursday, October 16, 2008 10:48 PM
  • Hello maklouf,

    I've the same problem now. Has you found a solution for this problem? Thanks.
    Wednesday, July 1, 2009 12:58 PM
  • I'm afraid that article is rather outdated (not compatible with Vista at all, and more generally with Fast User Switching and Terminal Services)...

    I'll track down the author and get this fixed. Thanks for indicating the source of the information.

     

    Hi Eric, I've the same problem. Exist anywhere a updated article now?
    Wednesday, July 1, 2009 2:31 PM
  • I've got the content of TokenInformation. The server code is as follows:

    ...

    DWORD dwReturnLen;
    DWORD dwSessionId=0;

    if (GetTokenInformation(hThreadToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId), &dwReturnLen))
    {
     ...

     _stprintf(szStatus, TEXT("CreateProcessAsUser failed: %lu (%s, Sess %lu)"),
      GetLastError(), szCmdLine, dwSessionId);

     ...

    szStatus is returned by the RPC server (running as LSA) to the client. No debugger was used.

    The client application was run from the command line in RD on the same box as the server. Session for the process is 2 as per qwinsta.

    When I overwrote the session ID on the duplicated token

    dwSessionId=0;

    SetTokenInformation(hPrimaryToken, TokenSessionId, &dwSessionId, sizeof(dwSessionId));

    then it worked.

    Note: The initial code did not do SetTokenInfo, nor anything else with dwSessionID, it was just logged just in case. Only when things did not work I've added SetTokenInfo and tried changing it.

    Note 2: Box was XP/SP2 with login from another XP/SP2.

    Adrian



    Hello Adrain/Eric, I am also facing the same problem that has been posted by you. Can I get the code snippet which has worked for you? Thanks, F
    Tuesday, February 9, 2010 5:50 AM
  • wolf

     

    DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
            public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
                ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
                String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    Try the above...

    Have a nice day...

     

    SREE


    THEY SAID I WAS LAZY, THEY DIDNT KNOW I DREAMT OF CODE!
    www.crawller.com
    PLS MARK AS ANSWER IF UR DOUBT IS SOLVED OR REVERT QUERY
    Monday, April 26, 2010 6:42 PM
  • Hi,

    I have a similar question, which I don't think was answered in this thread, if I missed it please point it out to me thanks!.

    Note: My Service is running under SYSTEM user name and the service will launch the application.

    Is it possible on (Vista /Win 7) to have a User A log onto Windows and runs an application with User B credientials?

    Given those conditions above, is it possible to do in Vista/Win 7?

    Wednesday, May 19, 2010 8:34 PM
  • DdaS-edEn and Eric:

     

    I have the same situation as DdaS-edEn's, except I'm working on Windows 7 64bit.   I copied your code and ran it, the application (notepad.exe) does not come up (not showing on the current desktop or in Task Mmaneger). Actually the CreateProcessAsUser() never returns (just gone), and the code after that are not executed.

    Has anyone   tried this code from Windows 7 64bit, calling from a printer language monitor running in the context of spoolsv.exe (a service running as Local System).

     

     

    Wednesday, October 27, 2010 11:08 PM
  • Prime83:

    I'm trying to do almost the same thing. I need to launch a little status window on the user's desktop when printing. Basically my DLL (printer language monitor) is running in the context of Spoolsv.exe (windows service under login 'System'). I had the same code working for XP and Vista 32bit, but under Windows 7 64bit, CreateProcessAsUser() never returns. It's not hanging, but my application never shows up, and the code after the CreateProcessAsUser() call never gets executed.

    Any help or direction would be greatly appreciated.
    Wednesday, October 27, 2010 11:16 PM
  • Hi all,

    I can successfully use CreateProcessAsUserW (it returns true) but every time I call it, Marshal.GetLastWin32Error() returns 5 (Access Denied). Before calling CreateProcessAsUserW, Marshal.GetLastWin32Error() returns 0.

    CreateProcessAsUserW fills PROCESS_INFORMATION with the correct ProcessID etc.

    Should I pay attention to this error ???

    My system is Windows 2008 Server R2 64 bit, and the class I actually use is:

     

      public class StartProcessHelper {
        [StructLayout(LayoutKind.Sequential)]
        public struct STARTUPINFO {
          public int cb;
          public string lpReserved;
          public string lpDesktop;
          public string lpTitle;
          public int dwX;
          public int dwY;
          public int dwXSize;
          public int dwXCountChars;
          public int dwYCountChars;
          public int dwFillAttribute;
          public int dwFlags;
          public short wShowWindow;
          public short cbReserved2;
          public IntPtr lpReserved2;
          public IntPtr hStdInput;
          public IntPtr hStdOutput;
          public IntPtr hStdError;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct PROCESS_INFORMATION {
          public IntPtr hProcess;
          public IntPtr hThread;
          public int dwProcessID;
          public int dwThreadID;
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct SECURITY_ATTRIBUTES {
          public int Length;
          public IntPtr lpSecurityDescriptor;
          public bool bInheritHandle;
        }
    
        public enum SECURITY_IMPERSONATION_LEVEL : uint {
          SecurityAnonymous = 0,
          SecurityIdentification = 1,
          SecurityImpersonation = 2,
          SecurityDelegation = 3
        }
    
        public enum TOKEN_TYPE : uint {
          TokenPrimary = 1,
          TokenImpersonation = 2
        }
    
        public const int GENERIC_ALL_ACCESS = 0x10000000;
    
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool CloseHandle(IntPtr handle);
    
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUserW(IntPtr token,
                                [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName,
                                [MarshalAs(UnmanagedType.LPTStr)] string lpCommandLine,
                                ref SECURITY_ATTRIBUTES lpProcessAttributes,
                                ref SECURITY_ATTRIBUTES lpThreadAttributes,
                                bool bInheritHandles,
                                uint dwCreationFlags,
                                IntPtr lpEnvironment,
                                [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory,
                                ref STARTUPINFO lpStartupInfo,
                                ref PROCESS_INFORMATION lpProcessInformation);
    
        [DllImport("advapi32.dll", SetLastError = true)]
        public static extern bool DuplicateTokenEx(IntPtr hExistingToken,
                              uint dwDesiredAccess,
                              ref SECURITY_ATTRIBUTES lpThreadAttributes,
                              SECURITY_IMPERSONATION_LEVEL impersonationLevel,
                              TOKEN_TYPE dwTokenType,
                              ref IntPtr phNewToken);
    
        [DllImport("kernel32.dll")]
        public static extern uint WTSGetActiveConsoleSessionId();
    
        [DllImport("wtsapi32.dll", SetLastError = true)]
        public static extern bool WTSQueryUserToken(uint sessionId,
                              out IntPtr phToken);
    
        public class StartProcessAsCurrentConsoleUserResult {
          public bool HasError { get; set; }
          public int LastWin32Error { get; set; }
          public PROCESS_INFORMATION ProcessInformation { get; set; }
        }
    
        public StartProcessAsCurrentConsoleUserResult StartProcessAsCurrentConsoleUser(string applicationName, string commandLine, string currentDirectory) {
          StartProcessAsCurrentConsoleUserResult result = new StartProcessAsCurrentConsoleUserResult();
    
          // Check last win 32 error and set result values
          Func<bool> CheckErrorLocal = () => {
            int lastWin32Error = Marshal.GetLastWin32Error();
            if (lastWin32Error != 0) {
              result.HasError = true;
              result.LastWin32Error = lastWin32Error;
              return true;
            }
            return false;
          };
    
          // Get acive console session
          uint consoleSessionId = WTSGetActiveConsoleSessionId();
          // This function returns 0xffffffff if no active console session exists
          if (consoleSessionId == uint.MaxValue) {
            result.HasError = true;
            return result;
          }
    
          // Get user token for active session
          IntPtr userToken;
          bool queryTokenResult = WTSQueryUserToken(consoleSessionId, out userToken);
          if (!queryTokenResult) {
            CheckErrorLocal();
            return result;
          }
    
          // Duplicate user token
          IntPtr duplicatedToken = IntPtr.Zero;
          PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
          SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
          sa.Length = Marshal.SizeOf(sa);
          bool duplicateTokenResult = DuplicateTokenEx(userToken,
                                 GENERIC_ALL_ACCESS,
                                 ref sa,
                                 SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
                                 TOKEN_TYPE.TokenPrimary,
                                 ref duplicatedToken);
          // Close original user token
          CloseHandle(userToken);
          if (!duplicateTokenResult) {
            CheckErrorLocal();
            return result;
          }
    
          // Create process
          STARTUPINFO si = new STARTUPINFO();
          si.cb = Marshal.SizeOf(si);
          si.lpDesktop = String.Empty;
          // TODO: ??? CreateProcessAsUserW sets last win 32 error to 5 "Access denied" but process is actually started and process information struct filled with valid PID etc. ???
          bool createProcessResult = CreateProcessAsUserW(duplicatedToken,
                                  applicationName,
                                  applicationName + " " + commandLine,
                                  ref sa,
                                  ref sa,
                                  false,
                                  0,
                                  IntPtr.Zero,
                                  currentDirectory,
                                  ref si,
                                  ref pi);
          if (createProcessResult == false) {
            CheckErrorLocal();
            // Don't return yet - we must close handles
          }
    
          if (pi.hProcess != IntPtr.Zero) {
            CloseHandle(pi.hProcess);
          }
          if (pi.hThread != IntPtr.Zero) {
            CloseHandle(pi.hThread);
          }
          if (duplicatedToken != IntPtr.Zero) {
            CloseHandle(duplicatedToken);
          }
    
          return result;
        }
      }

     

    I call it with the following sample code - starting notepad and open file C:\Sample.txt :

    Parameter values are as follows:

    appName = "C:\Windows\notepad.exe"

    cmdLineValue = "C:\Sample.txt"

    curDirValue = "C:\"

     

       StartProcessHelper procHelper = new StartProcessHelper();
       StartProcessHelper.StartProcessAsCurrentConsoleUserResult startProcResult = procHelper.StartProcessAsCurrentConsoleUser(appName, cmdLineValue, curDirValue);
    
    

     

    Sample caller isn't really service but is executed under the SYSTEM account - I execute it using:

    PsExec.exe -i -s "Path to executable that calls CreateProcessAsUserW"

    PsExec.exe can be found in PsTools package from sysinternals.com at http://technet.microsoft.com/en-us/sysinternals/bb896649.aspx

     

    Thenk you.

    • Edited by Georgi Ganchev Tuesday, November 9, 2010 4:46 PM Formatting was ____
    Tuesday, November 9, 2010 4:37 PM
  • For what is it worth, I was able to get Coder0xFF's PInvoke example working on Windows 7 Ultimate x64 by using the following flags:

    CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_UNICODE_ENVIRONMENT

     

    I couldn't get it working using the DETACHED_PROCESS flags, which is why I substituted the CREATE_NEW_CONSOLE.

     

    My code is running from a LocalSystem service.

    Saturday, January 22, 2011 5:02 PM