Skip to content

Instantly share code, notes, and snippets.

@scriptingstudio
Last active July 21, 2026 19:01
Show Gist options
  • Select an option

  • Save scriptingstudio/75ada25f1b943da9339b54d6f7662fac to your computer and use it in GitHub Desktop.

Select an option

Save scriptingstudio/75ada25f1b943da9339b54d6f7662fac to your computer and use it in GitHub Desktop.
Simple image extractor from exe/dll/osx/cpl files
<#
.SYNOPSIS
An AIO function to export icons stored within .DLL and .EXE files.
.DESCRIPTION
Export-Icon can export icons of all sizes, and all color formats to a number of formats, including bmp, png, jpg, gif, emf, exif, icon, tiff, heif, and webp. In addition, it can also export to a different size. All sizes are native, no scaling, except custom sizes.
This function quickly exports *all* icons stored within the resource file.
Export-Icon is independent on .NET version. It uses a custom icon extractor engine because Windows PowerShell doesn't have [System.Drawing.Icon]::ExtractIcon method (https://learn.microsoft.com/en-us/dotnet/api/system.drawing.icon?view=netframework-4.8.1). In addition, PowerShell 7.6+ [System.Drawing.Icon]::ExtractIcon method has a limited capability. Besides, neither ExtractIcon* functions from user32.dll have a parameter associated with size.
.PARAMETER Path
Mandatory. Position is 0. Specifies a filepath to the resource.
.PARAMETER Output
Specifies a directory where the exports should be stored. If no directory is specified, all icons will be exported to the user's TEMP directory. The value "*" selects the source's directory.
.PARAMETER Rename
Specifies an alternative name for the output files.
.PARAMETER Type
Specifies the type of file you would like to export to. The default is "ico".
Valid values are ico, icon, bmp, png, jpg, jpeg, gif, emf, exif, tiff, wmf, heif, and webp.
Note, that heif and webp are only available in PowerShell 7+.
.PARAMETER Range
Specifies index sets of icons to be exported. Empty value means *all* icons.
.PARAMETER Sizes
Primary size filter. Specifies a pixel size list of the exported icons. All icons will be squares, so if you want a 16x16 export, it would be -Sizes 16.
Standard values are 16, 24, 32, 48, 64, 72, 96, 128, and 256. The default is default size from the source. To select the standard size range specify -1. To disable this filter specify 0.
.PARAMETER Exclude
Secondary size filter. Specifies a list of sizes to exclude. The defaults are 8,20,40. To disable this filter specify 0.
.PARAMETER Colors
Indicates that the pixel format is N bits per pixel. The default is 32. You can specify a list of values. To disable the color filter specify 0.
.PARAMETER Background
Specifies the background color for the icons to be exported. The default is "Transparent",
but images like JPEG don't have the alpha channel and their background cannot be transparent, by default it is black, therefore you may need to set a desired color. For JPEG, if not specified white will be set.
You can specify the value in RGB(A) notation, i.e. "#123456EE"
Predefined colors see here https://learn.microsoft.com/en-us/dotnet/api/system.drawing.color
.PARAMETER Base64
Experimental. Indicates to create a complementary file for each exported icon with content in Base64 format.
.PARAMETER List
Indicates to get basic information about images stored in the source file.
.NOTES
Requires : PowerShell 5+
Version : 3.2
LastUpdate : 2026-July-21
.LINK
https://gist.github.com/scriptingstudio/75ada25f1b943da9339b54d6f7662fac
.EXAMPLE
Export-Icon C:\windows\system32\imageres.dll
Exports all icons stored within C:\windows\system32\imageres.dll to $env:temp\icons. Creates directory if required and automatically opens output directory.
.EXAMPLE
Export-Icon -Path "C:\Program Files (x86)\VMware\Infrastructure\Virtual Infrastructure Client\Launcher\VpxClient.exe" -Sizes 128,64 -Type png -Output C:\myicons
Exports the high-quality icon within VpxClient.exe to a transparent png in C:\myicons\. Resizes the exported image to 64x64 and 128x128.
.EXAMPLE
Export-Icon C:\windows\system32\imageres.dll -Range (10..24+0..5)
Exports icons specified by index to $env:temp\icons. Creates directory if required and automatically opens output directory.
.EXAMPLE
Export-Icon C:\windows\system32\imageres.dll -Rename myicon -Type jpg -Background Red
Exports all icons replacing default name "imageres" with "myicon" and setting background color to Red.
#>
function Export-Icon {
[CmdletBinding()]
[Alias('epi','e2i','cvi','ConvertTo-Icon')]
param (
[Parameter(Position=0,Mandatory)]
[alias('Fullname','FilePath','LiteralPath')]
[string] $Path,
[alias('directory')][string] $Output, # file folder name
[alias('alter')][string] $Rename, # the alternative name for output files
# commands
[alias('info')][switch] $List,
# output options
[ValidateSet('ico','bmp','png','jpg','gif','jpeg','emf','exif','icon','tiff','tif','wmf','heif','webp')]
[string] $Type = 'ico',
[alias('b64')][switch] $Base64, # experimental; create a complementary file; for what types?
[System.Drawing.Color] $Background = 'Transparent',
# filters
[alias('select','slice')][int[]] $Range, # icon indices
[alias('include')][int[]] $Sizes, # pri size filter
[int[]] $Exclude = (8,20), # complementary size filter
[int[]] $Colors = 32 # color filter
)
if ($IsLinux -or $IsMacOS) {
Write-Warning 'Operation is not supported on this platform. Windows only.'
return
}
# Extractor engine module
$iconHelper = @"
using System;
using System.Runtime.InteropServices;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
public sealed class IconInfo : IDisposable
{
private IconInfo(int groupIndex, string groupId, int index, string id, IntPtr handle)
{
GroupIndex = groupIndex;
GroupId = groupId;
Index = index;
Id = id;
Handle = handle;
Icon = null; // initializes later because of .NET not having System.Drawing namespace
}
public IntPtr Handle { get; private set; }
public int Index { get; private set; }
public int GroupIndex { get; private set; }
public string Id { get; private set; }
public string GroupId { get; private set; }
public object Icon { get; set; }
public void Dispose() {DestroyIcon(Handle);}
public static int GetTotal(string iconFilePath)
{
if (string.IsNullOrEmpty(iconFilePath)) return 0; // or ArgumentNullException?
IntPtr large; IntPtr small;
return ExtractIconEx(iconFilePath, -1, out large, out small, 1);
}
public static List<IconInfo> ImportIcons(string iconFilePath, int? byIndexOrResourceId = null)
{
if (string.IsNullOrEmpty(iconFilePath)) return null; // or ArgumentNullException?
var list = new List<IconInfo>();
var handle = LoadLibraryEx(iconFilePath, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (handle != IntPtr.Zero)
{
try
{
list = ImportIcons(handle, byIndexOrResourceId);
}
finally
{
FreeLibrary(handle);
}
}
return list;
}
private static List<IconInfo> ImportIcons(IntPtr handle, int? byIndexOrResourceId)
{
var list = new List<IconInfo>();
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 info = new IconInfo(groupIndices[(ushort)n.ToInt32()], groupIds[(ushort)n.ToInt32()], n.ToInt32() - 1, n.ToString(), iconHandle); //
list.Add(info);
}
return true;
}, IntPtr.Zero);
}
return list;
}
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();
}
}
}
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, 0x30000, 0, 0, 0);
}
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 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;
};
}
"@
# TODO: improve try/catch
try {$null = [IconInfo]}
catch {Add-Type -TypeDefinition $iconHelper -IgnoreWarnings -ErrorAction 0}
try {$null = [System.Drawing.Icon]}
catch {Add-Type -AssemblyName System.Drawing -IgnoreWarnings -ErrorAction 0}
#region Icon helpers module
function _importIcon ([string]$file, [int]$index=0) {
[IconInfo]::ImportIcons($file, $index) | ForEach-Object {
if ($_) {
$_.Icon = [System.Drawing.Icon]::FromHandle($_.Handle)
$_
}
}
} # END _importIcon
function _convert2ico ([string]$icofile, $igroup, [int[]]$sizes, [int[]]$exclude, [int[]]$colors) {
$png = "$env:TEMP\_tempexporticon_.png"
try {[System.IO.Stream]$outputStream = [System.IO.FileStream]::new($icofile, [System.IO.FileMode]::OpenOrCreate)} catch {return $false}
$sizes = @($sizes | Where-Object {$_ -gt 7 -and $_ -lt 257} | Sort-Object -Unique -Descending)
if ($sizes.count -eq 0) {$sizes = $igroup.icon.width}
$imageSizes = [System.Collections.Generic.List[int]]::new()
$imageStreams = foreach ($s in $sizes) {
if ($exclude.count -and $s -in $exclude) {continue}
if ($s -in $igroup.icon.width) {
$bitmap = ($igroup.icon | Where-Object {$_.width -eq $s})[0].ToBitmap()
$native = $true
}
else { # custom size
# downscale (smaller) from the nearest
# smaller, use bicubic; larger, use bilinear
$nearest = $igroup.icon | Where-Object {$_.width -gt $s}
$bitmap = $nearest[-1].icon.ToBitmap()
$native = $false
}
$cbit = [System.Drawing.Bitmap]::GetPixelFormatSize($bitmap.PixelFormat)
$bitmap.Save($png)
$bitmap.Dispose()
if ($colors.count -and $cbit -notin $colors) {continue}
[System.IO.Stream]$inputStream = [System.IO.FileStream]::new($png, [System.IO.FileMode]::Open)
if ($null -eq $inputStream) {continue}
$inputBit = [System.Drawing.Bitmap]::FromStream($inputStream)
$inputStream.Close()
Remove-Item -LiteralPath $png -Force -ErrorAction 0
if ($null -eq $inputBit) {continue}
$height = $inputBit.Height
$width = $inputBit.Width
#$width = $s # TODO: explore
#$height = $inputBit.Height / $inputBit.Width * $s
$imageSizes.Add($width)
$newBitmap = if ($native) {
[System.Drawing.Bitmap]::new($inputBit, [System.Drawing.Size]::new($width, $height))
} else {
_resize $inputBit $width $height
}
##if ($null -eq $newBitmap) {continue} # $sizes.count != $imageStreams.count
$memoryStream = [System.IO.MemoryStream]::new()
$newBitmap.Save($memoryStream, [System.Drawing.Imaging.ImageFormat]::Png)
$memoryStream
} # sizes
# possible issue: $imageSizes.count != $imageStreams.count
if ($null -eq $imageStreams -or -not @($imageStreams).count) {
$outputStream.Close()
return $false
}
$iconWriter = if ($outputStream) {[System.IO.BinaryWriter]::new($outputStream)}
if ($null -eq $iconWriter) {$outputStream.Close()}
if ($null -eq $outputStream -or $null -eq $iconWriter) {return $false}
# Normalize ico sizes for writing
$sizes = @($imageSizes)
if ($sizes.count -eq 1) { # 256 alone doesn't work, so we apply a pinpoint hack, 255
if ($sizes[0] -eq 256) {$sizes[0] = 255}
} else { # 256 will fail, so we apply a pinpoint hack, 0
$sizes = @(foreach ($item in $sizes) {
if ($item -eq 256) {0} else {$item}
})
}
# 0-1 reserved, 0
$iconWriter.Write([byte]0)
$iconWriter.Write([byte]0)
# 2-3 image type, 1 = icon, 2 = cursor
$iconWriter.Write([int16]1)
# 4-5 number of images
$iconWriter.Write([int16]$sizes.Length)
$offset = 6 + (16 * $sizes.Length)
for ($i = 0; $i -lt $sizes.Length; $i++) {
# image entry 1
# 0 image width
$iconWriter.Write([byte]$sizes[$i])
# 1 image height
$iconWriter.Write([byte]$sizes[$i])
# 2 number of colors
$iconWriter.Write([byte]0)
# 3 reserved
$iconWriter.Write([byte]0)
# 4-5 color planes
$iconWriter.Write([int16]0)
# 6-7 bits per pixel
$iconWriter.Write([int16]32)
# 8-11 size of image data
$iconWriter.Write([int]$imageStreams[$i].Length)
# 12-15 offset of image data
$iconWriter.Write($offset)
$offset += [int]$imageStreams[$i].Length;
}
# write image data
# png data must contain the whole png data file
for ($i = 0; $i -lt $sizes.Length; $i++) {
$iconWriter.Write($imageStreams[$i].ToArray())
$imageStreams[$i].Close()
$imageStreams[$i].Dispose()
}
# Cleanup
$iconWriter.Flush()
$outputStream.Close()
Remove-Item -LiteralPath $png -Force -ErrorAction 0
$true
} # END _convert2ico
function _validateIco ([string]$icoPath) {
if (Test-Path $icoPath) {
$param = @{TotalCount = 4}
if ($PSVersionTable.PSVersion.Major -lt 6) {$param['Encoding'] = 'Byte'}
else {$param['AsByteStream'] = $true}
$signature = Get-Content -LiteralPath $icoPath @param -ErrorAction 0
if ($signature -and ([System.BitConverter]::ToInt32($signature, 0)) -eq 0x10000) {
#Write-Host "ICO file successfully created in '$Output'" -ForegroundColor Green
} else {
Write-Host "ERROR: '$([System.IO.Path]::GetFileName($icoPath))' is not a valid ICO file." -ForegroundColor Red
Remove-Item -LiteralPath $icoPath -Force -ErrorAction 0
}
} else {
Write-Host "ERROR: ICO file is not created." -ForegroundColor Red
}
} # END _validateIco
function _convert2image ([string]$outFile, $igroup, [int[]]$sizes, [int[]]$exclude, [int[]]$colors, [string]$type) {
# using: $noalpha, $background
$sizes = @($sizes | Where-Object {$_ -gt 7 -and $_ -lt 257} | Sort-Object -Unique -Descending)
if ($sizes.count -eq 0) {$sizes = $igroup.icon.width}
$opstatus = 0
foreach ($s in $sizes) {
if ($exclude.count -and $s -in $exclude) {continue}
if ($s -in $igroup.icon.width) {
$native = $true
$bitmap = ($igroup.icon | Where-Object {$_.width -eq $s})[0].ToBitmap()
}
else {
$native = $false
$nearest = $igroup.icon | Where-Object {$_.width -gt $s}
$bitmap = $nearest[-1].icon.ToBitmap()
}
$cbit = [System.Drawing.Bitmap]::GetPixelFormatSize($bitmap.PixelFormat)
if ($colors.count -and $cbit -notin $colors) {$bitmap.Dispose(); continue}
$height = $bitmap.Height
$width = $bitmap.Width
#$width = $s # TODO: explore
#$height = $bitmap.Height / $bitmap.Width * $s
$newBitmap = if ($native -and $type -notmatch $noalpha) {
#[System.Drawing.Bitmap]::new($bitmap, [System.Drawing.Size]::new($width, $height))
$bitmap
} else {_resize $bitmap $width $height $Background}
$imgFile = $outFile.replace('%size%',$s)
try {
$newBitmap.Save($imgFile,[System.Drawing.Imaging.ImageFormat]$type)
if ($base64) {_toBase64 $newBitmap $imgFile}
} catch {}
$newBitmap.Dispose()
if (-not (Test-Path $imgFile)) {
Write-Warning "Failed to create file '$(Split-Path $imgFile -Leaf)'."
} else {$opstatus++}
} # sizes
$opstatus
} # END _convert2image
function _resize ([System.Drawing.Image]$image, [int]$width, [int]$height, [System.Drawing.Color]$background='Transparent') {
# HighQualityBilinear VS HighQualityBicubic
# smaller, use bicubic; larger, use bilinear
# bicubic-smoother for enlarging; bicubic-sharper for reduction
$destRect = [System.Drawing.Rectangle]::new(0, 0, $width, $height)
$destImage = [System.Drawing.Bitmap]::new($width, $height)
$destImage.SetResolution($image.HorizontalResolution, $image.VerticalResolution)
$graphics = [System.Drawing.Graphics]::FromImage($destImage)
$graphics.Clear($background)
#$graphics.CompositingMode = [System.Drawing.Drawing2D.CompositingMode]::SourceCopy
$graphics.CompositingMode = [System.Drawing.Drawing2D.CompositingMode]::SourceOver
$graphics.CompositingQuality = [System.Drawing.Drawing2D.CompositingQuality]::HighQuality
$graphics.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
$graphics.SmoothingMode = [System.Drawing.Drawing2D.SmoothingMode]::HighQuality
$graphics.PixelOffsetMode = [System.Drawing.Drawing2D.PixelOffsetMode]::HighQuality
$wrapMode = [System.Drawing.Imaging.ImageAttributes]::new()
$wrapMode.SetWrapMode([System.Drawing.Drawing2D.WrapMode]::TileFlipXY)
$graphics.DrawImage($image, $destRect, 0, 0, $image.Width, $image.Height, [System.Drawing.GraphicsUnit]::Pixel, $wrapMode)
$wrapMode.Dispose()
$graphics.Dispose()
$destImage
} # END _resize
function _toBase64 ($bitmap, $imgFile) {
$icon = [System.Drawing.Icon]::FromHandle($bitmap.GetHicon())
$memoryStream = [System.IO.MemoryStream]::new()
$icon.Save($memoryStream)
$bytes = $memoryStream.ToArray()
$memoryStream.Flush()
$memoryStream.Dispose()
$icon.Dispose()
$b64file = [System.IO.Path]::ChangeExtension($imgFile,'txt')
[Convert]::ToBase64String($bytes) | Out-File -FilePath $b64file -Encoding utf8 -Force -ErrorAction 0
} # END _toBase64
function _getinfo ($iconGroup) {
$iconGroup | ForEach-Object {
$bmp = $_.Icon.ToBitmap()
[pscustomobject]@{ # anything else?
Size = '{0}x{1}' -f $bmp.Width,$bmp.Height
Dimension = '{0}x{1}' -f $bmp.PhysicalDimension.Width,$bmp.PhysicalDimension.Height
Resolution = '{0}x{1}' -f $bmp.HorizontalResolution,$bmp.VerticalResolution
Colors = [System.Drawing.Bitmap]::GetPixelFormatSize($bmp.PixelFormat)
Flags = $bmp.Flags #[ImageFlags]$bmp.Flags
Group = $_.GroupIndex # primary key
Id = $_.GroupId
#$bmp.PropertyItems
}
$bmp.Dispose()
}
} # END _getinfo
#endregion Icon helpers
# Resolve target image type
if (-not $type) {$type = 'ico'}
if (-not $list -and $Type -match 'Heif|Webp' -and $PSVersionTable.PSEdition -ne 'Core') {
Write-Warning "The type specified ($Type) is not supported in Windows PowerShell. Please run this script in PowerShell 7+."
return
}
switch ($type) {
'jpg' {$type = 'jpeg'}
'icon' {$type = 'ico'}
'tif' {$type = 'tiff'}
}
# image formats that don't have alpha channel
$noalpha = 'jpeg' # regex
if ($type -match $noalpha -and (-not $Background -or $Background -eq 'Transparent')) {$Background = 'White'}
# Resolve file paths
$Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Path)
if (-not (Test-Path $Path -PathType Leaf -ErrorAction 0)) {
Write-Host 'ERROR: Input file is not found. Check the file path and try again.' -ForegroundColor Red
return
}
if ($Path -notmatch '\.(exe|dll|ocx|cpl)$') {
Write-Warning "It doesn't look like that the input file can contain icons."
}
if ($Output -eq '*') {$Output = [System.IO.Path]::GetDirectoryName($Path)}
elseif ($Output) {
$Output = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Output.TrimEnd('\'))
}
if (-not $list -and -not $Output -or -not (Test-Path $Output -PathType Container -ErrorAction 0)) {
$Output = "$env:TEMP\icons"
Write-Warning "Output directory is not specified or not available. Reset to default: '$Output'"
if (-not (Test-Path $Output)) { # create worktable
$null = New-Item -Path $Output -ItemType Directory -Force -ErrorAction 0
} else { # cleanup worktable
Remove-Item $Output\*.* -Force -ErrorAction 0
}
}
$basename = if ($Rename) {$Rename} else {[System.IO.Path]::GetFileNameWithoutExtension($Path)}
# Resolve extraction limits
if ($Exclude.count) {
$Exclude = @($Exclude | Where-Object {$_ -gt 15 -and $_ -lt 257} | Sort-Object -Unique)
}
if ($Colors.count) {
$Colors = @($Colors | Where-Object {$_ -gt 0} | Sort-Object -Unique)
}
if ($Sizes -eq -1) {$Sizes = 16,20,24,32,40,48,64,72,96,128,256} # 192
elseif ($Sizes.Count) {
$Sizes = $Sizes | Where-Object {
(-not $Exclude.count -or $_ -notin $Exclude) -and
($_ -gt 15 -and $_ -lt 257)
} | Sort-Object -Unique -Descending
} else {$Sizes = $null}
$startIndex = $lastIndex = 0
if ($list) {$Range = $null}
if ($Range.Count) {
$Range = @($Range | Where-Object {$_ -gt -1} | Sort-Object -Unique)
if ($Range.Count) {$startIndex,$lastIndex = $Range[0],$Range[-1]}
}
$index = $startIndex # icon counter & index
$total = [IconInfo]::GetTotal($Path)
if (-not $total -or $index -gt $total) {
Write-Warning "No icons found or start index was invalid."
return
}
Write-Host "Icons found: $total"
if ($lastIndex -lt 1 -or $lastIndex -gt $total) {$lastIndex = $total}
$info = [System.Collections.Generic.List[object]]::new()
# Extract icons
# TODO: improve operation status ($opstatus)
$opstatus = 0
if (-not $list) {Write-Host "Exporting to $($type.ToUpper())..."}
do { # till extraction error or range limit
if ($Range.Count -and $index -notin $Range) {$index++; continue}
Write-Host "Extracting image #$index of $lastIndex" -NoNewline
[array]$iconGroup = _importIcon $Path $index # handler group
if (-not $iconGroup.count) {$iconGroup = $null}
if ($null -eq $iconGroup) {
Write-Host ' - failed' -ForegroundColor Red
$index++
continue
}
Write-Host (' - {0} size(s)' -f $iconGroup.count) -ForegroundColor Green
if ($list) {
$info.AddRange(@(_getinfo $iconGroup))
}
elseif ($type -eq 'ico') {
$outFile = '{0}\{1}-{2}.{3}' -f $output,$basename,$index,$type
$opstatus += _convert2ico $outFile $iconGroup $sizes $exclude $colors
_validateIco $outFile
}
else {
$outFile = '{0}\{1}-{2}-%size%.{3}' -f $output,$basename,$index,$type
$opstatus += _convert2image $outFile $iconGroup $sizes $exclude $colors $type
}
# destroy current icon
$iconGroup | ForEach-Object {
$_.Icon.Dispose()
$_.Dispose()
}
$iconGroup.Clear()
$index++
} while ($null -ne $iconGroup -and $index -lt ($startIndex + $lastIndex))
Write-Host
# Finishing
if ($index -eq $startIndex) {
Write-Warning "No icons to extract."
if ($Output -like "$env:temp*") {Remove-Item -Path $Output -Force -Recurse -ErrorAction 0}
}
elseif ($list -and $info.count) {
#Write-Host "File '$Path' content"
$info | Group-Object -property Group | ForEach-Object {
[pscustomobject]@{
Icon = $_.name
Images = $_.count
Size = ($_.group.size | Sort-Object {[int]$_.split('x')[0]} -Unique) -join ', '
Resolution = ($_.group.Resolution | Sort-Object -Unique) -join ', '
Colors = ($_.group.Colors | Sort-Object -Unique) -join ', '
Id = ($_.group.Id | Sort-Object -Unique) -join ', '
#Dimension = ($_.group.Dimension | Sort-Object -Unique) -join ', '
#Flags = ($_.group.Flags | Sort-Object -Unique) -join ', '
File = $Path
}
}
}
elseif ($opstatus) { # TODO: improve statistics
Write-Host "Icons successfully created in '$Output' [$opstatus/$($lastIndex - $startIndex + 1) - file/image]"
if ($Output -like "$env:temp*") {Invoke-Item $Output}
}
else {
Write-Host "Failed to extract icons. Resolve all issues and try again." -ForegroundColor Red
}
} # END Export-Icon
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment