Upgrade Projects: Project Updater

Introduction

Click here to view the source code

Here is the past article related to Project Aggregation:

So, as I mentioned in my last blog post, I was working on a project involving numerous severely out of date .NET projects that I wanted to update. The caveat was I did not want to meticulously go through each one and upgrade them by hand, but instead to leverage Visual Studio’s SDK to do the upgrading process for me. Fortunately, there is a way to do this through the EnvDTE namespace and at this point I was able to first combine all the projects across various solutions into one single solution. After figuring this out I am now ready to start using this single solution to methodically upgrade each project as I see fit.

Project Details

After aggregating all my projects I was able to start working with the EnvDTE.Project class, but trying to figure out how to sift through the details of every project seem to be a bit problematic. It seems Microsoft does not actually give you deep tutorial details about how to mess with Visual Studio (VS) programmatically, which makes sense considering how easily you are able to shoot yourself in the foot (e.g. instances of VS staying open if not closed properly as mentioned in the last blog post). But I did manage to find a stackoverflow post that describes someone’s attempt to also change the target framework for their projects.

I went ahead and created an enumeration to represent all the possible framework versions that was currently available at the time of this writing (I did not bother to go below 3.5 considering I am trying to move towards progress not go backwards):

public enum TargetFramework
{
    [Description("3.5")]
    v3_5,
    [Description("4.0")]
    v4_0,
    [Description("4.5")]
    v4_5,
    [Description("4.5.1")]
    v4_5_1,
    [Description("4.5.2")]
    v4_5_2,
    [Description("4.6")]
    v4_6
}

At this point we can use much of the same implementation from our last post for manipulating VS projects and solutions. The one thing I included was a check for the Solution User Options (.suo) file. The file actually can become quite a necessity when aggregating a large amount of projects as it severely reduces the amount of loading times and manipulating projects for a solution. But this file is not always created if the application is programmatically opening the solution for the first time. For that reason, I attempt to force VS to create one by closing and reopening the solution:

var sourceDirectory = Path.GetDirectoryName(_solutionName);
var suoFiles = (new DirectoryInfo(sourceDirectory)).GetFiles("*.suo");
if (!suoFiles.Any())
{
    _logger.Log("No .suo file, closing and reopening solution");
    await CloseAsync();
    _dte = EnvDTEFactory.Create(_visualStudioVersion);
    await OpenSolution();
}

Just like with the aggregation process we are going to iterate through the upgrade process multiple times in case certain projects are not able to upgrade on a certain pass. At this point the upgrade process starts:

private void UpdateProject(ProjectWrapper project, TargetFramework framework)
{
    project.AttemptToReload();

    if (project.Project.Kind == Constants.vsProjectKindSolutionItems
        || project.Project.Kind == Constants.vsProjectKindMisc)
    {
        project.IsSpecialProject = true;
    }
    else
    {
        if (SetTargetFramework(project.Project, framework))
        {
            project.Reload();
            _logger.Log("Project Updated: {0}", project.Name);
        }

        lock (_nonUpdatedLocker)
        {
            _nonUpdatedProjects.Remove(project);
        }
    }
}

After initially going through the upgrade process I started receiving errors related to ‘Project Unavailable.’ After sifting online I found a solution to this issue by attempting to reload the project if it ends up unloading. The AttemptToReload() method simply attempts to access a property from the EnvDTE.Project and if it fails we will reload it. Although I found a solution to this problem it was off putting not to know why this problem occurs in the first place. But I did notice something after a project needed to be reloaded that it no longer had to be upgraded. So, it seems VS will automatically upgrade projects dependent on certain ones you are upgrading.

After some various trial and error, I noticed I was coming across special types of projects in the code base related to either a solution item or as miscellaneous. Given these types of projects have no purpose to upgrade, I simply marked them as special and move on (in case later I may want to use this information for some other purpose). Once we filter through the special projects, we can start setting the framework for a given project. The first thing we need to retrieve is the project’s target framework moniker, which is just the string value used to set the framework version, along with whether you want to set the project as a Client Profile:

private string GetTargetFrameworkMoniker(TargetFramework targetFramework, bool isClientProfile = false)
{
    var version = targetFramework.ToDescription();

    var clientProfile = isClientProfile ? ClientProfile : String.Empty;

    return String.Format(TargetMoniker, version, clientProfile);
}

So the return string value for wanting to set a projects framework for 4.6 as a non-Client Profile will be “.NETFramework,Version=v4.6”. From this point we will compare if the new target moniker differs from what is already set. If so, we go ahead and grab the project’s property related to ‘TargetFrameworkMoniker’ and set it with our new target monker:

private bool SetTargetFramework(Project project, TargetFramework targetFramework)
{
    var targetMoniker = GetTargetFrameworkMoniker(targetFramework);
    var currentMoniker = project.Properties.Item(TargetFrameworkMonikerIndex).Value;

    if (!currentMoniker.ToString().Contains("Silverlight")
        && !Equals(targetMoniker, currentMoniker))
    {
        project.Properties.Item(TargetFrameworkMonikerIndex).Value = targetMoniker;

        return true;
    }

	…
}

Once this process succeeds we will go ahead and reload the project again since after changing a project property it will tend to unload itself:

_project = (Project)((Array)(_project.DTE.ActiveSolutionProjects)).GetValue(0);

At this point, we are able to upgrade all projects programmatically. But there were quite a few issues I had to deal with that I could not figure out how to resolve through this process.

Issues

  • One of the main issues I still received from my last blog post was the EnvDTE interfaces becoming busy, which would result in a ‘RPC_E_SERVERCALL_RETRYLATER’ exception message. Many AttemptTo methods still had to be used to retry the process to see when the solution would become available again.
  • A problem I could not resolve programmatically was attempting to upgrade ASP.NET projects. Although I attempted to try and resolve this issue programmatically, since there were less than a handful of projects that needed upgrading I went ahead and upgraded them manually.
  • There was also a weird error I ran into stating ‘Inheritance security rules violated by type: ItemsCollectionEditor. Derived types must either match the security accessibility of the base type or be less accessible.’ This was quite a difficult problem to try and hunt down since even finding the offending project seemed to jump around from time to time. I found this post related to the error, which suggested a couple ideas to manipulate the AssesmblyInfo.cs files. Another error I came across as well during this time was a ‘Operation not supported’ exception. And I did find another post for this error as well, but they mostly described an explanation to the issue rather than providing a solution. In the end, I found the arrangement of the projects seemed to only allow for proper upgrading while using VS 2013.

Conclusion

As long as it took to get to the point of being able to automate the upgrade process for all the projects, it turned out this was actually the easiest part of the entire process. Getting the solutions to build again with all the architectural changes made in .NET and making sure applications were able to run again still were not enough. Despite the effort put in, one of the ORMs used within the projects were left so stagnated that later versions were incompatible staying on the old current framework of .NET, but the latest version made such significant changes to their product that it was close to impossible to modify all the hundreds of files using it. Although there were solutions, unfortunately so much time had be spent at this point that the upgrade process was put to a halt. Fortunately though I managed to gain a greater appreciation for VS’s SDK and came out with a couple of highly useful tools for aggregating and upgrading future projects.