This post is both the continuation of part I and part II installments but it also addresses new product that has shipped since VSTO 2005 SE and this is VSTO 2008 (which is also known as VSTO 3.0 - see the complete matrix of VSTOs in this post by Andrew Whitechapel ).
Here is a quick recap of the last 2 episodes:
Traditionally Microsoft Office has allowed All Users installation of COM Add-Ins by the virtue of registering the add-in under HKLM\Software\Microsoft\Office\<Application>\AddIns registry key. There were few problems with managing HKLM add-ins - in particular non-admin users could not disable an add-in explicitly as well as crashing add-ins could not have been disabled automatically for such users. So, when managed add-ins were introduced in Office 2007 it was decided to break the cycle and disallow registering managed add-in under HKLM.
Today only those managed add-ins registered under HKCU registry hive are activated by Office 2007. This creates a different kind of problem - "how an administrator can deploy an Office add-in so that every user on the machine can use it?".
The part I post introduced an internal Office 2007 registry replication mechanism. The very sketchy part II post described, at a very high level, how to take advantage of the registry replication and modify the VSTO 2005 SE add-ins setup projects to be deployable to all users (The technique is a mixture of moving registry keys around in the deployment project itself coupled with creating custom actions similarly to those described in Darryn's VSTO 2005 SE deployment article). As a side note: I really should have invested more time into part II and make it a full walkthrough article. Now I am paying for my laziness since I need to answer a lot of "please clarify" comments on that post. But hey, on the flip side, trying to connect the sketchy pieces together and build the nice deployment package does build character - doesn't it?
If you are just learning this stuff - please understand there is no quick fix solution and before trying what I am going to describe below - please make sure to read all 4 articles I just mentioned: start with the deployment article (which by itself consists of 2 articles, then post I and post II).
VSTO 2008 significantly improves over VSTO2005SE deployment experience by introducing ClickOnce based deployment. The idea is that, as it is the case for ClickOnce deployment of Windows Forms project, you can now "publish" VSTO 2008 projects. This will create a setup.exe file you can give to your users to deploy your VSTO add-in.
The problem is that ClickOnce is also a per-user solution. Notice that there is no such thing as ClickOnce for All Users. And, if you are an administrator running a server and you want to deploy VSTO solution to all users on your server - you really need an MSI based setup project. This is, of course, not something we do anymore in VSTO 2008.
Let's see what can be done about it though. First of all, go and take a look into samples at http://code.msdn.microsoft.com/VSTO3MSI. These samples show how to create an MSI based deployment package for VSTO 3.0. The samples target single user - not All Users - but this is a very good start.
Key difference between VSTO 3.0 MSIs and VSTO 2.0 MSIs is that security model has changed. In VSTO 3.0 we no longer rely on CAS for security - both us and our users did have their share of problems with CAS so enough is enough. This was replaced with a concept of "Inclusion List" based security. Consequently, one of the things you will find in the site above is an InclusionListCustomActions - custom actions that call InclusionList APIs to pre-trust your solution.
So, now supposedly you have followed the VSTO3MSI samples and created an MSI based deployment package.
Next step for you is to follow general guidelines in the part II article and modify this solution to use Office's replication mechanism for All User deployment by moving registry entries from HKCU under HKLM\Software\Microsoft\Office\12.0\ User Settings\MyCompany.MyAddIn\Create registry key. Also, same post describes how to add a custom action to set up Count registry value the replication mechanism is based in and as well as handling the uninstallation of the add-in.
Next step is to remove the InclusionListCustomActions - these CAs are not good for All Users deployment.
But what do you need to do instead? Read on.
“Inclusion list” is just reg entries under HKEY_CURRENT_USER\Software\Microsoft\VSTO\Security\Inclusion – notice it is under HKCU not HKLM. The format of those registry entries is pretty self-describing – we use some random GUID to create a unique entry in the list, the entry contains the path to add-ins .vsto file and manifest's public key. Although "inclusion list" APIs create these registry entries for you but only under HKCU hive. That is not good for all users deployment. Instead your setup needs to install those registry keys into HKLM\Software\Microsoft\Office\12.0\ User Settings\MyCompany.MyAddIn\Create\Software\Microsoft\VSTO\Security\Inclusion and rely on Office’s replication mechanism to copy those into HKCU.
So, first go to HKEY_CURRENT_USER\Software\Microsoft\VSTO\Security\Inclusion and find the entry in the list that pre-trusts your solution. Export this entry into a .reg file. This file should look like this:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Microsoft\VSTO\Security\Inclusion\30b8e0dd-8ff5-4828-a53b-4933424888fd]
"Url"="file:<SOMEPATH>/ExcelWorkbook1.vsto"
"PublicKey"="<RSAKeyValue><Modulus>nPAT3uo/l/ba+L74Am8cHuxNwe50oXwpVgdKyKQOjskBkZWMMb8vcFzN91NxMj3p7CehgQeGZNuuy64wmvwiFRRq20lKXca3Iv7dkWgED6rkG20EGp4je0E1LrxdwYQg5tj5OO0gn4+nXR201tBy2BuqV1hI1ydxCPNZ+jDX+Gk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
Next, change the highlighted text to
HKEY_LOCAL_MACHINE\Software\Microsoft\Office\12.0\User Settings\<MyCompany.MyAddIn>\Create
Next, change <SOMEPATH>\ to [TARGETDIR] and save .reg file.
Your .reg file now should look something like this:
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\Software\Microsoft\Office\12.0\User Settings\<MyCompany.MyAddIn>\Create\Software\Microsoft\VSTO\Security\Inclusion\30b8e0dd-8ff5-4828-a53b-4933424888fd]
"Url"="file:[TARGETDIR]ExcelWorkbook1.vsto"
"PublicKey"="<RSAKeyValue><Modulus>nPAT3uo/l/ba+L74Am8cHuxNwe50oXwpVgdKyKQOjskBkZWMMb8vcFzN91NxMj3p7CehgQeGZNuuy64wmvwiFRRq20lKXca3Iv7dkWgED6rkG20EGp4je0E1LrxdwYQg5tj5OO0gn4+nXR201tBy2BuqV1hI1ydxCPNZ+jDX+Gk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
Go into your Visual Studio deployment project, open the Registry View, right click on "Registry on Target Machine" root node in the view and click "Import ..." to import the .reg file.
That's it - you should now have the functioning MSI based deployment package for VSTO 3.0 solution. Use it!
As usual, the post is not a complete walkthrgough and might be sketchy in parts - so if you have something to add or you have found a mistake in the post - please leave a comment.
Today I have a guest writer on my blog - Eric Faller. Eric shows the correct way to render Office icons returned by GetImageMso API. I got involved into this by replying to this post in our forums but the explanation just would not fit into a regular forums post - so here we go.
-----------------------------------------------------------------------------------------
This is a follow-up to Andrew’s post about converting between the image formats used by Office and the .NET framework. I’ll be talking about handling the alpha channel (transparency) of the images, mentioned at the end of that post and in the comments. I’d recommend reading that post first in order to get up to speed on the IPictureDisp interface and some of the other concepts we’ll be discussing.
I’d also recommend reading the RibbonX Image FAQ on Jensen Harris’ blog. It has a lengthy discussion about the different formats Office has used for image transparency in the past, as well as some common pitfalls when loading images into Office. In this post I’ll be talking about getting images out of Office, but many of the problems will be similar (DDB vs DIB, etc.).
Office 2007 introduces a new API for fetching icon images, the GetImageMso function on the CommandBars object. It takes the ID of a Ribbon control and returns its icon in IPictureDisp format. You can use one of the many methods discussed in Andrew’s previous post to convert these objects into .NET-friendly System.Drawing.Bitmap objects.
If you do, you might notice that the icons don’t look exactly correct when you draw them – the transparent edges show up white and shadow elements look black. For example here’s what the “Paste” icon looks like if drawn on a WinForm:

If you’re only using the smaller versions of the icons (16x16), drawing them on a white background, and don’t care too deeply about pixel-perfect visuals, you might be OK with this. Calling Bitmap.MakeTransparent on the icon will help get rid of the white border, but it’s still not quite perfect.
The bad news is that if you want to stick with purely .NET code, you’re stuck with this – that’s the best that your icon can look. The problem is that the alpha channel has already been lost during the conversion from IPictureDisp to System.Drawing.Bitmap.
The CLR and GDI+ internally call Win32 GDI functions during the conversion, and these functions are not alpha channel-aware. GDI itself was written long before alpha channels became popular, and as a result almost all of the standard Win32 GDI functions will ignore the alpha channel and appear to “throw it away” during various copy and conversion operations. Alpha channel support was only added with the AlphaBlend function in Windows 98/2000 with the addition of MSIMG32.DLL.
The good news is that we can get a lot better transparency in our images if we’re willing to do a little native code interop and call AlphaBlend ourselves. It’s slightly complicated, so I’ll just show you the code and then explain it. Here’s a function that will convert an IPictureDisp object to a System.Drawing.Bitmap object, using the AlphaBlend function:
public static Bitmap ConvertWithAlphaBlend(IPictureDisp ipd)
{
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
GetObjectDIBSection((IntPtr)ipd.Handle, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// zero out the RGB values for all pixels with A == 0
// (AlphaBlend expects them to all be zero)
unsafe
{
RGBQUAD* pBits = (RGBQUAD*)(void*)dibsection.dsBm.bmBits;
for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
{
int offset = y * dibsection.dsBmih.biWidth + x;
if (pBits[offset].rgbReserved == 0)
{
pBits[offset].rgbRed = 0;
pBits[offset].rgbGreen = 0;
pBits[offset].rgbBlue = 0;
}
}
}
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
// get the HDCs and select the HBITMAP
Graphics graphics = Graphics.FromImage(bitmap);
IntPtr hdcDest = graphics.GetHdc();
IntPtr hdcSrc = CreateCompatibleDC(hdcDest);
IntPtr hobjOriginal = SelectObject(hdcSrc, (IntPtr)ipd.Handle);
// render the bitmap using AlphaBlend
BLENDFUNCTION blendfunction = new BLENDFUNCTION(AC_SRC_OVER, 0, 0xFF, AC_SRC_ALPHA);
AlphaBlend(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, width, height, blendfunction);
// clean up
SelectObject(hdcSrc, hobjOriginal);
DeleteDC(hdcSrc);
graphics.ReleaseHdc(hdcDest);
graphics.Dispose();
return bitmap;
}
Except for the “unsafe” block, the code should be pretty straightforward if you’re a Win32 GDI programmer: we create a new blank 32-bit HDC from a Bitmap object, create a compatible HDC to select the IPictureDisp’s HBITMAP into, render it with AlphaBlend, and clean up.
Now we need to look at the pixel manipulations inside the “unsafe” block. If we leave that section out, this is what we would get:

This is better – the shadow inside if the icon doesn’t look as bad, but we still have the white border in the regions of the icon that are completely transparent.
The problem happens because of an ambiguity that occurs when a pixel is completely transparent. In this case the A (‘alpha’) component of the pixel is zero, but the R, G and B components of the pixel can be anything since they don’t show up. What actually happens with those values is dependent on the convention that you follow. Unfortunately, Office follows a different convention than the AlphaBlend function does. The AlphaBlend function expects the RGB values to all be zero if the A value is zero. Office leaves the R, G and B values all equal to 255, which creates the white color seen in the images above. It does this so that the transparent pixels don’t turn out black if the image is “compacted” by GDI+ or the CLR, leaving us with images that look like this by default, which is even worse than what we started with:

Fortunately we can convert between the two conventions for the completely transparent pixels by checking for zero A values and zeroing out the RGB values. It takes some unsafe code to do it, but it works. Here’s how it looks:

It looks a lot better, but if you look carefully, it’s still not perfect. The shadow has been “halftoned”: all of the alpha values have been rounded to either 0 or 255, making the shadow either completely transparent or completely black. We want a nice gray gradient shadow. It looks like the problem happens in the Bitmap object, when converting to and from the HDC. If you skip the intermediate Bitmap object and use the above code to draw directly to a Graphics object on a window, then it will render properly. I’ve played around with the PixelFormat, CompositingMode, and other parameters to the Graphics and Bitmap objects, but haven’t been able to make it work.
It looks like we’ll have to give up on using AlphaBlend and go down to the lowest level: pixel-by-pixel copying. Since we were already doing per-pixel processing in the previous function, the new one actually looks simpler:
public static Bitmap ConvertPixelByPixel(IPictureDisp ipd)
{
// get the info about the HBITMAP inside the IPictureDisp
DIBSECTION dibsection = new DIBSECTION();
GetObjectDIBSection((IntPtr)ipd.Handle, Marshal.SizeOf(dibsection), ref dibsection);
int width = dibsection.dsBm.bmWidth;
int height = dibsection.dsBm.bmHeight;
// create the destination Bitmap object
Bitmap bitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
unsafe
{
// get a pointer to the raw bits
RGBQUAD* pBits = (RGBQUAD*)(void*)dibsection.dsBm.bmBits;
// copy each pixel manually
for (int x = 0; x < dibsection.dsBmih.biWidth; x++)
for (int y = 0; y < dibsection.dsBmih.biHeight; y++)
{
int offset = y * dibsection.dsBmih.biWidth + x;
if (pBits[offset].rgbReserved != 0)
{
bitmap.SetPixel(x, y, Color.FromArgb(pBits[offset].rgbReserved, pBits[offset].rgbRed, pBits[offset].rgbGreen, pBits[offset].rgbBlue));
}
}
}
return bitmap;
}
Here’s what the final pixel-perfect result looks like:

The final question you should have now is "where can I see the complete source code?". Easy. See attached DisplayIconAddIn.zip to get a shared add-in that demos this concept.
In this post I am going to discuss how the observations made in the previous post can be incorporated into a setup project for Office 2007 Add-Ins. As with any setup we will need to address installation, un-installation and repair procedures.
- Upon the installation of Add-In we will write registry keys that are similar to what testpropagation_create.reg file from the previous post contained. This installation will contain the Create instruction telling Office propagation mechanism to copy the registry key into HKCU profile.
- During the uninstall, we will replace the Create instruction with Delete instruction which will cause Office to delete the corresponding registry key in HKCU hive. We will also bump up Count registry value to tell Office that the instruction needs to be carried out.
- During the repair we will need to increase Count value. This will cause Office to copy over the registry values from HKLM into HKCU.
Let’s first take care of the installation of the required registry keys. The easiest way to achieve this is by slightly modifying the default setup project that is created alongside the regular VSTO 2005 SE project. Let’s create a new Excel 2007 Add-In called “MyAddIn”. You will notice that in addition VSTO has also created a companion MyAddInSetup project. Let’s open the Registry view and clear out all the registry entries that have been created for you (i.e. completely clear the contents under HKEY_CURRENT_USER). Now we are going to import the registry information as presented below. To achieve this you will need to copy the below lines into a .reg file, then right-click on “Registry on Target Machine” node, select “Import …” and import the .reg file you have just created (notice that first you might want to adjust the highlighted strings for your particular situation).
Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\12.0\User Settings\MyCompany.MyAddIn\Create\Software\Microsoft\Office\Excel\Addins\MyCompany.MyAddIn]
"Description"="MyAddIn -- an addin created with VSTO technology"
"Manifest"="[TARGETDIR]MyAddIn.dll.manifest"
"FriendlyName"="MyAddIn"
"LoadBehavior"=dword:00000003
"CommandLineSafe"=dword:00000001
After you complete this operation your Registry View should be very similar to this screenshot :
By adding above entries into Registry View we ensure that corresponding keys will be removed when Add-In is uninstalled. I would also suggest to set Create's key “DeleteOnUninstall” property in the Properties Window to True to make sure it is always deleted on uninstall .
Notice that now we need to add a Count value under the MyCompany.MyAddIn key. Using the Registry View to add this value would not work because any registry keys and values added through this view will be removed on uninstall. On opposite, we need Count value not just stay when Add-In is uninstalled, but its value should be incremented.
To achieve the desired effect we will create a Custom Action that will add Count value or, in case it already exists, we will simply increment its value.
Similarly, Custom Action will create a "Delete" instruction when Add-In is uninstalled.
Below I am showing code that Darryn Lavery (who put tremendous amount of effort to design and validate all I am talking about right now) has already written for his future MSDN article on this (yeah, there will be an MSDN article with the full how-to instructions) and he kindly shared it with me and I shamelessly sharing it you, but credit sitll belongs with Darryn.
Now let's move close and see how the particular Custom Action code could look like. First, let's start defining a class RegisterOffice2007AddIn with a couple of private methods:
class RegisterOffice2007AddIn {
#region private methods
private const string userSettingsLocation = @"Software\Microsoft\Office\12.0\User Settings";
public void IncrementCounter(RegistryKey instructionKey) {
int count = 1;
object value = instructionKey.GetValue("Count");
if (value != null) {
if ((int)value != Int32.MaxValue)
count = (int)value + 1;
}
instructionKey.SetValue("Count", count);
}
private string GetApplicationPath(string applicationName) {
switch (applicationName.ToLower()) {
case "excel":
return @"Software\Microsoft\Office\Excel\Addins\";
case "infopath":
return @"Software\Microsoft\Office\InfoPath\Addins\";
case "outlook":
return @"Software\Microsoft\Office\Outlook\Addins\";
case "powerpoint":
return @"Software\Microsoft\Office\PowerPoint\Addins\";
case "word":
return @"Software\Microsoft\Office\Word\Addins\";
case "visio":
return @"Software\Microsoft\Visio\Addins\";
case "project":
return @"Software\Microsoft\Office\MS Project\Addins\";
default:
throw new Exception(applicationName + " is not a supported application", null);
}
}
# endregion
The code above contains helper method IncrementCounter that is responsible for correctly updating the Count registry value. GetApplicationPath helper method returns application-specific path Add-Ins registration key. Now, we are ready to move to the top-level function that will be called during Install and Repair:
public void RegisterAddIn(string addInName) {
RegistryKey userSettingsKey = null;
RegistryKey instructionKey = null;
try {
userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);
if (userSettingsKey == null) {
throw new Exception("Internal error: Office User Settings key does not exist", null);
}
instructionKey = userSettingsKey.OpenSubKey(addInName, true);
if (instructionKey == null) {
instructionKey = userSettingsKey.CreateSubKey(addInName);
} else {
// Remove the Delete instruction
try {
instructionKey.DeleteSubKeyTree("DELETE");
} catch (ArgumentException) { } // Delete instruction did not exist but that is ok.
}
IncrementCounter(instructionKey);
} finally {
if (instructionKey != null)
instructionKey.Close();
if (userSettingsKey != null)
userSettingsKey.Close();
}
}
In the above method, we first make sure the "Delete" instruction is gone and then we increment the Counter value. Notice that "Create" instruction is not explicitly installed by the Custom Action - this is handled by the installer automatically because of our previous work with the Register View.
And, finally, the function that will be called during uninstall:
public void UnRegisterAddIn(string applicationName, string addInName) {
RegistryKey userSettingsKey = null;
RegistryKey instructionKey = null;
RegistryKey deleteKey = null;
try {
userSettingsKey = Registry.LocalMachine.OpenSubKey(userSettingsLocation, true);
if (userSettingsKey == null) {
throw new Exception("Internal error: Office User Settings key does not exist", null);
}
instructionKey = userSettingsKey.OpenSubKey(addInName, true);
if (instructionKey == null) {
instructionKey = userSettingsKey.CreateSubKey(addInName);
} else {
// Make sure there is no Create instruction
try {
instructionKey.DeleteSubKeyTree("CREATE");
} catch (ArgumentException) { } // Create instruction did not exist but that is ok.
}
string instructionString =
@"DELETE\" +
GetApplicationPath(applicationName) +
@"\" +
addInName;