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.