How to get the icon path and index associated with a file type?
<p>Given a file type (e.g. <code>.txt</code>) how can i get the:</p>

<ul>
<li>path</li>
<li>index</li>
</ul>

<p>to the file type's associated icon path and index, e.g.:</p>

<p>I want to convert <code>.txt</code> into:</p>

<ul>
<li><strong>Path</strong>: %SystemRoot%\system32\imageres.dll</li>
<li><strong>Index</strong>: -102</li>
</ul>

<p>With this information i can then extract the icon (e.g. using <code>SHDefExtractIcon</code>).</p>

<h2>Background</h2>

<p>Every type of file in Windows is registered in the registry. When an icon is assocated with the file, it is specified as a <strong>Path</strong> to the file that contains the icon, and the index of the icon resource (or as a resource ID if the index is negative). </p>

<p>Using <code>.txt</code> file as an example, the associated <strong>DefaultIcon</strong> is:</p>

<pre><code>%SystemRoot%\system32\imageres.dll,-102
</code></pre>

<h2>ExtractAssociatedIcon</h2>

<p>First there was the Win API function <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms648067(v=vs.85).aspx" rel="nofollow noreferrer"><strong>ExtractAssociatedIcon</strong></a>:</p>

<blockquote>
  <p>Retrieves a handle to an indexed icon found in a file or an icon found in an associated executable file.</p>
</blockquote>

<p>The idea is that you pass it the path, and the index, and it will go get the icon for you:</p>

<pre><code>String iconPath = "%SystemRoot%\system32\imageres.dll";
Word iIcon = -102;

HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);
</code></pre>

<p>That works only when you already <em>know</em> the path and index of the icon you want.</p>

<p>Fortunately, <strong>ExtractAssociatedIcon</strong> is also able to <strong>tell</strong> you the path and index for a icon's file:</p>

<blockquote>
  <p>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. </p>
</blockquote>

<p>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):</p>

<pre><code>String iconPath = "C:\Example.txt" + StringOfChar(\0, 32767); //pad the InOut buffer
Word iIcon = 0;

HICON ico = ExtractAssociatedIcon(0, iconPath, iIcon);
DestroyIcon(ico);
</code></pre>

<p>When the function returns:</p>

<ul>
<li><strong>iconPath</strong>: <code>%SystemRoot%\system32\imageres.dll</code></li>
<li><strong>iIcon</strong>: -102</li>
</ul>

<p>Why didn't i just use the <code>HICON</code> 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. </p>

<p>Also, the only way ExtractAssociatedIcon can perform it's heroic efforts of looking up by file type is if the file <strong>actually</strong> exists. If the specified file does not exist (which it doesn't - since there is no <code>foo.txt</code>), the function fails.</p>

<h2>SHDefExtractIcon</h2>

<p>Enter <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb762149(v=vs.85).aspx" rel="nofollow noreferrer"><strong>SHDefExtractIcon</strong></a>. It <a href="https://blogs.msdn.microsoft.com/oldnewthing/20140501-00/?p=1103" rel="nofollow noreferrer">is able to extract any size of icon i want</a>, you just have to pass it the <strong>path</strong> and <strong>index</strong> of the icon resource:</p>

<pre><code>String iconFile = "%SystemRoot%\system32\imageres.dll";
Int32 iIndex = -102;
HICON hLargeIcon;

if (SHDefExtractIcon(iconFile, iIndex, 0, out hLargeIcon, null, 256) == S_OK)
   return hLargeIcon
</code></pre>

<p>The only problem is that i have to get the associated <strong>path</strong> and <strong>index</strong> for a file type already. And <strong>SHDefExtractIcon</strong>, unlike <strong>ExtractAssociatedIcon</strong>, will not perform the heroic lookup for you.</p>

<p>For that i have to perform the lookup myself; which is my question.</p>

<h2>Buggy Registry Spelunking</h2>

<p>My first attempt is to <a href="https://blogs.msdn.microsoft.com/oldnewthing/20031226-00/?p=41343" rel="nofollow noreferrer">read the contract of file associations from the other side</a>. I know how default icons are registered, and i can go in reverse.</p>

<ul>
<li><p>convert the <code>.ext</code> to the assocated <code>ProgID</code>:</p>

<pre><code>HKEY_CLASSES_ROOT/.ext
    (default) = [ProgID]
</code></pre></li>
<li><p>Lookup the <code>DefaultIcon</code> under the <code>[ProgID]</code></p>

<pre><code>HKEY_CLASSES_ROOT/[progID]/DefaultIcon
    (default) = [path],[index]
</code></pre></li>
</ul>

<p>In my case:</p>

<pre><code>HKEY_CLASSES_ROOT/.txt
   (default) = txtfile

HEKY_CLASSES_ROOT/txtfile/DefaultIcon
   (default) = "%SystemRoot%\system32\imageres.dll,-102"
</code></pre>

<p>This is the approach used by the code behind <a href="https://stackoverflow.com/a/272044/12597">this accepted</a> Stackoverflow answer to the same question:</p>

<ul>
<li>convert ext to progID</li>
<li>lookup progID's DefaultIcon key</li>
</ul>

<p>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 handle<sup>1</sup>.</p>

<p>I'd like the Windows API <em>supported</em> way to perform the mapping from <code>.ext</code> to </p>

<ul>
<li>path</li>
<li>index</li>
</ul>

<h2>SHGetFileInfo</h2>

<p>There is a handy function <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx" rel="nofollow noreferrer"><strong>SHGetFileInfo</strong></a>. It's handy because <a href="https://blogs.msdn.microsoft.com/oldnewthing/20040601-00/?p=39073" rel="nofollow noreferrer">the filename doesn't need to <em>actually</em> exist</a>. If you pass it the <code>SHGFI_USEFILEATTRIBUTES</code> flag, It means:</p>

<blockquote>
  <p>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.</p>
</blockquote>

<p>This is good:</p>

<pre><code>SHELLFILEINFO sfi;
DWORD res = SHGetFileInfo("foo.txt", 
      FILE_ATTRIBUTE_NORMAL,
      ref shellFileInfo,
      sizeof(shellFileInfo),
      SHGFI_ICON | SHGFI_LARGEICON | SHGFI_SHELLICONSIZE | SHGFI_USEFILEATTRIBUTES);

if (res &lt;&gt; 0)
   return shellFileInfo.hIcon;
</code></pre>

<p>The only problem is that i cannot specify the icon size i want. I am limited to the sizes of icons that the <em>shell</em> decides <strong>it</strong> wants to use.</p>

<h2>IExtractImage</h2>

<p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb761848(v=vs.85).aspx" rel="nofollow noreferrer"><strong>IExtractImage</strong></a> is nice:</p>

<ul>
<li>it can return the [path],[index] of an associated icon</li>
<li>i can specify a desired size</li>
</ul>

<p>Unfortunately it requires a file to actually exist (it has to be something that <strong>exists</strong> in the shell namespace). When i have a file-type only i can't use <strong>IExtractImage</strong></p>

<h2>IThumbnailProvider</h2>

<p><a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb774614(v=vs.85).aspx" rel="nofollow noreferrer"><strong>IThumbnailProvider</strong></a>, introduced with Windows Vista, is the modern replacement for <strong>IExtractImage</strong>:</p>

<blockquote>
  <p><strong>Windows Vista</strong> IThumbnailProivder is new for Vista and replaces IExtractImage. Vista still supports <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb761848(v=vs.85).aspx" rel="nofollow noreferrer"><strong>IExtractImage</strong></a> but lacks the ability to return the image type (alpha or not).</p>
</blockquote>

<p>IThumbnailProvider also lets me supply the desired icon size. Excellent!</p>

<p><strong>IThumbnailProvider</strong> 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 <em>IThumbnailProvider</em> shell interface exposed by a file type.</p>

<p>Fortunately i can perform the same horrible hacks i used above, and crawl the registry manually:</p>

<pre><code>HKEY_CLASSES_ROOT/.ext/ShellEx/[InterfaceID]
   (default) = [ClassID]
</code></pre>

<p>if it doesn't exist:</p>

<pre><code>HKEY_CLASSES_ROOT/.ext
   (default) = [ProgID]

HKEY_CLASSES_ROOT/[ProgID]/ShellEx/[InterfaceID]
   (default) = [ClassID]
</code></pre>

<p>In the case of my <code>.avi</code> file:</p>

<pre><code>HKEY_CLASSES_ROOT/.avi/ShellEx/{e357fccd-a995-4576-b01f-234630154e96}
   (default) = "{9DBD2C50-62AD-11D0-B806-00C04FD706EC}"
</code></pre>

<p>And now i'm off to the races with a <code>CLSID</code>!</p>

<p>Unfortunately it ends there, as <strong>IThumbnailProvider</strong> requires a file. More precisely, it requires <code>IInitializeWithStream</code>. </p>

<p>I don't have a stream. I don't have a file. I only have the notion of a file type.</p>

<h2>AssocQueryString</h2>

<p>Perhaps <a href="https://msdn.microsoft.com/en-us/library/windows/desktop/bb773471(v=vs.85).aspx" rel="nofollow noreferrer"><strong>AssocQueryString</strong></a> 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.</p>

<h2>The question</h2>

<p>Given a file type (e.g. "x.txt"), how can i get the associated icon:</p>

<ul>
<li>path</li>
<li>index</li>
</ul>

<p>so i may extract the icon of my own desired size (likely using SHDefExtractIcon)?</p>

<h2>Footnotes</h2>

<p><sup>1</sup> exefile and <code>%1</code></p>