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