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