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
Related
I have a program that uses SetCursorPos to position the cursor. The program operates as it is supposed to when running on real hardware but, when running in a VM (VMware workstation 10.0.7) it doesn't work. The cursor does not move. I tried using SendInput instead (the syscall it makes is different, because of that, I thought it might work), the result is the same as with SetCursorPos, it works on real hardware, does not work when running in a VM.
The question is: does anyone know if either SetCursorPos or SendInput can be made to work in a VM and if yes, how ? Any other way to position the cursor at a specific place that works in a VM would be welcome as well.
Thank you for your help.
For anyone who cares to try, here is some of the code I've tried.
{$APPTYPE CONSOLE}
program ConsoleSetCursorPos;
uses
Windows
;
function GetConsoleWindow : HWND; stdcall; external kernel32;
procedure DoIt;
var
ConsoleWindow : HWND;
ClientRect : TRECT;
CursorPosRetVal : BOOL;
LastError : dword;
Desktop : HDESK;
begin
// the code below is not normally necessary - for testing only
Desktop := OpenInputDesktop(0, false, WINSTA_WRITEATTRIBUTES);
LastError := GetLastError;
writeln;
writeln('From OpenInputDesktop');
writeln('Last error (decimal) : ', LastError);
if Desktop = 0 then
begin
writeln('Program terminated due to OpenInputDesktop failure');
halt(255);
end;
if not SetThreadDesktop(Desktop) then
begin
writeln('Program terminated due to SetThreadDesktop failure');
halt(255);
end;
writeln;
// end of normally unnecessary code
SetLastError(0);
ConsoleWindow := GetConsoleWindow;
GetClientRect(ConsoleWindow, ClientRect);
ClientToScreen(ConsoleWindow, ClientRect.TopLeft);
CursorPosRetVal := SetCursorPos(ClientRect.Left, ClientRect.Top);
LastError := GetLastError;
if not CursorPosRetVal
then writeln('SetCursorPos returned false (failed)')
else writeln('SetCursorPos returned true (succeeded)');
writeln('Last error (decimal) : ', LastError);
if Desktop <> 0 then CloseDesktop(Desktop);
end;
begin
DoIt;
end.
As the remarks on SetCursorPos doc:
The cursor is a shared resource. A window should move the cursor only
when the cursor is in the window's client area.
The calling process must have WINSTA_WRITEATTRIBUTES access to the
window station.
The input desktop must be the current desktop when you call
SetCursorPos. Call OpenInputDesktop to determine whether the current
desktop is the input desktop. If it is not, call SetThreadDesktop with
the HDESK returned by OpenInputDesktop to switch to that desktop.
Or you can take the same try to un-installed the mouse driver from the VM as this answer.
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.
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.
Say I have got a program that hogs the processor and/or hard disk to the point that it makes it nearly impossible to do anything else on that computer. Now I don't want to kill that program because what it does is useful (it's a batch job that really is that CPU or disk heavy, e.g. it could ZIP a few gigabytes of data files) but for a short time I need to do something else on that computer. Is there any way an external program could do to freeze that performance killer for a while?
It's like the old DOS option to switch between programs without actually having multitasking.
Assume that the hypothetical program in question is a 3rd party product for which I don't have the source code and there is no way to tell it to pause.
I know I can change the program's priority class e.g. in TaskManager but that's not enough, I want to freeze it.
I am talking about Windows XP as the OS and would like to program a solution with Delphi. I have got all rights on the machine, so I could start something as administrator, replace files and I could also install a service if that is necessary.
You can freeze it with Process Explorer: Right-click on your program and select Suspend.
Here is some sample code for programmatic freezing from http://www.c-plusplus.de/forum/viewtopic-var-p-is-1460293.html, edited and omitted error checking for brevity:
#include <windows.h>
_NtSuspendProcess NtSuspendProcess =
(_NtSuspendProcess) GetProcAddress( GetModuleHandle( "ntdll" ),
"NtSuspendProcess" );
HANDLE ProcessHandle = OpenProcess( PROCESS_ALL_ACCESS, FALSE, pid);
NtSuspendProcess( ProcessHandle );
If you want to do it programatically you can use the approach described here.
What is does, is enumerating all the threads in a process and then suspending them. There is no SuspendProcess API, so this is a simulation of such a call.
Beware that this can potentionally have some bad side effects. It depend on the process and how it is written.
I don't know of any other way to do it in the Win32/64 API world. If you go lower to the kernel land and use the NT* APIs you have "NtSuspendProcess" API available. But this is undocumented so it can change with any version of windows or even with any service pack (not very likely though).
The declaration of "NtSuspendProcess" can be found in the JEDI ports of the windows APIs.
You can use my ProcessInfo component to suspend all threads belonging to the process. The approach is similar to what Runner explained to you. The code would be something like this:
var
Process : TProcessItem;
AThread: TThreadItem;
begin
Process := ProcessInfo1.RunningProcesses.FindByName('notepad.exe');
if Assigned(Process) then
begin
for AThread in Process.Threads do
AThread.SuspendThread;
end;
end;
You can download source code of ProcessInfo form here
function OpenThread(dwDesiredAccess: DWORD; InheritHandle: Boolean; dwThreadID: DWORD): THandle; stdcall; external 'kernel32.dll';
function ResumeProcess(PID: DWORD):Boolean;
var
tid, snap: THandle;
TE32: TThreadEntry32;
begin
Result := False;
snap := CreateToolHelp32SnapShot(TH32CS_SNAPTHREAD, 0);
TE32.dwSize := SizeOf(TThreadEntry32);
Thread32First(snap, TE32);
repeat
if TE32.th32OwnerProcessID = PID then begin
tid := OpenThread($0002, FALSE, TE32.th32ThreadID);
ResumeThread(tid);
Result := TRUE;
CloseHandle(tid);
end;
until Thread32Next(snap, TE32) = false;
CloseHandle(snap);
end;
function SuspendProcess(PID: DWORD): Boolean;
var
tid, snap: THandle;
TE32: TThreadEntry32;
begin
Result := False;
snap := CreateToolHelp32SnapShot(TH32CS_SNAPTHREAD, 0);
TE32.dwSize := SizeOf(TThreadEntry32);
Thread32First(snap, TE32);
repeat
if TE32.th32OwnerProcessID = PID then begin
tid := OpenThread($0002, FALSE, TE32.th32ThreadID);
SuspendThread(tid);
Result := TRUE;
CloseHandle(tid);
end;
until Thread32Next(snap, TE32) = false;
CloseHandle(snap);
end;
Hope this helps
I think this question was already asked, but I couldn't find a solution which works for me. I use Delphi 7 under Windows 7 Ultimate, 64 bit. Actually I started writing application under 32 bit OS, but then changes PC, so now its 64. In my program I use registration process with Licence ID generated from PROGID value of Windows. Unfortunately it doesn't read the value, seems like it is looking in a different folder, probably redirected by Windows 64 to 32 bit registry. Can you help? This is the code I use:
Registry := TRegistry.Create(KEY_READ OR $0100);
try
Registry.Lazywrite := false;
Registry.RootKey := HKEY_LOCAL_MACHINE;
if CheckForWinNT = true then
Begin
if not Registry.OpenKeyReadOnly('\Software\Microsoft\Windows NT\CurrentVersion') then showmessagE('cant open');
end
else
Registry.OpenKeyReadOnly('\Software\Microsoft\Windows\CurrentVersion');
result := Registry.ReadString('ProductID');
Registry.CloseKey;
finally
Registry.Free;
end; // try..finally
Also, do you know how to find whether program is running under 64 bit or 32 bit computer in Delphi 7?
You already asked this question see Registry ReadString method is not working in Windows 7 in Delphi 7.
So you know that you have to add $0100 in the TRegistry.Create. The problem with your code is that you use OpenKeyReadOnly which resets the Access property of the registry to KEY_READ, so KEY_READ or $0100 is lost.
Just use OpenKey instead of OpenKeyReadOnly, this won't reset your Access property.
Here is some Delphi 7 code to detect whether you are running in a 64-bit OS:
function Is64BitOS: Boolean;
type
TIsWow64Process = function(Handle:THandle; var IsWow64 : BOOL) : BOOL; stdcall;
var
hKernel32 : Integer;
IsWow64Process : TIsWow64Process;
IsWow64 : BOOL;
begin
// we can check if the operating system is 64-bit by checking whether
// we are running under Wow64 (we are 32-bit code). We must check if this
// function is implemented before we call it, because some older versions
// of kernel32.dll (eg. Windows 2000) don't know about it.
// see http://msdn.microsoft.com/en-us/library/ms684139%28VS.85%29.aspx
Result := False;
hKernel32 := LoadLibrary('kernel32.dll');
if (hKernel32 = 0) then RaiseLastOSError;
#IsWow64Process := GetProcAddress(hkernel32, 'IsWow64Process');
if Assigned(IsWow64Process) then begin
IsWow64 := False;
if (IsWow64Process(GetCurrentProcess, IsWow64)) then begin
Result := IsWow64;
end
else RaiseLastOSError;
end;
FreeLibrary(hKernel32);
end;
(Shamelessly plagiarized from myself, here)
It looks like you are passing KEY_WOW64_64KEY ($0100), so you should be looking at the 64-bit registry branch. If you want to look at the 32-bit registry branch, you should pass KEY_WOW64_32KEY ($0200).
As to your side question, whether it's a 64-bit computer (which is not the same thing as running on a 64-bit OS), have a look at the answers to this question.
I know this topic is about delphi 7, but I thought I was having problems reading the registry and came here to learn.. I ended up using Key_Read instead of all the extras suggested here.
I'm using Delphi 2010 and I used Key_Read just fine.
Here is my part of my source that works:
//Search registry
reg:=TRegistry.Create(KEY_READ);
with reg do begin
try
RootKey := HKEY_LOCAL_MACHINE;
if OpenKey('\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft',false) then
begin
memo.Lines.Add('HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft - exists');
wowdir1 := readstring('InstallPath');
memo.Lines.Add('InstallPath - ' + wowdir1);
newline;
closekey;
end;
if OpenKey('\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft',false) then
begin
memo.Lines.Add('HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Blizzard Entertainment\World of Warcraft - exists');
wowdir2 := readstring('GamePath');
memo.Lines.Add('GamePath - ' + wowdir2);
newline;
wowdir1 := readstring('');
closekey;
end;
if OpenKey('\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft',false) then
begin
memo.Lines.Add'HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\World of Warcraft - exists');
wowdir3 := readstring('InstallLocation');
memo.Lines.Add('InstallLocation - ' + wowdir3);
newline;
wowdir1 := readstring('');
closekey;
end;
finally
reg.Free;
end;
I tried the other Keys that are displayed here and found I don't need KEY_WOW64_64KEY OR KEY_WOW64_32KEY. This must have been a bug that has been corrected in Delphi 2010.