Search code examples
windowsdelphiphotoviewer

Get path to file currently open in Windows Picture & Fax viewer


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.


Solution

  • 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;