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.
11 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.

- 59,920
- 20
- 131
- 152
-
Is there something like this for MSBuild 3.5 \ Visual Studio 2008? – Rami A. Jun 26 '11 at 18:36
-
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. – John Leidegren Jun 27 '11 at 06:59
-
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. – Oskar Jan 26 '12 at 17:36
-
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? – John Leidegren Jan 26 '12 at 17:41
-
3This is fine, however it shows "Solution Items" groups as "Projects" which is incorrect. – Doug Sep 23 '12 at 08:16
-
3Here are the "using" statements to add: using System; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; – NealWalters Dec 20 '12 at 15:37
-
@Doug any fix about it? – Kiquenet Apr 08 '14 at 10:04
-
1@Kiquenet - 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. – oasten Jul 28 '14 at 13:39
-
3@oasten 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. – John Leidegren Jul 28 '14 at 14:59
-
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? – realbart May 26 '15 at 16:13
-
20There's a new public class `SolutionFile` introduced in Microsoft.Build.dll that's installed with Visual Studio 2015 (see https://msdn.microsoft.com/en-us/library/microsoft.build.construction.solutionfile(v=vs.121).aspx) – Phil Jul 09 '15 at 10:22
-
What is `s_SolutionParser_configurations`? Cant find it in `Object cfgArray = s_SolutionParser_configurations.GetValue(solutionParser, null);` – lindexi Feb 09 '17 at 01:59
-
@lindexi that's a bug introduced by http://stackoverflow.com/users/697477/teynon. I cannot notify http://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. – John Leidegren Feb 09 '17 at 08:50
-
Also, @lindexi look here https://msdn.microsoft.com/en-us/library/microsoft.build.construction.solutionfile(v=vs.121).aspx – John Leidegren Feb 09 '17 at 08:53
-
Thx @JohnLeidegren and I write the code without SolutionConfigtion. – lindexi Feb 09 '17 at 09:09
-
Thx @JohnLeidegren I use dte to get all the projects when I dev vsx. – lindexi Feb 09 '17 at 09:18
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!

- 1
- 1

- 1,026
- 12
- 17
-
1Very 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.ProjectsInOrder` – SliverNinja - MSFT Dec 09 '16 at 21:03
-
2I couldn't find such a class in the Microsoft.Build.dll v4.0.0.0 shipped with Visual Studio 2017. – Jeff G Aug 03 '17 at 21:27
-
-
1@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. – Inrego Aug 24 '17 at 18:12
-
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? – Mike Murphy Dec 05 '17 at 22:01
-
-
5@JeffG This package is now available on NuGet https://www.nuget.org/packages/Microsoft.Build/ – Robert Hardy Oct 24 '18 at 20:25
-
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. – Triynko Mar 17 '20 at 04:42
-
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? – Pierre van de Laar Jul 13 '23 at 16:52
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.

- 646
- 8
- 18
-
On the solution file I tested on, this slntools actually gave more details than the ReSharper libs. – Răzvan Flavius Panda Nov 20 '14 at 09:02
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));
}
}
}
}
}

- 36,600
- 15
- 168
- 198
-
5NOTE: As of this post the namespaces are a little different [perhaps JB refactored their stuff :) ]: ~JetBrains.Platform.ProjectModel ~JetBrains.Platform.Util ~JetBrains.Platform.Interop.WinApi – lewiSnort May 15 '15 at 21:12
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.

- 733,204
- 149
- 1,241
- 1,454
-
-
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('"') } – David Gardiner Jul 19 '13 at 08:19
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.

- 1,767
- 13
- 12
-
Did you have an performance problems using DTE and if so how did you solve it? http://stackoverflow.com/questions/1620199/manipulation-of-visual-studio-2008-solution-and-project-files – Chris Moutray Feb 10 '11 at 10:15
-
@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. – Andy Lowry Feb 10 '11 at 14:24
-
If sln have the folder that include project ,you cant get the project that included in folder. – lindexi Feb 09 '17 at 01:54
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);
}
}
}

- 4,723
- 2
- 50
- 62
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;
}
}
}
}
}

- 31
- 1
-
-
This seems to misinterpret ASP.Net *websites* where the `relativepath` becomes the URL that the site should run in IISExpress etc under. – Doug May 13 '14 at 13:27
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;
}
}
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

- 4,182
- 3
- 19
- 65
For what its worth I have now created a little project to read sln and proj files available on nuget:

- 8,252
- 4
- 48
- 56
-
-
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. – bytedev Oct 11 '21 at 01:06