Analysieren von Visual Studio Solution-Dateien


109

Wie kann ich SLN-Dateien (Visual Studio Solution) in .NET analysieren? Ich möchte eine App schreiben, die mehrere Lösungen zu einer zusammenführt und gleichzeitig die relative Erstellungsreihenfolge speichert.

Antworten:


113

Die .NET 4.0-Version der Microsoft.Build-Assembly enthält eine SolutionParser-Klasse im Microsoft.Build.Construction-Namespace, die Visual Studio-Lösungsdateien analysiert.

Leider ist diese Klasse intern, aber ich habe einige dieser Funktionen in eine Klasse eingeschlossen, die Reflektion verwendet, um zu einigen allgemeinen Eigenschaften zu gelangen, die Sie möglicherweise hilfreich finden.

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

Beachten Sie, dass Sie Ihr Zielframework in ".NET Framework 4" (kein Clientprofil) ändern müssen, um die Microsoft.Build-Referenz zu Ihrem Projekt hinzufügen zu können.


3
Dies ist in Ordnung, zeigt jedoch "Lösungselemente" -Gruppen als "Projekte" an, was falsch ist.
Doug

3
Hier sind die "using" -Anweisungen zum Hinzufügen: using System; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq;
NealWalters

1
@Kiquenet - Sie können die Eigenschaft "ProjectType" des s_ProjectInSolution-Objekts auf dieselbe Weise wie die anderen offengelegten Eigenschaften untersuchen. Dies gibt eine Aufzählung zurück, aber nur ToString (). Ich habe versucht, den Beitrag so zu bearbeiten, dass er zweimal enthalten ist, wurde aber jedes Mal in Flammen niedergeschossen.
Oasten

3
@oasten Während die SO-Community solche Änderungen mit guten Absichten missbilligt, sollten Sie sich an den Metadiskussionen beteiligen, wenn Sie mehr darüber erfahren möchten. Ich persönlich finde es manchmal ein bisschen verrückt. Bitte beachten Sie, dass ich absolut nichts damit zu tun hatte, dass Ihre Änderungen beendet wurden. Der richtige Weg, obwohl ich denke, dass Sie Änderungen hinzufügen können, besteht darin, alles als eine andere Antwort neu zu veröffentlichen. Auf diese Weise ist klar, dass Sie der Mitwirkende sind, zusätzlich zu dem, was ich ursprünglich bereitgestellt habe. Es ist sinnvoll, aber die Moderationstools bieten keinen guten Feedback-Mechanismus.
John Leidegren

18
SolutionFileIn Microsoft.Build.dll wurde eine neue öffentliche Klasse eingeführt, die mit Visual Studio 2015 installiert wurde (siehe msdn.microsoft.com/en-us/library/… )
Phil

70

Mit Visual Studio 2015 gibt es jetzt eine öffentlich zugängliche SolutionFileKlasse, mit der Lösungsdateien analysiert werden können:

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

Diese Klasse befindet sich in der Assembly Microsoft.Build.dll 14.0.0.0 . In meinem Fall befand es sich bei:

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

Vielen Dank an Phil für diesen Hinweis !


1
Sehr nützlich ... das habe ich für den Powershell-Verbrauch verwendet ...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

2
Ich konnte eine solche Klasse in der Microsoft.Build.dll v4.0.0.0, die mit Visual Studio 2017 geliefert wurde, nicht finden.
Jeff G

@ JeffG Versuchen Sie, msbuild alleine zu installieren.
Maciej Kucia

1
@JeffG Ich verwende auch VS 2017. Wenn ich Mircosoft.Build über die Registerkarte Assemblies in Add Reference hinzufüge, habe ich keinen Zugriff auf SolutionFile. Wenn ich jedoch die DLL im obigen Ordner durchsuche und darauf verweise, scheint sie zu funktionieren.
Inrego

3
@ JeffG Dieses Paket ist jetzt auf NuGet nuget.org/packages/Microsoft.Build
Robert Hardy

16

Ich weiß nicht, ob noch jemand nach Lösungen für dieses Problem sucht, aber ich bin auf ein Projekt gestoßen, das genau das zu tun scheint, was benötigt wird. https://slntools.codeplex.com/ Eine der Funktionen dieses Tools besteht darin, mehrere Lösungen zusammenzuführen.


In der Lösungsdatei, auf der ich getestet habe, enthielten diese slntools tatsächlich mehr Details als die ReSharper-Bibliotheken.
Răzvan Flavius ​​Panda

14

JetBrains (die Entwickler von Resharper) verfügen in ihren Assemblys über öffentliche SLN-Analysefunktionen (keine Reflexion erforderlich). Es ist wahrscheinlich robuster als die hier vorgeschlagenen Open Source-Lösungen (geschweige denn die ReGex-Hacks). Alles was Sie tun müssen ist:

  • Laden Sie die ReSharper-Befehlszeilentools herunter (kostenlos).
  • Fügen Sie Folgendes als Referenz zu Ihrem Projekt hinzu
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

Die Bibliothek ist nicht dokumentiert, aber Reflector (oder dotPeek) ist Ihr Freund. Beispielsweise:

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

4
HINWEIS: Ab diesem Beitrag sind die Namespaces etwas anders [vielleicht hat JB ihre Sachen
überarbeitet

9

Ich kann Ihnen keine Bibliothek anbieten und ich vermute, dass es da draußen keine gibt. Aber ich habe viel Zeit damit verbracht, mit .sln-Dateien in Batch-Bearbeitungsszenarien herumzuspielen, und ich habe festgestellt, dass Powershell ein sehr nützliches Werkzeug für diese Aufgabe ist. Das .SLN-Format ist ziemlich einfach und kann mit ein paar schnellen und schmutzigen Ausdrücken fast vollständig analysiert werden. Beispielsweise

Enthaltene Projektdateien.

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

Es ist nicht immer schön, aber es ist eine effektive Möglichkeit, Stapelverarbeitung durchzuführen.


Ja, das habe ich bisher gemacht
Filip Frącz

Um Lösungsordner auszuschließen, funktioniert dies für mich: (Get-Content MySolution.sln) | Where-Object {$ _ -match '(? = ^ Project (?! \ ("\ {2150E333-8FDC-42A3-9474-1A3956D46DE8 \}" \)) ^ (\ w +)'} | ForEach-Object {$ _ -match ". * = (. *) $" | out-null; $ entspricht [1]} | ForEach-Object {$ _. Split (",") [1] .Trim (). Trim ('"')}
David Gardiner

6

Wir haben ein ähnliches Problem beim automatischen Zusammenführen von Lösungen gelöst, indem wir ein Visual Studio-Plugin geschrieben haben, das eine neue Lösung erstellt und dann nach der * .sln-Datei gesucht und diese in die neue importiert hat:

dte2.Solution.AddFromFile(solutionPath, false);

Unser Problem war insofern etwas anders, als wir wollten, dass VS die Erstellungsreihenfolge für uns sortiert. Deshalb haben wir alle DLL-Referenzen nach Möglichkeit in Projektreferenzen konvertiert.

Wir haben dies dann zu einem Build-Prozess automatisiert, indem wir VS über COM-Automatisierung ausgeführt haben.

Diese Lösung war ein wenig Heath Robinson, hatte aber den Vorteil, dass VS die Bearbeitung durchführte, sodass unser Code nicht vom Format der SLN-Datei abhängig war. Das war hilfreich, als wir von VS 2005 nach 2008 und wieder nach 2010 wechselten.


Hatten Sie Leistungsprobleme mit DTE und wenn ja, wie haben Sie diese gelöst? stackoverflow.com/questions/1620199/…
Chris Moutray

@mouters Wir haben festgestellt, dass die Verwendung von DTE und der GUI in Bezug auf die Leistung ungefähr gleich ist. Die Installation nur minimaler VS-Optionen hat ein wenig geholfen, aber nicht viel. Da dies automatisiert ist und über Nacht ausgeführt wird, haben wir uns nicht mit der Leistung befasst.
Andy Lowry

Wenn sln den Ordner hat, der das Projekt enthält, können Sie das Projekt, das im Ordner enthalten ist, nicht erhalten.
Lindexi

5

Alles ist großartig, aber ich wollte auch die Fähigkeit zur SLN-Generierung erhalten - im obigen Code-Snapshot analysieren Sie nur SLN-Dateien - ich wollte etwas Ähnliches machen, außer um SLN mit geringfügigen Änderungen wieder in SLN-Datei generieren zu können . In solchen Fällen kann beispielsweise dasselbe Projekt für eine andere .NET-Plattform portiert werden. Im Moment ist es nur eine Neugenerierung, aber später werde ich es auch auf Projekte ausweiten.

Ich denke, ich wollte auch die Leistungsfähigkeit regulärer Ausdrücke und nativer Schnittstellen demonstrieren. (Kleinere Codemenge mit mehr Funktionalität)

Update 4.1.2017 Ich habe ein separates SVN-Repository zum Parsen der SLN-Lösung erstellt: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Unten ist mein eigenes Codebeispiel-Snippet (Vorgänger). Sie können jeden von ihnen verwenden.

Es ist möglich, dass der Parsing-Code für SVN-basierte Lösungen in Zukunft auch mit Generierungsfunktionen aktualisiert wird.

Update 4.2.2017 Der Quellcode in SVN unterstützt auch die SLN-Generierung.

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

3

Ich erklärte, festgestellt, dass die MSBuild-Klassen verwendet werden können, um die zugrunde liegenden Strukturen zu manipulieren. Ich werde später weiteren Code auf meiner Website haben.

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

Irgendeine Idee, woher AbstractX kommt?
gunr2171

Dies scheint ASP.Net- Websites falsch zu interpretieren, bei denen die relativepathURL wird, unter der die Site in IISExpress usw. ausgeführt werden soll.
Doug

1

Antwort von @ John-Leidegren ist großartig. Für die Zeit vor VS2015 ist dies von großem Nutzen. Es gab jedoch einen kleinen Fehler, da der Code zum Abrufen von Konfigurationen fehlte. Ich wollte es also hinzufügen, falls jemand Schwierigkeiten hat, diesen Code zu verwenden.
Die Verbesserung ist sehr einfach:

    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
}

Als zusätzliche Hilfe bieten Sie einfachen Code zum Durchsuchen der Eigenschaften von a, System.Typewie von @oasten vorgeschlagen.

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


0

Vielen Dank an John Leidegren, er bietet einen effektiven Weg. Ich schreibe eine hlper-Klasse, weil ich seinen Code nicht verwenden kann, der die s_SolutionParser_configurationsund die Projekte ohne FullName nicht finden kann.

Der Code befindet sich in Github , der die Projekte mit dem vollständigen Namen abrufen kann.

Und der Code kann SolutionConfiguration nicht bekommen.

Aber wenn Sie ein vsx entwickeln, sagt das vs nicht finden Microsoft.Build.dll, also können Sie versuchen, dte zu verwenden, um alle Projekte zu erhalten.

Der Code, der dte verwendet, um alle Projekte abzurufen, befindet sich in github


@ NP83 Danke und ich habe den Link gelöscht
lindexi
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.