Given a file type (e.g. .txt
) how can i get the:
- path
- index
to the file type's associated icon path and index, e.g.:
I want to convert .txt
into:
- Path: %SystemRoot%\system32\imageres.dll
- Index: -102
With this information i can then extract the icon (e.g. using SHDefExtractIcon
).
Background
Every type of file in Windows is registered in the registry. When an icon is assocated with the file, it is specified as a Path to the file that contains the icon, and the index of the icon resource (or as a resource ID if the index is negative).
Using .txt
file as an example, the associated DefaultIcon is:
%SystemRoot%\system32\imageres.dll,-102
ExtractAssociatedIcon
First there was the Win API function ExtractAssociatedIcon:
Retrieves a handle to an indexed icon found in a file or an icon found in an associated executable file.
The idea is that you pass it the path, and the index, and it will go get the icon for you:
String iconPath = "%SystemRoot%\system32\imageres.dll";
Word iIcon = -102;
HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);
That works only when you already know the path and index of the icon you want.
Fortunately, ExtractAssociatedIcon is also able to tell you the path and index for a icon's file:
If the function cannot obtain the icon handle from that file, and the file has an associated executable file, it looks in that executable file for an icon.
Correctly calling the function in this case is a bit tricky, as it will modify your supplied buffer (causing buffer overruns if you didn't pad your buffer to be long enough):
String iconPath = "C:\Example.txt" + StringOfChar(\0, 32767); //pad the InOut buffer
Word iIcon = 0;
HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);
DestroyIcon(ico);
When the function returns:
- iconPath:
%SystemRoot%\system32\imageres.dll
- iIcon: -102
Why didn't i just use the HICON
returned by ExtractAssociatedIcon? Because ExtratAssociatedIcon doesn't allow me to specify the size of the Icon i want. It returns the "Shell large icon" and that's it.
Also, the only way ExtractAssociatedIcon can perform it's heroic efforts of looking up by file type is if the file actually exists. If the specified file does not exist (which it doesn't - since there is no foo.txt
), the function fails.
SHDefExtractIcon
Enter SHDefExtractIcon. It is able to extract any size of icon i want, you just have to pass it the path and index of the icon resource:
String iconFile = "%SystemRoot%\system32\imageres.dll";
Int32 iIndex = -102;
HICON hLargeIcon;
if (SHDefExtractIcon(iconFile, iIndex, 0, out hLargeIcon, null, 256) == S_OK)
return hLargeIcon
The only problem is that i have to get the associated path and index for a file type already. And SHDefExtractIcon, unlike ExtractAssociatedIcon, will not perform the heroic lookup for you.
For that i have to perform the lookup myself; which is my question.
Buggy Registry Spelunking
My first attempt is to read the contract of file associations from the other side. I know how default icons are registered, and i can go in reverse.
convert the
.ext
to the assocatedProgID
:HKEY_CLASSES_ROOT/.ext (default) = [ProgID]
Lookup the
DefaultIcon
under the[ProgID]
HKEY_CLASSES_ROOT/[progID]/DefaultIcon (default) = [path],[index]
In my case:
HKEY_CLASSES_ROOT/.txt
(default) = txtfile
HEKY_CLASSES_ROOT/txtfile/DefaultIcon
(default) = "%SystemRoot%\system32\imageres.dll,-102"
This is the approach used by the code behind this accepted Stackoverflow answer to the same question:
- convert ext to progID
- lookup progID's DefaultIcon key
Assuming the ext exists, and the progID exists, and the DefaultIcon exists, and the path exists, and i can parse the path, it's an incorrect unsupported answer. There are edge cases that the accepted code does not handle1.
I'd like the Windows API supported way to perform the mapping from .ext
to
- path
- index
SHGetFileInfo
There is a handy function SHGetFileInfo. It's handy because the filename doesn't need to actually exist. If you pass it the SHGFI_USEFILEATTRIBUTES
flag, It means:
Do not access the disk. Pretend that the file/directory exists, and that its file attributes are what I passed as the dwFileAttributes parameter. Do this regardless of whether it actually exists or not.
This is good:
SHELLFILEINFO sfi;
DWORD res = SHGetFileInfo("foo.txt",
FILE_ATTRIBUTE_NORMAL,
ref shellFileInfo,
sizeof(shellFileInfo),
SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SHELLICONSIZE | SHGFI_USEFILEATTRIBUTES);
if (res <> 0)
return shellFileInfo.hIcon;
The only problem is that i cannot specify the icon size i want. I am limited to the sizes of icons that the shell decides it wants to use.
IExtractImage
IExtractImage is nice:
- it can return the [path],[index] of an associated icon
- i can specify a desired size
Unfortunately it requires a file to actually exist (it has to be something that exists in the shell namespace). When i have a file-type only i can't use IExtractImage
IThumbnailProvider
IThumbnailProvider, introduced with Windows Vista, is the modern replacement for IExtractImage:
Windows Vista IThumbnailProivder is new for Vista and replaces IExtractImage. Vista still supports IExtractImage but lacks the ability to return the image type (alpha or not).
IThumbnailProvider also lets me supply the desired icon size. Excellent!
IThumbnailProvider normally requires a file to exist in the shell namespace. But that's only because the Shell API is the only supported way to get ahold ("bind") to the IThumbnailProvider shell interface exposed by a file type.
Fortunately i can perform the same horrible hacks i used above, and crawl the registry manually:
HKEY_CLASSES_ROOT/.ext/ShellEx/[InterfaceID]
(default) = [ClassID]
if it doesn't exist:
HKEY_CLASSES_ROOT/.ext
(default) = [ProgID]
HKEY_CLASSES_ROOT/[ProgID]/ShellEx/[InterfaceID]
(default) = [ClassID]
In the case of my .avi
file:
HKEY_CLASSES_ROOT/.avi/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
(default) = "{9DBD2C50-62AD-11D0-B806-00C04FD706EC}"
And now i'm off to the races with a CLSID
!
Unfortunately it ends there, as IThumbnailProvider requires a file. More precisely, it requires IInitializeWithStream
.
I don't have a stream. I don't have a file. I only have the notion of a file type.
AssocQueryString
Perhaps AssocQueryString can help me? I don't actually know - it's a beast of a function. And i can't make head nor tails of it.
The question
Given a file type (e.g. "x.txt"), how can i get the associated icon:
- path
- index
so i may extract the icon of my own desired size (likely using SHDefExtractIcon)?
Footnotes
1 exefile and %1