3

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.

Community
  • 1
  • 1
Seedmanc
  • 357
  • 1
  • 3
  • 13
  • You have no legal way to do it unless the program has some SDK or documented API or at least has user definable add-ons support. – Vahid Nasehi Feb 10 '12 at 19:15
  • If there's no way to request information via interface, what about other ways? Maybe finding characteristics of Viewer window's components (like its TImage alternative) or peeking into process memory to find filename. – Seedmanc Feb 10 '12 at 19:18
  • @Seedmanc: Are you not reading the answers and comments you're getting? You cannot do this in any documented way. Peeking into process memory would be a good way for your product to be flagged by antivirus software and removed from a lot of systems. If you need to try to do it that way, you're doing something very wrong. – Ken White Feb 10 '12 at 19:32
  • Sorry for that, at the moment of posting my comment I haven't seen yet the first one. As for legal stuff, maybe I used the wrong word here, not "addon", basically I will be using Picture Viewer instead of implementing TImage and corresponding routines myself, since existing implementation satisfies me. I don't really see any harm here, as I am not going to change .exe of it in any way, nothing like that. – Seedmanc Feb 10 '12 at 19:38
  • @Seedmanc: Even if you try it that way, your application would depend on a specific version of that program over a specific OS version and update. – Vahid Nasehi Feb 10 '12 at 21:23
  • I didn't say you were doing anything *illegal*. I said you might be flagged by anti-virus software as some sort of virus or malware, and "peeking into process memory" of another application is something malware might attempt. If your app is being used on your own machine, and you feel comfortable doing so (and feel like wasting your time, as future updates or OS versions would probably break it), have fun. If you're doing this for an app you intend to distribute to others, think again about how you're doing things, because you're doing them wrong. :) – Ken White Feb 10 '12 at 22:48
  • You could inspect the Process "Current Directory" (as shown in PE) – kobik Feb 11 '12 at 13:59
  • @kobik I wonder, how do I do that? Also, I did some research and updated question. – Seedmanc Feb 12 '12 at 03:42

2 Answers2

4

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;    
Community
  • 1
  • 1
kobik
  • 21,001
  • 4
  • 61
  • 121
  • Thanks for the link and the code, but seems like I'm out of luck here. When I pass the PID I found based on Viewer's window handle to this function, I get "Process not found". When I manually set the PID to that found by Process Explorer, it gives me "C:\Documents and Settings\"username" instead of actual directory. Maybe I am doing something wrong prior to that? Here's a bit of my code: `H:=FindWindow('ShImgVw:CPreviewWnd',nil); //Get window handle ShowMessage( GetCurrentDirectoryFromPid( GetWindowThreadProcessId(H)));` Maybe it's due to Viewer being actually Explorer process, not Rundll. – Seedmanc Feb 15 '12 at 15:33
  • You are not working correctly with `GetWindowThreadProcessId`. You are returning `ThreadID`. you should use `GetWindowThreadProcessId(Wnd, @PID)`. also, Does the Current Directory you see in PE suits your needs? – kobik Feb 15 '12 at 16:05
  • also, if your "actual" directory is `My Documents` and the result is `C:\Documents and Settings\[user]\My Documents\` that means it's the valid actual directory, and all is good. – kobik Feb 15 '12 at 17:25
  • Thanks for correction, now this code repeats what I see in PE. However, it works only partially... The problem is, Viewer exists as a separate process (Rundll32) only when launched from command line or via Right Click - Open with. In this case CurrentDirectory suits my need perfectly. But when launched by doubleclick on image (and this is way more common way for it) it exists only as separate thread of explorer.exe - no actual process is created.Then CurrentDirectory doesn't work, always returning , since this is the property of explorer.Maybe it needs alternative for ThreadID. – Seedmanc Feb 16 '12 at 17:00
  • I suspect you tested it with Windows XP. on Win7 is creates a COM object and the process owner is dllhost. a whole new different story... – kobik Feb 16 '12 at 18:10
1

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.

Ken White
  • 123,280
  • 14
  • 225
  • 444