How to check if a process belongs to the current user? - delphi

I'm trying to get PIDs of processes which belongs to the current user but I don't know how to check the process owner.
This is my code (the user's checking condition is missing):
uses
TlHelp32, ...;
type
TCardinalArray = array of Cardinal;
function GetCurrentUserPIDs(const AProcessName : string) : TCardinalArray;
var
ContinueLoop: boolean;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
SetLength(Result, 0);
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while(ContinueLoop) do
begin
if(SameText(FProcessEntry32.szExeFile, AProcessName)) then
begin
if((* is this my process? *)) then
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := FProcessEntry32.th32ProcessID;
end;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;

I found a GetUserAndDomainFromPID function which allows to easily accomplish the task.
As Sertac Akyuz suggested, the function uses OpenProcessToken and GetTokenInformation. It also uses LookupAccountSid:
uses
TlHelp32;
type
PTOKEN_USER = ^TOKEN_USER;
_TOKEN_USER = record
User: TSidAndAttributes;
end;
TOKEN_USER = _TOKEN_USER;
function GetUserAndDomainFromPID(ProcessId: DWORD;
var User, Domain: string): Boolean;
var
hToken: THandle;
cbBuf: Cardinal;
ptiUser: PTOKEN_USER;
snu: SID_NAME_USE;
ProcessHandle: THandle;
UserSize, DomainSize: DWORD;
bSuccess: Boolean;
begin
Result := False;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION, False, ProcessId);
if ProcessHandle <> 0 then
begin
// EnableProcessPrivilege(ProcessHandle, 'SeSecurityPrivilege', True);
if OpenProcessToken(ProcessHandle, TOKEN_QUERY, hToken) then
begin
bSuccess := GetTokenInformation(hToken, TokenUser, nil, 0, cbBuf);
ptiUser := nil;
while (not bSuccess) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) do
begin
ReallocMem(ptiUser, cbBuf);
bSuccess := GetTokenInformation(hToken, TokenUser, ptiUser, cbBuf, cbBuf);
end;
CloseHandle(hToken);
if not bSuccess then
begin
Exit;
end;
UserSize := 0;
DomainSize := 0;
LookupAccountSid(nil, ptiUser.User.Sid, nil, UserSize, nil, DomainSize, snu);
if (UserSize <> 0) and (DomainSize <> 0) then
begin
SetLength(User, UserSize);
SetLength(Domain, DomainSize);
if LookupAccountSid(nil, ptiUser.User.Sid, PChar(User), UserSize,
PChar(Domain), DomainSize, snu) then
begin
Result := True;
User := StrPas(PChar(User));
Domain := StrPas(PChar(Domain));
end;
end;
if bSuccess then
begin
FreeMem(ptiUser);
end;
end;
CloseHandle(ProcessHandle);
end;
end;
Then I've written a function for getting the current windows username (It uses GetUserName):
const
UNLEN = 256; // Maximum user name length
function GetWindowsUsername: string;
var
UserName : string;
UserNameLen : Dword;
begin
UserNameLen := UNLEN;
SetLength(UserName, UserNameLen) ;
if GetUserName(PChar(UserName), UserNameLen)
then Result := Copy(UserName, 1, UserNameLen - 1)
else Result := '';
end;
The following function returns an array composed by all ids of processes who belongs to the current user (Note that processes are filtered by process name):
uses
TlHelp32;
type
TCardinalArray = array of Cardinal;
function GetCurrentUserPIDs(const AProcessName : string) : TCardinalArray;
var
ContinueLoop: boolean;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
UserName : string;
DomainName : string;
CurrentUser : string;
begin
CurrentUser := GetWindowsUsername();
SetLength(Result, 0);
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while(ContinueLoop) do
begin
if(SameText(FProcessEntry32.szExeFile, AProcessName)) then
begin
if(GetUserAndDomainFromPID(FProcessEntry32.th32ProcessID, UserName, DomainName)) then
begin
if(UserName = CurrentUser) then
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := FProcessEntry32.th32ProcessID;
end;
end;
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;

Related

Execute a command-line Application via ShellExecute() and get its return value

I am a Delphi developer in our company. We need a function which launches a command-line executable and get its return value.
The code I wrote, and all the examples I found on the Internet, do this via CreateProcess(), but my boss rejected this and told me that there MUST be a solution doing this via ShellExecute(). I can't find any example on the Internet doing this with ShellExecute(). All of them use CreateProcess().
Below are 3 methods I delivered to my boss. He did not like ShellExecute_AndGetReturnValue(). It's named "ShellExecute", but it does not use ShellExecute().
All of these 3 methods are working fine. But the first one is not using ShellExecute(). Instead it is using CreateProcess().
So, is it possible to solve/change the ShellExecute_AndGetReturnValue() method so that it will use ShellExecute() instead of CreateProcess()? All examples I found, all of them, use CreateProcess().
function ShellExecute_AndGetReturnValue(FileName : string; Params : string = ''; Show : Integer = SW_HIDE; WorkingDir : string = '') : string;
const
READ_BUFFER_SIZE = 2048;
var
Security: TSecurityAttributes;
readableEndOfPipe, writeableEndOfPipe, readableErrorEndOfPipe, writeableErrorEndOfPipe: THandle;
start: TStartUpInfo;
ProcessInfo: TProcessInformation;
Buffer: PAnsiChar;
BytesRead: DWORD;
AppRunning: DWORD;
ResultStdOutput : string;
ResultErrOutput : string;
lpDirectory : PAnsiChar;
CmdLine : string;
begin
Result := '';
Security.nLength := SizeOf(TSecurityAttributes);
Security.bInheritHandle := True;
Security.lpSecurityDescriptor := nil;
if CreatePipe(readableEndOfPipe, writeableEndOfPipe, #Security, 0) then
begin
Buffer := AllocMem(READ_BUFFER_SIZE + 1);
FillChar(Start, Sizeof(Start), #0);
FillChar(ProcessInfo, SizeOf(ProcessInfo), #0);
start.cb := SizeOf(start);
start.dwFlags := start.dwFlags or STARTF_USESTDHANDLES;
start.hStdInput := GetStdHandle(STD_INPUT_HANDLE);
start.hStdOutput := writeableEndOfPipe;
CreatePipe(readableErrorEndOfPipe, writeableErrorEndOfPipe, #Security, 0);
start.hStdError := writeableErrorEndOfPipe;
start.hStdError := writeableEndOfPipe;
start.dwFlags := start.dwFlags + STARTF_USESHOWWINDOW;
start.wShowWindow := Show;
UniqueString(FileName);
CmdLine := '"' + FileName + '" ' + Params;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
if CreateProcess(nil, PChar(CmdLine), nil, nil, True, NORMAL_PRIORITY_CLASS, nil, lpDirectory, start, ProcessInfo) then
begin
repeat
Apprunning := WaitForSingleObject(ProcessInfo.hProcess, 100);
Application.ProcessMessages;
until (Apprunning <> WAIT_TIMEOUT);
ResultStdOutput := '';
ResultErrOutput := '';
//Must Close write Handles before reading (if the console application does not output anything)
CloseHandle(writeableEndOfPipe);
CloseHandle(writeableErrorEndOfPipe);
repeat
BytesRead := 0;
ReadFile(readableEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
ResultStdOutput := ResultStdOutput + String(Buffer);
until (BytesRead < READ_BUFFER_SIZE);
if start.hStdOutput <> start.hStdError then
begin
BytesRead := 0;
ReadFile(readableErrorEndOfPipe, Buffer[0], READ_BUFFER_SIZE, BytesRead, nil);
Buffer[BytesRead]:= #0;
OemToAnsi(Buffer,Buffer);
ResultErrOutput := ResultErrOutput + String(Buffer);
end;
end;
Result := ResultStdOutput;
FreeMem(Buffer);
CloseHandle(ProcessInfo.hProcess);
CloseHandle(ProcessInfo.hThread);
CloseHandle(readableEndOfPipe);
CloseHandle(readableErrorEndOfPipe);
end;
end;
procedure ShellExecute_NoWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
exInfo: TShellExecuteInfo;
Ph: DWORD;
begin
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do
begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := GetActiveWindow();
lpVerb := PAnsiChar(Action);
lpParameters := PChar(Params);
lpFile := PChar(FileName);
nShow := Show;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
end;
if ShellExecuteEx(#exInfo) then
begin
Ph := exInfo.HProcess;
CloseHandle(Ph);
end;
end;
procedure ShellExecute_AndWait(FileName : string; Params : string = ''; Action : string = 'open'; Show : Integer = SW_SHOWNORMAL; WorkingDir : string = '');
var
exInfo: TShellExecuteInfo;
Ph: DWORD;
begin
FillChar(exInfo, SizeOf(exInfo), 0);
with exInfo do
begin
cbSize := SizeOf(exInfo);
fMask := SEE_MASK_NOCLOSEPROCESS or SEE_MASK_FLAG_DDEWAIT;
Wnd := GetActiveWindow();
lpVerb := PAnsiChar(Action);
lpParameters := PChar(Params);
lpFile := PChar(FileName);
nShow := Show;
if WorkingDir <> '' then
begin
lpDirectory := PAnsiChar(WorkingDir);
end else
begin
lpDirectory := PAnsiChar(ExtractFilePath(FileName));
end;
end;
if ShellExecuteEx(#exInfo) then
begin
Ph := exInfo.HProcess;
while WaitForSingleObject(ExInfo.hProcess, 50) <> WAIT_OBJECT_0 do
begin
Application.ProcessMessages;
end;
CloseHandle(Ph);
end;
end;
Task from your boss is not fully correct. Problem is that the generic solution of ShellExecute – is not start cmd.exe, this command starts an application that is linked to this type of file and starts it. So, to make it work like you want – it needs a lot of work.
One more thing – do you need to get the result of work of your program or console output of your program?
Here is modified part of sources from jcl library to return return code:
function PCharOrNil(const S: string): PChar;
begin
Result := Pointer(S);
end;
// memory initialization
procedure ResetMemory(out P; Size: Longint);
begin
if Size > 0 then
begin
Byte(P) := 0;
FillChar(P, Size, 0);
end;
end;
function ShellExecAndWait(const FileName: string; const Parameters: string;
const Verb: string; CmdShow: Integer; const Directory: string): cardinal;
var
Sei: TShellExecuteInfo;
Res: LongBool;
Msg: tagMSG;
ShellResult : boolean;
begin
ResetMemory(Sei, SizeOf(Sei));
Sei.cbSize := SizeOf(Sei);
Sei.fMask := SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS or
SEE_MASK_FLAG_DDEWAIT or SEE_MASK_NOASYNC;
Sei.lpFile := PChar(FileName);
Sei.lpParameters := PCharOrNil(Parameters);
Sei.lpVerb := PCharOrNil(Verb);
Sei.nShow := CmdShow;
Sei.lpDirectory := PCharOrNil(Directory);
{$TYPEDADDRESS ON}
ShellResult := ShellExecuteEx(#Sei);
{$IFNDEF TYPEDADDRESS_ON}
{$TYPEDADDRESS OFF}
{$ENDIF ~TYPEDADDRESS_ON}
if ShellResult then begin
WaitForInputIdle(Sei.hProcess, INFINITE);
while WaitForSingleObject(Sei.hProcess, 10) = WAIT_TIMEOUT do
repeat
Msg.hwnd := 0;
Res := PeekMessage(Msg, Sei.Wnd, 0, 0, PM_REMOVE);
if Res then
begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
until not Res;
if not GetExitCodeProcess(Sei.hProcess, Result) then
raise Exception.Create('GetExitCodeProcess fail');
CloseHandle(Sei.hProcess);
end else begin
raise Exception.Create('ShellExecuteEx fail');
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
xResult : cardinal;
begin
xResult := ShellExecAndWait('ping.exe', '', '', 1, ''); //xResult = 1
xResult := ShellExecAndWait('ping.exe', '8.8.8.8', '', 1, ''); //xResult = 0
end;
If you need to specify input/output pipes (to control stdin and stdout of the called process) then ShellExecute cannot be used. It simply does not support specifying these. Neither does ShellExecuteEx.
So the only option you have if you must use ShellExecute is to ShellExecute the command processor (CMD.EXE) and ask it to perform the redirection of input and output. This will limit your redirection source and target to physical files on the disk, as that's the way CMD.EXE allows redirection (>StdOut <StdIn).
Othwewise, your approach with CreateProcess is the way forward. What does your boss give as reason that you must use ShellExecute?
If you don't need redirection support, you can use ShellExecuteEx and then after a successful execution, you can obtain the Handle to the running process in Info.hProcess (Info is the TShellExecuteInfo structure passed to ShellExecuteEx).
This value can then be used in GetExitCodeProcess to determine if the process is still running, or if it has terminated (and you have thus retrieved the "Return Value", if I have correctly understood your use of this expression - it's actually called an "ExitCode", or - in batch files - an "ERRORLEVEL").
Incomplete code:
FUNCTION ShellExecuteAndWait(....) : DWORD;
.
.
VAR Info : TShellExecuteInfo;
.
.
Info.fMask:=Info.fMask OR SEE_MASK_NOCLOSEPROCESS;
IF NOT ShellExecuteEx(Info) THEN EXIT($FFFF8000);
IF Info.hProcess=0 THEN EXIT($FFFF0000);
REPEAT
IF NOT GetExitCodeProcess(Info.hProcess,Result) THEN EXIT($FFFFFFFF)
UNTIL Result<>STILL_ACTIVE
.
.
The above code should demonstrate how to do this...

Calculating window handle from module file path gets wrong results

In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I am trying to get the window handle of a running main task from the task's module path:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = 256;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_ALL_ACCESS, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
if Len > 0 then
begin
SetLength(WinFileName, Len);
if SameText(WinFileName, FindWindowRec.ModuleToFind) then
begin
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
var
FindWindowRec: TFindWindowRec;
function TformMain.GetmainWindowHandleFRomProcessPath(aProcessPath: string): HWND;
begin
Result := 0;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: aProcessPath', aProcessPath);
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, Integer(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
CodeSite.Send('TformMain.GetmainWindowHandleFRomProcessPath: Result', Result);
end;
end;
When I do this with:
GetmainWindowHandleFRomProcessPath('c:\windows\system32\notepad.exe');
... then I get the correct window handle.
When I do this with:
GetmainWindowHandleFRomProcessPath('C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe');
... then I get a WRONG (non-existing) window handle!
Why is this happening? How do I get the correct window handle?
The discussion with Remy and Andreas lead me to this successful working answer:
type
TFindWindowRec = record
ModuleToFind: string;
FoundHWnd: HWND;
end;
// The `RzShellUtils` unit is from Ray Konopka's Signature Library available from GetIt:
function PathsAreSamePIDL(const Path1, Path2: string): Boolean;
begin
var AIL1: PItemIdList;
var AIL2: PItemIdList;
RzShellUtils.ShellGetIdListFromPath(Path1, AIL1);
RzShellUtils.ShellGetIdListFromPath(Path2, AIL2);
var CompResult:= RzShellUtils.CompareAbsIdLists(AIL1, AIL2);
Result := CompResult = 0;
end;
function EnumWindowsCallBack(aHandle: HWND; var FindWindowRec: TFindWindowRec): BOOL; stdcall;
const
C_FileNameLength = MAX_PATH;
PROCESS_QUERY_LIMITED_INFORMATION = $1000;
var
WinFileName: string;
PID, hProcess: DWORD;
Len: Byte;
begin
Result := True;
SetLength(WinFileName, C_FileNameLength);
GetWindowThreadProcessId(aHandle, PID);
hProcess := OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, PID);
Len := GetModuleFileNameEx(hProcess, 0, PChar(WinFileName), C_FileNameLength);
CloseHandle(hProcess);
if Len > 0 then
begin
SetLength(WinFileName, Len);
//if SameText(WinFileName, FindWindowRec.ModuleToFind) then
if PathsAreSamePIDL(WinFileName, FindWindowRec.ModuleToFind) then
begin
var IsVisible := IsWindowVisible(aHandle);
if not IsVisible then EXIT;
var IsOwned := GetWindow(aHandle, GW_OWNER) <> 0;
if IsOwned then EXIT;
var IsAppWindow := GetWindowLongPtr(aHandle, GWL_EXSTYLE) and WS_EX_APPWINDOW <> 0;
if not IsAppWindow then EXIT;
Result := False;
FindWindowRec.FoundHWnd := aHandle;
end;
end;
end;
function TformMain.GetMainWindowHandleFromProcessPath(aProcessPath: string): HWND;
var
FindWindowRec: TFindWindowRec;
begin
Result := 0;
FindWindowRec.ModuleToFind := aProcessPath;
FindWindowRec.FoundHWnd := 0;
EnumWindows(#EnumWindowsCallback, LPARAM(#FindWindowRec));
if FindWindowRec.FoundHWnd <> 0 then
begin
Result := FindWindowRec.FoundHWnd;
end;
end;
I don't understand why the person who moved the discussion to another page deleted the latest comments. Was there anything forbidden in those deleted comments?
Again: Thank you to Remy and Andreas!

SendKeys from WM_HOTKEY handler

Have AppActivate and SendKeys functions.
When use: AppActivate('*WordPad'); SendKeys('Test");
this works fine - application activated and text pasted
but then use it from WM_HOTKEY handler from the same program,
this is not worked.
Any ideas?
procedure wm_hotkeyhandler(var Msg: TWMHotkey); message WM_HOTKEY;
...
procedure TFormMain.wm_hotkeyhandler(var Msg: TWMHotkey);
var
Pt: TPoint;
begin
inherited;
if (Msg.HotKey = HotKeyId_L) then SendKeys('Test'); // not pasted to active app
if (Msg.HotKey = HotKeyId_M) then begin
// invoke context menu and paste text after click to menu item, works fine
GetCursorPos(Pt);
popPaste.Popup(Pt.x, Pt.y);
end;
end;
Update 1:
// this code works fine
procedure TFormTest.btnAppActivateClick(Sender: TObject);
var
sTitle, sKeys: string;
begin
sTitle := '*WordPad';
sKeys := 'Hello{ENTER}World!';
AppActivate(sTitle);
SendKeys(PChar(sKeys), False);
end;
menu item handler (which invoked by hotkey HotKeyId_M):
procedure TFormMain.mnPasteLoginClick(Sender: TObject);
begin
SendKeys('Hello{ENTER}World!', False);
end;
hotkeys:
HotKeyId_L: Integer;
HotKeyId_M: Integer;
initialization of hotkeys:
HotKeyId_L := GlobalAddAtom('HotKeyL');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL + MOD_ALT, Byte('L'));
HotKeyId_M := GlobalAddAtom('HotKeyM');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL + MOD_ALT, Byte('M'));
Update 2: (full code for test)
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Menus;
type
TForm2 = class(TForm)
btnActivate: TButton;
popPopup: TPopupMenu;
Paste1: TMenuItem;
procedure btnActivateClick(Sender: TObject);
procedure Paste1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
HotKeyId_L: Integer;
HotKeyId_M: Integer;
procedure wm_hotkeyhandler(var Msg: TWMHotkey); message WM_HOTKEY;
public
{ Public declarations }
end;
var
Form2: TForm2;
implementation
{$R *.DFM}
type
TCompareDirection = (cdHead, cdTail, cdNone);
TWindowObj = class(TObject)
private
targetTitle : PChar;
compareLength: Integer;
FCompareDirection: TCompareDirection;
FWindowHandle: THandle;
public
constructor Create;
destructor Destroy; override;
function Equal(ATitle: PChar): Boolean;
function SetTitle(const Title: string ): Boolean;
property WindowHandle: THandle read FWindowHandle write FWindowHandle;
end;
function EnumWindowsProc(hWnd: HWND; lParam: LPARAM):Bool; export; stdcall;
var
WinObj: TWindowObj;
aWndName: array[0..MAX_PATH] of Char;
begin
Result := True;
WinObj := TWindowObj(lParam);
GetWindowText(hWnd, aWndName, MAX_PATH);
if WinObj.Equal(aWndName) then begin
WinObj.WindowHandle := hWnd;
Result := False; // Stop Enumerate
end;
end;
function GetWindowHandleByTitle(const Title: string): THandle;
var
WinObj: TWindowObj;
begin
Result := 0;
WinObj := TWindowObj.Create;
try
if WinObj.SetTitle(Title) then begin
EnumWindows(#EnumWindowsProc, Integer(WinObj));
Result := WinObj.WindowHandle;
end;
finally
WinObj.Free;
end;
end;
function AppActivate(const Title: string ): Boolean;
var
hWnd: THandle;
begin
hWnd := GetWindowHandleByTitle(Title);
Result := (hWnd > 0);
if Result then begin
SendMessage(hWnd, WM_SYSCOMMAND, SC_HOTKEY, hWnd);
SendMessage(hWnd, WM_SYSCOMMAND, SC_RESTORE, hWnd);
SetForegroundWindow(hWnd);
end;
end;
constructor TWindowObj.Create;
begin
TargetTitle := nil;
FWindowHandle := 0;
end;
destructor TWindowObj.Destroy;
begin
inherited Destroy;
if Assigned(TargetTitle) then
StrDispose(TargetTitle) ;
end;
function TWindowObj.Equal(ATitle: PChar): Boolean;
var
p : Pchar;
stringLength : integer;
begin
Result := False;
if (TargetTitle = nil) then
Exit;
case FCompareDirection of
cdHead: begin
if StrLIComp(ATitle, TargetTitle, compareLength) = 0 then
Result := True;
end;
cdTail: begin
stringLength := StrLen(ATitle);
p := #ATitle[stringLength - compareLength];
if (StrLIComp(p, Targettitle, compareLength) = 0) then
Result := True;
end;
cdNone: begin
Result := True;
end;
end;
end;
function TWindowObj.SetTitle(const Title: string ): Boolean;
var
pTitle, p: PChar;
begin
Result := False;
pTitle := StrAlloc(Length(Title) + 1);
StrPCopy(pTitle, Title);
p := StrScan(pTitle, '*');
if Assigned(p) then begin
if StrLen(pTitle) = 1 then begin {full matching }
FCompareDirection := cdNone;
compareLength := 0;
TargetTitle := nil;
StrDispose(pTitle);
end
else
if (p = pTitle) then begin {tail matching }
Inc(p);
if StrScan(p, '*') <> nil then begin
{MessageDlg( 'Please 1 wild char ', mtError, [mbOK],0 ); }
StrDispose( pTitle);
TargetTitle := nil;
FCompareDirection := cdNone;
Comparelength := 0;
exit;
end;
FCompareDirection := cdTail;
CompareLength := StrLen(PTitle) - 1;
TargetTitle := StrAlloc(StrLen(p) + 1 );
StrCopy(targetTitle, p);
StrDispose(PTitle);
end
else begin
p^ := #0;
FCompareDirection := cdHead;
CompareLength := Strlen( pTitle );
Targettitle := pTitle;
end;
end
else begin
FCompareDirection := cdHead;
compareLength := Strlen( pTitle );
TargetTitle := pTitle;
end;
Result := True;
end;
//========================================
// SendKeys
//
// Converts a string of characters and key names to keyboard events and passes them to Windows.
//
// Example syntax:
// SendKeys('abc123{left}{left}{left}def{end}456{left 6}ghi{end}789', True);
function SendKeys(SendStr: PChar; Wait: Boolean): Boolean;
type
WBytes = array[0..pred(SizeOf(Word))] of Byte;
TSendKey = record
Name : ShortString;
VKey : Byte;
end;
const
// Array of keys that SendKeys recognizes.
// If you add to this list, you must be sure to keep it sorted alphabetically
// by Name because a binary search routine is used to scan it.}
MaxSendKeyRecs = 41;
SendKeyRecs : array[1..MaxSendKeyRecs] of TSendKey = (
(Name:'BACKSPACE'; VKey:VK_BACK),
(Name:'BKSP'; VKey:VK_BACK),
(Name:'BREAK'; VKey:VK_CANCEL),
(Name:'BS'; VKey:VK_BACK),
(Name:'CAPSLOCK'; VKey:VK_CAPITAL),
(Name:'CLEAR'; VKey:VK_CLEAR),
(Name:'DEL'; VKey:VK_DELETE),
(Name:'DELETE'; VKey:VK_DELETE),
(Name:'DOWN'; VKey:VK_DOWN),
(Name:'END'; VKey:VK_END),
(Name:'ENTER'; VKey:VK_RETURN),
(Name:'ESC'; VKey:VK_ESCAPE),
(Name:'ESCAPE'; VKey:VK_ESCAPE),
(Name:'F1'; VKey:VK_F1),
(Name:'F10'; VKey:VK_F10),
(Name:'F11'; VKey:VK_F11),
(Name:'F12'; VKey:VK_F12),
(Name:'F13'; VKey:VK_F13),
(Name:'F14'; VKey:VK_F14),
(Name:'F15'; VKey:VK_F15),
(Name:'F16'; VKey:VK_F16),
(Name:'F2'; VKey:VK_F2),
(Name:'F3'; VKey:VK_F3),
(Name:'F4'; VKey:VK_F4),
(Name:'F5'; VKey:VK_F5),
(Name:'F6'; VKey:VK_F6),
(Name:'F7'; VKey:VK_F7),
(Name:'F8'; VKey:VK_F8),
(Name:'F9'; VKey:VK_F9),
(Name:'HELP'; VKey:VK_HELP),
(Name:'HOME'; VKey:VK_HOME),
(Name:'INS'; VKey:VK_INSERT),
(Name:'LEFT'; VKey:VK_LEFT),
(Name:'NUMLOCK'; VKey:VK_NUMLOCK),
(Name:'PGDN'; VKey:VK_NEXT),
(Name:'PGUP'; VKey:VK_PRIOR),
(Name:'PRTSC'; VKey:VK_PRINT),
(Name:'RIGHT'; VKey:VK_RIGHT),
(Name:'SCROLLLOCK'; VKey:VK_SCROLL),
(Name:'TAB'; VKey:VK_TAB),
(Name:'UP'; VKey:VK_UP)
);
{Extra VK constants missing from Delphi's Windows API interface}
VK_NULL=0;
VK_SemiColon=186;
VK_Equal=187;
VK_Comma=188;
VK_Minus=189;
VK_Period=190;
VK_Slash=191;
VK_BackQuote=192;
VK_LeftBracket=219;
VK_BackSlash=220;
VK_RightBracket=221;
VK_Quote=222;
VK_Last=VK_Quote;
ExtendedVKeys : set of byte =
[VK_Up,
VK_Down,
VK_Left,
VK_Right,
VK_Home,
VK_End,
VK_Prior, {PgUp}
VK_Next, {PgDn}
VK_Insert,
VK_Delete];
const
INVALIDKEY = $FFFF {Unsigned -1};
VKKEYSCANSHIFTON = $01;
VKKEYSCANCTRLON = $02;
VKKEYSCANALTON = $04;
UNITNAME = 'SendKeys';
var
UsingParens, ShiftDown, ControlDown, AltDown, FoundClose : Boolean;
PosSpace : Byte;
I, L : Integer;
NumTimes, MKey : Word;
KeyString : String[20];
procedure DisplayMessage(Msg: PChar);
begin
MessageBox(0, Msg, UNITNAME, 0);
end;
function BitSet(BitTable, BitMask: Byte): Boolean;
begin
Result := ByteBool(BitTable and BitMask);
end;
procedure SetBit(var BitTable : Byte; BitMask : Byte);
begin
BitTable:=BitTable or Bitmask;
end;
procedure KeyboardEvent(VKey, ScanCode: Byte; Flags: DWORD);
var
KeyboardMsg : TMsg;
begin
keybd_event(VKey, ScanCode, Flags, 0);
if Wait then
while PeekMessage(KeyboardMsg, 0, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE) do begin
TranslateMessage(KeyboardMsg);
DispatchMessage(KeyboardMsg);
end;
end;
procedure SendKeyDown(VKey: Byte; NumTimes: Word; GenUpMsg: Boolean);
var
Cnt: Word;
ScanCode: Byte;
NumState: Boolean;
KeyBoardState: TKeyboardState;
begin
if (VKey = VK_NUMLOCK) then begin
NumState := ByteBool(GetKeyState(VK_NUMLOCK) and 1);
GetKeyBoardState(KeyBoardState);
if NumState then
KeyBoardState[VK_NUMLOCK] := (KeyBoardState[VK_NUMLOCK] and not 1)
else
KeyBoardState[VK_NUMLOCK] := (KeyBoardState[VK_NUMLOCK] or 1);
SetKeyBoardState(KeyBoardState);
Exit;
end;
ScanCode := Lo(MapVirtualKey(VKey, 0));
for Cnt := 1 to NumTimes do
if (VKey in ExtendedVKeys) then begin
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY);
if GenUpMsg then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY or KEYEVENTF_KEYUP)
end
else begin
KeyboardEvent(VKey, ScanCode, 0);
if GenUpMsg then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
end;
end;
procedure SendKeyUp(VKey: Byte);
var
ScanCode : Byte;
begin
ScanCode := Lo(MapVirtualKey(VKey, 0));
if (VKey in ExtendedVKeys)then
KeyboardEvent(VKey, ScanCode, KEYEVENTF_EXTENDEDKEY and KEYEVENTF_KEYUP)
else
KeyboardEvent(VKey, ScanCode, KEYEVENTF_KEYUP);
end;
procedure SendKey(MKey: Word; NumTimes: Word; GenDownMsg: Boolean);
begin
if BitSet(Hi(MKey), VKKEYSCANSHIFTON) then SendKeyDown(VK_SHIFT, 1, False);
if BitSet(Hi(MKey), VKKEYSCANCTRLON) then SendKeyDown(VK_CONTROL, 1, False);
if BitSet(Hi(MKey), VKKEYSCANALTON) then SendKeyDown(VK_MENU, 1, False);
SendKeyDown(Lo(MKey), NumTimes, GenDownMsg);
if BitSet(Hi(MKey), VKKEYSCANSHIFTON) then SendKeyUp(VK_SHIFT);
if BitSet(Hi(MKey), VKKEYSCANCTRLON) then SendKeyUp(VK_CONTROL);
if BitSet(Hi(MKey), VKKEYSCANALTON) then SendKeyUp(VK_MENU);
end;
// Implements a simple binary search to locate special key name strings
function StringToVKey(KeyString: ShortString): Word;
var
Found, Collided : Boolean;
Bottom, Top, Middle : Byte;
begin
Result := INVALIDKEY;
Bottom := 1;
Top := MaxSendKeyRecs;
Found := False;
Middle := (Bottom + Top) div 2;
repeat
Collided:=((Bottom=Middle) or (Top=Middle));
if (KeyString=SendKeyRecs[Middle].Name) then begin
Found:=True;
Result:=SendKeyRecs[Middle].VKey;
end
else begin
if (KeyString>SendKeyRecs[Middle].Name) then
Bottom:=Middle
else
Top:=Middle;
Middle:=(Succ(Bottom+Top)) div 2;
end;
until (Found or Collided);
if (Result = INVALIDKEY) then
DisplayMessage('Invalid Key Name');
end;
procedure PopUpShiftKeys;
begin
if (not UsingParens) then begin
if ShiftDown then SendKeyUp(VK_SHIFT);
if ControlDown then SendKeyUp(VK_CONTROL);
if AltDown then SendKeyUp(VK_MENU);
ShiftDown := False;
ControlDown := False;
AltDown := False;
end;
end;
var
AllocationSize : integer;
begin
AllocationSize := MaxInt;
Result := False;
UsingParens := False;
ShiftDown := False;
ControlDown := False;
AltDown := False;
I := 0;
L := StrLen(SendStr);
if (L > AllocationSize) then
L := AllocationSize;
if (L = 0) then
Exit;
while (I < L) do begin
case SendStr[I] of
'(': begin
UsingParens := True;
Inc(I);
end;
')': begin
UsingParens := False;
PopUpShiftKeys;
Inc(I);
end;
'%': begin
AltDown := True;
SendKeyDown(VK_MENU, 1, False);
Inc(I);
end;
'+': begin
ShiftDown := True;
SendKeyDown(VK_SHIFT, 1, False);
Inc(I);
end;
'^': begin
ControlDown := True;
SendKeyDown(VK_CONTROL, 1, False);
Inc(I);
end;
'{': begin
NumTimes := 1;
if (SendStr[Succ(I)] = '{') then begin
MKey := VK_LEFTBRACKET;
SetBit(WBytes(MKey)[1], VKKEYSCANSHIFTON);
SendKey(MKey, 1, True);
PopUpShiftKeys;
Inc(I, 3);
Continue;
end;
KeyString := '';
FoundClose := False;
while (I <= L) do begin
Inc(I);
if (SendStr[I] = '}') then begin
FoundClose := True;
Inc(I);
Break;
end;
KeyString := KeyString + Upcase(SendStr[I]);
end;
if Not FoundClose then begin
DisplayMessage('No Close');
Exit;
end;
if (SendStr[I] = '}') then begin
MKey := VK_RIGHTBRACKET;
SetBit(Wbytes(MKey)[1], VKKEYSCANSHIFTON);
SendKey(MKey, 1, True);
PopUpShiftKeys;
Inc(I);
Continue;
end;
PosSpace:=Pos(' ', KeyString);
if (PosSpace <> 0) then begin
NumTimes := StrToInt(Copy(KeyString, Succ(PosSpace), Length(KeyString) - PosSpace));
KeyString := Copy(KeyString, 1, Pred(PosSpace));
end;
If (Length(KeyString)=1) then
MKey := vkKeyScan(KeyString[1])
else
MKey := StringToVKey(KeyString);
If (MKey <> INVALIDKEY) then begin
SendKey(MKey, NumTimes, True);
PopUpShiftKeys;
Continue;
end;
end;
'~': begin
SendKeyDown(VK_RETURN, 1, True);
PopUpShiftKeys;
Inc(I);
end;
else
MKey := vkKeyScan(SendStr[I]);
if (MKey <> INVALIDKEY) then begin
SendKey(MKey, 1, True);
PopUpShiftKeys;
end
else
DisplayMessage('Invalid KeyName');
Inc(I);
end;
end;
Result := True;
PopUpShiftKeys;
end;
procedure TForm2.btnActivateClick(Sender: TObject);
var
sTitle, sKeys: string;
begin
sTitle := '*WordPad';
sKeys := 'Hello{ENTER}World!';
AppActivate(sTitle);
SendKeys(PChar(sKeys), False);
end;
procedure TForm2.Paste1Click(Sender: TObject);
begin
SendKeys('Hello{ENTER}World!', False);
end;
procedure TForm2.FormCreate(Sender: TObject);
begin
HotKeyId_L := GlobalAddAtom('HotKeyP');
RegisterHotKey(Handle, HotKeyId_L, MOD_CONTROL or MOD_ALT, Byte('L'));
HotKeyId_M := GlobalAddAtom('HotKeyM');
RegisterHotKey(Handle, HotKeyId_M, MOD_CONTROL or MOD_ALT, Byte('M'));
end;
procedure TForm2.FormDestroy(Sender: TObject);
begin
UnregisterHotKey(Handle, HotKeyId_L);
GlobalDeleteAtom(HotKeyId_L);
end;
procedure TForm2.wm_hotkeyhandler(var Msg: TWMHotkey);
var
Pt: TPoint;
begin
inherited;
if (Msg.HotKey = HotKeyId_L) then SendKeys('Hello{ENTER}World!', False);
if (Msg.HotKey = HotKeyId_M) then begin
GetCursorPos(Pt);
popPopup.Popup(Pt.x, Pt.y);
end;
end;
end.

Delphi - File size by URL - error 12150

Delphi 6 Prof, Win7 OS.
I have this code to get the file size by URL:
function TDDWIToolObject.GetFileSize(out Size: Int64): boolean;
var
hInet: HINTERNET;
hRequest : HINTERNET;
lpdwBufferLength: DWORD;
lpdwReserved : DWORD;
ServerName: string;
Resource: string;
FileSizeBuffer : array[0..32] of char;
//SizeCard : Cardinal;
begin
ParseURL(Url, ServerName, Resource);
Result := False;
Size := 0;
hInet := InternetOpen(PChar(_UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if hInet=nil then begin
FErrorCode := GetLastError;
Exit;
end;
try
hRequest := InternetOpenUrl(hInet, PChar(URL), PCHar(Headers), Length(HEaders), 0, 0);
try
FillChar(FileSizeBuffer, SizeOf(FileSizeBuffer), #0);
lpdwBufferLength := SizeOf(FileSizeBuffer);
lpdwReserved :=0;
if not HttpQueryInfo(
hRequest,
HTTP_QUERY_CONTENT_LENGTH,
#FileSizeBuffer, lpdwBufferLength, lpdwReserved) then begin
FErrorCode:=GetLastError;
Exit;
end;
Size := StrToInt64(StrPas(FileSizeBuffer));
Result := True;
finally
InternetCloseHandle(hRequest);
end;
finally
InternetCloseHandle(hInet);
end;
end;
It is working good with my machine + DSL connection.
But when I check this code in other place which is fully protected (proxy + many policies) then I get error code 12150... :-(
What is interesting, that I can download this file, when I use this code:
function TDDWIToolObject.DownloadFile;
var
hInet: HINTERNET;
hFile: HINTERNET;
pbuffer: Pointer;
bytesRead: DWORD;
Stm : TFileStream;
TotalBytes : Int64;
AbortIt : boolean;
begin
Result := False;
FErrorCode := -1;
FAborted := False;
hInet := InternetOpen(PChar(_UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if hInet = nil
then begin
FErrorCode := GetLastError;
Exit;
end;
try
hFile := InternetOpenURL(hInet, PChar(URL), PChar(FHeaders), Length(FHeaders), 0, 0);
if hFile = nil
then begin
FErrorCode := GetLastError;
Exit;
end;
try
Stm := TFileStream.Create(FN, fmCreate);
try
GetMem(pbuffer, FBufferSize);
try
TotalBytes := 0; AbortIt := False;
while (not FAborted) do begin
if not InternetReadFile(hFile, pbuffer, FBufferSize, bytesRead) then begin
FErrorCode := GetLastError;
Exit;
end;
if bytesRead > 0 then begin
Stm.WriteBuffer(pbuffer^, bytesRead);
if Assigned(FOnBytesArrived)
then begin
inc(TotalBytes, bytesRead);
FOnBytesArrived(Self, TotalBytes, AbortIt);
if AbortIt
then Abort;
end;
end else begin
break;
end;
end;
finally
FreeMem(pbuffer);
end;
if not FAborted
then Result := True;
finally
Stm.Free;
end;
finally
InternetCloseHandle(hFile);
end;
finally
InternetCloseHandle(hInet);
end;
end;
It is interesting because sysadmin allowed the requests to this URL in proxy, and the downloads work for me - only the content length requests fail.
I want to ask one question, but it is hard to do, because many of them is in my mind...
Do you have some idea what we can do to allow the requests?
Maybe HttpRequest uses another port, or some "illegal" what isn't proxiable?
Or my code is seems to be wrong? Why it is working in nonprotected area? What are the differents between download and get content length requests?
So the question is as in summarized format: do you have some idea what can I do to I get the file size before downloading?
Thanks for it.
This error can be caused by many factors (ERROR_HTTP_HEADER_NOT_FOUND), in your code you are not checking the result of the InternetOpenUrl function before to call the HttpQueryInfo method, and I don't see the content of the _UserAgent and Headers variables to give you a more exact answer. Anyway try using the HttpOpenRequest and HttpSendRequest functions instead of InternetOpenURL.
Try this sample
uses
Windows,
SysUtils,
WinInet;
const
sUserAgent = 'Mozilla/5.001 (windows; U; NT4.0; en-US; rv:1.0) Gecko/25250101';
function GetWinInetError(ErrorCode:Cardinal): string;
const
winetdll = 'wininet.dll';
var
Len: Integer;
Buffer: PChar;
begin
Len := FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE or FORMAT_MESSAGE_FROM_SYSTEM or
FORMAT_MESSAGE_ALLOCATE_BUFFER or FORMAT_MESSAGE_IGNORE_INSERTS or FORMAT_MESSAGE_ARGUMENT_ARRAY,
Pointer(GetModuleHandle(winetdll)), ErrorCode, 0, #Buffer, SizeOf(Buffer), nil);
try
while (Len > 0) and {$IFDEF UNICODE}(CharInSet(Buffer[Len - 1], [#0..#32, '.'])) {$ELSE}(Buffer[Len - 1] in [#0..#32, '.']) {$ENDIF} do Dec(Len);
SetString(Result, Buffer, Len);
finally
LocalFree(HLOCAL(Buffer));
end;
end;
procedure ParseURL(const lpszUrl: string; var Host, Resource: string);
var
lpszScheme : array[0..INTERNET_MAX_SCHEME_LENGTH - 1] of Char;
lpszHostName : array[0..INTERNET_MAX_HOST_NAME_LENGTH - 1] of Char;
lpszUserName : array[0..INTERNET_MAX_USER_NAME_LENGTH - 1] of Char;
lpszPassword : array[0..INTERNET_MAX_PASSWORD_LENGTH - 1] of Char;
lpszUrlPath : array[0..INTERNET_MAX_PATH_LENGTH - 1] of Char;
lpszExtraInfo : array[0..1024 - 1] of Char;
lpUrlComponents : TURLComponents;
begin
ZeroMemory(#lpszScheme, SizeOf(lpszScheme));
ZeroMemory(#lpszHostName, SizeOf(lpszHostName));
ZeroMemory(#lpszUserName, SizeOf(lpszUserName));
ZeroMemory(#lpszPassword, SizeOf(lpszPassword));
ZeroMemory(#lpszUrlPath, SizeOf(lpszUrlPath));
ZeroMemory(#lpszExtraInfo, SizeOf(lpszExtraInfo));
ZeroMemory(#lpUrlComponents, SizeOf(TURLComponents));
lpUrlComponents.dwStructSize := SizeOf(TURLComponents);
lpUrlComponents.lpszScheme := lpszScheme;
lpUrlComponents.dwSchemeLength := SizeOf(lpszScheme);
lpUrlComponents.lpszHostName := lpszHostName;
lpUrlComponents.dwHostNameLength := SizeOf(lpszHostName);
lpUrlComponents.lpszUserName := lpszUserName;
lpUrlComponents.dwUserNameLength := SizeOf(lpszUserName);
lpUrlComponents.lpszPassword := lpszPassword;
lpUrlComponents.dwPasswordLength := SizeOf(lpszPassword);
lpUrlComponents.lpszUrlPath := lpszUrlPath;
lpUrlComponents.dwUrlPathLength := SizeOf(lpszUrlPath);
lpUrlComponents.lpszExtraInfo := lpszExtraInfo;
lpUrlComponents.dwExtraInfoLength := SizeOf(lpszExtraInfo);
InternetCrackUrl(PChar(lpszUrl), Length(lpszUrl), ICU_DECODE or ICU_ESCAPE, lpUrlComponents);
Host := lpszHostName;
Resource := lpszUrlPath;
end;
function GetRemoteFileSize(const Url : string): Integer;
var
hInet : HINTERNET;
hConnect : HINTERNET;
hRequest : HINTERNET;
lpdwBufferLength: DWORD;
lpdwReserved : DWORD;
ServerName: string;
Resource: string;
ErrorCode : Cardinal;
begin
ParseURL(Url,ServerName,Resource);
Result:=0;
hInet := InternetOpen(PChar(sUserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
if hInet=nil then
begin
ErrorCode:=GetLastError;
raise Exception.Create(Format('InternetOpen Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
end;
try
hConnect := InternetConnect(hInet, PChar(ServerName), INTERNET_DEFAULT_HTTP_PORT, nil, nil, INTERNET_SERVICE_HTTP, 0, 0);
if hConnect=nil then
begin
ErrorCode:=GetLastError;
raise Exception.Create(Format('InternetConnect Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
end;
try
hRequest := HttpOpenRequest(hConnect, PChar('HEAD'), PChar(Resource), nil, nil, nil, 0, 0);
if hRequest<>nil then
begin
try
lpdwBufferLength:=SizeOf(Result);
lpdwReserved :=0;
if not HttpSendRequest(hRequest, nil, 0, nil, 0) then
begin
ErrorCode:=GetLastError;
raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
end;
if not HttpQueryInfo(hRequest, HTTP_QUERY_CONTENT_LENGTH or HTTP_QUERY_FLAG_NUMBER, #Result, lpdwBufferLength, lpdwReserved) then
begin
Result:=0;
ErrorCode:=GetLastError;
raise Exception.Create(Format('HttpQueryInfo Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
end;
finally
InternetCloseHandle(hRequest);
end;
end
else
begin
ErrorCode:=GetLastError;
raise Exception.Create(Format('HttpOpenRequest Error %d Description %s',[ErrorCode,GetWinInetError(ErrorCode)]));
end;
finally
InternetCloseHandle(hConnect);
end;
finally
InternetCloseHandle(hInet);
end;
end;

Help me fix a Delphi function that counts Registry items

I have a function that counts registry items and added a new option to also retrieve the registry items names and values. Unfortunately I can't seem to understand why only the first item for each registry key is retrieved and why the values all have the same name.
Anyone see any apparent problem with the code below?
function CountRegistryItems(Root: HKEY; SubKey: string; var KeysCount: Integer;
var ValuesCount: Integer; GetValues: Boolean; const List: TStrings): Boolean;
type
TRegKeyInfo = record
NumSubKeys: Integer;
MaxSubKeyLen: Integer;
NumValues: Integer;
MaxValueLen: Integer;
MaxDataLen: Integer;
FileTime: TFileTime;
end;
var
Info: TRegKeyInfo;
i: integer;
SL: TStringList;
Status: Integer;
Key: HKEY;
Len: DWORD;
S: string;
PartialKeysCount: Integer;
PartialValuesCount: Integer;
KeyType, MaxValLen, MaxValNameLen, ValNameLen, ValLen: Cardinal;
ValName, Val: PChar;
Size: DWORD;
ValueName: string;
begin
KeysCount := 0;
ValuesCount := 0;
Result := False;
if GetValues and (List <> nil) then
List.BeginUpdate;
SL := TStringList.Create;
Try
// open current key
Status := RegOpenKeyEx(Root, PChar(SubKey), 0, KEY_READ or KEY_ENUMERATE_SUB_KEYS, Key);
if Status = ERROR_SUCCESS then
Try
// get key info
FillChar(Info, SizeOf(TRegKeyInfo), 0);
Status := RegQueryInfoKey(Key, nil, nil, nil, #Info.NumSubKeys,
#Info.MaxSubKeyLen, nil, #Info.NumValues, #Info.MaxValueLen,
#Info.MaxDataLen, nil, #Info.FileTime);
if Status = ERROR_SUCCESS then
begin
Result := True;
// NEW CODE
if GetValues and (List <> nil) then
begin
MaxValNameLen := Info.MaxValueLen*2+3;
MaxValLen := Info.MaxDataLen+1;
// Get values
GetMem(ValName, MaxValNameLen);
GetMem(Val, MaxValLen);
//if Info.NumValues <> 0 then
for i := 0 to Info.NumValues-1 do
begin
// Clear buffers
ValName^ := #0;
ValNameLen := MaxValNameLen;
if Val <> nil then
begin
Val^ := #0;
ValLen := MaxValLen;
end;
// Get value information
if RegEnumValue(Root, i, ValName, ValNameLen, nil, #KeyType,
PByte(Val), #ValLen) = ERROR_SUCCESS then
begin
//if ((KeyType = REG_SZ) or (KeyType = REG_MULTI_SZ)
//or (KeyType = REG_EXPAND_SZ)) then
begin
if ValName^ <> #0 then
List.Add(ValName + '=' + Val)
else
List.Add('Default' + '=' + Val);
end;
end;
end;
// Free buffers
//FreeMem(ValName);
//if Val <> nil then FreeMem(Val);
end;
// END NEW CODE
// enum subkeys
SetString(S, nil, Info.MaxSubKeyLen + 1);
for i := 0 to Info.NumSubKeys - 1 do
begin
Len := Info.MaxSubKeyLen + 1;
Status := RegEnumKeyEx(Key, i, PChar(S), Len, nil, nil, nil, nil);
if Status <> ERROR_SUCCESS then Continue;
SL.Add(PChar(S));
end;
end;
if Info.NumSubKeys > 0 then Inc(KeysCount, Info.NumSubKeys);
if Info.NumValues > 0 then Inc(ValuesCount, Info.NumValues);
Finally
RegCloseKey(Key);
End;
// search subkeys
if SL.Count > 0 then
begin
for i := 0 to SL.Count - 1 do
begin
Application.ProcessMessages;
PartialKeysCount := 0;
PartialValuesCount := 0;
CountRegistryItems(Root, SubKey + '\' + SL[i], PartialKeysCount,
PartialValuesCount, GetValues, List);
KeysCount := KeysCount + PartialKeysCount;
ValuesCount := ValuesCount + PartialValuesCount;
end;
end;
Finally
SL.Free;
if GetValues and (List <> nil) then
List.EndUpdate;
End;
end;
You can use TRegistry:
uses Registry;
function CountRegistryItems(Root: HKEY;
SubKey: string;
var KeysCount: Integer;
var ValuesCount: Integer;
GetValues: Boolean;
const List: TStrings): Boolean;
var
Reg : TRegistry;
KeyInfo : TRegKeyInfo;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := Root;
if Reg.OpenKey(SubKey,False) then
begin
Reg.GetKeyInfo(KeyInfo);
ValuesCount := KeyInfo.NumValues;
KeysCount := KeyInfo.NumSubKeys;
if (GetValues) and (Assigned(List)) then
begin
List.Clear;
Reg.GetValueNames(List);
end;
end;
Result := True;
finally
Reg.Free;
end;
end;
you need to use the Key variable instead of Root when calling RegEnumValue function.

Resources