---
title: "Howto: Set custom Visual Studio Addin menu icons without a satellite dll"
url: "https://osherove.com/blog/2008/8/2/howto-set-custom-visual-studio-addin-menu-icons-without-a-sa.md"
canonical: "https://osherove.com/blog/2008/8/2/howto-set-custom-visual-studio-addin-menu-icons-without-a-sa.html"
published: "2008-08-02T03:28:59"
updated: "2008-08-02T03:28:59"
tags: [".NET", ".Net 2.0", "Extensibility", "Tips & Tricks"]
author: "Roy Osherove"
description: "Update : Here is a more full featured declarative example of doing buttons with icons . OK. Setting icons on your visual studio addins is officially a sucky process and I wish MS would make this a much less painful…"
usage: "Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page)."
license: "https://creativecommons.org/licenses/by/4.0/"
---

# Howto: Set custom Visual Studio Addin menu icons without a satellite dll

By Roy Osherove August 2, 2008 In [.NET](https://osherove.com/blog/category/net), [.Net 2.0](https://osherove.com/blog/category/net-2-0), [Extensibility](https://osherove.com/blog/category/extensibility), [Tips & Tricks](https://osherove.com/blog/category/tips-tricks)

**Historical article** · Originally published August 2, 2008; technical details may be outdated.

**Update**: [Here is a more full featured declarative example of doing buttons with icons](http://weblogs.asp.net/rosherove/archive/2008/08/02/declarative-visual-studio-addin-buttons-with-icons.aspx).

OK. Setting icons on your visual studio addins is **officially** a sucky process and I wish MS would make this a much less painful experience. I cannot **believe** how much time I have wasted on this.Thanks to [Jamie](http://weblogs.asp.net/nunitaddin/) I have a solution for my problems and I’ve added a couple more things.

Problem: You created an addin for VS and you’d like to have custom icons. You google for it and find that it’s not trivial.

**Solution**: There are a couple of ways to do this. the one I will **not** show is [having a satellite DLL](http://msdn.microsoft.com/en-us/library/ms228771.aspx) that has the icons resources for the addin. If you are working with VS 2003 as well, you’ll have to do it [this way](http://msdn.microsoft.com/en-us/library/ms228771.aspx).

Here is the way I’m doing it right now. A lot of this code is from inside testdriven.net’s assemblies (got jamies permission to post this) and I put it in a helper class that I can use easily. In short, you need the following code (will handle .ico and .bmp). see how to use it later below.

using System;

using System.Drawing;

using System.Drawing.Imaging;

using System.Runtime.InteropServices;

using System.Windows.Forms;

using EnvDTE;

using EnvDTE80;

using Microsoft.VisualStudio.CommandBars;

using stdole;

namespace MyAddin1

{

    public class IconUtils

    {

        public static void AddCommandWithicon(Commands2 root,

            CommandBarPopup menuToAddTo,

           string name,

           string buttonText,

           string buttonDescription,

           string iconFile,

           AddIn addInInstance)

        {

            object\[\] contextGUIDS \= null;

            Command command \= root.AddNamedCommand2(addInInstance,

                                                        name,

                                                        buttonText,

                                                        buttonDescription,

                                                        true, 59,

                                                        ref contextGUIDS,

                                                        (int)vsCommandStatus.vsCommandStatusSupported +

                                                        (int)vsCommandStatus.vsCommandStatusEnabled,

                                                        (int)vsCommandStyle.vsCommandStylePictAndText,

                                                        vsCommandControlType.vsCommandControlTypeButton);

            CommandBarButton control \= (CommandBarButton)command.AddControl(menuToAddTo.CommandBar, 1);

            SetControlPicture(control, iconFile);

        }

        private static Color guessTransparentColor(Bitmap bitmap)

        {

            Color pixel \= bitmap.GetPixel(0, 0);

            Color color2 \= bitmap.GetPixel(bitmap.Width \- 1, 0);

            Color color3 \= bitmap.GetPixel(0, bitmap.Height \- 1);

            Color color4 \= bitmap.GetPixel(bitmap.Width \- 1, bitmap.Height \- 1);

            if (pixel \== color2)

            {

                return pixel;

            }

            if (color2 \== color3)

            {

                return color2;

            }

            if (color3 \== color4)

            {

                return color3;

            }

            return color4;

        }

        protected static Bitmap PrepareImage(Image image, Color transparentColor)

        {

            Bitmap bitmap \= new Bitmap(image);

            Bitmap bitmap2 \= new Bitmap(0x10, 0x10, PixelFormat.Format24bppRgb);

            Color color \= guessTransparentColor(bitmap);

            for (int i \= 0; i < bitmap2.Width; i++)

            {

                for (int j \= 0; j < bitmap2.Height; j++)

                {

                    Color baseColor \= color;

                    if ((i < bitmap.Width) && (j < bitmap.Height))

                    {

                        baseColor \= bitmap.GetPixel(i, j);

                    }

                    baseColor \= Color.FromArgb(0xff, baseColor);

                    if (baseColor != color)

                    {

                        bitmap2.SetPixel(i, j, baseColor);

                    }

                    else if ((transparentColor != color) && (baseColor \== transparentColor))

                    {

                        if (baseColor.R \> 0)

                        {

                            bitmap2.SetPixel(i, j, Color.FromArgb(baseColor.R \- 1, baseColor.G, baseColor.B));

                        }

                        else

                        {

                            bitmap2.SetPixel(i, j, Color.FromArgb(baseColor.R + 1, baseColor.G, baseColor.B));

                        }

                    }

                    else

                    {

                        bitmap2.SetPixel(i, j, transparentColor);

                    }

                }

            }

            return bitmap2;

        }

        protected static Bitmap PrepareMask(Image image)

        {

            Bitmap bitmap \= new Bitmap(image);

            Bitmap bitmap2 \= new Bitmap(0x10, 0x10, PixelFormat.Format24bppRgb);

            Color color \= guessTransparentColor(bitmap);

            for (int i \= 0; i < image.Width; i++)

            {

                for (int j \= 0; j < image.Height; j++)

                {

                    Color pixel \= bitmap.GetPixel(i, j);

                    Color color3 \= ((pixel \== color) || (pixel.A < 0xff)) ? Color.White : Color.Black;

                    bitmap2.SetPixel(i, j, color3);

                }

            }

            return bitmap2;

        }

        \[StructLayout(LayoutKind.Sequential, CharSet \= CharSet.Auto)\]

        private struct SHFILEINFO

        {

            public IntPtr hIcon;

            public int iIcon;

            public int dwAttributes;

            \[MarshalAs(UnmanagedType.ByValArray, SizeConst \= 0x100)\]

            public char\[\] szDisplayName;

            \[MarshalAs(UnmanagedType.ByValArray, SizeConst \= 80)\]

            public char\[\] szTypeName;

        }

        protected static Icon GetIconFromFile(string path)

        {

            SHFILEINFO psfi \= new SHFILEINFO();

            SHGetFileInfo(path, 0x80, ref psfi, Marshal.SizeOf(psfi), 0x111);

            return Icon.FromHandle(psfi.hIcon);

        }

        \[DllImport("shell32.dll", CharSet \= CharSet.Auto)\]

        private static extern IntPtr SHGetFileInfo(string pszPath, int dwFileAttributes, ref SHFILEINFO psfi, int cbFileInfo, int uFlags);

        \[DllImport("oleaut32.dll", CharSet \= CharSet.Auto, SetLastError \= true)\]

        internal static extern int OleLoadPictureFile(object fileName, \[MarshalAs(UnmanagedType.IDispatch)\] ref object iPictureDisp);

        static Color VS\_MENUCOLOR \= Color.FromArgb(0xec, 0xe9, 0xd8);

        public static void SetControlPicture(CommandBarButton button,string fileName)

        {

            Image image1 \= GetImageFromAnyFormat(fileName);

            Bitmap image2 \= PrepareImage(image1, VS\_MENUCOLOR);

            Bitmap image3 \= PrepareMask(image2);

            button.Picture \= CreatePictureDisp(image2);

        }

        protected static Image GetImageFromAnyFormat(string path)

        {

            string str \= path.ToLower();

            if (str.EndsWith(".exe") || str.EndsWith(".ico"))

            {

                return GetIconFromFile(path).ToBitmap();

            }

            return Image.FromFile(path, true);

        }

        protected static object OleLoadPictureFile(string fileName)

        {

            object iPictureDisp \= null;

            OleLoadPictureFile(fileName, ref iPictureDisp);

            return iPictureDisp;

        }

        protected static StdPicture CreatePictureDisp(Image image)

        {

            return (StdPicture) ImageConverter.GetIPictureDispFromImage(image);

            //here is another approach:

//            string tempFileName = Path.GetTempFileName();

//            image.Save(tempFileName, ImageFormat.Bmp);

//            return (StdPicture) OleLoadPictureFile(tempFileName);

        }

    }

    class ImageConverter : AxHost

    {

        // Methods

        internal ImageConverter()

            : base("52D64AAC-29C1-CAC8-BB3A-115F0D3D77CB")

        {

        }

        public static IPictureDisp GetIPictureDispFromImage(Image image)

        {

            return (IPictureDisp)AxHost.GetIPictureDispFromPicture(image);

        }

    }

}

Here is how you can use this code:

Here is what the code in Connect.cs would look if I were creating two buttons with the same icon to the “tools”:

public void OnConnection(object application, ext\_ConnectMode connectMode, object addInInst, ref Array custom)

        {

            \_applicationObject \= (DTE2)application;

            \_addInInstance \= (AddIn)addInInst;

            if(connectMode \== ext\_ConnectMode.ext\_cm\_UISetup)

            {

                Commands2 commands \= (Commands2)\_applicationObject.Commands;

                CommandBar menuBarCommandBar \= ((CommandBars)\_applicationObject.CommandBars)\["MenuBar"\];

                CommandBarControl toolsControl \= menuBarCommandBar.Controls\["Tools"\];

                IconUtils.AddCommandWithicon(commands, (CommandBarPopup)toolsControl,

                    "SomeCommandName",

                    "My Button 1",

                    "Executes the command for MyAddin1",

                    @"c:\\a.bmp",

                    \_addInInstance);

                IconUtils.AddCommandWithicon(commands, (CommandBarPopup)toolsControl,

                    "SomeCommandName2",

                    "My Button 2",

                    "Executes the command for MyAddin1",

                    @"c:\\a.bmp",

                    \_addInInstance);

            }

        }

In the next post I’ll describe how sucky the “command” model is with VS addins and how you can create a somewhatreasonable model for custom actions and buttons that is more maintainable and nderstandable.

## More in .NET

[All posts in .NET](https://osherove.com/blog/category/net)

- Aug 2, 2008

    ### [Declarative Visual Studio addin buttons with icons](https://osherove.com/blog/2008/8/2/declarative-visual-studio-addin-buttons-with-icons.html)

- Aug 2, 2008

    ### [Deadlock detectors](https://osherove.com/blog/2008/8/2/deadlock-detectors.html)

- Jul 15, 2008

    ### [Configuration via Typemock Interception](https://osherove.com/blog/2008/7/15/configuration-via-typemock-interception.html)

- Jul 14, 2008

    ### [ALT.NET Israel registration is open](https://osherove.com/blog/2008/7/14/altnet-israel-registration-is-open.html)


---

## About the author

**Roy Osherove**: Senior Solutions Architect at AWS working on AI for production and AI-driven development; author of The Art of Unit Testing and Elastic Leadership, with about 20 years of consulting and training on unit testing, TDD, team leadership and continuous delivery.

### Sites

- [RobotPaper](https://robotpaper.ai/): This blog: AI patterns and anti-patterns, AI and coding agents, and engineering leadership.
- [osherove.com](https://osherove.com): Roy's personal site. Author of The Art of Unit Testing and Elastic Leadership; consulting and training on unit testing, TDD, team leadership and continuous delivery, plus about 20 years of blog posts.
- [The Art of Unit Testing](https://www.artofunittesting.com): The Art of Unit Testing, 3rd Edition (Manning): readable, maintainable, trustworthy unit tests, plus TDD workshops in C#, JavaScript, Java and Kotlin.
- [5whys.com](https://www.5whys.com): Roy's writing on software team leadership: self-organizing teams, surviving chaos, learning mode and the Elastic Leadership model.
- [Pipeline Driven](https://pipelinedriven.org): Continuous delivery and DevOps practices: build patterns, pipeline-friendly testing, DevOps metrics and enterprise DevOps.
- [Elastic Leadership](https://www.elasticleadership.com): Elastic Leadership, Roy's framework for software team leaders (survival, learning and self-organizing modes).

### Social

- X: https://x.com/royosherove
- LinkedIn: https://www.linkedin.com/in/osherove/

### Contact

- Contact form: https://forms.gle/jQSx5KoXvrVA1JCK7

### License

Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page). License: https://creativecommons.org/licenses/by/4.0/
