Parsing Visual Studio Solution files
Asked Answered
F

11

116

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.

Frontward answered 1/4, 2009 at 20:0 Comment(1)
For editing sln files there appears to be nuget.org/packages/SLNTools.Core which I haven't used, and I've been hacking on this fork of SlnParser to make it read-write: nuget.org/packages/SlnEditorHeadliner
B
119

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.

Briefing answered 8/1, 2011 at 15:10 Comment(16)
Is there something like this for MSBuild 3.5 \ Visual Studio 2008?Chindwin
I wen't looking but I didn't find anything. However, you should be able to use this code with the 3.5 tool chain.Briefing
Wow, thanks. Has anyone successfully gone further and used the ProjectParser as well? I'm trying to parse a gigantic solution file and list the TargetFrameworkVersion of each project.Unclog
You can get at stuff in projects simply by using the public stuff in Microsoft.Build.Evaluation I think that was the right one, or did you mean something else?Briefing
This is fine, however it shows "Solution Items" groups as "Projects" which is incorrect.Bankbook
Here are the "using" statements to add: using System; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq;Weihs
@Bankbook any fix about it?Marquise
@Marquise - you can examine the "ProjectType" property of the s_ProjectInSolution object in the same way as the other properties exposed. This returns an enum but just ToString() it. I tried to edit the post to include this twice but just got shot down in flames each time.Kearney
@Kearney While good intentions the SO community frowns on such edits you should engage in the meta discussions if you want to learn more about this. I personally find that it's a bit crazy sometimes. Please note that I had absolutely nothing to do with your edits getting shut down. The proper way though I think to add you edits is actually to repost everything as another answer. This way it's clear that you are the contributor on top of that I originally provided. It makes sense but the moderation tools don't really provide a good feedback mechanism.Briefing
It works... partially. Te reason I'm reading the solution is to resolve dependencies. However, the "Dependencies"-property remains empty, even after calling "ParseNestedProjects". Does anybody have any idea why?Trimaran
There's a new public class SolutionFile introduced in Microsoft.Build.dll that's installed with Visual Studio 2015 (see msdn.microsoft.com/en-us/library/…)Zoes
What is s_SolutionParser_configurations? Cant find it in Object cfgArray = s_SolutionParser_configurations.GetValue(solutionParser, null);Illaudable
@Illaudable that's a bug introduced by stackoverflow.com/users/697477/teynon. I cannot notify stackoverflow.com/users/697477/teynon but he should fix that in an answer of his own. I will revert the edited answer to a previously working state. I know people like to edit things but I'd much prefer if the code was left as is, editing questions to add features into code samples is not why we have an edit button. He should provide his changes as a separate answer to this question.Briefing
Also, @Illaudable look here msdn.microsoft.com/en-us/library/…Briefing
Thx @JohnLeidegren and I write the code without SolutionConfigtion.Illaudable
Thx @JohnLeidegren I use dte to get all the projects when I dev vsx.Illaudable
I
76

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!

Imp answered 29/9, 2015 at 11:29 Comment(11)
Very useful...this is what I used for powershell consumption... Add-Type -Path "C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll" $slnFile = [Microsoft.Build.Construction.SolutionFile]::Parse($slnPath); $slnFile.ProjectsInOrderUnpile
I couldn't find such a class in the Microsoft.Build.dll v4.0.0.0 shipped with Visual Studio 2017.Statolatry
@JeffG Try installing msbuild alone.Imp
@JeffG I am also using VS 2017. If I add Mircosoft.Build from the Assemblies tab in Add Reference, then I do not have access to SolutionFile. However, if I browse and reference the dll located in the folder above, then it does seem to work.Dorsoventral
I love all these solutions but how do i find the version of the .NET framework each project uses and a list of the references in each project?Lignocellulose
or which project is set to be the start up?Lignocellulose
@JeffG This package is now available on NuGet nuget.org/packages/Microsoft.BuildLintwhite
When I install the NuGet package, it just completely fails to parse the solution file. SolutionFile.Parse returns error: MSB0001: Internal MSBuild Error: "c:\source\MySolution.sln unexpectedly not a rooted path". When you install a NuGet package and use such a basic class, isn't it just supposed to work? From what I'm reading above, you have to have specific versions of the msbuild dll refeerenced and may even have to update an environment PATH variable. Insane.Provost
I am using Visual Studio 2022 and Microsoft.Build.Construction no longer seems to contain SolutionFile. Also C:\Program Files (x86)\Reference Assemblies\Microsoft doesn't contain a MSBuild folder. Is this solution no longer valid?Hydrastinine
@PierrevandeLaar SolutionFile class seems to still be there: learn.microsoft.com/en-us/dotnet/api/… . I just installed it in VS 2022 via Nuget package: Microsoft.Build (also discussed in an earlier comment). Excited/hoping to use it in a small CLI program (or library) to call from a different script to fix some build issues on a legacy .NET Framework project.Mitrewort
I just want to add that this library is working great for me in a simple .NET 8 program for parsing a .NET Framework solution file (which I guess shouldn't be surprising, since it's based on MSBuild, not on what language/framework is used).Mitrewort
E
16

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.

Eringo answered 14/12, 2011 at 17:8 Comment(1)
On the solution file I tested on, this slntools actually gave more details than the ReSharper libs.Blalock
B
15

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));
                }
            }
        }
    }
}
Burgh answered 28/5, 2014 at 17:51 Comment(1)
NOTE: As of this post the namespaces are a little different [perhaps JB refactored their stuff :) ]: ~JetBrains.Platform.ProjectModel ~JetBrains.Platform.Util ~JetBrains.Platform.Interop.WinApiOverset
C
10

I can't really offer you a library and my guess is there isn't one that exists out there. But I've spent a deal of time messing around with .sln files in batch editting scenarios and I've found Powershell to be a very useful tool for this task. The .SLN format is pretty simple and can be almost completely parsed with a few quick and dirty expressions. For Example

Included Project files.

gc ConsoleApplication30.sln | 
  ? { $_ -match "^Project" } | 
  %{ $_ -match ".*=(.*)$" | out-null ; $matches[1] } | 
  %{ $_.Split(",")[1].Trim().Trim('"') }

It's not always pretty, but it is an effective way to do batch processing.

Colitis answered 1/4, 2009 at 20:37 Comment(2)
Yeah, that's what I've been doing so farWrand
To exclude solution folders this works for me: (Get-Content MySolution.sln) | Where-Object { $_ -match '(?=^Project(?!\("\{2150E333-8FDC-42A3-9474-1A3956D46DE8\}"\)))^(\w+)' } | ForEach-Object { $_ -match ".*=(.*)$" | out-null ; $matches[1] } | ForEach-Object { $_.Split(",")[1].Trim().Trim('"') }Bounds
S
6

we solved a similar problem of merging solutions automatically by writing a Visual Studio plugin which created a new solution then searched for *.sln file and imported them into the new one using:

dte2.Solution.AddFromFile(solutionPath, false);

Our problem was slightly different in that we wanted VS to sort out the build order for us, so we then converted any dll references to project references where possible.

We then automated this into a build process by running VS via COM automation.

This solution was a little Heath Robinson, but had the advantage that VS was doing the editing so our code was not dependant on format of the sln file. Which was helpful when we moved from VS 2005 to 2008 and again to 2010.

Supernova answered 8/1, 2011 at 15:32 Comment(3)
Did you have an performance problems using DTE and if so how did you solve it? #1620699Coincidental
@mouters We found that using DTE and using the gui were about the same performance wise. Installing only minimal VS options helped a little, but not much. As this is automated and running overnight, performance is not something we have been concerned with.Supernova
If sln have the folder that include project ,you cant get the project that included in folder.Illaudable
F
5

Everything is great, but I wanted also to get sln generation capability - in code snapshot above you're only parsing .sln files - I wanted to make similar thing except to be able to re-generate sln with slight modifications back to .sln file. Such cases could be for example porting same project for different .NET platform. For now it's only sln re-generation, but later on I will expand it to projects as well.

I guess that I wanted also to demonstrate the power of regular expressions and native interfaces. (Smaller amount of code with more functionality)

Update 4.1.2017 I've created separate svn repository for parsing .sln solution: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Below is my own code sample snippet (predecessor). You're free to use any of them.

It's possible that in future svn based solution parsing code will be update with generation capabilities as well.

Update 4.2.2017 Source code in SVN is supporting .sln generation as well.

using System;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Text;


public class Program
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class SolutionProject
    {
        public string ParentProjectGuid;
        public string ProjectName;
        public string RelativePath;
        public string ProjectGuid;

        public string AsSlnString()
        { 
            return "Project(\"" + ParentProjectGuid + "\") = \"" + ProjectName + "\", \"" + RelativePath + "\", \"" + ProjectGuid + "\"";
        }
    }

/// <summary>
/// .sln loaded into class.
/// </summary>
public class Solution
{
    public List<object> slnLines;       // List of either String (line format is not intresting to us), or SolutionProject.

    /// <summary>
    /// Loads visual studio .sln solution
    /// </summary>
    /// <param name="solutionFileName"></param>
    /// <exception cref="System.IO.FileNotFoundException">The file specified in path was not found.</exception>
    public Solution( string solutionFileName )
    {
        slnLines = new List<object>();
        String slnTxt = File.ReadAllText(solutionFileName);
        string[] lines = slnTxt.Split('\n');
        //Match string like: Project("{66666666-7777-8888-9999-AAAAAAAAAAAA}") = "ProjectName", "projectpath.csproj", "{11111111-2222-3333-4444-555555555555}"
        Regex projMatcher = new Regex("Project\\(\"(?<ParentProjectGuid>{[A-F0-9-]+})\"\\) = \"(?<ProjectName>.*?)\", \"(?<RelativePath>.*?)\", \"(?<ProjectGuid>{[A-F0-9-]+})");

        Regex.Replace(slnTxt, "^(.*?)[\n\r]*$", new MatchEvaluator(m =>
            {
                String line = m.Groups[1].Value;

                Match m2 = projMatcher.Match(line);
                if (m2.Groups.Count < 2)
                {
                    slnLines.Add(line);
                    return "";
                }

                SolutionProject s = new SolutionProject();
                foreach (String g in projMatcher.GetGroupNames().Where(x => x != "0")) /* "0" - RegEx special kind of group */
                    s.GetType().GetField(g).SetValue(s, m2.Groups[g].ToString());

                slnLines.Add(s);
                return "";
            }), 
            RegexOptions.Multiline
        );
    }

    /// <summary>
    /// Gets list of sub-projects in solution.
    /// </summary>
    /// <param name="bGetAlsoFolders">true if get also sub-folders.</param>
    public List<SolutionProject> GetProjects( bool bGetAlsoFolders = false )
    {
        var q = slnLines.Where( x => x is SolutionProject ).Select( i => i as SolutionProject );

        if( !bGetAlsoFolders )  // Filter away folder names in solution.
            q = q.Where( x => x.RelativePath != x.ProjectName );

        return q.ToList();
    }

    /// <summary>
    /// Saves solution as file.
    /// </summary>
    public void SaveAs( String asFilename )
    {
        StringBuilder s = new StringBuilder();

        for( int i = 0; i < slnLines.Count; i++ )
        {
            if( slnLines[i] is String ) 
                s.Append(slnLines[i]);
            else
                s.Append((slnLines[i] as SolutionProject).AsSlnString() );

            if( i != slnLines.Count )
                s.AppendLine();
        }

        File.WriteAllText(asFilename, s.ToString());
    }
}


    static void Main()
    {
        String projectFile = @"yourown.sln";

        try
        {
            String outProjectFile = Path.Combine(Path.GetDirectoryName(projectFile), Path.GetFileNameWithoutExtension(projectFile) + "_2.sln");
            Solution s = new Solution(projectFile);
            foreach( var proj in s.GetProjects() )
            {
                Console.WriteLine( proj.RelativePath );
            }

            SolutionProject p = s.GetProjects().Where( x => x.ProjectName.Contains("Plugin") ).First();
            p.RelativePath = Path.Combine( Path.GetDirectoryName(p.RelativePath) , Path.GetFileNameWithoutExtension(p.RelativePath) + "_Variation" + ".csproj");

            s.SaveAs(outProjectFile);

        }
        catch (Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }
    }
}
Filaria answered 30/9, 2014 at 20:7 Comment(0)
F
3

I expounded, determined that the MSBuild classes can be used to manipulate the underlying structures. I will have further code on my web site later.

// VSSolution

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using AbstractX.Contracts;

namespace VSProvider
{
    public class VSSolution : IVSSolution
    {
        //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;
        private string solutionFileName;
        private List<VSProject> projects;

        public string Name
        {
            get
            {
                return Path.GetFileNameWithoutExtension(solutionFileName);
            }
        }

        public IEnumerable<IVSProject> Projects
        {
            get
            {
                return projects;
            }
        }

        static VSSolution()
        {
            s_SolutionParser = Type.GetType("Microsoft.Build.Construction.SolutionParser, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
            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 string SolutionPath
        {
            get
            {
                var file = new FileInfo(solutionFileName);

                return file.DirectoryName;
            }
        }

        public VSSolution(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);
            }

            this.solutionFileName = solutionFileName;

            projects = new List<VSProject>();
            var array = (Array)s_SolutionParser_projects.GetValue(solutionParser, null);

            for (int i = 0; i < array.Length; i++)
            {
                projects.Add(new VSProject(this, array.GetValue(i)));
            }
        }

        public void Dispose()
        {
        }
    }
}

// VSProject

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Xml;
using AbstractX.Contracts;
using System.Collections;

namespace VSProvider
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class VSProject : IVSProject
    {
        static readonly Type s_ProjectInSolution;
        static readonly Type s_RootElement;
        static readonly Type s_ProjectRootElement;
        static readonly Type s_ProjectRootElementCache;
        static readonly PropertyInfo s_ProjectInSolution_ProjectName;
        static readonly PropertyInfo s_ProjectInSolution_ProjectType;
        static readonly PropertyInfo s_ProjectInSolution_RelativePath;
        static readonly PropertyInfo s_ProjectInSolution_ProjectGuid;
        static readonly PropertyInfo s_ProjectRootElement_Items;

        private VSSolution solution;
        private string projectFileName;
        private object internalSolutionProject;
        private List<VSProjectItem> items;
        public string Name { get; private set; }
        public string ProjectType { get; private set; }
        public string RelativePath { get; private set; }
        public string ProjectGuid { get; private set; }

        public string FileName
        {
            get
            {
                return projectFileName;
            }
        }

        static VSProject()
        {
            s_ProjectInSolution = Type.GetType("Microsoft.Build.Construction.ProjectInSolution, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectInSolution_ProjectName = s_ProjectInSolution.GetProperty("ProjectName", BindingFlags.NonPublic | BindingFlags.Instance);
            s_ProjectInSolution_ProjectType = s_ProjectInSolution.GetProperty("ProjectType", 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_ProjectRootElement = Type.GetType("Microsoft.Build.Construction.ProjectRootElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);
            s_ProjectRootElementCache = Type.GetType("Microsoft.Build.Evaluation.ProjectRootElementCache, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectRootElement_Items = s_ProjectRootElement.GetProperty("Items", BindingFlags.Public | BindingFlags.Instance);
        }

        public IEnumerable<IVSProjectItem> Items
        {
            get
            {
                return items;
            }
        }

        public VSProject(VSSolution solution, object internalSolutionProject)
        {
            this.Name = s_ProjectInSolution_ProjectName.GetValue(internalSolutionProject, null) as string;
            this.ProjectType = s_ProjectInSolution_ProjectType.GetValue(internalSolutionProject, null).ToString();
            this.RelativePath = s_ProjectInSolution_RelativePath.GetValue(internalSolutionProject, null) as string;
            this.ProjectGuid = s_ProjectInSolution_ProjectGuid.GetValue(internalSolutionProject, null) as string;

            this.solution = solution;
            this.internalSolutionProject = internalSolutionProject;

            this.projectFileName = Path.Combine(solution.SolutionPath, this.RelativePath);

            items = new List<VSProjectItem>();

            if (this.ProjectType == "KnownToBeMSBuildFormat")
            {
                this.Parse();
            }
        }

        private void Parse()
        {
            var stream = File.OpenRead(projectFileName);
            var reader = XmlReader.Create(stream);
            var cache = s_ProjectRootElementCache.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { true });
            var rootElement = s_ProjectRootElement.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).First().Invoke(new object[] { reader, cache });

            stream.Close();

            var collection = (ICollection)s_ProjectRootElement_Items.GetValue(rootElement, null);

            foreach (var item in collection)
            {
                items.Add(new VSProjectItem(this, item));
            }

        }

        public IEnumerable<IVSProjectItem> EDMXModels
        {
            get 
            {
                return this.items.Where(i => i.ItemType == "EntityDeploy");
            }
        }

        public void Dispose()
        {
        }
    }
}

// VSProjectItem

using System;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.IO;
using System.Xml;
using AbstractX.Contracts;

namespace VSProvider
{
    [DebuggerDisplay("{ProjectName}, {RelativePath}, {ProjectGuid}")]
    public class VSProjectItem : IVSProjectItem
    {
        static readonly Type s_ProjectItemElement;
        static readonly PropertyInfo s_ProjectItemElement_ItemType;
        static readonly PropertyInfo s_ProjectItemElement_Include;

        private VSProject project;
        private object internalProjectItem;
        private string fileName;

        static VSProjectItem()
        {
            s_ProjectItemElement = Type.GetType("Microsoft.Build.Construction.ProjectItemElement, Microsoft.Build, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", false, false);

            s_ProjectItemElement_ItemType = s_ProjectItemElement.GetProperty("ItemType", BindingFlags.Public | BindingFlags.Instance);
            s_ProjectItemElement_Include = s_ProjectItemElement.GetProperty("Include", BindingFlags.Public | BindingFlags.Instance);
        }

        public string ItemType { get; private set; }
        public string Include { get; private set; }

        public VSProjectItem(VSProject project, object internalProjectItem)
        {
            this.ItemType = s_ProjectItemElement_ItemType.GetValue(internalProjectItem, null) as string;
            this.Include = s_ProjectItemElement_Include.GetValue(internalProjectItem, null) as string;
            this.project = project;
            this.internalProjectItem = internalProjectItem;

            // todo - expand this

            if (this.ItemType == "Compile" || this.ItemType == "EntityDeploy")
            {
                var file = new FileInfo(project.FileName);

                fileName = Path.Combine(file.DirectoryName, this.Include);
            }
        }

        public byte[] FileContents
        {
            get 
            {
                return File.ReadAllBytes(fileName);
            }
        }

        public string Name
        {
            get 
            {
                if (fileName != null)
                {
                    var file = new FileInfo(fileName);

                    return file.Name;
                }
                else
                {
                    return this.Include;
                }
            }
        }
    }
}
Fannie answered 16/11, 2011 at 2:59 Comment(2)
Any idea where AbstractX comes from?Instantaneity
This seems to misinterpret ASP.Net websites where the relativepath becomes the URL that the site should run in IISExpress etc under.Bankbook
U
1

Answer by @john-leidegren is great. For pre-VS2015, this is of great use. But there was a minor mistake, as the code to retrieve configurations was missing. So wanted to add it, in case someone is struggling to use this code.
The enhancement is very simple:

    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 readonly PropertyInfo s_SolutionParser_configurations;//this was missing in john's answer


    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);
            s_SolutionParser_configurations = s_SolutionParser.GetProperty("SolutionConfigurations", BindingFlags.NonPublic | BindingFlags.Instance); //this was missing in john's answer

            // additional info:
            var PropNameLst = GenHlp_PropBrowser.PropNamesOfType(s_SolutionParser);
            // the above call would yield something like this:
            // [ 0] "SolutionParserWarnings"        string
            // [ 1] "SolutionParserComments"        string
            // [ 2] "SolutionParserErrorCodes"      string
            // [ 3] "Version"                       string
            // [ 4] "ContainsWebProjects"           string
            // [ 5] "ContainsWebDeploymentProjects" string
            // [ 6] "ProjectsInOrder"               string
            // [ 7] "ProjectsByGuid"                string
            // [ 8] "SolutionFile"                  string
            // [ 9] "SolutionFileDirectory"         string
            // [10] "SolutionReader"                string
            // [11] "Projects"                      string
            // [12] "SolutionConfigurations"        string
        }
    }

    public List<SolutionProject> Projects { get; private set; }
    public List<SolutionConfiguration> Configurations { get; private set; }

   //...
   //...
   //... no change in the rest of the code
}

As additional help, providing simple code to browse through the properties of a System.Type as suggested by @oasten.

public class GenHlp_PropBrowser
{
    public static List<string> PropNamesOfClass(object anObj)
    {
        return anObj == null ? null : PropNamesOfType(anObj.GetType());
    }
    public static List<String> PropNamesOfType(System.Type aTyp)
    {
        List<string> retLst = new List<string>();
        foreach ( var p in aTyp.GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) )
        {
            retLst.Add(p.Name);
        }
        return retLst;
    }
}
Upside answered 14/7, 2016 at 14:29 Comment(0)
I
0

Thank @John Leidegren he offers an effective way. I write a hlper class for I cant use his code that cant find the s_SolutionParser_configurations and the projects without FullName.

The code is in github that can get the projects with the FullName.

And the code cant get SolutionConfiguration.

But when you dev a vsx the vs will say cant find Microsoft.Build.dll ,so you may try use dte to get all the projects.

The code that use dte to get all the projects is in github

Illaudable answered 9/2, 2017 at 2:55 Comment(1)
@NP83 Thank you and I deleted the linkIllaudable
R
0

For what its worth I have now created a little project to read sln and proj files available on nuget:

https://www.nuget.org/packages/ByteDev.DotNet/

Rahel answered 21/1, 2019 at 16:44 Comment(2)
nice work, but does not have the project source files?Inclinable
The project was primarily built for non-.NET Framework (.NET Core/Standard/.NET 5 etc.) projects which do not reference the source files explicitly from the project file. Instead they assume all the files are added to the project and the ones to ignore are specified instead.Rahel

© 2022 - 2024 — McMap. All rights reserved.