Visual Studio Version Interoperability

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.

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.

ProjectConverter Screen Shot

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.

"Open With" screen shot

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