Jump to content

Error getting PID from _ShellExecuteEX


LarryK
 Share

Recommended Posts

Hi,

I need to open a file with it's reference program and get it's PID. I need the PID so I can close the application later with ProcessClose($PID).

I have read the forums and discovered the _ShellExecuteEx() script posted by Ascend4nt (Thank you!) and it seems to work great except for .url files. When executing a file with the .url extension, it does not provide a PID.

$PID = _ShellExecuteEx("c:\amazon.url") returns 0

I have tried setting the default browser to Internet Explorer and Firefox with the same result. What am I missing?

Thank you,

Larry

Link to comment
Share on other sites

Hello LarryK,

First of all, since _ShellExecuteEx() is not a native function of AutoIt, you should provide the code to your custom _ShellExecuteEx() Function. Did you remember to code your custom _ShellExecuteEx() Function to return a process ID?

The Native function ShellExecute() does not normally return a process ID, rather it returns a 1 or 0 depending on if it was able to execute the command. If your looking for a return on the process ID in a native funtion, you might want to look into Run()

Realm

My Contributions: Unix Timestamp: Calculate Unix time, or seconds since Epoch, accounting for your local timezone and daylight savings time. RegEdit Jumper: A Small & Simple interface based on Yashied's Reg Jumper Function, for searching Hives in your registry. 

Link to comment
Share on other sites

Not all filetypes will return a PID # for the extended ShellExecute unfortunately.

You might try looking into 'AssocQueryString' to find out what program is launched when a .URL file is ShellExecute'd.

Link to comment
Share on other sites

Realm,

Sorry for not providing the script. Here it is. (This code sample was written by Ascend4nt and based on the original function by MrCreatoR.)

; =================================================================================================
; Func _ShellExecuteEx($sCmd, $sParams = "", $sFolder = "", $sVerb = "", $iState = @SW_SHOWNORMAL,$bCloseProcessHandle=True)
;
; Parameters are the same as ShellExecute(), except for the addition of:
;
; $bCloseProcessHandle = If True (recommended unless planning on using Process Handles),
;           then the Process Handle (if received) is closed
;       If False, the Process Handle (if received) is returned - this can be used to do additional work with Processes
;       Usage is mostly recommended for the _ShellExecuteExWait() function and/or getting exit code.
;
; Return is different from ShellExecute() in the following way:
;   Success: @error = 0, and either the process ID (if $bCloseProcessHandle is True, and process ID received) is returned,
;     or a 2-element array (if $bCloseProcessHandle is False):
;       $array[0]=Process ID if new process started (and process handle+ID received),
;       $array[1]=Process Handle if new process started (and process handle received)
;   Failure: @error set and 0 is returned
;       @error = 1 = parameters error
;       @error = 2 = call failed (probably due to parameter error. can use _WinAPI_GetLastError())
;
; NOTE: Recommended to run on Windows 2000 or higher because:
;   According to Microsoft at http://msdn2.microsoft.com/en-us/library/bb762154.aspx,
;   Windows 95/98/Me: ShellExecuteEx is supported by the Microsoft Layer for Unicode (MSLU).
;   To use this, you must add certain files to your application,
;       as outlined in Microsoft Layer for Unicode on Windows Me/98/95 Systems.
;   So it appears it will break on those machines without MSLU(?)
;
;   Initial Code from MrCreatoR on AutoIt Forum Topic:: http://www.autoitscript.com/forum/index.php?showtopic=69868
;   Enhancements/Modifications by: Ascend4nt
;   (including process handle/ID extract & close-handle code, plus Unicode/path enhancements, & CoInitializeEx call)
; =================================================================================================

Func _ShellExecuteEx($sCmd, $sParams = "", $sFolder = "", $sVerb = "", $iState = @SW_SHOWNORMAL,$bCloseProcessHandle=True)
    Local $stINFO,$stVerb,$stPath,$stArgs,$stWDir,$aRet,$hWnd=0,$aProcessArray[2]=[0,0]
    Local $iParamLen,$iCmdLen,$iFolderLen


    $iParamLen=StringLen($sParams)
    ; Verify string lengths are less than maximum.
    ; Through testing, 1997 (1996+1 NULL-term) is the maximum parameter length for this call (Unicode)
    If $iParamLen>1996 Then Return SetError(1,0,0)

    $iCmdLen=StringLen($sCmd)
    ; Verify string lengths are less than maximum. [MAX_PATH is 260, but Unicode allows exceptions]
    ; 32767 max length for Unicode strings if prefixed with '\\?\'
    If $iCmdLen>259 Then
        ; 32767-NULL=32766 - 4 (\\?\) = 32762
        If $iCmdLen>(32766-4) Then Return SetError(1,0,0)
        $sCmd='\\?\' & $sCmd
    EndIf

    $iFolderLen=StringLen($sFolder)
    ; Verify string lengths are less than maximum. [MAX_PATH is 260, but Unicode allows exceptions]
    ; 32767 max length for Unicode strings if prefixed with '\\?\'
    If $iFolderLen>259 Then
        ; 32767-NULL=32766 - 4 (\\?\) = 32762
        If $iFolderLen>(32766-4) Then Return SetError(1,0,0)
        $sFolder='\\?\' & $sFolder
    EndIf

    ; Setup string structures
    $stVerb = DllStructCreate("wchar["&(StringLen($sVerb)+1)&"]")
    $stPath = DllStructCreate("wchar[" &($iCmdLen+1)& "];wchar")
    $stArgs = DllStructCreate("wchar[" &($iParamLen+1)& "];wchar")
    $stWDir = DllStructCreate("wchar[" &($iFolderLen+1)& "];wchar")

    ; Initialize string structures (which are then used by pointer in the larger SHELLEXECUTEINFO structure)
    DllStructSetData($stVerb, 1, $sVerb)
    DllStructSetData($stPath, 1, $sCmd)
    DllStructSetData($stWDir, 1, $sFolder)
    DllStructSetData($stArgs, 1, $sParams)

    ; SHELLEXECUTEINFO structure
    $stINFO = DllStructCreate("ulong;ulong;long;ptr;ptr;ptr;ptr;long;long;ptr;ptr;long;ulong;long;long")

    ; SHELLEXECUTEINFO structure initialize
    DllStructSetData($stINFO, 1, DllStructGetSize($stINFO)) ; cbSize, size (in bytes) of structure
    ; ------------------------------------------------------------------------------------------------------
    ; fMask Options:
    ;   0x40 = SEE_MASK_NOCLOSEPROCESS. The 15th element in structure (hProcess) will be set with the Process handle
    ;       NOTE: per MSDN, this handle *must* be closed by the caller. (similar to how "OpenProcess" must use "CloseProcess")
    ;   0x400 = SEE_MASK_FLAG_NO_UI = Do not display an error message box if an error occurs.
    ;       This is not default ShellExecute() behavior, which will display the error message box
    ; ------------------------------------------------------------------------------------------------------
    DllStructSetData($stINFO, 2, BitOR(0x40,0x400)) ; fMask
    ; HWND - MSDN: A window handle to any message boxes that the system might produce while executing this function.
    DllStructSetData($stINFO, 3, $hWnd) ; Is this supposed to *receive* instead of send? I have yet to get clarity on this.
    DllStructSetData($stINFO, 4, DllStructGetPtr($stVerb))  ; lpVerb: pointer to the verb string
    DllStructSetData($stINFO, 5, DllStructGetPtr($stPath))  ; lpFile: pointer to the $cmd string
    DllStructSetData($stINFO, 6, DllStructGetPtr($stArgs))  ; lpParameters: pointer to the parameters/arguments string
    DllStructSetData($stINFO, 7, DllStructGetPtr($stWDir))  ; lpDirectory: pointer to working directory string
    DllStructSetData($stINFO, 8, $iState)   ; nShow = state to show window as

#cs
    ; ------------------------------------------------------------------------------------------------------
    ; Per MSDN Documentation, the following call should be done prior to calling ShellExecuteEx:
    ; CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE) ; COINIT_APARTMENTTHREADED = 0x2,COINIT_DISABLE_OLE1DDE = 0x4
    ; Reason:
    ;   "Because ShellExecuteEx can delegate execution to Shell extensions (data sources, context menu handlers, verb implementations)
    ;   that are activated using Component Object Model (COM), COM should be initialized before ShellExecuteEx is called.
    ;   Some Shell extensions require the COM single-threaded apartment (STA) type. In that case, COM should be initialized as shown here.
    ;   There are certainly instances where ShellExecuteEx does not use one of these types of Shell extension and those instances would not
    ;   require COM to be initialized at all. Nonetheless, it is good practice to always initalize COM before using this function."
    ; ------------------------------------------------------------------------------------------------------
#ce
    DllCall("ole32.dll", "int", "CoInitializeEx", "ptr", Chr(0), "dword", BitOR(2,4))
    ; I don't care if it succeeds. Doesn't seem to make much difference even to call it.

    $aRet=DllCall("shell32.dll", "int", "ShellExecuteExW", "ptr", DllStructGetPtr($stINFO))

    If @error Or Not $aRet[0] Then
        ; DLLStructDelete()'s:
        $stINFO=0
        $stVerb=0
        $stPath=0
        $stArgs=0
        $stWDir=0
        Return SetError(2,@error,0)
    EndIf
    ; Get Process Handle, if one exists (non-NULL if new process started, otherwise
    ;   NULL if app that performs 'verb' is already running, or is perhaps a 'properties' dialog etc)
    $aProcessArray[1]=DllStructGetData($stINFO,15)
    ; Get Process ID from Handle
    If ($aProcessArray[1]) Then
        $aRet=DllCall("Kernel32.dll","dword","GetProcessId","long",$aProcessArray[1])
        If IsArray($aRet) Then $aProcessArray[0]=$aRet[0]
    EndIf

    ;ConsoleWrite("Handle passed to function:" & Number($hWnd) & ", Handle AFTER call:" & Number(DllStructGetData($stINFO,3)) & @CRLF)
    ;ConsoleWrite("Process Handle:" & Number($hProcess) & ", Process ID:" & Number($vProcessID) & @CRLF)

    ; Close Handle
    If $bCloseProcessHandle And $aProcessArray[1] Then DllCall('kernel32.dll','ptr', 'CloseHandle','ptr', $aProcessArray[1])

    ; DLLStructDelete()'s:
    $stINFO=0
    $stVerb=0
    $stPath=0
    $stArgs=0
    $stWDir=0
    If ($bCloseProcessHandle) Then Return SetError(0,0,$aProcessArray[0])
    SetError(0,0)
    Return $aProcessArray
EndFunc

The native ShellExecute() function runs files correctly but does not return a PID. (ex: ShellExecute("c:\amazon.url") starts a browser that goes to Amazon.com.) Run() returns a PID, but works with executable files types like .exe, .bat, etc. _ShellExecuteEx() works like the native ShellExecute() funciton but returns a PID under most circumstances. Here are two examples of calls I've tried:

$PID = _ShellExecuteEx("c:\test.xls") ; correctly returns a PID

$PID = _ShellExecuteEx("c:\amazon.url") ; does not return a PID ($PID = 0)

With the .url file, the browser starts and the Amazon home page is loaded like I would expect, but the $PID is 0. I am a novice with system level coding, so I was wondering if I was missing something obvious. Or, is there a better way to execute a file type like a .url, .xls or .doc file that will return a PID?

Link to comment
Share on other sites

Look at the second to last line in the function and look at what it's returning. It's returning an array, not a variable. Do a _ArrayDisplay to see what it's returning. Also, look at the description at the top of the function itself, it tells you what the return is. You're using it wrong.

If I posted any code, assume that code was written using the latest release version unless stated otherwise. Also, if it doesn't work on XP I can't help with that because I don't have access to XP, and I'm not going to.
Give a programmer the correct code and he can do his work for a day. Teach a programmer to debug and he can do his work for a lifetime - by Chirag Gude
How to ask questions the smart way!

I hereby grant any person the right to use any code I post, that I am the original author of, on the autoitscript.com forums, unless I've specifically stated otherwise in the code or the thread post. If you do use my code all I ask, as a courtesy, is to make note of where you got it from.

Back up and restore Windows user files _Array.au3 - Modified array functions that include support for 2D arrays.  -  ColorChooser - An add-on for SciTE that pops up a color dialog so you can select and paste a color code into a script.  -  Customizable Splashscreen GUI w/Progress Bar - Create a custom "splash screen" GUI with a progress bar and custom label.  -  _FileGetProperty - Retrieve the properties of a file  -  SciTE Toolbar - A toolbar demo for use with the SciTE editor  -  GUIRegisterMsg demo - Demo script to show how to use the Windows messages to interact with controls and your GUI.  -   Latin Square password generator

Link to comment
Share on other sites

Ascend4nt,

Thank you for the reply. Could you please tell me where I might find more information about 'AssocQueryString'?

Is this a property that I could set that would possibly return a PID via _ShellExecuteEx()?

Also, thank you for posting the code originally! It has been a BIG help!

Link to comment
Share on other sites

Actually, BrewMan, he's using it correctly. It will only return an array if the last parameter is False. (it defaults to True)

Also - I just realized, .URL is run using 'rundll32.exe' on 'ieframe.dll', so basically it shouldn't return a PID, or at least not a proper one at that. AssocQueryString returns '"C:\Windows\System32\rundll32.exe" "C:\Windows\System32\ieframe.dll",OpenURL %l'

_WinAPI_FindExecutable() returns 'C:\Windows\System32\ieframe.dll'

Looking it up using HKEY_CLASSES_ROOT\InternetShortcut\Open\Command returns the same as AssocQueryString.. so its probably best to look up the .URL file's destination protocol (http, ftp) in the registry to find the real program that would execute.

And reading the shortcut location from a .URL file is pretty simple too:

$sURL=IniRead($sURLFullPath,"InternetShortcut","URL","")

Just grab the part before the semicolon, then look it up via AssocQueryString or read the default value from HKEY_CLASSES_ROOT\[protocol]\shell\open\command (where [protocol] is http, ftp, etc)

If you need to pull out the protocol, you can use a PCRE like this:

$sProtocol=StringRegExpReplace($sURL,'([^:]+):.*','$1')

*edit: Okay, here's a freebie. This is an older UDF function I have:

; =================================================================================================================
; Func _WinAPI_AssocQueryString($sCommandOrExt,$sShellVerb="open")
;
; Given an extension (dot included, ex: .txt) or a 'type:' (without the colon, ex: mailto, http),
;   this function returns the commandline to use to call said type.
;   Usually you replace %1,%l,%L,%d,%D with the string containing the filename or string to execute
;
; NOTE: .SCF files are a special case that should be tested for outside of this function
;   Otherwise, this will return 'explorer.exe' and using it will cause a nasty popup
;   message to occur, anoying the user.
;
; $sCommandOrExt = Either the command/type without the colong (http, mailto, ftp), or
;   an extension, WITH the leading dot (.txt,.cmd,.doc)
; $sShellVerb = verb to lookup. Most often this will be "open", but can also be
;   "edit","print","properties", or whatever else the particular type has available for verbs
;   [NOTE: only certain files have more than "open" as a verb]
;
; Return:
;   Succes: Commandline used to invoke the extension or command/type, @error=0
;   Failure: "" (Empty string). @error may be set:
;       @error = 1 = invalid parameter
;       @error = 2 = DLLCall error, @extended contains actual DLLCall() error (check AutoIt Help)
;       @error = 3 = Failure returned from API call
;
; Author: Ascend4nt
; =================================================================================================================

Func _WinAPI_AssocQueryString($sCommandOrExt,$sShellVerb="open")
    If Not IsString($sCommandOrExt) Or $sCommandOrExt="" Then Return SetError(1,0,"")

    ; 0x400 (IGNOREUNKNOWN) required for WinVista to return appropriate value
    ;   otherwise it will return info even for unknowns (works on XP either way (0 or 0x400))
    Local $aRet=DllCall("shlwapi.dll","long","AssocQueryStringW","int",0x400,"int",1,"wstr",$sCommandOrExt, _
        "wstr",$sShellVerb,"wstr",'',"dword*",65536)
    If @error Then Return SetError(2,@error,"")
    If $aRet[0] Or $aRet[6]=0 Then Return SetError(3,0,"")
    Return $aRet[5]
EndFunc
Edited by Ascend4nt
Link to comment
Share on other sites

Ascend4nt,

Thank you for your help!

When I use your _WinAPI_AssocQueryString() funciton with a ".url" extension, I get a string containing:

"C:\WINDOWS\system32\rundll32.exe" "C:\WINDOWS\system32\ieframe.dll",OpenURL %l

From reading the function header, I would replace the %I with "c:\amazon.url". Then, how would I execute the string? Would the function that I use to execute the string return a valid PID?

I apologize for being such a newbie about this...

Larry

Link to comment
Share on other sites

You'd be getting a Process ID # for rundll32 using that method. I think you should reread my post since I explained that you need to get the protocol instead to find the program that will be run.

Additionally, keep in mind that many browsers will not open a second instance, and you will never get a proper Process ID # by running the program with a new URL. Its probably best to just find out what browser will be (or is) open, then ShellExecute() the URL, then get the PID from open instance(s) of the given program.

For instance, call _WinAPI_AssocQueryString() with 'http', then grab the executable out of it. For example, on my system, the default browser is firefox, so under the 'http' protocol is the following command line: "C:\Program Files (x86)\Mozilla Firefox\firefox.exe" -requestPending -osint -url "%1"

So I could use this to get the Process name:

$sCmd='"C:\Program Files (x86)\Mozilla Firefox\firefox.exe" -requestPending -osint -url "%1"'
$sProcess=StringRegExp($sCmd,'\\([^\\]+\.exe)',1)
If Not @error Then $sProcess=$sProcess[0]
ConsoleWrite("Process name:"&$sProcess&@CRLF)

After that, we could ShellExecute() the .URL file, then check with ProcessExists($sProcess) or ProcessList($sProcess) in the case where multiple instances of a program can be opened. In the latter case, you'd have to do further processing to see which PID is the correct one, or you can alternately use the $sCmd with Run() and replace %1 or %l etc with the URL (NOT the URL file - the actual URL string) and hope that the PID that is returned is the correct one, and not just the PID of a program that hands the command-line over to an already-open instance.

Anyway, it can be a bit confusing, so you'll have to figure out just what it is you want to do and which is the best method of doing it.

*edit: PCRE adjustment

Edited by Ascend4nt
Link to comment
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
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...