Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 :

  • automatic
  • no interactive.
  • detect the session ID of the user logged

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...

like image 576
user1039768 Avatar asked Nov 10 '11 14:11

user1039768


1 Answers

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;
like image 74
Remy Lebeau Avatar answered Oct 07 '22 00:10

Remy Lebeau