MZ-Tools Articles Series: PRB: COMException 0x80020003 getting events from commandbar popup in Visual Studio 2010

In the old days of VB5/VB6 extensibility which used the Office commandbars model where there weren’t commands (only CommandBarControls) you had to create buttons (CommandBarButton) directly on commandbars, and you had to use the VBE.Events.CommandBarEvents(CommandBarButton) artifact to get the Click event of a CommandBarButton (you couldn’t even get the Click event using the more natural WithEvents clause).

Somehow this strange artifact got in the extensibility of the new Visual Studio .NET 2002 IDE (likely because it still used the Office commandbar model). However, in Visual Studio you have commands (EnvDTE.Command) and the recommended way is to create CommandBarButtons from commands (Command.AddControl), not directly on commandbars without a command (CommandBar.Controls.Add), and rather than getting a Click event when a CommandBarButton is clicked, you get a method (IDTCommandTarget.Exec) called when the command is executed. So, DTE.Events.CommandBarEvents is no longer necessary. There is still an scenario where you may want to create a CommandBarButton without an underlying command, for example the context menu of a listview of a toolwindow of an add-in, where you want the look and feel of the Visual Studio menus so you use a CommandBarPopup, but you don’t want commands. However, in this scenario you can use the Click event of the CommandBarButton class, without using DTE.Events.CommandBarEvents.

But people were still using DTE.Events.CommandBarEvents for another case: to know when a CommandBarControl which is a CommandBarPopup is clicked just before showing the children CommandBarButtons, presumably to set its state (enabled, disabled, invisible, etc.). Well, it happens that the new WPF-based commandbars of Visual Studio 2010 break that case, and this has caused some inconvenience to some people.This new article documents this problem:

PRB: COMException 0x80020003 getting events from commandbar popup in Visual Studio 2010
http://www.mztools.com/articles/2011/MZ2011003.aspx

Fortunately the remedy is easy: use the IDTCommandTarget.QueryStatus method:

HOWTO: Controlling the state of command in a Visual Studio add-in
http://www.mztools.com/articles/2007/MZ2007025.aspx

The strange case of VisualStudio.DTE.9.0 registry key missing for Visual Studio 2008 Professional

The other day I received a bug report from a customer of my MZ-Tools 6.0 add-in because the setup was not detecting any installation of Visual Studio while he claimed that he had Visual Studio 2008 Professional. The setup of that version of MZ-Tools detects the installation of Visual Studio versions checking the existence of the registry entries HKEY_CLASSES_ROOT\VisualStudio.DTE.<version>, where <version> can be 8.0 for VS 2005, 9.0 for VS 2008, etc. It does so because those entries are not created for Express editions of Visual Studio, which in turn don’t support add-ins.

So, I asked the customer to check the existence of HKEY_CLASSES_ROOT\VisualStudio.DTE.9.0 and he informed me that the registry key didn’t exist, while it should because Visual Studio 2008 Professional creates it. In the next e-mail he mentioned the “educational” version of Visual Studio 2008 Professional. I was not aware of this edition, but I found that there is a Microsoft Education Product Center that offers licenses for “academic” institutions. I don’t know for sure if the “Professional” edition of Visual Studio is different for academic institutions and for companies, but there is another registry key that can be used to detect if Visual Studio is installed:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<version>

My customer confirmed me that that registry key did exist. My only doubt was if Express editions create those too so I searched the web and I found my own article (I always love when this happen) HOWTO: Detect installed Visual Studio editions, packages or service packs that states that Express editions don’t create:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<version>

but:

  • For VB Express: HKEY_LOCAL_MACHINE\Software\Microsoft\VBExpress
  • For VC Express: HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress
  • For C# Express: HKEY_LOCAL_MACHINE\Software\Microsoft\VCSExpress
  • For J# Express: HKEY_LOCAL_MACHINE\Software\Microsoft\VJSExpress
  • For Web Developer Express: HKEY_LOCAL_MACHINE\Software\Microsoft\VWDExpress

So, future versions of my setup will use HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\<version>.

The strange case of InvalidCastException in Microsoft.VisualStudio.PlatformUI.Automation.CommandBarCustomizer.Remove of VS 2010

At the time of this writing, this is still an unsolved case, but I am posting it anyway with the hope that some day someone can solve it.

Since I included support for Visual Studio 2010 in my add-in MZ-Tools 6.0, I have received bug reports from three customers with this exception:

System.InvalidCastException: Specified cast is not valid.
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)
at System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(Int32 errorCode)
at Microsoft.VisualStudio.PlatformUI.Automation.CommandBarCustomizer.Remove(ControlCustomizer control)
at Microsoft.VisualStudio.PlatformUI.Automation.CommandBarControl.Delete(Object Temporary)
at Microsoft.VisualStudio.PlatformUI.Automation.CommandBarControl._Marshaler.<>c__DisplayClass10.<Delete>b__f()
at Microsoft.VisualStudio.Shell.ThreadHelper.Invoke(Action action)
at Microsoft.VisualStudio.PlatformUI.Automation.CommandBarControl._Marshaler.Delete(Object Temporary)
at EnvDTE.Command.Delete()

The exception happens when I delete an EnvDTE.Command of the add-in, which in turn deletes the CommandBarButtons created from it, a technique that I described in the article:

HOWTO: Prevent dead CommandBarButtons when Visual Studio or an add-in crashes.
http://www.mztools.com/articles/2009/MZ2009002.aspx

The problem is not reproducible and it has only happened to three customers, always in Visual Studio 2010, not in Visual Studio 2005 or 2008, so it is related to the new WPF-based commandbars of Visual Studio 2010. So, I used Reflector for .NET (freeware, soon to be paid version) to see what could cause the InvalidCastException in the Microsoft.VisualStudio.PlatformUI.Automation.CommandBarCustomizer.Remove(ControlCustomizer control) method. The Microsoft.VisualStudio.PlatformUI.Automation.CommandBarCustomizer class resides in the Microsoft.VisualStudio.Shell.UI.Internal.dll assembly of the folder C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE and the Remove method is like this:

public void Remove(ControlCustomizer control)
{
   ErrorHandler.ThrowOnFailure(base.Wrapped.Remove(control.Wrapped));
}

The ControlCustomizer class is like this:

public class ControlCustomizer : InterfaceWrapper<IVsControlCustomizerPrivate>, IDisposable

And the InterfaceWrapper<T> interface is like this:

public abstract class InterfaceWrapper<T>
{
   // Fields
   private readonly T _wrapped;

   // Methods
   protected InterfaceWrapper(T wrapped);

   // Properties
   public T Wrapped { get; }
}

So, on the one hand, control.Wrapped returns something of the IVsControlCustomizerPrivate type.

On the other hand, the base of the CommandBarCustomizer is of InterfaceWrapper<IVsCommandBarCustomizerPrivate> type, the Wrapped property returns something of IVsCommandBarCustomizerPrivate type and its Remove method is like this:

IVsCommandBarCustomizerPrivate[PreserveSig]
int Remove([In, MarshalAs(UnmanagedType.Interface)] IVsControlCustomizerPrivate pControl);

which expects a parameter of the IVsControlCustomizerPrivate type, the same type that we are passing!. So, how is that an InvalidCastException can happen? The only scenario where I have seen that two types with the same names (name, full name, assembly names) can’t be cast is because they belong to an assembly that is loaded twice from different locations. I am not sure if somehow this is happening here.

The strange case of VSLangProj80.ProjectProperties3.AbsoluteProjectDirectory

When you have a System.Type that is a component, the best way to get its public properties is to use System.ComponentModel.TypeDescriptor.GetProperties(type), rather than System.Type.GetProperties(). This is so because a component type can have a designer which is able to add new properties to the type, and to remove or change existing properties. For example, controls have a Locked property that is added by the designer of controls (ControlDesigner class), it is not a property that belongs to the System.Windows.Forms.Control type.

If the type is not a component, I guess that System.ComponentModel.TypeDescriptor.GetProperties returns the same public properties than System.Type.GetProperties. System.ComponentModel.TypeDescriptor.GetProperties returns a collection of PropertyDescriptor, which has an IsBrowsable property that tells you if a property is browsable or not.

So, the other day I got an strange issue: I was calling System.ComponentModel.TypeDescriptor.GetProperties on the VSLangProj80.ProjectProperties3 type (which is not a component type but my code was called other times with types that are components), to get the properties that are likely to exist in the EnvDTE.Project.Properties collection. And I got in the results the “AbsoluteProjectDirectory” property, that I noticed that didn’t appear in the Object Browser of Visual Studio, despite its IsBrowsable property returning True. How come?

I was aware that VSLangProj80 stuff is not pure .NET but an imported typelib or something like that, but it was not until I used .NET Reflector that I discovered that the get accessor method of the AbsoluteProjectDirectory property has the System.Runtime.InteropServices.TypeLibFuncFlags attribute with the &H40 value:

<DispId(&H2732)> _
ReadOnly Property AbsoluteProjectDirectory As <MarshalAs(UnmanagedType.BStr)> String
   <MethodImpl(MethodImplOptions.InternalCall, MethodCodeType:=MethodCodeType.Runtime), DispId(&H2732), TypeLibFunc(CShort(&H40))> _
   Get
      ...
   End Get
End Property

which matches the FHidden value:

<Serializable, Flags, ComVisible(True)> _
Public Enum TypeLibFuncFlags
   ' Fields
   FBindable = 4
   FDefaultBind = &H20
   FDefaultCollelem = &H100
   FDisplayBind = &H10
   FHidden = &H40
   FImmediateBind = &H1000
   FNonBrowsable = &H400
   FReplaceable = &H800
   FRequestEdit = 8
   FRestricted = 1
   FSource = 2
   FUiDefault = &H200
   FUsesGetLastError = &H80
End Enum

That explained the issue and I was able to tweak my code to deal with that case. BTW, notice that there is also a TypeLibFuncFlags.FNonBrowsable flag.

The strange case of the registry key HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config\Projects\{C8D11400-126E-41CD-887F-60BD40844F9E}

Yesterday I returned from some days of vacations at Israel (Jerusalem and Tel-Aviv) and since the flight was being incredibly annoying and boring (after some hours in the airport and then a long delay before taking off) I decided to grab my laptop to work on a feature of the next version of my MZ-Tools add-in. This feature needs to know the guids of the project types supported by the Visual Studio IDE where MZ-Tools is loaded. I already wrote articles in the MZTools Articles Series about:

HOWTO: Get the project flavor (subtype) of a Visual Studio project from an add-in
http://www.mztools.com/articles/2007/MZ2007016.aspx

and

INFO: List of known project type Guids
http://www.mztools.com/Articles/2008/MZ2008017.aspx

Visual Studio 2005 and 2008 used to store project type Guids in the registry key HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\<version>\Projects and I thought that the same applied to Visual Studio 2010. However, I noticed two strange issues:

1) The project type guid {C8D11400-126E-41CD-887F-60BD40844F9E} (that belongs to Database projects of Visual Studio 2010) was not in the HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0\Projects registry key (or HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0\Projects if you are using a 64-bit Windows OS). Searching the registry I found that it was stored only in the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config\Projects registry key.

2) Somehow, this code of my add-in was returning that project type guid even when it is not in the registry key (LocalMachine) that the code is querying:

Dim registryKey As RegistryKey

registryKey  = Microsoft.Win32.Registry.LocalMachine.OpenSubKey("Software\Microsoft\VisualStudio\10.0\Projects", False)

registryKey.GetSubKeyNames()

...

registryKey.Close()

Needless to say, without Internet access and Google I depleted the battery of the laptop without finding the answers.

Today I found two posts of Aaron Marten about the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config registry key introduced by Visual Studio 2010. So, that registry key is built merging the HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0 registry key with Pkgdef files on disk. Since the {C8D11400-126E-41CD-887F-60BD40844F9E} value doesn’t appear in the HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0\Projects registry key, it could only come from a Pkgdef file, and certainly I found it: C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\Extensions\Data\vspackage.pkgdef, which contains this fragment:

[$RootKey\$Projects\{C8D11400-126E-41CD-887F-60BD40844F9E}]
@=”Database”
“DisplayName”=”Database”
“DisplayProjectFileExtensions”=”#210”
“Package”=”{068E2583-0872-403B-AF4C-6C2A8F2D8C3E}”
“DefaultProjectExtension”=”dbproj”
“PossibleProjectExtensions”=”dbproj;dbp”
“ProjectTemplatesDir”=”$RootFolder$VSTSDB\ProjectTemplates\Database”
“Language(VsTemplate)”=”Database”

So, that explained issue #1, but what about the #2? How is that querying the subkeys of HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0\Projects returns the value {C8D11400-126E-41CD-887F-60BD40844F9E} that is only present in the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config\Projects registry key? Notice that this was a good thing for my purposes, but I didn’t have an explanation. So I used the Process Monitor utility to trace registry activity and I found that the code above was actually querying the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config\Projects registry key!!! That could only mean that somehow Visual Studio performs a registry redirection (not mentioned in the posts of Aaron), so that any query from the devenv.exe process (or its extensions!) to HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0 is redirected to HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\10.0_Config. To confirm this, I executed that code from a Windows Forms application and not from a Visual Studio add-in, and certainly the {C8D11400-126E-41CD-887F-60BD40844F9E} value is not returned, because in this case no registry redirection is performed and the true HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\10.0\Projects registry key is queried.

MZ-Tools Articles Series: PRB: NotImplementedException adding a solution folder to a solution folder in Visual Studio from a macro or add-in

This new small article of the MZ-Tools Articles Series documents a problem that happens creating a solution folder (VS 2005 or higher) belonging to an existing solution folder, that was reported in the MSDN VSX Forum and it was reported on the web long time ago too:

PRB: NotImplementedException adding a solution folder to a solution folder in Visual Studio from a macro or add-in
http://www.mztools.com/articles/2011/MZ2011002.aspx

I wouldn’t say it is a bug because a NotImplementedException is normally “by design” but anyway I have reported it to Microsoft Connect and maybe it is implemented or fixed in next Visual Studio versions:

Microsoft Connect: NotImplementedException adding solution folder to solution folder
https://connect.microsoft.com/VisualStudio/feedback/details/635048/notimplementedexception-adding-solution-folder-to-solution-folder

A possible workaround would be to create the child solution folder belonging to the solution too, and then drag and drop it over the first solution folder, but drag and drop operations can’t be done using the automation model (EnvDTE).

Update (Dec 3, 2013): see this new post with a full workaround

MZ-Tools Articles Series: HOWTO: Create a solution from a Visual Studio add-in.

I have always found creating solutions using the automation model (EnvDTE) somewhat tricky, because there are two phases: creating the solution and saving the solution to disk, and the methods used to do that are confusing:

– It happens that you have a Solution.Create method, which one would expect to receive only the name of the solution in the Solution Explorer (which is also the base name for the .sln file), but it receives the folder of the solution too, although this method doesn’t save the solution file (.sln) on disk (so, why does it need the folder of the solution?).

– You have also the Solution.SaveAs method, which receives as parameter the name of the .sln file with path, two (combined) pieces of information that you already specified in the Solution.Create method. One would expect a Solution.Save method without parameters. It happens that you have the Solution.Saved property, which is read/write, but I am not sure if you can save the solution setting its value to true.

– Finally, the folder of the solution must exist. Visual Studio doesn’t bother to create it (why?). At least the example of the documentation about Solution2.Create and Solution3.Create warns you, but there is no example if you fall in the SolutionClass.Create or _Solution.Create help pages.

– The confusion is such that the MSDN documentation about the Solution3.Create method lists two “overloaded” methods with the same name and signatures.

I have written the following article which shows also how to get the Visual Studio projects folder, which is the location that you likely want to use to save the solution:

HOWTO: Create a solution from a Visual Studio add-in.
http://www.mztools.com/articles/2011/MZ2011001.aspx

6 months without blogging

I haven’t written a new post in this blog since 6 months and 2 days ago. If you are subscribed through RSS you may have notice it 🙂 The reason is that the IT department of the company where I worked for “suffered” an outsourcing at May 15th, which included me as part of the “pack”, and during these months my life has been under a lot of pressure during day hours and even on nights and weekends, specially the first months. It seems that management is not the same in a final client than in a IT provider ;-). Since I wanted to keep my commitment to MZ-Tools (there will be a new major version 7.0 next year) and of course to my family, the two resulting casualties have been this blog and the Microsoft Most Valuable Professional (MVP) tittle that won’t be awarded to me next January like the past 7 years (I haven’t written new articles of the MZ-Tools Series and I haven’t answered questions in the Visual Studio Extensibility MSDN Forum either). Nonetheless I will try to write new posts from time to time since there are still areas of add-in development that I haven’t covered, such as unit testing add-ins within Visual Studio (that I have invested a lot of time lately).

Reduced user interface customizations in Visual Studio 2010

I was thinking about blogging about the different (reduced) user interface customizations in the new WPF-based toolbars of Visual Studio 2010 (the most noticeable, the totally modal Customize dialog) but I have learned today that someone else was more exhaustive reporting them to Microsoft Connect back in the Beta 2 timeframe:

Limited ability to customize VS 2010 Beta 2 IDE
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=511327

VS SDK, packages, add-ins, macros and more…