Introduction
Microsoft officially released Visual Studio 2008 in February 2008 and Visual Studio 2010 in May 2010. However, many users were disappointed that the file format for the older Visual Studio "project files" was not backwards compatible.
- If you open an older Visual Studio project with a newer version of Visual Studio, you will be prompted for a conversion wizard.
- The conversion is "one way"... once converted, the project can no longer be opened with the previous version.
- If you selected "no" at the conversion wizard, Visual Studio just exits, and the project is not opened
So, what if you decided to do a phased rollout of the newer Visual Studio... where some programmers are still running VS2005/VS2008? Any project that been touched by VS2008/VS2010 is now inaccessible to the users of VS2005/VS2008.
One solution would be to keep both the older Visual Studio on your system when you install the newer version. Both versions co-exists peacefully. However, that doesn't solve the problem of "accidently" converting a project. Also, Visual Studio is huge (particularly if you install the MSDN documentation).
But wait... I heard that Visual Studio has the ability to target multiple versions of the Framework! Yes, that's true, but the project files that target the .Net Framework 2.0 are stillnot backwards compatible with the older versions.
Another problem exists when you download source code from the internet that's in a different format from what you've got installed.
Project Converter
The technique described in this article uses an external utility to convert between the Visual Studio project file formats. It can be run on systems which do not have any version of Visual Studio installed at all.
The program can be launched as a normal windows application or it can be installed in order to get a new Explorer "shell extension" that adds ProjectConverter to the "Open With" option when right-clicking on a *.sln file.
Note: This utility just edits the project files... it does not edit the source code itself. It does not attempt to resolve references that may be specific to a particular version of the Framework. For example, if you have a .Net Framework 3.5 application that uses LINQ, this utility will not somehow magically convert the source code so that it will compile on VS2005 .
The Solution (*.sln) file
The solution file is a text-based file that contains information about one or more projects. Below is a typical solution file:
Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "ProjectConverter", "ProjectConverter.vbproj", "{B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}" EndProject Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "Setup", "Setup\Setup.vdproj", "{09667F41-0E35-4D40-A0A9-E71BA6740D93}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}.Debug|Any CPU.Build.0 = Debug|Any CPU {B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}.Release|Any CPU.ActiveCfg = Release|Any CPU {B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}.Release|Any CPU.Build.0 =Release|Any CPU {09667F41-0E35-4D40-A0A9-E71BA6740D93}.Debug|Any CPU.ActiveCfg = Debug {09667F41-0E35-4D40-A0A9-E71BA6740D93}.Release|Any CPU.ActiveCfg = Release EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal
You can see file file format marker and Project version is in the first few lines. The version number in this file sets the number you see in the solution file's icon (er, well... using the following "translation"):
Product Name | Product Version | File Format |
---|---|---|
Visual Studio .Net | v7.0 | 7 |
Visual Studio .Net 2003 | v7.1 | 8 |
Visual Studio 2005 | v8.0 | 9 |
Visual Studio 2008 | v9.0 | 10 |
Visual Studio 2010 | v10.0 | 11 |
The Project (*.vbproj, *.csproj, and *.vcproj) files
Below is a typical XML-based project file. The items that require tweaking to be compatible between the different versions are highlighted in yellow.
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProductVersion>9.0.21022</ProductVersion> <SchemaVersion>2.0</SchemaVersion> <ProjectGuid>{B637ACFD-0AFC-4FBB-A8C0-602B5ABA62F0}</ProjectGuid> <OutputType>WinExe</OutputType> <StartupObject>ProjectConverter.My.MyApplication</StartupObject> <RootNamespace>ProjectConverter</RootNamespace> <AssemblyName>ProjectConverter</AssemblyName> <FileAlignment>512</FileAlignment> <MyType>WindowsForms</MyType> <TargetFrameworkVersion>v2.0</TargetFrameworkVersion> <OptionExplicit>On</OptionExplicit> <OptionCompare>Binary</OptionCompare> <OptionStrict>Off</OptionStrict> <OptionInfer>On</OptionInfer> <ApplicationIcon>Icon1.ico</ApplicationIcon> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <DebugSymbols>true</DebugSymbols> <DebugType>full</DebugType> <DefineDebug>true</DefineDebug> <DefineTrace>true</DefineTrace> <OutputPath>bin\Debug\</OutputPath> <DocumentationFile> </DocumentationFile> <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn> </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <DebugType>pdbonly</DebugType> <DefineDebug>false</DefineDebug> <DefineTrace>true</DefineTrace> <Optimize>true</Optimize> <OutputPath>bin\Release\</OutputPath> <DocumentationFile> </DocumentationFile> <NoWarn>42016,41999,42017,42018,42019,42032,42036,42020,42021,42022</NoWarn> </PropertyGroup> <ItemGroup> <Reference Include="System" /> <Reference Include="System.Data" /> <Reference Include="System.Deployment" /> <Reference Include="System.Drawing" /> <Reference Include="System.Windows.Forms" /> <Reference Include="System.Xml" /> </ItemGroup> <ItemGroup> <Import Include="Microsoft.VisualBasic" /> <Import Include="System" /> <Import Include="System.Collections" /> <Import Include="System.Collections.Generic" /> <Import Include="System.Data" /> <Import Include="System.Drawing" /> <Import Include="System.Diagnostics" /> <Import Include="System.Windows.Forms" /> </ItemGroup> <ItemGroup> <Compile Include="fmMain.vb"> <SubType>Form</SubType> </Compile> <Compile Include="fmMain.Designer.vb"> <DependentUpon>fmMain.vb</DependentUpon> <SubType>Form</SubType> </Compile> <Compile Include="My Project\AssemblyInfo.vb" /> <Compile Include="My Project\Application.Designer.vb"> <AutoGen>True</AutoGen> <DependentUpon>Application.myapp</DependentUpon> </Compile> <Compile Include="My Project\Resources.Designer.vb"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> <DependentUpon>Resources.resx</DependentUpon> </Compile> <Compile Include="My Project\Settings.Designer.vb"> <AutoGen>True</AutoGen> <DependentUpon>Settings.settings</DependentUpon> <DesignTimeSharedInput>True</DesignTimeSharedInput> </Compile> </ItemGroup> <ItemGroup> <EmbeddedResource Include="fmMain.resx"> <DependentUpon>fmMain.vb</DependentUpon> <SubType>Designer</SubType> </EmbeddedResource> <EmbeddedResource Include="My Project\Resources.resx"> <Generator>VbMyResourcesResXFileCodeGenerator</Generator> <LastGenOutput>Resources.Designer.vb</LastGenOutput> <CustomToolNamespace>My.Resources</CustomToolNamespace> <SubType>Designer</SubType> </EmbeddedResource> </ItemGroup> <ItemGroup> <None Include="app.config" /> <None Include="My Project\Application.myapp"> <Generator>MyApplicationCodeGenerator</Generator> <LastGenOutput>Application.Designer.vb</LastGenOutput> </None> <None Include="My Project\Settings.settings"> <Generator>SettingsSingleFileGenerator</Generator> <CustomToolNamespace>My</CustomToolNamespace> <LastGenOutput>Settings.Designer.vb</LastGenOutput> </None> </ItemGroup> <ItemGroup> <Content Include="Icon1.ico" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" /> <!-- To modify your build process, add your task inside one of the targets below and uncomment it. Other similar extension points exist, see Microsoft.Common.targets. --> <Target Name="BeforeBuild"> </Target> <Target Name="AfterBuild"> </Target> --> </Project>
The differences in the two file formats are summarized below:
Element | VS2005 | VS2008 | VS2010 |
---|---|---|---|
Tools Version | absent | 3.5 | 4.0 |
Product Version | 8.0.50727 | 9.0.21022 | absent |
Old Tools Version | absent | 2.0 (if converted) | 3.5 |
Target Framework Version | absent | 2.0 | 4.0 |
Bootstrapper | .NET Framework 2.0 | .NET Framework 2.0 (x86) | many |
Import Project | $(MSBuildBinPath) | $(MSBuildToolsPath) | $(MSBuildToolsPath) |
Other Project types
The Deployment Project type (*.vdproj) files appear to be backward compatible and therefore do not require conversion.
There is a completely new project file format for C/C++ in VS2010, so this utility will not be able to convert C/C++ projects to or from this new format. Sorry...
Note: No other project types are supported by this utility.
Source Code
If you're interested in the inner-workings of the ProjectConverter application, take a look at the following, otherwise just skip this section and download the installer file below.
We edit the text-based solution file first and get the list of subordinate projects that are associated with the solution. The solution file has a binary "marker" at the beginning of the file... so that means we need to use both the binary and text readers/writers to manipulate the file.
' First we read the solution file and build a list of ' project files that we find inside. We need both a binary ' reader and stream reader. fs = New FileStream(tbSolutionFile.Text, FileMode.Open) sr = New StreamReader(fs) br = New BinaryReader(fs) ' let's read Unicode Byte Order Mark (with CRLF) bom = br.ReadBytes(5) ' if we don't have a BOM, we create a default one If bom(0) <> &HEF Then bom(0) = &HEF bom(1) = &HBB bom(2) = &HBF bom(3) = &HD bom(4) = &HA ' rewind the streamreaders fs.Seek(0, SeekOrigin.Begin) End If sc = New StringCollection Do While sr.Peek() >= 0 buf = sr.ReadLine ' no need for any fancy parsing routines If buf.StartsWith("Microsoft Visual Studio Solution File, Format Version") Then ' Yes, I know, this looks counter intuitive... but format version numbers ' do not match the Visual Studio version numbers. Select Case ConvertTo Case Versions.Version8 sc.Add("Microsoft Visual Studio Solution File, Format Version 9.00") Case Versions.Version9 sc.Add("Microsoft Visual Studio Solution File, Format Version 10.00") Case Versions.Version10 sc.Add("Microsoft Visual Studio Solution File, Format Version 11.00") End Select Continue Do End If If buf.StartsWith("# Visual") Then Select Case ConvertTo Case Versions.Version8 sc.Add("# Visual Studio 2005") Case Versions.Version9 sc.Add("# Visual Studio 2008") Case Versions.Version10 sc.Add("# Visual Studio 2010") End Select Continue Do End If sc.Add(buf) ' parse the project files If buf.StartsWith("Project(") Then Dim ProjParts(), ProjFile, ProjExt As String ProjParts = buf.Split(","c) If ProjParts.Length = 3 Then ProjFile = Path.GetDirectoryName(tbSolutionFile.Text) & "\" & _ ProjParts(1).Trim.Trim(Chr(34)) ' we only support VB, C#, and C/C++ project types so that means ' we do not attempt to convert Deployment Projects (*.vdproj) Try ProjExt = Path.GetExtension(ProjFile) If ProjExt = ".vbproj" Or ProjExt = ".csproj" Then ' convert the VB/C# project files If ConvertProject(ProjFile, ExistingVersion) Then ProjCount += 1 End If ElseIf ProjExt = ".vcproj" Or ProjExt = ".vcxproj" Then ' convert the C/C++project files If ConvertVCProject(ProjFile, ExistingVersion) Then ProjCount += 1 End If End If Catch ex As Exception MsgBox("Could not convert project file" & vbCr & ex.Message, _ MsgBoxStyle.Critical, "Error") ' TODO: Perform a "rollback" on those files that ' got converted so far? Exit Sub End Try End If End If Loop fs.Close() ' OK now it's time to save the converted Solution file fs = New FileStream(tbSolutionFile.Text, FileMode.Open) sw = New StreamWriter(fs) bw = New BinaryWriter(fs) ' write the BOM bytes (using the BinaryWriter) bw.Write(bom) bw.Flush() ' write the remaining text (using the StreamWriter) For Each buf In sc sw.WriteLine(buf) Next sw.Flush() fs.Close()
Next, we edit the XML-based project files using the XML Document Object Model (XDOM) routines. Admittedly, using XDOM is a bit more complicated, since you're selecting elements and attributes to add/delete/modify.
' ' Convert the VB and C# Project file ' Private Function ConvertProject(ByVal ProjFile As String, ByVal ExistingVersion As Double) _ As Boolean Dim xd As Xml.XmlDocument Dim xnsm As Xml.XmlNamespaceManager Dim xn, xtemp As Xml.XmlNode Dim temp As String xd = New Xml.XmlDocument xd.Load(ProjFile) xnsm = New Xml.XmlNamespaceManager(New Xml.NameTable()) xnsm.AddNamespace("prj", "http://schemas.microsoft.com/developer/msbuild/2003") ' let's do a quick sanity check to make sure we've got the ' version that we're expecting. Hey, it happens xn = xd.SelectSingleNode("/prj:Project", xnsm) If xn IsNot Nothing Then xtemp = xn.Attributes("ToolsVersion") If xtemp IsNot Nothing Then ' converting to VS2008, but project is already at VS2008 If ConvertTo = Versions.Version9 And xtemp.InnerText = "3.5" Then ' exit quietly Return False End If ' converting to VS2010, but project is already at VS2010 If ConvertTo = Versions.Version10 And xtemp.InnerText = "4.0" Then ' exit quietly Return False End If Else ' If converting to VS2005, but project is already at VS2005 If ConvertTo = Versions.Version8 Then ' exit quietly Return False End If End If Else ' no such node? That's bad, very bad... Throw New ApplicationException("Invalid project file") End If ' the ToolsVersion attribute (we already have selected that node) xn.Attributes.Remove(xn.Attributes("ToolsVersion")) Select Case ConvertTo Case Versions.Version8 ' it gets removed Case Versions.Version9 ' add the attribute xtemp = xd.CreateAttribute("ToolsVersion") xtemp.Value = "3.5" xn.Attributes.Append(CType(xtemp, Xml.XmlAttribute)) Case Versions.Version10 ' add the attribute xtemp = xd.CreateAttribute("ToolsVersion") xtemp.Value = "4.0" xn.Attributes.Append(CType(xtemp, Xml.XmlAttribute)) End Select ' the ProjectVersion element xn = xd.SelectSingleNode("/prj:Project/prj:PropertyGroup/prj:ProductVersion", xnsm) If xn IsNot Nothing Then Select Case ConvertTo Case Versions.Version8 xn.InnerText = My.Settings.VS2005_Version Case Versions.Version9 xn.InnerText = My.Settings.VS2008_Version Case Versions.Version10 ' not used... strange xn.InnerText = "" End Select End If ' the OldToolsVersion element in the first PropertyGoup xn = xd.SelectSingleNode("/prj:Project/prj:PropertyGroup", xnsm) xtemp = xd.SelectSingleNode("/prj:Project/prj:PropertyGroup/prj:OldToolsVersion", xnsm) If xtemp IsNot Nothing Then xn.RemoveChild(xtemp) End If Select Case ConvertTo Case Versions.Version8 ' it gets removed Case Versions.Version9 ' add a new element ' Note: this doesn't appear to be added in every project type, ' but I bet it's harmless xtemp = xd.CreateElement("OldToolsVersion", xnsm.LookupNamespace("prj")) xtemp.AppendChild(xd.CreateTextNode("2.0")) xn.AppendChild(xtemp) Case Versions.Version10 ' add a new element xtemp = xd.CreateElement("OldToolsVersion", xnsm.LookupNamespace("prj")) xtemp.AppendChild(xd.CreateTextNode("3.5")) xn.AppendChild(xtemp) End Select ' remove/tweak the optional TargetFrameworkVersion element xn = xd.SelectSingleNode("/prj:Project/prj:PropertyGroup", xnsm) xtemp = xd.SelectSingleNode( _ "/prj:Project/prj:PropertyGroup/prj:TargetFrameworkVersion", xnsm) If xtemp IsNot Nothing Then xn.RemoveChild(xtemp) End If Select Case ConvertTo Case Versions.Version8 ' it gets removed Case Versions.Version9 xtemp = xd.CreateElement("TargetFrameworkVersion", _ xnsm.LookupNamespace("prj")) xtemp.AppendChild(xd.CreateTextNode("v3.5")) xn.AppendChild(xtemp) Case Versions.Version10 xtemp = xd.CreateElement("TargetFrameworkVersion", _ xnsm.LookupNamespace("prj")) xtemp.AppendChild(xd.CreateTextNode("v4.0")) xn.AppendChild(xtemp) End Select ' the optional BootStrapper elements. I only remove the inappropriate values, I ' do not attempt to add the newer values Select Case ConvertTo Case Versions.Version8 ' alter the value for the ProductName element using the older framework tag xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.2.0""]", xnsm) If xn IsNot Nothing Then xtemp = xn.SelectSingleNode("prj:ProductName", xnsm) xtemp.FirstChild.Value = ".NET Framework 2.0" End If ' remove the newer framework options xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.3.0""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.3.5""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Client.3.5""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.3.5.SP1""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Windows.Installer.3.1""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If Case Versions.Version9 ' alter the value for the ProjectName using the newer framework tag xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.2.0""]", xnsm) If xn IsNot Nothing Then xtemp = xn.SelectSingleNode("prj:ProductName", xnsm) xtemp.FirstChild.Value = ".NET Framework 2.0 %28x86%29" End If ' remove the newer framework options xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Client.3.5""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.3.5.SP1""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Windows.Installer.3.1""]", xnsm) If Not xn Is Nothing Then xn.ParentNode.RemoveChild(xn) End If Case Versions.Version10 ' alter the value for the ProjectName using the newer framework tag xn = xd.SelectSingleNode("/prj:Project/prj:ItemGroup/prj:BootstrapperPackage" & _ "[@Include=""Microsoft.Net.Framework.2.0""]", xnsm) If xn IsNot Nothing Then xtemp = xn.SelectSingleNode("prj:ProductName", xnsm) xtemp.FirstChild.Value = ".NET Framework 2.0 %28x86%29" End If End Select ' The MSBuildToolsPath vs. MSBuildBinPath environmental variable. Oddly enough ' a fully patched VS2005 uses the newer MSBuildToolsPath. So, this should only ' be required if you don't have VS2005 SP1 installed. However, I can't detect ' that, so we take the worst case scenario, and use the older version For Each xn In xd.SelectNodes("/prj:Project/prj:Import", xnsm) xtemp = xn.Attributes("Project") If xtemp IsNot Nothing Then temp = xn.Attributes("Project").Value If ConvertTo >= Versions.Version9 Then ' convert it to the newer MSBuildToolsPath If temp.Contains("MSBuildBinPath") Then temp = temp.Replace("MSBuildBinPath", "MSBuildToolsPath") xtemp.Value = temp End If Else ' convert it to the older MSBuildBinPath If temp.Contains("MSBuildToolsPath") Then temp = temp.Replace("MSBuildToolsPath", "MSBuildBinPath") xtemp.Value = temp End If End If End If Next xd.Save(ProjFile) Return True End Function
Downloads/Links
Download the MSI Installer file:
ProjectConverterSetup.msi
Download the VB.Net Source code for this program:
ProjectConverter2.zip
The C# source code version will be available shortly