Get path to file currently open in Windows Picture & Fax viewer
-
28-04-2021 - |
質問
I am writing an "add-on" for Windows Picture viewer that will need to send commands to it (like "Show next/previous image") and obtain file path to currently selected image. I managed to implement sending commands via SendMessage, but I don't know how to request info from a process. Is this possible? So far I can only extract filename from window title, but this restricts usage to just one folder, I need the full path.
[EDIT] I did some search and found, that there's (undocumented?) possibility to find list of all handles used by process, using function NTQuerySystemInformation (As seen here Delphi - get what files are opened by an application). The problem is, however, that the example provided there doesn't show file handles for me at all (only non-harddrive device handles), and while I found working example here http://www.codeguru.com/Cpp/W-P/system/processesmodules/article.php/c2827/, seems like Picture Viewer doesn't hold any handle to previewed file when launched from explorer.
解決
You can get the Process "Current Directory" (as shown in Process Explorer).
Take a look at Two ways to get the command line of another process using Delphi by RRUZ.
Based on that article, we could get the CurrentDirectory
found in the RTL_USER_PROCESS_PARAMETERS
(offset 36) structure:
type
Uint4B = Cardinal;
Uint2B = Word;
UChar = Byte;
Ptr32 = Pointer;
TUNICODE_STRING = UNICODE_STRING;
TCURDIR = packed record
DosPath : TUNICODE_STRING;
Handle : Ptr32;
end;
TRTL_USER_PROCESS_PARAMETERS = packed record
MaximumLength : Uint4B;
Length : Uint4B;
Flags : Uint4B;
DebugFlags : Uint4B;
ConsoleHandle : Ptr32;
ConsoleFlags : Uint4B;
StandardInput : Ptr32;
StandardOutput : Ptr32;
StandardError : Ptr32;
CurrentDirectory : TCURDIR;
DllPath : TUNICODE_STRING;
ImagePathName : TUNICODE_STRING;
CommandLine : TUNICODE_STRING;
Environment : Ptr32;
StartingX : Uint4B;
StartingY : Uint4B;
CountX : Uint4B;
CountY : Uint4B;
CountCharsX : Uint4B;
CountCharsY : Uint4B;
FillAttribute : Uint4B;
WindowFlags : Uint4B;
ShowWindowFlags : Uint4B;
WindowTitle : TUNICODE_STRING;
DesktopInfo : TUNICODE_STRING;
ShellInfo : TUNICODE_STRING;
RuntimeData : TUNICODE_STRING;
// +0x090 CurrentDirectores : [32] _RTL_DRIVE_LETTER_CURDIR
end;
Here is how to obtain CurrentDirectory
:
function GetCurrentDirectoryFromPid(PID: THandle): string;
const
STATUS_SUCCESS = $00000000;
SE_DEBUG_NAME = 'SeDebugPrivilege';
OffsetProcessParametersx32 = $10; //16
OffsetCurrentDirectoryx32 = $24; //36
var
ProcessHandle : THandle;
rtlUserProcAddress : Pointer;
CurrentDirectory : TCURDIR;
CurrentDirectoryContents : WideString;
ProcessBasicInfo : PROCESS_BASIC_INFORMATION;
ReturnLength : Cardinal;
TokenHandle : THandle;
lpLuid : TOKEN_PRIVILEGES;
OldlpLuid : TOKEN_PRIVILEGES;
begin
Result:='';
if OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TokenHandle) then
begin
try
if not LookupPrivilegeValue(nil, SE_DEBUG_NAME, lpLuid.Privileges[0].Luid) then
RaiseLastOSError
else
begin
lpLuid.PrivilegeCount := 1;
lpLuid.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED;
ReturnLength := 0;
OldlpLuid := lpLuid;
//Set the SeDebugPrivilege privilege
if not AdjustTokenPrivileges(TokenHandle, False, lpLuid, SizeOf(OldlpLuid), OldlpLuid, ReturnLength) then RaiseLastOSError;
end;
ProcessHandle := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, false, PID);
if ProcessHandle=0 then RaiseLastOSError
else
try
// get the PROCESS_BASIC_INFORMATION to access to the PEB Address
if (NtQueryInformationProcess(ProcessHandle,0{=>ProcessBasicInformation},@ProcessBasicInfo, sizeof(ProcessBasicInfo), @ReturnLength)=STATUS_SUCCESS) and (ReturnLength=SizeOf(ProcessBasicInfo)) then
begin
//get the address of the RTL_USER_PROCESS_PARAMETERS struture
if not ReadProcessMemory(ProcessHandle, Pointer(Longint(ProcessBasicInfo.PEBBaseAddress) + OffsetProcessParametersx32), @rtlUserProcAddress, sizeof(Pointer), ReturnLength) then
RaiseLastOSError
else
if ReadProcessMemory(ProcessHandle, Pointer(Longint(rtlUserProcAddress) + OffsetCurrentDirectoryx32), @CurrentDirectory, sizeof(CurrentDirectory), ReturnLength) then
begin
SetLength(CurrentDirectoryContents, CurrentDirectory.DosPath.length);
//get the CurrentDirectory field
if ReadProcessMemory(ProcessHandle, CurrentDirectory.DosPath.Buffer, @CurrentDirectoryContents[1], CurrentDirectory.DosPath.Length, ReturnLength) then
Result := WideCharLenToString(PWideChar(CurrentDirectoryContents), CurrentDirectory.DosPath.length div 2)
else
RaiseLastOSError;
end;
end
else
RaiseLastOSError;
finally
CloseHandle(ProcessHandle);
end;
finally
CloseHandle(TokenHandle);
end;
end
else
RaiseLastOSError;
end;
他のヒント
You can't do this, as the application has no COM interfaces defined that would provide that info on request. You could get it if, as you've indicated, it displayed the path and filename in the window caption, but because it doesn't the information is unavailable.