SAMPLES: How to create top menus, sub menus, context menus, toolbars

The questions of how to create a toolbar, a top menu, a sub menu, an entry on a context menu, etc. appear from time to time on the forums, so I have created a bunch of samples on GitHub:

visualstudioextensibility/VSX-Samples

They use what I consider best practice: the use of the CommandPlacements section in the .vsct file for centralized place to set parent-child relationships.

The code samples are the following:

HOWTO: Create a Visual Studio toolbar
Demonstrates how to create a toolbar.

toolbar

HOWTO: Create a Visual Studio top menu
Demonstrates how to create a top menu.

topmenu

HOWTO: Create Visual Studio commands on a submenu
Demonstrates the use of the CommandPlacements section of the .vsct file to place a command inside a new group on a submenu inside a new group on the Solution context menu and on the Standard toolbar.

submenutoolbar

submenucontextmenu

HOWTO: Create a Visual Studio command on the Solution context menu
Demonstrates the use of the CommandPlacements section of the .vsct file to place a command inside a new group inside the Solution context menu.

solutioncontextmenu

HOWTO: Create a Visual Studio command on the project context menu
Demonstrates the use of the CommandPlacements section of the .vsct file to place a command inside a new group on the context menu of a Windows project, Web project, or multiple projects selected.

projectcontextmenu

HOWTO: Create a Visual Studio command on the file context menu
Demonstrates the use of the CommandPlacements section of the .vsct file to place a command inside a new group on the context menu of a file or multiple files (belonging to the same project, or to different projects).

filecontextmenu

HOWTO: Create a Visual Studio command on the code window context menu
Demonstrates the use of the CommandPlacements section of the .vsct file to place a command inside a new group on the code window context menu.

codewindowcontextmenu

SAMPLE: How to create a Visual Studio command that accepts parameters

Some weeks ago a user of my MZ-Tools add-in asked me how to automate the Review Quality feature. So far, this feature could be invoked on demand, or automatically when an output file is built using the user interface. The request was very reasonable: automated reviews when the code is built using some script, or when code is checked-in in source control (not in the context of a build). So, the requirement is to execute some feature programmatically with input parameters (feature name, log file, etc.) and generate output files (results file, log file, etc.). My last attempt at automating some feature of MZ-Tools was in 2007, when I wrote the post Frustrations with command-line add-ins for Visual Studio. So, it was time for another attempt.

If you try to execute a command with arguments, for example in the Command window, likely you will get this error:

commandwindow

It happens that there are four steps that you need to code to ensure that your command accepts parameters:

commandwithparametes

Rather than listing the steps here, I have created a sample on GitHub:

HOWTO: Create a Visual Studio command that accepts arguments

One of the steps (the magical “$” value that must be assigned to the ParametersDescription property of the OleMenuCommand) is so esoteric that it’s not properly documented anywhere, but some comments of Ryan Molden in a forum explains something about it.

A new website about Visual Studio resources

I have launched a new website to help developers using Visual Studio / Visual Studio Code / Visual Studio Team Services / Team Foundation Server:

http://www.visualstudioresources.com/

visualstudioresources

And this is a cross post that explains my motivation to do it:

Back in 1995, when I started to work on a company, Internet didn’t exist as we know it today. At that time, Microsoft supplied its Microsoft Developer Network (MSDN) Library with technical information for developers in a couple of CDs that arrived to the office quarterly:

msdnlibrary

You could read the content of your interest in a few days, and then you didn’t receive more content until the next quarter. There were some printed magazines, but they were monthly and you read them in a few days. Content and resources were scarce. Today, more than 20 years later, the problem is the opposite: there is so many content in so many ways that we have to use search engines to find what we need. Also, the way we learn today is not the way we used to learn 20 years ago, at least not for every developer. Some people still prefer to read the official documentation from the manufacturer. Other people prefer to learn with thick, comprehensive, books. Other prefer small articles and posts. Others don’t like to read so much and prefer videos or courses. Yet others prefer to learn reading code of samples or other apps.

I’ve created this site with the goal of providing a huge directory of resources of any kind to learn about the Visual Studio family: Visual Studio, Visual Studio Code, Visual Studio Team Services and Team Foundation Server. It is not a website about .NET or about programming languages (C#, VB.NET, F#, etc.), but only about the “tooling”, the development environments and the application lifecycle management (ALM) systems that Microsoft provides to developers to become more productive.

This website starts small and modest, but hopefully it will grow in the next months and years to reach the depth of the Visual Studio Extensibility (VSX) that I created years ago for developers extending Visual Studio.

I hope you like it.

Another two strange cases of MZ-Tools 8.0 for VBA could not be loaded

It seems that the path to load a .NET-based add-in in the VBA editor of Office is full of mines. In the last couple of days I have discovered two new scenarios (apart from this and this) that can cause “<Add-In> could not be loaded”. I will document them here for future reference for me or other people:

This first one happens if you are not using a COM Shim, you are using .NET Framework 2.0 and the version of Office is 2000, 2002 (XP) or 2003. It is due to a lockback policy that “prevents the .NET Framework 2.0 common language runtime (CLR) from initializing when the .NET Framework 2.0 is hosted in either the Word process space or the Excel process space. The policy restriction limits Word and Excel from loading versions of the .NET Framework that are later than version 1.1. Therefore, the .NET Framework 2.0 assemblies cannot load”. It is documented in the Knowledge Base article You cannot load a .NET Framework 2.0 assembly from Visual Basic for Applications in Word 2003 and earlier versions, or in Excel 2003 and earlier versions and fortunately has a solution through a registry key HKEY_CLASSES_ROOT\Interface\ {000C0601-0000-0000-C000-000000000046} that can be created. I was aware of this problem since long time ago and my setup created that registry key if Office 2003 or 2002 was detected. However, I noticed that my add-in was failing for Office 2000 (yes, I have users running Office 2000 on old PCs) because I added support for Office 2000 later and I didn’t update the setup to check that version. This case is difficult to diagnose because the following script works perfectly (I mean, the problem only manifests loading the assembly in Office):

Dim o
Set o = CreateObject("MZTools8VBA")

The other problem was also hard to diagnose. It happened when using a COM Shim to load the CLR using code from the COM Shim wizards provided by Microsoft, which calls the CorBindToRuntimeEx function passing NULL to the first pwszVersion parameter. A NULL value means to load the highest CLR installed below CLR 4.0, which should be CLR 2.0. However, on Windows XP I got HRESULT 0x80131700. Fortunately I found on a comments of the post COM Shim CLR Loader Bug that the problem could be solved passing the specific version “v2.0.50727”, which certainly fixed the problem for me.

The strange case of System.InvalidCastException (“Unable to cast COM object of type ‘System.__ComObject’ to class type System.Windows.Forms.UserControl”) showing toolwindow

In my last post I explained a problem that can happen when a .NET-based add-in for the VBA/VB6 editor that uses a COM Shim tries to use any Common Language Runtime (CLR) that it’s installed on the target machine (CLR 2.0 or CLR 4.0), assuming that the add-in is built with .NET Framework 2.0/3.0/3.5 and therefore can use CLR 2.0 or CLR 4.0. This post is about other problem that can happen.

The ideal approach, that was the first that I implemented, is that the COM Shim uses at run-time the first detected CLR that it supports. In my case the logic was:

  • First it detects if CLR 2.0 is installed. If so, use it.
  • If not, it detects if CLR 4.0 is installed. If so, uses it.

This would be the best approach because at any given time, it uses the available(s) CLRs. The default CLRs installed by Windows are:

  • Windows XP: Neither CLR 2.0 nor CLR 4.0
  • Windows Vista, Windows 7: CLR 2.0
  • Windows 8, Windows 8.1, Windows 10: CLR 4.0

And life would be great if that were the whole picture. However, most add-ins need to create and show toolwindows. For a .NET-based add-in, this is somewhat tricky because toolwindows need ActiveX UserDocuments, and .NET doesn’t support UserDocuments. However, a UserControl can be used instead with some tricks as I explained in HOWTO: Create a toolwindow for the VBA editor of Office from an add-in with Visual Studio .NET. The UserControl, being .NET-based, needs to be registered as a COM component, and .NET-based COM components need to specify the required runtime version of the .NET Framework in the Windows registry, in the RuntimeVersion value inside the InProcServer32 key:

HKEY_CLASSES_ROOT\progid
   (default) = progId
HKEY_CLASSES_ROOT\progid\CLSID
   (default) = clsid
HKEY_CLASSES_ROOT\CLSID\{clsid}
   (default) = progid
HKEY_CLASSES_ROOT\CLSID\{clsid}\InProcServer32
   (default) = mscoree.dll
   Class = ClassName
   ThreadingModel = Both
   Assembly = Stringized_assembly_reference
   Codebase = path_of_private_assembly
   RuntimeVersion = version_of_the_runtime
...

toolwindowcontrolregistration

This registration of the usercontrol used by the toolwindow must be done by the installer at setup-time. And I used in the installer the same logic than in the COM Shim: first it detects CLR 2.0 and, if not available, then it detects CLR 4.0.

However, a couple of my users reported this exception when clicking a button that should show a toolwindow:

System.InvalidCastException: Unable to cast COM object of type ‘System.__ComObject’ to class type ‘System.Windows.Forms.UserControl’. Instances of types that represent COM components cannot be cast to types that do not represent COM components; however they can be cast to interfaces as long as the underlying COM component supports QueryInterface calls for the IID of the interface.

Toolwindows are created calling the CreateToolWindow function, that receives in the second parameter the ProgId of the ActiveX UserDocument that the function will create internally and return as output last parameter:

Windows.CreateToolWindow (AddInInst, ProgID, Caption, GuidPosition, DocObj) As Window

And that exception happened in a line of code that casts the usercontrol instance returned by the last output parameter (which should be a UserControl instance) to the System.Windows.Forms.UserControl type. Why?

It took me a while to discover the cause (the scenarios are so messy that often I need to go walking or swimming to get clarity thinking about the problem…). But at some point I realized that there were some scenarios where the runtime version of the usercontrol and the runtime version of the COM Shim would mismatch:

The first one is that you have Windows 10, which by default provides .NET Framework 4.0 (CLR 4.0) and not .NET Framework 2.0/3.0/3.5 (CLR 2.0). The user runs the setup of the add-in. The usercontrol is registered with the only runtime available (v4.0.30319). When the add-in is loaded the COM Shim uses the only runtime available, also v4.0.30319. So far so good. However, one day the user installs .NET Framework 2.0/3.0/3.5, which is an optional feature of Windows 10:

windowsoptionalfeatures

In this case, the usercontrol remains registered with runtime version v4.0.30319 (unless the user runs the installer again) but the COM Shim, that detects CLRs at runtime, not at setup time, would use CLR 2.0! (because its logic gives it preference over v4.0.30319). And this causes the System.InvalidCastException, because the CreateToolwindow returns an instance of a CLR 4.0 usercontrol. Since such thing cannot enter the CLR 2.0 of the COM Shim, a System.__ComObject wrapper is created, that cannot be cast to a UserControl type.

The second scenario is that you have Windows 10, which by default provides .NET Framework 4.0 (CLR 4.0) but the user has installed .NET Framework 2.0/3.0/3.5 (CLR 2.0). The user runs the setup of the add-in. The usercontrol is registered with the runtime v2.0.50727 (because the logic gives it preference over v4.0.30319). When the add-in is loaded the COM Shim uses also the preferred runtime v2.0.50727 over v4.0.30319. So far so good. However, one day the user uninstalls the optional .NET Framework 2.0/3.0/3.5. In this case, the usercontrol remains registered with runtime version v2.0.50727 (unless the user runs the installer again) but the COM Shim, that detects CLRs at runtime, not at setup time, would use CLR 4.0 instead of CLR 2.0! This causes a different exception, though.

So I thought a different approach: rather than using whatever CLR is installed on the machine (with preference for CLR 2.0 over CLR 4.0), I use the default CLR that should be installed on each Windows version:

  • Windows XP: none. But the setup enforces that .NET Framework 2.0 must be installed.
  • Windows Vista, Windows 7: .Net Framework 2.0
  • Windows 8, Windows 8.1, Windows 10: .Net Framework 4.0

This approach makes the COM Shim immune to installation/uninstallation of additional .NET Framework installations (because default .NET Frameworks are OS components and cannot be uninstalled, only optional .NET Frameworks can be uninstalled).

And while this approach seemed fine, another two users reported the same System.InvalidCastException, both cases on Windows 10 using VB6.  More walking and swimming :-). The reason is that VB6 can be set to run in compatibility mode with Windows XP:

windowsxpcompatibility

What happens if a user is running Windows 7 and sets VB6 with compatibility mode with Windows XP? The setup was run on Windows 7, so the usercontrol is registered with runtime version v2.0.50727 (the default for Windows 7). When VB6 in compatibility mode loads the COM Shim, it thinks that it is running on Windows XP and uses the .NET Framework 2.0 that the setup required. So nothing bad happens.

But what happens if the user is running Windows 10 and sets VB6 with compatibility mode with Windows XP? The setup was run on Windows 10 (not in compatibility mode), so the usercontrol is registered with runtime version v4.0.30319 (the default for Windows 10). When VB6 in compatibility mode loads the COM Shim, it thinks that it is running on Windows XP and uses the .NET Framework 2.0 that the setup required. So a System.InvalidCastException happens when performing the cast operation due to the mix of runtime versions.

The best solution, that I hope that fixes this problem for good, is to make the COM Shim to read the registered runtime version of the usercontrol of the toolwindow from the Windows registry, and use the matching CLR. With this approach, it doesn’t matter what Windows version the COM Shim thinks it is running on, because it won’t use the default CLR of that Windows version, but the one that was determined at setup-time.

Update (September 12, 2016): it happens that there is a way to register a .NET component as a COM component indicating that it can work with CLR 2.0 and CLR 4.0. Instead of using the RuntimeVersion name mentioned above, you can use the SupportedRuntimeVersions name and provide a list of CLRs in order of preference:

HKEY_CLASSES_ROOT\progid
   (default) = progId
HKEY_CLASSES_ROOT\progid\CLSID
   (default) = clsid
HKEY_CLASSES_ROOT\CLSID\{clsid}
   (default) = progid
HKEY_CLASSES_ROOT\CLSID\{clsid}\InProcServer32
   (default) = mscoree.dll
   Class = ClassName
   ThreadingModel = Both
   Assembly = Stringized_assembly_reference
   Codebase = path_of_private_assembly
   SupportedRuntimeVersions = v2.0.50727;v4.0
...

See the post In-Proc SxS and Migration Quick Start. So, original approach for the COM Shim would work:

  • First it detects if CLR 2.0 is installed. If so, use it.
  • If not, it detects if CLR 4.0 is installed. If so, uses it.

The strange case of mscoree.dll and the CLRCreateInstance function

Since I started three months ago to use a COM Shim for my .NET-based MZ-Tools 8.0 for VBA/VB6 to achieve isolation and .NET CLR independence, nobody had reported a single problem. However, last week two different problems were reported. This post is about the first one. The error was that on Windows XP, MZ-Tools 8.0 for VB/VBA failed to load. This dreaded error message:

MZTools8CouldNotBeLoaded

Fortunately, the user reported that installing .NET Framework 4.0 the problem was solved, but why?

The error message is hiding the actual error code and description (Microsoft started to show that crucial piece of information when an add-in fails to load in Visual Studio .NET 2002). I was able to reproduce the problem so I didn’t need to contact the user for further information. An approach to know why an add-in is failing to load on any IDE is to use Process Monitor to capture disk and registry activity and then guess what is failing (some missing registry entry or file, etc.). Unfortunately the current version of Process Monitor doesn’t support Windows XP, and although I couldn’t find an earlier version to download, I found the Registry Monitor and File Monitor utilities (that later became unified in Process Monitor) and they worked on Windows XP.  However, I couldn’t guess the problem. I always forget that the easiest way to know the actual error is to create a VBScript with this code that creates a COM object with the ProgId of the add-in (typically “MyAddIn.Connect”):

Dim o
Set o = CreateObject("MZTools8VBA")

And executing that script shows the actual problem:

ProcedureEntryNotFound

ProcedureEntryNotFoundScript

So, the problem was that the CLRCreateInstance function (used by the COM Shim to load the Common Language Runtime, CLR) was not found in mscoree.dll. At this point I knew I needed another tool: depends.exe (Dependency Walker).

The CLRCreateInstance function was introduced in .NET Framework 4.0 to load the CLR 4.0. The CLR 2.0 used by .NET Frameworks 2.0, 3.0 and 3.5 is loaded with a different function. My COM Shim has this logic:

  • First it detects if .NET Framework 2.0 is installed. If so, it uses the old API to load CLR 2.0.
  • If not, it detects if .NET Framework 4.0 is installed. If so, it uses the new CLRCreateInstance API to load CLR 4.0.

Note: this logic that uses run-time detection of the CLR rather than setup-time detection is flawed by a different reason that caused the second reported problem (for another post).

Since my setup requires .NET Framework 2.0 on Windows XP, I knew that the CLRCreateInstance function was not called on Windows XP. However, since my COM Shim dll linked it at compile-time:

COMShimOld

the whole dll of the COM Shim failed to load if that function is missing in mscoree.dll (it seems that my knowledge of C/C++ linking is very limited, I thought I would get a runtime error only if the COM Shim tried to call that missing function…).

This explained why installing .NET Framework 4.0 solved the problem: even if the COM Shim still used CLR 2.0 (by the logic above), the mscoree.dll updated by .NET Framework 4.0 provided the missing CLRCreateInstance function and the COM Shim dll didn’t fail to load.

But a question remained that got me puzzled for several days: nobody had reported this problem on Windows 7, that lacks .NET Framework 4.0 (it installs only .NET Framework 3.5 / CLR 2.0), in these three months. And since my setup doesn’t require .NET Framework 4.0 on Windows 7, I was sure that not all users have .NET Framework 4.0 installed. I should have received tons of bug reports with the same problem. But not a single one… The first thing I did was to check the version of mscoree.dll of my virtual machines with Windows 7. First surprise: they showed 4.0.xxxx, although .NET Framework 4.0 was not installed:

mscoreeWindows7WithSP1

Then I used depends.exe to check that the mscoree.dll of Windows 7 certainly exports the CLRCreateInstance function:

mscoreeExportedFunctionsWindows7WithSP1

So, how is that Windows 7, lacking .NET Framework 4.0, has a mscoree.dll version 4.0.xxxx that provides the CLRCreateInstance method to load the CLR 4.0?

At this point I decided to start from scratch: I created a virtual machine with Windows 7 RTM, and suprise, surprise, mscoree.dll was version 2.0.xxxx:

mscoreeWindows7WithoutSP1

and certainly lacked the CLRCreateInstance method:

mscoreeExportedFunctionsWindows7WithoutSP1

And of course, installing VB6 or Office and MZ-Tools 8.0 reproduced the problem that happened on Windows XP.

It was only after I applied SP1 to Windows 7, that mscoree.dll was updated to version 4.0.xxxx. So, it seems that Microsoft decided to include the mscoree.dll version 4.0.xxxx of .NET Framework 4.0 in the service pack 1 of Windows 7, even if that service pack doesn’t include .NET Framework 4.0.

Of course the best solution is to use dynamic linking at run-time using GetProcAddress so that the COM Shim can be loaded even if the CLRCreateInstance function is missing:

COMShimNew

For the record, Windows 10 uses version 10.0.xxxx of mscoree.dll:

mscoreeWindows10

Case closed.

HOWTO: Knowing the folder where Visual Studio is loading its own dlls from

If you are extending the Team Explorer toolwindow of Visual Studio, you will know it is particularly tricky, because it is not well documented, and you require some interfaces that are defined in dlls used by the Team Explorer. For example, I wrote a series of articles last year such as:

In all of them I had to specify the Visual Studio assemblies and location, such as:

  • Microsoft.TeamFoundation.Controls.dll (in folder C:\Program Files (x86)\Microsoft Visual Studio <version>\Common7\IDE\ReferenceAssemblies\v4.5\)
  • Microsoft.TeamFoundation.Git.Controls.dll (in folder C:\Program Files (x86)\Microsoft Visual Studio <version>\Common7\IDE\ReferenceAssemblies\v4.5\)

It happens that the name of the assembly and/or the folder can change from one version of Visual Studio to the next (and maybe from one update to another in the same Visual Studio version). Even the interfaces can change, but that’s another story.

To know the assemblies and folders that a Visual Studio process (devenv.exe) is using, you can use the invaluable Process Explorer tool. Here you can see it running two instances of devenv.exe (one for Visual Studio 2013 and another for VS 2015):

ProcessExplorer

You can configure it to show the dlls loaded by the selected process, clicking the View > Show Lower Pane and the View > Lower Pane View > DLLs:

ProcessExplorerViewDlls

With this tool you can find that Visual Studio 2013 loads Microsoft.VisualStudio.TeamFoundation.WorkItemTracking.dll from the GAC (C:\Windows\Microsoft.NET\assembly\GAC_MSIL\Microsoft.VisualStudio.TeamFoundation.WorkItemTracking\v4.0_12.0.0.0__b03f5f7f11d50a3a\Microsoft.VisualStudio.TeamFoundation.WorkItemTracking.dll):

WorkItemTrackingDllVS2013

While Visual Studio 2015 loads it (surprise, surprise) from the folder that contains its Team Explorer extension (C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\Extensions\<randomname>\Microsoft.VisualStudio.TeamFoundation.WorkItemTracking.dll):

WorkItemTrackingDllVS2015

Finally, Process Explorer provides also a Search dialog to find all the processes that have loaded a dll with some name:

ProcessExplorerSearch

New Visual Studio “15” Preview 3 Feature: Roaming Extension Manager

Just in case you missed it, the Preview 3 of Visual Studio “15” introduces the Roaming Extension Manager, that allows you “to keep track of all your favorite extensions across all of your development environments. Roaming your extensions keeps track of the extensions you have installed by creating a synchronized list in the cloud. “:

RoamingExtensionManager

You have some details and explanation of behavior/icons in the Release Notes.

dotnetConf 2016 Session: The Power of Roslyn: Improving Your Productivity with Live Code Analyzers

Dustin Campbell gave yet another session about the .NET Compiler Platform (“Roslyn”), this time at the dotnetConf 2016 and about live code analyzers:

“Two years ago we open sourced the C# and Visual Basic compilers and exposed their functionality through APIs as the Roslyn project. The Roslyn analyzer API empowers the developer community to be more productive by lighting up their own features in the editor in real-time—meaning anyone can write a refactoring, code fix, code style rule, or code diagnostic. Come learn more about the Roslyn project and what these live analyzers mean for you and your productivity.”

I have added this  session to the Videos section of this website, but here you have the link:

Component Diagnostics extension for VSX developers

There are tons of extensions on the Visual Studio Gallery, but most of them are for general developers using Visual Studio, or for some specific development technology (Web, etc.). However, there are some extensions that are specific to Visual Studio Extensibility (VSX) developers. The most known is the Extensibility Tools extension by Mads Kristensen, but there are also little gems from other Microsoft members.

I found a reference to this one by Paul Harrington that I didn’t know in a thread of the MSDN VSX forum:

Component Diagnostics
“Provides real-time diagnostics output of various sub-systems within Visual Studio. Access the toolwindow from the Help menu.”

ComponentDiagnostics

It supports VS 2012, 2013 and 2015. Despite the name, it’s an extension to diagnose Visual Studio subsystems such as:

  • Document Tab Well
  • File Change Service
  • Main Frame Data Model
  • Navigation History
  • OLE Component Manager
  • Package Manager
  • Running Document Table
  • Scrollbar Theming
  • Selection and UIContext
  • Solution
  • Task Scheduler Service

If your extension interacts with one of those subsystems, you can find it very useful. Furthermore, it is extensible so your own extension can provide diagnostics on that tool using the IVsDiagnosticsProvider interface.

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