![]() |
User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurde
Mein kleines Installationsprogram benötigt Administratoren-Rechte, welche ich via Manifest einfordere.
Unter Windows 7 /Vista erfolgt damit bereits vor Program-Start ein User-Wechsel auf einen User mit Amdinistrator-Rechte, falls der eingeloggte User keine entsprechenden Rechte besitzt. Beim Installieren hole ich mit SHGetFolderPath verschiedene User-spezifische Folders. Wenn im dritte Parameter Token 0 übergeben wird, erhalten ich in dieser Situation natürlich den Pfad des Administrator-Users und nicht jenen des ursprüglich eingeloggten Users. Jetzt versuchte ich mit dem dritten Parameter das Token des geforderten Users mitzugeben, doch kriege ich immer "The handle is Invalid" zurück. Mit folgender Funktion erhalte ich das Token vom Parent-Prozess (welcher in der Regel auf den eingeloggten User verweist):
Code:
Beim Aufruf erhalte ich dann immer ein HResult für "The handle is Invalid" zurück.
function GetTokenOfProcess(ProcessId: DWORD): THandle;
const PROCESS_QUERY_LIMITED_INFORMATION = $1000; var ProcessHandle, TokenHandle: THandle; begin Result := 0; ProcessHandle := 0; try ProcessHandle:= OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, True, ProcessId); if ProcessHandle <> 0 then begin if OpenProcessToken(ProcessHandle, TOKEN_QUERY or TOKEN_IMPERSONATE, TokenHandle) then result := TokenHandle; // call CloseHandle(TokenHandle) at the end end; finally CloseHandle(ProcessHandle); end; end;
Code:
hRes := SHGetFolderPath(Handle, CSIDL_PROGRAMS, GetTokenOfProcess(ParentProcessID), SHGFP_TYPE_DEFAULT, Folder)
Hat jemand eine Idee was hier falsch läuft. Oder gibt es einen einfacheren Weg für dieses Problem? |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Warum so umständlich? Wenn dein Programm noch Dateien benötigt, die benutzersüpezifisch sind, dann lass sie vom Programm beim ersten Start anlegen.
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Gute Idee, nur in meinem Fall geht dies leider nicht. Ich brauche bereits zu Installationszeitpunkt diese Verzeichnisse.
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Warum das? Zum Installationszeitpunkt, ist dein eigentliches Programm doch noch gar nicht aktiv.
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Eventuell wird hier ein Token benötigt, womit man mehr machen kann, als nur TOKEN_QUERY?
Siehe Schließanlagen: Nur weil ich einen Schlüssel hab, der zu einer Tür paßt, muß er nicht gleich auch noch zu einer gewissen anderen Tür passen. Und sicher daß der ParentProcess überhaupt im gewünschten Usercontext läuft? Besser wäre es wohl, wenn du hier irgendwie den User aussliest, in dessen Session/Desktop das Programm grade läuft/angezeigt wird. |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Den User aus der Session zu bestimmen ist sicherlich der bessere Ansatz als den Parent-Prozess zu nehmen. Besten Dank himitsu für diesen Tip!
Am zweiten Paramter DesiredAcess von OpenProcessToken liegt es nicht, denn ich habe testweise mal TOKEN_ALL_ACCESS eingesetzt und dasselbe Ergebnis erhalten. Um beispielsweise ein Menu-Eintrag ins Startmenu zu schreiben ist der Zeitpunkt der Installation sicher der einzig richtige. Für Konfigurationsdateien wäre der erste Programm-Start sicher auch geeignet, wie Luckie schreibt. Und dann fragt sich sicherlich auch, warum jemand ein Installer selber schreiben muss. Dazu braucht es gute Gründe doch die sind bei dieser Applikation geben. |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Zitat:
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Zitat:
Ich fände es schön, wenn setups fragen würden (was einige Wenige sogar schon machen) wo es installiert werden soll. - für aktuellen benutzer - für alle Benutzer |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Dann muss er aber nicht umständlich mit Tokens und Sessions rumhantieren. Den Default-User gibt es nicht, das ist nur ein Ordner unter den Benutzerprofilen, der für alle gilt. Da kann er dann einfach seinen Startmenüeintrag machen. Was ich aber auch nicht leiden kann, weil eine Verknüpfung die da liegt, kann der einzelne Benutzer nicht löschen oder verschieben, wenn er sie nicht haben will oder an einer anderen Stelle.
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Genau, mein Installationsprogram frägt den Anwender, ob für alle oder nur für den laufenden User installiert werden soll. Im ersten Falle wird dann als CSIDL bspw. für den Startordner CSIDL_COMMON_PROGRAMS verwendet während für den aktuellen User CSIDL_PROGRAMS abgefragt wird. Dieser Ansatz funktionierte auch bis und mit XP problemlos. Seit aber immer mehr User ohne Admin-Rechte laufen und gezielt für die Installation ein User-Wechsel vorgenommen wird, läuft dies wie erwähnt nicht mehr.
Die Verwendung des Default-Users ist mea. auch nicht geeignet, den dieser wird als Template-User verwendet, wenn in Zukunkt ein neuer User eingerichtet wird. |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Stimmt, DefaultUser war eh falsch ... gab es nicht früher mal einen User AllUsers?
In Win2008 isses C:\ProgramData\Microsoft\Windows\Start Menu\Programs . Tja, dann wirst du es wohl doch z.B. mal über den Besitzer der Session probieren müssen. :gruebel: |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Unterdessen habe ich meinen Fehler entdeckt. Es lag an einem Seiteneffekt und gar nicht am gezeigten Code - der funktionierte bereits richtig.
Ich habe für Test-Zwecke das Token noch gebraucht um Domain und UserName darzustellen. Dafür hätte ich aber das Token vorher noch duplizieren müssen. Hier zeige ich gerne die jetzt implementierte Lösung:
Code:
function CreateTokenOfProcess(ProcessId: DWORD): THandle;
const PROCESS_QUERY_LIMITED_INFORMATION = $1000; var ProcessHandle, TokenHandle: THandle; begin result := 0; ProcessHandle := 0; try ProcessHandle:= OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, True, ProcessId); if ProcessHandle <> 0 then begin if OpenProcessToken(ProcessHandle, TOKEN_QUERY or TOKEN_IMPERSONATE, TokenHandle) then result := TokenHandle; // Call CloseHandle(TokenHandle) by caller end; finally CloseHandle(ProcessHandle); end; end; function GetParentProcessID: DWORD; // This function should be part of the API! var Snap: THandle; Proc: TProcessEntry32; CurrentProc: cardinal; begin result := 0; Snap := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0); try CurrentProc := GetCurrentProcessId; Proc.dwSize := SizeOf(TProcessEntry32); Process32First(Snap, Proc); repeat if Proc.th32ProcessID = CurrentProc then begin result := Proc.th32ParentProcessID; Break; end; until (not Process32Next(Snap, Proc)); finally CloseHandle(Snap); end; end; procedure TForm1.Button1Click(Sender: TObject); var hres: HResult; Folder: array[0..Max_Path] of Char; Token: THandle; begin Token := CreateTokenOfProcess(GetParentProcessID); try hRes := SHGetFolderPath(Handle, CSIDL_PROGRAMS, Token, SHGFP_TYPE_DEFAULT, Folder); if not Succeeded(hRes) then raise Exception.Create(SysErrorMessage(hRes))); ShowMessage('SHGetFolderPath Succeeded:' + string(Folder)); finally CloseHandle(Token); end; end; Den Ansatz über die Session ID bringe ich leider nicht zum Laufen. Hier kurz der Abriss:
Code:
Ich versuchte noch das Privileg SE_TCB_NAME zu setzen, scheiterte aber auch damit.
function WTSQueryUserToken(SessionId: DWORD; phToken: pHandle):bool; stdcall; external 'wtsapi32.dll';
function ProcessIdToSessionId(dwProcessId: DWORD; pSessionId: DWORD): bool; stdcall; external 'Kernel32.dll'; function CreateTokenFromSession: THandle; var SessionID: cardinal; begin if not ProcessIdToSessionId(GetCurrentProcessId, DWORD(@SessionID)) then raise Exception.Create(SysErrorMessage(GetLastError))); { Korrekte Session ID erhalten } if not WTSQueryUserToken(SessionId, @result) then raise Exception.Create(SysErrorMessage(GetLastError))); { Hier bekomme ich immer "A required privilege is not held by the client" } end; Die Lösung mit dem Parent-Prozess sollten im normalen Umfeld eigentlich gut funktieren. Probleme entstehen, wenn der Installer aus einem Batch oder einer anderen Applikation gestartet wird, die bereits den User-Switch vorgenommen haben. Da mein Installer über kein Command-Line Interface verfügt, mache ich mir hierzu aber keine weiteren Sorgen. Im Gegensatz zu Unix fehlt unter Windows ein richtiger Root-Process, der die User-Session gestartet hat. Der Explorer-Prozess übernimmt zwar mehrheitlich diese Aufgabe, kann aber jederzeit beendet und neugestartet werden. Ergo ist es schwierig über den Prozessbaum den gültigen Root-Prozess zu finden. Aus diesem Grunde versuche ich mal mit diesem Workaround zu leben. |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
OK, das ist jetzt für Alice die den Installationsprozess angestoßen hat. Und was ist mit Benutzer Bob, der sich am nächsten Tag anmeldet und die Software nutzen will?
|
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Die Anwendung der Funktion "Installation nur für den aktuellen Benutzer" ist vorallem dann von Nutzen, wenn der PC für verschiedene
Aufgaben genutzt wird. Klarerweise muss beim Installieren immer übelegt werden, ob dieses Program nur von mir oder auch von anderen Benutzer eingesetzt wird. Für eine Office-Anwendung mag diese Funktion vielleicht keinen Sinn machen. Für eine Lohnbuchhaltung vielleicht aber schon. Wie bereits erwähnt, bietet mein Installer auch die Option "Installation für alle Benutzer" an. Eine Checkbox-Liste mit den vorhandenen Benutzern und zusätzlich dem Default-Benutzer (für künftige erstellte Benutzer) anzuzeigen, wäre aus technischer Sicht die perfekte Lösung. Nur ob diese Lösung dann auch vom Anwender noch verstanden wird, wage ich zu beweifeln. |
AW: User-Spezifische Folder ermitteln wenn mit "Run als Administrator" gestartet wurd
Stimmt, die meisten Benutzer nehmen eh nur das was voreingestellt ist und denken darüber nicht nach. Nur für Firmenadmins ist soetwas lebensnotwendig.
Ich würde hier eher den Aufwand schätzen und dann mit dem Nutzen abwiegen. Lohnt es sich? |
Alle Zeitangaben in WEZ +1. Es ist jetzt 13:48 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz