Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

SolutionConverter

0.00/5 (No votes)
27 May 2010 2  
A Visual Studio solution version converter to upgrade/downgrade solutions between VS versions.

Introduction

SolutionConverter will convert a complete Visual Studio solution from one version to another; it allows you to convert your solutions to both older and newer versions. Currently, Visual Studio 2005, 2008, and 2010 are supported, including Visual C# Express and Visual Basic Express editions. Visual C++ project conversion is not yet supported.

SolutionConverter

Background

I recently upgraded to Visual Studio 2010 at home and encountered the dreaded problem of opening my solutions in older Visual Studio versions. I attend H.I.T. where they have yet to install the new version, so I simply can't open my work on the institute's computers. After doing some research, I found this very nice article, but unfortunately, the application didn't support Visual Studio 2010 solutions when I started working on this project. Another thing I noticed that was missing was that it only changed the solution file and did not touch the project files, which also have to be edited for a smooth conversion.

So I decided to write my own tool with a view into the future, with simpler code which should allow easier extension in the future.

Looking through Google some more, this page popped up, explaining what exactly needed to be changed in the solution and project files to make the conversion work without any issues. I recommend going over it before continuing with the article to easily understand why the code is written the way it is.

Using the Code

This code will demonstrate the following:

  • Loading and analyzing a solution file. Detecting the version of Visual Studio it was intended for, and extracting the list of projects the solution contains.
  • Loading and analyzing the project files.
  • Converting the Solution file into the target version.
  • Working with the XML file, and converting the project files into the target version.

Loading the solution file and determining the solution version:

/// Retrieves the solution version from the file.
protected VisualStudioVersion GetSolutionVersion()
{
    VisualStudioVersion solVer = VisualStudioVersion.Unknown;
    TextReader reader = new StreamReader(this.solutionPath);
    string sln = reader.ReadToEnd();
    reader.Close();

    // Here we loop through the possible Visual Studio versions. 
    // Trying to determine the version of our solution.
    foreach (VisualStudioVersion version in Enum.GetValues(
             typeof(VisualStudioVersion)))
    {
        if (sln.Contains(version.GetStringValue()))
        {
            solVer = version;
        }
    }

    return solVer;
}

VisualStudioVersion is an enum which represents the possible Visual Studio versions. The enum values use a custom attribute called StringValueAttribute, which allows an enum value to have a sort of 'ToString' method, GetStringValue(). GetStringValue() is a generic extension method which extracts the string value from the first StringValueAttribute occurrence on the specific enum value. To make it clearer, here is the enum definition:

/// Lists all possible solution versions.
public enum VisualStudioVersion
{
    /// Unknown Visual Studio version.
    [StringValue("Unknown")]
    Unknown,        
    /// Visual Studio 2002.
    [StringValue("Format Version 7.00")]
    VisualStudio2002 = 2002,
    /// Visual Studio 2003.
    [StringValue("Format Version 8.00")]
    VisualStudio2003 = 2003,
    /// Visual Studio 2005.
    [StringValue("Format Version 9.00")]
    VisualStudio2005 = 2005,
    /// Visual Studio 2008.
    [StringValue("Format Version 10.00")]
    VisualStudio2008 = 2008,
    /// Visual Studio 2010.
    [StringValue("Format Version 11.00")]
    VisualStudio2010 = 2010,
}

As you can see, the string values simply represent the values that will appear in the solution file. By simply checking which of these strings the solution file contains, we can determine the Visual Studio version.

In a similar fashion, we can determine the specific IDE the solution was created in, such as Visual Studio or some of the Express editions.

/// Gets the IDE version.
protected IdeVersion GetIdeVersion()
{
    IdeVersion ideVer = IdeVersion.Default;
    TextReader reader = new StreamReader(this.solutionPath);
    string sln = reader.ReadToEnd();
    reader.Close();

    // Here we loop through the possible Visual Studio versions. 
    // Trying to determine the IDE version.
    foreach (IdeVersion version in Enum.GetValues(typeof(IdeVersion)))
    {
        if (sln.Contains(version.GetStringValue()))
        {
            ideVer = version;
        }
    }
    
    return ideVer;
}

/// Lists all possible IDE versions.
public enum IdeVersion
{
    /// Using the default version or Unknown.
    [StringValue(null)]
    Default,
    /// The full Visual Studio version.
    [StringValue("Visual Studio")]
    VisualStudio,
    /// C# Express Edition.
    [StringValue("Visual C# Express")]
    CSExpress,
    /// Visual Basic Express Edition.
    [StringValue("Visual Basic Express")]
    VBExpress
}

Now that we know what the environment in which the solution was created in, we can continue and convert the solution into the target version in which the solution was created in.

public ConversionResult ConvertTo(VisualStudioVersion solutionVersion, IdeVersion ideVersion)
{
    ...

    bool success = true;
    StreamReader reader = new StreamReader(this.solutionPath);
    FileStream stream = null;
    TextWriter writer = null;
    string sln = reader.ReadToEnd();
    reader.Close();

    // Replace the solution version.
    sln = sln.Replace(this.VisualStudioVersion.GetStringValue(), 
                      solutionVersion.GetStringValue());
    
    // If possible, also update the IDE version as well.
    if (ideVersion != IdeVersion.Default && this.IdeVersion != IdeVersion.Default)
    {
        string oldVersion, newVersion;
        oldVersion = this.IdeVersion.GetStringValue() + " " + 
                        ((int)this.VisualStudioVersion).ToString();
        newVersion = ideVersion.GetStringValue() + " " + 
                   ((int)solutionVersion).ToString();
        sln = sln.Replace(oldVersion, newVersion);
    }
    
    // Now write the file back to the hard drive.
    try
    {
        stream = File.OpenWrite(this.solutionPath);
        writer = new StreamWriter(stream, Encoding.UTF8);
        writer.Write(sln);
    }
    catch (Exception)
    {
        success = false;
    }
    finally
    {
        this.IsReady = false;
        if (stream != null)
        {
            writer.Close();
        }
    }
    
    ...
}

It is that simple, in C#, to convert the solution from one version to another. Now, we also need to convert the projects. We use a Regex expression and a LINQ query to retrieve the path and filename of every project in the solution:

private void ConvertProjects(VisualStudioVersion solutionVersion)
{
    using (StreamReader reader = File.OpenText(this.solutionPath))
    {
        List<string> projectStrings = new List<string>();
        IConverter projectConverter;
        string projectName, path;
        int index;

        // A bit messy, but gets the job done.
        // Retrieve the actual path of the solution.
        path = this.solutionPath.Remove(this.solutionPath.IndexOf(
                  Path.GetFileName(this.solutionPath)), 
                  Path.GetFileName(this.solutionPath).Length);
        this.projectConversionResults.Clear();
        
        // Selects all the strings that contain the "Project("{xxxxxxxx
        //    -xxxx-xxxx-xxxx-xxxxxxxxxxxx}") = "Name", "Name\Name.xxproj","
        // signature.
        projectStrings.AddRange(
           from Match match in this.projectPathRegex.Matches(reader.ReadToEnd())
           where match.Success
           select match.Value);

        // Here we attempt to convert all the projects
        // and save the conversion results.
        foreach (string projectString in projectStrings)
        {
            // Cut string down to only contain the solution relative path.
            projectName = projectString.TrimEnd(new char[] { ' ', ',', '\"' });
            index = projectName.LastIndexOf('\"') + 1;
            projectName = path + projectName.Substring
            (index, projectName.Length - index);
            projectConverter = null;

            // Make sure we don't crash the program
            // by trying to convert a Visual C++ project
            if (!Path.GetExtension(projectName).Contains("vc"))
            {
                projectConverter = new ProjectConverter(projectName);
            }
            else
            {
                // Reserved for a Visual C++ Project converter here.
            }

            if (projectConverter != null)
            {
                this.projectConversionResults.Add(
                         projectConverter.ConvertTo(solutionVersion));
            }
        }
    }
}

The code above creates a ProjectConverter object for each of the solution's projects, invokes the Convert method, and adds the conversion result to the ConversionResults collection.

Now we need to identify the project version we are working on. Since the solution file is a formatted text file, all we have to do is read it, replace the relevant values, and save it. Things change when working on project files. Project files in Visual Studio are XML files that contain all the relevant information and settings for the project. These files also rely on the XML namespace 'http://schemas.microsoft.com/developer/msbuild/2003'. There are four elements and attributes that we are going to need access to (as per this):

  • ToolsVersion attribute
  • ProductVersion element
  • TargetFrameworkVersion element
  • Project attribute of the Import element

Reading the information from the file and into the XElement and XAttribute objects:

public bool Reload()
{
    try
    {
        this.project = XDocument.Load(this.projectPath);
    }
    catch (Exception)
    {
        this.project = null;
    }

    bool ret = false;
    this.vsVersion = VisualStudioVersion.Unknown;
    this.IsReady = false;
    if (this.project != null)
    {
        this.toolsVersion = this.project.Root.Attribute(XName.Get("ToolsVersion"));
        this.productVersion = this.project.Root.Element(XName.Get("PropertyGroup", 
                              this.namespaceName)).Element(XName.Get(
                              "ProductVersion", this.namespaceName));
        this.targetFrameworkVersion = this.project.Root.Element
                (XName.Get("PropertyGroup", 
                                     this.namespaceName)).Element(XName.Get(
                                     "TargetFrameworkVersion", this.namespaceName));
        this.importProject = this.project.Root.Element(XName.Get("Import", 
                             this.namespaceName)).Attribute(XName.Get("Project"));
        this.Name = Path.GetFileNameWithoutExtension(this.projectPath);

        this.vsVersion = this.GetVsVersion();
        this.IsReady = true;
        ret = true;
    }

    return ret;
}

Here is the code that we can use to determine which Visual Studio version this project belongs to:

/// Gets the Visual Studio version of the current project.
private VisualStudioVersion GetVsVersion()
{
    VisualStudioVersion vsVer = VisualStudioVersion.Unknown;

    double product;
    if (Double.TryParse(this.productVersion.Value, out product))
    {
        if (product == 7)
        {
            vsVer = VisualStudioVersion.VisualStudio2002;
        }
        else if (product == 7.1)
        {
            vsVer = VisualStudioVersion.VisualStudio2003;
        }
        else if (product == 8)
        {
            vsVer = VisualStudioVersion.VisualStudio2005;
        }
        else if (product == 9)
        {
            vsVer = VisualStudioVersion.VisualStudio2008;
        }
        else if (product == 10)
        {
            vsVer = VisualStudioVersion.VisualStudio2010;
        }
    }

    return vsVer;
}

And here is the code used to convert the projects. What we do is remove or add the relevant attributes or elements to the XML file, saving it afterwards. Since we are working directly with an XML object, we will receive a valid XML file after this procedure, ready to be used in a different Visual Studio version.

public ConversionResult ConvertTo(VisualStudioVersion vsVersion)
{
    ...
    
    // Calling the relevant method for editing
    // the attributes and elements of the XML.
    switch (vsVersion)
    {
        case VisualStudioVersion.VisualStudio2002:
            this.ConvertToVs2002(vsVersion);
            break;
        case VisualStudioVersion.VisualStudio2003:
            this.ConvertToVs2003(vsVersion);
            break;
        case VisualStudioVersion.VisualStudio2005:
            this.ConvertToVs2005(vsVersion);
            break;
        case VisualStudioVersion.VisualStudio2008:
            this.ConvertToVs2008(vsVersion);
            break;
        case VisualStudioVersion.VisualStudio2010:
            this.ConvertToVs2010(vsVersion);
            break;
    }

    // Saving the Project XML file.
    FileStream fileStream = null;
    XmlTextWriter writer = null;
    ret.ConverterReference = this;
    try
    {
        fileStream = new FileStream(this.projectPath, FileMode.Create);
        writer = new XmlTextWriter(fileStream, Encoding.UTF8);
        writer.Formatting = Formatting.Indented;
        this.project.WriteTo(writer);
        ret.ConversionStatus = ConversionStatus.Succeeded;
    }
    catch (Exception)
    {
        ret.ConversionStatus = ConversionStatus.Failed;
    }
    finally
    {
        this.IsReady = false;
        if (writer != null)
        {
            writer.Close();
        }
    }

    return ret;
}
    
/// Converts to VS2008 version.
private void ConvertToVs2008(VisualStudioVersion vsVersion)
{
    if (this.toolsVersion != null)
    {
        double tools;
        if (Double.TryParse(this.toolsVersion.Value, out tools) && tools != 3.5)
        {
            this.toolsVersion.Value = "3.5";
        }
    }
    else
    {
        this.project.Root.Add(new XAttribute(XName.Get("ToolsVersion"), "3.5"));
    }

    if (this.productVersion != null)
    {
        this.productVersion.Value = "9.0.21022";
    }

    if (this.targetFrameworkVersion != null)
    {
        double framework;
        if (Double.TryParse(
            this.targetFrameworkVersion.Value.Substring(1, 
                   this.targetFrameworkVersion.Value.Length - 2),
                   out framework) && framework > 3.5)
        {
            this.targetFrameworkVersion.Value = "v3.5";
        }
    }
    else
    {
        XElement newToolsVersion = new XElement(XName.Get("TargetFrameworkVersion", 
                                       this.namespaceName), "v2.0");
        this.project.Root.Element(XName.Get("PropertyGroup", 
                                  this.namespaceName)).Add(newToolsVersion);
    }

    if (this.importProject != null && 
             !this.importProject.Value.Contains("$(MSBuildToolsPath)"))
    {
        this.importProject.Value = 
          this.importProject.Value.Replace("$(MSBuildBinPath)", "$(MSBuildToolsPath)");
    }
}

References and Inspirations

History

  • 6 May, 2010 - Version 1.0.3779.3862 - Initial release.
  • 9 May, 2010 - Updated source code (included DLL).
  • 27 May, 2010 - Updated article text.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here