Aug 232011
 

Continuing on from Part 1, our objective in this article is to create a new Visual Studio solution, and then populate it with some existing projects.  To facilitate this, it would be beneficial if you had a few projects already created.  For this example, I have defined the following structure:

C:\UnitTesting – Root Folder

C:\UnitTesting\TestSolution – An existing folder which contains subdirectories which hold project files.

The sample structure is as follows:

image

So basically, there are three projects and two reference each other.  Our goal is to create a new solution file in the root directory which is then populated with references to the existing project files.

Please bear in mind that this is all demo code, and lacks the normal hardening and checks which production code should have.  Please use wisely, as there are no refunds.. ha!

I’ve reworked the previous example to be a little more robust, as follows:

  1. Encapsulated the functionality to create a new blank solution file (a method called CreateSolution())
  2. Second, I’ve added a new function called AddProjects which will take a path, plus details about the target solution file.

I’ve moved the DTE2 object to be a static object, and refactored CreateSolution thus:

private static DTE2 _dte2 = (DTE2)Microsoft.VisualBasic.Interaction.CreateObject("VisualStudio.DTE.10.0", "");

public static void CreateSolution(string path, string solutionName)
{
    Solution4 solutionObject = (Solution4)_dte2.Solution;
    solutionObject.Create(path, solutionName);
    solutionObject.Close(true);
}

Unit testing for this was easy enough:

[TestMethod]
public void CreateEmptySolution()
{
    SolutionHelper.CreateSolution(@"C:\UnitTesting\", "UnitTestSolution");
}

[TestMethod]
public void AddProjects()
{
    File.Delete(@"C:\UnitTesting\UnitTestSolution.sln");
    SolutionHelper.AddProjects(@"C:\UnitTesting\UnitTestSolution.sln", @"C:\UnitTesting\TestSolution", "*.*proj");
}

As you can see, all pretty straightforward.  So reviewing our CreateSolution function, it now just takes a path and solution name (omit the “.sln” extension) and creates the empty solution.

The next step is to iterate and find projects, and finally – add them to a solution file.  I’ve added some logic which creates the target Solution File if it does not exist.

The AddProjects function is pretty easy to understand:

public static void AddProjects(string solutionFile, string projectPath, string projectWildCardMatch)
{
    if (!File.Exists(solutionFile))
    {
        CreateSolution(Path.GetDirectoryName(solutionFile), Path.GetFileName(solutionFile));
    }

    Solution4 solutionObject = (Solution4)_dte2.Solution;
    solutionObject.Open(solutionFile);

    var fileNames = Directory.GetFiles(projectPath, projectWildCardMatch, SearchOption.AllDirectories);

    foreach (string projectFile in fileNames)
    { 
        solutionObject.AddFromFile(projectFile, false);
    }

    solutionObject.Close(true);
}

Hopefully you can see how easy it is to add projects to the solution.  The boolean value in the call Solution.AddFromFile is important, as it elects to use the currently loaded solution.  If omitted, it is either attached to the project’s original solution (in my experience).  Make sure you save the solution when closing it.

If we open the generated solution file in Visual Studio 2010, you’ll see that not only are the projects added, but they also show the references:

image

Here is a complete copy of the source file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using EnvDTE80;
using EnvDTE100;
using System.IO;
using EnvDTE;
using System.Diagnostics;

namespace VSAutomation.Toolkit
{
    public static class SolutionHelper
    {
        private static DTE2 _dte2 = (DTE2)Microsoft.VisualBasic.Interaction.CreateObject("VisualStudio.DTE.10.0", "");

        public static void CreateSolution(string path, string solutionName)
        {
            Solution4 solutionObject = (Solution4)_dte2.Solution;
            solutionObject.Create(path, solutionName);
            solutionObject.Close(true);
        }

        public static void AddProjects(string solutionFile, string projectPath)
        {
            if (!File.Exists(solutionFile))
            {
                CreateSolution(Path.GetDirectoryName(solutionFile), Path.GetFileName(solutionFile));
            }

            Solution4 solutionObject = (Solution4)_dte2.Solution;
            solutionObject.Open(solutionFile);
            
            var fileNames = Directory.GetFiles(projectPath, "*.*proj", SearchOption.AllDirectories);

            foreach (string projectFile in fileNames)
            { 
                solutionObject.AddFromFile(projectFile, false);
            }

            solutionObject.Close(true);
        }
    }
}
Aug 082011
 

Now for something completely different.  Recently I had the desire to build a Visual Studio 2010 solution file for a number of project files under a directory.  There were many projects, and each one linked to a solution file – but I wanted a “master” solution file.. one to rule them all(?)

Generally, I hate manual repetitious tasks, and laboriously adding all the projects to a  new, blank, solution file seemed tedious and not fun one bit.  Then I decided that I’d write a program which would iterate all the projects and create a solution file for me.  Sounds simple enough, right?

Well, it took me on a journey into Visual Studio automation.  There are many facets of working with VS, as I found out, and backwards compatibility reigns supreme.  What I’m going to introduce you to is pretty much just for Visual Studio 2010, but in the right hands a design could come which supports versions dating back to Visual Studio 2005.

The Prerequisites

Optional Extras

Once you’ve got your system all nicely configured, we’re ready to begin.

The Beginning

To begin with, I created a Class Library.  My intention was to create a large amount of nice, reusable automation for integration into Team Build, utilities and so forth.  You never know when this is going to be very, very handy.

To start our journey, I decided that my requirement was going to be very simple (for Part 1): create a blank solution file using Visual Studio 2010.  Easy enough, right?  Well, as you’d expect, the devil is in the details.  This took me quite a bit of time to get right, but in the end the outcome was most pleasing.

So open up Visual Studio, create a project type you’d prefer and then ready the project, as so:

  1. Add a reference
  2. Pick the COM tab
  3. Scroll and select “Microsoft Development Environment 10.0”
  4. This will add several COM interop assemblies to your project (and also include the previous versions)
  5. Click on the .Net tab and select “Microsoft.VisualBasic” (even if you aren’t coding in Visual Basic)

image image

Now it is time to go code us up a simple solution.  I’m going to be working on a far more robust library, but in the meantime this will introduce you to the extremely powerful DTE and DTE2 objects.

We have to (sadly) instantiate a COM object wrapper, which will actually load devenv in a non-interactive mode, from which we can then cherry pick functionality we’d like to use.  For this demo, we’re just going to create a Visual Studio 2010 compatible solution file.

Consider the following very simple class:

namespace VSAutomation.Toolkit
{
    public static class SolutionHelper
    {
        public static void CreateSolution(string path, string solutionName)
        {
            DTE2 dte2 = (DTE2)Microsoft.VisualBasic.Interaction.CreateObject("VisualStudio.DTE.10.0", "");
            Solution4 solutionObject = (Solution4)dte2.Solution;
            solutionObject.Create(path, solutionName);
            solutionObject.Close(true);
        }
    }
}

You will notice that we create a COM object instance using the VisualBasic namespace.  When the code actually creates the object, under the hood, the machine will see an instance of Visual Studio created (but no GUI):

image

Once it is loaded, we can get access to the VS Solution functionality.  From here, it is a simple matter of supplying the path and solution name (omit the .sln extension) and, assuming all goes to plan, you will have the following output:

image

Obviously this is just the tip of the iceberg. 

We have so much powerful functionality to exploit, so we won’t stop at this.  Since we’ve achieved our objective for Part 1, we’ll wrap up here, but check back for Part 2 where we’ll populate the solution file with existing (or new) projects!

In the next part, we’ll also discuss ways of supporting the older versions (Visual Studio 2008 and 2005) as well as providing a more verbose sample.


Further Reading

How to: Get References to the DTE and DTE2 Objects

Solution4.Create Method (String, String)