Archive: Check existing file associations


Check existing file associations
Is there a way to check if a certain filetype is already associated with a program?


The documented way to do this is with the Assoc* API, but guess what, it sucks:

  1. It does not check "deep enough" (SystemFileAssociations etc), it only checks HKCR\.exe and its HKCR\ProgId mapping but for most people that is probably fine. But just because the API said no does not mean that doubleclicking the file will fail or open the openwith dialog. The filetype could have something under SystemFileAssociations or there could be a registered dynamic verb (IContextMenu).
  2. stackoverflow.com/questions/18682679/using-assocquerystring-to-get-64-bit-application-command-from-32-bit-application

!include Win\COM.nsh
!include LogicLib.nsh
!define ASSOCF_NOFIXUPS 0x0100
!define ASSOCF_IGNOREBASECLASS 0x0200
!define ASSOCSTR_COMMAND 1
!define ASSOCKEY_SHELLEXECCLASS 1
!define AT_FILEEXTENSION 0
!define AL_EFFECTIVE 1

StrCpy $2 ".txt"

; Choose one of these 3 methods, IApplicationAssociationRegistration is Vista+ and AssocQuery* is IE5/Win98SE/2000+

!insertmacro ComHlpr_CreateInProcInstance ${CLSID_ApplicationAssociationRegistration} ${IID_IApplicationAssociationRegistration} r1 ""
${If} $1 <> 0
${IApplicationAssociationRegistration::QueryCurrentDefault} $1 '("$2",${AT_FILEEXTENSION},${AL_EFFECTIVE},0r3).r0'
DetailPrint "HRESULT=$0,ProgId WStr pointer=$3"
${If} $0 = 0
System::Call *$3(&w1024.r0)
System::Call OLE32::CoTaskMemFree(ir3)
DetailPrint "SUCCESS: ProgId=$0"
${EndIf}
${IUnknown::Release} $1 ""
${EndIf}

System::Call 'SHLWAPI::AssocQueryString(i${ASSOCF_NOFIXUPS}|${ASSOCF_IGNOREBASECLASS},i${ASSOCSTR_COMMAND},t"$2",i0,t.r1,*i1024)i.r0'
DetailPrint "HRESULT=$0,Cmd=$1"

System::Call 'SHLWAPI::AssocQueryKey(i${ASSOCF_NOFIXUPS},i${ASSOCKEY_SHELLEXECCLASS},t"$2",i0,*i0r1)i.r0'
DetailPrint "HRESULT=$0"
${If} $0 = 0
System::Call 'ADVAPI32::RegCloseKey(i$1)'
DetailPrint "SUCCESS: $2 is registered"
${EndIf}

I can't get that code to work. What is COM.nsh?


Originally posted by empezar
I can't get that code to work. What is COM.nsh?
I believe that code should work on 2.46 but I only tested on 3.0, anyway, the AssocQueryString line is probably the method you want...

That worked, though I don't know what to do with the result.

HRESULT is always 0 even if the file is not associated, because it's always associated with either a program or the "Open with"-program.

How do I tell if "rundll32" can be found within the $1 string? StrContains doesn't work because it seems to be looking to match a whole word, while "rundll" is just a part of the program path.


Originally posted by empezar
HRESULT is always 0 even if the file is not associated, because it's always associated with either a program or the "Open with"-program.
And you used ASSOCF_IGNOREBASECLASS? Which extension are you checking and which OS is this?

Yes. I've tried the bottom two methods. Both of them fail to differentiate between associated and not associated file types. I've tested the file types "qwz" and "qwp", where "qwz" files open with ezQuake when double clicked. Using Windows 7 64-bit.


You can try adding ASSOCF_INIT_IGNOREUNKNOWN (0x00000400) as well but openwith was never returned for me when I tested on Win7 (32bit).

The following code is a bad attempt at doing it manually, it is incomplete and half of this stuff is probably undocumented.

!include LogicLib.nsh
Section
StrCpy $2 ".flac"
;BUGBUG: Should check HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\KindMap\.ext first?
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\$2\UserChoice" "ProgId"
Push $0
Call Assoc_RedirectProgId
Pop $0
Push $0
Call Assoc_QueryHasKey
Pop $1
${If} $1 = 0 ; ProgId did not exist?
${OrIf} $0 == "" ;UserChoice did not exist?
ReadRegStr $0 HKCR $2 ""
Push $0
Call Assoc_RedirectProgId
Pop $0
${EndIf}
Push $0
Call Assoc_QueryProgIdDefVerb
Pop $1
${IfThen} "$0$1" != "$1$0" ${|} Goto assoc_done ${|}
StrCpy $0 "SystemFileAssociations\$2"
Push $0
Call Assoc_QueryProgIdDefVerb
Pop $1
${IfThen} "$0$1" != "$1$0" ${|} Goto assoc_done ${|}

ReadRegStr $0 HKCR $2 "PerceivedType"
${If} $0 != ""
StrCpy $0 "SystemFileAssociations\$0"
Push $0
Call Assoc_QueryProgIdDefVerb
Pop $1
${IfThen} "$0$1" != "$1$0" ${|} Goto assoc_done ${|}
${EndIf}

ReadRegStr $0 HKCR "SystemFileAssociations\$2" "PerceivedType"
${If} $0 != ""
StrCpy $0 "SystemFileAssociations\$0"
Push $0
Call Assoc_QueryProgIdDefVerb
Pop $1
${IfThen} "$0$1" != "$1$0" ${|} Goto assoc_done ${|}
${EndIf}

;BUGBUG: If we still have not found anything we are supposed to look under %ProgId/UserChoice%\Clsid\ for a GUID and its ShellFolder data, what to do after that I did not investigate
;NOTE: Last resort is to look under HKCR\*, HKCR\AllFilesystemObjects and finally HKCR\Unknown but that is not a good idea for what we are trying to detect, skipping

assoc_done:
${If} "$0$1" != "$1$0"
DetailPrint $2:PID=$0,Verb=$1
${EndIF}
SectionEnd

Function Assoc_QueryHasKey
Exch $0
Push $1
ClearErrors
EnumRegKey $1 HKCR $0 0
IfErrors 0 yes
EnumRegValue $1 HKCR $0 0
StrCpy $1 ""
IfErrors done
yes:
StrCpy $0 "1$1" ; <> 0 for checking and store subkey if found
done:
Pop $1
Exch $0
FunctionEnd

Function Assoc_QueryHasNamedValue
Exch $0 ;name
Exch
Exch $1 ;key
Push $2
Push $3
StrCpy $3 0
loop:
ClearErrors
EnumRegValue $2 HKCR $1 $3
IfErrors done
IntOp $3 $3 + 1
StrCmp $0 $2 0 loop
StrCpy $2 1
done:
StrCpy $0 $2
Pop $3
Pop $2
Pop $1
Exch $0
FunctionEnd

Function Assoc_RedirectProgId
Exch $0
Push $1
ReadRegStr $1 HKCR "$0\CurVer" ""
StrCmp $1 "" +2
StrCpy $0 $1
Pop $1
Exch $0
FunctionEnd

Function Assoc_QueryProgIdDefVerb
Exch $0
Push $1 ; save reg
Push $2
Push $0
Push "NoStaticDefaultVerb"
Call Assoc_QueryHasNamedValue
Pop $1
${If} $1 <> 0
StrCpy $0 ""
${Else}
StrCpy $1 $0
ReadRegStr $0 HKCR "$1\Shell" "" ; BUGBUG: The default verb string can contain space/comma separated verb names, parsing required
${IfThen} $0 == "" ${|} StrCpy $0 "open" ${|}
Push "$1\Shell\$0"
Call Assoc_QueryHasKey
Pop $2
${If} $2 = 0
Push "$1\Shell"
Call Assoc_QueryHasKey
Pop $2
StrCpy $0 $2 "" 1
${If} $2 <> 0
${AndIf} $0 != ""
Push "$1\Shell\$0"
Call Assoc_QueryHasKey
Pop $2
${IfThen} $2 = 0 ${|} StrCpy $0 "" ${|}
${EndIf}
${EndIf}
${EndIf}
Pop $2 ;restore reg
Pop $1
Exch $0
FunctionEnd

I've tried upgrading to NSIS 3.0 to no avail in this matter.

Tried both IApplicationAssociationRegistration and AssocQuery*.

Nothing works.

I'd rather not use undocumented "bad attempts" at solving this matter, and simply checking for a file association shouldn't require that much code?

How do I check for associations using the documented version? The Assoc* API you mentioned? I can only find information on how to SET and REMOVE associations using that thing.


Originally posted by empezar
How do I check for associations using the documented version? The Assoc* API you mentioned? I can only find information on how to SET and REMOVE associations using that thing.
AssocQueryString and there is no API to Set/Remove, only query, not sure what you are looking at...

With what program associated specific files can be found as follows:

OutFile "opens_by_default.exe"
; For example HTM files
Section
StrCpy $R1 htm
FileOpen $0 "$TEMP\opens_by_default.$R1" "w"
FileClose $0
System::Call "Shell32::FindExecutable(t '$TEMP\opens_by_default.$R1', i 0, t .r1)"
Delete "$TEMP\opens_by_default.$R1"
MessageBox MB_OK '$R1 files by default opens the program: : $1 '
SectionEnd

shell32!FindExecutableW on Win7(x86) uses AssocQueryString(ASSOCF_INIT_IGNOREUNKNOWN,ASSOCSTR_EXECUTABLE,file,...) and XP uses AssocQueryString(0,ASSOCSTR_EXECUTABLE,file,...)