Get a list of all indexed files in Windows with Delphi - windows

I would like to list all the files that windows has indexed using its Windows Indexing Service.
Specified file extensions are acceptable.
For instance: I am working an a software which presents user media such as photos and videos. I am currently using the following custom procedure to find the files myself:
function FindAllFiles_Safe(aDirectory, aFilter: string; aIncludeSubDirs: boolean): string;
{$IFDEF DCC}
var TD: TDirectory;
SO: TSearchOption;
DF: TStringDynArray;
i: integer;
sl: TStringList;
MaskArray: TStringDynArray;
Predicate: TDirectory.TFilterPredicate;
{$ENDIF}
begin
{$IFDEF FPC}
result:=FindAllFiles(aDirectory,aFilter,aIncludeSubDirs).text;
{$ENDIF}
{$IFDEF DCC}
MaskArray := SplitString(aFilter, ';');
if aIncludeSubDirs=true then SO:=TSearchOption.soAllDirectories;
Predicate :=
function(const Path: string; const SearchRec: TSearchRec): Boolean
var Mask: string;
begin
for Mask in MaskArray do
if MatchesMask(SearchRec.Name, Mask) then
exit(True);
exit(False);
end;
//DF:=TD.GetFiles(aDirectory, Predicate, SO);
DF:=TD.GetFiles(aDirectory, SO, Predicate);
if length(DF)=0 then exit;
sl:=TStringList.Create;
for i := 0 to length(DF)-1 do sl.Add(DF[i]);
result:=sl.Text;
sl.Free;
{$ENDIF}
end;
Is there a way to access files that Windows has already indexed?
I'd like to take advantage of Windows Indexing Service to quickly retrieve files, rather then wasting resources if Windows already has done it before.

One of the ways to query the index of the Windows Search is use ADO and the Query Syntax (AQS) and SQL.
Try this sample code (off course you can improve the SQL sentence to filter and speed up the results)
{$APPTYPE CONSOLE}
{$R *.res}
uses
ADOInt,
SysUtils,
ActiveX,
ComObj,
Variants;
procedure QuerySystemIndex;
var
Connection : _Connection;
RecordSet: _RecordSet;
v: Variant;
begin;
OleCheck(CoCreateInstance(CLASS_Connection, nil, CLSCTX_ALL, IID__Connection, Connection));
OleCheck(CoCreateInstance(CLASS_RecordSet, nil, CLSCTX_ALL, IID__RecordSet, RecordSet));
Connection.CursorLocation := adUseClient;
Connection.Open('Provider=Search.CollatorDSO;Extended Properties=''Application=Windows'';', '', '', adConnectUnspecified);
Recordset.Open('SELECT Top 5 System.ItemPathDisplay FROM SYSTEMINDEX', Connection, adOpenForwardOnly, adLockReadOnly, adCmdText);
Recordset.MoveFirst;
v:='System.ItemPathDisplay';
while not Recordset.EOF do
begin
Writeln(Recordset.Fields.Item[v].Value);
Recordset.MoveNext();
end;
end;
begin
try
CoInitialize(nil);
try
QuerySystemIndex;
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
You can found alternatives ways to access the Search Index on the MSDN documentation.

There is an API for Windows Search (previously known as Windows Desktop Search).
However, whilst the Windows Search API is undoubtedly enormously powerful, I think for simply locating files based on file extension (or even other constituent elements in the file name) the Windows Search API is likely to prove prohibitively complex and provide negligible benefit, unless you are dealing with a truly extraordinary number of files.

Related

"Group by" a certain folder in windows explorer

What I am trying to do is to crate a folder for my application, and to make sure each time a user enters this folder, it's grouped, like this:
except that the disks would be replaced by some folders/files.
so basically I'm trying to achieve exactly what "Group by" function does:
and I have to do this in my application with c/c++ code or a bat. I'm guessing this needs to be done in the registry, but I cannot find where. any idea?
thanks.
You must understand that changing of Explorer view mode with registry is dirty hack. So USE ON YOUR OWN RISK. TESTED ON WINDOWS 7 ONLY.
procedure SetFolderGroupBy(AParentWnd: HWND; const AFolder: UnicodeString; const AColumn: TPropertyKey; AAscending: Boolean);
var
Desktop: IShellFolder;
Attr: DWORD;
Eaten: DWORD;
IDList: PItemIDList;
Bag: IPropertyBag;
Direction: DWORD;
begin
OleCheck(SHGetDesktopFolder(Desktop));
try
Attr := 0;
OleCheck(Desktop.ParseDisplayName(AParentWnd, nil, PWideChar(AFolder), Eaten, IDList, Attr));
try
OleCheck(SHGetViewStatePropertyBag(IDList, 'Shell', SHGVSPB_FOLDERNODEFAULTS, IPropertyBag, Bag));
try
OleCheck(Bag.Write('SniffedFolderType', 'Generic'));
finally
Bag := nil;
end;
OleCheck(SHGetViewStatePropertyBag_(IDList, 'Shell\{5C4F28B5-F869-4E84-8E60-F11DB97C5CC7}', SHGVSPB_FOLDERNODEFAULTS, IPropertyBag, Bag));
try
if AAscending then Direction := SORT_ASCENDING
else Direction := DWORD(SORT_DESCENDING);
OleCheck(Bag.Write('GroupByDirection', Direction));
OleCheck(Bag.Write('GroupByKey:FMTID', GUIDToString(AColumn.fmtid)));
OleCheck(Bag.Write('GroupByKey:PID', AColumn.pid));
OleCheck(Bag.Write('GroupView', DWORD(-1)));
finally
Bag := nil;
end;
finally
CoTaskMemFree(IDList);
end;
finally
Desktop := nil;
end;
end;

Use Delphi to Find Special Drives

I am trying to write a small program in Delphi 2007 to access files off of a portable USB drive whenever it is plugged in to a Windows 7 machine. This drive does not show up as a standard drive letter though. It appears under Portable Devices in Windows Explorer. I have written the following code to enumerate all the items under 'Computer':
Procedure TfrmMain.ComputerChanged(Var Msg: TMessage);
Var
Enum: IEnumIDList;
Fetched: Longword;
Item: PItemIDList;
Path: String;
Computer: IShellFolder;
StrRet: TSTRRET;
Begin
Status('Computer changed... Checking folders.');
fDesktop.BindToObject(fCompPidl, Nil, IID_IShellFolder, Computer);
If Assigned(Computer) And
(Computer.EnumObjects(Self.Handle, SHCONTF_FOLDERS, Enum) = NOERROR) Then
Begin
While (Enum.Next(1, Item, Fetched) = NOERROR) Do
Begin
FillChar(StrRet, SizeOf(StrRet), #0);
Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
Path := StrRetToStr(StrRet, Item);
Status(Path);
End;
End;
End;
(note: the Status procedure just outputs a message to a TMemo.)
This is called whenever my application is notified of a change by the Windows shell subsystem. It enumerates all of the local drives and network drives but nothing else (iCloud Photos drive is missing as well).
Does anyone know how I can access the files on these virtual drives?
You more than likely aren't initializing COM correctly. Your code will work as-is if you don't call CoInitializeEx or if you call it with a bad value, but the Portable Device drivers require apartment threading to work.
Based on your code, here's a sample app that works correctly and shows portable devices. If you comment out the CoInitializeEx/CoUninitialize calls or pass in COINIT_MULTITHREADED instead it will still work, but it only shows the drives.
program ListMyComputer;
{$APPTYPE CONSOLE}
uses
ComObj, ShlObj, ShellApi, ShLwApi, ActiveX, Windows, SysUtils;
var
Enum: IEnumIDList;
Fetched: Longword;
CompPidl, Item: PItemIDList;
Path: PWideChar;
Desktop, Computer: IShellFolder;
StrRet: TSTRRET;
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
try
WriteLn('Computer changed... Checking folders.');
SHGetDesktopFolder(Desktop);
SHGetFolderLocation(0, CSIDL_DRIVES, 0, 0, CompPidl);
Desktop.BindToObject(CompPidl, Nil, IID_IShellFolder, Computer);
CoTaskMemFree(CompPidl);
If Assigned(Computer) And
(Computer.EnumObjects(0, SHCONTF_FOLDERS, Enum) = NOERROR) Then
Begin
While (Enum.Next(1, Item, Fetched) = NOERROR) Do
Begin
FillChar(StrRet, SizeOf(StrRet), #0);
Computer.GetDisplayNameOf(Item, SHGDN_FORADDRESSBAR or SHGDN_NORMAL, StrRet);
StrRetToStr(#StrRet, Item, Path);
WriteLn(Path);
CoTaskMemFree(Path);
End;
End;
WriteLn('Enumeration complete');
ReadLn;
finally
CoUninitialize
end;
end.
Thanks to #SertacAkyuz for pointing out the need to use Windows Portable Device API which lead me to this Experts Exchange question discussing the same thing. Sinisa Vuk supplied an awesome code example to answer that question which I have linked (it's too long to embed) here with permission: http://pastebin.com/0hSWv5pE

Stored procedure Text saving in Delphi

I need to create stored procedure into oracle from delphi with TQuery.
But the SQL.text is difficult to uunderstand.
Is there any way to store direct text as pl/SQL with out quotes?
'create or replace '+
'function WholeTableRecovery(i_tablname IN varchar) return varchar '+
'as '+
Is it possible with resource file
Thanks in advance
Since you are using Delphi 2010 in the tags (I have no Delphi 7 here to test), a comfortable method would be storing the SQLs in separate textfiles, together with a RC file containing the directives for the resource compiler.
The RC files will contain the names of the resource you want to use together with the filenames containing the SQLs you want to store. The content for the example would look like this:
My_First_Speaking_ResourceName RCDATA "MyFirstSQL.sql"
My_Second_Speaking_ResourceName RCDATA "MySecondSQL.sql"
There is no need to call BRCC32 directly if you include the resource containing RC and resulting RES :
{$R 'MySQLResources.res' 'resources\MySQLResources.rc'}
You might wrap the usage of TResourceStream for your convenience, the way shown in the example would use Strings you might also work with the stream directly as mentioned by TLama MyQuery.SQL.LoadFromStream(rs);
implementation
{$R *.dfm}
{$R 'MySQLResources.res' 'resources\MySQLResources.rc'}
function LoadSqlResource(resourceName: string): string;
var
rs: TResourceStream;
sl: TStringList;
s : string;
begin
sl := TStringList.Create;
try
rs := TResourceStream.Create(hinstance, resourceName, RT_RCDATA);
try
rs.Position := 0;
sl.LoadFromStream(rs);
Result := sl.Text;
finally
rs.Free;
end;
finally
sl.Free;
end;
end;
procedure CallOneSql(Q:TADOQuery;ResourceName:String);
begin
Q.SQL.Text := LoadSqlResource('My_First_Speaking_ResourceName');
Q.ExecSQL;
end;
With a call like CallOneSql(MyQuery,'My_First_Speaking_ResourceName');
Make sure to create the project, not just compile if you made changes on the RC or the SQL files.

Delphi, Windows: Best way to find whether web-browser is running?

What is the best way to find whether a web-browser is running?
Using Delphi XE2 and on Windows, I need to find whether the following web-browsers are currently running:
A) Mozilla Firefox
B) Apple Safari
C) Google Chrome
If found, the process will be terminated because the home page of the web-browser needs to be changed programmatically by modifying the web-browser configuration files (which is either not possible or could result in unpredictable results if done when the web-browser is running).
Does the output from the EnumWindows API function contain sufficient information needed to handle the above task? If yes, then are the window class names for each of the above web-browsers documented anywhere? If no, then which method is most reliable?
TIA.
Terminate a process without the user permission is not good practice, instead you must ask to the user if he wants terminate the app (in this case the web browser).
Now back to your question, you can detect if a app(webbroser) is running checking for the process name (firefox.exe, chrome.exe , safari.exe) using the CreateToolhelp32Snapshot method.
uses
Windows,
tlhelp32,
SysUtils;
function IsProcessRunning(const ListProcess: Array of string): boolean;
var
hSnapshot : THandle;
lppe : TProcessEntry32;
I : Integer;
begin
result:=false;
hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if hSnapshot <> INVALID_HANDLE_VALUE then
try
lppe.dwSize := SizeOf(lppe);
if Process32First(hSnapshot, lppe) then
repeat
for I := Low(ListProcess) to High(ListProcess) do
if SameText(lppe.szExeFile, ListProcess[i]) then
Exit(True);
until not Process32Next(hSnapshot, lppe);
finally
CloseHandle(hSnapshot);
end;
end;
and use like so
IsProcessRunning(['firefox.exe','chrome.exe','safari.exe'])
Now if you want a more reliable way you can search for the class name of the Window (using the FindWindowEx method) and then the PID of the process owner of the handle (using GetWindowThreadProcessId), from here you can use the PID of the process to resolve the name of exe.
{$APPTYPE CONSOLE}
uses
Windows,
tlhelp32,
SysUtils;
function GetProcessName(const th32ProcessID: DWORD): string;
var
hSnapshot : THandle;
lppe : TProcessEntry32;
begin
result:='';
hSnapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if hSnapshot <> INVALID_HANDLE_VALUE then
try
lppe.dwSize := SizeOf(lppe);
if Process32First(hSnapshot, lppe) then
repeat
if lppe.th32ProcessID=th32ProcessID then
Exit(lppe.szExeFile);
until not Process32Next(hSnapshot, lppe);
finally
CloseHandle(hSnapshot);
end;
end;
function IsWebBrowserRunning(const ClassName, ExeName :string) : Boolean;
var
hWindow : THandle;
dwProcessId: DWORD;
begin
result:=False;
hWindow:= FindWindowEx(0, 0, PChar(ClassName), nil);
if hWindow<>0 then
begin
dwProcessId:=0;
GetWindowThreadProcessId(hWindow, dwProcessId);
if dwProcessId>0 then
exit(Sametext(GetProcessName(dwProcessId),ExeName));
end;
end;
begin
try
if IsWebBrowserRunning('MozillaWindowClass','firefox.exe') then
Writeln('Firefox is Running');
if IsWebBrowserRunning('{1C03B488-D53B-4a81-97F8-754559640193}','safari.exe') then
Writeln('Safari is Running');
if IsWebBrowserRunning('Chrome_WidgetWin_1','chrome.exe') then
Writeln('Chrome is Running');
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
readln;
end.

How I can list the contents of a folder of a remote machine

I'm looking for a windows api function or another way to get the content (folder and files) of a folder located in a machine on my LAN. off course which I have a valid windows user and password for every machine which I want to access.
You can use the WMI , check the CIM_DataFile and CIM_Directory classes.
Some Notes
1.First you must enable the wmi remote access in the client machines. Read these articles to see how do this and the differences between windows versions Connecting to WMI on a Remote Computer, Securing a Remote WMI Connection.
2.Always you must use filters (Where conditions) to restrict the result of these WMI classes.
3.Always you must use the Drive field as condition, because these classes return the files of all drives.
4.The Wmi interprets the \ (Backslash) character as a reserverd symbol so you must need to escape that character to avoid problems with the WQL sentence.
Delphi Code
{$APPTYPE CONSOLE}
uses
SysUtils,
ActiveX,
ComObj,
Variants;
procedure GetRemoteFolderContent(Const WbemComputer,WbemUser,WbemPassword,Path:string);
const
wbemFlagForwardOnly = $00000020;
var
FSWbemLocator : OLEVariant;
FWMIService : OLEVariant;
FWbemObjectSet: OLEVariant;
FWbemObject : OLEVariant;
oEnum : IEnumvariant;
iValue : LongWord;
WmiPath : string;
Drive : string;
begin;
//The path
//Get the drive
Drive :=ExtractFileDrive(Path);
//get the path and add a backslash to the end
WmiPath :=IncludeTrailingPathDelimiter(Copy(Path,3,Length(Path)));
//escape the backslash character
WmiPath :=StringReplace(WmiPath,'\','\\',[rfReplaceAll]);
Writeln('Connecting');
FSWbemLocator := CreateOleObject('WbemScripting.SWbemLocator');
//Establish the connection
FWMIService := FSWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword);
Writeln('Files');
Writeln('-----');
//Get the files from the specified folder
FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_DataFile Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('%s',[FWbemObject.Name]));
FWbemObject:=Unassigned;
end;
Writeln('Folders');
Writeln('-------');
//Get the folders from the specified folder
FWbemObjectSet:= FWMIService.ExecQuery(Format('SELECT * FROM CIM_Directory Where Drive="%s" AND Path="%s"',[Drive,WmiPath]),'WQL',wbemFlagForwardOnly);
oEnum := IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant;
while oEnum.Next(1, FWbemObject, iValue) = 0 do
begin
Writeln(Format('%s',[FWbemObject.Name]));
FWbemObject:=Unassigned;
end;
end;
begin
try
CoInitialize(nil);
try
GetRemoteFolderContent('remote_machine','user','password','C:\');
GetRemoteFolderContent('remote_machine','user','password','C:\Program Files');
finally
CoUninitialize;
end;
except
on E:EOleException do
Writeln(Format('EOleException %s %x', [E.Message,E.ErrorCode]));
on E:Exception do
Writeln(E.Classname, ':', E.Message);
end;
Writeln('Press Enter to exit');
Readln;
end.
Without the authorization part, it's simple enough. The right way to do the authorization is to call Windows.pas method WNetAddConnection2 and go that way.
However, because I'm in a simple hack mode, I tried this, and it basically works:
uses Types, IOUtils, ShellApi; // Works in Delphi XE.
procedure TForm5.Button1Click(Sender: TObject);
var
dirs:TStringDynArray;
files:TStringDynArray;
apath, dir,filename:String;
begin
ListBox1.Items.Clear;
apath := '\\hostname\sharename';
// This should be calling WNetAddConnection2:
// instead It's an evil (portable) hack.
ShellExecute(HWND(0), 'open', PChar('net use /delete '+ apath),
nil,nil,SW_SHOW );
ShellExecute(HWND(0), 'open', PChar('net use '+ apath+' /user:uid pswd'),
nil,nil,SW_SHOW );
dirs := TDirectory.GetDirectories(apath);
if Length(dirs)=0 then
ListBox1.Items.Add('None found.')
else
for dir in dirs do
ListBox1.Items.Add('Directory: '+dir);
files := TDirectory.GetFiles(apath);
for filename in files do
ListBox1.Items.Add('File: '+filename );
end;
Abject Apologies for the ugly hack of ShellExecute "net use". (Grin) Note that I have elected to "mount" this shared folder without giving it a drive letter, avoiding the problem of what to do if that drive is already mapped.
Here's a good link with a WNetAddConnection2 code sample that I will link to instead of poaching. It shows a sample of the non-evil way to do it. :-) Then you can use the Directory enumeration code as I have shown above.
I suppose this is included in Warren's answer, but to cut to the chase, IOUtils.TDirectory supports UNCs:
implementation
uses IOUtils,types;
procedure GetFiles;
var
i: integer;
files: TStringDynArray;
begin
files := TDirectory.GetFiles('\\aServer\aPath\aShare\', '*.aFileFilter');
for i := Low(files)to High(files) do
memo1.Lines.Add(files[i]);
end;
etc, etc...

Resources