Delphi-PRAXiS
Seite 3 von 5     123 45      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Programm von Dienst starten lassen (Jetzt aber wirklich mal) (https://www.delphipraxis.net/109191-programm-von-dienst-starten-lassen-jetzt-aber-wirklich-mal.html)

Dezipaitor 23. Mai 2008 22:07

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Eine Session (oder Sitzung) ist ein abgetrennter Bereich, der eigene WindowStations und Desktops besitzt (und einiges andere).
Jeder Benutzer, der sich per Winlogon einloggt, wird eine neue SessionID zugewiesen.
In Windows 2000 gibt es nur die 0. In XP bekommt der erste Benutzer die 0, alle anderen, 1 oder größer. Dienste tummeln sich immer in Session 0 und sind daher mit dem ersten eingeloggten Benutzer in einem Boot. Daher können Dienst auf den Benutzerdesktop zugreifen und CreateCompatibilityQueryUserToken findet Explorer.exe

In Vista jedoch, hat der erste Benutzer die Session ID #1 und die Dienste #0. Da jede Session ihren eigenen Desktop hat (vereinfacht ausgedrückt) und Explorer.exe nur für normale Benutzer gestartet wird, gibt es Probleme.

Die Session ID #2, dich ich angegeben habe, habe ich mit der Absicht angegeben, dass man einen Fehler bekommst. Nur in diesem Fall macht man sich Gedanken drüber. (Hatte vorhin wenig Zeit was zu schreiben).

CodeX 23. Mai 2008 23:45

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Vielen Dank für die Erläuterung! Damit wird so einiges klarer.

Hole mir die TokenSessionID jetzt dynamisch vom aktuellen Benutzer und erstelle für diesen auch gleich den passenden Umgebungsblock.
Scheint alles zu funktionieren. :)

Intern werde ich also drei verschiedene Blöcke für 2000, XP und Vista basteln, damit das jeweils korrekt funktioniert.
Gehe ich richtig in der Annahme, dass sich 2003 wie XP verhält? Und 2008 wie Vista? Stehen mir momentan leider nicht zum Testen zur Verfügung...

Christian, ich möchte noch loswerden, dass ich ganz großen Respekt vor Deinem Wissen und Deiner Leistung habe. Allein wenn ich sehe, was alles in der JWSCL steckt, kann ich nur staunen. Wenn man aber noch bedenkt, dass da jemand dahinter steckt, der das alles versteht und systematisch aufgebaut hat, kann man nur noch gratulieren. Dann nimmst Du Dir aber noch die Zeit, mir kleinem Programmierer, bei meinen Problemen zu helfen. Einfach klasse. Vielen Dank für alles! :hello:

Remko 24. Mai 2008 09:27

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Great that you got it all working! Maybe you can post your final code for future reference?

Just adding some notes, also future reference:
Vista SP1 and Server 2008 share the same kernel and terminal server.
I don't see why WTSQueryUserTokenEx should fail on vista (it uses undocumented api from JwaWinsta but actually it does exactly the same as WTSQueryUserToken internally).
Don't forget this note about WTSGetActiveConsoleSession:
Return Value
The session identifier of the session that is attached to the physical console. If there is no session attached to the physical console, (for example, if the physical console session is in the process of being attached or detached), this function returns 0xFFFFFFFF.
On Windows 2000 with Terminal Server it's safe to assume session 0 is the console session.
If you launch a process as user system you always get the environment of Default User because system doesn't have one, same goes for CURRENT_USER in the registry.

CodeX 25. Mai 2008 17:06

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Sure, this is my final code (so far ;)):

Delphi-Quellcode:
      ZeroMemory(@StartupInfo, sizeof(StartupInfo));
      StartupInfo.cb         := SizeOf(StartupInfo);
      StartupInfo.lpDesktop  := 'winsta0\default';

      pCmdLine := TJwPChar('"'+App+'" ' + Parameters);

      pCurDir := Nil;
      if Length(CurDir) > 0 then
        pCurDir := TJwPChar(CurDir);


    //get the token from the service system session
    ServiceToken := TJwSecurityToken.CreateTokenEffective(MAXIMUM_ALLOWED);

    //copy the token to be able to change the TokenSessionId
    //Info: Win2000:  Only 0
    //       WinXP:    Service=0, 1.User=0, 2.User=1, 3.User=2, ...
    //       WinVista: Service=0, 1.User=1, 2.User=2, 3.User=3, ...
    CopiedToken := TJwSecurityToken.CreateDuplicateExistingToken(ServiceToken.TokenHandle, MAXIMUM_ALLOWED);


    //get the token of the logged in user
    if is2000 then
      UserToken := TJwSecurityToken.CreateCompatibilityQueryUserToken(MAXIMUM_ALLOWED, 'explorer.exe')
    else //XP, 2003, Vista, 2008
      UserToken := TJwSecurityToken.CreateWTSQueryUserTokenEx(nil, WtsGetActiveConsoleSessionID);


    //give the copied token the same sessionid as the logged in user
    CopiedToken.TokenSessionId := UserToken.TokenSessionId;

     
    //create the environment block using the logged in user
    JwaWindows.CreateEnvironmentBlock(@pEnv, UserToken.TokenHandle, true);


    try
     if not CreateProcessAsUser(  { TODO : UNICODE VERSION? }
      CopiedToken.TokenHandle,
      TJwPChar(App),    //__in_opt    LPCTSTR lpApplicationName,
      pCmdLine,         //__inout_opt LPTSTR lpCommandLine,
      nil,              //__in_opt    LPSECURITY_ATTRIBUTES lpProcessAttributes,
      nil,              //__in_opt    LPSECURITY_ATTRIBUTES lpThreadAttributes,
      true,             //__in        BOOL bInheritHandles,
      CREATE_NEW_CONSOLE or CREATE_DEFAULT_ERROR_MODE or CREATE_UNICODE_ENVIRONMENT,
                         //__in        DWORD dwCreationFlags,
      pEnv,             //__in_opt    LPVOID lpEnvironment,
      pCurDir,          //__in_opt    LPCTSTR lpCurrentDirectory,
      StartupInfo,      //__in        LPSTARTUPINFO lpStartupInfo,
      ProcInfo          //__out       LPPROCESS_INFORMATION lpProcessInformation
     ) then
     raiseLastOsError;
    finally
      DestroyEnvironmentBlock(pEnv);
    end;


      CloseHandle(ProcInfo.hProcess);
      CloseHandle(ProcInfo.hThread);
Getestet mit 2000, XP und Vista und für gut befunden. :)
"is2000" ist eine Prozedur, die prüft, ob das aktuelle Betriebssystem Win2000 ist. Müsste man sich also noch dazubasteln.

Einige der Schritte (z.B. SessionID kopieren, CreateProcessAsUser statt CreateProcess, ...) sind nicht für alle Betriebssysteme notwendig, schaden aber auch nicht. Ich fand es sinnvoller, den Code übersichtlich zu lassen, als wirklich unterschiedliche Blöcke für jedes Betriebssystem zu erstellen.

Was ich noch nicht so recht weiß, ist, ob es wirklich notwendig ist, die Unicode Version von CreateProcessAsUser (mit entsprechenden Parametern) zu verwenden. Sehe aber auch keine Nachteile, weshalb ich das wohl noch machen werde.

Anmerkungen und Verbesserungsvorschläge werden natürlich gern angenommen. ;)

Dezipaitor 25. Mai 2008 21:25

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
  • Du hast 3 Tokeninstanzen, die du nicht wieder löscht.
  • Wenn irgendwas nach ServiceToken := ... fehlschlägt, dann werden diese Instanzen nicht gelöscht. Also einzeln mit try/finally für nach jedem Create eins erstellen.
    Delphi-Quellcode:
    Create1
    try
      create2
      try
      finally
        Free2
      end;
    finally
      Free1;
    end;
    Übrigens:
    JwsclComUtils implementiert AutoDestructZeiger.

    Delphi-Quellcode:
    Token2 := TJwSecurityToken....
    TJwAutoPointer.Wrap(Token2);
    In diesem Fall wird das Objekt Token2 gelöscht, wenn man beim "End;" ankommt.

    Es geht auch mehrmals. Alle mit Wrap umschlossenen Objekte werden zerstört. Egal welcher Variablenname.
    Delphi-Quellcode:
    Token2 := TJwSecurityToken....
    TJwAutoPointer.Wrap(Token2);
    Token2 := TJwSecurityToken....
    TJwAutoPointer.Wrap(Token2);

    Man kann soetwas auch für eine Instanzvariable machen, wenn man das COM Objekt speichert.
    Delphi-Quellcode:
     TXY = class
       myWrappedToken : IJwAutoPointer;
       Token2 := TJwSecurityToken;

       procedure Do;
     end;

    procedure TXY.Do;
    begin
      Token2 := TJwSecurityToken....
      myWrappedToken := TJwAutoPointer.Wrap(Token2);
    end;
    Sobald die Klasseninstanz zerstört wird, wird das Token gelöscht.



  • Jedes Create aus der JWSCL kann eine Exception liefern. Man sollte sie nicht ignorieren. Es ist zum eigenen Schutz, da die JWSCL alle Fehlercodes der SecurityAPI in Exceptions umwandelt, wird verhindert, dass Code danach ausgeführt wird, der so garnicht zum Laufen kommen sollte.
  • WtsGetActiveConsoleSessionID liefert dasselbe wie UserToken.TokenSessionId. Du brauchst also garkein UserToken erstellen. Für Win2000 kannst du auch einfach 0 verwenden.
  • Diese Zeile
    Delphi-Quellcode:
    true,             //__in        BOOL bInheritHandles,
    funktioniert nicht unbedingt unter XP. Unter Vista jedoch schon. Sie funkz dann nicht, wenn die Session eine andere ist, als die des Prozesses in dem CreateProcess... aufgerufen wird. Handles können nicht über Sessiongrenzen hinweg vererbt werden. Also false setzen.
  • UNICODE <-> ANSICODE
    Delphi-Quellcode:
    CreateProcessAsUser(  { TODO : UNICODE VERSION? }
          CopiedToken.TokenHandle,
          TJwPChar(App),
    Wenn du das mit UNICODE kompilierst, dann funkz es nicht, weil TJwPChar dann ein PWideChar sein wird. Dasselbe mit CreateProcessAsUserW (wenn du es änderst). Also entweder machst du ein IFDEF UNICODE um CreateProcessAsUserA/W oder du verwendest nicht TJwPchar sonder PAnsiChar.
  • Delphi-Quellcode:
        JwaWindows.CreateEnvironmentBlock(@pEnv, UserToken.TokenHandle, true);
    Warum true hier? Willst du Env-Strings von SYSTEM mit drin haben?

CodeX 1. Jun 2008 19:30

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Zitat:

Zitat von Dezipaitor
WtsGetActiveConsoleSessionID liefert dasselbe wie UserToken.TokenSessionId. Du brauchst also garkein UserToken erstellen. Für Win2000 kannst du auch einfach 0 verwenden.

Das stimmt, aber ich brauche das UserToken für
Delphi-Quellcode:
JwaWindows.CreateEnvironmentBlock(@pEnv, UserToken.TokenHandle, false);
sowieso. Da komme ich ja nicht drumherum, wenn ich die Benutzerumgebung mitgeben will.


Wenn ich TJwAutoPointer.Wrap(...) verwende, brauche ich dann die try-finally Blöcke trotzdem noch oder reicht das tatsächlich einfach nach jedem Erzeugen das Token einmal zu wrappen?


Deine anderen Hinweise habe ich soweit befolgt und den Code angepasst. Danke!

Dezipaitor 1. Jun 2008 19:50

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Entweder wrappen oder try/finally.

Dezipaitor 21. Jun 2008 15:13

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Ja was ist denn? Funktioniert es oder nicht?

CodeX 21. Jun 2008 19:27

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Ja, klar. Hatte das ja schon bei meinem letzen Posting geschrieben. Habe eben noch die kleineren Anpassungen, die Du geschrieben hast, eingebaut.

So sieht das ganze in der letzten Version aus:

Delphi-Quellcode:
procedure StartApp(const App, Parameters, CurDir : TJwString);
var
  StartupInfo : TStartupInfoA;
  ProcInfo : TProcessInformation;
  pEnv : Pointer;
 
  pCurDir,
  pCmdLine : TJwPChar;

  ServiceToken, CopiedToken, UserToken : TJwSecurityToken;
begin

    ZeroMemory(@StartupInfo, sizeof(StartupInfo));
    StartupInfo.cb         := SizeOf(StartupInfo);
    StartupInfo.lpDesktop  := 'winsta0\default';

    pCmdLine := TJwPChar('"'+App+'" ' + Parameters);

    pCurDir := Nil;
    if Length(CurDir) > 0 then
      pCurDir := TJwPChar(CurDir);


    //get the token from the service system session
    ServiceToken := TJwSecurityToken.CreateTokenEffective(MAXIMUM_ALLOWED);
    TJwAutoPointer.Wrap(ServiceToken);

    //copy the token to be able to change the TokenSessionId
    //Info: Win2000:  Only 0
    //       WinXP:    Service=0, 1.User=0, 2.User=1, 3.User=2, ...
    //       WinVista: Service=0, 1.User=1, 2.User=2, 3.User=3, ...
    CopiedToken := TJwSecurityToken.CreateDuplicateExistingToken(ServiceToken.TokenHandle, MAXIMUM_ALLOWED);
    TJwAutoPointer.Wrap(CopiedToken);

   
    //get the token of the logged in user
    if is2000 then
      UserToken := TJwSecurityToken.CreateCompatibilityQueryUserToken(MAXIMUM_ALLOWED, 'explorer.exe')
    else //XP, 2003, Vista, 2008
      UserToken := TJwSecurityToken.CreateWTSQueryUserTokenEx(nil, WtsGetActiveConsoleSessionID);
    TJwAutoPointer.Wrap(UserToken);

    //give the copied token the same sessionid as the logged in user
    CopiedToken.TokenSessionId := UserToken.TokenSessionId;
   

    //create the environment block using the logged in user
    JwaWindows.CreateEnvironmentBlock(@pEnv, UserToken.TokenHandle, false);


    try
     if not CreateProcessAsUser(
      CopiedToken.TokenHandle,
      TJwPChar(App),    //__in_opt    LPCTSTR lpApplicationName,
      pCmdLine,         //__inout_opt LPTSTR lpCommandLine,
      nil,              //__in_opt    LPSECURITY_ATTRIBUTES lpProcessAttributes,
      nil,              //__in_opt    LPSECURITY_ATTRIBUTES lpThreadAttributes,
      false,             //__in        BOOL bInheritHandles,
      CREATE_NEW_CONSOLE or CREATE_DEFAULT_ERROR_MODE or CREATE_UNICODE_ENVIRONMENT,
                         //__in        DWORD dwCreationFlags,
      pEnv,             //__in_opt    LPVOID lpEnvironment,
      pCurDir,          //__in_opt    LPCTSTR lpCurrentDirectory,
      StartupInfo,      //__in        LPSTARTUPINFO lpStartupInfo,
      ProcInfo          //__out       LPPROCESS_INFORMATION lpProcessInformation
     ) then
     raiseLastOsError;
    finally
      DestroyEnvironmentBlock(pEnv);
    end;


    CloseHandle(ProcInfo.hProcess);
    CloseHandle(ProcInfo.hThread);

end;

Dezipaitor 21. Jun 2008 22:44

Re: Programm von Dienst starten lassen (Jetzt aber wirklich
 
Ja das sieht doch super aus. Kann ich das für den JEDI Blog verwenden als Post?


Alle Zeitangaben in WEZ +1. Es ist jetzt 12:19 Uhr.
Seite 3 von 5     123 45      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz