问题
I have an installer-like application that I have to run as elevated on Vista. But from there I have to start a new process as non-elevated. Any hints how to do this with Delphi2007?
回答1:
I found an excellent example for C++ and adapted it for Delphi:
unit MediumIL;
interface
uses
Winapi.Windows;
function CreateProcessMediumIL(lpApplicationName: PWChar; lpCommandLine: PWChar; lpProcessAttributes: PSecurityAttributes; lpThreadAttributes: PSecurityAttributes; bInheritHandle: BOOL; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: PWChar; const lpStartupInfo: TStartupInfoW; var lpProcessInformation: TProcessInformation): DWORD;
implementation
type
TOKEN_MANDATORY_LABEL = record
Label_: SID_AND_ATTRIBUTES;
end;
PTOKEN_MANDATORY_LABEL = ^TOKEN_MANDATORY_LABEL;
TTokenMandatoryLabel = TOKEN_MANDATORY_LABEL;
PTokenMandatoryLabel = ^TTokenMandatoryLabel;
TCreateProcessWithTokenW = function (hToken: THandle; dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: LPCWSTR; const lpStartupInfo: TStartupInfoW; out lpProcessInfo: TProcessInformation): BOOL; stdcall;
const
SECURITY_MANDATORY_UNTRUSTED_RID = $00000000;
SECURITY_MANDATORY_LOW_RID = $00001000;
SECURITY_MANDATORY_MEDIUM_RID = $00002000;
SECURITY_MANDATORY_HIGH_RID = $00003000;
SECURITY_MANDATORY_SYSTEM_RID = $00004000;
SECURITY_MANDATORY_PROTECTED_PROCESS_RID = $00005000;
function GetShellWindow: HWND; stdcall; external 'user32.dll' name 'GetShellWindow';
// writes Integration Level of the process with the given ID into dwProcessIL
// returns Win32 API error or 0 if succeeded
function GetProcessIL(dwProcessID: DWORD; var dwProcessIL: DWORD): DWORD;
label
_CleanUp;
var
hProcess: THandle;
hToken: THandle;
dwSize: DWORD;
pbCount: PByte;
pdwProcIL: PDWORD;
pTIL: PTokenMandatoryLabel;
dwError: DWORD;
begin
dwProcessIL := 0;
pTIL := nil;
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, dwProcessID);
if (hProcess = 0) then
goto _CleanUp;
if (not OpenProcessToken(hProcess, TOKEN_QUERY, hToken)) then
goto _CleanUp;
if (not GetTokenInformation(hToken, TokenIntegrityLevel, nil, 0, dwSize) and (GetLastError() <> ERROR_INSUFFICIENT_BUFFER)) then
goto _CleanUp;
pTIL := HeapAlloc(GetProcessHeap(), 0, dwSize);
if (pTIL = nil) then
goto _CleanUp;
if (not GetTokenInformation(hToken, TokenIntegrityLevel, pTIL, dwSize, dwSize)) then
goto _CleanUp;
pbCount := PByte(GetSidSubAuthorityCount(pTIL^.Label_.Sid));
if (pbCount = nil) then
goto _CleanUp;
pdwProcIL := GetSidSubAuthority(pTIL^.Label_.Sid, pbCount^ - 1);
if (pdwProcIL = nil) then
goto _CleanUp;
dwProcessIL := pdwProcIL^;
SetLastError(ERROR_SUCCESS);
_CleanUp:
dwError := GetLastError();
if (pTIL <> nil) then
HeapFree(GetProcessHeap(), 0, pTIL);
if (hToken <> 0) then
CloseHandle(hToken);
if (hProcess <> 0) then
CloseHandle(hProcess);
Result := dwError;
end;
// Creates a new process lpApplicationName with the integration level of the Explorer process (MEDIUM IL)
// If you need this function in a service you must replace FindWindow() with another API to find Explorer process
// The parent process of the new process will be svchost.exe if this EXE was run "As Administrator"
// returns Win32 API error or 0 if succeeded
function CreateProcessMediumIL(lpApplicationName: PWChar; lpCommandLine: PWChar; lpProcessAttributes: PSecurityAttributes; lpThreadAttributes: PSecurityAttributes; bInheritHandle: BOOL; dwCreationFlags: DWORD; lpEnvironment: LPVOID; lpCurrentDirectory: PWChar; const lpStartupInfo: TStartupInfoW; var lpProcessInformation: TProcessInformation): DWORD;
label
_CleanUp;
var
hProcess: THandle;
hToken: THandle;
hToken2: THandle;
bUseToken: BOOL;
dwCurIL: DWORD;
dwErr: DWORD;
f_CreateProcessWithTokenW: TCreateProcessWithTokenW;
hProgman: HWND;
dwExplorerPID: DWORD;
dwError: DWORD;
begin
bUseToken := False;
// Detect Windows Vista, 2008, Windows 7 and higher
if (GetProcAddress(GetModuleHandleA('Kernel32'), 'GetProductInfo') <> nil) then
begin
dwErr := GetProcessIL(GetCurrentProcessId(), dwCurIL);
if (dwErr <> 0) then
begin
Result := dwErr;
Exit;
end;
if (dwCurIL > SECURITY_MANDATORY_MEDIUM_RID) then
bUseToken := True;
end;
// Create the process normally (before Windows Vista or if current process runs with a medium IL)
if (not bUseToken) then
begin
if (not CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandle, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation)) then
begin
Result := GetLastError();
Exit;
end;
CloseHandle(lpProcessInformation.hThread);
CloseHandle(lpProcessInformation.hProcess);
Result := ERROR_SUCCESS;
Exit;
end;
f_CreateProcessWithTokenW := GetProcAddress(GetModuleHandleA('Advapi32'), 'CreateProcessWithTokenW');
if (not Assigned(f_CreateProcessWithTokenW)) then // This will never happen on Vista!
begin
Result := ERROR_INVALID_FUNCTION;
Exit;
end;
hProgman := GetShellWindow();
dwExplorerPID := 0;
GetWindowThreadProcessId(hProgman, dwExplorerPID);
// ATTENTION:
// If UAC is turned OFF all processes run with SECURITY_MANDATORY_HIGH_RID, also Explorer!
// But this does not matter because to start the new process without UAC no elevation is required.
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, dwExplorerPID);
if (hProcess = 0) then
goto _CleanUp;
if (not OpenProcessToken(hProcess, TOKEN_DUPLICATE, hToken)) then
goto _CleanUp;
if (not DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, hToken2)) then
goto _CleanUp;
if (not f_CreateProcessWithTokenW(hToken2, 0, lpApplicationName, lpCommandLine, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation)) then
goto _CleanUp;
SetLastError(ERROR_SUCCESS);
_CleanUp:
dwError := GetLastError();
if (hToken <> 0) then
CloseHandle(hToken);
if (hToken2 <> 0) then
CloseHandle(hToken2);
if (hProcess <> 0) then
CloseHandle(hProcess);
CloseHandle(lpProcessInformation.hThread);
CloseHandle(lpProcessInformation.hProcess);
Result := dwError;
end;
end.
To use this in your project, simply use unit MediumIL:
uses MediumIL;
…
procedure TForm1.FormCreate(Sender: TObject);
var
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
begin
ZeroMemory(@StartupInfo, SizeOf(StartupInfo));
ZeroMemory(@ProcessInfo, SizeOf(ProcessInfo));
CreateProcessMediumIL('C:\Windows\notepad.exe', nil, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInfo);
end;
回答2:
This blog post is detailed and useful
http://developersoven.blogspot.com/2007/02/leveraging-vistas-uac-with-delphi-part.html
The idea is to use your app with low privilege and COM Dll with elevated privilege. Then when you need elevation, you just fire up COM. Full MPLed source link is included in the post.
回答3:
Note sure if this will help but there is a similar questions here its in c#.net but it may give you some clues where to look, or you could try a port to Delphi.
and just a tip try not to have update/install/setup in the application file name as vista will automatically add the security icon to there exe's.
回答4:
You could use the CreateProcessWithLogonW() API call:
function CreateProcessWithLogonW(lpUsername: PWideChar; lpDomain: PWideChar;
lpPassword: PWideChar; dwLogonFlags: DWORD; lpApplicationName: PWideChar;
lpCommandLine: PWideChar; dwCreationFlags: DWORD; lpEnvironment: Pointer;
lpCurrentDirectory: PWideChar; const lpStartupInfo: TStartupInfo;
var lpProcessInformation: TProcessInformation): BOOL; stdcall;
external 'advapi32.dll' name 'CreateProcessWithLogonW';
procedure RunAs(AUsername, APassword, ADomain, AApplication: string);
const
LOGON_WITH_PROFILE = $00000001;
var
si: TStartupInfo;
pi: TProcessInformation;
begin
ZeroMemory(@si, SizeOf(si));
si.cb := SizeOf(si);
si.dwFlags := STARTF_USESHOWWINDOW;
si.wShowWindow := SW_NORMAL;
ZeroMemory(@pi, SizeOf(pi));
if not CreateProcessWithLogonW(PWideChar(WideString(AUsername)),
PWideChar(WideString(ADomain)), PWideChar(WideString(APassword)),
LOGON_WITH_PROFILE, nil, PWideChar(WideString(AApplication)),
0, nil, nil, si, pi)
then
RaiseLastOSError;
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
end;
回答5:
The following article by Aaron Margosis covers exactly this topic: FAQ: How do I start a program as the desktop user from an elevated app?
The basic idea is to obtain the user token of the shell process, i.e. explorer.exe
, make a primary token from that and finally launch the new process with that token.
The article includes some C++ code which should be easy enough to translate to Delphi. It also includes the following itemised list outlining the approach:
- Enable the SeIncreaseQuotaPrivilege in your current token
- Get an HWND representing the desktop shell (GetShellWindow)
- Get the Process ID (PID) of the process associated with that window (GetWindowThreadProcessId)
- Open that process (OpenProcess)
- Get the access token from that process (OpenProcessToken)
- Make a primary token with that token (DuplicateTokenEx)
- Start the new process with that primary token (CreateProcessWithTokenW)
回答6:
I'd like to add to Elçins answer above:
If the code line:
if (not DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, nil, SecurityImpersonation, TokenPrimary, hToken2)) then
goto _CleanUp;
returns with error 5 (Access Denied)
, then the TOKEN_ALL_ACCESS
needs to be OR
ed with TOKEN_ADJUST_SESSIONID
(0x100).
On Delphi 2010, change LPVOID
to POINTER
.
来源:https://stackoverflow.com/questions/514968/how-to-run-a-process-non-elevated-with-delphi2007