Check specific process is elevated in delphi - windows

I have the following code that can help to check whether the process is running under elevated.
How can I modify the code in order to allow it to check whether a certain process is elevated?
function IsElevated: Boolean;
const
TokenElevation = TTokenInformationClass(20);
type
TOKEN_ELEVATION = record
TokenIsElevated: DWORD;
end;
var
TokenHandle: THandle;
ResultLength: Cardinal;
ATokenElevation: TOKEN_ELEVATION;
HaveToken: Boolean;
begin
if CheckWin32Version(6, 0) then
begin
TokenHandle := 0;
HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle);
if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then
HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle);
if HaveToken then
begin
try
ResultLength := 0;
if GetTokenInformation(TokenHandle, TokenElevation, #ATokenElevation, SizeOf(ATokenElevation), ResultLength) then
Result := ATokenElevation.TokenIsElevated <> 0
else
Result := False;
finally
CloseHandle(TokenHandle);
end;
end
else
Result := False;
end
else
Result := True;
end;

Your answer is almost in your question...
The function IsElevated get the result from GetTokenInformation which takes a TokenHandle. That TokenHandle is given by OpenProcessToken which receives the current process handle.
Now you are interested not by current process but by another for which you have ProcessID. So you get process handle you need by calling OpenProcess with processID. It is likely you need elevated privilege to do that.

Related

Should I raise an exception when getting the size or the position of a file?

I'm trying to make a class in Delphi that handles files. I have a property that returns the size of the file and another one that returns the position of the file. I don't know if any error can happen with these calls. Should I raise an exception?
My code is:
function TFile.GetSize: Int64;
var
FileSizeHi, FileSizeLo: Cardinal;
begin
FileSizeLo := GetFileSize(FHandle, #FileSizeHi);
if (FileSizeLo = INVALID_FILE_SIZE) and (GetLastError = NO_ERROR) then
Result := $FFFFFFFF
else
Result := FileSizeLo or Int64(FileSizeHi) shl 32;
end;
function TFile.GetPosition: Int64;
var
FilePosHi, FilePosLo: Cardinal;
begin
FilePosHi := 0;
FilePosLo := 0;
FilePosLo := SetFilePointer(FHandle, FilePosLo, #FilePosHi, FILE_CURRENT);
if (FilePosLo = INVALID_SET_FILE_POINTER) and (GetLastError = NO_ERROR) then
Result := $FFFFFFFF
else
Result := FilePosLo or Int64(FilePosHi) shl 32;
end;
I don't know what error could happen when I call GetFileSize or SetFilePointer (without moving the file pointer).
Yes, errors can happen with those functions, so I would recommend raising an exception, otherwise the caller doesn't know if it has received an invalid value or not, as $FFFFFFFF is a valid size/position for 64bit values. Perhaps you meant to use -1 ($FFFFFFFFFFFFFFFF) instead?
However, whether you raise an exception or not, your GetLastError() check is wrong. It needs to use <> instead of =. When the file function returns $FFFFFFFF for the low value, GetLastError() will return 0 when the low value really is $FFFFFFFF, otherwise GetLastError() will return non-zero when the low/high values are invalid.
Try this:
function TFile.GetSize: Int64;
var
FileSizeHi, FileSizeLo: DWORD;
begin
FileSizeLo := GetFileSize(FHandle, #FileSizeHi);
if (FileSizeLo = INVALID_FILE_SIZE) and (GetLastError <> NO_ERROR) then
RaiseLastOSError // or: Result := -1
else
Result := FileSizeLo or (Int64(FileSizeHi) shl 32);
end;
function TFile.GetPosition: Int64;
var
FilePosHi, FilePosLo: DWORD;
begin
FilePosHi := 0;
FilePosLo := 0;
FilePosLo := SetFilePointer(FHandle, FilePosLo, #FilePosHi, FILE_CURRENT);
if (FilePosLo = INVALID_SET_FILE_POINTER) and (GetLastError <> NO_ERROR) then
RaiseLastOSError // or: Result := -1
else
Result := FilePosLo or (Int64(FilePosHi) shl 32);
end;
On a side note, consider using GetFileSizeEx() and SetFilePointerEx() instead, as they operate on 64bit values without breaking them up into low/high parts.

How to get amin rights during runtime using delphi xe5 [duplicate]

We need to change some settings to the HKEY_LOCAL_MACHINE at runtime.
Is it possible to prompt for uac elevation if needed at runtime, or do I have to launch a second elevated process to do 'the dirty work'?
i would relaunch yourself as elevated, passing command line parameters indicating what elevated thing you want to do. You can then jump right to the appropriate form, or just save your HKLM stuff.
function RunAsAdmin(hWnd: HWND; filename: string; Parameters: string): Boolean;
{
See Step 3: Redesign for UAC Compatibility (UAC)
http://msdn.microsoft.com/en-us/library/bb756922.aspx
This code is released into the public domain. No attribution required.
}
var
sei: TShellExecuteInfo;
begin
ZeroMemory(#sei, SizeOf(sei));
sei.cbSize := SizeOf(TShellExecuteInfo);
sei.Wnd := hwnd;
sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
sei.lpVerb := PChar('runas');
sei.lpFile := PChar(Filename); // PAnsiChar;
if parameters <> '' then
sei.lpParameters := PChar(parameters); // PAnsiChar;
sei.nShow := SW_SHOWNORMAL; //Integer;
Result := ShellExecuteEx(#sei);
end;
The other Microsoft suggested solution is to create an COM object out of process (using the specially created CoCreateInstanceAsAdmin function). i don't like this idea because you have to write and register a COM object.
Note: There is no "CoCreateInstanceAsAdmin" API call. It's just some code floating around. Here's the Dephi version i stumbled around for. It is apparently based on the trick of prefixing a class guid string with the "Elevation:Administrator!new:" prefix when normally hidden code internally calls CoGetObject:
function CoGetObject(pszName: PWideChar; pBindOptions: PBindOpts3;
const iid: TIID; ppv: PPointer): HResult; stdcall; external 'ole32.dll';
procedure CoCreateInstanceAsAdmin(const Handle: HWND;
const ClassID, IID: TGuid; PInterface: PPointer);
var
BindOpts: TBindOpts3;
MonikerName: WideString;
Res: HRESULT;
begin
//This code is released into the public domain. No attribution required.
ZeroMemory(#BindOpts, Sizeof(TBindOpts3));
BindOpts.cbStruct := Sizeof(TBindOpts3);
BindOpts.hwnd := Handle;
BindOpts.dwClassContext := CLSCTX_LOCAL_SERVER;
MonikerName := 'Elevation:Administrator!new:' + GUIDToString(ClassID);
Res := CoGetObject(PWideChar(MonikerName), #BindOpts, IID, PInterface);
if Failed(Res) then
raise Exception.Create(SysErrorMessage(Res));
end;
One other question: How do you handle someone running as standard user in Windows XP?
You can't "elevate" an existing process. Elevated processes under UAC have a different token with a different LUID, different mandatory integrity level, and different group membership. This level of change can't be done within a running process - and it would be a security problem if that could happen.
You need to launch a second process elevated that would do the work or by creating a COM object that runs in an elevated dllhost.
http://msdn.microsoft.com/en-us/library/bb756922.aspx gives an example "RunAsAdmin" function and a "CoCreateInstanceAsAdmin" function.
EDIT: I just saw "Delphi" in your title. Everything I listed is obviously native, but if Delphi provides access to ShellExecute-like functionality you should be able to adapt the code from the link.
A sample of ready-to-use code:
Usage example:
unit Unit1;
interface
uses
Windows{....};
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Label3: TLabel;
Label4: TLabel;
Button1: TButton;
Button2: TButton;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
procedure StartWait;
procedure EndWait;
end;
var
Form1: TForm1;
implementation
uses
RunElevatedSupport;
{$R *.dfm}
const
ArgInstallUpdate = '/install_update';
ArgRegisterExtension = '/register_global_file_associations';
procedure TForm1.FormCreate(Sender: TObject);
begin
Label1.Caption := Format('IsAdministrator: %s', [BoolToStr(IsAdministrator, True)]);
Label2.Caption := Format('IsAdministratorAccount: %s', [BoolToStr(IsAdministratorAccount, True)]);
Label3.Caption := Format('IsUACEnabled: %s', [BoolToStr(IsUACEnabled, True)]);
Label4.Caption := Format('IsElevated: %s', [BoolToStr(IsElevated, True)]);
Button1.Caption := 'Install updates';
SetButtonElevated(Button1.Handle);
Button2.Caption := 'Register file associations for all users';
SetButtonElevated(Button2.Handle);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
StartWait;
try
SetLastError(RunElevated(ArgInstallUpdate, Handle, Application.ProcessMessages));
if GetLastError <> ERROR_SUCCESS then
RaiseLastOSError;
finally
EndWait;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
StartWait;
try
SetLastError(RunElevated(ArgRegisterExtension, Handle, Application.ProcessMessages));
if GetLastError <> ERROR_SUCCESS then
RaiseLastOSError;
finally
EndWait;
end;
end;
function DoElevatedTask(const AParameters: String): Cardinal;
procedure InstallUpdate;
var
Msg: String;
begin
Msg := 'Hello from InstallUpdate!' + sLineBreak +
sLineBreak +
'This function is running elevated under full administrator rights.' + sLineBreak +
'This means that you have write-access to Program Files folder and you''re able to overwrite files (e.g. install updates).' + sLineBreak +
'However, note that your executable is still running.' + sLineBreak +
sLineBreak +
'IsAdministrator: ' + BoolToStr(IsAdministrator, True) + sLineBreak +
'IsAdministratorAccount: ' + BoolToStr(IsAdministratorAccount, True) + sLineBreak +
'IsUACEnabled: ' + BoolToStr(IsUACEnabled, True) + sLineBreak +
'IsElevated: ' + BoolToStr(IsElevated, True);
MessageBox(0, PChar(Msg), 'Hello from InstallUpdate!', MB_OK or MB_ICONINFORMATION);
end;
procedure RegisterExtension;
var
Msg: String;
begin
Msg := 'Hello from RegisterExtension!' + sLineBreak +
sLineBreak +
'This function is running elevated under full administrator rights.' + sLineBreak +
'This means that you have write-access to HKEY_LOCAL_MACHINE key and you''re able to write keys and values (e.g. register file extensions globally/for all users).' + sLineBreak +
'However, note that this is usually not a good idea. It is better to register your file extensions under HKEY_CURRENT_USER\Software\Classes.' + sLineBreak +
sLineBreak +
'IsAdministrator: ' + BoolToStr(IsAdministrator, True) + sLineBreak +
'IsAdministratorAccount: ' + BoolToStr(IsAdministratorAccount, True) + sLineBreak +
'IsUACEnabled: ' + BoolToStr(IsUACEnabled, True) + sLineBreak +
'IsElevated: ' + BoolToStr(IsElevated, True);
MessageBox(0, PChar(Msg), 'Hello from RegisterExtension!', MB_OK or MB_ICONINFORMATION);
end;
begin
Result := ERROR_SUCCESS;
if AParameters = ArgInstallUpdate then
InstallUpdate
else
if AParameters = ArgRegisterExtension then
RegisterExtension
else
Result := ERROR_GEN_FAILURE;
end;
procedure TForm1.StartWait;
begin
Cursor := crHourglass;
Screen.Cursor := crHourglass;
Button1.Enabled := False;
Button2.Enabled := False;
Application.ProcessMessages;
end;
procedure TForm1.EndWait;
begin
Cursor := crDefault;
Screen.Cursor := crDefault;
Button1.Enabled := True;
Button2.Enabled := True;
Application.ProcessMessages;
end;
initialization
OnElevateProc := DoElevatedTask;
CheckForElevatedTask;
end.
And support unit itself:
unit RunElevatedSupport;
{$WARN SYMBOL_PLATFORM OFF}
{$R+}
interface
uses
Windows;
type
TElevatedProc = function(const AParameters: String): Cardinal;
TProcessMessagesMeth = procedure of object;
var
// Warning: this function will be executed in external process.
// Do not use any global variables inside this routine!
// Use only supplied AParameters.
OnElevateProc: TElevatedProc;
// Call this routine after you have assigned OnElevateProc
procedure CheckForElevatedTask;
// Runs OnElevateProc under full administrator rights
function RunElevated(const AParameters: String; const AWnd: HWND = 0; const AProcessMessages: TProcessMessagesMeth = nil): Cardinal; overload;
function IsAdministrator: Boolean;
function IsAdministratorAccount: Boolean;
function IsUACEnabled: Boolean;
function IsElevated: Boolean;
procedure SetButtonElevated(const AButtonHandle: THandle);
implementation
uses
SysUtils, Registry, ShellAPI, ComObj;
const
RunElevatedTaskSwitch = '0CC5C50CB7D643B68CB900BF000FFFD5'; // some unique value, just a GUID with removed '[', ']', and '-'
function CheckTokenMembership(TokenHandle: THANDLE; SidToCheck: Pointer; var IsMember: BOOL): BOOL; stdcall; external advapi32 name 'CheckTokenMembership';
function RunElevated(const AParameters: String; const AWnd: HWND = 0; const AProcessMessages: TProcessMessagesMeth = nil): Cardinal; overload;
var
SEI: TShellExecuteInfo;
Host: String;
Args: String;
begin
Assert(Assigned(OnElevateProc), 'OnElevateProc must be assigned before calling RunElevated');
if IsElevated then
begin
if Assigned(OnElevateProc) then
Result := OnElevateProc(AParameters)
else
Result := ERROR_PROC_NOT_FOUND;
Exit;
end;
Host := ParamStr(0);
Args := Format('/%s %s', [RunElevatedTaskSwitch, AParameters]);
FillChar(SEI, SizeOf(SEI), 0);
SEI.cbSize := SizeOf(SEI);
SEI.fMask := SEE_MASK_NOCLOSEPROCESS;
{$IFDEF UNICODE}
SEI.fMask := SEI.fMask or SEE_MASK_UNICODE;
{$ENDIF}
SEI.Wnd := AWnd;
SEI.lpVerb := 'runas';
SEI.lpFile := PChar(Host);
SEI.lpParameters := PChar(Args);
SEI.nShow := SW_NORMAL;
if not ShellExecuteEx(#SEI) then
RaiseLastOSError;
try
Result := ERROR_GEN_FAILURE;
if Assigned(AProcessMessages) then
begin
repeat
if not GetExitCodeProcess(SEI.hProcess, Result) then
Result := ERROR_GEN_FAILURE;
AProcessMessages;
until Result <> STILL_ACTIVE;
end
else
begin
if WaitForSingleObject(SEI.hProcess, INFINITE) <> WAIT_OBJECT_0 then
if not GetExitCodeProcess(SEI.hProcess, Result) then
Result := ERROR_GEN_FAILURE;
end;
finally
CloseHandle(SEI.hProcess);
end;
end;
function IsAdministrator: Boolean;
var
psidAdmin: Pointer;
B: BOOL;
const
SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
SECURITY_BUILTIN_DOMAIN_RID = $00000020;
DOMAIN_ALIAS_RID_ADMINS = $00000220;
SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
begin
psidAdmin := nil;
try
// Создаём SID группы админов для проверки
Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
psidAdmin));
// Проверяем, входим ли мы в группу админов (с учётов всех проверок на disabled SID)
if CheckTokenMembership(0, psidAdmin, B) then
Result := B
else
Result := False;
finally
if psidAdmin <> nil then
FreeSid(psidAdmin);
end;
end;
{$R-}
function IsAdministratorAccount: Boolean;
var
psidAdmin: Pointer;
Token: THandle;
Count: DWORD;
TokenInfo: PTokenGroups;
HaveToken: Boolean;
I: Integer;
const
SECURITY_NT_AUTHORITY: TSidIdentifierAuthority = (Value: (0, 0, 0, 0, 0, 5));
SECURITY_BUILTIN_DOMAIN_RID = $00000020;
DOMAIN_ALIAS_RID_ADMINS = $00000220;
SE_GROUP_USE_FOR_DENY_ONLY = $00000010;
begin
Result := Win32Platform <> VER_PLATFORM_WIN32_NT;
if Result then
Exit;
psidAdmin := nil;
TokenInfo := nil;
HaveToken := False;
try
Token := 0;
HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, Token);
if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then
HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, Token);
if HaveToken then
begin
Win32Check(AllocateAndInitializeSid(SECURITY_NT_AUTHORITY, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS, 0, 0, 0, 0, 0, 0,
psidAdmin));
if GetTokenInformation(Token, TokenGroups, nil, 0, Count) or
(GetLastError <> ERROR_INSUFFICIENT_BUFFER) then
RaiseLastOSError;
TokenInfo := PTokenGroups(AllocMem(Count));
Win32Check(GetTokenInformation(Token, TokenGroups, TokenInfo, Count, Count));
for I := 0 to TokenInfo^.GroupCount - 1 do
begin
Result := EqualSid(psidAdmin, TokenInfo^.Groups[I].Sid);
if Result then
Break;
end;
end;
finally
if TokenInfo <> nil then
FreeMem(TokenInfo);
if HaveToken then
CloseHandle(Token);
if psidAdmin <> nil then
FreeSid(psidAdmin);
end;
end;
{$R+}
function IsUACEnabled: Boolean;
var
Reg: TRegistry;
begin
Result := CheckWin32Version(6, 0);
if Result then
begin
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if Reg.OpenKey('\Software\Microsoft\Windows\CurrentVersion\Policies\System', False) then
if Reg.ValueExists('EnableLUA') then
Result := (Reg.ReadInteger('EnableLUA') <> 0)
else
Result := False
else
Result := False;
finally
FreeAndNil(Reg);
end;
end;
end;
function IsElevated: Boolean;
const
TokenElevation = TTokenInformationClass(20);
type
TOKEN_ELEVATION = record
TokenIsElevated: DWORD;
end;
var
TokenHandle: THandle;
ResultLength: Cardinal;
ATokenElevation: TOKEN_ELEVATION;
HaveToken: Boolean;
begin
if CheckWin32Version(6, 0) then
begin
TokenHandle := 0;
HaveToken := OpenThreadToken(GetCurrentThread, TOKEN_QUERY, True, TokenHandle);
if (not HaveToken) and (GetLastError = ERROR_NO_TOKEN) then
HaveToken := OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, TokenHandle);
if HaveToken then
begin
try
ResultLength := 0;
if GetTokenInformation(TokenHandle, TokenElevation, #ATokenElevation, SizeOf(ATokenElevation), ResultLength) then
Result := ATokenElevation.TokenIsElevated <> 0
else
Result := False;
finally
CloseHandle(TokenHandle);
end;
end
else
Result := False;
end
else
Result := IsAdministrator;
end;
procedure SetButtonElevated(const AButtonHandle: THandle);
const
BCM_SETSHIELD = $160C;
var
Required: BOOL;
begin
if not CheckWin32Version(6, 0) then
Exit;
if IsElevated then
Exit;
Required := True;
SendMessage(AButtonHandle, BCM_SETSHIELD, 0, LPARAM(Required));
end;
procedure CheckForElevatedTask;
function GetArgsForElevatedTask: String;
function PrepareParam(const ParamNo: Integer): String;
begin
Result := ParamStr(ParamNo);
if Pos(' ', Result) > 0 then
Result := AnsiQuotedStr(Result, '"');
end;
var
X: Integer;
begin
Result := '';
for X := 1 to ParamCount do
begin
if (AnsiUpperCase(ParamStr(X)) = ('/' + RunElevatedTaskSwitch)) or
(AnsiUpperCase(ParamStr(X)) = ('-' + RunElevatedTaskSwitch)) then
Continue;
Result := Result + PrepareParam(X) + ' ';
end;
Result := Trim(Result);
end;
var
ExitCode: Cardinal;
begin
if not FindCmdLineSwitch(RunElevatedTaskSwitch) then
Exit;
ExitCode := ERROR_GEN_FAILURE;
try
if not IsElevated then
ExitCode := ERROR_ACCESS_DENIED
else
if Assigned(OnElevateProc) then
ExitCode := OnElevateProc(GetArgsForElevatedTask)
else
ExitCode := ERROR_PROC_NOT_FOUND;
except
on E: Exception do
begin
if E is EAbort then
ExitCode := ERROR_CANCELLED
else
if E is EOleSysError then
ExitCode := Cardinal(EOleSysError(E).ErrorCode)
else
if E is EOSError then
else
ExitCode := ERROR_GEN_FAILURE;
end;
end;
if ExitCode = STILL_ACTIVE then
ExitCode := ERROR_GEN_FAILURE;
TerminateProcess(GetCurrentProcess, ExitCode);
end;
end.
Usually, putting the text "Setup" or "Install" somewhere in your EXE name is enough to make Windows run with elevated privileges automatically, and is well worth doing if it is a setup utility you are writing, as it's so easy to do.
I am now running into problems though on Windows 7, when not logged in as an Administrator, and am having to use the right-click Run As Administrator when running manually (running the program via Wise installation wizard is still fine)
I see though that Delphi 10.1 Berlin has a very easy to use new option under Project Options | Application. Just tick Enable Administrator Privileges, and the manifest is done for you, so easy!
NB. make sure you only do these kind of changes via a separate setup program, running your application with elevated privileges all the time can cause problems with other things, for example e-mail, where the default mail profile no longer gets picked up.
Edit: Jan 2018: since writing this answer in August 2017, it seems a lot of Windows updates have come out, that now require the user to right-click and Run As Administrator on just about everything, even on installation exe's built with Wise. Even Outlook is no longer installing properly without running as administrator. There is no more automated elevation at all it seems.

How to permanently terminate Windows Explorer (the "explorer.exe" process)?

I'm using the following code to terminate a process:
function KillTask(ExeFileName: string): Integer;
const
PROCESS_TERMINATE = $0001;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
Result := 0;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while Integer(ContinueLoop) <> 0 do
begin
if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) =
UpperCase(ExeFileName)) or (UpperCase(FProcessEntry32.szExeFile) =
UpperCase(ExeFileName))) then
Result := Integer(TerminateProcess(
OpenProcess(PROCESS_TERMINATE,
BOOL(0),
FProcessEntry32.th32ProcessID),
0));
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
The problem is, when I call the above function in order to permanently terminate the explorer.exe, the Windows Explorer terminates though, but it's re-started afterwards:
KillTask('explorer.exe');
I'm using Delphi XE3, Delphi 7 and Windows 8.
Based on this Exit Explorer feature and code debugged by Luke in this post you may try to use the following code:
Warning:
This way is absolutely undocumented! So all constants and variables appearing in this post are fictitious. Any resemblance to real, documented code is purely coincidental :-)
function ExitExplorer: Boolean;
var
TrayHandle: HWND;
const
WM_EXITEXPLORER = $5B4;
begin
Result := False;
TrayHandle := FindWindow('Shell_TrayWnd', nil);
if TrayHandle <> 0 then
Result := PostMessage(TrayHandle, WM_EXITEXPLORER, 0, 0);
end;
I've tested it in Windows 7, where it works and doesn't even need the administrator elevation. Don't know how about the other systems (I'd say this won't work at least on Windows XP, but it's just a guess).

How to Start an application and obtain a handle to it with Delphi?

I want to start an application from Delphi, and obtain a handle to it, so I can embed the main window of said application on a frame of type TFrame. So far I have tried:
Function TFrmEmbeddedExe.StartNewApplication : Boolean;
var
SEInfo: TShellExecuteInfo;
ExitCode : DWORD;
begin
FillChar(SEInfo, SizeOf(SEInfo), 0) ;
SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
with SEInfo do
begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := self.Handle;
lpFile := PChar(self.fexecuteFileName) ;// Example could be 'C:\Windows\Notepad.exe'
nShow := SW_SHOWNORMAL;//SW_HIDE;
end;
if ShellExecuteEx(#SEInfo) then
begin
sleep(1500);
self.fAppWnd := FindWindow(nil, PChar(self.fWindowCaption)); //Example : 'Untitled - Notepad'
if self.fAppWnd <> 0 then
begin
Windows.SetParent(self.fAppWnd, SEInfo.Wnd);
ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
result := true;
end
else
result := false;
end
else
result := false;
end ;
The above code actually works, but findWindow will find any given instans of the application I started. I want to embed the exact instans that I Shellexecuted.
So if Notepad had been started a couple of times, there is no way I can get the correct one using FindWindow.
I have tried:
Function TfrmEmbeddedExe.CreateProcessNewApplication : Boolean;
var
zAppName: array[0..512] of char;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
Res : DWORD;
DoWait : Boolean;
begin
DoWait := False;
StrPCopy(zAppName, self.fexecuteFileName); //'C:\Windows\Notepad.exe'
FillChar(StartupInfo, Sizeof(StartupInfo), #0);
StartupInfo.cb := Sizeof(StartupInfo);
StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
StartupInfo.wShowWindow := SW_SHOWNORMAL;
if CreateProcess (zAppName,
nil, { pointer to command line string }
nil, { pointer to process security attributes }
nil, { pointer to thread security attributes }
false, { handle inheritance flag }
CREATE_NEW_CONSOLE or { creation flags }
NORMAL_PRIORITY_CLASS,
nil, { pointer to new environment block }
nil, { pointer to current directory name }
StartupInfo, { pointer to STARTUPINFO }
ProcessInfo) then { pointer to PROCESS_INF }
begin
if DoWait then //just set it to false... so it will never enter here
begin
WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
GetExitCodeProcess(ProcessInfo.hProcess, Res);
end
else
begin
self.fAppWnd := ProcessInfo.hProcess;
Windows.SetParent(self.fAppWnd, self.Handle);
ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
end;
result := true;
end
else begin
Result := false;
end;
end;
PLEASE DO NOT RUN THE ABOVE CODE! It produces weird results involving picking a seemingly random window anywhere in all running applications and embedding that (even menu-items from the Windows start menu..)
So basically what I need is how do I start an application, and grab a handle to the application's main window.
Here's the rough outline of what you need to do. I'll leave the coding up to you:
Start your process with either ShellExecuteEx or CreateProcess. This will yield a process handle.
Call WaitForInputIdle on the process handle. This gives the process a chance to load and start its message loop.
Pass the process handle to GetProcessId to obtain the process ID.
Use EnumWindows to enumerate the top level windows.
Pass each of these windows to GetWindowThreadProcessId to check whether or not you have found the top level window of your target process.
Once you find a window whose process ID matches your target process, you're done!
Don't forget to close your process handles once you are done with them.
This code works for me:
Create a "Utils"- Unit with the following >>
....
interface
.....
function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;
implementation
type
TEnumData = record // Record Type for Enumeration
WHdl: HWND;
WPid: DWORD;
WTitle: String;
end;
PEnumData = ^TEnumData; // Pointer to Record Type
// Enumeration Function for GetWinHandleFromProcId (below)
function EnumWindowsProcMatchPID(WHdl: HWND; EData: PEnumData): bool; stdcall;
var
Wpid : DWORD;
begin
Result := True; // continue enumeration
GetWindowThreadProcessID(WHdl, #Wpid);
// Filter for only visible windows, because the Pid is not unique to the Main Form
if (EData.WPid = Wpid) AND IsWindowVisible(WHdl) then
begin
EData.WHdl := WHdl;
Result := False; // stop enumeration
end;
end;
// Find Window from Process Id and return the Window Handle
function GetWinHandleFromProcId(ProcId: DWORD): HWND;
var
EnumData: TEnumData;
begin
ZeroMemory(#EnumData, SizeOf(EnumData));
EnumData.WPid := ProcId;
EnumWindows(#EnumWindowsProcMatchPID, LPARAM(#EnumData));
Result := EnumData.WHdl;
end;
// Run Program using CreateProcess >> Return Window Handle and Process Handle
function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;
var
StartInfo: TStartupInfo;
ProcInfo: TProcessInformation;
ProcessId : DWORD;
WinHdl : HWND;
bOK : boolean;
ix : integer;
begin
FillChar(StartInfo, SizeOf(StartInfo), 0);
StartInfo.cb := SizeOf(StartInfo);
StartInfo.dwFlags := STARTF_USESHOWWINDOW;
StartInfo.wShowWindow := SW_Show;
bOK := CreateProcess(PChar(PName), PChar(CmdLine), nil, nil, False, 0, nil, nil, StartInfo, ProcInfo);
ProcessHdl := ProcInfo.hProcess;
ProcessId := ProcInfo.dwProcessId;
// Note : "WaitForInputIdle" does not always wait long enough, ...
// so we combine it with a repeat - until - loop >>
WinHdl := 0;
if bOK then // Process is running
begin
WaitForInputIdle(ProcessHdl,INFINITE);
ix := 0;
repeat // Will wait (up to 10+ seconds) for a program that takes very long to show it's main window
WinHdl := GetWinHandleFromProcId(ProcessId);
Sleep(25);
inc(ix);
until (WinHdl > 0) OR (ix > 400); // Got Handle OR Timeout
end;
Result := WinHdl;
CloseHandle(ProcInfo.hThread);
end;
Put this in your main program that uses the "Utils"- Unit >>
var
SlaveWinHdl : HWND; // Slave Program Window Handle
SlaveProcHdl : HWND; // Slave Program Process Handle
// Button to run Notepad - Returning Window Handle and Process Handle
procedure TForm1.Button1Click(Sender: TObject);
var
Pname, Pcmnd: string;
begin
Pname := 'C:\WINDOWS\system32\notepad.exe';
Pcmnd := '';
SlaveWinHdl := RunProg(Pname, Pcmnd, SlaveProcHdl);
end;
// Button to Close program using Window Handle
procedure TForm1.Button2Click(Sender: TObject);
begin
PostMessage(SlaveWinHdl, WM_CLOSE, 0, 0);
end;
// Button to Close program using Process Handle
procedure TForm1.Button3Click(Sender: TObject);
begin
TerminateProcess(SlaveProcHdl, STILL_ACTIVE);
CloseHandle(SlaveProcHdl);
end;
So there you have it, a complete solution of how to Run an external program,
and then Close it by using either the Window Handle or Process Handle.
Extra Bonus: Sometimes you have to find the handles for a program that is already running.
You can find it based on the Window- Title with the following code (added to your “Utils” unit) >>
function EnumWindowsProcMatchTitle(WHdl: HWND; EData: PEnumData): bool; stdcall;
var
WinTitle: array[0..255] of char;
Wpid : DWORD;
begin
Result := True; // continue enumeration
GetWindowText(WHdl, WinTitle, 256);
if (Pos(EData.WTitle, StrPas(WinTitle)) <> 0) then // Will also match partial title
begin
EData.WHdl := WHdl;
GetWindowThreadProcessID(WHdl, #Wpid);
EData.WPid := Wpid;
Result := False; // stop enumeration
end;
end;
function GetHandlesFromWinTitle(WinTitle: String; out ProcHdl : HWND): HWND;
var
EnumData: TEnumData;
begin
ZeroMemory(#EnumData, SizeOf(EnumData));
EnumData.WTitle := WinTitle;
EnumWindows(#EnumWindowsProcMatchTitle, LPARAM(#EnumData));
ProcHdl := OpenProcess(PROCESS_ALL_ACCESS,False,EnumData.WPid);
Result := EnumData.WHdl;
end;
And call it (from your main program), like this >>
strWT := ‘MyList.txt – Notepad’; // example of Notepad Title
SlaveWinHdl := GetHandlesFromWinTitle(strWT, SlaveProcHdl);

how to get access console buffer from another process? AttachConsole ERROR_INVALID_PARAMETER

I want to get access to the buffer of another process console (via AttachConsole), for calling ReadConsoleOutput, etc.
Is a DOS 16bit application. I can't use pipes because it doesn't writes output secuentially (it emulates "windows".. like FAR commander if you know what I mean).
So I should:
1) launch the app
2) get the process id
3) call AttachConsole(ProcId)
4) call GetConsoleScreenBufferInfo to get the size
5) call ReadConsoleOutput
The problem is at 3: when I call AttachConsole ir returns 0, and after a call to GetLastError it reports ERROR_INVALID_PARAMETER 87 (0x57).
The only parameter of AttachConsole is the ProcessId and I've checked it with ProcessExplorer that is right (it's actually the PID of ntvdm.exe that emulates the app).
Delphi code:
function AttachConsole(dwProcessId: DWORD): Cardinal; external kernel32 name 'AttachConsole';
var
Handle: HWND;
function EnumWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
s: string;
IsVisible, IsOwned, IsAppWindow: Boolean;
begin
Result := True;//carry on enumerating
IsVisible := IsWindowVisible(hwnd);
if not IsVisible then
exit;
IsOwned := GetWindow(hwnd, GW_OWNER)<>0;
if IsOwned then
exit;
IsAppWindow := GetWindowLongPtr(hwnd, GWL_STYLE) and WS_EX_APPWINDOW<>0;
if not IsAppWindow then
exit;
SetLength(s, GetWindowTextLength(hwnd));
GetWindowText(hwnd, PChar(s), Length(s)+1);
if AnsiContainsText(s, '????.EXE') then // set windows name to search
Handle := hwnd;
end;
procedure Test(Strings: TStrings);
var
ProcessID: Cardinal;
begin
Handle := 0;
EnumWindows(#EnumWindowsProc, 0);
Strings.Add('Handle: ' + IntToStr(Handle));
if Handle <> 0 then
SetForegroundWindow(Handle);
Sleep(100);
GetWindowThreadProcessId(Handle, #ProcessID);
Strings.Add('ProcessId: ' + IntToStr(ProcessID));
if AttachConsole(ProcessId) <> 0 then
Strings.Add('Ok Attached')
else
Strings.Add('Error: ' + IntToStr(GetLastError));
end;
Drop memo and button in form. At OnClick call Test(Memo1.Lines).
===== EDIT complete solution =====
function AttachAndGetConsoleHandle(ProcessId: Cardinal): Cardinal;
begin
if not AttachConsole(ProcessId) then
raise Exception.Create('AttachConsole error: ' + IntToStr(GetLastError));
Result := GetStdHandle(STD_OUTPUT_HANDLE);
if Result = INVALID_HANDLE_VALUE then
raise Exception.Create('GetStdHandle(STD_OUTPUT_HANDLE) error: ' + IntToStr(GetLastError));
end;
procedure DettachConsole;
begin
if not FreeConsole then
raise Exception.Create('FreeConsole error: ' + IntToStr(GetLastError));
end;
function ReadConsole(ConsoleHandle: Cardinal): TStringList;
var
BufferInfo: _CONSOLE_SCREEN_BUFFER_INFO;
BufferSize, BufferCoord: _COORD;
ReadRegion: _SMALL_RECT;
Buffer: Array of _CHAR_INFO;
I, J: Integer;
Line: AnsiString;
begin
Result := TStringList.Create;
ZeroMemory(#BufferInfo, SizeOf(BufferInfo));
if not GetConsoleScreenBufferInfo(ConsoleHandle, BufferInfo) then
raise Exception.Create('GetConsoleScreenBufferInfo error: ' + IntToStr(GetLastError));
SetLength(Buffer, BufferInfo.dwSize.X * BufferInfo.dwSize.Y);
BufferSize.X := BufferInfo.dwSize.X;
BufferSize.Y := BufferInfo.dwSize.Y;
BufferCoord.X := 0;
BufferCoord.Y := 0;
ReadRegion.Left := 0;
ReadRegion.Top := 0;
ReadRegion.Right := BufferInfo.dwSize.X;
ReadRegion.Bottom := BufferInfo.dwSize.Y;
if ReadConsoleOutput(ConsoleHandle, Pointer(Buffer), BufferSize, BufferCoord, ReadRegion) then
begin
for I := 0 to BufferInfo.dwSize.Y - 1 do
begin
Line := '';
for J := 0 to BufferInfo.dwSize.X - 1 do
Line := Line + Buffer[I * BufferInfo.dwSize.X + J].AsciiChar;
Result.Add(Line)
end
end
else
raise Exception.Create('ReadConsoleOutput error: ' + IntToStr(GetLastError));
end;
The definition should be:
function AttachConsole(dwProcessId: DWORD): BOOL; stdcall; external
kernel32 name 'AttachConsole';
So the code following it should be:
if AttachConsole(ProcessId) then
Can't help you anymore than that.

Resources