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.
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:
protected VisualStudioVersion GetSolutionVersion()
{
VisualStudioVersion solVer = VisualStudioVersion.Unknown;
TextReader reader = new StreamReader(this.solutionPath);
string sln = reader.ReadToEnd();
reader.Close();
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:
public enum VisualStudioVersion
{
[StringValue("Unknown")]
Unknown,
[StringValue("Format Version 7.00")]
VisualStudio2002 = 2002,
[StringValue("Format Version 8.00")]
VisualStudio2003 = 2003,
[StringValue("Format Version 9.00")]
VisualStudio2005 = 2005,
[StringValue("Format Version 10.00")]
VisualStudio2008 = 2008,
[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.
protected IdeVersion GetIdeVersion()
{
IdeVersion ideVer = IdeVersion.Default;
TextReader reader = new StreamReader(this.solutionPath);
string sln = reader.ReadToEnd();
reader.Close();
foreach (IdeVersion version in Enum.GetValues(typeof(IdeVersion)))
{
if (sln.Contains(version.GetStringValue()))
{
ideVer = version;
}
}
return ideVer;
}
public enum IdeVersion
{
[StringValue(null)]
Default,
[StringValue("Visual Studio")]
VisualStudio,
[StringValue("Visual C# Express")]
CSExpress,
[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();
sln = sln.Replace(this.VisualStudioVersion.GetStringValue(),
solutionVersion.GetStringValue());
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);
}
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;
path = this.solutionPath.Remove(this.solutionPath.IndexOf(
Path.GetFileName(this.solutionPath)),
Path.GetFileName(this.solutionPath).Length);
this.projectConversionResults.Clear();
projectStrings.AddRange(
from Match match in this.projectPathRegex.Matches(reader.ReadToEnd())
where match.Success
select match.Value);
foreach (string projectString in projectStrings)
{
projectName = projectString.TrimEnd(new char[] { ' ', ',', '\"' });
index = projectName.LastIndexOf('\"') + 1;
projectName = path + projectName.Substring
(index, projectName.Length - index);
projectConverter = null;
if (!Path.GetExtension(projectName).Contains("vc"))
{
projectConverter = new ProjectConverter(projectName);
}
else
{
}
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
attributeProductVersion
elementTargetFrameworkVersion
elementProject
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:
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)
{
...
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;
}
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;
}
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.
A 2nd year student majoring in Computer Science at Holon Institute of Technology.