user1039768
user1039768

Reputation: 171

CreateProcessAsUser doesn't work when "change user"

First , I want to thank all the persons who works for this site, very useful for a developer. This is the first I am blocked in my developement since 3 days. I have searched solutions on Internet but I find nothing which solves this issue.

So, I develop a service which have to execute an external program on vista/seven/xp when a user is logged. Some characteristics of this service :

To run the external GUI application as the interactive user:

  1. To be sure a user session is opened, I list ALL the "explorer.exe" process, extract their Pid and SessionID with the msdn function ProcessIdToSessionId
  2. if the SessionID of the logged user is equal with the session ID of this "explorer.exe" process, I am sure that the "good" desktop is running so now I can execute the external program. (I say "good" desktop because, as you know, more than one user session can be opened on the system )
  3. after that, I run the application with this function:

    function RunInteractive(prog_filename: String; sessionID: Cardinal): boolean;
    var hToken: THandle;
    si: _STARTUPINFOA;
    pi: _PROCESS_INFORMATION;
    begin
    ZeroMemory(@si, SizeOf(si));
    si.cb := SizeOf(si);
    SI.lpDesktop := nil;
    if WTSQueryUserToken(sessionID, hToken)
    then  begin
          if CreateProcessAsUser(hToken, nil, PChar(prog_filename), nil, nil, False, 0, nil, PChar(ExtractFilePath(prog_filename)), si, pi)
          then  result := true
          else result := false;
        end
    else  Begin
          result := false;
          End;
    CloseHandle(hToken);
    end;
    

This code is ok in most of case except one: when I change User. Let me explain it with 2 simple users (Domain\user1 and Domain\user2):

  1. To be clean, I install the service and reboot the system
  2. I open session with user1: the external program is executed and I can see its form
  3. I close the session and opensession with user2 : the external program is executed and I can see its the form.

If I do this X times, the result is always the same, very good...but If I do this:

  1. I re-install the service and reboot the system
  2. I open session with user1: the external program is executed and I can see its form
  3. this time, I am not close the session but change user with user2 : the external program is executed but I cannot see the form and an error is occured : System error code 5: Access denied.

Something is wrong but I don't find the solution. Thanks for your answers...

Upvotes: 16

Views: 4348

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 596703

You don't need to enumerate running explorer.exe processes, you can use WTSGetActiveConsoleSessionId() instead, and then pass that SessionId to WTSQueryUserToken(). Note that WTSQueryUserToken() returns an impersonation token but CreateProcessAsUser() needs a primary token, so use DuplicateTokenEx() for that conversion.

You should also use CreateEnvironmentBlock() so the spawned process has a proper environment that is suited to the user account that is being used.

Lastly, set the STARTUPINFO.lpDesktop field to 'WinSta0\Default' instead of nil so the spawned UI can be made visible correctly.

I have been using this approach for several years now and have not had any problems with it. For example:

function CreateEnvironmentBlock(var lpEnvironment: Pointer; hToken: THandle; bInherit: BOOL): BOOL; stdcall; external 'userenv.dll'
function DestroyEnvironmentBlock(lpEnvironment: Pointer): BOOL; stdcall; external 'userenv.dll';

function RunInteractive(prog_filename: String): Boolean;
var
  hUserToken, hToken: THandle;
  si: _STARTUPINFOA;
  pi: _PROCESS_INFORMATION;
  SessionId: DWORD;
  Env: Pointer;
begin
  Result := False;

  ZeroMemory(@si, SizeOf(si));
  si.cb := SizeOf(si);
  si.lpDesktop := 'WinSta0\Default';

  SessionId := WTSGetActiveConsoleSessionId;
  if SessionId = $FFFFFFFF then Exit;

  if not WTSQueryUserToken(SessionID, hToken) then Exit;
  try
    if not DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, nil, SecurityIdentification, TokenPrimary, hUserToken) then Exit;
  finally
    CloseHandle(hToken);
  end;

  try
    if not CreateEnvironmentBlock(Env, hUserToken, False) then Exit;
    try
      Result := CreateProcessAsUser(hUserToken, nil, PChar(prog_filename), nil, nil, False, CREATE_UNICODE_ENVIRONMENT, Env, PChar(ExtractFilePath(prog_filename)), si, pi);
      if Result then
      begin
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
      end;
    finally
      DestroyEnvironmentBlock(Env);
    end;
  finally
    CloseHandle(hUserToken);
  end;
end;

Upvotes: 16

Marcus Adams
Marcus Adams

Reputation: 53860

Probably your method of getting the session ID by finding the "good" explorer.exe is not working for fast user switching.

Try having your application register for Session change notifications with WTSRegisterSessionNotification. You will then get notifications when the session switches, complete with the current session ID.

Note the following:

To receive session change notifications from a service, use the HandlerEx function.

Upvotes: 0

Related Questions