mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-07 01:15:33 +00:00
456 lines
14 KiB
ObjectPascal
456 lines
14 KiB
ObjectPascal
(*
|
|
Name: utilexecute
|
|
Copyright: Copyright (C) SIL International.
|
|
Documentation:
|
|
Description:
|
|
Create Date: 1 Jan 2013
|
|
|
|
Modified Date: 23 Feb 2016
|
|
Authors: mcdurdin
|
|
Related Files:
|
|
Dependencies:
|
|
|
|
Bugs:
|
|
Todo:
|
|
Notes:
|
|
History: 01 Jan 2013 - mcdurdin - I3631 - V9.0 - if svn commit fails, then cvscommit app needs to be aware of this.
|
|
16 Apr 2014 - mcdurdin - I4170 - V9.0 - Console execute in utilexecute.pas needs a temp copy of buffer to avoid write access violations
|
|
23 Feb 2016 - mcdurdin - I4983 - Starting a subprocess can fail due to constant buffer for CreateProcessW
|
|
*)
|
|
unit utilexecute;
|
|
|
|
interface
|
|
|
|
uses
|
|
WinApi.ShellApi,
|
|
WinApi.Windows;
|
|
|
|
type
|
|
TUtilExecuteCallbackEvent = procedure(var Cancelled: Boolean) of object;
|
|
TUtilExecuteCallbackWaitEvent = procedure(hProcess: THandle; var Waiting, Cancelled: Boolean) of object;
|
|
|
|
TUtilExecute = class sealed
|
|
class function Console(const cmdline, curdir: string; var FLogText: string; var ExitCode: Integer; FOnCallback: TUtilExecuteCallbackEvent = nil): Boolean; overload; static; // I3631
|
|
class function Console(const cmdline, curdir: string; var FLogText: string; FOnCallback: TUtilExecuteCallbackEvent = nil): Boolean; overload; static; // I3631
|
|
class function WaitForProcess(const cmdline, curdir: string; ShowWindow: Integer = SW_SHOWNORMAL; FOnCallback: TUtilExecuteCallbackWaitEvent = nil): Boolean; overload; static;
|
|
class function WaitForProcess(const cmdline, curdir: string; var EC: Cardinal; ShowWindow: Integer = SW_SHOWNORMAL; FOnCallback: TUtilExecuteCallbackWaitEvent = nil): Boolean; overload; static;
|
|
class function Shell(Handle: HWND; const process, curdir: string; const parameters: string = ''; ShowWindow: Integer = SW_SHOWNORMAL; const Verb: string = 'open'): Boolean; static;
|
|
class function ShellCurrentUser(Handle: HWND; const process, curdir: string; const parameters: string = ''; ShowWindow: Integer = SW_SHOWNORMAL; const Verb: string = 'open'): Boolean; static;
|
|
class function URL(const url: string): Boolean;
|
|
|
|
class function CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean): Boolean; overload;
|
|
class function CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean; var AExitCode: Cardinal): Boolean; overload;
|
|
|
|
class function Execute(const cmdline, curdir: string; ShowWindow: Integer): Boolean; overload; static;
|
|
class function Execute(const cmdline, curdir: string; ShowWindow: Integer; var pi: TProcessInformation): Boolean; overload; static;
|
|
end;
|
|
|
|
implementation
|
|
|
|
uses
|
|
System.SysUtils,
|
|
|
|
Unicode;
|
|
|
|
class function TUtilExecute.Console(const cmdline, curdir: string; var FLogText: string; FOnCallback: TUtilExecuteCallbackEvent = nil): Boolean;
|
|
var
|
|
ec: Integer;
|
|
begin
|
|
Result := TUtilExecute.Console(cmdline, curdir, FLogText, ec, FOnCallback); // I3631
|
|
end;
|
|
|
|
class function TUtilExecute.Console(const cmdline, curdir: string; var FLogText: string; var ExitCode: Integer; FOnCallback: TUtilExecuteCallbackEvent = nil): Boolean; // I3631
|
|
var
|
|
si: TStartupInfo;
|
|
b, ec: DWord;
|
|
cmdlinebuf: string;
|
|
buf: array[0..512] of ansichar; // I3310
|
|
SecAttrs: TSecurityAttributes;
|
|
hsoutread, hsoutwrite: THandle;
|
|
hsinread, hsinwrite: THandle;
|
|
pi: TProcessInformation;
|
|
n: Integer;
|
|
FCancelled: Boolean;
|
|
begin
|
|
Result := False;
|
|
FillChar(SecAttrs, SizeOf(SecAttrs), #0);
|
|
SecAttrs.nLength := SizeOf(SecAttrs);
|
|
SecAttrs.lpSecurityDescriptor := nil;
|
|
SecAttrs.bInheritHandle := TRUE;
|
|
if not CreatePipe(hsoutread, hsoutwrite, @SecAttrs, 0) then Exit;
|
|
|
|
FillChar(SecAttrs, SizeOf(SecAttrs), #0);
|
|
SecAttrs.nLength := SizeOf(SecAttrs);
|
|
SecAttrs.lpSecurityDescriptor := nil;
|
|
SecAttrs.bInheritHandle := TRUE;
|
|
if not CreatePipe(hsinread, hsinwrite, @SecAttrs, 0) then
|
|
begin
|
|
CloseHandle(hsoutread);
|
|
CloseHandle(hsoutwrite);
|
|
Exit;
|
|
end;
|
|
|
|
{ See support.microsoft.com kb 190351 }
|
|
|
|
n := 0;
|
|
FLogText := '';
|
|
|
|
SetLength(cmdlinebuf, Length(cmdline)); // I4170
|
|
StrCopy(PChar(cmdlinebuf), PChar(cmdline)); // I4170
|
|
|
|
//Sreen.Cursor := crHourglass;
|
|
try
|
|
si.cb := SizeOf(TStartupInfo);
|
|
si.lpReserved := nil;
|
|
si.lpDesktop := nil;
|
|
si.lpTitle := nil;
|
|
si.dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
|
|
si.wShowWindow := SW_HIDE;
|
|
si.cbReserved2 := 0;
|
|
si.lpReserved2 := nil;
|
|
si.hStdInput := hsinread;
|
|
si.hStdOutput := hsoutwrite;
|
|
si.hStdError := hsoutwrite;
|
|
|
|
if CreateProcess(nil, PChar(cmdlinebuf), // I4170
|
|
nil, nil, True, NORMAL_PRIORITY_CLASS, nil, PChar(curdir), si, pi) then
|
|
try
|
|
if GetExitCodeProcess(pi.hProcess, ec) then
|
|
begin
|
|
while ec = STILL_ACTIVE do
|
|
begin
|
|
Sleep(20);
|
|
Inc(n);
|
|
if n = 30 then
|
|
begin
|
|
if Assigned(FOnCallback) then
|
|
begin
|
|
FCancelled := False;
|
|
FOnCallback(FCancelled);
|
|
if FCancelled then
|
|
begin
|
|
SetLastError(ERROR_CANCELLED);
|
|
Exit;
|
|
end;
|
|
end;
|
|
n := 0;
|
|
end;
|
|
PeekNamedPipe(hsoutread, nil, 0, nil, @b, nil);
|
|
if b > 0 then
|
|
begin
|
|
ReadFile(hsoutread, buf, High(buf), b, nil);
|
|
FLogText := FLogText + String_AtoU(Copy(buf, 1, b));
|
|
end;
|
|
if not GetExitCodeProcess(pi.hProcess, ec) then ec := 0;
|
|
end;
|
|
end;
|
|
|
|
repeat
|
|
PeekNamedPipe(hsoutread, nil, 0, nil, @b, nil);
|
|
if b > 0 then
|
|
begin
|
|
ReadFile(hsoutread, buf, High(buf), b, nil);
|
|
FLogText := FLogText + String_AtoU(Copy(buf, 1, b));
|
|
end;
|
|
until b = 0;
|
|
|
|
ExitCode := ec; // I3631
|
|
|
|
Result := True;
|
|
finally
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
end;
|
|
finally
|
|
CloseHandle(hsoutread);
|
|
CloseHandle(hsoutwrite);
|
|
CloseHandle(hsinread);
|
|
CloseHandle(hsinwrite);
|
|
//Screen.Cursor := crDefault;
|
|
end;
|
|
end;
|
|
|
|
class function TUtilExecute.Shell(Handle: HWND; const process, curdir, parameters: string;
|
|
ShowWindow: Integer; const Verb: string): Boolean;
|
|
begin
|
|
Result := ShellExecute(Handle, PChar(Verb), Pchar(process), PChar(parameters), Pchar(curdir), ShowWindow) > 32;
|
|
end;
|
|
|
|
class function TUtilExecute.ShellCurrentUser(Handle: HWND; const process,
|
|
curdir, parameters: string; ShowWindow: Integer; const Verb: string): Boolean;
|
|
begin
|
|
// Use mysterious ways to get the shell process to launch. There are at least
|
|
// three different ways to do this. The way we are using has been used by the
|
|
// bootstrap for years so pretty sure it's robust!
|
|
// Alternative: https://devblogs.microsoft.com/oldnewthing/20131118-00/?p=2643
|
|
// Alternative: https://devblogs.microsoft.com/oldnewthing/20190425-00/?p=102443
|
|
Result := CreateProcessAsShellUser(process, '"'+process+'" '+parameters, False);
|
|
end;
|
|
|
|
class function TUtilExecute.URL(const url: string): Boolean;
|
|
begin
|
|
Result := ShellExecute(GetActiveWindow, nil, PChar(url), nil, nil, SW_SHOW) >= 32;
|
|
end;
|
|
|
|
class function TUtilExecute.WaitForProcess(const cmdline, curdir: string; ShowWindow: Integer; FOnCallback: TUtilExecuteCallbackWaitEvent): Boolean;
|
|
var
|
|
ec: Cardinal;
|
|
begin
|
|
Result := WaitForProcess(cmdline, curdir, ec, ShowWindow, FOnCallback);
|
|
end;
|
|
|
|
class function TUtilExecute.WaitForProcess(const cmdline, curdir: string; var EC: Cardinal; ShowWindow: Integer; FOnCallback: TUtilExecuteCallbackWaitEvent): Boolean;
|
|
var
|
|
Waiting: Boolean;
|
|
si: TStartupInfoW;
|
|
pi: TProcessInformation;
|
|
Cancelled: Boolean;
|
|
buf: PChar;
|
|
begin
|
|
Result := False;
|
|
|
|
Waiting := True;
|
|
si.cb := SizeOf(TStartupInfo);
|
|
si.lpReserved := nil;
|
|
si.lpDesktop := nil;
|
|
si.lpTitle := nil;
|
|
si.dwFlags := STARTF_USESHOWWINDOW;
|
|
si.wShowWindow := ShowWindow;
|
|
si.cbReserved2 := 0;
|
|
si.lpReserved2 := nil;
|
|
|
|
EC := $FFFFFFFF;
|
|
|
|
buf := AllocMem((Length(cmdline)+1)*sizeof(Char)); // I4983
|
|
try
|
|
StrPCopy(buf, cmdline); // I4983
|
|
if CreateProcess(nil, buf, // I4983
|
|
nil, nil, True, NORMAL_PRIORITY_CLASS, nil, PWideChar(curdir), si, pi) then
|
|
try
|
|
while Waiting do
|
|
begin
|
|
if Assigned(FOnCallback) then
|
|
begin
|
|
Cancelled := False;
|
|
FOnCallback(pi.hProcess, Waiting, Cancelled);
|
|
if Cancelled then
|
|
begin
|
|
SetLastError(ERROR_CANCELLED);
|
|
Exit;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
WaitForSingleObject(pi.hProcess, INFINITE);
|
|
Waiting := False;
|
|
end;
|
|
end;
|
|
|
|
if not GetExitCodeProcess(pi.hProcess, EC) then
|
|
EC := $FFFFFFFF;
|
|
|
|
Result := True;
|
|
finally
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
end;
|
|
finally
|
|
FreeMem(buf); // I4983
|
|
end;
|
|
end;
|
|
|
|
class function TUtilExecute.Execute(const cmdline, curdir: string; ShowWindow: Integer): Boolean;
|
|
var
|
|
pi: TProcessInformation;
|
|
begin
|
|
Result := Execute(cmdline, curdir, ShowWindow, pi);
|
|
if Result then
|
|
begin
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
end;
|
|
end;
|
|
|
|
class function TUtilExecute.Execute(const cmdline, curdir: string; ShowWindow: Integer; var pi: TProcessInformation): Boolean;
|
|
var
|
|
si: TStartupInfoW;
|
|
buf: PChar;
|
|
begin
|
|
Result := False;
|
|
|
|
si.cb := SizeOf(TStartupInfo);
|
|
si.lpReserved := nil;
|
|
si.lpDesktop := nil;
|
|
si.lpTitle := nil;
|
|
si.dwFlags := STARTF_USESHOWWINDOW;
|
|
si.wShowWindow := ShowWindow;
|
|
si.cbReserved2 := 0;
|
|
si.lpReserved2 := nil;
|
|
|
|
buf := AllocMem((Length(cmdline)+1)*sizeof(Char));
|
|
try
|
|
StrPCopy(buf, cmdline);
|
|
if CreateProcess(nil, buf,
|
|
nil, nil, True, NORMAL_PRIORITY_CLASS, nil, PWideChar(curdir),
|
|
si, pi) then
|
|
begin
|
|
Result := True;
|
|
end;
|
|
finally
|
|
FreeMem(buf);
|
|
end;
|
|
end;
|
|
|
|
// Refactored from UCreateProcessAsShellUser
|
|
|
|
function GetShellWindow: HWND; stdcall; external user32;
|
|
|
|
type
|
|
TCreateProcessWithTokenW = function(hToken: THANDLE; dwLogonFlags: DWORD; lpApplicationName: LPCWSTR; lpCommandLine: LPWSTR;
|
|
dwCreationFlags: DWORD; lpEnvironment: Pointer; lpCurrentDirectory: LPCWSTR; lpStartupInfo: PSTARTUPINFOW;
|
|
lpProcessInformation: PPROCESSINFORMATION): BOOL; stdcall;
|
|
|
|
var
|
|
CreateProcessWithTokenW: TCreateProcessWithTokenW = nil;
|
|
|
|
class function TUtilExecute.CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean): Boolean; // I2757
|
|
var
|
|
ec: Cardinal;
|
|
begin
|
|
Result := CreateProcessAsShellUser(process, cmdline, Wait, ec);
|
|
end;
|
|
|
|
class function TUtilExecute.CreateProcessAsShellUser(const process, cmdline: WideString; Wait: Boolean; var AExitCode: Cardinal): Boolean; // I2757
|
|
var
|
|
dwProcessId: Cardinal;
|
|
hProcess, hShellProcessToken: THandle;
|
|
hDesktopWindow: THandle;
|
|
si: TStartupInfoW;
|
|
pi: TProcessInformation;
|
|
hPrimaryToken: NativeUInt; // I3309
|
|
hCurrentThreadToken: THandle;
|
|
tkp: TTokenPrivileges;
|
|
retlen: Cardinal;
|
|
|
|
procedure DoWait;
|
|
var
|
|
Msg: TMsg;
|
|
Waiting: Boolean;
|
|
begin
|
|
Waiting := True;
|
|
while Waiting do
|
|
begin
|
|
case MsgWaitForMultipleObjects(1, pi.hProcess, FALSE, INFINITE, QS_ALLINPUT) of
|
|
WAIT_OBJECT_0 + 1:
|
|
while PeekMessageW(Msg, 0, 0, 0, PM_REMOVE) do
|
|
begin
|
|
TranslateMessage(Msg);
|
|
DispatchMessage(Msg);
|
|
end;
|
|
WAIT_OBJECT_0, WAIT_ABANDONED_0:
|
|
Waiting := False;
|
|
else
|
|
Waiting := False;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function IsElevated: Boolean;
|
|
var
|
|
hToken: THandle;
|
|
Elevation: TTokenElevation;
|
|
cbSize: DWORD;
|
|
begin
|
|
if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hToken) then
|
|
try
|
|
cbSize := sizeof(TTokenElevation);
|
|
if GetTokenInformation(hToken, TokenElevation, @Elevation, sizeof(TTokenElevation), cbSize) then
|
|
Exit(Elevation.TokenIsElevated <> 0);
|
|
finally
|
|
CloseHandle(hToken);
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
const
|
|
SE_INCREASE_QUOTA_NAME = 'SeIncreaseQuotaPrivilege';
|
|
TOKEN_ADJUST_SESSIONID = $100;
|
|
begin
|
|
Result := False;
|
|
|
|
FillChar(si, SizeOf(si), 0);
|
|
si.cb := SizeOf(si);
|
|
|
|
FillChar(pi, SizeOf(pi), 0);
|
|
|
|
if not IsElevated then
|
|
begin
|
|
if not CreateProcessW(PWideChar(process), PWideChar(cmdline), nil, nil, False, NORMAL_PRIORITY_CLASS, nil, nil, si, pi) then Exit;
|
|
end
|
|
else
|
|
begin
|
|
if not Assigned(CreateProcessWithTokenW) then Exit;
|
|
|
|
hDesktopWindow := GetShellWindow;
|
|
if hDesktopWindow = 0 then Exit;
|
|
if GetWindowThreadProcessId(hDesktopWindow, dwProcessId) = 0 then Exit;
|
|
|
|
if not ImpersonateSelf(SecurityImpersonation) then Exit;
|
|
try
|
|
if not LookupPrivilegeValue(nil, SE_INCREASE_QUOTA_NAME, tkp.Privileges[0].Luid) then Exit;
|
|
|
|
tkp.PrivilegeCount := 1; // one privilege to set
|
|
tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
|
|
|
|
if not OpenThreadToken(GetCurrentThread, TOKEN_ADJUST_PRIVILEGES, False, hCurrentThreadToken) then Exit;
|
|
try
|
|
if not AdjustTokenPrivileges(hCurrentThreadToken, False, tkp, 0, nil, retlen) then Exit;
|
|
|
|
hProcess := OpenProcess(PROCESS_QUERY_INFORMATION, False, dwProcessId);
|
|
if hProcess = 0 then Exit;
|
|
try
|
|
if not OpenProcessToken(hProcess, TOKEN_DUPLICATE, hShellProcessToken) then Exit;
|
|
try
|
|
if not DuplicateTokenEx(hShellProcessToken, TOKEN_QUERY or TOKEN_ASSIGN_PRIMARY or TOKEN_DUPLICATE or TOKEN_ADJUST_DEFAULT or TOKEN_ADJUST_SESSIONID,
|
|
nil, SecurityImpersonation, TokenPrimary, hPrimaryToken) then Exit;
|
|
try
|
|
if not CreateProcessWithTokenW(hPrimaryToken, 0, PWideChar(process), PWideChar(cmdline), NORMAL_PRIORITY_CLASS, nil, nil, @si, @pi) then Exit;
|
|
finally
|
|
CloseHandle(hPrimaryToken);
|
|
end;
|
|
finally
|
|
CloseHandle(hShellProcessToken);
|
|
end;
|
|
finally
|
|
CloseHandle(hProcess);
|
|
end;
|
|
finally
|
|
CloseHandle(hCurrentThreadToken);
|
|
end;
|
|
finally
|
|
RevertToSelf;
|
|
end;
|
|
end;
|
|
|
|
Result := True;
|
|
|
|
if Wait then // I2757 // I4318
|
|
begin
|
|
DoWait;
|
|
GetExitCodeProcess(pi.hProcess, AExitCode);
|
|
end;
|
|
|
|
CloseHandle(pi.hProcess);
|
|
CloseHandle(pi.hThread);
|
|
end;
|
|
|
|
var
|
|
hAdvApi32: THandle = 0;
|
|
initialization
|
|
hAdvApi32 := LoadLibrary(advapi32);
|
|
if hAdvApi32 <> 0 then
|
|
CreateProcessWithTokenW := GetProcAddress(hAdvApi32, 'CreateProcessWithTokenW');
|
|
finalization
|
|
if hAdvApi32 <> 0 then
|
|
FreeLibrary(hAdvApi32);
|
|
end.
|