Getting driver files for a particular device - delphi

I would like to know how I can get all the driver files for a particular device just like the Device Manager does?
I have the following code:
procedure TdlgMain.Test(const DeviceIndex: Integer);
var
PnPHandle: HDEVINFO;
DevData: TSPDevInfoData;
DeviceInterfaceData: TSPDeviceInterfaceData;
FunctionClassDeviceData: PSPDeviceInterfaceDetailData;
Success: LongBool;
Devn: Integer;
BytesReturned: DWORD;
SerialGUID: TGUID;
begin
ZeroMemory(#DevData, SizeOf(SP_DEVINFO_DATA));
DevData.cbSize := SizeOf(SP_DEVINFO_DATA);
ZeroMemory(#DeviceInterfaceData, SizeOf(TSPDeviceInterfaceData));
DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
if not SetupDiEnumDeviceInfo(hAllDevices,
DeviceIndex, DevData) then Exit;
SerialGUID := DevData.ClassGuid;
PnPHandle := SetupDiGetClassDevs(#SerialGUID, nil, 0, DIGCF_PRESENT or DIGCF_DEVICEINTERFACE);
if PnPHandle = Pointer(INVALID_HANDLE_VALUE) then
Exit;
Devn := 0;
repeat
DeviceInterfaceData.cbSize := SizeOf(TSPDeviceInterfaceData);
Success := SetupDiEnumDeviceInterfaces(PnPHandle, nil, SerialGUID, Devn, DeviceInterfaceData);
if Success then
begin
DevData.cbSize := SizeOf(DevData);
BytesReturned := 0;
// get size required for call
SetupDiGetDeviceInterfaceDetail(PnPHandle, #DeviceInterfaceData, nil, 0, BytesReturned, #DevData);
if (BytesReturned <> 0) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) then
begin
// allocate buffer and initialize it for call
FunctionClassDeviceData := AllocMem(BytesReturned);
FunctionClassDeviceData.cbSize := SizeOf(TSPDeviceInterfaceDetailData);
//FunctionClassDeviceData.cbSize := BytesReturned;
if SetupDiGetDeviceInterfaceDetail(PnPHandle, #DeviceInterfaceData,
FunctionClassDeviceData, BytesReturned, BytesReturned, #DevData) then
begin
ShowMessage(FunctionClassDeviceData.DevicePath);
end else
RaiseLastOSError();
FreeMem(FunctionClassDeviceData);
end;
end;
Inc(Devn);
until not Success;
SetupDiDestroyDeviceInfoList(PnPHandle);
But the ShowMessage() is either not called at all or returns \. How do I get the files properly?
I had a look at devcon from the WinDDK, but it does not return the files either.
Thank you.

I figured it out. There's no API to do it for you, you need to parse the INF files to achieve the result. Here's a quick-n-dirty solution for all of you, who are interested.
procedure TdlgMain.Test(const DeviceIndex: Integer);
var
Paths: TStringList;
I: Integer;
function GetWinDir: string; inline;
var
dir: array [0 .. MAX_PATH] of Char;
begin
GetWindowsDirectory(dir, MAX_PATH);
Result := IncludeTrailingBackslash(StrPas(dir));
end;
function GetSpecialFolderPath(const folder: Integer): string; inline;
const
SHGFP_TYPE_CURRENT = 0;
var
path: array [0 .. MAX_PATH] of Char;
begin
if SUCCEEDED(SHGetFolderPath(0, folder, 0, SHGFP_TYPE_CURRENT, #path[0]))
then
Result := IncludeTrailingBackslash(path)
else
Result := '';
end;
function LocateInfFile(const F: String): String; inline;
var
T: String;
begin
Result := '';
if (Pos(SysUtils.PathDelim, F) > 0) then
begin
Result := F;
Exit;
end;
T := GetWinDir();
if (FileExists(T + 'inf\' + F)) then
Result := T + 'inf\' + F
else if (FileExists(T + 'system32\' + F)) then
Result := T + 'system32\' + F;
end;
procedure ReadSectionNoKeys(const AFile, ASection: String;
const SL: TStringList);
var
TheFile: TStringList;
Line: String;
TrimEnd: Boolean;
Idx, Tmp: Integer;
begin
TrimEnd := False;
TheFile := TStringList.Create();
try
TheFile.LoadFromFile(AFile);
Idx := TheFile.IndexOf('[' + ASection + ']');
if (Idx <> -1) then
begin
Idx := Idx + 1;
while True do
begin
Line := Trim(TheFile[Idx]);
Inc(Idx);
if (Pos(';', Line) = 1) then
continue;
if (Pos('[', Line) > 0) then
Break;
Tmp := Pos(',', Line);
if (Tmp > 0) then
TrimEnd := True
else
begin
Tmp := PosEx(';', Line, 3);
if (Tmp > 0) then
TrimEnd := True;
end;
if (Line <> '') then
begin
if (TrimEnd) then
begin
Line := Trim(Copy(Line, 1, Tmp - 1));
TrimEnd := False;
end;
SL.Add(Line);
end;
if (Idx = (TheFile.Count - 1)) then
Break;
end;
end;
finally
TheFile.Free();
end;
end;
function IniReadStr(const Ini: TIniFile; const S, L, D: String): String;
var
T: Integer;
begin
Result := Ini.ReadString(S, L, D);
T := Pos(';', Result);
if (T > 0) then
Result := Trim(Copy(Result, 1, T - 1));
end;
procedure ParseInfFile(const InfFile, SectionName: String);
var
I: TIniFile;
SL, FilesList: TStringList;
X, Y, Tmp: Integer;
Pth, S, S1: String;
begin
I := TIniFile.Create(InfFile);
try
if (SectionName <> '') and (I.SectionExists(SectionName)) then
begin
// Check if the section has a value called "CopyFiles".
if (I.ValueExists(SectionName, 'CopyFiles')) then
begin
// It has. Read it to a string and separate by commas.
SL := TStringList.Create();
try
SL.CommaText := IniReadStr(I, SectionName, 'CopyFiles', '');
// Now, every line of the string list is a section name. Check
// the destination directory of each.
if (I.SectionExists('DestinationDirs')) then
for X := 0 to SL.Count - 1 do
begin
S := IniReadStr(I, 'DestinationDirs', SL[X], '');
if (S = '') then
S := IniReadStr(I, 'DestinationDirs', 'DefaultDestDir', '');
if (S <> '') then
begin
// Split the path by comma, if any.
Tmp := Pos(',', S);
S1 := '';
if (Tmp > 0) then
begin
S1 := Trim(Copy(S, Tmp + 1, Length(S)));
S := Trim(Copy(S, 1, Tmp - 1));
end;
// Convert the numeric value of S to a proper directory.
Pth := '';
if (S = '10') then
Pth := GetWinDir();
if (S = '11') then
Pth := GetWinDir() + 'system32\';
if (S = '12') then
Pth := GetWinDir() + 'system32\drivers\';
if (S = '50') then
Pth := GetWinDir() + 'system\';
if (S = '30') then
Pth := ExtractFileDrive(GetWinDir());
if (StrToInt(S) >= 16384) then
Pth := GetSpecialFolderPath(StrToInt(S));
if (S1 <> '') then
Pth := IncludeTrailingBackslash(Pth + S1);
// If we got the path, read the files.
if (Pth <> '') then
begin
FilesList := TStringList.Create();
try
ReadSectionNoKeys(InfFile, SL[X], FilesList);
for Y := 0 to FilesList.Count - 1 do
if (Paths.IndexOf(Pth + FilesList[Y]) = -1) then
Paths.Add(Pth + FilesList[Y]);
finally
FilesList.Free();
end;
end;
end;
end;
finally
SL.Free();
end;
end;
// Check if there're "Include" and "Needs" values.
if ((I.ValueExists(SectionName, 'Include')) and
(I.ValueExists(SectionName, 'Needs'))) then
begin
// Split both by comma.
SL := TStringList.Create();
FilesList := TStringList.Create();
try
SL.CommaText := IniReadStr(I, SectionName, 'Include', '');
FilesList.CommaText := IniReadStr(I, SectionName, 'Needs', '');
if (SL.Text <> '') and (FilesList.Text <> '') then
for X := 0 to SL.Count - 1 do
for Y := 0 to FilesList.Count - 1 do
ParseInfFile(LocateInfFile(SL[X]), FilesList[Y]);
finally
FilesList.Free();
SL.Free();
end;
end;
end;
finally
I.Free();
end;
end;
begin
Paths := TStringList.Create();
try
ParseInfFile(LocateInfFile(DeviceHelper.InfName), DeviceHelper.InfSection);
Paths.Sort();
ListView_InsertGroup(lvAdvancedInfo.Handle, 'Driver Files', 2);
for I := 0 to Paths.Count - 1 do
ListView_AddItemsInGroup(lvAdvancedInfo, '', Paths[I], 2);
finally
Paths.Free();
end;
end;

Related

How to load TTreeView items from database along with its items image index

I have saved my TreeView inside my DataBase by using the next :
var
BlobField :TField;
Query:TADOQuery;
Stream:TStream;
...
try
Query.SQL.Text := 'Select TOP(1) * From MyTable';
DBQueryConnect(Query); // I used this Procedure to connect the Query to the database
try
Query.First;
Query.Edit;
try
BlobField := Query.FieldByName('MyField') as TField;
Stream := Query.CreateBlobStream(BlobField, bmWrite);
try
MyTreeView.SaveToStream(Stream,TEncoding.UTF8);
finally
Stream.Free;
end;
Query.Post;
except
Query.Cancel;
raise;
end;
finally
Query.Close;
end;
finally
Query.Free;
end;
end;
and I loaded back the TTreeView form the DataBase by using the next :
...
var
Query:TADOQuery;
Stream:TStream;
begin
Query:=TADOQuery.Create(Self);
try
Query.SQL.Add('Select * From MyTable') ;
DBQueryConnect(Query);
Query.First;
Stream:=Query.CreateBlobStream(Query.FieldByName('MyField'), bmread);
MyTreeView.LoadFromStream(Stream);
Stream.Free;
finally
Query.Free;
end;
how can I retrive the imageindex for my TreeView items from the saved data ..
Thank you .
Perharps we can modify exsisting SaveTreeToStream and LoadTreeFromStream like this :
function GetBufStart(Buffer,idxSeparator: string; var Level,ImageIndex: Integer): string;
var
Pos: Integer;
sidx:String;
begin
Pos := 1;
Level := 0;
ImageIndex := -1;
while (CharInSet(Buffer[Pos], [' ', #9])) do
begin
Inc(Pos);
Inc(Level);
end;
Result := Copy(Buffer, Pos, Length(Buffer) - Pos + 1);
//Check Image Index
pos:=System.SysUtils.AnsiPos(idxSeparator,Result);
if Pos>0 then begin
sidx:=copy(result,Pos + Length(idxSeparator), length(result) - Pos + 1);
ImageIndex := StrToIntDef(sidx,-1);
Result := Copy(Result, 1, Pos - 1);
end;
end;
procedure LoadTreeFromStream(Nodes:TTreeNodes; Stream:TStream; Encoding:TEncoding; idxSeparator:String='|||');
var
List: TStringList;
ANode, NextNode: TTreeNode;
ALevel, i, ImageIndex: Integer;
CurrStr: string;
begin
List := TStringList.Create;
Nodes.BeginUpdate;
try
try
Nodes.Clear;
List.LoadFromStream(Stream, Encoding);
ANode := nil;
for i := 0 to List.Count - 1 do
begin
CurrStr := GetBufStart(PChar(List[i]), idxSeparator, ALevel, ImageIndex);
if ANode = nil then
ANode := Nodes.AddChild(nil, CurrStr)
else if ANode.Level = ALevel then
ANode := Nodes.AddChild(ANode.Parent, CurrStr)
else if ANode.Level = (ALevel - 1) then
ANode := Nodes.AddChild(ANode, CurrStr)
else if ANode.Level > ALevel then
begin
NextNode := ANode.Parent;
while NextNode.Level > ALevel do
NextNode := NextNode.Parent;
ANode := Nodes.AddChild(NextNode.Parent, CurrStr);
end
else raise Exception.CreateFmt('Invalid level (%d) for item "%s"', [ALevel, CurrStr]);
ANode.ImageIndex:=ImageIndex;
end;
finally
Nodes.EndUpdate;
List.Free;
end;
except
Nodes.Owner.Invalidate; // force repaint on exception
raise;
end;
end;
procedure SaveTreeToStream(Nodes:TTreeNodes; Stream:Tstream; Encoding:TEncoding; idxSeparator:String='|||');
const
TabChar = #9;
EndOfLine = #13#10;
var
I: Integer;
ANode: TTreeNode;
NodeStr: TStringBuilder;
Buffer, Preamble: TBytes;
begin
if Nodes.Count > 0 then
begin
if Encoding = nil then
Encoding := TEncoding.Default;
//Buffer := Encoding.GetBytes('');
Preamble := Encoding.GetPreamble;
if Length(Preamble) > 0 then
Stream.WriteBuffer(Preamble{$IFNDEF CLR}[0]{$ENDIF}, Length(Preamble));
NodeStr := TStringBuilder.Create(1024);
try
ANode := Nodes[0];
while ANode <> nil do
begin
NodeStr.Length := 0;
for I := 0 to ANode.Level - 1 do
NodeStr.Append(TabChar);
NodeStr.Append(ANode.Text);
NodeStr.Append(idxSeparator);
NodeStr.Append(ANode.ImageIndex);
NodeStr.Append(EndOfLine);
Buffer := Encoding.GetBytes(NodeStr.ToString);
Stream.Write(Buffer{$IFNDEF CLR}[0]{$ENDIF}, Length(Buffer));
ANode := ANode.GetNext;
end;
finally
NodeStr.Free;
end;
end;
end;
You can replace
MyTreeView.SaveToStream(Stream,TEncoding.UTF8);
with
SaveTreeToStream(MyTreeView.Items,Stream,TEncoding.UTF8);
and MyTreeView.LoadFromStream(Stream); with LoadTreeFromStream(MyTreeView.Items,Stream,TEncoding.UTF8);

Error in ShellLink creation with negative IconIndex value

In Delphi XE7, I use this code to create a SHELL LINK pointing to a specific folder. This folder is displayed in Windows Explorer with a custom folder icon defined by a desktop.ini file inside this folder. The SHELL LINK should be created with the icon parameters found in the desktop.ini file, i.e. pointing to the same icon resource as the desktop.ini file. So here is the code:
function GetDesktopIniIconDataFromFolder(const APath: string; var VIconIndex: Integer): string;
var
DeskTopIniFile: string;
DesktopIni: System.IniFiles.TIniFile;
ThisIconFileStr, ThisIconIndexStr: string;
ThisIconIndexInt: Integer;
begin
Result := '';
if DirectoryExists(APath) then
begin
DeskTopIniFile := IncludeTrailingPathDelimiter(APath) + 'Desktop.ini';
if FileExists(DeskTopIniFile) then
begin
DesktopIni := System.IniFiles.TIniFile.Create(DeskTopIniFile);
try
ThisIconFileStr := DesktopIni.ReadString('.ShellClassInfo', 'IconFile', '');
if ThisIconFileStr <> '' then
begin
ThisIconIndexStr := DesktopIni.ReadString('.ShellClassInfo', 'IconIndex', '');
if ThisIconIndexStr <> '' then
begin
ThisIconIndexInt := System.SysUtils.StrToIntDef(ThisIconIndexStr, MaxInt);
if ThisIconIndexInt <> MaxInt then
begin
Result := ThisIconFileStr;
VIconIndex := ThisIconIndexInt;
end;
end;
end;
finally
DesktopIni.Free;
end;
end;
end;
end;
function MyCreateShellLink(const LinkFileName, AssocFileName, Desc, WorkDir,
Args, IconFileName: string; const IconIdx: Integer): Boolean;
var
SL: Winapi.ShlObj.IShellLink;
PF: Winapi.ActiveX.IPersistFile;
begin
Result := False;
Winapi.ActiveX.CoInitialize(nil);
try
if Winapi.ActiveX.Succeeded(
Winapi.ActiveX.CoCreateInstance(
Winapi.ShlObj.CLSID_ShellLink,
nil,
Winapi.ActiveX.CLSCTX_INPROC_SERVER,
Winapi.ShlObj.IShellLink, SL
)
) then
begin
SL.SetPath(PChar(AssocFileName));
SL.SetDescription(PChar(Desc));
SL.SetWorkingDirectory(PChar(WorkDir));
SL.SetArguments(PChar(Args));
if (IconFileName <> '') and (IconIdx >= 0) then
SL.SetIconLocation(PChar(IconFileName), IconIdx);
PF := SL as Winapi.ActiveX.IPersistFile;
Result := Winapi.ActiveX.Succeeded(
PF.Save(PWideChar(WideString(LinkFileName)), True)
);
end;
finally
Winapi.ActiveX.CoUninitialize;
end;
end;
// Usage:
var
IconFile: string;
IconIndex: Integer;
begin
IconFile := GetDesktopIniIconDataFromFolder(APath, IconIndex);
if IconFile <> '' then
MyCreateShellLink(ALinkFileName, ATargetFileName, ADescription, AWorkDir, AArgs, IconFile, IconIndex);
This works well, EXCEPT in cases where the IconIndex in the desktop.ini file is a negative value (which means the negative value indicates a resource ID rather than an ordinal value), like in this example:
[.ShellClassInfo]
InfoTip=#Shell32.dll,-12688
IconFile=%SystemRoot%\system32\mydocs.dll
IconIndex=-101
In this case the created SHELL LINK is erroneous, which means the Shell LINK does not contain the correct icon reference.
So how can I translate the negative IconIndex value -101 from the desktop.ini file to a value I can use in the MyCreateShellLink function?
If you want to use negative IconIndex then pass FULL path of icon to SetIconLocation. Use the following variant of GetDesktopIniIconDataFromFolder:
function GetDesktopIniIconDataFromFolder(const APath: string; var AIconIndex: Integer): string;
var
Setting: TSHFolderCustomSettings;
begin
ZeroMemory(#Setting, SizeOf(Setting));
Setting.dwSize := SizeOf(Setting);
Setting.dwMask := FCSM_ICONFILE;
SetLength(Result, MAX_PATH + 1);
Setting.pszIconFile := PChar(Result);
Setting.cchIconFile := MAX_PATH;
if Succeeded(SHGetSetFolderCustomSettings(#Setting, PChar(APath), FCS_READ)) then
begin
Result := PChar(Result);
AIconIndex := Setting.iIconIndex;
end
else
Result := '';
end;
It automatically expands variables of icon path. Also it supports IconResource parameter of desktop.ini.
Variant 2 (universal)
function GetObjectIconFileName(AParentWnd: HWND; const AName: UnicodeString; var AIndex: Integer): UnicodeString;
var
Desktop: IShellFolder;
Attr: DWORD;
Eaten: DWORD;
IDList: PItemIDList;
Parent: IShellFolder;
Child: PItemIDList;
ExtractIconW: IExtractIconW;
ExtractIconA: IExtractIconA;
AnsiResult: AnsiString;
Flags: DWORD;
Ext: UnicodeString;
BuffSize: DWORD;
P: Integer;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := SFGAO_STREAM;
OleCheck(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(AName), Eaten, IDList, Attr));
try
OleCheck(SHBindToParent(IDList, IShellFolder, Pointer(Parent), Child));
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconW, nil, ExtractIconW)) then
try
SetLength(Result, MAX_PATH + 1);
if (ExtractIconW.GetIconLocation(0, PWideChar(Result), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := PWideChar(Result);
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconW := nil;
end
else
if Succeeded(Parent.GetUIObjectOf(AParentWnd, 1, Child, IExtractIconA, nil, ExtractIconA)) then
try
SetLength(AnsiResult, MAX_PATH + 1);
if (ExtractIconA.GetIconLocation(0, PAnsiChar(AnsiResult), MAX_PATH, AIndex, Flags) = S_OK) then
begin
Result := UnicodeString(PAnsiChar(AnsiResult));
if // (Flags and GIL_NOTFILENAME = 0) and // Dont know why shell return GIL_NOTFILENAME flag
FileExists(Result) then
Exit
else
Result := '';
end
else
Result := '';
finally
ExtractIconA := nil;
end;
finally
CoTaskMemFree(IDList);
end;
finally
Desktop := nil;
end;
if Attr and SFGAO_STREAM <> 0 then
begin
Ext := ExtractFileExt(AName);
if (AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, nil, #BuffSize) = S_FALSE) and (BuffSize > 1) then
begin
SetLength(Result, BuffSize - 1);
if Succeeded(AssocQueryStringW(ASSOCF_NONE, ASSOCSTR_DEFAULTICON, PWideChar(Ext), nil, PWideChar(Result), #BuffSize)) then
begin
AIndex := 0;
P := LastDelimiter(',', Result);
if P > 0 then
begin
AIndex := StrToIntDef(Copy(Result, P + 1, MaxInt), MaxInt);
if AIndex <> MaxInt then
Delete(Result, P, MaxInt)
else
AIndex := 0;
end;
Exit;
end;
end;
end;
Result := '';
end;

How can I reverse string to bin?

How can I convert bin to string?
For example:
string:='s';----------->bin:='0011';
How do I convert it reverse?
My stringtobin code is:
function StrToBinStr( aString: string ): string;
var
i : integer;
begin
for i := 1 to Length( aString ) do
result := IntToBin( byte(aString[i]), 4 );
end;
function IntToBin(aValue, Bits: integer): string;
var
i : integer;
begin
for i := Bits-1 downto 0 do
result := result + Copy( '10', Word(((1 shl i) and AValue) = 0)+1, 1 );
end;
This may help:
function IntToBin( const Value: LongInt; Digits: Byte;
const Spaces: Boolean ): AnsiString;
begin
if Digits > 32 then
Digits := 32;
SetLength( Result, Digits );
Result := '';
while Digits > 0 do
begin
if (Spaces) and ((Digits mod 8) = 0) then
Result := Result + #32;
Dec(Digits, 1);
Result := Result + IntToStr((Value shr Digits) and 1);
end;
end;
function BinToInt( Value: AnsiString ): LongInt;
var
cTmp: AnsiChar;
liCtr, liLen: LongInt;
begin
Value := AnsiString(StringReplace(Value, #32, '', [rfReplaceAll]));
liLen := Length(Value);
cTmp := Value[liLen];
Dec(liLen);
Result := StrToInt(cTmp);
liCtr := 1;
while liLen > 0 do
begin
cTmp := Value[liLen];
Dec( liLen );
Result := Result + (StrToInt(cTmp) shl liCtr );
Inc(liCtr);
end;
end;
Sample use:
procedure TForm1.FormShow(Sender: TObject);
var
TestStr: AnsiString;
i: Integer;
Temp: AnsiString;
begin
TestStr := 'ABC';
Temp := '';
for i := 1 to Length(TestStr) do
Temp := Temp + IntToBin(Ord(AnsiChar(TestStr[i])), 8, False);
ShowMessage('Temp = ' + Temp);
TestStr := '';
i := 1;
while i < Length(Temp) do
begin
TestStr := TestStr + AnsiChar(BinToInt(Copy(Temp, i, 8)));
Inc(i, 8);
end;
ShowMessage('TestStr = ' + TestStr);
end;
As I said in my comment to your original question, I think this is a terrible idea, but these work.
function _ConvertHexToWideString(AHex: AnsiString): WideString;
var wBinaryStream: TMemoryStream;
begin
try
wBinaryStream := TMemoryStream.Create;
try
wBinaryStream.Size := Length(AHex) div 2;
if wBinaryStream.Size > 0 then
HexToBin(PAnsiChar(AHex), wBinaryStream.Memory, wBinaryStream.Size);
except
end;
SetString(Result, PWideChar(wBinaryStream.Memory), wBinaryStream.Size div SizeOf(WideChar));
finally
FreeAndNil(wBinaryStream);
end;
end;

programatically extract the file name from a Download Link using delphi

How i can extract the file name from a Download Link using Delphi
Example
http://pj-mirror01.mozilla.org/pub/mozilla.org/firefox/releases/3.6/win32/es-CL/Firefox%20Setup%203.6.exe
The result must be
Firefox Setup 3.6.exe
Try this
function GetURLFilename(const FilePath:String;Const Delimiter:String='/'):String;
var I: Integer;
begin
I := LastDelimiter(Delimiter, FILEPATH);
Result := Copy(FILEPATH, I + 1, MaxInt);
Result := UrlDecode(Result);
end;
URlDecode was copied from http://www.torry.net/dpfl/dzurl.html and looks like
function UrlDecode(const EncodedStr: String): String;
var
I: Integer;
begin
Result := '';
if Length(EncodedStr) > 0 then
begin
I := 1;
while I <= Length(EncodedStr) do
begin
if EncodedStr[I] = '%' then
begin
Result := Result + Chr(HexToInt(EncodedStr[I+1]
+ EncodedStr[I+2]));
I := Succ(Succ(I));
end
else if EncodedStr[I] = '+' then
Result := Result + ' '
else
Result := Result + EncodedStr[I];
I := Succ(I);
end;
end;
end;
function HexToInt(HexStr: String): Int64;
var RetVar : Int64;
i : byte;
begin
HexStr := UpperCase(HexStr);
if HexStr[length(HexStr)] = 'H' then
Delete(HexStr,length(HexStr),1);
RetVar := 0;
for i := 1 to length(HexStr) do begin
RetVar := RetVar shl 4;
if HexStr[i] in ['0'..'9'] then
RetVar := RetVar + (byte(HexStr[i]) - 48)
else
if HexStr[i] in ['A'..'F'] then
RetVar := RetVar + (byte(HexStr[i]) - 55)
else begin
Retvar := 0;
break;
end;
end;
Result := RetVar;
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