Mar 26, 2009

Have you ever tried to change the default icon used on a VSNET Addin project? (Updated)

That icon is embedded in hexadecimal in the .Addin xml file.

<AboutIconData>000001000200101010000000040028010000260000002020100000000400E80200004E0100002800000010000000200000000100040000000000C0000000000000000000000000000000000000000000000000008000008000000080800080000000800080008080000080808000C0C0C0000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF000000000000000000007FFF8FF8FF8F00007FFF8FF8FF8F000078888888888800007F778778778F00007F778778778F000078888888888800007F778778778F00007F778778778F000078888888888800007F778778778F00007F778778778F000078888888800000007FFF8FF8F7F700007FFF8FF8F770000077777777770000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0010000C0030000C0070000C00F0000280000002000000040000000010004000000000000020000000000000000000010000000100000000000000000008000008000000080800080000000800080008080000080808000C0C0C0000000FF0000FF000000FFFF00FF000000FF00FF00FFFF0000FFFFFF00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000220022B2222B2022B2022B02222B22B022022B22022B2022B2022B22000022B022022B22022B2022B2022B22000022B02222B022022B2022B2022B22222B222B22022B22022B2022B2022B22022B222222022B22022B222B0222B00222B0220222022B000000000000000000000000002222B000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000555555555555555555555555555555555555555555555555555555555555555500000000000000000000000000000000055000055005555500550055055005500550005550550005505500550550550005500555505500055055005505505500055055055055000550550055055055000550550550550005505500550550550005555005505500055055555005505550055500055055000550000000000055000550000550550005500000000550550000000000000000000000000000000000555555555555555555555555555555555555555555555555555555555555555500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF30042201208420F1208420F104842000208420802080861223FFFFFF07FFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000FFFFFFFF9E60CC999C4E4C93984E4C93924E4C93924E4C93864E41918E4E7FF39E4E7F93FFFFFFFF0000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</AboutIconData>


I've found some people discussing ways to do that.
Someone talked about an alternate undocumented tag to put a reference but it didn't work, but another one really offered me the missing and useful information that the blob there in the xml is just the .ico file in hex form.

Problem is that I wanted to make it an automatic thing: edit the .ico file, build the project and voilá! the new icon shows when you select your rebuilt addin in the test VSNET instance "About" dialog.

So first I've coded this small utility program:


using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;

namespace IconHexer
{
class Program
{
static void Main(string[] args)
{
try {
if (args.Length >= 1) {
string icoFile = args[0];
if (File.Exists(icoFile)) {
StringBuilder builder = new StringBuilder();
byte[] data = File.ReadAllBytes(icoFile);
for (int i = 0; i < data.Length; i++)
builder.Append(data[i].ToString("X2"));
string hexedIcon = builder.ToString();
if (args.Length < 2)
Console.WriteLine(hexedIcon);
else
for (int j = 1; j < args.Length; j++) {
string addinFile = args[j];
if (File.Exists(addinFile)) {
string addinBackupFile = addinFile + ".backup";
string xml = File.ReadAllText(addinFile, Encoding.Unicode);
int start = xml.IndexOf("<AboutIconData>");
int end = xml.IndexOf("</AboutIconData>");
if (end > start && start > 0) {
xml = xml.Substring(0, start + "<AboutIconData>".Length)
+ hexedIcon
+ xml.Substring(end);
if (File.Exists(addinBackupFile))
File.Delete(addinBackupFile);
File.Move(addinFile, addinBackupFile);
File.WriteAllText(addinFile, xml, Encoding.Unicode);
Console.WriteLine("File '{0}' updated (backuped at '{1}')", addinFile, addinBackupFile);
}
} else {
ShowError("Addin file '{0}' doesn't exist", addinFile);
}
}
} else {
ShowError("Icon file '{0}' doesn't exist", icoFile);
}
} else {
ShowError("Please use '{0} pathToIcoFile [pathToAddinFile]'", Path.GetFileName(Assembly.GetEntryAssembly().Location));
}
} catch (Exception e) {
ShowError(e.ToString());
}
}

private static void ShowError(string format, params string[] parameters)
{
Console.Error.WriteLine("ERROR: " + format, parameters);
Environment.ExitCode = 1;
}
}
}

After building it as an exe tool in the solution I've tweaked the csproj for the Addin project to specially mark the two addin files:
<ItemGroup>
<Content Include="..\..\..\Addins\NUnitRunnerAddIn - For Testing.AddIn">
<Link>NUnitRunnerAddIn - For Testing.AddIn</Link>
<AddinFile>true</AddinFile>
</Content>
<EmbeddedResource Include="NUnitRunnerAddIn.ico" />
<Content Include="NUnitRunnerAddIn.AddIn">
<AddinFile>true</AddinFile>
</Content>
</ItemGroup>

and use them on a BeforeBuild target:
<Target Name="BeforeBuild">
<Message Importance="high" Text="Updating embedded icon on .Addin files"/>
<Exec
WorkingDirectory="$(ProjectDir)"
Command="&quot;$(SolutionDir)IconHexer\bin\$(ConfigurationName)\IconHexer&quot; &quot;$(ProjectDir)NUnitRunnerAddIn.ico&quot; &quot;%(Content.Identity)&quot;"
Condition="%(Content.AddinFile) == 'true'" />
</Target>

Last step is to put a build dependency on the tool project from the addin project, and edit the icon file (which path is hardcoded on the example above) and build the solution and run the addin and as expected the new icon is there in the VSNET about box as promised.

Hope it helps some other guy having to do that in the future...

No comments: