6
votes

I need to extract all icons from an EXE and save them as disk files, but I cannot use the simplest solution (ExtractIconEx) because I need to extract the ICO files in a way that preserves the large-size icons, even when the code runs on systems running Windows XP. I will later extract the wanted icons (128x128 and other vista size icons) using other code, but I need a way to extract all icons, with all their resources, as they are present in the EXE, regardless of whether the PC my code runs on is running XP, Vista, or Win7.

So far I know that icons are in the RT_GROUP_ICONS.

I know some people must have done this before in Delphi, because utilities like IcoFX and resource-explorer seem to have done this. I once remember seeing a completely open-source Delphi tool that would do it, but it was years ago.

To restate the problem I have with ExtractIconEx - It will not access the entire .ico file, it will only extract the supported icon resource formats, which ever single resolution (size) that the platform supports. So on XP, for example, it will extract a 32x32 or 48x48 icon, but not a Vista-format 128x128 icon.

Update: This is a modified version of the accepted answer that solves my future-proofing worry. if somehow the function we are calling was to disappear from User32.dll in a future windows version, I would want it to fail more gracefully, than to fail to load up my whole application.

unit ExtractIconUtils;

interface

uses Graphics,Forms,Windows;

//----------------------------------------------------------------------------
// ExtractIcons
// Call "private" MS Api to extract Icon file. This calls a publically
// documented function marked as deprecated in the MSDN documentation.
// It was no doubt Not Originally Intended to be documented, or publically
// accessed, but it provides functionality that its hard to live without.
// It exists on Windows 2000, XP, Vista, and Windows7, but might not exist
// in some future Windows version (released after year 2011).
//
// uses global   hUserDll    : THandle;
//----------------------------------------------------------------------------
function ExtractIcons(exeFilename,icoOutFileName:String;icoSize:Integer):Boolean;



var
  hUserDll    : THandle;





implementation



function ExtractIcons(exeFilename,icoOutFileName:String;icoSize:Integer):Boolean;
const
{$ifdef UNICODE}
 ExtractProcName='PrivateExtractIconsW';
{$else}
 ExtractProcName='PrivateExtractIconsA';
{$endif}
type
  TExtractFunc = function(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall;
var
  hIcon   : THandle;
  nIconId : DWORD;
  Icon    : TIcon;
  PrivateExtractIcons:TExtractFunc;
begin
  result := false;
  if (hUserDll<4) then begin
    hUserDll := LoadLibrary('user32.dll');
    if (hUserDll<4) then exit;
  end;

     { PrivateExtractIcons:
        MSDN documentation says that this function could go away in a future windows
        version, so we must try to load it, and if it fails, return false, rather than
        doing a static DLL import.
     }
    PrivateExtractIcons :=     GetProcAddress(hUserDll, ExtractProcName);

    if not Assigned(PrivateExtractIcons) then exit;

    //extract a icoSize x icoSize  icon where icoSize is one of 256,128,64,48,32,16
    if PrivateExtractIcons ( PWideChar(exeFilename),
                            0, icoSize, icoSize, @hIcon, @nIconId, 1, LR_LOADFROMFILE) <>0 then
    try
      Icon:=TIcon.Create;
      try
        Icon.Handle:=hIcon;
        Icon.SaveToFile(icoOutFileName);
        result := true;
      finally
        Icon.Free;
      end;
    finally
      DestroyIcon (hIcon);
    end;
end ;


initialization
  // none

finalization
   if (hUserDll>4) then
      FreeLibrary(hUserDll);

end.
2
Vista icons are 256px PNG images, not 128px, FWIWDavid Heffernan
are you tried the PrivateExtractIcons function ? msdn.microsoft.com/en-us/library/ms648075RRUZ
It's a functional working idea, but it scares me that it will break in the future (thanks MSDN!)Warren P
@WarrenP, very nice code update! :) I was just looking for something like this. BTW, I believe that DestroyIcon is not needed since Icon.Free will handle that via internal FImage: TIconImage.kobik
@WarrenP you choose by compiler switch to use ANSI or WIDE function call, but then you have to use PChar instead of PWideChar ;o) and move the hUserDLL into implementation section to make it unit global and not global to all, just to reduce sideeffectsSir Rufo

2 Answers

9
votes

The PrivateExtractIcons function can help you. You can specify the size of the icon which you want to extract:

{$IFDEF UNICODE}
    function PrivateExtractIcons(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall ; external 'user32.dll' name 'PrivateExtractIconsW';
{$ELSE}
    function PrivateExtractIcons(lpszFile: PChar; nIconIndex, cxIcon, cyIcon: integer; phicon: PHANDLE; piconid: PDWORD; nicon, flags: DWORD): DWORD; stdcall ; external 'user32.dll' name 'PrivateExtractIconsA';
{$ENDIF}

procedure TMainForm.Button1Click(Sender: TObject);
var
    hIcon   : THandle;
    nIconId : DWORD;
    Icon    : TIcon;
begin
    //Extract a 128x128 icon
    if PrivateExtractIcons ('C:\Users\Public\Documents\RAD Studio\Projects\2010\Aero Colorizer\AeroColorizer.exe', 0, 128, 128, @hIcon, @nIconId, 1, LR_LOADFROMFILE) <>0 then
    try
        Icon:=TIcon.Create;
        try
            Icon.Handle:=hIcon;
            Icon.SaveToFile(ExtractFilePath(ParamStr(0))+'Aicon.ico');
        finally
            Icon.Free;
        end;
    finally
        DestroyIcon (hIcon);
    end;
end ;
1
votes

Here is a working example on Delphi Praxis. It reads the default RT_GROUP_ICON and associated RT_ICON resources for a specified executable and saves them as as a complete multi-image .ICO file.

It seems to be confused by 256 pixel images (saves with invalid format), at least on XP, so it might need a little tweaking.