52

In .Net (C# or VB: don't care), given a file path string, FileInfo struct, or FileSystemInfo struct for a real existing file, how can I determine the icon(s) used by the shell (explorer) for that file?

I'm not currently planning to use this for anything, but I became curious about how to do it when looking at this question and I thought it would be useful to have archived here on SO.

Community
  • 1
  • 1
Joel Coehoorn
  • 399,467
  • 113
  • 570
  • 794

10 Answers10

64
Imports System.Drawing
Module Module1

    Sub Main()    
        Dim filePath As String =  "C:\myfile.exe"  
        Dim TheIcon As Icon = IconFromFilePath(filePath)  

        If TheIcon IsNot Nothing Then    
            ''#Save it to disk, or do whatever you want with it.
            Using stream As New System.IO.FileStream("c:\myfile.ico", IO.FileMode.CreateNew)
                TheIcon.Save(stream)          
            End Using
        End If
    End Sub

    Public Function IconFromFilePath(filePath As String) As Icon
        Dim result As Icon = Nothing
        Try
            result = Icon.ExtractAssociatedIcon(filePath)
        Catch ''# swallow and return nothing. You could supply a default Icon here as well
        End Try
        Return result
    End Function
End Module
Blorgbeard
  • 101,031
  • 48
  • 228
  • 272
Stefan
  • 11,423
  • 8
  • 50
  • 75
  • That's fine for .exes, .dlls, or other files that contain icons. But what about text files or other simple files, where the icon may vary based on what program was installed or a setting the user altered? – Joel Coehoorn Jan 20 '09 at 17:44
  • It should work for ALL files that has an associated icon, it does not have to have an assoiciated program what I know. – Stefan Jan 20 '09 at 17:52
  • What is returned if there is no associated icon? – Joel Coehoorn Jan 20 '09 at 18:01
  • +1 Icon.ExtractAssociatedIcon will return exactly the same icon as the one showed by windows explorer – Wim Coenen Jan 20 '09 at 18:55
  • 2
    Testing shows this does work. I was unclear on his initial explanation- I thought it only looked for Icon resources within the file itself, but happily this turns out not to be the case. – Joel Coehoorn Jan 20 '09 at 19:54
  • Edited to separate code relevant to the question from the application code that tests it – Joel Coehoorn Jan 20 '09 at 20:02
  • 1
    Awesome! How can I get an icon for a folder ? – bohdan_trotsenko Aug 06 '09 at 10:04
  • 7
    If you want to do the same thing for WPF you can use the Handle property of the System.Drawing.Icon to create a BitmapSource for an Image: image.Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHIcon( result.Handle, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions() ); Note that ExtractAssociatedIcon always returns the 32x32 pixel version of the icon. – Christian Rodemeyer Jan 16 '10 at 17:22
  • @Stefan Any help here, when I change my system tray icon to anything other than the default, it becomes blank/empty/transparent... – ganders Aug 14 '13 at 19:16
  • It just returns the generic unknown file type icon for me when I use it on a .dll I built that does not contain an icon image. I want it to return the system .dll icon. – xr280xr Nov 21 '13 at 20:36
  • Due to the msdn ExtractAssociatedIcon can only throw a ArgumentException. In my Opinion you should just let that through, since it means the provided Filename is either invalid or the file can't be found. –  Mar 09 '16 at 08:44
  • Seems like this won't work for VBScript, only VB.net, but I was able to convert the C# version of this answer into a self compiling and executing script: https://github.com/jgstew/tools/blob/master/CSharp/ExtractAssociatedIcon.bat – jgstew Sep 28 '19 at 04:25
  • @Stefan Why icon quality so low ? – Gray Programmerz Feb 19 '21 at 06:16
18

You should use SHGetFileInfo.

Icon.ExtractAssociatedIcon works just as well as SHGetFileInfo in most cases, but SHGetFileInfo can work with UNC paths (e.g. a network path like "\\ComputerName\SharedFolder\") while Icon.ExtractAssociatedIcon cannot. If you need or might need to use UNC paths, it would be best to use SHGetFileInfo instead of Icon.ExtractAssociatedIcon.

This is good CodeProject article on how to use SHGetFileInfo.

Zach Johnson
  • 23,678
  • 6
  • 69
  • 86
  • Do these APIs obtain the dynamic icons such as the preview icon generated for PDF documents and images? The linked CodeProject project caches the images by file extension, so it would seem the answer is no. – Triynko Feb 04 '14 at 20:05
17

Please ignore everyone telling you to use the registry! The registry is NOT AN API. The API you want is SHGetFileInfo with SHGFI_ICON. You can get a P/Invoke signature here:

http://www.pinvoke.net/default.aspx/shell32.SHGetFileInfo

  • 4
    Since we are after C# or VB, Stefan's answer is much simpler. – Wim Coenen Jan 20 '09 at 18:57
  • If incase anyone looking to get icons in unity3d then this method works. I had tried `System.Drawing.Icon.ExtractAssociatedIcon` & shell32.dll ExtractAssociatedIcon. While first method gave me wrong icons, second method worked but icons were not always coming proper. Finally stumbled across this answer and it works exactly as I intended. – killer_mech Oct 26 '18 at 02:54
  • Not to use registry - probably you meant pinvoke ? – TarmoPikaro Nov 11 '22 at 21:01
10

Nothing more than a C# version of Stefan's answer.

using System.Drawing;

class Class1
{
    public static void Main()
    {
        var filePath =  @"C:\myfile.exe";
        var theIcon = IconFromFilePath(filePath);

        if (theIcon != null)
        {
            // Save it to disk, or do whatever you want with it.
            using (var stream = new System.IO.FileStream(@"c:\myfile.ico", System.IO.FileMode.CreateNew))
            {
                theIcon.Save(stream);
            }
        }
    }

    public static Icon IconFromFilePath(string filePath)
    {
        var result = (Icon)null;

        try
        {
            result = Icon.ExtractAssociatedIcon(filePath);
        }
        catch (System.Exception)
        {
            // swallow and return nothing. You could supply a default Icon here as well
        }

        return result;
    }
}
damix911
  • 4,165
  • 1
  • 29
  • 44
  • 1
    I wanted a way to do this from the command line, so I converted this answer into self compiling and executing C# within a BAT script. I also added the ability to take a filepath as an argument. See here: https://github.com/jgstew/tools/blob/master/CSharp/ExtractAssociatedIcon.bat – jgstew Sep 28 '19 at 04:27
7

This works for me in my projects, hope this helps someone.

It's C# with P/Invokes it will work so far on x86/x64 systems since WinXP.

(Shell.cs)

using System;
using System.Drawing;
using System.IO;
using System.Runtime.InteropServices;

namespace IconExtraction
{
    internal sealed class Shell : NativeMethods
    {
        #region OfExtension

        ///<summary>
        /// Get the icon of an extension
        ///</summary>
        ///<param name="filename">filename</param>
        ///<param name="overlay">bool symlink overlay</param>
        ///<returns>Icon</returns>
        public static Icon OfExtension(string filename, bool overlay = false)
        {
            string filepath;
            string[] extension = filename.Split('.');
            string dirpath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "cache");
            Directory.CreateDirectory(dirpath);
            if (String.IsNullOrEmpty(filename) || extension.Length == 1)
            {
                filepath = Path.Combine(dirpath, "dummy_file");
            }
            else
            {
                filepath = Path.Combine(dirpath, String.Join(".", "dummy", extension[extension.Length - 1]));
            }
            if (File.Exists(filepath) == false)
            {
                File.Create(filepath);
            }
            Icon icon = OfPath(filepath, true, true, overlay);
            return icon;
        }
        #endregion

        #region OfFolder

        ///<summary>
        /// Get the icon of an extension
        ///</summary>
        ///<returns>Icon</returns>
        ///<param name="overlay">bool symlink overlay</param>
        public static Icon OfFolder(bool overlay = false)
        {
            string dirpath = Path.Combine(System.AppDomain.CurrentDomain.BaseDirectory, "cache", "dummy");
            Directory.CreateDirectory(dirpath);
            Icon icon = OfPath(dirpath, true, true, overlay);
            return icon;
        }
        #endregion

        #region OfPath

        ///<summary>
        /// Get the normal,small assigned icon of the given path
        ///</summary>
        ///<param name="filepath">physical path</param>
        ///<param name="small">bool small icon</param>
        ///<param name="checkdisk">bool fileicon</param>
        ///<param name="overlay">bool symlink overlay</param>
        ///<returns>Icon</returns>
        public static Icon OfPath(string filepath, bool small = true, bool checkdisk = true, bool overlay = false)
        {
            Icon clone;
            SHGFI_Flag flags;
            SHFILEINFO shinfo = new SHFILEINFO();
            if (small)
            {
                flags = SHGFI_Flag.SHGFI_ICON | SHGFI_Flag.SHGFI_SMALLICON;
            }
            else
            {
                flags = SHGFI_Flag.SHGFI_ICON | SHGFI_Flag.SHGFI_LARGEICON;
            }
            if (checkdisk == false)
            {
                flags |= SHGFI_Flag.SHGFI_USEFILEATTRIBUTES;
            }
            if (overlay)
            {
                flags |= SHGFI_Flag.SHGFI_LINKOVERLAY;
            }
            if (SHGetFileInfo(filepath, 0, ref shinfo, Marshal.SizeOf(shinfo), flags) == 0)
            {
                throw (new FileNotFoundException());
            }
            Icon tmp = Icon.FromHandle(shinfo.hIcon);
            clone = (Icon)tmp.Clone();
            tmp.Dispose();
            if (DestroyIcon(shinfo.hIcon) != 0)
            {
                return clone;
            }
            return clone;
        }
        #endregion
    }
}

(NativeMethods.cs)

using System;
using System.Drawing;
using System.Runtime.InteropServices;

namespace IconExtraction
{
    internal class NativeMethods
    {
        public struct SHFILEINFO
        {
            public IntPtr hIcon;
            public int iIcon;
            public uint dwAttributes;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
            public string szDisplayName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
            public string szTypeName;
        };

        [DllImport("user32.dll")]
        public static extern int DestroyIcon(IntPtr hIcon);

        [DllImport("shell32.dll", CharSet = CharSet.Auto, BestFitMapping = false, ThrowOnUnmappableChar = true)]
        public static extern IntPtr ExtractIcon(IntPtr hInst, string lpszExeFileName, int nIconIndex);

        [DllImport("Shell32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true)]
        public static extern int SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, SHGFI_Flag uFlags);

        [DllImport("Shell32.dll")]
        public static extern int SHGetFileInfo(IntPtr pszPath, uint dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, SHGFI_Flag uFlags);
    }

    public enum SHGFI_Flag : uint
    {
        SHGFI_ATTR_SPECIFIED = 0x000020000,
        SHGFI_OPENICON = 0x000000002,
        SHGFI_USEFILEATTRIBUTES = 0x000000010,
        SHGFI_ADDOVERLAYS = 0x000000020,
        SHGFI_DISPLAYNAME = 0x000000200,
        SHGFI_EXETYPE = 0x000002000,
        SHGFI_ICON = 0x000000100,
        SHGFI_ICONLOCATION = 0x000001000,
        SHGFI_LARGEICON = 0x000000000,
        SHGFI_SMALLICON = 0x000000001,
        SHGFI_SHELLICONSIZE = 0x000000004,
        SHGFI_LINKOVERLAY = 0x000008000,
        SHGFI_SYSICONINDEX = 0x000004000,
        SHGFI_TYPENAME = 0x000000400
    }
}
k1ll3r8e
  • 729
  • 16
  • 22
1

If you're only interested in an icon for a specific extension and if you don't mind creating a temporary file you can follow the example displayed here

C# code:

    public Icon LoadIconFromExtension(string extension)
    {
        string path = string.Format("dummy{0}", extension);
        using (File.Create(path)) { }
        Icon icon = Icon.ExtractAssociatedIcon(path);
        File.Delete(path);
        return icon;
    }
default
  • 11,485
  • 9
  • 66
  • 102
1

The problem with the registry approach is that you are not explicitly getting the icon index id. Sometimes (if not all times), you get an icon ResourceID which is an alias the application developer used to name the icon's slot.

The registry method therefore implies that all developers use ResourceIDs which are the same as the implicit icon index id (which is zero based, absolute, deterministic).

Scan the registry location and you will see lots of negative numbers, sometimes even text references - i.e. not the icon index id. An implicit method seems better as it lets the OS do the work.

Only testing this new method now but it makes sense and hopefully solves this problem.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
OnyxxOr
  • 11
  • 1
  • 1
    Update - Zach's link works out great! Shell takes care of the hard work and I don't have to worry about Resource / Icon IDs anymore :) Thanks guys – OnyxxOr Jul 28 '11 at 01:13
0

This link seems to have some info. It involves a lot of registry traversing, but it seems doable. The examples are in C++

Restore the Data Dumps
  • 38,967
  • 12
  • 96
  • 122
0
  • determine extension
  • in registry, go to "HKCR\.{extension}", read the default value (let's call it filetype)
  • in "HKCR\{filetype}\DefaultIcon", read the default value: this is the path to the icon file (or icon container file, like an .exe with an embedded icon resource)
  • if needed, use your preferred method of extracting the icon resource out of the mentioned file

edit/moved up from the comments:

If the icon is in a container file (this is quite common), there will be a counter after the path, like this: "foo.exe,3". This means it is icon number 4 (the index is zero-based) of the available icons. A value of ",0" is implicit (and optional). If the counter is 0 or missing, the fist available icon will be used by the shell.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • If it's an icon container file that contains several icons, how do you know which to use? – Joel Coehoorn Jan 20 '09 at 18:02
  • There is a counter after the path, like "foo.exe,3". This means it is icon no. 4 (the index is zero-based) of the available icons. A value of ",0" is implicit and therefore optional. If it is missing, the fist available icon will be used by the shell. – Tomalak Jan 20 '09 at 18:04
  • 3
    The registry is not an API! There are other ways to specify icons, and this method will be wrong. Please use the SHGetFileInfo API for this. – Tron Jan 20 '09 at 18:31
  • @timbagas: "and this method will be wrong"... Wrong in what way, other than "not using an API"? – Tomalak Jan 20 '09 at 18:42
0

Application can contain multiple icons, and extracting only one of them might be insufficient for your needs. I by myself wanted to pick up icon to reuse it later on in compilation for making shim.

Official method which works - use IconLib.Unofficial 0.73.0 or higher.

Add code like this:

MultiIcon multiIcon = new MultiIcon();
multiIcon.Load(<in path>);
multiIcon.Save(<out path>, MultiIconFormat.ICO);

can extract icons which are used by application.

However - library itself works in .net framework 4.6.1 - v4.8, does not work in .net core.

Other methods which I have tried also:

Icon icon = Icon.ExtractAssociatedIcon(<in path>);
using (FileStream stream = new FileStream(<out path>, FileMode.CreateNew))
{
    icon.Save(stream);
}

Works only for one icon, but it's also corrupted somehow. Similar effect I was getting when using pinvoke method SHGetFileInfo.

Using PeNet library, code looks like this:

var peFile = new PeFile(cmdArgs.iconpath);
byte[] icon = peFile.Icons().First().AsSpan().ToArray();
File.WriteAllBytes(iconPath, icon);

PeNet allows extraction of icons, but they are not in original format, also there are multiple of them. In this commit the whole feature is developed - but no clue yet how feature should be used. Maybe need to wait for feature to mature. (See Penet issue #258)

ICSharpCode.Decompiler can be used on private methods to provide similar functionality:

PEFile file = new PEFile(cmdArgs.iconpath);
var resources = file.Reader.ReadWin32Resources();
if (resources != null)
{
    var createAppIcon = typeof(WholeProjectDecompiler).GetMethod("CreateApplicationIcon", BindingFlags.Static | BindingFlags.NonPublic);

    byte[] icon = (byte[])createAppIcon.Invoke(null, new[] { file });
    File.WriteAllBytes(iconPath, icon);
}

But I had exception on .net core 3.1 compiled binaries, maybe that library does not work for all cases.

TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62