Analisando arquivos de solução do Visual Studio

109

Como posso analisar arquivos de solução do Visual Studio (SLN) no .NET? Eu gostaria de escrever um aplicativo que mesclasse várias soluções em uma enquanto salvava a ordem de construção relativa.

Filip Frącz
fonte

Respostas:

113

A versão .NET 4.0 do assembly Microsoft.Build contém uma classe SolutionParser no namespace Microsoft.Build.Construction que analisa os arquivos de solução do Visual Studio.

Infelizmente, essa classe é interna, mas envolvi parte dessa funcionalidade em uma classe que usa reflexão para obter algumas propriedades comuns que podem ser úteis.

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

Observe que você precisa alterar sua estrutura de destino para ".NET Framework 4" (não o perfil do cliente) para poder adicionar a referência Microsoft.Build ao seu projeto.

John Leidegren
fonte
3
Isso é bom, mas mostra os grupos de "Itens de solução" como "Projetos", o que está incorreto.
Doug
3
Aqui estão as instruções "usando" para adicionar: using System; using System.Reflection; using System.Collections.Generic; using System.Diagnostics; using System.IO; usando System.Linq;
NealWalters
1
@Kiquenet - você pode examinar a propriedade "ProjectType" do objeto s_ProjectInSolution da mesma forma que as outras propriedades expostas. Isso retorna um enum, mas apenas ToString (). Tentei editar o post para incluir isso duas vezes, mas fui atingido em chamas todas as vezes.
oasten
3
@oasten Apesar de boas intenções, a comunidade SO desaprova tais edições, você deve se envolver nas metodiscussões se quiser aprender mais sobre isso. Eu pessoalmente acho que às vezes é um pouco louco. Observe que eu não tive absolutamente nada a ver com o encerramento de suas edições. Penso que a maneira adequada de adicionar edições é, na verdade, repassar tudo como outra resposta. Dessa forma, fica claro que você é o contribuidor além do que eu forneci originalmente. Faz sentido, mas as ferramentas de moderação não fornecem um bom mecanismo de feedback.
John Leidegren
18
Há uma nova classe pública SolutionFileintroduzida no Microsoft.Build.dll que é instalado com o Visual Studio 2015 (consulte msdn.microsoft.com/en-us/library/… )
Phil
70

Com o Visual Studio 2015, agora há uma SolutionFileclasse acessível publicamente que pode ser usada para analisar arquivos de solução:

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

Essa classe é encontrada no assembly Microsoft.Build.dll 14.0.0.0 . No meu caso, estava localizado em:

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

Obrigado a Phil por apontar isso !

Maciej Kucia
fonte
1
Muito útil ... isso é o que eu usei para consumo de 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
Não consegui encontrar essa classe no Microsoft.Build.dll v4.0.0.0 enviado com o Visual Studio 2017.
Jeff G
@JeffG Tente instalar o msbuild sozinho.
Maciej Kucia
1
@JeffG Também estou usando o VS 2017. Se eu adicionar Mircosoft.Build a partir da guia Assemblies em Add Reference, então não terei acesso ao SolutionFile. No entanto, se eu navegar e fizer referência à dll localizada na pasta acima, ela parece funcionar.
Inrego
3
@JeffG Este pacote agora está disponível no NuGet nuget.org/packages/Microsoft.Build
Robert Hardy
16

Não sei se alguém ainda está procurando soluções para esse problema, mas me deparei com um projeto que parece fazer exatamente o que é necessário. https://slntools.codeplex.com/ Uma das funções dessa ferramenta é mesclar várias soluções.

WiredWiz
fonte
No arquivo de solução que testei, este slntools realmente forneceu mais detalhes do que as bibliotecas ReSharper.
Răzvan Flavius ​​Panda
14

JetBrains (os criadores do Resharper) têm habilidades de análise sintática pública em seus conjuntos (sem necessidade de reflexão). É provavelmente mais robusto do que as soluções de código aberto existentes sugeridas aqui (sem falar dos hacks ReGex). Tudo que você precisa fazer é:

  • Baixe as ferramentas de linha de comando ReSharper (grátis).
  • Adicione o seguinte como referência ao seu projeto
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

A biblioteca não está documentada, mas o Reflector (ou, na verdade, dotPeek) é seu amigo. Por exemplo:

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
fonte
4
NOTA: A partir desta postagem, os namespaces são um pouco diferentes [talvez JB tenha refatorado suas coisas :)]: ~ JetBrains.Platform.ProjectModel ~ JetBrains.Platform.Util ~ JetBrains.Platform.Interop.WinApi
lewiSnort
9

Eu realmente não posso oferecer uma biblioteca e meu palpite é que não existe nenhuma lá fora. Mas eu passei muito tempo mexendo em arquivos .sln em cenários de edição em lote e descobri que o Powershell é uma ferramenta muito útil para essa tarefa. O formato .SLN é bastante simples e pode ser quase completamente analisado com algumas expressões rápidas e sujas. Por exemplo

Arquivos de projeto incluídos.

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

Nem sempre é bonito, mas é uma maneira eficaz de fazer processamento em lote.

JaredPar
fonte
Sim, é isso que tenho feito até agora
Filip Frącz
Para excluir pastas de solução, isso funciona para mim: (Get-Content MySolution.sln) | Where-Object {$ _ -match '(? = ^ Project (?! \ ("\ {2150E333-8FDC-42A3-9474-1A3956D46DE8 \}" \))) ^ (\ w +)'} | ForEach-Object {$ _ -match ". * = (. *) $" | out-null; $ corresponde a [1]} | ForEach-Object {$ _. Split (",") [1] .Trim (). Trim ('"')}
David Gardiner
6

resolvemos um problema semelhante de mesclar soluções automaticamente escrevendo um plug-in do Visual Studio que criou uma nova solução, em seguida, procurou o arquivo * .sln e os importou para o novo usando:

dte2.Solution.AddFromFile(solutionPath, false);

Nosso problema era um pouco diferente, pois queríamos que o VS classificasse a ordem de construção para nós, então convertemos todas as referências dll em referências de projeto sempre que possível.

Em seguida, automatizamos isso em um processo de construção executando o VS via automação COM.

Essa solução era um pouco Heath Robinson, mas tinha a vantagem de que o VS estava fazendo a edição, então nosso código não dependia do formato do arquivo sln. O que foi útil quando mudamos do VS 2005 para 2008 e novamente para 2010.

Andy Lowry
fonte
Você teve problemas de desempenho ao usar o DTE e, em caso afirmativo, como resolvê-los? stackoverflow.com/questions/1620199/…
Chris Moutray
@mouters Descobrimos que usar DTE e usar a interface gráfica do usuário têm praticamente o mesmo desempenho. Instalar apenas opções mínimas do VS ajudou um pouco, mas não muito. Como isso é automatizado e executado durante a noite, o desempenho não é algo com que nos preocupamos.
Andy Lowry
Se sln tiver a pasta que inclui o projeto, você não poderá obter o projeto que está incluído na pasta.
lindexi
5

Tudo está ótimo, mas eu queria também obter capacidade de geração de sln - no instantâneo do código acima, você está apenas analisando arquivos .sln - eu queria fazer algo semelhante, exceto para poder regenerar o sln com pequenas modificações no arquivo .sln . Esses casos podem ser, por exemplo, portar o mesmo projeto para uma plataforma .NET diferente. Por enquanto, é apenas a regeneração do sln, mas mais tarde irei expandi-lo para projetos também.

Acho que também queria demonstrar o poder das expressões regulares e das interfaces nativas. (Menor quantidade de código com mais funcionalidade)

Atualização 4.1.2017 Criei um repositório svn separado para analisar a solução .sln: https://sourceforge.net/p/syncproj/code/HEAD/tree/

Abaixo está meu próprio trecho de amostra de código (predecessor). Você está livre para usar qualquer um deles.

É possível que, no futuro, o código de análise de solução baseada em svn seja atualizado com recursos de geração também.

Atualização 4.2.2017 O código-fonte no SVN também oferece suporte à geração de .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
fonte
3

Expus, determinei que as classes MSBuild podem ser usadas para manipular as estruturas subjacentes. Terei mais código em meu site mais tarde.

// 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;
                }
            }
        }
    }
}
Ken
fonte
Alguma ideia de onde vem o AbstractX?
gunr2171
Isso parece interpretar mal os sites ASP.Net em que relativepathse torna a URL que o site deve executar no IISExpress etc.
Doug
1

A resposta de @ john-leidegren é ótima. Para pré-VS2015, isso é de grande utilidade. Mas houve um pequeno erro, pois o código para recuperar as configurações estava faltando. Então, queria adicioná-lo, caso alguém esteja com dificuldades para usar esse código.
O aprimoramento é muito simples:

    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
}

Como ajuda adicional, fornecer código simples para navegar pelas propriedades de a System.Typeconforme sugerido por @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
fonte
0

Obrigado @John Leidegren, ele oferece uma maneira eficaz. Eu escrevo uma classe de hlper porque não posso usar o código dele que não consigo encontrar os_SolutionParser_configurations os projetos sem FullName.

O código está no github que pode obter os projetos com o FullName.

E o código não pode obter SolutionConfiguration.

Mas quando você desenvolve um vsx o vs dirá não consigo encontrar Microsoft.Build.dll , então você pode tentar usar dte para obter todos os projetos.

O código que usa dte para obter todos os projetos está no github

Lindexi
fonte
@ NP83 Obrigado e eu deletei o link
lindexi