Extract A File Icon Using PowerShell and .NET
I’ve been resisting buying a Stream Deck for a while now but finally succumbed to temptation and it arrived today. Setting it up is fairly easy but associating a program with a button does not extract the icon from the executable. So I looked for a way to extract the icon myself. And of course I looked to use PowerShell.
I knew there was a Drawing namespace in .NET so I browsed the latest .NET framework on the MSDN site and found what I was looking for! When looking for information on any .NET classes, methods or properties, the MSDN site is a really good place to start.
Discovery Process
The System.Drawing
namespace has a number of classes within it. But the one called Icon
stood out and after reading more about it was the place I wanted to start. So before we begin to investigate this class and the namespace where it sits further, we need to add it to our PowerShell session.
PS> Add-Type -AssemblyName System.Drawing
The System.Drawing.Icon
class has a static member called ExtractAssociatedIcon
according to the documentation so we need to use Get-Member -Static
to see it.
PS> [System.Drawing.Icon] | Get-Member -Static
TypeName: System.Drawing.Icon
Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, Syst...
ExtractAssociatedIcon Method static System.Drawing.Icon ExtractAssociate...
FromHandle Method static System.Drawing.Icon FromHandle(Syste...
new Method System.Drawing.Icon new(System.Drawing.Icon...
ReferenceEquals Method static bool ReferenceEquals(System.Object o...
And there we have the ExtractAssociatedIcon
member. Let’s try a test and see what data is returned.
PS> $icon = [System.Drawing.Icon]::ExtractAssociatedIcon("$($ENV:windir)\Notepad.exe")
PS> $icon | Get-Member
TypeName: System.Drawing.Icon
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object IC...
CreateObjRef Method System.Runtime.Remoting.ObjRef CreateOb...
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetLifetimeService Method System.Object GetLifetimeService()
GetObjectData Method void ISerializable.GetObjectData(System...
GetType Method type GetType()
InitializeLifetimeService Method System.Object InitializeLifetimeService()
Save Method void Save(System.IO.Stream outputStream)
ToBitmap Method System.Drawing.Bitmap ToBitmap()
ToString Method string ToString()
Handle Property System.IntPtr Handle {get;}
Height Property int Height {get;}
Size Property System.Drawing.Size Size {get;}
Width Property int Width {get;}
The ToBitmap
method sounds like what we need. The MSDN documentation agrees.
PS> $icon.ToBitmap()
Tag :
PhysicalDimension : {Width=32, Height=32}
Size : {Width=32, Height=32}
Width : 32
Height : 32
HorizontalResolution : 96
VerticalResolution : 96
Flags : 2
RawFormat : [ImageFormat: b96b3caa-0728-11d3-9d7b-0000f81ef32e]
PixelFormat : Format32bppArgb
Palette : System.Drawing.Imaging.ColorPalette
FrameDimensionsList : {7462dc86-6180-4c7e-8e3f-ee7333a7a483}
PropertyIdList : {}
PropertyItems : {}
So we have the bitmap but how do we save it to a file? Again Get-Member
comes to our rescue.
PS> $icon.ToBitmap() | Get-Member
TypeName: System.Drawing.Bitmap
Name MemberType Definition
---- ---------- ----------
...
MakeTransparent Method void MakeTransparent(), void MakeTransp...
RemovePropertyItem Method void RemovePropertyItem(int propid)
RotateFlip Method void RotateFlip(System.Drawing.RotateFl...
Save Method void Save(string filename), void Save(s...
SaveAdd Method void SaveAdd(System.Drawing.Imaging.Enc...
SelectActiveFrame Method int SelectActiveFrame(System.Drawing.Im...
...
Note that Save()
method has several overloaded defintions (an overloaded method can accept different parameters). To see each overloaded method simply call it without the usual ()
.
PS> $icon.ToBitmap().Save
OverloadDefinitions
-------------------
void Save(string filename)
void Save(string filename, System.Drawing.Imaging.ImageFormat format)
void Save(string filename, System.Drawing.Imaging.ImageCodecInfo encoder,
System.Drawing.Imaging.EncoderParameters encoderParams)
void Save(System.IO.Stream stream, System.Drawing.Imaging.ImageFormat format)
void Save(System.IO.Stream stream, System.Drawing.Imaging.ImageCodecInfo
encoder, System.Drawing.Imaging.EncoderParameters encoderParams)
The only one we will use is the first one - void Save(string filename)
- which accepts a path to save to.
Save The Bitmap To A File
After our investigation now know what methods we are going to use to extract the bitmap from notepad.exe
and then save it to a file. So let’s finally save our bitmap!
PS> $icon.ToBitmap().save('notepad.bmp')
PS> ls notepad.bmp
Directory: C:\Users\Paul
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 25/07/2017 20:43 2653 notepad.bmp
If you open that bitmap you will see the notepad.exe
icon as you expected.
Stream Deck Doesn’t Like These Bitmaps
Elgato (maker of Stream Deck) recommends that the icons are at least 72 x 72 pixels. The icons we save are 32 x 32 pixels so they are a little small but the Stream Deck application can use them. For some reason that I’ve not taken too much time to understand, the Stream Deck application does not like the icons we extract. Converting them to another format seems to work. We can even then convert them back to a bitmap and they still work. So I want to save the extracted icons as a JPEG.
Save The Bitmap Icons As A JPEG
As stated earlier the Save()
method has several overloads one of which looks to be what we are looking for - void Save(string filename, System.Drawing.Imaging.ImageFormat format)
. The MSDN documentation confirms that we can use it to save the icon as a Jpeg, Gif, Tiff and more. So let’s put the code together.
PS> $icon.ToBitmap().save('notepad.jpg', [System.Drawing.Imaging.ImageFormat]::Jpeg)
PS> ls notepad.jpg
Directory: C:\Users\Paul
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 25/07/2017 20:59 1028 notepad.jpg
If you open that file you find it is a JPEG file just as we wanted! Job done. And Stream Deck is now happy too.
Code To Extract An Embedded File Icon And Save It As A JPEG
So let’s put all this together into a handy function.
Add-Type -AssemblyName System.Drawing
function Export-Icon {
<#
.SYNOPSIS
Exports a file icon to a specified format.
.DESCRIPTION
Exports the icon associated with a while to a specified image format.
.NOTES
Author : Paul Broadwith (https://github.com/pauby)
History : 1.0 - 25/07/17 - Initial version.
.LINK
https://www.github.com/pauby
.EXAMPLE
Export-Icon -Path "C:\Windows\Notepad.exe"
Exports the icon for "C:\Windows\Notepad.exe" and saves it in bitmap
(BMP) format to "Notepad.bmp" in the current current directory.
.EXAMPLE
Export-Icon -Path "C:\Windows\Notepad.exe" `
-Destination "C:\Temp\Notepad-Icon.jpg" -Format JPEG
Exports the icon for "C:\Windows\Notepad.exe" and saves it in JPEG
format to "C:\Temp\Notepad-Icon.jpg"
#>
[CmdletBinding()]
Param (
# Path of the source file the icon is to be exported from.
[Parameter(Mandatory=$true)]
[string]$Path,
# Path to where the icon will be saved to. If this is blank or missing
# the file will be saved in the current directory with the basename of
# the $Path with the correct format extension.
[string]$Destination,
# Format to save the icon as. Defaults to a bitmap (BMP).
[System.Drawing.Imaging.ImageFormat]$Format = "Bmp"
)
if (!$Destination) {
$basename = (Get-Item $Path).BaseName
$extension = $format.ToString().ToLower()
$Destination = "$basename.$extension"
}
# Extract the icon from the source file.
$icon = [System.Drawing.Icon]::ExtractAssociatedIcon($Path)
# Save the icon in the specified format
$icon.ToBitmap().Save($Destination, [System.Drawing.Imaging.ImageFormat]::$Format)
}
So there you have a new function for your collection which is particularly handy for new Stream Deck users who want to use icons that isn’t provided by default.