Get filepath to file(s) used by a process, without handle.exe Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Get filepath to file(s) used by a process, without handle.exe

11 Dec 2019, 12:02

The Microsoft Sysinternals tool Handle can show which files are in use by a process. For example we can call it from a script to get the files used by the VLC video player like this

Code: Select all

RunWait %comspec% /c ""C:\dir\handle.exe" -p "vlc.exe" > C:\dir\text.txt"
We can also narrow the search with a filefragment to output only .mkv files in use by VLC processes

Code: Select all

RunWait %comspec% /c ""C:\dir\handle.exe" -p "vlc.exe" ".mkv" > C:\dir\text.txt"
I want to do the same in AutoHotkey but without the external tool handle.exe

It is possible, because WhoLockedMe by Bruttosozialprodukt lists all files used (locked) by all processes. But that impressive GUI script is overkill for my purpose and it takes some time to load. I tried to extract a smaller function from it but got lost in the advanced code and failed.

I also tried this short method of getting information from a named process

Code: Select all

for process in ComObjGet("winmgmts:").ExecQuery("Select * from Win32_Process WHERE Name='vlc.exe'")
{
    msgbox % process["Name"]
    msgbox % process["CommandLine"]
}
The string in "CommandLine" includes a path to the file used by VLC if VLC was started by double clicking the video file in Explorer, but not if the file was dragged and dropped onto VLC. I see no other strings for showing the file used by the process here
https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process

My goal is to get a small function that quickly and silently takes a process name (process/window ID) as input and returns a list of file paths used by the process.
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe  Topic is solved

11 Dec 2019, 16:57

Tried to implement:

Code: Select all

procName := "explorer.exe"

SetBatchLines, -1
Process, Exist, % procName
if !(PID := ErrorLevel) {
   MsgBox, Process not found
   ExitApp
}
MsgBox, % Clipboard := GetOpenedFiles(PID)
ExitApp

GetOpenedFiles(PID) {
; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_ex.htm
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm
   static PROCESS_DUP_HANDLE := 0x0040, SystemExtendedHandleInformation := 0x40, DUPLICATE_SAME_ACCESS := 0x2
        , FILE_TYPE_DISK := 1, structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", 0, "UInt", PID)
   arr := {}
   res := size := 1
   while res != 0 {
      VarSetCapacity(buff, size, 0)
      res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation, "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
   }
   NumberOfHandles := NumGet(buff)
   VarSetCapacity(filePath, 1026)
   Loop % NumberOfHandles {
      ProcessId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
      if (PID = ProcessId) {
         HandleValue := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
         DllCall("DuplicateHandle", "Ptr", hProcess, "Ptr", HandleValue, "Ptr", DllCall("GetCurrentProcess")
                                  , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS)
         if DllCall("GetFileType", "Ptr", lpTargetHandle) = FILE_TYPE_DISK
            && DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
               arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
         DllCall("CloseHandle", "Ptr", lpTargetHandle)
      }
   }
   DllCall("CloseHandle", "Ptr", hProcess)
   for k in arr
      str .= (str = "" ? "" : "`n") . k
   Return str
}
Last edited by teadrinker on 03 Dec 2020, 15:22, edited 1 time in total.
neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: Get filepath to file(s) used by a process, without handle.exe

12 Dec 2019, 18:08

Thank you very much teadrinker! I added code to get the single filepath to the video currently playing in VLC. Your function works reliably in my tests. In cases where more than one VLC window is open we can just add a step to get the PID from the VLC window that is active, or some other such condition.

Code: Select all

#NoEnv
#SingleInstance force

;Get filepath to video currently playing in VLC

;Get VLC process ID
procName := "vlc.exe"
Process, Exist, % procName
if !(PID := ErrorLevel) {
   MsgBox, Process not found
   ExitApp
}

;Get list of filepaths to files currently used by VLC
VLCFiles := GetOpenedFiles(PID)

;Find filepath from list that match VLC window title filename
;(Requires VLC setting to show filename in title)
WinGetTitle, VLCTitle, ahk_pid %PID% ahk_class Qt5QWindowIcon
VLCFilename := StrReplace(VLCTitle, " - VLC media player", "")

Loop, Parse, VLCFiles, `n, `r
{
    SplitPath, A_LoopField, Filename
    if (VLCFilename = Filename)
    {
        Filepath := A_LoopField
        Break
    }
}

MsgBox % FilePath

ExitApp

;Function to get list of filepaths to files currently used by a process PID
;By teadrinker , https://www.autohotkey.com/boards/viewtopic.php?p=305205#p305205

GetOpenedFiles(PID) {
   static PROCESS_DUP_HANDLE := 0x0040, SystemExtendedHandleInformation := 0x40, DUPLICATE_SAME_ACCESS := 0x2
        , structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", 0, "UInt", PID)
   res := size := 1
   while res != 0 {
      VarSetCapacity(buff, size, 0)
      res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation, "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
   }
   NumberOfHandles := NumGet(buff)
   VarSetCapacity(filePath, 512)
   Loop % NumberOfHandles {
      ProcessId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
      if (PID = ProcessId) {
         HandleValue := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
         DllCall("DuplicateHandle", "Ptr", hProcess, "Ptr", HandleValue, "Ptr", DllCall("GetCurrentProcess")
                                  , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS)
         if DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
            str .= RegExReplace(filePath, "^\\\\\?\\") "`n"
         DllCall("CloseHandle", "Ptr", lpTargetHandle)
      }
   }
   DllCall("CloseHandle", "Ptr", hProcess)
   Return str
}
I tried to study each line in your function to understand what is going on. Some relevant references I found
https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle
https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm
https://stackoverflow.com/questions/4120849/whats-the-differences-between-a-process-id-and-a-process-handle
It would help me understand better if you have time to add some brief comments for the steps in the function.

Worth mentioning that there is a VLC setting to show the file URI as window title, and we could of course derive the filepath directly from that. But a disadvantage then is that the filename won't be visible in the title if the path is long. With your function we can keep the VLC window title nice and short but still get the filepath for use in AHK scripting.
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

12 Dec 2019, 20:10

neogna2 wrote: It would help me understand better if you have time to add some brief comments for the steps in the function.
Added some comments and links:

Code: Select all

GetOpenedFiles(PID) {
   static PROCESS_DUP_HANDLE := 0x0040, SystemExtendedHandleInformation := 0x40, DUPLICATE_SAME_ACCESS := 0x2
        , structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", 0, "UInt", PID)
   arr := {}
   res := size := 1
   while res != 0 {
      VarSetCapacity(buff, size, 0) ; get SYSTEM_HANDLE_INFORMATION_EX and SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
                                    ; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_ex.htm
                                    ; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm
      res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation, "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
   }
   NumberOfHandles := NumGet(buff) ; get all opened handles count from SYSTEM_HANDLE_INFORMATION_EX
   VarSetCapacity(filePath, 512)
   Loop % NumberOfHandles {
      ; get UniqueProcessId from SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
      ProcessId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
      if (PID = ProcessId) {
         ; get HandleValue from SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
         HandleValue := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
         DllCall("DuplicateHandle", "Ptr", hProcess, "Ptr", HandleValue, "Ptr", DllCall("GetCurrentProcess")
                                  , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS)
         ; get the file name from the duplicated handle
         if DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
            arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
         DllCall("CloseHandle", "Ptr", lpTargetHandle)
      }
   }
   DllCall("CloseHandle", "Ptr", hProcess)
   for k in arr
      str .= (str = "" ? "" : "`n") . k
   Sort, str
   Return str
}
neogna2 wrote: Your function works reliably in my tests.
Unfortunately I noticed that the function GetFinalPathNameByHandle() hangs in some cases, that makes the script unresponsive. If I have time, I'll think about a workaround.
neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: Get filepath to file(s) used by a process, without handle.exe

13 Dec 2019, 08:05

Thanks for line comments.
teadrinker wrote:
12 Dec 2019, 20:10
Unfortunately I noticed that the function GetFinalPathNameByHandle() hangs in some cases, that makes the script unresponsive. If I have time, I'll think about a workaround.
Describe a case when it hangs and I can try to help troubleshoot it.
Would adding try prevent the hanging (maybe at the cost of not retrieving a path in problem cases)?

Code: Select all

Try {
         if DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
            arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
}
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

13 Dec 2019, 10:28

neogna2 wrote: Describe a case when it hangs and I can try to help troubleshoot it.
It's not that simple. :) I use SciTE4AutoHotkey. To add some features I inject into it AutoHotkey.dll with my code. Such inject causes GetFinalPathNameByHandle hanging. However, handle.exe works without any troubles.
neogna2 wrote: Would adding try prevent the hanging (maybe at the cost of not retrieving a path in problem cases)?
No, this can't help.
neogna2
Posts: 591
Joined: 15 Sep 2016, 15:44

Re: Get filepath to file(s) used by a process, without handle.exe

15 Dec 2019, 13:09

teadrinker wrote:
13 Dec 2019, 10:28
To add some features I inject into it AutoHotkey.dll with my code. Such inject causes GetFinalPathNameByHandle hanging. However, handle.exe works without any troubles.
Ok, that's beyond my troubleshooting ability. The function has worked fine in my VLC script so far. If the function only runs into trouble in such special cases as you describe then it can still be very useful. So I'll keep the "solved" tag on your earlier comment until new information comes in, if you don't mind.
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

15 Dec 2019, 13:25

I don't mind. :) Actually I found workaround — creating a new thread and launch GetFinalPathNameByHandle in it, but this dramatically decreased performance.
meanprogram
Posts: 12
Joined: 30 Aug 2019, 17:17

Re: Get filepath to file(s) used by a process, without handle.exe

23 Jan 2020, 23:20

teadrinker wrote:
11 Dec 2019, 16:57
Tried to implement:

Code: Select all

procName := "vlc.exe"
Process, Exist, % procName
if !(PID := ErrorLevel) {
   MsgBox, Process not found
   ExitApp
}

MsgBox, % Clipboard := GetOpenedFiles(PID)

GetOpenedFiles(PID) {
   static PROCESS_DUP_HANDLE := 0x0040, SystemExtendedHandleInformation := 0x40, DUPLICATE_SAME_ACCESS := 0x2
        , structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", 0, "UInt", PID)
   arr := {}
   res := size := 1
   while res != 0 {
      VarSetCapacity(buff, size, 0)
      res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation, "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
   }
   NumberOfHandles := NumGet(buff)
   VarSetCapacity(filePath, 512)
   Loop % NumberOfHandles {
      ProcessId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
      if (PID = ProcessId) {
         HandleValue := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
         DllCall("DuplicateHandle", "Ptr", hProcess, "Ptr", HandleValue, "Ptr", DllCall("GetCurrentProcess")
                                  , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS)
         if DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
            arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
         DllCall("CloseHandle", "Ptr", lpTargetHandle)
      }
   }
   DllCall("CloseHandle", "Ptr", hProcess)
   for k in arr
      str .= (str = "" ? "" : "`n") . k
   Sort, str
   Return str
}
With this approach I get full path. I wanted to know is there any possibility to leverage this function so that when a user click on Save As, it check whether the file opened is from a c: drive, if yes than dont let them save it.
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

24 Jan 2020, 01:29

meanprogram wrote: With this approach I get full path.
No, you get all the file paths opened by the process.
meanprogram
Posts: 12
Joined: 30 Aug 2019, 17:17

Re: Get filepath to file(s) used by a process, without handle.exe

24 Jan 2020, 02:41

teadrinker wrote:
24 Jan 2020, 01:29
meanprogram wrote: With this approach I get full path.
No, you get all the file paths opened by the process.
I have filtered the path, using IfInstr and return true if path contains "c:\Test"

Now any possibility to restrict user from performing save as operation, can you guide me
User avatar
jNizM
Posts: 3183
Joined: 30 Sep 2013, 01:33
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

24 Jan 2020, 02:52

Yes... use GPO! (e.g. AppLocker)
Because as soon the script is closed / terminated, the restriction does not work.
[AHK] v2.0.5 | [WIN] 11 Pro (Version 22H2) | [GitHub] Profile
meanprogram
Posts: 12
Joined: 30 Aug 2019, 17:17

Re: Get filepath to file(s) used by a process, without handle.exe

24 Jan 2020, 03:04

jNizM wrote:
24 Jan 2020, 02:52
Yes... use GPO! (e.g. AppLocker)
Because as soon the script is closed / terminated, the restriction does not work.
I dont understand, how could Applocker help here. All it can do is to restrict user from using Adibe reader all together, i dont want to restrict that. I want to restrict them fromusing Save As feature
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

24 Jan 2020, 04:17

As I uderstand you want to prevent a menu item executing belonging to another application. This is out of this topic discussion, so you need to create a new topic with the title like «how to prevent a menu item executing by condition», maybe someone will get you any suggestions.
fenchai
Posts: 292
Joined: 28 Mar 2016, 07:57

Re: Get filepath to file(s) used by a process, without handle.exe

30 May 2020, 18:50

was there a fix to the function hanging the script? it seems to freeze the script after 5+ calls
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

31 May 2020, 06:58

Works reliably only when using two processes:

Code: Select all

procName := "explorer.exe"

DetectHiddenWindows, On

Process, Exist, % procName
if !(PID := ErrorLevel) {
   MsgBox, Process not found
   ExitApp
}
info := []
OnMessage( 0x4A, Func("WM_COPYDATA_READ").Bind(info) )
files := GetOpenedFiles(PID, info)
MsgBox, % files != "" ? files : "Failed to get opened files"
ExitApp

GetOpenedFiles(PID, info) {
   static PROCESS_DUP_HANDLE := 0x0040, inherit := true
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", inherit, "UInt", PID)
   fail := 0, prev := skip := "", last := -1, arrSkip := {}
   Loop {
      if !ChildPID {
         ChildPID := ExecScript( GetScript(hProcess, PID, skip) )
         WinWait, ahk_pid %ChildPID%
         try RB := new RemoteBuffer(ChildPID, 4)
         catch e {
            MsgBox, % e.Message
            break
         }
         SendString( RB.ptr, WinExist() )
      }
      Sleep, 100
      try {
         RB.Read(buff, 4)
         last := NumGet(buff, "UInt")
      }
      catch
         last := -1
      if (last = prev) {
         RB := ""
         Process, Close, % ChildPID
         prev := skip := ChildPID := ""
         arrSkip[last] := ""
         for k in arrSkip
            skip .= (skip = "" ? "" : "|") . k
         ++fail
         continue
      }
      prev := last
   } until fail = 5 || info[1]
   DllCall("CloseHandle", "Ptr", hProcess)
   Return info[1]
}

ExecScript(script) {
   shell := ComObjCreate("WScript.Shell")
   exec := shell.Exec("AutoHotkey.exe /ErrorStdOut *")
   exec.StdIn.Write(script)
   exec.StdIn.Close()
   Return exec.ProcessID
}

GetScript(hTargetProc, PID, skip) {
; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_ex.htm
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm
   script =
   (
      #NoTrayIcon
      SetBatchLines, -1
      
      hParentWnd := %A_ScriptHwnd%
      hTargetProc := %hTargetProc%
      hCurrProc := DllCall("GetCurrentProcess")
      targetPID := %PID%
      skip := %skip%
      info := []
      res := size := 1
      structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
      i := 0
      arr := {}
      OnMessage( 0x4A, Func("WM_COPYDATA_READ").Bind(info) )
      
      while !(pData := info[1]) && A_Index < 10
         Sleep, 100
      if !pData
         ExitApp
      
      while res != 0 {
         VarSetCapacity(buff, size, 0)
         res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation := 0x40
                                                        , "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
      }
      handleCount := NumGet(buff)
      VarSetCapacity(filePath, 1026)
      Loop `% handleCount {
         processId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
         if (processId = targetPID) {
            i++
            Loop, parse, skip, |
               if (i - 1 = A_LoopField)
                  continue 2
            handle := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
            DllCall("DuplicateHandle", "Ptr", hTargetProc, "Ptr", handle, "Ptr", hCurrProc
                                     , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS := 0x2)
            if DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
               arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
            DllCall("CloseHandle", "Ptr", lpTargetHandle)
            NumPut(i, pData + 0, "UInt")
         }
      }
      for file in arr
         files .= (files = "" ? "" : "``n") . file
      SendString(files, hParentWnd)
      ExitApp
      
      WM_COPYDATA_READ(info, wp, lp) {
         info[1] := StrGet(NumGet(lp + A_PtrSize*2), "UTF-16")
      }

      SendString(string, hWnd) {
         VarSetCapacity(message, size := StrPut(string, "UTF-16")*2, 0)
         StrPut(string, &message, "UTF-16")
         
         VarSetCapacity(COPYDATASTRUCT, A_PtrSize*3)
         NumPut(size, COPYDATASTRUCT, A_PtrSize, "UInt")
         NumPut(&message, COPYDATASTRUCT, A_PtrSize*2)
         
         DllCall("SendMessage", "Ptr", hWnd, UInt, WM_COPYDATA := 0x4A, "Ptr", 0, "Ptr", &COPYDATASTRUCT)
      }
   )
   Return script
}

WM_COPYDATA_READ(info, wp, lp) {
   info[1] := StrGet(NumGet(lp + A_PtrSize*2), "UTF-16")
}

SendString(string, hWnd) {
   VarSetCapacity(message, size := StrPut(string, "UTF-16")*2, 0)
   StrPut(string, &message, "UTF-16")
   
   VarSetCapacity(COPYDATASTRUCT, A_PtrSize*3)
   NumPut(size, COPYDATASTRUCT, A_PtrSize, "UInt")
   NumPut(&message, COPYDATASTRUCT, A_PtrSize*2)
   
   DllCall("SendMessage", "Ptr", hWnd, UInt, WM_COPYDATA := 0x4A, "Ptr", 0, "Ptr", &COPYDATASTRUCT)
}

class RemoteBuffer
{
   __New(PID, size) {
      static flags := (PROCESS_VM_OPERATION := 0x8) | (PROCESS_VM_WRITE := 0x20) | (PROCESS_VM_READ := 0x10)
           , Params := ["UInt", MEM_COMMIT := 0x1000, "UInt", PAGE_READWRITE := 0x4, "Ptr"]
         
      if !this.hProc := DllCall("OpenProcess", "UInt", flags, "Int", 0, "UInt", PID, "Ptr")
         throw Exception("Can't open remote process PID = " . PID . "`nA_LastError: " . A_LastError, "RemoteBuffer.__New")
      
      if !this.ptr := DllCall("VirtualAllocEx", "Ptr", this.hProc, "Ptr", 0, "Ptr", size, Params*) {
         DllCall("CloseHandle", "Ptr", this.hProc)
         throw Exception("Can't allocate memory in remote process PID = " . PID . "`nA_LastError: " . A_LastError, "RemoteBuffer.__New")
      }
   }
   
   __Delete() {
      DllCall("VirtualFreeEx", "Ptr", this.hProc, "Ptr", this.ptr, "UInt", 0, "UInt", MEM_RELEASE := 0x8000)
      DllCall("CloseHandle", "Ptr", this.hProc)
   }
   
   Read(ByRef localBuff, size, offset = 0) {
      VarSetCapacity(localBuff, size, 0)
      if !DllCall("ReadProcessMemory", "Ptr", this.hProc, "Ptr", this.ptr + offset, "Ptr", &localBuff, "Ptr", size, "PtrP", bytesRead)
         throw Exception("Can't read data from remote buffer`nA_LastError: " . A_LastError, "RemoteBuffer.Read")
      Return bytesRead
   }
   
   Write(pData, size, offset = 0) {
      if !res := DllCall("WriteProcessMemory", "Ptr", this.hProc, "Ptr", this.ptr + offset, "Ptr", pData, "Ptr", size, "PtrP", bytesWritten)
         throw Exception("Can't write data to remote buffer`nA_LastError: " . A_LastError, "RemoteBuffer.Write")
      Return bytesWritten
   }
}
teadrinker
Posts: 4330
Joined: 29 Mar 2015, 09:41
Contact:

Re: Get filepath to file(s) used by a process, without handle.exe

31 May 2020, 10:26

This seems to work well:

Code: Select all

procName := "explorer.exe"

SetBatchLines, -1
Process, Exist, % procName
if !(PID := ErrorLevel) {
   MsgBox, Process not found
   ExitApp
}
MsgBox, % Clipboard := GetOpenedFiles(PID)
ExitApp

GetOpenedFiles(PID) {
; SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_ex.htm
; https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm
   static PROCESS_DUP_HANDLE := 0x0040, SystemExtendedHandleInformation := 0x40, DUPLICATE_SAME_ACCESS := 0x2
        , FILE_TYPE_DISK := 1, structSize := A_PtrSize*3 + 16 ; size of SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
   hProcess := DllCall("OpenProcess", "UInt", PROCESS_DUP_HANDLE, "UInt", 0, "UInt", PID)
   arr := {}
   res := size := 1
   while res != 0 {
      VarSetCapacity(buff, size, 0)
      res := DllCall("ntdll\NtQuerySystemInformation", "Int", SystemExtendedHandleInformation, "Ptr", &buff, "UInt", size, "UIntP", size, "UInt")
   }
   NumberOfHandles := NumGet(buff)
   VarSetCapacity(filePath, 1026)
   Loop % NumberOfHandles {
      ProcessId := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize, "UInt")
      if (PID = ProcessId) {
         HandleValue := NumGet(buff, A_PtrSize*2 + structSize*(A_Index - 1) + A_PtrSize*2)
         DllCall("DuplicateHandle", "Ptr", hProcess, "Ptr", HandleValue, "Ptr", DllCall("GetCurrentProcess")
                                  , "PtrP", lpTargetHandle, "UInt", 0, "UInt", 0, "UInt", DUPLICATE_SAME_ACCESS)
         if DllCall("GetFileType", "Ptr", lpTargetHandle) = FILE_TYPE_DISK
            && DllCall("GetFinalPathNameByHandle", "Ptr", lpTargetHandle, "Str", filePath, "UInt", 512, "UInt", 0)
               arr[ RegExReplace(filePath, "^\\\\\?\\") ] := ""
         DllCall("CloseHandle", "Ptr", lpTargetHandle)
      }
   }
   DllCall("CloseHandle", "Ptr", hProcess)
   for k in arr
      str .= (str = "" ? "" : "`n") . k
   Return str
}
Chiefkes
Posts: 30
Joined: 04 May 2020, 20:01

Re: Get filepath to file(s) used by a process, without handle.exe

03 Dec 2020, 09:43

Hi Teadrinker,

is there a way to speed this up if i'm only looking for a particular filetype: ".wav"?
Chiefkes
Posts: 30
Joined: 04 May 2020, 20:01

Re: Get filepath to file(s) used by a process, without handle.exe

03 Dec 2020, 10:52

Also is there perhaps a reliable way to skip straight past handles open by other processes to the region of system handles pertaining to the PID in question? In my testing the NumberOfHandles loop A_Index normally gets to around 100,000 before it starts matching with the PID

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: filipemb, Google [Bot] and 143 guests