Extract A File Icon Using PowerShell and .NET

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.