Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active August 26, 2026 06:02
Show Gist options
  • Select an option

  • Save scriptingstudio/0b15927ac5cf9016d0fdcd0dc6e101e6 to your computer and use it in GitHub Desktop.

Select an option

Save scriptingstudio/0b15927ac5cf9016d0fdcd0dc6e101e6 to your computer and use it in GitHub Desktop.
Yet another icon extractor from DLL,EXE,ICO for C#, PowerShell, and Python
/*
Synopsis : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO
Version : 3.1
Lastupdate : 2026-Aug-11
// get amount of icons stored in a dll
var icons = IconReader.GetTotal(@"c:\windows\system32\imageres.dll");
// get all icons handles with index 0 from a dll
var icons = IconReader.ImportIcons(@"c:\windows\system32\shell32.dll", 0);
// get all icons handles from a dll
var icons = IconReader.ImportIcons(@"c:\windows\system32\shell32.dll");
// get all icons handles from an ico
var icons = IconReader.ImportIcons(@"<your_ico_filepath>");
// dispose imported icons
icons.ForEach(i => i.Dispose());
*/
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
public sealed class IconReader : IDisposable
{
public IntPtr Handle { get; } // primary ID for any icon
public int Index { get; } // global index, inc multi sizing
public int GroupIndex { get; } // icon group (multi-size icon) index; for EXE,DLL
public string Id { get; } // primarily for EXE,DLL
public string GroupId { get; } // primarily for EXE,DLL
public int Width { get; }
public int Height { get; }
public int Bpp { get; }
public int Colors { get; }
public int ImgLength { get; }
public object? Icon { get; set; } // icon image storage
private IconReader(int groupIndex, string groupId, int index, string id, int width, int height, int bpp, int colors, int len, IntPtr handle)
{
GroupIndex = groupIndex;
GroupId = groupId;
Index = index;
Id = id;
Width = width;
Height = height;
Bpp = bpp;
Colors = colors;
ImgLength = len;
Handle = handle;
Icon = null; // not initialized because .NET requires System.Drawing.Common NuGet package installed
}
public override string ToString() => Index.ToString();
public void Dispose() => DestroyIcon(Handle);
// DLL,EXE: the total doesn't take into account multi sizing
public static int GetTotal(string iconFilePath)
{
if (string.IsNullOrEmpty(iconFilePath)) return 0; // or ArgumentNullException?
if (Path.GetExtension(iconFilePath).ToLower() == ".ico")
{
byte[] signature;
FileStream stream;
try {
using (stream = File.Open(iconFilePath, FileMode.Open))
{
using (var reader = new BinaryReader(stream, System.Text.Encoding.UTF8, false))
{ // don't modify "using" syntax
signature = reader.ReadBytes(6); // 4+2
if (signature.Length < 6) // corrupted
throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file.");
}
}
}
catch {throw new ArgumentException($"Unable to read '{iconFilePath}' file.");}
if (BitConverter.ToInt32(signature[..4], 0) != 0x10000) // Basic validation
throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file.");
return BitConverter.ToInt16(signature[4..6], 0);
}
else // dll or exe
{
IntPtr large; IntPtr small;
var r = ExtractIconEx(iconFilePath, -1, out large, out small, 1);
if ((uint)r == uint.MaxValue) {return 0;} else {return r;}
}
} // GetTotal
public static List<IconReader> ImportIcons(string iconFilePath, int? byIndexOrResourceId = null)
{
if (string.IsNullOrEmpty(iconFilePath))
throw new ArgumentNullException(nameof(iconFilePath));
if (Path.GetExtension(iconFilePath).ToLower() == ".ico")
return ImportIco(iconFilePath, byIndexOrResourceId);
var icons = new List<IconReader>();
var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (handle != IntPtr.Zero)
{
try
{
icons = ImportIcons(handle, byIndexOrResourceId);
}
finally
{
FreeLibrary(handle);
}
}
return icons;
} // ImportIcons
private static List<IconReader> ImportIcons(IntPtr handle, int? byIndexOrResourceId)
{
var icons = new List<IconReader>();
var entries = new Dictionary<ushort, GRPICONDIRENTRY>();
var groupIndices = new Dictionary<ushort, int>();
var groupIds = new Dictionary<ushort, string>();
var groupIndex = 0;
if (EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) =>
{
if (byIndexOrResourceId.HasValue && byIndexOrResourceId.Value >= 0 && byIndexOrResourceId.Value != groupIndex)
{
groupIndex++;
return true;
}
string name;
if (n.ToInt64() > ushort.MaxValue)
{
name = Marshal.PtrToStringAuto(n);
}
else
{
name = n.ToInt32().ToString(CultureInfo.InvariantCulture);
}
if (byIndexOrResourceId.HasValue && byIndexOrResourceId.Value < 0 && !string.Equals((-byIndexOrResourceId.Value).ToString(CultureInfo.InvariantCulture), name, StringComparison.Ordinal))
{
groupIndex++;
return true;
}
try
{
ExtractIconGroupEntries(handle, n, t, groupIndex, entries, groupIndices, groupIds);
groupIndex++;
}
catch
{
// do nothing
}
return true;
}, IntPtr.Zero))
{
EnumResourceNames(handle, new IntPtr(RT_ICON), (m, t, n, lp) =>
{
var iconHandle = ExtractIcon(handle, n, t, entries);
if (iconHandle != IntPtr.Zero)
{
var id = (ushort)n.ToInt32();
//var ent = entries[id]; // ICONDIR
var w = entries[id].bWidth == 0 ? 256 : entries[id].bWidth;
var h = entries[id].bHeight == 0 ? 256 : entries[id].bHeight;
var l = entries[id].dwBytesInRes;
var bpp = entries[id].wBitCount;
var colors = entries[id].bColorCount;
var info = new IconReader(groupIndices[id], groupIds[id], n.ToInt32() - 1, n.ToString(), w,h,bpp,colors,l, iconHandle);
icons.Add(info);
}
return true;
}, IntPtr.Zero);
}
return icons;
} // ImportIcons
private static List<IconReader> ImportIco(string iconFilePath, int? byIndexOrSize)
{
var icons = new List<IconReader>();
byte[] bytes;
try {bytes = File.ReadAllBytes(iconFilePath);} catch
{
throw new ArgumentException($"Unable to read '{iconFilePath}' file.");
}
// Basic validation
if (bytes.Length < 6 || BitConverter.ToInt32(bytes[..4],0) != 0x10000)
throw new ArgumentException($"'{iconFilePath}' is not a valid ICO file.");
int dirend = 16 * BitConverter.ToInt16(bytes[4..6],0); // dirlen * amount
for (int i = 6, k = 0; i < dirend; i += 16, k++)
{
var direntry = bytes[i..(i + 16)]; // raw icon resource
/* No advantages over simple byte indexing
GCHandle enthandle = GCHandle.Alloc(direntry, GCHandleType.Pinned);
var entry = Marshal.PtrToStructure<ICONDIRENTRY>(enthandle.AddrOfPinnedObject());
enthandle.Free();*/
int w = direntry[0] == 0 ? 256 : direntry[0];
int h = direntry[1] == 0 ? 256 : direntry[1];
var l = (int)BitConverter.ToUInt32(direntry[8..12],0);
var bpp = BitConverter.ToUInt16(direntry[6..8],0);
var dataoffset = (int)BitConverter.ToUInt32(direntry[12..16],0);
var imgbytes = bytes[dataoffset .. (dataoffset + l)];
var iconHandle = CreateIconFromResourceEx(imgbytes,l,true,0x00030000,w,h,0);
//TODO if (!byIndexOrSize.HasValue || byIndexOrSize.Value < 0 || byIndexOrSize.Value == k || byIndexOrSize.Value == w) {
var info = new IconReader(0,"", k,(k+1).ToString(), w,h,bpp,direntry[2],l, iconHandle);
icons.Add(info);
}
return icons;
} // ImportIco
private static void ExtractIconGroupEntries(IntPtr module, IntPtr name, IntPtr type, int index, Dictionary<ushort, GRPICONDIRENTRY> entries, Dictionary<ushort, int> groupIndices, Dictionary<ushort, string> groupIds)
{
var handle = FindResource(module, name, type);
if (handle == IntPtr.Zero) return;
var size = SizeofResource(module, handle);
if (size == 0) return;
var resource = LoadResource(module, handle);
if (resource == IntPtr.Zero) return;
var ptr = LockResource(resource);
if (ptr == IntPtr.Zero) return;
// GRPICONDIR
ptr += 2; // idReserved;
var idtype = Marshal.ReadInt16(ptr);
if (idtype != 1) return; // idType, 1 for ICO
var elementSize = Marshal.SizeOf<GRPICONDIRENTRY>();
ptr += 2;
var count = Marshal.ReadInt16(ptr);
ptr += 2;
for (var i = 0; i < count; i++)
{
var entry = Marshal.PtrToStructure<GRPICONDIRENTRY>(ptr);
ptr += elementSize;
entries[entry.nId] = entry;
// is it a string or an id?
groupIndices[entry.nId] = index;
if (name.ToInt64() > ushort.MaxValue)
{
var id = Marshal.PtrToStringAuto(name);
groupIds[entry.nId] = id;
}
else
{
groupIds[entry.nId] = "#" + name.ToInt32();
}
}
} // ExtractIconGroupEntries
private static IntPtr ExtractIcon(IntPtr module, IntPtr name, IntPtr type, Dictionary<ushort, GRPICONDIRENTRY> entries)
{
if (!entries.TryGetValue((ushort)name.ToInt32(), out _)) return IntPtr.Zero;
var hres = FindResource(module, name, type);
if (hres == IntPtr.Zero) return IntPtr.Zero;
var size = SizeofResource(module, hres);
if (size == 0) return IntPtr.Zero;
var res = LoadResource(module, hres);
if (res == IntPtr.Zero) return IntPtr.Zero;
var ptr = LockResource(res);
if (ptr == IntPtr.Zero) return IntPtr.Zero;
return CreateIconFromResourceEx(ptr, size, true, 0x00030000, 0, 0, 0);
} // ExtractIcon
private delegate bool EnumResNameProc(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
[DllImport("kernel32")]
private static extern int SizeofResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32")]
private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("user32")]
private static extern IntPtr CreateIconFromResourceEx(IntPtr presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags);
[DllImport("user32")]
private static extern IntPtr CreateIconFromResourceEx(byte[] presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags);
[DllImport("user32")]
private static extern bool DestroyIcon(IntPtr handle);
[DllImport("Shell32", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr LockResource(IntPtr hResData);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, int dwFlags);
[DllImport("kernel32")]
private static extern bool FreeLibrary(IntPtr hModule);
private const int LOAD_LIBRARY_AS_DATAFILE = 0x2;
private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20;
private const int RT_ICON = 3;
private const int RT_GROUP_ICON = RT_ICON + 11;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct GRPICONDIRENTRY
{
public byte bWidth;
public byte bHeight;
public byte bColorCount;
public byte bReserved;
public short wPlanes;
public short wBitCount;
public int dwBytesInRes;
public ushort nId;
}
/*[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct ICONDIRENTRY
{
public byte bWidth;
public byte bHeight;
public byte bColorCount;
public byte bReserved;
public short wPlanes;
public short wBitCount;
public int dwBytesInRes;
public int dwImageOffset;
}*/
} // IconReader
# Synopsis : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO
# Version : 3.1
# Lastupdate : 2026-Aug-11
$IconReader = @"
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
public sealed class IconReader : IDisposable
{
public IntPtr Handle { get; private set; } // primary ID for any icon
public int Index { get; private set; } // global index, inc multi sizing
public int GroupIndex { get; private set; } // icon group (multi-size icon) index; for EXE,DLL
public string Id { get; private set; } // primarily for EXE,DLL
public string GroupId { get; private set; } // primarily for EXE,DLL
public int Width { get; private set; }
public int Height { get; private set; }
public int Bpp { get; private set; }
public int Colors { get; private set; }
public int ImgLength { get; private set; }
public object Icon { get; set; } // icon image storage
private IconReader(int groupIndex, string groupId, int index, string id, int width, int height, int bpp, int colors, int len, IntPtr handle)
{
GroupIndex = groupIndex;
GroupId = groupId;
Index = index;
Id = id;
Width = width;
Height = height;
Bpp = bpp;
Colors = colors;
ImgLength = len;
Handle = handle;
Icon = null; // initializes outside the class because System.Drawing namespace should be loaded before compilation
}
public void Dispose() {DestroyIcon(Handle);}
// DLL,EXE: the total doesn't take into account multi sizing
public static int GetTotal(string iconFilePath)
{
if (string.IsNullOrEmpty(iconFilePath)) return 0; // or ArgumentNullException?
if (Path.GetExtension(iconFilePath).ToLower() == ".ico")
{
FileStream stream;
try {stream = File.Open(iconFilePath, FileMode.Open);} catch {return 0;}
var reader = new BinaryReader(stream, System.Text.Encoding.UTF8, false);
byte[] bytes = reader.ReadBytes(6); // 4+2
stream.Close();
stream.Dispose();
reader.Close();
reader.Dispose();
if (bytes.Length < 6)
throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file.");
var signature = new byte[4];
var total = new byte[2];
Array.Copy(bytes,0, signature,0,4);
Array.Copy(bytes,4, total,0,2);
if (BitConverter.ToInt32(signature,0) != 0x10000) // Basic validation
throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file.");
return BitConverter.ToInt16(total,0);
}
else // dll or exe
{
IntPtr large; IntPtr small;
var r = ExtractIconEx(iconFilePath, -1, out large, out small, 1);
if ((uint)r == uint.MaxValue) {return 0;} else {return r;}
}
} // GetTotal
public static List<IconReader> ImportIcons(string iconFilePath, int? byIndexOrResourceId = null)
{
if (string.IsNullOrEmpty(iconFilePath))
throw new ArgumentNullException("Filename is empty.");
if (Path.GetExtension(iconFilePath).ToLower() == ".ico")
return ImportIco(iconFilePath, byIndexOrResourceId);
var icons = new List<IconReader>();
var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (handle != IntPtr.Zero)
{
try
{
icons = ImportIcons(handle, byIndexOrResourceId);
}
finally
{
FreeLibrary(handle);
}
}
return icons;
} // ImportIcons
private static List<IconReader> ImportIcons(IntPtr handle, int? byIndexOrResourceId)
{
var icons = new List<IconReader>();
var entries = new Dictionary<ushort, GRPICONDIRENTRY>();
var groupIndices = new Dictionary<ushort, int>();
var groupIds = new Dictionary<ushort, string>();
var groupIndex = 0;
if (EnumResourceNames(handle, new IntPtr(RT_GROUP_ICON), (m, t, n, lp) =>
{
if (byIndexOrResourceId.HasValue && byIndexOrResourceId.Value >= 0 && byIndexOrResourceId.Value != groupIndex)
{
groupIndex++;
return true;
}
string name;
if (n.ToInt64() > ushort.MaxValue)
{
name = Marshal.PtrToStringAuto(n);
}
else
{
name = n.ToInt32().ToString(CultureInfo.InvariantCulture);
}
if (byIndexOrResourceId.HasValue && byIndexOrResourceId.Value < 0 && !string.Equals((-byIndexOrResourceId.Value).ToString(CultureInfo.InvariantCulture), name, StringComparison.Ordinal))
{
groupIndex++;
return true;
}
try
{
ExtractIconGroupEntries(handle, n, t, groupIndex, entries, groupIndices, groupIds);
groupIndex++;
}
catch
{
// do nothing
}
return true;
}, IntPtr.Zero))
{
EnumResourceNames(handle, new IntPtr(RT_ICON), (m, t, n, lp) =>
{
var iconHandle = ExtractIcon(handle, n, t, entries);
if (iconHandle != IntPtr.Zero)
{
var id = (ushort)n.ToInt32();
//var ent = entries[id]; // ICONDIR
var w = entries[id].bWidth == 0 ? 256 : entries[id].bWidth;
var h = entries[id].bHeight == 0 ? 256 : entries[id].bHeight;
var l = entries[id].dwBytesInRes;
var bpp = entries[id].wBitCount;
var colors = entries[id].bColorCount;
var info = new IconReader(groupIndices[id], groupIds[id], n.ToInt32() - 1, n.ToString(), w,h,bpp,colors,l, iconHandle);
icons.Add(info);
}
return true;
}, IntPtr.Zero);
}
return icons;
} // ImportIcons
private static List<IconReader> ImportIco(string iconFilePath, int? byIndexOrSize)
{
var icons = new List<IconReader>();
byte[] bytes;
try {bytes = File.ReadAllBytes(iconFilePath);} catch {
throw new ArgumentException("Unable to read '"+iconFilePath+"' file.");
}
if (bytes.Length < 6) // corrupted
throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file.");
var signature = new byte[4];
Array.Copy(bytes,0, signature,0,4);
if (BitConverter.ToInt32(signature,0) != 0x10000) // basic validation
throw new ArgumentException("'"+iconFilePath+"' is not a valid ICO file.");
var direntry = new byte[16];
var total = new byte[2];
Array.Copy(bytes,4, total,0,2);
int dirend = 16 * BitConverter.ToInt16(total,0); // dirlen * amount
ICONDIRENTRY entry;
for (int i = 6, k = 0; i < dirend; i += 16, k++)
{
Array.Copy(bytes,i, direntry,0,16);
GCHandle enthandle = GCHandle.Alloc(direntry, GCHandleType.Pinned);
try { // get icon resource structure
entry = Marshal.PtrToStructure<ICONDIRENTRY>(enthandle.AddrOfPinnedObject());
} catch {continue;}
finally {enthandle.Free();}
var w = entry.bWidth == 0 ? 256 : entry.bWidth;
var h = entry.bHeight == 0 ? 256 : entry.bHeight;
var l = entry.dwBytesInRes;
var imgbytes = new byte[l];
Array.Copy(bytes,entry.dwImageOffset, imgbytes,0,l);
var iconHandle = CreateIconFromResourceEx(imgbytes,l,true,0x00030000,w,h,0);
//TODO if (!byIndexOrSize.HasValue || byIndexOrSize.Value < 0 || byIndexOrSize.Value == k || byIndexOrSize.Value == w) {
var info = new IconReader(0,"",k,(k+1).ToString(), w,h,entry.wBitCount,entry.bColorCount,l, iconHandle);
icons.Add(info);
}
return icons;
} // ImportIco
private static void ExtractIconGroupEntries(IntPtr module, IntPtr name, IntPtr type, int index, Dictionary<ushort, GRPICONDIRENTRY> entries, Dictionary<ushort, int> groupIndices, Dictionary<ushort, string> groupIds)
{
var handle = FindResource(module, name, type);
if (handle == IntPtr.Zero) return;
var size = SizeofResource(module, handle);
if (size == 0) return;
var resource = LoadResource(module, handle);
if (resource == IntPtr.Zero) return;
var ptr = LockResource(resource);
if (ptr == IntPtr.Zero) return;
// GRPICONDIR
ptr += 2; // idReserved;
var idtype = Marshal.ReadInt16(ptr);
if (idtype != 1) return; // idType, 1 for ICO
var elementSize = Marshal.SizeOf<GRPICONDIRENTRY>();
ptr += 2;
var count = Marshal.ReadInt16(ptr);
ptr += 2;
for (var i = 0; i < count; i++)
{
var entry = Marshal.PtrToStructure<GRPICONDIRENTRY>(ptr);
ptr += elementSize;
entries[entry.nId] = entry;
// is it a string or an id?
groupIndices[entry.nId] = index;
if (name.ToInt64() > ushort.MaxValue)
{
var id = Marshal.PtrToStringAuto(name);
groupIds[entry.nId] = id;
}
else
{
groupIds[entry.nId] = "#" + name.ToInt32();
}
}
} // ExtractIconGroupEntries
private static IntPtr ExtractIcon(IntPtr module, IntPtr name, IntPtr type, Dictionary<ushort, GRPICONDIRENTRY> entries)
{
GRPICONDIRENTRY x;
if (!entries.TryGetValue((ushort)name.ToInt32(), out x)) return IntPtr.Zero;
var hres = FindResource(module, name, type);
if (hres == IntPtr.Zero) return IntPtr.Zero;
var size = SizeofResource(module, hres);
if (size == 0) return IntPtr.Zero;
var res = LoadResource(module, hres);
if (res == IntPtr.Zero) return IntPtr.Zero;
var ptr = LockResource(res);
if (ptr == IntPtr.Zero) return IntPtr.Zero;
return CreateIconFromResourceEx(ptr, size, true, 0x00030000, 0, 0, 0);
} // ExtractIcon
private delegate bool EnumResNameProc(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool EnumResourceNames(IntPtr hModule, IntPtr lpszType, EnumResNameProc lpEnumFunc, IntPtr lParam);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr FindResource(IntPtr hModule, IntPtr lpName, IntPtr lpType);
[DllImport("kernel32")]
private static extern int SizeofResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("kernel32")]
private static extern IntPtr LoadResource(IntPtr hModule, IntPtr hResInfo);
[DllImport("user32")]
private static extern IntPtr CreateIconFromResourceEx(IntPtr presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags);
[DllImport("user32")]
private static extern IntPtr CreateIconFromResourceEx(byte[] presbits, int dwResSize, bool fIcon, int dwVer, int cxDesired, int cyDesired, int flags);
[DllImport("user32")]
private static extern bool DestroyIcon(IntPtr handle);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr LockResource(IntPtr hResData);
[DllImport("kernel32", CharSet = CharSet.Unicode)]
private static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hFile, int dwFlags);
[DllImport("kernel32")]
private static extern bool FreeLibrary(IntPtr hModule);
[DllImport("Shell32", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
private static extern int ExtractIconEx(string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons);
private const int LOAD_LIBRARY_AS_DATAFILE = 0x2;
private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20;
private const int RT_ICON = 3;
private const int RT_GROUP_ICON = RT_ICON + 11;
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct GRPICONDIRENTRY
{
public byte bWidth;
public byte bHeight;
public byte bColorCount;
public byte bReserved;
public short wPlanes;
public short wBitCount;
public int dwBytesInRes;
public ushort nId;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
private struct ICONDIRENTRY
{
public byte bWidth;
public byte bHeight;
public byte bColorCount;
public byte bReserved;
public short wPlanes;
public short wBitCount;
public int dwBytesInRes;
public int dwImageOffset;
}
}
"@ # IconReader
Add-Type -TypeDefinition $IconReader
Add-Type -AssemblyName System.Drawing
'''
SYNOPSIS : Icon reader
DESCRIPTION : A utility class that extracts the list of all icons handles (all sizes, all color counts) for a binary file like DLL, EXE, or ICO.
FEATURES : Export: by size and index, multisize icons, split icon groups by size, various formats including ICO,PNG,JPG,WEBP,TIF; Win32 engine, PIL for none-ICO images
VERSION : 1.4
LASTUPDATE : 2026-Aug-25
STATUS : Demo as Python learning track; tested with 3.14.7
LINK : https://en.wikipedia.org/wiki/ICO_(file_format)
NOTES : Only builtin sizes can be exported, th's no native way to resize
BACKLOG : Input filters
ISSUES : PIL sucks: bmp, no transparency, no antialising; gif, no antialising
'''
import pathlib,struct,os,re
import ctypes
from ctypes import wintypes
try:
from PIL import Image # external package :-(
pilpkg = True
except: pilpkg = False
class IconReader:
@property
def Handle(self): return self.__handle # for .NET
@property
def Index(self): return self.__index
@property
def GroupIndex(self): return self.__groupIndex
@property
def Id(self): return self.__id
@property
def GroupId(self): return self.__groupId
@property
def Width(self): return self.__width
@property
def Height(self): return self.__height
@property
def Bpp(self): return self.__bpp
@property
def Colors(self): return self.__colors
@property
def ImgLength(self): return self.__imgLength
def __init__(self,groupIndex,groupId,index,iid,width,height,bpp,colors,ilen,handle,imgdata=None):
self.__handle = handle # pri dyn ID for icon in .NET; useless in Python
self.__index = index # global index, inc multi sizing
self.__groupIndex = groupIndex # EXE,DLL: icon group (multi-size icon) index; ICO: always 0
self.__id = iid # for EXE,DLL
self.__groupId = groupId # for EXE,DLL
self.__width = width # 256 if width == 0 else width
self.__height = height # 256 if height == 0 else height
self.__bpp = bpp
self.__colors = colors
self.__imgLength = ilen
self.Icon = imgdata # icon image/object storage
# Utilities
@staticmethod
def GetTotal(iconFilePath) -> int:
if os.name != 'nt' : return 0
if iconFilePath == '' or iconFilePath is None : return 0
#if pathlib.Path(iconFilePath).exists() == False : return 0
# FileNotFoundError: No such file or directory
CLS = IconReader # work around: surrogate "self"
icount = 0
if pathlib.Path(iconFilePath).suffix.lower() == '.ico':
try:
with open(iconFilePath, 'rb') as f:
data = f.read(6)
if len(data) < 6 : return 0
isignature,icount = struct.unpack(CLS.__ICOHEADER, data[0:6])
if isignature != 0x10000 : return 0
except:
print('Error reading file.')
return None
else:
handle = CLS.__load_library(CLS,iconFilePath)
if handle != 0 and handle is not None:
# enumerator callback
def lpEnumFunc(m,t,n,lp) : nonlocal icount; icount += 1; return True
CLS.__enum_resource_names(CLS,handle,CLS.__RT_GROUP_ICON,lpEnumFunc)
CLS.__kernel32.FreeLibrary(ctypes.c_ulong(handle))
return icount
# END GetTotal
@staticmethod
def ImportIcons(iconFilePath, byIndexOrResourceId=None):
if os.name != 'nt' : return None
if iconFilePath == '' or iconFilePath is None : return None
#if pathlib.Path(iconFilePath).exists() == False : return None
CLS = IconReader # work around: surrogate "self"
if pathlib.Path(iconFilePath).suffix.lower() == '.ico':
return CLS.__import_ico(CLS, iconFilePath, byIndexOrResourceId)
icons = [] # output
handle = CLS.__load_library(CLS,iconFilePath)
if handle != 0 and handle is not None:
#icons = CLS.__extract_icons(CLS, handle, byIndexOrResourceId)
#CLS.__kernel32.FreeLibrary(ctypes.c_ulong(handle))
try: icons = CLS.__extract_icons(CLS, handle, byIndexOrResourceId)
finally: CLS.__kernel32.FreeLibrary(ctypes.c_ulong(handle))
return icons
# END ImportIcons
@staticmethod
def ExportIcons(iconFilePath, iconInfo=None, outputPath=None, index=[], sizes=[], split=False, type='ico', background='#ffffff'):
if os.name != 'nt' : return 0
if (outputPath == '' or outputPath is None) and (iconFilePath == '' or iconFilePath is None) : return False
src = str(pathlib.Path(iconFilePath).resolve().suffix)[1:].lower() # exe,dll,ico
if src == 'ico' and not split and (sizes is None or len(sizes) == 0) : split = True
if iconInfo is None or len(iconInfo) == 0 : iconInfo = IconReader.ImportIcons(iconFilePath)
if iconInfo is None or len(iconInfo) == 0 : return False
# Resolve output file name and create a template
if outputPath == '' or outputPath is None:
sfx = r'-extract-%index%.ico' if src == 'ico' else r'-%index%.ico'
outputPath = str(pathlib.Path(iconFilePath).resolve().with_suffix('')) + sfx
else:
fn = pathlib.Path(iconFilePath).resolve().stem
outputPath = r'{0}\{1}-%index%.ico'.format(pathlib.Path(outputPath).resolve(), fn)
if re.match(r'c:\\(windows.+|program.+|[^\\]+)$',outputPath,re.I) is not None:
print('WARNING: System location specified as output. Export may fail. Make sure that you have write access rights.')
# Validate input filters
if index is not None or len(index) > 0:
index = sorted(
list(set([n for n in index if type(n).__name__ == 'int' and n > -1])), reverse=True
)
if sizes is not None or len(sizes) > 0:
sizes = sorted(
list(set([n for n in sizes if type(n).__name__ == 'int' and n > 15 and n < 257])), reverse=True
)
# experimental; png,jpg,...
if type.lower() != 'ico':
return IconReader.__export_image(IconReader, iconInfo, outputPath, type, index, sizes,background)
# Group by GroupIndex first; extract indices
gi_list = set([n.GroupIndex for n in iconInfo if n.Icon is not None])
for i in gi_list: # foreach index of icon resource as a group of sizes
if len(index) != 0 and i not in index : continue # filter
iconGroup = [n for n in iconInfo if i == n.GroupIndex] # select a group by index
series = len(iconGroup) if split else 1 # mode selector
for s in range(series):
icoWork = [iconGroup[s]] if split else iconGroup # select a chunk to work with
# resolve output file name
# PROBLEM: size may be wrong because of filtering (below)
icofile = outputPath.replace('%index%',f'{str(i)}%sz%%color%')
if split:
if icoWork[0].Colors == 0:
icofile = icofile.replace('%sz%%color%',f'-{icoWork[0].Width}')
else:
icofile = icofile.replace('%sz%%color%',f'-{icoWork[0].Width}-{icoWork[0].Colors}')
else:
icofile = icofile.replace('%sz%%color%','')
try: fhandle = open(icofile,'wb')
except:
print(f"ERROR: Failed to create file '{icofile}'. Check filepath, write access, file lock and try again.")
#if pathlib.Path(icofile).exists() : os.remove(icofile)
continue
inew = False # no icons to export
memstream = bytearray() # memory buffer
memstream.extend(struct.pack('@BBHH',0,0,1,len(icoWork))) # ICO file header
offset = 6 + (16 * len(icoWork))
# foreach size in icon (group of sizes)
#natives = set([n.Width for n in icoWork]) # builtin sizes
for icocount,icon in enumerate(icoWork):
#if icon.Icon is None : continue # already filtered
if len(sizes) != 0 and icon.Width not in sizes : continue # filter
w = 0 if icon.Width == 256 else icon.Width
h = 0 if icon.Height == 256 else icon.Height
entry = struct.pack('@BBBBHHII',w,h,0,0,0,32,icon.ImgLength,offset)
memstream.extend(entry)
offset += icon.ImgLength
inew = True
for icon in icoWork:
#if icon.Icon is not None: # already filtered
if len(sizes) == 0 or icon.Width in sizes : memstream.extend(icon.Icon)
icocount += 1 # because it starts with 0
if inew and icocount != len(icoWork):
# if a filter happend update icon amount value with actual
memstream[4],memstream[5] = struct.pack('@H',icocount)
if inew : fhandle.write(memstream) # flush buffer to a file
# Cleanup
fhandle.close()
memstream.clear()
icoWork.clear()
if not inew : os.remove(icofile)
else : print(f"Icon '{icofile}' successfully exported.")
iconGroup.clear()
if len(gi_list) > 0: return True
else: return False
# END ExportIcons
# Icon helpers
def __extract_icons(self, handle, byIndexOrResourceId=None):
icons = [] # output
entries = {} # directory entry table
groupIndices = {} # for DLL,EXE
groupIds = {} # for DLL,EXE
groupIndex = 0 # batch index
def lpEnumFunc(m,t,n,lp): # enumerator callback
nonlocal groupIndex, entries, groupIndices, groupIds
# odd condition: test!!!
if byIndexOrResourceId is not None and byIndexOrResourceId.Value >= 0 and byIndexOrResourceId != groupIndex:
groupIndex += 1
return True
if isinstance(n, str):
name = n
else:
name = ctypes.cast(n, ctypes.c_wchar_p).value ##.decode()
# odd condition: test!!!
if byIndexOrResourceId is not None and byIndexOrResourceId < 0 and str(-byIndexOrResourceId).upper() != name.upper():
groupIndex += 1
return True
try:
self.__extract_icon_groups(self,handle, n, t, groupIndex, entries, groupIndices, groupIds)
groupIndex += 1
except: pass # do nothing
return True
if self.__enum_resource_names(self,handle,self.__RT_GROUP_ICON,lpEnumFunc) != 0:
def lpEnumFunc(m,t,n,lp): # enumerator callback
nonlocal icons, groupIndices, groupIds
if len(entries) == 0 : return False #TODO ??? True
imgbytes = self.__get_imgbytes(self,handle,n,t) # th's no ImageOffset in RESDIR
if len(imgbytes) > 0:
iconHandle = self.__user32.CreateIconFromResourceEx(imgbytes, len(imgbytes), True, 0x00030000, 0, 0, 0)
if iconHandle != 0 and iconHandle is not None:
id = int(n)
w,h = entries[id]['bWidth'],entries[id]['bHeight']
l = entries[id]['dwBytesInRes']
bpp = entries[id]['wBitCount']
colors = entries[id]['bColorCount']
if colors == 0 and bpp == 8 : colors = 256 # normalize
if 0 == w : w = 256 # normalize
if 0 == h : h = 256 # normalize
# TODO if byIndexOrResourceId is None or byIndexOrResourceId >= 0 and (byIndexOrResourceId == w or byIndexOrResourceId == groupIndices[id]):
info = IconReader(groupIndices[id], groupIds[id], int(n) - 1, str(n), w,h,bpp,colors,l, iconHandle,imgbytes)
icons.append(info)
return True
self.__enum_resource_names(self,handle, self.__RT_ICON, lpEnumFunc)
return icons
# END __extract_icons
# experimental; requires PIL package, sucks
def __export_image(self, iconInfo, outputPath, type, index, sizes, background):
if not pilpkg:
print('ERROR: PIL package not installed. Installation command is "python -m pip install pillow".')
return False
import io
# normalize type and background
type = type.lower() # bmp,gif are unreliable
if type not in ['png','jpg','jpeg','tif','tiff','webp','bmp','gif'] : return False
if type == 'jpg' : type = 'jpeg'
elif type == 'tif' : type = 'tiff'
if background is None or background == '' : background = '#ffffff'
bmphdr = struct.pack('@BBHH',0,0,1,1) # image header
for icon in iconInfo:
if icon.Icon == None or len(icon.Icon) == 0 : continue # test for image data
ii = icon.GroupIndex
if len(index) != 0 and ii not in index : continue # filter
# for resizing; experimental
#iconGroup = [n for n in iconInfo if ii == n['GroupIndex']] # select a group by index
#natives = set([n['Width'] for n in iconGroup]) # builtin sizes
if len(sizes) != 0 and icon.Width not in sizes : continue # filter
# resolve file name
icofile = outputPath.replace('%index%',f'{str(ii)}%sz%%color%')
icofile = re.sub(r'\.ico$',f'.{type}',icofile)
if icon.Colors == 0:
icofile = icofile.replace('%sz%%color%',f'-{icon.Width}')
else:
icofile = icofile.replace('%sz%%color%',f'-{icon.Width}-{icon.Colors}')
w = 0 if icon.Width == 256 else icon.Width
h = 0 if icon.Height == 256 else icon.Height
direntry = struct.pack('@BBBBHHII', w, h, 0, 0, 0, 32, icon.ImgLength, 22)
inMemoryICO = io.BytesIO(bmphdr + direntry + icon.Icon)
img = Image.open(inMemoryICO)
if type in ['jpeg','bmp']:
bg = Image.new('RGBA',(icon.Width,icon.Height),background)
#bg.putalpha(255) # adds(255)/removes(0) antialiasing
if type == 'jpeg' : img = Image.alpha_composite(bg,img).convert('RGB')
else :
#bg.putdata([(255,255,255,0) for n in bg.getdata()])
img = Image.alpha_composite(bg,img) # this adds antialiasing
#elif type in ['gif']: # TODO remove boundary pixelization/add antialiasing
params = {'quality':100}
if type in ['jpeg','bmp'] : params['transparency'] = 0
img.save(icofile, **params)
img.close()
inMemoryICO.close()
'''try:
img = Image.open(inMemoryICO)
img.save(icofile,type)
print(f"Icon '{icofile}' successfully exported.")
except PIL.UnidentifiedImageError:
print(f"ERROR: Failed to open image.")
except OSError:
print(f"ERROR: Failed to create file '{icofile}'. Check filepath, write access, file lock and try again.")
finally:
if img != None : img.close()
inMemoryICO.close()'''
return True
# END __export_image
def __import_ico(self, iconFilePath, byIndexOrSize=None):
try:
with open(iconFilePath, 'rb') as f:
data = f.read()
if len(data) < 6 : return None
isignature,icount = struct.unpack(self.__ICOHEADER, data[0:6])
if isignature != 0x10000 or icount == 0 : return None
except:
print('Error reading file.')
return None
if byIndexOrSize is not None and byIndexOrSize < 0 : byIndexOrSize = None
ENTRYFORMAT = '@BBBBHHII'
icons = [] # output icon storage
#entrysize = struct.calcsize(ENTRYFORMAT) # must be 16
diroffset = range(6,16*icount,16)
for k,i in enumerate(diroffset):
w,h,colors,_,_,bpp,l,dataoffset = struct.unpack(ENTRYFORMAT, data[i:i+16])
if 0 == w : w = 256 # normalize
if 0 == h : h = 256 # normalize
# TODO if byIndexOrSize is None or byIndexOrSize >= 0 and (byIndexOrSize == w or byIndexOrSize == k)):
imgbytes = data[dataoffset:(dataoffset+l)]
iconHandle = self.__user32.CreateIconFromResourceEx(imgbytes, l, True, 0x0030000, w, h, 0)
info = IconReader(0, "", k, k+1, w,h,bpp,colors,l, iconHandle,imgbytes)
icons.append(info)
return icons
# END __import_ico
def __extract_icon_groups(self, module, name, type, index, entries, groupIndices, groupIds):
hres = self.__find_resource(self, module, name, type)
if hres == 0 or hres is None : return
#size = self.__sizeof_resource(self, module, hres)
#if size == 0 : return
resource = self.__load_resource(self, module, hres)
if resource == 0 or resource is None : return
ptr = self.__lock_resource(self, resource)
if ptr == 0 or ptr is None : return
# GRPICONDIR
ptr += 2 # idReserved
idtype = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_uint16)).contents.value
if idtype != 1 : return # idType: 1 - ICO, 2 - CUR
ENTRYFORMAT = '@BBBBhhIH'
entrySize = struct.calcsize(ENTRYFORMAT) # must be 14
ptr += 2
count = ctypes.cast(ptr, ctypes.POINTER(ctypes.c_uint16)).contents.value
ptr += 2
for i in range(count):
# restore ICONDIR
ebytes = ctypes.cast(ptr, ctypes.POINTER((ctypes.c_byte * entrySize))).contents # raw entry
ptr += entrySize
values = struct.unpack(ENTRYFORMAT, ebytes)
entry = dict(zip(self.__GRPICONDIRENTRY, values))
##if entry['bWidth'] == 0: entry['bWidth'] = 256 # normalize later
##if entry['bHeight'] == 0: entry['bHeight'] = 256
entries[entry['nId']] = entry
# is it a string or an id?
groupIndices[entry['nId']] = index
if isinstance(name, str):
groupIds[entry['nId']] = f"#{name}"
else:
id = ctypes.cast(name, ctypes.c_wchar_p).value #.decode()
groupIds[entry['nId']] = id
# END __extract_icon_groups
def __get_imgbytes(self, module, name, type):
# https://devblogs.microsoft.com/oldnewthing/?p=7083
hres = self.__find_resource(self, module, name, type)
if hres == 0 or hres is None : return 0
size = self.__sizeof_resource(self, module, hres)
if size == 0 : return 0
resource = self.__load_resource(self, module, hres)
if resource == 0 or resource is None : return 0
ptr = self.__lock_resource(self, resource)
if ptr == 0 or ptr is None : return 0
return ctypes.cast(ptr, ctypes.POINTER((ctypes.c_byte * size))).contents
# END __get_imgbytes
# Generic helpers
def __load_library(self, lpFileName):
LoadLibraryExW = self.__kernel32.LoadLibraryExW
LoadLibraryExW.argtypes = [wintypes.LPCWSTR, wintypes.HANDLE, wintypes.DWORD]
LoadLibraryExW.restype = wintypes.HMODULE
flags = self.__LOAD_LIBRARY_AS_DATAFILE | self.__LOAD_LIBRARY_AS_IMAGE_RESOURCE
return LoadLibraryExW(lpFileName, 0, flags)
def __enum_resource_names(self, handle, lpType, lpEnumFunc):
ENUMFUNC = ctypes.WINFUNCTYPE(ctypes.c_bool, wintypes.HMODULE, wintypes.LPVOID, wintypes.LPVOID, wintypes.LPARAM)
enum_func = ENUMFUNC(lpEnumFunc)
enum_func.restype = ctypes.c_bool
EnumResourceNames = self.__kernel32.EnumResourceNamesW
EnumResourceNames.argtypes = [wintypes.HMODULE, wintypes.LPVOID, ENUMFUNC, wintypes.LPARAM]
EnumResourceNames.restype = ctypes.c_bool
return EnumResourceNames(handle, lpType, enum_func, 0)
def __find_resource(self, hModule, lpName, lpType):
FindResource = self.__kernel32.FindResourceW
FindResource.argtypes = [wintypes.HMODULE, wintypes.LPVOID, wintypes.LPVOID]
FindResource.restype = wintypes.HRSRC
return FindResource(hModule, lpName, lpType)
def __sizeof_resource(self, hModule, hResInfo):
SizeofResource = self.__kernel32.SizeofResource
SizeofResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC]
SizeofResource.restype = wintypes.DWORD
return SizeofResource(hModule, hResInfo)
def __load_resource(self, hModule, hResInfo):
LoadResource = self.__kernel32.LoadResource
LoadResource.argtypes = [wintypes.HMODULE, wintypes.HRSRC]
LoadResource.restype = wintypes.HGLOBAL
return LoadResource(hModule, hResInfo)
def __lock_resource(self, hResData):
LockResource = self.__kernel32.LockResource
LockResource.argtypes = [wintypes.HGLOBAL]
LockResource.restype = ctypes.c_void_p
return LockResource(hResData)
__user32 = ctypes.WinDLL(r'C:\Windows\System32\user32.dll')
__kernel32 = ctypes.WinDLL(r'C:\Windows\System32\kernel32.dll')
__LOAD_LIBRARY_AS_DATAFILE = 0x2
__LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x20
__RT_ICON = 3
__RT_GROUP_ICON = __RT_ICON + 11
__GRPICONDIRENTRY = ["bWidth","bHeight","bColorCount","bReserved","wPlanes","wBitCount","dwBytesInRes","nId"]
#__ICONDIRENTRY = ["bWidth","bHeight","bColorCount","bReserved","wPlanes","wBitCount","dwBytesInRes","dwImageOffset"]
__ICOHEADER = '@IH'
# END class IconReader
icons = IconReader.ImportIcons(r'C:\windows\system32\WindowsPowerShell\v1.0\PowerShell.exe')
fmtR = '{0:>{1}}'
fmtL = '{0:<{1}}'
for i in icons:
print(
fmtR.format(i.Handle,11),
fmtR.format(i.Index,3),
fmtR.format(i.Width,4),
fmtR.format(i.Bpp,3),
fmtR.format(i.Colors,3),
fmtR.format(i.ImgLength,8),
fmtR.format(i.GroupIndex,3),
fmtL.format(i.GroupId,13)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment