3

I'm looking for a way to tell whether or not an EXE file contains an application icon. From the answer here, I tried this:

bool hasIcon = Icon.ExtractAssociatedIcon(exe) != null;

But this seems to work even if the EXE has no icon. Is there a way to detect this in .NET?


edit: I'm OK with solutions involving P/Invoke.

Community
  • 1
  • 1
James Ko
  • 32,215
  • 30
  • 128
  • 239
  • By "default icon" do you mean the default icon that Visual Studio gives to new Windows Forms? – cbr Nov 09 '15 at 21:39
  • Have you tried comparing it to the default icon? – Blue Nov 09 '15 at 21:39
  • @cubrr No, the default icon for executables on Windows. – James Ko Nov 09 '15 at 21:41
  • Well, for the default icon, get the byte array for that, find the image for the compare it with one in the exe, extract that icon into byte array and compare with default... – t0mm13b Nov 09 '15 at 21:41
  • 1
    If I understand you correctly, you want to detect, if the associates icon is `IDI_APPLICATION` (see [LoadIcon](https://msdn.microsoft.com/en-us/library/windows/desktop/ms648072%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396) Windows API function). As far as I see the handle of this icon is **32512**, so you can just check if your [Icon.Handle](https://msdn.microsoft.com/en-us/library/system.drawing.icon.handle%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396) equals to `(IntPtr)32512`. – Mark Shevchenko Nov 09 '15 at 21:44
  • @MarkShevchenko Yes, that's what I meant. However, `Console.WriteLine(icon.Handle)` is printing out random numbers as far as I can see. – James Ko Nov 09 '15 at 21:50
  • As far as I'm concerned, you can test for an icon, but you cannot test if the icon was taken from the default icon VS used for applications. Other than testing each pixel of the icon and see if it matches some other icon, I don't see how you could do that if that's what you're asking. – Jonathan Wood Nov 09 '15 at 22:05
  • @JonathanWood I think he is already stated above in the comments that this is not the case. If you are talking about something else, please clarify how is this different to what has been already discussed. – Andrew Savinykh Nov 09 '15 at 22:06
  • @zespri: He clarified with "the default icon for executables on Windows". He's not clear as far as I'm concerned. If he really means that the EXE has no application icon, then perhaps he should just say that.' – Jonathan Wood Nov 09 '15 at 22:09
  • @JonathanWood I've edited my question, sorry if I wasn't clear. – James Ko Nov 09 '15 at 22:11
  • It could help if you explain, *why* you'd like to do that. So let's assume you know if `exe` has an icon or not, now what? I'm asking because you can use FindResource / LoadResource / LoadIcon / LoadImage APIs, but that would mean parsing the executable's resource directory, whatever you find there might not much what explorer displays, because explorer might do it differently. So what is your purpose? – Andrew Savinykh Nov 10 '15 at 03:46

2 Answers2

1

You can get the IDI_APPLICATION icon through SystemIcons.Application property from SystemIcons class

if (Icon.ExtractAssociatedIcon(exe).Equals(SystemIcons.Application)) 
{
    ...
}

See MSDN for more details.

Lloyd
  • 29,197
  • 4
  • 84
  • 98
Meska
  • 85
  • 1
  • 10
  • Awesome answer, but `Icon` doesn't override the Equals method unfortunately, so this is reporting false on my machine... – James Ko Nov 09 '15 at 22:04
  • @JamesKo: How would this answer your question? I thought you wanted to know if the application defines an application icon. This code seems to be testing the icon against a system icon. When would that be true? – Jonathan Wood Nov 09 '15 at 22:17
  • @JonathanWood If the icon was equal to the default application icon, then that would imply it had no defined icon. – James Ko Nov 09 '15 at 22:19
  • i saved to disk the icon that returns the ExtractAssociatedIcon and the SystemIcon, they have small differences and different sizes im not sure why, they are not equals. i will try do my best to try find a reliable solution to update my answer, but i think only platform invoking could be the solution if this didnt worked as expected – Meska Nov 09 '15 at 22:19
  • @Meska Sure, I'm fine with P/Invoke. – James Ko Nov 09 '15 at 22:24
0

Try this. Define your pinvoke like this:

[DllImport("user32.dll")]
internal static extern IntPtr LoadImage(IntPtr hInst, IntPtr name, uint type, int cxDesired, int cyDesired, uint fuLoad);

[DllImport("kernel32.dll")]
static extern bool EnumResourceNames(IntPtr hModule, int dwID, EnumResNameProcDelegate lpEnumFunc, IntPtr lParam);

delegate bool EnumResNameProcDelegate(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);

[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LoadLibraryEx(string name, IntPtr handle, uint dwFlags);

private const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020;
private const int IMAGE_ICON = 1;
private const int RT_GROUP_ICON = 14;

Then you can write a function like this:

static bool HasIcon(string path)
{
    // This loads the exe into the process address space, which is necessary
    // for LoadImage / LoadIcon to work note, that LOAD_LIBRARY_AS_DATAFILE
    // allows loading a 32-bit image into 64-bit process which is otherwise impossible
    IntPtr moduleHandle = LoadLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);

    if (moduleHandle == IntPtr.Zero)
    {
        throw new ApplicationException("Cannot load executable");
    }

    IntPtr index = IntPtr.Zero;
    bool hasIndex = false;

    bool enumerated = EnumResourceNames(moduleHandle, RT_GROUP_ICON, (module, type, name, param) =>
    {
        index = name;
        hasIndex = true;
        // Only load first icon and bail out
        return false;
    }, IntPtr.Zero);

    if (!enumerated || !hasIndex)
    {
        return false;
    }

    // Strictly speaking you do not need this you can return true now
    // This is to demonstrate how to access the icon that was found on
    // the previous step
    IntPtr result = LoadImage(moduleHandle, index, IMAGE_ICON, 0, 0, 0);
    if (result == IntPtr.Zero)
    {
        return false;
    }

    return true;
}

It has added bonus that if you want to, after LoadImage you can load the icon with

Icon icon = Icon.FromHandle(result);

and do whatever you want with that.

Important note: I have not done any clean up in the function, so you cannot use it as is, you'll leak handles/memory. Proper clean up is left as an exercise for the reader. Read the description of every of the winapi function used in MSDN and call corresponding clean up functions as needed.

An alternate way using shell32 api can be found here, although I don't know if it has the same problem you encountered.

Also, old, but still very relevant article: https://msdn.microsoft.com/en-us/library/ms997538.aspx

Community
  • 1
  • 1
Andrew Savinykh
  • 25,351
  • 17
  • 103
  • 158