Analizowanie plików rozwiązania Visual Studio

109

Jak analizować pliki rozwiązania Visual Studio (SLN) w .NET? Chciałbym napisać aplikację, która łączy wiele rozwiązań w jedno, jednocześnie zapisując względną kolejność kompilacji.

Filip Frącz
źródło

Odpowiedzi:

113

Wersja .NET 4.0 zestawu Microsoft.Build zawiera klasę SolutionParser w przestrzeni nazw Microsoft.Build.Construction, która analizuje pliki rozwiązań programu Visual Studio.

Niestety ta klasa jest wewnętrzna, ale zawarłem część tej funkcji w klasie, która używa odbicia, aby uzyskać dostęp do niektórych typowych właściwości, które mogą okazać się pomocne.

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

Zauważ, że musisz zmienić platformę docelową na „.NET Framework 4” (nie profil klienta), aby móc dodać odwołanie Microsoft.Build do projektu.

John Leidegren
źródło
3
Jest to w porządku, ale pokazuje grupy „Rozwiązania” jako „Projekty”, co jest niepoprawne.
Doug
3
Oto instrukcje „using” do dodania: using System; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq;
NealWalters
1
@Kiquenet - możesz zbadać właściwość „ProjectType” obiektu s_ProjectInSolution w taki sam sposób, jak inne udostępnione właściwości. Zwraca to wyliczenie, ale tylko ToString (). Próbowałem zredagować post, aby to uwzględnić dwukrotnie, ale za każdym razem zostałem zestrzelony w płomieniach.
oasten
3
@oasten Mimo dobrych intencji, społeczność SO marszczy brwi z powodu takich zmian, powinieneś zaangażować się w dyskusje na temat meta, jeśli chcesz dowiedzieć się więcej na ten temat. Osobiście uważam, że czasami jest to trochę szalone. Pamiętaj, że nie miałem absolutnie nic wspólnego z zamykaniem twoich edycji. Myślę, że właściwym sposobem dodawania zmian jest w rzeczywistości ponowne opublikowanie wszystkiego jako kolejna odpowiedź. W ten sposób jasne jest, że oprócz tego, co pierwotnie zapewniłem, jesteś współtwórcą. Ma to sens, ale narzędzia moderacyjne nie zapewniają dobrego mechanizmu informacji zwrotnej.
John Leidegren,
18
W SolutionFileMicrosoft.Build.dll wprowadzono nową klasę publiczną, która jest instalowana z programem Visual Studio 2015 (patrz msdn.microsoft.com/en-us/library/ ... )
Phil,
70

W programie Visual Studio 2015 istnieje teraz publicznie dostępna SolutionFileklasa, której można użyć do analizowania plików rozwiązań:

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

Ta klasa znajduje się w zestawie Microsoft.Build.dll 14.0.0.0 . W moim przypadku znajdował się pod adresem:

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

Dzięki Philowi za wskazanie tego !

Maciej Kucia
źródło
1
Bardzo przydatne ... to jest to, czego użyłem do konsumpcji PowerShell ...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
Nie mogłem znaleźć takiej klasy w Microsoft.Build.dll v4.0.0.0 dostarczanym z Visual Studio 2017.
Jeff G
@JeffG Spróbuj zainstalować samodzielnie msbuild.
Maciej Kucia
1
@JeffG Używam również VS 2017. Jeśli dodam Mircosoft.Build z zakładki Assemblies w Add Reference, to nie mam dostępu do SolutionFile. Jeśli jednak przeglądam i odwołuję się do biblioteki dll znajdującej się w powyższym folderze, wydaje się, że działa.
Inrego
3
@JeffG Ten pakiet jest już dostępny na NuGet nuget.org/packages/Microsoft.Build
Robert Hardy
16

Nie wiem, czy ktoś nadal szuka rozwiązania tego problemu, ale trafiłem na projekt, który wydaje się robić dokładnie to, co jest potrzebne. https://slntools.codeplex.com/ Jedną z funkcji tego narzędzia jest łączenie wielu rozwiązań.

WiredWiz
źródło
W pliku rozwiązania, na którym testowałem, to slntools faktycznie podało więcej szczegółów niż biblioteki ReSharper.
Răzvan Flavius ​​Panda
14

JetBrains (twórcy Resharper) mają publiczne możliwości parsowania sln w swoich zespołach (nie jest wymagana refleksja). Jest prawdopodobnie bardziej wytrzymały niż sugerowane tutaj istniejące rozwiązania open source (nie mówiąc już o hackach ReGex). Wszystko, co musisz zrobić, to:

  • Pobierz narzędzia wiersza poleceń ReSharper (bezpłatnie).
  • Dodaj następujące informacje jako odwołania do projektu
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

Biblioteka nie jest udokumentowana, ale Reflector (a właściwie dotPeek) jest Twoim przyjacielem. Na przykład:

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));
                }
            }
        }
    }
}
Ohad Schneider
źródło
4
UWAGA: W tym poście przestrzenie nazw są trochę inne [być może JB zrefaktorowało ich rzeczy :)]: ~ JetBrains.Platform.ProjectModel ~ JetBrains.Platform.Util ~ JetBrains.Platform.Interop.WinApi
lewiSnort
9

Naprawdę nie mogę ci zaoferować biblioteki i przypuszczam, że nie ma takiej, która tam istnieje. Ale spędziłem sporo czasu na zabawie z plikami .sln w scenariuszach edycji wsadowej i odkryłem, że Powershell jest bardzo przydatnym narzędziem do tego zadania. Format .SLN jest dość prosty i można go prawie całkowicie przeanalizować za pomocą kilku szybkich i brudnych wyrażeń. Na przykład

Dołączone pliki projektu.

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

Nie zawsze jest ładny, ale jest skutecznym sposobem przetwarzania wsadowego.

JaredPar
źródło
Tak, to właśnie robiłem do tej pory
Filip Frącz
Aby wykluczyć foldery rozwiązań, działa to dla mnie: (Get-Content MySolution.sln) | Where-Object {$ _ -match '(? = ^ Project (?! \ ("\ {2150E333-8FDC-42A3-9474-1A3956D46DE8 \}" \))) ^ (\ w +)'} | ForEach-Object {$ _ -match ". * = (. *) $" | out-null; $ dopasowania [1]} | ForEach-Object {$ _. Split (",") [1] .Trim (). Trim ('"')}
David Gardiner,
6

rozwiązaliśmy podobny problem automatycznego scalania rozwiązań, pisząc wtyczkę Visual Studio, która utworzyła nowe rozwiązanie, a następnie wyszukała plik * .sln i zaimportowała je do nowego za pomocą:

dte2.Solution.AddFromFile(solutionPath, false);

Nasz problem był nieco inny, ponieważ chcieliśmy, aby VS uporządkował dla nas kolejność kompilacji, więc wtedy, gdy było to możliwe, przekonwertowaliśmy wszelkie odwołania dll na odwołania do projektu.

Następnie zautomatyzowaliśmy to w procesie kompilacji, uruchamiając VS za pośrednictwem automatyzacji COM.

To rozwiązanie było trochę Heath Robinson, ale miało tę zaletę, że VS robił edycję, więc nasz kod nie był zależny od formatu pliku sln. Co było pomocne, gdy przeszliśmy z VS 2005 na 2008 i ponownie na 2010.

Andy Lowry
źródło
Czy podczas korzystania z DTE wystąpiły problemy z wydajnością, a jeśli tak, jak je rozwiązałeś? stackoverflow.com/questions/1620199/…
Chris Moutray
@mouters Okazało się, że używanie DTE i GUI zapewniało mniej więcej taką samą wydajność. Zainstalowanie tylko minimalnych opcji VS pomogło trochę, ale niewiele. Ponieważ jest to zautomatyzowane i działa z dnia na dzień, wydajność nie jest czymś, o co się martwiliśmy.
Andy Lowry,
Jeśli sln ma folder zawierający projekt, nie można uzyskać projektu zawartego w folderze.
lindexi
5

Wszystko jest świetne, ale chciałem również uzyskać możliwość generowania sln - w migawce kodu powyżej analizujesz tylko pliki .sln - chciałem zrobić podobną rzecz, z wyjątkiem możliwości ponownego wygenerowania sln z niewielkimi modyfikacjami z powrotem do pliku .sln . Takimi przypadkami może być na przykład przeniesienie tego samego projektu na inną platformę .NET. Na razie to tylko re-generacja sln, ale później rozszerzę to również na projekty.

Wydaje mi się, że chciałem także zademonstrować potęgę wyrażeń regularnych i natywnych interfejsów. (Mniejsza ilość kodu z większą funkcjonalnością)

Aktualizacja 4.1.2017 Utworzyłem osobne repozytorium svn do parsowania rozwiązania .sln: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Poniżej znajduje się mój własny przykładowy fragment kodu (poprzednik). Możesz używać dowolnego z nich.

Możliwe, że w przyszłości kod parsowania rozwiązania opartego na svn zostanie zaktualizowany z możliwościami generowania.

Aktualizacja 4.2.2017 Kod źródłowy w SVN obsługuje również generowanie .sln .

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);
        }
    }
}
TarmoPikaro
źródło
3

Wyjaśniłem, ustaliłem, że klasy MSBuild mogą być używane do manipulowania podstawowymi strukturami. Dalszy kod będę miał później na mojej stronie internetowej.

// 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;
                }
            }
        }
    }
}
Rozpoznać
źródło
Masz jakiś pomysł, skąd pochodzi AbstractX?
gunr2171
Wydaje się, że błędnie interpretuje to witryny ASP.Net, w których relativepathstaje się adresem URL, pod którym witryna powinna działać w IISExpress itp.
Doug
1

Odpowiedź @ john-leidegren jest świetna. W przypadku wersji przed VS2015 jest to bardzo przydatne. Ale był drobny błąd, ponieważ brakowało kodu do pobierania konfiguracji. Więc chciałem to dodać, na wypadek, gdyby ktoś miał problemy z użyciem tego kodu.
Ulepszenie jest bardzo proste:

    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
}

Dodatkową pomocą jest zapewnienie prostego kodu do przeglądania właściwości a, System.Typezgodnie z sugestią @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;
    }
}
elimad
źródło
0

Dzięki @John Leidegren oferuje skuteczny sposób. Piszę klasę hlper, ponieważ nie mogę użyć jego kodu, który nie może znaleźć plikus_SolutionParser_configurations projektów i projektów bez FullName.

Kod jest w githubie który może pobrać projekty z FullName.

A kod nie może uzyskać SolutionConfiguration.

Ale kiedy stworzysz vsx, vs powie, że nie można znaleźć Microsoft.Build.dll , więc możesz spróbować użyć dte, aby uzyskać wszystkie projekty.

Kod, który używa dte do pobierania wszystkich projektów, znajduje się na github

lindexi
źródło
@ NP83 Dziękuję i
usunąłem