AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Tutorials Delphi CreateProcessWithLogonW + Environment
Tutorial durchsuchen
Ansicht
Themen-Optionen

CreateProcessWithLogonW + Environment

Ein Tutorial von Olli · begonnen am 2. Aug 2005 · letzter Beitrag vom 18. Aug 2005
Antwort Antwort
Olli
Einführung

Hier soll es um MSDN-Library durchsuchenCreateProcessWithLogonW() gehen, jene Funktion die seit einiger Zeit häufiger benutzt wird, weil manche den Umweg über das alte MSDN-Library durchsuchenCreateProcessAsUser, dem man ein Token des bereits angemeldeten Benutzers (siehe MSDN-Library durchsuchenLogonUser(), benötigte aber z.B. das Privileg SE_TCB_NAME) übergeben mußte. Dagegen ist nichts einzuwenden, zumal offenbar keinerlei spezielle Privilegien notwendig sind um MSDN-Library durchsuchenCreateProcessWithLogonW() aufzurufen, bei der älteren Alternative jedoch schon.

Voraussetzung: Es wird entsprechend der Verfügbarkeit der besprochenen API mindestens Windows 2000 vorausgesetzt!

Environment Block (EB)

Nun ist es so, daß man, wie bei einigen anderen Win32-APIs auch, einen sog. Environment-Block (dt.: "Umgebungsblock") - ab hier abgekürzt als EB - übergeben kann (, nicht muß!). Und genau hier gibt es offenbar ein Verständnisproblem.
  • Wenn der EB Strings enthält, warum ist er dann ein generischer (untypisierter) Pointertyp?
  • Was hat es mit dieser komischen Beschreibung der "Struktur" des EB auf sich?
Nunja, hier soll es also darum gehen diese und eventuell noch mehr auftauchende Fragen zu beantworten.

Zuersteinmal kennt jeder von euch sicher schon den EB?! Richtig? Wer jetzt sagt: Nein! der darf mal die <Windowstaste+R> drücken und dann "CMD /K SET" (ohne die Anführungsstriche) eingeben und <Enter> drücken. In dem Konsolenfenster, welches sich daraufhin öffnet, sieht man die Ausgabe des EB. Jetzt geht dem einen oder anderen vielleicht schon ein Licht auf. Der EB enthält also die Umgebungsvariablen (%PATH% ist z.B. sehr wichtig!). Der Befehl "SET" ist nicht nur zur Ausgabe, sondern auch zum Setzen von Umgebungsvariablen zuständig. Daher sollten sich alle, die mehr Infos zu diesem Befehl wollen, diese mit "SET /?" selber holen

Spezielle generische Eigenarten

Nun da wir wissen was der EB enthält, kommt die nächste Sache: warum ist es ein generischer Pointertyp? (MSDN-Library durchsuchenLPVOID entspricht Delphis Delphi-Referenz durchsuchenPointer)
Die Frage ist sehr einfach zu beantworten. Da man sowohl einen Pointer auf eine nullterminierte Kette von ANSI-Zeichen (Delphi-Referenz durchsuchenPAnsiChar, entspricht aktuell PChar) als auch auf eine nullterminierte Kette von Unicode-Zeichen (Delphi-Referenz durchsuchenPWideChar) übergeben kann, ist es relativ klar, daß man keinen dieser beiden Typen bevorzugen kann. Stattdessen wird ein generischer Pointertyp gewählt. Alles klar?

Datenformat

Gut, also weiter im Text. Was bedeutet die Beschreibung im PSDK? Nunja, einigen, z.B. jenen die bereits mit den Registrywerten der Leistungsprotokolle (Performance) zu tun hatten; und auch jenen, die REG_MULTI_SZ schreiben mußten; oder sogar jenen die direkt auf API-Ebene den Datei-Öffnen/Speichern-Dialog aufgerufen haben, wird die Struktur bekannt vorkommen. In all diesen Fällen gilt nämlich, daß es sich um eine Liste von Listen von nullterminierten Zeichenketten handelt. Wie funktioniert das? Schauen wir uns ein Beispiel an, welches mit Delphi-Literalen veranschaulicht wird:
Delphi-Quellcode:
const bla1 = 'String1' + #0 + 'String2' + #0 + 'LetzterString' + #0 + #0;
const bla2 = 'String1' + #0 + 'String2' + #0 + 'LetzterString' + #0#0;
Hinweis: bla1 und bla2 enthalten identische Werte. Ich wollte nur darauf aufmerksam machen, daß man zwischen Zeichenliteralen (#0 und #0) auch das "+" weglassen kann.

Dies ist also alles was uns das PSDK zu vermitteln sucht. Es ist einfach eine Aneinanderkettung von Einzelstrings, welche durch #0 getrennt und mit einem Extra-"#0" abgeschlossen sind. Nun ist das ja ziemlich kompliziert, wenn wir uns vorstellen, daß wir einen Puffer erstellen müßten, den wir mit Nullen füllen um danach auf die gezeigte Weise die Strings aneinanderzufügen. Aber das ist nicht halb so kompliziert wie man zuerst meinen könnte ...

So geht's in Delphi

So wird's gemacht: Delphis Stringtypen Delphi-Referenz durchsuchenAnsiString (je nach Compilereinstellung ist String identisch mit AnsiString) und Delphi-Referenz durchsuchenWideString sind ja "counted strings" also gezählte Stringtypen. Den Unterschied macht hier, daß wir in einen solchen gezählten Strings nicht darauf angewiesen sind, daß ein #0 (Zeichen mit den Ordinalwert 0) den String abschließt. Es ist also durchaus zulässig in einen Delphistring eine #0 zu schreiben. Genau dies machen wir uns zunutze ... ja sogar noch besser, dank der Tatsache daß #0 im Kontext eines WideStrings eben das Delphi-Referenz durchsuchenWideChar #0 beschreibt, ist der Code sogar fast portabel

In Delphi schreiben wir also tatsächlich
Delphi-Quellcode:
var StringVariable: AnsiString;
begin
  StringVariable := 'String1' + #0 + 'String2' + #0 + 'LetzterString' + #0 + #0;
end;
oder
Delphi-Quellcode:
var StringVariable: WideString;
begin
  StringVariable := 'String1' + #0 + 'String2' + #0 + 'LetzterString' + #0 + #0;
end;
Wobei man sofort bemerkt, daß sich nur der Typ der StringVariablen ändert, nicht jedoch der eigentliche Code der Zuweisung. Auch dies kann man sich zunutzemachen.

Erklärung: Ich verwende StringType und CharType ab jetzt als Platzhalter für WideString/AnsiString und WideChar/AnsiChar - wobei es auch die zusammengesetzte Form PWideChar/PAnsiChar geben wird!

Selbstgekocht ist am leckersten ...

Der EB möchte also beispielsweise folgende Form für zu definierende Umgebungsvariablen PATH und USERNAME:
Delphi-Quellcode:
var StringVariable: StringType;
begin
  StringVariable := 'USERNAME=ottokar' + #0 + 'PATH=C:\Bla' + #0 + #0;
end;
War doch eigentlich ganz einfach, oder?

... abgekupfert schmeckt aber auch nicht schlecht ...

Statt sich den EB mühsam selbst zusammenzubasteln, gibt es noch die Variante sich den EB des aktuellen Prozesses über eine API zu kopieren. Die besagte API heißt MSDN-Library durchsuchenCreateEnvironmentBlock(). Nachfolgend die Deklarationen dieser Funktion und ihres Gegenparts (MSDN-Library durchsuchenDestroyEnvironmentBlock) in Delphi:
Delphi-Quellcode:
function CreateEnvironmentBlock(
  var lpEnvironment: LPVOID;
  hToken: HANDLE;
  bInherit: Boolean
  ): Boolean; stdcall; external 'userenv.dll';

function DestroyEnvironmentBlock(
  lpEnvironment: LPVOID
  ): Boolean; stdcall; external 'userenv.dll';
Da man sich vorher noch vom aktuellen Prozeß das Token holen muß und auch noch Checks auf die Rückgabewerte nötig sind, ist dies ein typischer Kandidat für eine Wrapperfunktion:
Delphi-Quellcode:
(******************************************************************************
Function to get a copy of the current environment.
The returned pointer MUST BE DESTROYED using the DestroyEnvironmentBlock() API.
******************************************************************************)


function GetCurrentEnvironmentBlock(): LPVOID;
var
  hToken: HANDLE;
begin
  Result := nil;
  // Open process token of current process
  if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY or TOKEN_DUPLICATE, hToken)) then
  try
    // Create copy of current environment block
    if (not CreateEnvironmentBlock(Result, hToken, True)) then
      Result := nil;
  finally
    CloseHandle(hToken); // Close the token of the current process
  end;
end;
Diese Funktion gibt bei Erfolg den Pointer auf den EB (in Unicodeform!) zurück, oder nil bei Mißerfolg.

Es wären also da die Varianten der Übergabe eines eigenen selbstgebastelten EB, eines kopierten EB und es wird noch eine weitere hinzukommen.

Praxis

Da ich mal hoffe, daß alle Leser halbwegs mit der Delphi-Syntax vertraut sind und sich entsprechende Änderungen (zusätzliche Parameter an die API übergeben, usw.) selbst implementieren können. Mein Code beschränkt sich auf eine Untermenge der verfügbaren Parameter und verwaltet den Rest intern. Dadurch muß man z.B. schonmal keine Strukturen übergeben - die sowieso nur relevant wären um Fenstergrößen und -positionen zu übergeben.

Außerdem ist meine Funktion insofern hartkodiert, als daß sie immer CREATE_UNICODE_ENVIRONMENT in den Flags benutzt (zumal GetCurrentEnvironmentBlock() ja auch die Kopie als Unicode zurückgibt )! Die Parameternamen entsprechen denen der Ursprungs-API, bedürfen also keiner speziellen Erklärung.
Delphi-Quellcode:
(******************************************************************************
Function to wrap around the CreateProcessWithLogon() API
******************************************************************************)


function CreateProcessWithLogonWrapper(
  lpUsername: LPCWSTR;
  lpDomain: LPCWSTR;
  lpPassword: LPCWSTR;
  lpApplicationName: LPCWSTR;
  lpCommandLine: LPWSTR;
  dwCreationFlags: DWORD;
  lpEnvironment: LPVOID = nil
  ): Boolean;
var
  sui: STARTUPINFOW;
  pi: PROCESS_INFORMATION;
begin
  Result := False;
  // Fill sui with zeroes
  ZeroMemory(@sui, sizeof(sui));
  // Fill size member
  sui.cb := sizeof(sui);
  // Create process
  if (CreateProcessWithLogonW(
    lpUsername,
    lpDomain, // Domain
    lpPassword,
    LOGON_WITH_PROFILE, // No special options
    lpApplicationName, // Module to execute
    lpCommandLine, // Activates extensions
    dwCreationFlags or CREATE_UNICODE_ENVIRONMENT, // Only these options for now
    lpEnvironment,
    nil, // Use current directory
    sui, // STARTUPINFO
    pi // PROCESS_INFORMATION
    )) then
    Result := True; // Return success
end;
Ich führe also die neue Funktion CreateProcessWithLogonWrapper() ein, die einfach nur die - für uns wichtigsten - Parameter weitergibt und den Rest intern selbermacht.

Für uns ist ja im Kontext dieses Themas nur interessant, wie sich unsere Funktion bei den verschiedenen Varianten als Übergabe für lpEnvironment schlägt. Aber Moment ... ich hatte noch eine 3te Variante versprochen. Und hier ist sie denn auch: nil. Wenn wir im PSDK auf NULL stoßen, so ist dies ein Alias für nil in Delphi. In der Beschreibung zu MSDN-Library durchsuchenCreateProcessWithLogonW() steht nun:
auf Englisch ...:
Pointer to an environment block for the new process. If this parameter is NULL, the new process uses the environment of the specified user instead of the environment of the calling process.
... und auf Deutsch heißt das:
Zeiger auf den Umgebungsblock für den neuen Prozeß. Wenn dieser Parameter NULL ist, benutzt der neue Prozeß die Umgebung des angegebenen Benutzers statt der Umgebung des aufrufenden Prozesses
Aha. Also müssen wir nur nil übergeben und die korrekte Umgebung wird erzeugt?! So ist es. Aber es gibt noch ein paar Kleinigkeiten zu beachten. Dazu kommen wir gleich.

Vergleichen wir nun alle 3 beschriebenen Methoden zur Übergabe des EB. Dazu übergeben wir einmal nil, einmal den selbstgebastelten EB und zuguterletzt den kopierten EB. Das gesamte Projekt mit dem das nachvollzogen werden kann, gibt es im Anhang. Hier sollten nur die Ergebnisse besprochen werden (jeweils der gleiche Ausschnitt an Variablen, sofern vorhanden):
  • Fall 1 (nil/Benutzerumgebung): Hier kommt bei mir folgendes heraus:
    Code:
    TEMP=C:\DOKUME~1\ottokar\LOKALE~1\Temp
    TMP=C:\DOKUME~1\ottokar\LOKALE~1\Temp
    USERDOMAIN=HUGO4
    [color=red]USERNAME=ottokar[/color]
    USERPROFILE=C:\Dokumente und Einstellungen\ottokar
    VS71COMNTOOLS=C:\Programme\Microsoft Visual Studio .NET 2003\Common7\Tools\
    windir=C:\WINNT
    lpEnvironment := nil;
  • Fall 2 (selbstgebastelt): Hier bekomme folgendes Ergebnis:
    Code:
    [color=red]USERNAME=SONSTWAS[/color]
    PATH=C:\Blabla\bla\blubb
    Delphi-Quellcode:
    env:= 'USERNAME=SONSTWAS' + #0 + 'PATH=C:\Blabla\bla\blubb' + #0 + #0;
    lpEnvironment := @env[1];
  • Fall 3 (kopiert): Et voila ... zuguterletzt:
    Code:
    TEMP=C:\Temp
    TMP=C:\Temp
    USERDOMAIN=HUGO4
    [color=red]USERNAME=Administrator[/color]
    USERPROFILE=C:\Dokumente und Einstellungen\Administrator
    VS71COMNTOOLS=C:\Programme\Microsoft Visual Studio .NET 2003\Common7\Tools\
    windir=C:\WINNT
    lpEnvironment := GetCurrentEnvironmentBlock();

Aufpassen ...

Sieh mal einer an. Im Fall #1 wird also tatsächlich eine neue Umgebung erzeugt (ja, mein Testnutzerkonto heißt "ottokar"). In Fall #2 wird wiederum nur das wiedergegeben, was wir übergeben hatten. Im letzten Fall (#3) wird statt des angepaßten EB (siehe #1) tatsächlich eine exakte Kopie des aktuellen EB übergeben.

Das Schmankerl zum Schluß ...

Ganz zum Schluß möchte ich euch noch zwei Hilfsfunktionen (sind ebenfalls im Anhang enthalten) mit auf den Weg geben, die benutzt werden können um einen EB auszulesen:
Delphi-Quellcode:
(******************************************************************************
Unicode version to dump the environment created using CreateEnvironmentBlock()
******************************************************************************)


procedure DumpEnvironmentW(lpEnvironment: LPVOID);
var
  Env: PWideChar;
begin
  Env := lpEnvironment;
  Writeln('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
  while (lstrlenW(Env) > 0) do
  begin
    Writeln(string(WideString(Env)));
    Env := PWideChar(DWORD(Env) + DWORD(lstrlenW(Env) + 1) * DWORD(sizeof(Env^)));
  end;
  Writeln('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
end;

(******************************************************************************
ANSI version to dump the environment created using CreateEnvironmentBlock()
******************************************************************************)


procedure DumpEnvironmentA(lpEnvironment: LPVOID);
var
  Env: PAnsiChar;
begin
  Env := lpEnvironment;
  Writeln('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
  while (lstrlenA(Env) > 0) do
  begin
    Writeln(string(Env));
    Env := PAnsiChar(DWORD(Env) + DWORD(lstrlenA(Env) + 1) * DWORD(sizeof(Env^)));
  end;
  Writeln('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~');
end;
EDIT: ringlis Einwand von hier eingearbeitet. Ich hatte fälschlicherweise die Beschreibung einer anderen Funktion im PSDK aufgerufen. Die Kommentare im Anhang wurden entsprechend korrigiert.
Angehängte Dateien
Dateityp: zip runasdlp_179.zip (2,2 KB, 200x aufgerufen)
 
18. Aug 2005, 19:53
Dieses Thema wurde von "Chakotay1308" von "Neuen Beitrag zur Code-Library hinzufügen" nach "Tutorials und Kurse" verschoben.
Das ist eindeutig ein Tutorial.
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 07:03 Uhr.
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