I'm trying to change the icon of external executable programmatically. I've googled and found much information about this problem using C++. Basically, I need to use BeginUpdateResource, UpdateResource and EndUpdateResource. The problem is - I don't know what to pass to UpdateResource in C#.
Here's the code I have so far:
class IconChanger
{
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr BeginUpdateResource(string pFileName,
[MarshalAs(UnmanagedType.Bool)]bool bDeleteExistingResources);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool UpdateResource(IntPtr hUpdate, string lpType, string lpName, ushort wLanguage,
IntPtr lpData, uint cbData);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool EndUpdateResource(IntPtr hUpdate, bool fDiscard);
public enum ICResult
{
Success,
FailBegin,
FailUpdate,
FailEnd
}
public ICResult ChangeIcon(string exeFilePath, byte[] iconData)
{
// Load executable
IntPtr handleExe = BeginUpdateResource(exeFilePath, false);
if (handleExe == null)
return ICResult.FailBegin;
// Get language identifier
CultureInfo currentCulture = CultureInfo.CurrentCulture;
int pid = ((ushort)currentCulture.LCID) & 0x3ff;
int sid = ((ushort)currentCulture.LCID) >> 10;
ushort languageID = (ushort)((((ushort)pid) << 10) | ((ushort)sid));
// Get pointer to data
GCHandle iconHandle = GCHandle.Alloc(iconData, GCHandleType.Pinned);
// Replace the icon
if (UpdateResource(handleExe, "#3", "#1", languageID, iconHandle.AddrOfPinnedObject(), (uint)iconData.Length))
{
if (EndUpdateResource(handleExe, false))
return ICResult.Success;
else
return ICResult.FailEnd;
}
else
return ICResult.FailUpdate;
}
}
Regarding lpType - in C++, you pass RT_ICON (or RT_GROUP_ICON). What value should I pass in C#? The same question goes for lpName parameter. I'm not sure about language identifier (I found this on Internet) since I cannot test it. I'm also not sure whether I'm providing appropriate icon data. Currently, iconData contains the bytes from .ico file.
Can anybody point me to right direction?
Thank you very much.
This is the solution that worked for me. I was not able to write it in .NET, but have managed to write C++ DLL which I am referencing in my C# application.
C++ DLL
The contents of C++ solution I am building the DLL from:
C# code
This is C# code which I'm using to import
ChangeIcon
function from previously written DLL:Hope at least somebody will find this useful.
I managed to get this working in pure C# using ResourceHacker and this posting as an example. Just use a regular .ico as input. In ResourceHacker (http://www.angusj.com/resourcehacker/) you will see the icon identifier (in my case 1) and the language identifier (in my case 1043):
I used this code:
Just some pointers, this is quite hard to get right. Pass an RT_ICON by lying about the lpType argument. Change it from string to IntPtr and pass (IntPtr)3.
The lpData argument is quite tricky. You need to pass the data the way it is compiled by the resource compiler (rc.exe). I have no idea if it mangles the raw data of the .ico file. The only reasonable thing to try is to read the data from the .ico file with FileStream into a byte[], you already seem to be doing this. I think the function was really designed to copy a resource from one binary image to another. Odds of your approach working are not zero.
You are also ignoring another potential problem, the resource ID of the icon of the program isn't necessarily 1. It often is not, 100 tends to be a popular choice, but anything goes. EnumResourceNames would be required to make it reliable. The rule is that the lowest numbered ID sets the icon for the file. I'm not actually sure if that really means that the resource compiler puts the lowest number first, something that the API probably doesn't do.
A very small failure mode is that UpdateResource can only updated numbered resource items, not named ones. Using names instead of numbers is not uncommon but the vast majority of images use numbers for icons.
And of course, the odds that this will work without a UAC manifest are zero. You are hacking files that you don't normally have write access to.
C# declaration for UpdateResource:
For a string resource type or name, you just pass the string. For system-predefined types like
RT_ICON
and int IDs likeIDI_APPLICATION
, you pass that integer value reinterpret-casting it to a pointer, like(char*)3
forRT_ICON
.