Jump to content

_WinAPI_FindExecutable Replacement


 Share

Recommended Posts

_WinAPI_FindExecutable is a useful function. However, if the file doesn't exist then you can't use it. Of course, you can go and read the registry a few times to get the information that you need but there's already a function available in the Shell API that lets you do this; it's called AssocQueryString. Here's an example of how to use it.

Global Const $ASSOCF_INIT_NOREMAPCLSID = 0x00000001
Global Const $ASSOCF_INIT_BYEXENAME = 0x00000002
Global Const $ASSOCF_OPEN_BYEXENAME = 0x00000002
Global Const $ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004
Global Const $ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008
Global Const $ASSOCF_NOUSERSETTINGS = 0x00000010
Global Const $ASSOCF_NOTRUNCATE = 0x00000020
Global Const $ASSOCF_VERIFY = 0x00000040
Global Const $ASSOCF_REMAPRUNDLL = 0x00000080
Global Const $ASSOCF_NOFIXUPS = 0x00000100
Global Const $ASSOCF_IGNOREBASECLASS = 0x00000200

Global Const $ASSOCSTR_COMMAND = 1
Global Const $ASSOCSTR_EXECUTABLE = 2
Global Const $ASSOCSTR_FRIENDLYDOCNAME = 3
Global Const $ASSOCSTR_FRIENDLYAPPNAME = 4
Global Const $ASSOCSTR_NOOPEN = 5
Global Const $ASSOCSTR_SHELLNEWVALUE = 6
Global Const $ASSOCSTR_DDECOMMAND = 7
Global Const $ASSOCSTR_DDEIFEXEC = 8
Global Const $ASSOCSTR_DDEAPPLICATION = 9
Global Const $ASSOCSTR_DDETOPI = 10

Func _ShellAPI_AssocQueryString($flags, $str, $pszAssoc, $pszExtra, $pszOut, $pcchOut)
    Local $aRet = DllCall("shlwapi.dll", "long", "AssocQueryStringW", "long", $flags, "long", $str, "ptr", $pszAssoc, "ptr", $pszExtra, "ptr", $pszOut, "ptr", $pcchOut)
    Return $aRet[0]
EndFunc

Func _FileAssociation($sExt)
    Local $tAssoc = DllStructCreate("wchar[" & StringLen($sExt) + 1 & "]")
    DllStructSetData($tAssoc, 1, $sExt & Chr(0))
    Local $tdOut = DllStructCreate("dword")
    DllStructSetData($tdOut, 1, 0)
    Local $nRet = _ShellAPI_AssocQueryString($ASSOCF_VERIFY, $ASSOCSTR_EXECUTABLE, DllStructGetPtr($tAssoc), 0, 0, DllStructGetPtr($tdOut))
    If $nRet <> 1 Then Return SetError(1, 0, "{unknown}")
    Local $tsOut = DllStructCreate("wchar[" & DllStructGetdata($tdOut, 1) & "]")
    $nRet = _ShellAPI_AssocQueryString($ASSOCF_VERIFY, $ASSOCSTR_EXECUTABLE, DllStructGetPtr($tAssoc), 0, DllStructGetPtr($tsOut), DllStructGetPtr($tdOut))
    If $nRet <> 0 Then Return SetError(1, 0, "{unknown}")
    Return SetErroR(0, 0, DllStructGetData($tsOut, 1))
EndFunc

Global $sExt = ".txt"
While True
    $sExt = InputBox("Enter extension", "Extension", $sExt)
    If @error <> 0 Then ExitLoop
    If StringLeft($sExt, 1) <> "." Then $sExt = "." & $sExt
    MsgBox(64, $sExt, _FileAssociation($sExt))
Wend

Hope someone finds this useful. Apologies if it's been covered before but I was inspired by a posting on the help forum.

WBD

Link to comment
Share on other sites

Really nice function :D , here's another example.

; WideBoyDixon
; http://www.autoitscript.com/forum/index.php?showtopic=96988

Global Const $ASSOCF_INIT_NOREMAPCLSID = 0x00000001
Global Const $ASSOCF_INIT_BYEXENAME = 0x00000002
Global Const $ASSOCF_OPEN_BYEXENAME = 0x00000002
Global Const $ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004
Global Const $ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008
Global Const $ASSOCF_NOUSERSETTINGS = 0x00000010
Global Const $ASSOCF_NOTRUNCATE = 0x00000020
Global Const $ASSOCF_VERIFY = 0x00000040
Global Const $ASSOCF_REMAPRUNDLL = 0x00000080
Global Const $ASSOCF_NOFIXUPS = 0x00000100
Global Const $ASSOCF_IGNOREBASECLASS = 0x00000200

Global Const $ASSOCSTR_COMMAND = 1
Global Const $ASSOCSTR_EXECUTABLE = 2
Global Const $ASSOCSTR_FRIENDLYDOCNAME = 3
Global Const $ASSOCSTR_FRIENDLYAPPNAME = 4
Global Const $ASSOCSTR_NOOPEN = 5
Global Const $ASSOCSTR_SHELLNEWVALUE = 6
Global Const $ASSOCSTR_DDECOMMAND = 7
Global Const $ASSOCSTR_DDEIFEXEC = 8
Global Const $ASSOCSTR_DDEAPPLICATION = 9
Global Const $ASSOCSTR_DDETOPI = 10

Func _ShellAPI_AssocQueryString($flags, $str, $pszAssoc, $pszExtra, $pszOut, $pcchOut)
    Local $aRet = DllCall("shlwapi.dll", "long", "AssocQueryStringW", "long", $flags, "long", $str, "ptr", $pszAssoc, "ptr", $pszExtra, "ptr", $pszOut, "ptr", $pcchOut)
    Return $aRet[0]
EndFunc   ;==>_ShellAPI_AssocQueryString

Func _FileAssociation($sExt)
    Local $tAssoc = DllStructCreate("wchar[" & StringLen($sExt) + 1 & "]")
    DllStructSetData($tAssoc, 1, $sExt & Chr(0))
    Local $tdOut = DllStructCreate("dword")
    DllStructSetData($tdOut, 1, 0)
    Local $nRet = _ShellAPI_AssocQueryString($ASSOCF_VERIFY, $ASSOCSTR_EXECUTABLE, DllStructGetPtr($tAssoc), 0, 0, DllStructGetPtr($tdOut))
    If $nRet <> 1 Then Return SetError(1, 0, "{unknown}")
    Local $tsOut = DllStructCreate("wchar[" & DllStructGetData($tdOut, 1) & "]")
    $nRet = _ShellAPI_AssocQueryString($ASSOCF_VERIFY, $ASSOCSTR_EXECUTABLE, DllStructGetPtr($tAssoc), 0, DllStructGetPtr($tsOut), DllStructGetPtr($tdOut))
    If $nRet <> 0 Then Return SetError(1, 0, "{unknown}")
    Return SetError(0, 0, DllStructGetData($tsOut, 1))
EndFunc   ;==>_FileAssociation



; =========================================================================================
; Example #2
; =========================================================================================

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>

Local $listview, $button, $item1, $item2, $item3, $input1, $msg

GUICreate("_WinAPI_FindExecutable Example", 600, 400)

$hListView = GUICtrlCreateListView("Extension|Executable", 0, 0, 600, 400);,$LVS_SORTDESCENDING)

$i = 0
$bFirstFound = False ; needed because the first entry on my system ist !ut_auto_file
While 1
    $i += 1
    $var = RegEnumKey("HKEY_CLASSES_ROOT", $i)
    If @error <> 0 Then ExitLoop
    If $bFirstFound = False Then
        If StringLeft($var, 1) = "." Then $bFirstFound = True
    ElseIf StringLeft($var, 1) <> "." Then
        ExitLoop
    EndIf
    If StringLeft($var, 1) = "." Then GUICtrlCreateListViewItem($var & "|" & _FileAssociation($var), $hListView)
WEnd

_GUICtrlListView_SetColumnWidth($hListView, 0, $LVSCW_AUTOSIZE)
_GUICtrlListView_SetColumnWidth($hListView, 1, $LVSCW_AUTOSIZE)

GUISetState()

Do
    $msg = GUIGetMsg()
    Sleep(10)

Until $msg = $GUI_EVENT_CLOSE
Link to comment
Share on other sites

It's cool. Multiple options at hand is good to have.

Thanks.

Why are you doing '& Chr(0)'?

What else could it be if you omit that with char/wchar?

I was just doing this quickly. I wasn't sure about setting up a string in a DllStruct and whether I had to null terminate the value or whether the DllStructSetData did it or, indeed, if the value gets initialized to nulls.

You don't even have to create that structures, just use 'wstr' in DllCall().

Ahhh. Again, more haste less speed I guess.

Interestingly, the ASCII version of this function doesn't appear to work on my copy of XP which is why I used the wide version. Strange.

WBD

Link to comment
Share on other sites

Thanks.

I was just doing this quickly. I wasn't sure about setting up a string in a DllStruct and whether I had to null terminate the value or whether the DllStructSetData did it or, indeed, if the value gets initialized to nulls.

Ahhh. Again, more haste less speed I guess.

Interestingly, the ASCII version of this function doesn't appear to work on my copy of XP which is why I used the wide version. Strange.

WBD

Wow, fails with me too. You found a bug for microsoft?

If you wouldn't mind me posting... I would do it like this (KaFu's example):

; http://www.autoitscript.com/forum/index.php?showtopic=96988

Global Const $ASSOCF_INIT_NOREMAPCLSID = 0x00000001
Global Const $ASSOCF_INIT_BYEXENAME = 0x00000002
Global Const $ASSOCF_OPEN_BYEXENAME = 0x00000002
Global Const $ASSOCF_INIT_DEFAULTTOSTAR = 0x00000004
Global Const $ASSOCF_INIT_DEFAULTTOFOLDER = 0x00000008
Global Const $ASSOCF_NOUSERSETTINGS = 0x00000010
Global Const $ASSOCF_NOTRUNCATE = 0x00000020
Global Const $ASSOCF_VERIFY = 0x00000040
Global Const $ASSOCF_REMAPRUNDLL = 0x00000080
Global Const $ASSOCF_NOFIXUPS = 0x00000100
Global Const $ASSOCF_IGNOREBASECLASS = 0x00000200

Global Const $ASSOCSTR_COMMAND = 1
Global Const $ASSOCSTR_EXECUTABLE = 2
Global Const $ASSOCSTR_FRIENDLYDOCNAME = 3
Global Const $ASSOCSTR_FRIENDLYAPPNAME = 4
Global Const $ASSOCSTR_NOOPEN = 5
Global Const $ASSOCSTR_SHELLNEWVALUE = 6
Global Const $ASSOCSTR_DDECOMMAND = 7
Global Const $ASSOCSTR_DDEIFEXEC = 8
Global Const $ASSOCSTR_DDEAPPLICATION = 9
Global Const $ASSOCSTR_DDETOPI = 10



Func _FileAssociation($sExt)

    Local $aCall = DllCall("shlwapi.dll", "int", "AssocQueryStringW", _
            "dword", $ASSOCF_VERIFY, _
            "dword", $ASSOCSTR_EXECUTABLE, _
            "wstr", $sExt, _
            "ptr", 0, _
            "wstr", "", _
            "dword*", 65536)

    If @error Then
        Return SetError(1, 0, ""); call failed
    EndIf

    If Not $aCall[0] Then
        Return SetError(0, 0, $aCall[5])
    ElseIf $aCall[0] = 0x80070002 Then
        Return SetError(0, 0, "{unknown}"); COM Error 0x80070002, The system cannot find the file specified.'
    ElseIf $aCall[0] = 0x80004005 Then
        Return SetError(0, 0, "{fail}"); E_FAIL
    Else
        Return SetError(2, $aCall[0], ""); dammit!!!
    EndIf

EndFunc  ;==>_FileAssociation



; =========================================================================================
; Example #2
; =========================================================================================

#include <GUIConstantsEx.au3>
#include <WindowsConstants.au3>
#include <GuiListView.au3>

Local $listview, $button, $item1, $item2, $item3, $input1, $msg

GUICreate("_WinAPI_FindExecutable Example", 600, 400)

$hListView = GUICtrlCreateListView("Extension|Executable", 0, 0, 600, 400);,$LVS_SORTDESCENDING)

$i = 0
$bFirstFound = False; needed because the first entry on my system ist !ut_auto_file
While 1
    $i += 1
    $var = RegEnumKey("HKEY_CLASSES_ROOT", $i)
    If @error <> 0 Then ExitLoop
    If $bFirstFound = False Then
        If StringLeft($var, 1) = "." Then $bFirstFound = True
    ElseIf StringLeft($var, 1) <> "." Then
        ExitLoop
    EndIf
    If StringLeft($var, 1) = "." Then GUICtrlCreateListViewItem($var & "|" & _FileAssociation($var), $hListView)
WEnd

_GUICtrlListView_SetColumnWidth($hListView, 0, $LVSCW_AUTOSIZE)
_GUICtrlListView_SetColumnWidth($hListView, 1, $LVSCW_AUTOSIZE)

GUISetState()

Do
    $msg = GUIGetMsg()

Until $msg = $GUI_EVENT_CLOSE

♡♡♡

.

eMyvnE

Link to comment
Share on other sites

I'm just a bit concerned about this:

"dword*", 65536)

The final parameter points to a DWORD which specifies the number of characters available in the previous parameter. In my original example, I called the function twice; the first time to find out how much space is required to store the string and the second time with a pointer to a suitably large string. If you're unfortunate and the DWORD that resides at memory location 0x10000 isn't big enough then the function will fail.

WBD

Link to comment
Share on other sites

I'm just a bit concerned about this:

"dword*", 65536)

The final parameter points to a DWORD which specifies the number of characters available in the previous parameter. In my original example, I called the function twice; the first time to find out how much space is required to store the string and the second time with a pointer to a suitably large string. If you're unfortunate and the DWORD that resides at memory location 0x10000 isn't big enough then the function will fail.

WBD

I thought about that and was expecting that question :D

MAX_PATH is applicable here. But since unicode version is used extended-length path (32767 characters) are also covered, even doubled. There shouldn't be a problem with that in any case.

♡♡♡

.

eMyvnE

Link to comment
Share on other sites

I thought about that and was expecting that question :D

MAX_PATH is applicable here. But since unicode version is used extended-length path (32767 characters) are also covered, even doubled. There shouldn't be a problem with that in any case.

No no no! :D The parameter is a *pointer* to a DWORD. You're asking the function call to look at the contents of memory address 0x10000. The code as it stands is dangerous. I don't know the full inner workings of DllCall but I don't like the idea of not properly allocating the memory and passing in pointers to random memory addresses.

WBD

Link to comment
Share on other sites

Important: According to this article, Windows Vista REQUIRES the ASSOCF_IGNOREUNKNOWN (0x400) flag to be applied to the first argument. This will prevent any problematic returns, and its good practice to use, as it works when added to the options for non-Vista machines as well (I've been using it for a while with no ill-effects on XP).

The explanation:

With Windows Vista, you have to add ASSOCF_IGNOREUNKNOWN to the flags parameter. Otherwise, if a file type is not registered it may return you information about the "Unknown" application instead of just fessing up and admitting nothing is registered.

On another note, this association function can also be used for protocol/links by calling the function with the text preceding the colon (http, mailto, ftp).

(One weird exception: Shell Command Files (.SCF extensions) will return "explorer.exe" and will cause a messagebox to pop up. Those are best called only with ShellExecute() (and I really only know of 'Show Desktop.scf', which can be done through an AutoIT command anyway)

FINALLY, just for kicks, here's code I have that does exactly the same thing (modified from code by Joon)

; =================================================================================================
; Func _GetDefaultVerbCommandFromReg($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
; The results for this are usually a match to _WinAPI_AssocQuery(), except this adds 1 exception:
;   a .CPL file command-line lookup fix. (requires a 'verb' of 'cplopen')
;
; 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.
; ADDITIONALLY: .CPL files don't conform to the regular 'shell\open\command', so this type
;   is handled slightly differently (simply prefixing 'open' with 'cpl' (='cplopen')
;
; $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
;   Failure: Empty string.
;
; Author: Ascend4nt, modified from code by Joon
; =================================================================================================

Func _GetDefaultVerbCommandFromReg($sCommandOrExt,$sShellVerb="open")
    Local $sContentType
    
    ; Is it a file extension? (.TXT, .CMD etc)
    If StringLeft($sCommandOrExt,1)=='.' Then
        $sContentType = RegRead("HKEY_CLASSES_ROOT\" & $sCommandOrExt,"")
        ; Nothing there?
        If $sContentType="" Then Return ""
        ; Test for special .CPL (control panel) case
        ; .CPL uses 'cplopen\command'
        If StringCompare($sCommandOrExt,".CPL",0)=0 And StringCompare($sShellVerb,"open",0)=0 Then _
            $sShellVerb="cpl"&$sShellVerb
    Else
        ; Else it's a command/type, *without* the trailing ':' (if programmer called this correctly)
        ;   No need to retrieve content type from another registry position.
        $sContentType=$sCommandOrExt
    EndIf
    ; Return the command string (or "" if nothing is there)
    Return RegRead("HKEY_CLASSES_ROOT\" & $sContentType & "\shell\" & $sShellVerb & "\command","")
EndFunc
Link to comment
Share on other sites

No no no! :D The parameter is a *pointer* to a DWORD. You're asking the function call to look at the contents of memory address 0x10000. The code as it stands is dangerous. I don't know the full inner workings of DllCall but I don't like the idea of not properly allocating the memory and passing in pointers to random memory addresses.

WBD

AAhhh... you are wrong. Memory is properly allocated, filled, addressed and all that is needed. Call is perfectly valid and done in a most efficient way in AutoIt.

But, don't take my word for it. After all, who am I to know?

♡♡♡

.

eMyvnE

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...