Search code examples
c#windowswinapiwindows-shell

How to extract overlay icon or its path from file in windows


We have an icon overlay extension which displays different overlays depending on the file status. For automated tests, i am searching for a to check if the correct overlay is displayed. Therefore, i ask if it is possible to extract the overlay icon itself or maybe get the path to the icon for comparing.

I tried following code:

SHFILEINFO shfi = new SHFILEINFO();
IntPtr result = SHGetFileInfo(filePath, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_SMALLICON | SHGFI_OVERLAYINDEX);

if (result != IntPtr.Zero)
{
    int overlayIndex = shfi.iIcon >> 24; 
    Console.WriteLine($"Overlay Index: {overlayIndex}");

    DestroyIcon(shfi.hIcon);
}

Despite the overlayIndex is 0 every time, is there a way to get the overlay icon without the main icon to compare or check?

EDIT: I tried following method after Jimi's comment:

private static Icon GetOverlay(string filePath)
{
    var imageListGuid = new Guid("46EB5926-582E-4017-9FDF-E8998DAA0950");
    IMAGELIST sysImageList;
    var result = SHGetImageList(SHIL_SMALL, ref imageListGuid, out sysImageList);

    if (result == 0)
    {
        var shfi = new SHFILEINFO();
        var hSuccess = SHGetFileInfo(filePath, 0, ref shfi, (uint)Marshal.SizeOf(shfi), SHGFI_ICON | SHGFI_OVERLAYINDEX | SHGFI_SMALLICON);

        if (hSuccess != IntPtr.Zero)
        {
            var iconIndex = shfi.iIcon;
            var overlayIndex = iconIndex & 0xF;

            var hOverlayIcon = ImageList_GetIcon(sysImageList.handle, overlayIndex, 0);
            var overlay = Icon.FromHandle(hOverlayIcon);

            return overlay;
        }
    }

    return null;
}

Unfortunately i never get the overlay icon, it is some kind of standard icon.


Solution

  • You can use IImageList::GetOverlayImage method, here is a full sample:

    static void Main()
    {
        var info = new SHFILEINFO();
        SHGetFileInfo(@"c:\temp\my.lnk", FileAttributes.Normal, ref info, Marshal.SizeOf(info), SHGFI.SHGFI_ICON | SHGFI.SHGFI_OVERLAYINDEX);
        DestroyIcon(info.hIcon); // file + overlay, we don't need it
    
        // per doc: upper eight bits of the iIcon member 
        var overlayIndex = info.iIcon >> 24; // overlayIndex >= 1 and <= 15 (system limit)
        if (overlayIndex > 0)
        {
            // change size of required icon with SHIL parameter (JUMBO is 256x256)
            SHGetImageList(SHIL.SHIL_LARGE, typeof(IImageList).GUID, out var list);
            if (list != null)
            {
                list.GetOverlayImage(overlayIndex, out var imageIndex);
                if (imageIndex >= 0)
                {
                    list.GetIcon(imageIndex, ILD.ILD_NORMAL, out var hicon);
                    if (hicon != IntPtr.Zero)
                    {
                        using (var bmp = Bitmap.FromHicon(hicon))
                        {
                            bmp.Save("Overlay" + overlayIndex + ".png");
                            DestroyIcon(hicon);
                        }
                    }
                }
            }
        }
    }
    
    [DllImport("shell32", CharSet = CharSet.Unicode)]
    public static extern IntPtr SHGetFileInfo(string pszPath, FileAttributes dwFileAttributes, ref SHFILEINFO psfi, int cbSizeFileInfo, SHGFI uFlags);
    
    [DllImport("shell32")]
    private static extern int SHGetImageList(SHIL iImageList, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IImageList ppv);
    
    [DllImport("user32")]
    public static extern bool DestroyIcon(IntPtr handle);
    
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHFILEINFO
    {
        public IntPtr hIcon;
        public int iIcon;
        public int dwAttributes;
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string szDisplayName;
    
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 80)]
        public string szTypeName;
    }
    
    public enum SHIL
    {
        SHIL_LARGE = 0,
        SHIL_SMALL = 1,
        SHIL_EXTRALARGE = 2,
        SHIL_SYSSMALL = 3,
        SHIL_JUMBO = 4,
    }
    
    [Flags]
    public enum ILD
    {
        ILD_NORMAL = 0x00000000,
        ILD_TRANSPARENT = 0x00000001,
        ILD_MASK = 0x00000010,
        ILD_IMAGE = 0x00000020,
        ILD_ROP = 0x00000040,
        ILD_BLEND25 = 0x00000002,
        ILD_BLEND50 = 0x00000004,
        ILD_OVERLAYMASK = 0x00000F00,
        ILD_PRESERVEALPHA = 0x00001000,
        ILD_SCALE = 0x00002000,
        ILD_DPISCALE = 0x00004000,
        ILD_ASYNC = 0x00008000,
        ILD_SELECTED = ILD_BLEND50,
        ILD_FOCUS = ILD_BLEND25,
        ILD_BLEND = ILD_BLEND50,
    }
    
    [ComImport, Guid("46eb5926-582e-4017-9fdf-e8998daa0950"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public partial interface IImageList
    {
        [PreserveSig]
        int Add(IntPtr hbmImage, IntPtr hbmMask, out int pi);
    
        [PreserveSig]
        int ReplaceIcon(int i, IntPtr hicon, out int pi);
    
        [PreserveSig]
        int SetOverlayImage(int iImage, int iOverlay);
    
        [PreserveSig]
        int Replace(int i, IntPtr hbmImage, IntPtr hbmMask);
    
        [PreserveSig]
        int AddMasked(IntPtr hbmImage, uint crMask, out int pi);
    
        [PreserveSig]
        int Draw(ref int pimldp);
    
        [PreserveSig]
        int Remove(int i);
    
        [PreserveSig]
        int GetIcon(int i, ILD flags, out IntPtr picon);
    
        [PreserveSig]
        int GetImageInfo(int i, out int pImageInfo);
    
        [PreserveSig]
        int Copy(int iDst, [MarshalAs(UnmanagedType.IUnknown)] object punkSrc, int iSrc, uint uFlags);
    
        [PreserveSig]
        int Merge(int i1, [MarshalAs(UnmanagedType.IUnknown)] object punk2, int i2, int dx, int dy, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
    
        [PreserveSig]
        int Clone([MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
    
        [PreserveSig]
        int GetImageRect(int i, IntPtr prc);
    
        [PreserveSig]
        int GetIconSize(out int cx, out int cy);
    
        [PreserveSig]
        int SetIconSize(int cx, int cy);
    
        [PreserveSig]
        int GetImageCount(out int pi);
    
        [PreserveSig]
        int SetImageCount(uint uNewCount);
    
        [PreserveSig]
        int SetBkColor(uint clrBk, out uint pclr);
    
        [PreserveSig]
        int GetBkColor(out uint pclr);
    
        [PreserveSig]
        int BeginDrag(int iTrack, int dxHotspot, int dyHotspot);
    
        [PreserveSig]
        int EndDrag();
    
        [PreserveSig]
        int DragEnter(IntPtr hwndLock, int x, int y);
    
        [PreserveSig]
        int DragLeave(IntPtr hwndLock);
    
        [PreserveSig]
        int DragMove(int x, int y);
    
        [PreserveSig]
        int SetDragCursorImage([MarshalAs(UnmanagedType.IUnknown)] object punk, int iDrag, int dxHotspot, int dyHotspot);
    
        [PreserveSig]
        int DragShowNolock(bool fShow);
    
        [PreserveSig]
        int GetDragImage(IntPtr ppt, IntPtr pptHotspot, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppv);
    
        [PreserveSig]
        int GetItemFlags(int i, out uint dwFlags);
    
        [PreserveSig]
        int GetOverlayImage(int iOverlay, out int piIndex);
    }
    
    [Flags]
    public enum SHGFI
    {
        SHGFI_ICON = 0x000000100,
        SHGFI_DISPLAYNAME = 0x000000200,
        SHGFI_TYPENAME = 0x000000400,
        SHGFI_ATTRIBUTES = 0x000000800,
        SHGFI_ICONLOCATION = 0x000001000,
        SHGFI_EXETYPE = 0x000002000,
        SHGFI_SYSICONINDEX = 0x000004000,
        SHGFI_LINKOVERLAY = 0x000008000,
        SHGFI_SELECTED = 0x000010000,
        SHGFI_ATTR_SPECIFIED = 0x000020000,
        SHGFI_LARGEICON = 0x000000000,
        SHGFI_SMALLICON = 0x000000001,
        SHGFI_OPENICON = 0x000000002,
        SHGFI_SHELLICONSIZE = 0x000000004,
        SHGFI_PIDL = 0x000000008,
        SHGFI_USEFILEATTRIBUTES = 0x000000010,
        SHGFI_ADDOVERLAYS = 0x000000020,
        SHGFI_OVERLAYINDEX = 0x000000040,
    }