Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parsing Visual Studio Solution files

How can I parse Visual Studio solution (SLN) files in .NET? I would like to write an app that merges multiple solutions into one while saving the relative build order.

like image 377
Filip Frącz Avatar asked Apr 01 '09 20:04

Filip Frącz


4 Answers

The .NET 4.0 version of the Microsoft.Build assembly contains a SolutionParser class in the Microsoft.Build.Construction namespace that parses Visual Studio solution files.

Unfortunately this class is internal, but I've wrapped some of that functionality in a class that uses reflection to get at some common properties you might find helpful.

public class Solution {     //internal class SolutionParser     //Name: Microsoft.Build.Construction.SolutionParser     //Assembly: Microsoft.Build, Version=4.0.0.0      static readonly Type s_SolutionParser;     static readonly PropertyInfo s_SolutionParser_solutionReader;     static readonly MethodInfo s_SolutionParser_parseSolution;     static readonly PropertyInfo s_SolutionParser_projects;      static Solution()     {         s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);         if (s_SolutionParser != null)         {             s_SolutionParser_solutionReader = s_SolutionParser.GetProperty("SolutionReader", BindingFlags.NonPublic | BindingFlags.Instance);             s_SolutionParser_projects = s_SolutionParser.GetProperty("Projects", BindingFlags.NonPublic | BindingFlags.Instance);             s_SolutionParser_parseSolution = s_SolutionParser.GetMethod("ParseSolution", BindingFlags.NonPublic | BindingFlags.Instance);         }     }      public List<SolutionProject> Projects { get; private set; }      public Solution(string solutionFileName)     {         if (s_SolutionParser == null)         {             throw new InvalidOperationException("Can not find type 'Microsoft.Build.Construction.SolutionParser' are you missing a assembly reference to 'Microsoft.Build.dll'?");         }         var solutionParser = s_SolutionParser.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(null);         using (var streamReader = new StreamReader(solutionFileName))         {             s_SolutionParser_solutionReader.SetValue(solutionParser, streamReader, null);             s_SolutionParser_parseSolution.Invoke(solutionParser, null);         }         var projects = new List<SolutionProject>();         var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null);         for (int i = 0; i < array.Length; i++)         {             projects.Add(new SolutionProject(array.GetValue(i)));         }         this.Projects = projects;     } }  [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")] public class SolutionProject {     static readonly Type s_ProjectInSolution;     static readonly PropertyInfo s_ProjectInSolution_ProjectName;     static readonly PropertyInfo s_ProjectInSolution_RelativePath;     static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;     static readonly PropertyInfo s_ProjectInSolution_ProjectType;      static SolutionProject()     {         s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);         if (s_ProjectInSolution != null)         {             s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);             s_ProjectInSolution_RelativePath = s_ProjectInSolution.GetProperty("RelativePath", BindingFlags.NonPublic | BindingFlags.Instance);             s_ProjectInSolution_ProjectGuid = s_ProjectInSolution.GetProperty("ProjectGuid", BindingFlags.NonPublic | BindingFlags.Instance);             s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", BindingFlags.NonPublic | BindingFlags.Instance);         }     }      public string ProjectName { get; private set; }     public string RelativePath { get; private set; }     public string ProjectGuid { get; private set; }     public string ProjectType { get; private set; }      public SolutionProject(object solutionProject)     {         this.ProjectName = s_ProjectInSolution_ProjectName.GetValue(solutionProject, null) as string;         this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(solutionProject, null) as string;         this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(solutionProject, null) as string;         this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(solutionProject, null).ToString();     } } 

Note that you have to change your target framework to ".NET Framework 4" (not client profile) to be able to add the Microsoft.Build reference to your project.

like image 163
John Leidegren Avatar answered Sep 27 '22 23:09

John Leidegren


With Visual Studio 2015 there is now a publicly accessible SolutionFile class which can be used to parse solution files:

using Microsoft.Build.Construction; var _solutionFile = SolutionFile.Parse(path); 

This class is found in the Microsoft.Build.dll 14.0.0.0 assembly. In my case it was located at:

C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll 

Thanks to Phil for pointing this out!

like image 26
Matt Kucia Avatar answered Sep 27 '22 22:09

Matt Kucia


I don't know if anyone is still looking for solutions to this problem, but I ran across a project that seems to do just what is needed.

https://slntools.codeplex.com/ was migrated to https://github.com/mtherien/slntools

One of the functions of this tool is to merge multiple solutions together.

like image 34
WiredWiz Avatar answered Sep 27 '22 23:09

WiredWiz


JetBrains (the creators of Resharper) have public sln parsing abilities in their assemblies (no reflection needed). It's probably more robust than the existing open source solutions suggested here (let alone the ReGex hacks). All you need to do is:

  • Download the ReSharper Command Line Tools (free).
  • Add the following as references to your project
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

The library is not documented, but Reflector (or indeed, dotPeek) is your friend. For example:

public static void PrintProjects(string solutionPath)
{
    var slnFile = SolutionFileParser.ParseFile(FileSystemPath.Parse(solutionPath));
    foreach (var project in slnFile.Projects)
    {
        Console.WriteLine(project.ProjectName);
        Console.WriteLine(project.ProjectGuid);
        Console.WriteLine(project.ProjectTypeGuid);
        foreach (var kvp in project.ProjectSections)
        {
            Console.WriteLine(kvp.Key);
            foreach (var projectSection in kvp.Value) 
            {
                Console.WriteLine(projectSection.SectionName);
                Console.WriteLine(projectSection.SectionValue);
                foreach (var kvpp in projectSection.Properties)
                {
                    Console.WriteLine(kvpp.Key); 
                    Console.WriteLine(string.Join(",", kvpp.Value));
                }
            }
        }
    }
}
like image 37
Ohad Schneider Avatar answered Sep 28 '22 00:09

Ohad Schneider