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.
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.
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!
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.
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:
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));
}
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With