Parsing Visual Studio Solutions (.sln) files

This is something that intrigues me, but until now I not have found a definitive answer.

What I have use most cases is the following code:

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 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);
        }
    }

    public string ProjectName { get; private set; }
    public string RelativePath { get; private set; }
    public string ProjectGuid { 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;
    }
}

From: https://social.msdn.microsoft.com/Forums/en-US/6ac84a8a-6b5b-40d5-adfc-38e978d6cdbe/parsing-a-visual-studio-solution-file-in-the-c-code?forum=csharplanguage

However I do not love this solution.
Other options that I have not yet explored are related to create a parser for VS Solutions.

One interesting post uses the Sprache

Which provides a monadic parser.The terms comes from the functional programming world.

In functional programming, a popular approach to building recursive descent parsers is to model parsers as functions, and to define higher-order functions (or combinators ) that
implement grammar constructions such as sequencing, choice, and repetition. Such parsers
form an instance of a monad , an algebraic structure from mathematics that has proved
useful for addressing a number of computational problems.

There is no grammar definition for solutions. The closests thing I could find was this:

sln = ver-header entries  
entries = (project | global)+

ver-header = visual-studio ", " format-version "\n" comment-version "\n"  
visual-studio = "Microsoft Visual Studio Solution File"  
format-version = "Format Version numeric-version  
numeric-version = nv_2002 | nv_2003 | nv_2005 | nv_2008 | nv_2010 | nv_2012  
nv_2002 = "7.00"  
nv_2003 = "8.00"  
nv_2005 = "9.00"  
nv_2008 = "10.00"  
nv_2010 = "11.00"  
nv_2012 = "12.00"  
comment-version = "# Visual Studio " year-version  
year_version = "2005" | "2008" | "2010" | "2012"

project = project-id "\n" project-parms* "EndProject" "\n"  
project-id = "Project(" '"{' pt-guid '}"' )" = " proj_parms  
proj_parms = proj-name ", " proj-path ", " proj_guid  
pt-guid = vcppguid | vbguid | vcsharpguid | webguid | siguid  
vcppguid = "8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942"  
vbguid = "F184B08F-C81C-45F6-A57F-5ABD9991F28F"  
vcsharpguid = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"  
webguid = "E24C65DC-7377-472b-9ABA-BC803B73C61A"  
siguid = "2150E333-8FDC-42A3-9474-1A3956D46DE8"  
proj_name = '"' STRING '"'  
proj_path = '"' STRING '"'  
proj_guid = HEX{8} "-" HEX{4} "-" HEX{4} "-" HEX{4} "-" HEX{12}

global = "Global" "\n" global-section* "EndGlobal" "\n"  
global_section = gs_start "\n" gs_body "EndGlobalSection" "\n"  
gs_start = "GlobalSection" "(" gs_kind ")" " = " gs_when  
gs_kind =   "SolutionConfigurationPlatforms"  
          | "ProjectConfigurationPlatform"
          | "SolutionProperties"
          | "ExtensibilityGlobals"
          | "ExtensibilityAddIns"
gs_kind_05 =   "SolutionConfiguration"  
             | "ProjectConfiguration"
             | "ProjectDependencies"
gs_when = "preSolution" | "postSolution"  
gs_body = gs_configs | gs_plats | gs_props  
gs_configs = (gs_config "\n")*  
gs_config = sol_cfg_plat " = " sol_cfg_plat  
sol_cfg_plat = sol_cfg "|" sol_plat  
sol_cfg = "Debug" | "Release" | STRING  
sol_plat = "Win32" | "x64" | STRING  
gs_plats = (gs_plat "\n")*  
gs_plat = "{" proj_guid "}" "." sol_cfg_plat "." gs_action " = " sol_cfg_plat  
gs_action = "ActiveCfg | "Build.0"  
gs_props = "HideSolutionNode = " ("TRUE" | "FALSE") "\n"  

Taken from this post

And finally I just found a parser. I haven't tried yet but it even has a nuget :) Onion.SolutionParser.Parser and Github Code