I would like to get the number of embedded (or associated) icons for an EXE or DLL file. In a different question, there were some code provided to extract all the icon files, but there was no information showing how to actually use the iconIndex with -1
that should give the total number of icon objects.
From the ExtractIconExW documentation:
UINT ExtractIconExW(
[in] LPCWSTR lpszFile,
[in] int nIconIndex,
[out] HICON *phiconLarge,
[out] HICON *phiconSmall,
UINT nIcons
);
with the following explanation for nIconIndex
:
If this value is –1 and phiconLarge and phiconSmall are both NULL, the function returns the total number of icons in the specified file. If the file is an executable file or DLL, the return value is the number of RT_GROUP_ICON resources. If the file is an .ico file, the return value is 1.
So I tried to do exactly this with the following:
function get-icon-count {
Param (
[parameter(Mandatory = $true)] [string] $SourceFile = 'C:/Windows/system32/shell32.dll',
[parameter(Mandatory = $False)] [Int32] $IconIndex = 0 # -1: To count icons,
)
$code = @'
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;
using System.Management.Automation;
namespace System {
public class FileIconInfo {
[DllImport("Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
public static extern int ExtractIconExW( string szFileName, int nIconIndex, out IntPtr phiconLarge, out IntPtr phiconSmall, uint nIcons);
}
}
'@
#@'
Add-Type -AssemblyName System.Drawing
$refAssemblies = @([Drawing.Icon].Assembly.Location) # C:\Program Files\PowerShell\7\System.Drawing.Common.dll
try {
Add-Type -TypeDefinition $code -PassThru -ReferencedAssemblies $refAssemblies #| Import-Module -Assembly { $_.Assembly }
$img = [System.FileIconInfo]::ExtractIconExW($SourceFile, -1, $null, $null, 1)
} Catch {
Write-Host -f Red "[ERROR] Failed in ExtractIconExW()!"
Write-Host -f Red "[ERROR] $_"
$_ | select *
Return
}
Write-Host -f DarkYellow "The number of icon indexes fund are: $([int32]$img)"
Return $([int32]$img)
}
However, running this with:
. .\extract-icon.ps1 && get-icon-count 'C:\<path-to>\shell32.dll'
You can try with any EXE's or DLL's that are known to contain a lot of icons. Like these:
'C:\Windows\SystemResources\shell32.dll.mun'
'C:\Windows\SystemResources\imageres.dll.mun'
But I get the Exception:
Argument: '3' should be a System.Management.Automation.PSReference. Use [ref].
It seem that it doesn't like the $null
.
How can I fix the script to get the number of icons in a DLL/EXE?
If you're just looking to get icon count probably ExtractIcon
is simpler than ExtractIconEx
(which is meant to extract large and small icons). In your current implementation you're also missing a call to (EDIT: This is not needed when getting the icon count - DestroyIcon
to dispose the handle as suggested in Remarks.-1
as argument to nIconIndex
- my bad).
Add-Type -Namespace Native -Name ShellApi '
[DllImport("shell32.dll", CharSet = CharSet.Unicode)]
private static extern IntPtr ExtractIconW(
[In] IntPtr hInst,
[In] string lpszExeFileName,
int nIconIndex);
public static int GetIconCount(string path)
{
return ExtractIconW(IntPtr.Zero, path, -1).ToInt32();
}'
Usage is just by passing the assembly name or path to executable or .ico
:
[Native.ShellApi]::GetIconCount('shell32.dll') # 329
[Native.ShellApi]::GetIconCount('user32.dll') # 7
[Native.ShellApi]::GetIconCount((Get-Process -Id $PID).Path) # 1