CreateProcessAsUser ne fonctionne pas lorsque “change user”

Tout d’abord, je tiens à remercier toutes les personnes qui travaillent pour ce site, très utile pour un développeur. C’est la première fois que je suis bloqué dans mon développement depuis 3 jours. J’ai cherché des solutions sur Internet mais je ne trouve rien qui résout ce problème.

Je développe donc un service qui doit exécuter un programme externe sur Vista / Seven / XP lorsqu’un utilisateur est connecté. Quelques caractéristiques de ce service:

  • automatique
  • pas interactif
  • détecter l’ID de session de l’utilisateur connecté

Pour exécuter l’application graphique externe en tant qu’utilisateur interactif:

  1. Pour être sûr qu’une session utilisateur est ouverte, je liste TOUS le processus “explorer.exe”, extrait leur Pid et SessionID avec la fonction msdn ProcessIdToSessionId
  2. Si l’ID de session de l’utilisateur connecté est égal à l’ID de session de ce processus “explorer.exe”, je suis sûr que le “bon” bureau est en cours d’exécution et que je peux maintenant exécuter le programme externe. (Je dis “bon” ordinateur de bureau car, comme vous le savez, plusieurs sessions utilisateur peuvent être ouvertes sur le système)
  3. après cela, je lance l’application avec cette fonction:

    function RunInteractive(prog_filename: Ssortingng; 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; 

Ce code est correct dans la plupart des cas sauf un: quand je change d’utilisateur. Permettez-moi de l’expliquer avec 2 utilisateurs simples (Domain \ user1 et Domain \ user2):

  1. Pour être propre, j’installe le service et redémarre le système
  2. J’ouvre la session avec user1: le programme externe est exécuté et je peux voir sa forme
  3. Je ferme la session et opensession avec user2: le programme externe est exécuté et je peux voir sa forme.

Si je le fais X fois, le résultat est toujours le même, très bon … mais si je fais ceci:

  1. Je réinstalle le service et redémarre le système
  2. J’ouvre la session avec user1: le programme externe est exécuté et je peux voir sa forme
  3. cette fois, je ne ferme pas la session mais change d’utilisateur avec user2: le programme externe est exécuté mais je ne peux pas voir le formulaire et une erreur est survenue: Code d’erreur système 5: Accès refusé.

Quelque chose ne va pas, mais je ne trouve pas la solution. Merci pour vos réponses …

Vous n’avez pas besoin d’énumérer les processus explorer.exe en cours d’exécution, vous pouvez utiliser WTSGetActiveConsoleSessionId() , puis transmettre ce SessionId à WTSQueryUserToken() . Notez que WTSQueryUserToken() renvoie un jeton d’emprunt d’identité mais CreateProcessAsUser() besoin d’un jeton principal, utilisez donc DuplicateTokenEx() pour cette conversion.

Vous devez également utiliser CreateEnvironmentBlock() pour que le processus généré ait un environnement approprié adapté au compte d’utilisateur utilisé.

Enfin, définissez le champ STARTUPINFO.lpDesktop sur 'WinSta0\Default' au lieu de nil pour que l’interface utilisateur générée puisse être rendue visible correctement.

J’utilise cette approche depuis plusieurs années maintenant et je n’ai eu aucun problème avec elle. Par exemple:

 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: Ssortingng): 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; 

Probablement votre méthode pour obtenir l’ID de session en trouvant le “bon” explorer.exe ne fonctionne pas pour un changement rapide d’utilisateur.

Essayez de faire enregistrer votre application pour les notifications de modification de session avec WTSRegisterSessionNotification . Vous recevrez alors des notifications lorsque la session change, complétez avec l’ID de session en cours.

Notez les points suivants:

Pour recevoir les notifications de modification de session d’un service, utilisez la fonction HandlerEx .