Get File Icon used by Shell
Asked Answered
P

10

53

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.

Primalia answered 20/1, 2009 at 17:30 Comment(1)
A
65
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
Acuity answered 20/1, 2009 at 17:40 Comment(13)
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?Primalia
It should work for ALL files that has an associated icon, it does not have to have an assoiciated program what I know.Acuity
What is returned if there is no associated icon?Primalia
+1 Icon.ExtractAssociatedIcon will return exactly the same icon as the one showed by windows explorerJennijennica
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.Primalia
Edited to separate code relevant to the question from the application code that tests itPrimalia
Awesome! How can I get an icon for a folder ?Centeno
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.Electrochemistry
@Acuity Any help here, when I change my system tray icon to anything other than the default, it becomes blank/empty/transparent...Away
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.Salcedo
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.Geoff
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: github.com/jgstew/tools/blob/master/CSharp/…Sacculate
@Acuity Why icon quality so low ?Departmentalize
W
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.

Wohlert answered 5/5, 2009 at 2:26 Comment(1)
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.Amylaceous
M
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

Magallanes answered 20/1, 2009 at 18:24 Comment(3)
Since we are after C# or VB, Stefan's answer is much simpler.Jennijennica
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.Filomenafiloplume
Not to use registry - probably you meant pinvoke ?Pogy
R
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;
    }
}
Rascal answered 18/10, 2013 at 8:8 Comment(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: github.com/jgstew/tools/blob/master/CSharp/…Sacculate
I
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
    }
}
Isleana answered 10/6, 2014 at 16:46 Comment(0)
M
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.

Making answered 27/7, 2011 at 19:49 Comment(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 guysMaking
L
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;
    }
Lagunas answered 29/6, 2012 at 6:48 Comment(0)
Y
0

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

Yongyoni answered 20/1, 2009 at 17:42 Comment(0)
P
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.

Precondition answered 20/1, 2009 at 17:48 Comment(4)
If it's an icon container file that contains several icons, how do you know which to use?Primalia
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.Precondition
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.Keelykeen
@timbagas: "and this method will be wrong"... Wrong in what way, other than "not using an API"?Precondition
P
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.

Pogy answered 12/11, 2022 at 8:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.