Environmental Transforms/AppSettings Transforms for Visual Studio 2013 Preview

Published on: Author: Michael

In a recent series of articles I discussed how to create an environmental tranform template that could be run on each build.  I also posted a series of articles on how to generate a template to generate a strongly typed class to back appsettings in a configuration file.  Alas shortly thereafter VS2013 Preview was released and changes to VS have broken the code.  This post will discuss the minor changes that need to be made to get everything to work.


Shared Targets


The first problem is with the shared .targets file.  Within the file an inline task is used to run the environmental transforms during a build.  Because of an issue with MSBuild it has to dynamically load the necessary assembly to do the transform and, as such, uses the dynamic keyword to keep things simple.  MSBuild v12 (the version shipping with VS2013) has a “bug” that causes the dynamic binding to fail.  I reported this to Microsoft on the Connect site.  Microsoft identified it as a bug but said it was unlikely to be fixed before release.  Fortunately though they provided a workaround that I’ll discuss next.


For VS2013 you can continue to use the same shared targets file but you will need to make an adjustment to the inline task.  MSBuild has a problem (I don’t really understand why) with inline tasks that use the AssemblyFile attribute to identify the assembly and the task uses the dynamic keyword.  Fortunately the solution is to simply switch to the AssemblyName attribute instead.  The assembly name needs to be fully qualified but otherwise it is a simple change.


<UsingTask TaskName="TransformXmlFiles" 
    TaskFactory="CodeTaskFactory"
    AssemblyName="Microsoft.Build.Tasks.v4.0,Version=4.0.0.0,PublicKeyToken=b03f5f7f11d50a3a,Culture=Neutral">

After this change is made the .targets file will now work with VS 2012 and VS 2013 Preview. 


VSIX Installation Target


The documentation for defining versioning ranges for the Visual Studio installation target is wrong as of VS 2013 Preview.  I reported this to Microsoft via Connect and they confirmed they would be updating it.  The first issue is that the documentation states that if you specify just a version (i.e. 11.0) then that identifies a minimal version with no maximal.  However as VS 2013 Preview this is no longer true.  Instead a single version indicates that the extension will only work with that version of VS.  Thus VS 2012 extensions will not show up for VS 2013.  To fix that we need to use a version range.


This brings up the second set of errors in the documentation.  The documentation states that a square bracket ([) indicates a maximum inclusive version but then it mixes inclusive and exclusive with parenthesis.  After some testing I’ve found that square brackets should be used for minimum/maximum inclusive ranges.  So to support VS 2012 and VS 2013 you should use the version range of [11.0,12.0].  Updating the VSIX manifest accordingly allows the extension to be installed in either version.


T4 Toolbox


The next problem is that the T4 templates rely on the T4 Toolbox which hasn’t been updated yet.  Without that extension our custom extension will not install.  To work around the issue we need to temporarily modify the VSIX package for T4 Toolbox so it will install on VS 2013.


  1. Download T4 Toolbox from the Visual Studio Gallery
  2. Open the VSIX file, it is a zip file.
  3. Extract the extension.vsixmanifest file..
  4. Open the file in a text editor.
  5. Change the line that identifies the installation target to use the version range mentioned earlier [11.0,12.0]
  6. Save and close the file.
  7. Add the file back to the VSIX file.
  8. Run the installer and it should install for VS 2013 Preview.

Note that this works for any VSIX file but ideally you should wait for the author to verify their extension before installing it.


Nested Projects


This is not actually a problem with VS 2013 but rather an implementation issue with the code we use to find projects.  Here’s the relevant piece of code.


foreach (EnvDTE.Project project in DteInstance.Solution.Projects)
{
   if (String.Compare(project.Name, projectName, true) == 0)
      return project;
};

Unfortunately this code will only return root level projects.  Projects that are contained in solution folders (yes it happens) will not be seen as only the root items are returned.  To fix that we will need to modify the code to enumerate solution folders as well.  Here is the updated code that will now find projects in subfolders.


foreach (var project in GetAllProjects(source, false))
{
   if (String.Compare(project.Name, projectName, true) == 0)
      return project;
};

public static IEnumerable<EnvDTE.Project> GetAllProjects ( this EnvDTE.DTE source, bool includeFolders )
{
   foreach (EnvDTE.Project project in source.Solution.Projects)
   {
      var isFolder = IsProjectFolder(project);

      if (isFolder)
      {
         foreach (var item in GetProjectsCore(project, includeFolders))
            yield return item;
      };

      if (!isFolder || includeFolders)
         yield return project;
   };
}

private static IEnumerable<EnvDTE.Project> GetProjectsCore ( EnvDTE.Project project, bool includeFolders ) {
   foreach (EnvDTE.ProjectItem item in project.ProjectItems)
   {
      if (item.SubProject != null)
      {
         var isFolder = IsProjectFolder(item.SubProject);
         if (isFolder)
         {
            foreach (var child in GetProjectsCore(item.SubProject, includeFolders))
               yield return child;
         };

         if (!isFolder || includeFolders)
            yield return item.SubProject;
      };
   };
}

Summary


Getting the transforms and code working with VS 2013 Preview was pretty straightforward although it would have been nice if they had just worked.  Nevertheless none of the changes were that bad.  I’ve attached an updated copy of the files from the earlier articles.


Code