问题
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:
- 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
- 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 )
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):
- To be clean, I install the service and reboot the system
- I open session with user1: the external program is executed and I can see its form
- 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:
- I re-install the service and reboot the system
- I open session with user1: the external program is executed and I can see its form
- 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...
回答1:
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;
回答2:
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.
来源:https://stackoverflow.com/questions/8081429/createprocessasuser-doesnt-work-when-change-user