The convoluted build configuration automation model (EnvDTE/EnvDTE80)

One of the most complex things of the automation model (EnvDTE/EnvDTE80) of Visual Studio is the set of classes/properties to manage the build configurations of solutions and projects, because of the complexity itself of the configuration model of Visual Studio with matrixes of configuration names/platform names per solution and per project (nothing to object here, though), but also because of some unfortunate naming, inconsistencies and lacks in the automation model. So, let’s try to understand it (this post will also help me to remember all this in the future):

From the purely visual point of view, Visual Studio offers something named “Configuration Manager” under the “Build” menu. Notice that the “Configuration Manager” is something that belongs to the solution, not to a project (more on this later), and therefore the context menu of a solution in the Solution Explorer offers the “Configuration Manager…” menu while the context menu of a project doesn’t. You can think the (solution) configuration manager as a 2-D matrix of items: one dimension is the configuration name (such as “Debug”, “Release”) and the other dimension is the platform name (such as “Any CPU”, “Itanium”, etc.). Both dimensions are expandable with new values. Now, each item (configuration name, platform name) in the matrix is in turn a collection, where each collection item holds a project name, project configuration name, project platform name and whether to build it or not. Notice that you have solution configuration names and project configuration names (which can be the same or not) and solution platforms and project platforms (which must be the same).

The Configuration Manager of Visual Studio offers a filtered view of this 2-D matrix of collections, where:

  • The two combo boxes at the top, Active Solution Configuration and Active Solution Platform, filter the collection in the 2-D matrix that you see below.
  • The collection is shown in turn in a table where each row is an item of the collection (one row per project in the solution).

The Configuration Manager violates IMHO a principle of user interface design, mixing items (configuration names or platform names) with actions (“New…”, “Edit…”, “Delete…”) in the same combo box. It would be more intuitive to put three separate buttons to the right of the combo box for the actions such as “New…”, “Rename…” (which renames the selected item in the combobox) or “Delete” (which deletes the selected item in the combobox). Notice that in the current implementation, to delete an item you have to “edit” first… weird. Other than that, the Configuration Manager is not so complex. Maybe an alternative design to view the whole matrix at the same time with a tree-listview with a 3-level hierarchy (configuration name   platform name table of projects) would be more complex.

Now, the automation model (EnvDTE/EnvDTE80) to manage all this programmatically:

  • The first thing that you notice is that there is an EnvDTE.ConfigurationManager class, but there is no way to reach it from the EnvDTE.Solution.SolutionBuild object. Strange, isn’t it?
  • Instead, the SolutionBuild class offers a collection of EnvDTE.SolutionConfigurations. Notice that the collection is linear, not a 2-D matrix or array as I modeled it, so you have to make a mental switch in your brain when dealing with this. Say that you want all the solution configurations with the configuration name “Debug” and any platform: you have to iterate the whole collection checking the EnvDTE.SolutionConfiguration.Name property… Say that you want all the solution configurations with the platform name “Itanium” and any configuration name: you have to iterate the whole collection checking the EnvDTE80.SolutionConfiguration2.PlatformName property (next paragraph). Say that you want all the solution configuration names or solution platform names: you have to iterate the whole collection collecting non-duplicated property values!
  • Each EnvDTE.SolutionConfiguration has a Name property but in the EnvDTE automation model of VS.NET 2002/2003 lacks a PlatformName property. Microsoft added it in the EnvDTE80 automation model of VS 2005, where the EnvDTE80.SolutionConfiguration2 class has the PlatformName property. The weird thing about VS.NET 2002/2003 is that it offered platforms for projects (but not for solutions!), although “.NET” was the only possible value for a project platform. The automation team only followed suit providing the EnvDTE.Configuration.PlatformName (which refers to project configuration as we’ll see in a moment) in the automation model of VS.NET 2002.
  • Did I mention EnvDTE.Configuration in the last paragraph? We have solution configurations and project configurations. Which one does EnvDTE.Configuration refer to? Since we have an EnvDTE.SolutionConfiguration, EnvDTE.Configuration can only refer to a project configuration. It would have been much clearer to name it EnvDTE.ProjectConfiguration. Naming it EnvDTE.Configuration forces you to another mental switch in your brain. It happens that it can refers also to a file (ProjectItem) configuration given its Type property with values vsConfigurationTypeProject, vsConfigurationTypeProjectItem, but I don’t care, enter the object-oriented programming world, use an EnvDTE.ConfigurationBase type and inherit two EnvDTE.ProjectConfiguration, EnvDTE.ProjectItemConfiguration classes.
  • Back to the EnvDTE.SolutionConfiguration, which is each item in the 2-D matrix of solution configurations/solution platforms, which in turn is a collection, do you remember?. The actual collection is provided through the SolutionConfiguration.SolutionContexts property, where each EnvDTE.SolutionContext is a row in the visual Configuration Manager, with the project name, project configuration name, project platform name and whether to build/deploy or not. Notice that the visual Configuration Manager says “Project contexts (check the project configurations to build or deploy” while the automation model says “EnvDTE.SolutionContexts”. Another mental switch for your brain…
  • Say that you want to add a new solution configuration name (another item in one of the dimensions of the matrix). The automation model offers the EnvDTE.SolutionConfigurations.Add(newName, existingName, propagate). OK. Say that you want to add a new solution platform name (another item in the other dimension of the matrix). As we said, the EnvDTE automation model has no notion of solution platform names, only project platform names. No problem, go to the EnvDTE80 automation model of VS 2005 which must have an EnvDTE80.SolutionConfigurations2.AddPlatform(…). Oops! There is no such EnvDTE80.SolutionConfigurations2 class, not to mention the AddPlatform method… It seems that the automation model of VS 2005 doesn’t provide a way to add new solution platforms programmatically, at least directly. You may have noticed that each time that you create a new solution configuration name or project configuration name through the visual Configuration Manager the dialog that is shown offers a checkbox to propagate the creation of the configuration name to the project(s) / solution respectively, and the same happen for the platform creation. So, you can create a project platform programmatically (shown later) and propagate the creation of a matching solution platform.
  • Back to the intriguing EnvDTE.ConfigurationManager. If it is not used for solution build configurations (EnvDTE.SolutionConfigurations is used instead), what is for? It happens that you get an EnvDTE.ConfigurationManager from EnvDTE.Project.ConfigurationManager. So, it something that belongs to a project, not to a solution. Just the contrary of the visual interface. It should have been named EnvDTE.ProjectConfigurationManager. Another mental switch for your brain…
  • The EnvDTE.ConfigurationManager manages project build configurations (with project configuration names/project platform names) in a 2-D matrix approach. Instead of using only a linear collection of EnvDTE.Configurations (like the EnvDTE.SolutionBuild.SolutionConfigurations), you can use the Item(configuration, platform) property to get an EnvDTE.Configuration in the matrix. You can get a collection of all the EnvDTE.Configurations for a given configuration (with any platform) with the ConfigurationRow(configuration) property. You can get a collection of all the EnvDTE.Configurations for a given platform (with any configuration) with the Platform(platform) property (not “PlatformColumn” as you would expect, but “Platform”…) and whose help summary is “Returns a collection of ConfigurationAssignment object for the specified platform.”, sorry, ConfigurationAssignment???… You can get all the values of the configurations dimension using the ConfigurationRowNames, which returns an Object instead of an array or collection of strings as the “Names” part of the property name should imply… You can get all the possible values for the platforms dimension using the PlatformNames property, which returns also an Object and not an array of string directly, and that should have been named AvailablePlatformNames, to distinguish it from the SupportedPlatforms property that returns an array with the actually used platforms (correction Aug,30: SupportedPlatforms are the platforms supported by the .NET Framework while PlatformNames are the ones actually created for the project, what did I say about confusing names?), and that  should have been named SupportedPlatformNames but who cares at this point since your brain should be used now to deal with a 2-D matrix of “configuration rows” and “platforms” and not “rows” and “cols” or “configurations” and “platforms”… OK, you don’t like the 2-D approach to manage project build configurations, you miss the linear collection approach of SolutionBuild.SolutionConfigurations (in fact I would prefer the opposite, to manage in a 2D-matrix approach the solution configurations) but no problem, you can! You have the visible ConfigurationManager.Count property that according to the Object Browser help returns “value indicating the count of objects of the collection”, excuse me, since when the ConfigurationManager is a collection? and if you would expect a ConfigurationManager.Configurations property, bad luck, you have the less intuitive and hidden ConfigurationManager.GetEnumerator method, oh my!… The EnvDTE.ConfigurationManager allows you to add values to the dimensions (project configurations or project platforms) using the AddConfigurationRow and AddPlatform methods or delete them using the DeleteConfigurationRow and DeletePlatform methods. The ConfigurationManager class offers also an ActiveConfiguration property which returns an EnvDTE.Configuration. Which is this? The SolutionBuild class has an ActiveConfiguration property which refers to the item filtered by the two comboboxes at the top of the visual configuration manager of Visual Studio. The Project. ConfigurationManager.ActiveConfiguration can only refer to the project configuration for that active solution configuration whose configuration name and platform name matches the values in the row belonging to the project in the filtered view of the visual configuration manager (which in the automation model is a EnvDTE.SolutionContext, not an EnvDTE.Configuration).
  • There are other poorly named properties such as SolutionBuild.LastBuildInfo, which returns an integer with the projects built successfully, or it was the failed ones? You have always to check the help file…
  • One last thing: I mentioned that the EnvDTE.SolutionContext class contains properties such as ShouldBuild, ShouldDeploy that matches the checkboxes in the visual Configuration Manager of Visual Studio. The EnvDTE.Configuration (project configuration) has IsBuildable, IsDeployable properties that I am not sure about their purposes, but don’t confuse them.

I will leave as an exercise for the reader to create a VS 2008 solution with:

  • Four solution configuration names (say “Debug1”, “Debug2”, “Release1”, “Release2”).
  • Two solution platforms (say “Any CPU”, “Itanium”).
  • Two projects (say “Project1” and “Project2”).
  • “Project1” with four project configurations (“Debug1”, “Debug2”, “Release1”, “Release2”) and two project platforms (“Any CPU”, “Itanium”).
  • “Project2” with two project configurations (say “Debug” mapped to solution configurations “Debug1” and “Debug2”, and say “Release” mapped to solution configurations “Release 1” and “Release2”) and two project platforms (“Any CPU”, “Itanium”).

And then create some macros to get all that information programmatically. How is your brain by now? 😉

Question: DTE.Version = “7.00” for VS.NET 2002, “7.10” for VS.NET 2003,… which are the next values in the series for VS 2005 and VS 2008?

You would say that they should be “8.00” for VS 2005 and “9.00” for VS 2008, but somehow Microsoft changed the pattern and the actual results are “8.0” for VS 2005 and “9.0” for VS 2008. I found a bug today in the code for the next version of my MZ-Tools add-in because of this change… An unnecessary change, I would say.

So, for VS.NET 2002, VS.NET 2003, VS 2005 and VS 2008 we have respectively:

  • For DTE.Version: “7.00”, “7.10”, “8.0”, “9.0”
  • For DTE ProgIDs: “VisualStudio.DTE.7”, “VisualStudio.DTE.7.1”, “VisualStudio.DTE.8.0”, “VisualStudio.DTE.9.0”

Did you notice the inconsistencies in the trailing digits at some point in the sequences?

And if you thought that at least the significant digits make sense, drum roll please:

  • For the solution format inside the .sln file: “7.00” for VS.NET 2002, “8.00” for VS.NET 2003 (!), “9.00” for VS 2005 (!!) and “10.00” for VS 2008 (!!!)…

Visual Studio 2008 SP1 new icon showing versión “9”

Apart from fixing bugs and providing new features, VS 2008 SP1 includes a tiny welcome detail: it shows the Visual Studio version (“9”) in the icon of the shortcuts and in the icon of the main window caption. For those of us developing extensions for Visual Studio, we have to have several Visual Studio versions installed side by side: I have four from VS.NET 2002 to VS 2008 because my MZ-Tools add-in still targets the four versions. While I have the shortcuts sorted in the Windows Quick Launch toolbar so there is no problem there, when I had several of them open I had no way to distinguish them easily and I wish Microsoft uses “Microsoft Visual Studio 200?” in the main window caption rather than “Microsoft Visual Studio”. While SP1 keeps the same caption, the icon now allows me to distinguish the Visual Studio 2008 version.

MZ-Tools Articles Series: HOWTO: Add an error with navigation to the Error List from a Visual Studio add-in

It is conspicuous that the EnvDTE80.ErrorItems collection introduced by VS 2005 lacks an Add method to add errors to the ErrorList, so it was a common belief, apparently even within Microsoft and also mine until recently that only SDK packages can add errors to the ErrorList. There is good news: there is not only one approach, but two!

The first one was explained by Dave Sexton in this forum post and it uses the OutputWindowPane.OutputTaskItemString method but has some limitations.

I was investigating another approach and it was not until today that I became aware of the difference between GetService (that didn’t work in my sample) and GetGlobalService 🙂

Of course there is always in the VS automation world something else that doesn’t work as expected such as a +1 line number offset that others have posted in the MSDN forum more than once, but I managed to work around it.

So, here is the second working approach:

HOWTO: Add an error with navigation to the Error List from a Visual Studio add-in
http://www.mztools.com/articles/2008/MZ2008022.aspx

I always get the feeling that the automation model (EnvDTE) should be much more powerful and intuitive (no ErrorItems.Add method but OutputWindowPane.OutputTaskItemString to add errors to the Error List???) than what it is, and that the bridge between the automation model and the SDK is much more complicated that it should be (EnvDTE.DTE lacking a GetService method, assemblies that are only in the GAC or that you need to get installing the SDK that I don’t want to install, IVSHierarchy <-> EnvDTE.Project conversions, 0-based collections vs 1-based collections, etc.). The result is several hours spent making something simple work…

MZ-Tools Articles Series: HOWTO: Get an OutputWindowPane to output some string from a Visual Studio add-in or macro

To deal with international versions of Visual Studio and other Microsoft products (such as Office) has always been tricky. I think to remember that many years ago some version of Excel went as far as localizing the VBA statements (or macro language before VBA, not sure now), so while in English you had:

For i = 1 to 100
...
Next

in Spanish you had:

Para i = 1 a 100
...
Siguiente

I suppose that was intended to get accountants into macros without forcing them to learn two languages: VBA and, hmmm, English. Of course the experiment didn’t work well and currently VBA is English-only.

Today things should be easy if two rules are followed:

  • The user interface is localized, but the names of objects in the internals (ie: programming) should not be localized. So, classes should have a property such as “Caption” that is localized (in .NET the Caption property has been replaced by the Text property but it’s the same) and a property such as “Name” that should NOT be localized. Every programmer can understand the difference between “Name” and “Caption”, right?
  • When locating an item in a collection, the English name should be used as index, never the localized name.

Alas, the reality is not so easy:

  • The Office Commandbar class has the Name and NameLocal properties (and lacks a Caption property). You can learn searching the MSDN help (don’t expect the Intellisense help to tell the difference) that NameLocal actually means something like LocalizedName. The difference between “Name” and “NameLocal” is not so clear as between “Name” and “Caption”.
  • Visual Studio command names are also localized (http://msdn.microsoft.com/en-us/library/aa290377(VS.71).aspx), supposedly because a programmer can type them in the Command window, I guess. But MS did it right and the Commands collection can be queried by English names even on localized Visual Studio versions
  • Visual Studio output window panes (EnvDTE.OutputWindowPane) have a Name property, and guess what? The Name is localized, so in Spanish is “Generar” instead of “Build”. I know, I know, VS is so extensible that it uses Guids to identify things (as if fully qualified names were not enough) and therefore the EnvDTE.OutputWindowPane class has a Guid property too that you should use to locate a specific output window pane. My point is that the Name property of an EnvDTE.OutputWindowPane should be “Caption” and it would not cause havoc when an English developer receives a call from a German customer saying that the product doesn’t work with German Visual Studio because the developer thought that Name would not be localized. The last thing that I want to see is forcing developers to hardcode strings in their product with all the localized names that the product needs with its Korean, Chinese, Japanese, etc. strings…). Even the code generated by the add-in wizard of Visual Studio does the same as I explained in another article.
  • With all that precedents, since you cannot be sure that your product will work with international Visual Studio versions, you have to test, but VS forces you to install the whole product in another language, rather than supplying downloadable localized resource DLLs to put in some folder (in VS.NET 2002/2003 you could get them from the CD of the international versions but in VS 2005 they are hidden in CAB files…)

So, my latest article explains how to get an output window pane by Guid without learning Chinese 😉

HOWTO: Get an OutputWindowPane to output some string from a Visual Studio add-in or macro
http://www.mztools.com/articles/2008/MZ2008023.aspx

The (missing) Visual Studio 2008 SDK Reference

While I don’t write VS packages with the SDK, sometimes I need to use the SDK documentation to get an interface/service (most of the time to answer some question in the forums, or to do something that can’t be achieved with the automation model EnvDTE for add-ins). Since I don’t know by heart the name of all interfaces, I use the following page to search for an interface with some word in the name:

Interfaces
http://msdn.microsoft.com/en-us/library/bb180708(VS.80).aspx

Notice that the page belongs to Visual Studio 2005 Visual Studio Visual Studio SDK Reference Visual Studio Platform Reference (you can navigate the tree to the left). For some reason, the page still displays the warning “[This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.]” but I can live with that.

When I navigate the tree to locate the equivalent for Visual Studio 2008, I never find it, so today I investigated a little more and I found the following page:

Visual Studio SDK Reference
http://msdn.microsoft.com/en-us/library/bb166217.aspx

which applies to VS 2008 and where the “Visual Studio Platform Reference” link is not a link (neither the Managed Package Framework Reference link), and for that reason I guess it doesn’t appear in the tree too. The page still shows the warning [Note: This topic is pre-release documentation and is subject to change in future releases. Blank topics are included as placeholders.] and apparently is not updated since November 2007.

So, until the VS 2008 documentation is updated, you have to use the VS 2005 SDK reference documentation.

Code files without a project

Most code files belong to a project, which can be C#, VB.NET, etc. and all the files of the project must be in the same language. VS 2005 introduced Web Site projects which allow to mix code files in different languages. In both cases, when you request the code model from the code file using ProjectItem.FileCodeModel, you get something.

A couple of days ago I became aware of a third “kind” of code file: introduced by VS 2005, you can right-click the solution node in the Solution Explorer, click Add, Add New Item… and select “Visual C# Class” or “Visual Basic Class”. I don’t know why one would want to add code files to the Solution Items node of the Solution since they won’t be compiled, but you can. The interesting thing from the automation model (EnvDTE) point of view is that the ProjectItem.FileCodeModel returns Nothing (null in C#). So, chances are that the features of your add-in that deal with code files will fail against this kind of code files.

More on PRB: “Could not copy temporary files to the output directory” when running add-in project

Three years ago I wrote the article PRB: ‘Could not copy temporary files to the output directory’ error building Visual Studio .NET add-in explaining the common cause for that error when running an add-in project.

There is a second cause that happens when you have a dead devenv.exe process, which can happen from time to time while debugging add-ins, interrupting abruptly the debugging session, entering the computer in sleep mode or something else that I can’t determine for sure, but the fact is that you end with two devenv.exe instances in memory, a visible one with your add-in project and a hidden one locking the DLL. Since this second one doesn’t have a visible window, you have to use the Processes tab of the Task Manager to see it. And of course, you want to kill it, but how do you know which devenv.exe process to kill? The Task Manager of Windows is not of much help, but you can use the much more powerful Process Explorer utility from the former SysInternals (now owned by Microsoft and provided through Microsoft TechNet):

http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx

With Process Explorer, which can replace the Task Manager when pressing Ctrl+Alt+Del and clicking the Task Manager button (Options, Replace Task Manager menu) you can right-click a devenv.exe process and select the “Bring to Front” menu. If the main IDE window brings to front, you know that the devenv.exe process is active. Otherwise, it is a dead one and you can kill it. Before using Process Explorer, I always had to choose randomly and I almost ended killing the wrong one.

MZ-Tools Articles Series: HOWTO: Retrieve the Command that created a CommandBarControl from a Visual Studio add-in or macro

The question “How do I retrieve a Command from a CommandBarControl?” has been posted in the forums at least a couple of times and my answer always was “It’s not possible” because the CommandBarControl lacks a Command property, which is the intuitive thing that one would think. It was not until today that I realized that MS provided a way, although far from intuitive:

HOWTO: Retrieve the Command that created a CommandBarControl from a Visual Studio add-in or macro
http://www.mztools.com/articles/2008/MZ2008021.aspx

HOTFIX: A command-line add-in does not load when you start the Visual Studio 2005 IDE

Quite a few months ago I published a post that became quite popular about the Frustrations with command-line add-ins for Visual Studio, where it was mentioned a bug in the Visual Studio 2005 IDE which didn’t loaded command-line add-ins. Today I have found that Microsoft released a hotfix for this problem:

FIX: A command-line add-in does not load when you start the Visual Studio 2005 IDE
http://support.microsoft.com/kb/934517

While it would have been better not to introduce this bug in VS 2005 SP1, it is nice that Microsoft fixed it after SP1 and the patch for Windows Vista, when VS 2008 was about to be released. It is also very very nice that you can download hotfixes without calling MS Support Services.

Now, the funny thing: the KB article says that this hotfix introduces an incompatibility with the XBox 360 SDK. It’s a good thing that I am in the Sony PS3 / PSP camp 😉

BTW, I found this hotfix while searching the list of hotfixes for Visual Studio trying to solve a very nasty problem with the VBC compiler (VB.NET) of VS 2008 that is crashing. It’s nice to see that there is a growing list of hotfixes, although none for my problem (it seems). UPDATE (8 August, 2008): I narrowed the problem to a custom attribute applied to a property and seeing the list of fixes of VS 2008 SP1 beta for VB.NET it mentions Connect ID 322131: “An application that contains a custom attribute causes the compiler to crash” so hopefully that’s my fix 🙂