Skip to content

Instantly share code, notes, and snippets.

@santisq
Last active February 12, 2024 19:17
Show Gist options
  • Save santisq/9d070d44271a04f4a15b966ff34f4d8c to your computer and use it in GitHub Desktop.
Save santisq/9d070d44271a04f4a15b966ff34f4d8c to your computer and use it in GitHub Desktop.
Add-Type -AssemblyName System.Drawing
$refAssemblies = @(
[Drawing.Icon].Assembly.Location
if ($IsCoreCLR) {
[psobject].Assembly.Location
$pwshLocation = Split-Path -Path ([psobject].Assembly.Location) -Parent
$pwshRefAssemblyPattern = [IO.Path]::Combine($pwshLocation, 'ref', '*.dll')
(Get-Item -Path $pwshRefAssemblyPattern).FullName
}
)
Add-Type @'
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Drawing;
using System.IO;
using System.Management.Automation;
namespace ExtractIconEx
{
[Cmdlet(VerbsLifecycle.Invoke, "ExtractIconEx")]
[OutputType(typeof(FileInfo))]
public sealed class InvokeExtractIconEx : PSCmdlet
{
private string _defaultSource = "shell32.dll";
[Parameter]
[ValidateNotNullOrEmpty]
public string SourceLibrary
{
get
{
return _defaultSource;
}
set
{
_defaultSource = value;
}
}
[Parameter(Position = 0)]
[ValidateNotNullOrEmpty]
public string DestinationFolder { get; set; }
[Parameter(Position = 1)]
public int IconIndex { get; set; }
protected override void EndProcessing()
{
if (string.IsNullOrEmpty(DestinationFolder))
{
DestinationFolder = SessionState.Path.CurrentFileSystemLocation.Path;
}
try
{
FileInfo[] fileInfos = ShellApi.ExtractIcon(
SourceLibrary,
GetUnresolvedProviderPathFromPSPath(DestinationFolder),
IconIndex);
WriteObject(fileInfos, enumerateCollection: true);
}
catch (Exception e)
{
WriteError(new ErrorRecord(
e, "IconExtractError", ErrorCategory.NotSpecified, null));
}
}
}
internal sealed class SafeIconHandle : SafeHandle
{
[DllImport("user32.dll")]
private static extern bool DestroyIcon(IntPtr hIcon);
internal SafeIconHandle() : base(IntPtr.Zero, true)
{
}
public override bool IsInvalid
{
get
{
return handle == IntPtr.Zero;
}
}
protected override bool ReleaseHandle()
{
return DestroyIcon(handle);
}
}
internal static class ShellApi
{
[DllImport(
"shell32.dll",
CharSet = CharSet.Unicode,
SetLastError = true)]
private static extern uint ExtractIconExW(
string szFileName,
int nIconIndex,
out SafeIconHandle phiconLarge,
out SafeIconHandle phiconSmall,
uint nIcons);
private static void ExtractIconEx(
string fileName,
int iconIndex,
out SafeIconHandle iconLarge,
out SafeIconHandle iconSmall)
{
if (ExtractIconExW(fileName, iconIndex, out iconLarge, out iconSmall, 1) == uint.MaxValue)
{
throw new Win32Exception();
}
}
internal static FileInfo[] ExtractIcon(
string source,
string destinationFolder,
int iconIndex)
{
SafeIconHandle largeIconHandle;
SafeIconHandle smallIconHandle;
ExtractIconEx(source, iconIndex, out largeIconHandle, out smallIconHandle);
FileInfo[] outFiles;
using (largeIconHandle)
using (smallIconHandle)
using (Icon largeIcon = Icon.FromHandle(largeIconHandle.DangerousGetHandle()))
using (Icon smallIcon = Icon.FromHandle(smallIconHandle.DangerousGetHandle()))
{
outFiles = new[]
{
new FileInfo(GetPath(source, destinationFolder, "largeIcon", iconIndex)),
new FileInfo(GetPath(source, destinationFolder, "smallIcon", iconIndex))
};
largeIcon.ToBitmap().Save(outFiles[0].FullName);
smallIcon.ToBitmap().Save(outFiles[1].FullName);
}
return outFiles;
}
private static string GetPath(
string source,
string destinationFolder,
string name,
int iconIndex)
{
return Path.Combine(
destinationFolder,
string.Format("{0}-{1}-{2}.bmp", source, name, iconIndex));
}
}
}
'@ -PassThru -ReferencedAssemblies $refAssemblies | Import-Module -Assembly { $_.Assembly }

Uses ExtractIconExW function to extract icons from libraries. This function should be compatible with both, Windows PowerShell 5.1 and PowerShell 7+. It will extract both icons, large and small, from a given index (-InconIndex). The function outputs 2 FileInfo instances pointing to the created icons given in -DestinationFolder. If no destination folder is provided, the icons will be extracted to the PowerShell current directory ($pwd).

# Extracts to PWD
Invoke-ExtractIconEx -InconIndex 1

# Targeting a different library
Invoke-ExtractIconEx -SourceLibrary user32.dll -InconIndex 1

# Using a different target folder
Invoke-ExtractIconEx path\to\my\folder -InconIndex 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment