Jump to content
Sign in to follow this  
Ascend4nt

_WinAPI_ProcessGetFileName

Recommended Posts

Ascend4nt

..

Edited by Ascend4nt

Share this post


Link to post
Share on other sites
ZokNy

gr8func, thx!

Share this post


Link to post
Share on other sites
KaFu

Nice, I love those process functions not related to WMI :)...

Here's the AU 3.3 code:

#include-once

; ==================================================================================================
; <_WinAPI_ProcessGetFileName.au3>
;
; Function:
;   _WinAPI_ProcessGetFilename()
;
; Author: Ascend4nt, with OpenProcess/CloseHandle code by lod3n
; ==================================================================================================

; ==================================================================================================
; Func _WinAPI_ProcessGetFilename($vProcessID,$bFullPath=False)
;
; Function to get the process executable filename, or full path name, for Process
;      using DLL calls rather than WMI.
;
; $vProcessID = either a process name ("explorer.exe") or Process ID (2314)
; $bFullPath = If True, return the full path to the executable. If False, just return the process executable filename.
;
; Returns:
;   Success: String - either full path or just executable name, based on $bFullPath parameter
;   Failure: "" empty string, with @error set:
;      @error = 1 = process name not found to 'exist'
;      @error = 2 = DLL call error, use _WinAPI_GetLastError()
;
; Author: Ascend4nt, with OpenProcess/CloseHandle code by lod3n
; ==================================================================================================

Func _WinAPI_ProcessGetFilename($vProcessID,$bFullPath=False)
    ; Not a Process ID? Must be a Process Name
    If Not IsNumber($vProcessID) Then
        $vProcessID=ProcessExists($vProcessID)
        ; Process Name not found (or invalid parameter?)
        If $vProcessID==0 Then Return SetError(1,0,"")
    EndIf
   
    Local $hProcess,$stFilename,$aRet,$sFilename,$sDLLFunctionName
   
    ; Since the parameters and returns are the same for both of these DLL calls, we can keep it all in one function
    If $bFullPath Then
        $sDLLFunctionName="GetModuleFileNameEx"
    Else
        $sDLLFunctionName="GetModuleBaseName"
    EndIf   
   
    ; Get process handle (lod3n)
    Local $hProcess = DllCall('kernel32.dll','ptr', 'OpenProcess','int', BitOR(0x400,0x10),'int', 0,'int', $vProcessID)
    If @error Or Not IsArray($hProcess) Then Return SetError(2,0,"")
   
    ; Create 'receiving' string buffers and make the call
    ;If @AutoItUnicode Then
        ; Path length size maximum in Unicode is 32767 (-1 for NULL)
        $stFilename=DllStructCreate("wchar[32767]")
        ; we append 'W' to function names because these are the 'Wide' (Unicode) variants
        $aRet=DllCall("Psapi.dll","dword",$sDLLFunctionName & 'W', _
            "ptr",$hProcess[0],"ptr",Chr(0),"ptr",DllStructGetPtr($stFilename),"dword",32767)
    ;Else
    #cs
        ; Path length size maximum otherwise is 260 (-1 for NULL)
        $stFilename=DllStructCreate("char[260]")
        $aRet=DllCall("Psapi.dll","dword",$sDLLFunctionName, _
            "ptr",$hProcess[0],"ptr",Chr(0),"ptr",DllStructGetPtr($stFilename),"dword",260)
    EndIf
    #ce

    ; Error from either call? Cleanup and exit with error
    If @error Or Not IsArray($aRet) Then
        ; Close the process handle
        DllCall('kernel32.dll','ptr', 'CloseHandle','ptr', $hProcess[0])
        ; DLLStructDelete()'s:
        $stFilename=0
        $hProcess=0
        Return SetError(2,0,"")
    EndIf
   
    ;$aRet[0] = size of string copied over, minus null-terminator
    ;$stFilename should now contain either the filename or full path string (based on $bFullPath)
    $sFilename=DllStructGetData($stFilename,1)
       
    DllCall('kernel32.dll','ptr', 'CloseHandle','ptr', $hProcess[0])
    ; DLLStructDelete()'s
    $stFilename=0
    $hProcess=0

    Return SetError(0,0,$sFilename)
EndFunc

; ==================================================================================================
; Test:
; ==================================================================================================

ConsoleWrite("Process full path for svchost.exe:" & _WinAPI_ProcessGetFilename("svchost.exe",True) & @CRLF)
Edited by KaFu

Share this post


Link to post
Share on other sites
Ascend4nt

Thanks guys. I've found the solution for for Windows x64 variants, in particular GetProcessImageFileName , but there's one issue: it returns device information rather than a 'drive:' at the start. In other words, this would be returned for "C:\Windows\System32\cmd.exe":

\Device\Harddisk0\Partition1\Windows\System32\cmd.exe

So I'm wondering - does anyone know an easy way to convert THAT into a regular path?

Help is appreciated! Thanks

Ascend4nt

Share this post


Link to post
Share on other sites
KaFu
Ascend4nt

Actually, I just finished working on a series of functions that is necessary to accomplish this feat. It's much tougher than I had thought!

*EDIT: Argh, scratch the first part - my goof! I didn't realize 'QueryDosDeviceW' would take drive letters, so I was feeding it the GUID names. I could simply avoid all that nonsense and use DriveGetDrives("ALL")+QueryDosDeviceW instead of that whole slew of DLL calls. Thanks for making me think twice Kafu :)

First I have to build an array of volume GUID names ("\\?\Volume{GUID}\"), Physical Path names ("C:\" etc), and Device names ("\Device\HarddiskVolume1","\Device\CDRom0", etc) by way of these DLL calls:

FindFirstVolumeW (get first GUID & handle)

GetVolumePathNamesForVolumeNameW (get physical drive)

QueryDosDeviceW (get Device Name)

FindNextVolumeW (continue with next GUID)

(loop back to 2nd call until no more left, then:)

FindVolumeClose

SECOND (as if that wasn't enough of a pain), I do the calls to get the device-name path for the process:

OpenProcess

GetProcessImageFileNameW

CloseProcess

And then finally a call to translate the string from that using the array from the first loop. It sure seems like overkill doesn't it! But it works... I may post code up once I can make it look purty.. and rewrite the array-build to use a Global array and a bool indicating whether or not it's been initialized..

take it easy

-Ascend4nt

*On Edit: I can't seem to - for the life of me - get a path for 'csrss.exe', using either of the methods I've listed! I know 'DTaskManager' lists it properly, but I'd sure like to know what they are using to get it... maybe WMI is needed for some things after all?

Edited by ascendant

Share this post


Link to post
Share on other sites
KaFu

On 32bit the key to acquire command-line for csrss.exe seems to be in the _GetPrivilege_SEDEBUG() func by wraithdu. Don't know if it works for 64bit. Attached an example from Manko and wraithdu.

#RequireAdmin

#include <WinAPI.au3>   
#include <array.au3>

Global Const $PROCESS_VM_READ=0x10
Global Const $PROCESS_QUERY_INFORMATION = 0x400

_GetPrivilege_SEDEBUG()

global $output[1][1]
$var = ProcessList()

ReDim $output[UBound($var)][3]

For $i = 1 to UBound($var) - 1
    $output[$i][0] = $var[$i][0]
    $output[$i][1] = $var[$i][1]
    $output[$i][2] = _GetCommandLineFromPID($output[$i][1])
Next

_ArrayDisplay($output)






; ==================================================================================================
; _GetCommandLineFromPID($PID)
; ==================================================================================================
; Manko
; http://www.autoitscript.com/forum/index.php?showtopic=88214&view=findpost&p=633581




Func _GetCommandLineFromPID($PID)
    $ret1=DllCall("kernel32.dll", 'int', 'OpenProcess', 'int', $PROCESS_VM_READ+$PROCESS_QUERY_INFORMATION, 'int', False, 'int', $PID)
    $tag_PROCESS_BASIC_INFORMATION = "int ExitStatus;" & _
                                     "ptr PebBaseAddress;" & _
                                     "ptr AffinityMask;" & _
                                     "ptr BasePriority;" & _
                                     "ulong UniqueProcessId;" & _
                                     "ulong InheritedFromUniqueProcessId;"
    $PBI=DllStructCreate($tag_PROCESS_BASIC_INFORMATION)
    DllCall("ntdll.dll", "int", "ZwQueryInformationProcess", "hwnd", $ret1[0], "int", 0, "ptr", DllStructGetPtr($PBI), "int", _
                                                                                                DllStructGetSize($PBI), "int",0)
    $dw=DllStructCreate("ptr")
    DllCall("kernel32.dll", "int", "ReadProcessMemory", "hwnd", $ret1[0], _
                            "ptr", DllStructGetData($PBI,2)+0x10, _; PebBaseAddress+16 bytes <-- ptr _PROCESS_PARAMETERS
                            "ptr", DllStructGetPtr($dw), "int", 4, "ptr", 0)
    $unicode_string = DllStructCreate("ushort Length;ushort MaxLength;ptr String")
    DllCall("kernel32.dll", "int", "ReadProcessMemory", "hwnd", $ret1[0], _
                                 "ptr", DllStructGetData($dw, 1)+0x40, _; _PROCESS_PARAMETERS+64 bytes <-- ptr CommandLine Offset (UNICODE_STRING struct) - Win XP / Vista.
                                 "ptr", DllStructGetPtr($unicode_string), "int", DllStructGetSize($unicode_string), "ptr", 0)
    $ret=DllCall("kernel32.dll", "int", "ReadProcessMemory", "hwnd", $ret1[0], _
                                 "ptr", DllStructGetData($unicode_string, "String"), _; <-- ptr Commandline Unicode String
                                 "wstr", 0, "int", DllStructGetData($unicode_string, "Length") + 2, "int*", 0); read Length + terminating NULL (2 bytes in unicode)
    DllCall("kernel32.dll", 'int', 'CloseHandle', "hwnd", $ret1[0])
    If $ret[5] Then Return $ret[3] ; If bytes returned, return commandline...
    Return ""               ; Getting empty string is correct behaviour when there is no commandline to be had...
EndFunc ; ...or if we're not allowed for some reason.... vista...





; ==================================================================================================
; _GetPrivilege_SEDEBUG()
; ==================================================================================================
; wraithdu
; http://www.autoitscript.com/forum/index.php?showtopic=88214&view=findpost&p=634408
;
; ####################### Below Func is Part of example - Needed to get commandline from more processes. ############
; ####################### Thanks for this function, wraithdu! (Didn't know it was your.) smile.gif #########################

Func _GetPrivilege_SEDEBUG()
    Local $tagLUIDANDATTRIB = "int64 Luid;dword Attributes"
    Local $count = 1
    Local $tagTOKENPRIVILEGES = "dword PrivilegeCount;byte LUIDandATTRIB[" & $count * 12 & "]"; count of LUID structs * sizeof LUID struct
    Local $TOKEN_ADJUST_PRIVILEGES = 0x20
    Local $call = DllCall("advapi32.dll", "int", "OpenProcessToken", "ptr", _WinAPI_GetCurrentProcess(), "dword", $TOKEN_ADJUST_PRIVILEGES, "ptr*", "")
    Local $hToken = $call[3]
    $call = DllCall("advapi32.dll", "int", "LookupPrivilegeValue", "str", Chr(0), "str", "SeDebugPrivilege", "int64*", "")
  ;msgbox(0,"",$call[3] & " " & _WinAPI_GetLastErrorMessage())
    Local $iLuid = $call[3]
    Local $TP = DllStructCreate($tagTOKENPRIVILEGES)
    Local $LUID = DllStructCreate($tagLUIDANDATTRIB, DllStructGetPtr($TP, "LUIDandATTRIB"))
    DllStructSetData($TP, "PrivilegeCount", $count)
    DllStructSetData($LUID, "Luid", $iLuid)
    DllStructSetData($LUID, "Attributes", $SE_PRIVILEGE_ENABLED)
    $call = DllCall("advapi32.dll", "int", "AdjustTokenPrivileges", "ptr", $hToken, "int", 0, "ptr", DllStructGetPtr($TP), "dword", 0, "ptr", Chr(0), "ptr", Chr(0))
    Return ($call[0] <> 0); $call[0] <> 0 is success
EndFunc ;==>_GetPrivilege_SEDEBUG
Edited by KaFu

Share this post


Link to post
Share on other sites
Ascend4nt

KaFu, thanks for that! Very interesting... at first I just tested their function, and realized it only gives the path that was used to execute the program, so I figured that wouldn't work in finding the full paths, but then I pulled the _GetPrivilege_SEDEBUG() function out, added it to my exe/path name examples, and wa-la, it works! =)

This is awesome. Problem is, I don't want to require Admin privileges to run my program, so I'd like to be able to just detect if the program has admin privileges and then if not, to avoid calling _GetPrivilege_SEDEBUG() altogether and returning the limited subset of paths (hmm.. could also 'search' for the executable using the PATH environment variable - but then thats making assumptions..)

Problem now lies in the IsAdmin() function, which in Vista (per documentation):

"It returns 1 under Windows VISTA only if the script contains #RequireAdmin"

So that makes running the IsAdmin() function pointless if you already know going in that you have admin privileges. Argh.. this is giving me a headache! hehe

-A

Share this post


Link to post
Share on other sites
KaFu

Haven't found an answer yet, but found some interesting reading that might lead to a solution:

'Escape from DLL Hell with Custom Debugging and Instrumentation Tools and Utilities' (the title say's it all :))

http://msdn.microsoft.com/en-us/library/bb985842.aspx

Esp. chapter 'Getting Process Information with WTS APIs' seems interesting...

Cheers

Share this post


Link to post
Share on other sites
wraithdu

Since you need Admin privs to get SEDEBUG, then your Vista users who have UAC enabled are going to have to run the app with elevated privileges. I think testing with IsAdmin() should work just fine for what you want to do.

Share this post


Link to post
Share on other sites
Ascend4nt

..

Edited by Ascend4nt

Share this post


Link to post
Share on other sites
Ascend4nt

3/8/09: Updated both _WinAPI_ProessGetFilename and _WinAPI_ProcessGetPathname functions to be AutoIT 3.3 compatible (also 3.2.12.1 UNICODE)

Share this post


Link to post
Share on other sites
trancexx

3/8/09: Updated both _WinAPI_ProessGetFilename and _WinAPI_ProcessGetPathname functions to be AutoIT 3.3 compatible (also 3.2.12.1 UNICODE)

More correct of "ptr",Chr(0) is "ptr", 0

You don't need $stFilename. Instead of creating that strucure leave that job to AutoIt. You can replace ),"ptr",DllStructGetPtr($stFilename) with "wstr","" and collect data this like $sFilename = $aRet[3]

IsArray($aRet) is not needed (@error already did that job), but you can check return value - in your case for $sDLLFunctionName return value 0 indicates failure.

... hope you don't mind me and my observations.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
Ascend4nt

More correct of "ptr",Chr(0) is "ptr", 0

You don't need $stFilename. Instead of creating that strucure leave that job to AutoIt. You can replace ),"ptr",DllStructGetPtr($stFilename) with "wstr","" and collect data this like $sFilename = $aRet[3]

IsArray($aRet) is not needed (@error already did that job), but you can check return value - in your case for $sDLLFunctionName return value 0 indicates failure.

... hope you don't mind me and my observations.

Hmm.. are you certain about the "wstr" part? I know in *sending* strings to DLL calls you can use that option, but when you need to *receive* data - there has to be some knowledge beforehand as to what size the receiving buffer needs to be. And I'm betting AutoIT doesn't have a database of API function's buffer sizes..

As for the other parts, thanx, duly noted. I'll forego the extra checks next time :P

Share this post


Link to post
Share on other sites
TheSaint

Thanks for sharing! :P


AutoIt.4.Life Clubrooms - Life is like a Donut (secret key)

Make sure brain is in gear before opening mouth!
Remember, what is not said, can be just as important as what is said.

Spoiler

What is the Secret Key? Life is like a Donut

If I put effort into communication, I expect you to read properly & fully, or just not comment.
Ignoring those who try to divert conversation with irrelevancies.
If I'm intent on insulting you or being rude, I will be obvious, not ambiguous about it.
I'm only big and bad, to those who have an over-active imagination.

I may have the Artistic Liesense ;) to disagree with you. TheSaint's Toolbox (be advised many downloads are not working due to ISP screwup with my storage)

userbar.png

Share this post


Link to post
Share on other sites
trancexx

Hmm.. are you certain about the "wstr" part? I know in *sending* strings to DLL calls you can use that option, but when you need to *receive* data - there has to be some knowledge beforehand as to what size the receiving buffer needs to be. And I'm betting AutoIT doesn't have a database of API function's buffer sizes..

As for the other parts, thanx, duly noted. I'll forego the extra checks next time :P

Size of str buffer is 65536 bytes and wstr is 131072 bytes (twice of str).

I guess devs could change that with new releases of AutoIt but that is highly unlikely.


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
Ascend4nt

Size of str buffer is 65536 bytes and wstr is 131072 bytes (twice of str).

I guess devs could change that with new releases of AutoIt but that is highly unlikely.

Wow, really? I honestly never suspected that. Is that documented somewhere? Thx for pointing this out

-Ascend4nt

Share this post


Link to post
Share on other sites
trancexx

No, I made that up just to show off.

Sounds convincing?


♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites
Ascend4nt
:P wth? Is that sarcasm or are you serious. Because I was asking an honest question. Where did you get the info from (assuming it isn't a joke?)

Share this post


Link to post
Share on other sites
trancexx

:P wth? Is that sarcasm or are you serious. Because I was asking an honest question. Where did you get the info from (assuming it isn't a joke?)

It's in the help file. DllCall() - description of types.

You can test it like this maybe (with all limitations of this example):

$aCall = DllCall("kernel32.dll", "none", "RtlFillMemory", _
        "str", "", _
        "dword", 65536, _ ; 65536 + add to crash
        "ubyte", 65) ; e.g. A

ConsoleWrite(@error & @CRLF)
ConsoleWrite(StringLen($aCall[1]) & @CRLF)

Add few more bytes (characters) to crash it.

Edited by trancexx

♡♡♡

.

eMyvnE

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×