解析Visual Studio解决方案文件

Fil*_*ącz 107 .net c# parsing visual-studio

如何在.NET中解析Visual Studio解决方案(SLN)文件?我想编写一个应用程序,将多个解决方案合并为一个,同时保存相对构建顺序.

Joh*_*ren 112

.NET 4.0版本的Microsoft.Build程序集在Microsoft.Build.Construction命名空间中包含一个解析Visual Studio解决方案文件的SolutionParser类.

不幸的是这个类是内部的,但我在一个使用反射的类中包含了一些功能,以获得一些你可能会发现有用的常见属性.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,您必须将目标框架更改为".NET Framework 4"(而不是客户端配置文件)才能将Microsoft.Build引用添加到项目中.

  • 在Visual Studio 2015中安装的Microsoft.Build.dll中引入了一个新的公共类`SolutionFile`(参见https://msdn.microsoft.com/en-us/library/microsoft.build.construction.solutionfile(v=vs) 0.121)的.aspx) (14认同)
  • 以下是要添加的"using"语句:using System; 使用System.Reflection; 使用System.Collections.Generic; 使用System.Diagnostics; 使用System.IO; 使用System.Linq; (3认同)
  • @oasten虽然SO社区对这些编辑表示满意,但如果您想了解更多相关信息,则应参与元讨论.我个人觉得有时候有点疯狂.请注意,我完全没有关闭你的编辑.虽然我认为添加编辑的正确方法实际上是将所有内容重新发布为另一个答案.这样很明显,你是我最初提供的贡献者.这是有道理的,但审核工具并没有真正提供良好的反馈机制. (3认同)
  • 这很好,但它将"解决方案项目"组显示为"项目",这是不正确的. (2认同)

Mat*_*cia 62

使用Visual Studio 2015,现在有一个可公开访问的SolutionFile类,可用于解析解决方案文件:

using Microsoft.Build.Construction;
var _solutionFile = SolutionFile.Parse(path);
Run Code Online (Sandbox Code Playgroud)

此类位于Microsoft.Build.dll 14.0.0.0程序集中.就我而言,它位于:

C:\Program Files (x86)\Reference Assemblies\Microsoft\MSBuild\v14.0\Microsoft.Build.dll
Run Code Online (Sandbox Code Playgroud)

由于菲尔指出了这一点!

  • 非常有用...这就是我用于 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` (3认同)
  • 我在Visual Studio 2017附带的Microsoft.Build.dll v4.0.0.0中找不到这样的类. (2认同)
  • @JeffG现在可以在NuGet https://www.nuget.org/packages/Microsoft.Build/上获得此软件包。 (2认同)

Wir*_*Wiz 16

我不知道是否有人仍在寻找这个问题的解决方案,但我遇到了一个似乎只需要做的项目. https://slntools.codeplex.com/ 此工具的一个功能是将多个解决方案合并在一起.


Oha*_*der 14

JetBrains(Resharper的创造者)在他们的集合中具有公共 sln解析能力(不需要反射).它可能比这里建议的现有开源解决方案更强大(更不用说ReGex黑客攻击了).你需要做的就是:

  • 下载ReSharper命令行工具(免费).
  • 添加以下内容作为项目的引用
    • JetBrains.Platform.ProjectModel
    • JetBrains.Platform.Util
    • JetBrains.Platform.Interop.WinApi

图书馆没有记录,但Reflector(或者确实是dotPeek)是你的朋友.例如:

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));
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 注意:从这篇文章开始,名称空间有点不同[也许JB重构了他们的东西:)]:〜JetBrains.Platform.ProjectModel~JetBrains.Platform.Util~JetBrains.Platform.Interop.WinApi (4认同)

Jar*_*Par 8

我真的不能给你一个图书馆,我的猜测是那里没有一个存在.但是我花了很多时间在批处理编辑场景中乱搞.sln文件,我发现Powershell是一个非常有用的工具来完成这项任务..SLN格式非常简单,几乎可以用一些快速和脏的表达式完全解析.例如

包含的项目文件.

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

它并不总是很漂亮,但它是进行批处理的有效方法.


And*_*wry 6

我们通过编写Visual Studio插件解决了自动合并解决方案的类似问题,该插件创建了一个新的解决方案,然后搜索*.sln文件并使用以下方法将它们导入到新文件中:

dte2.Solution.AddFromFile(solutionPath, false);
Run Code Online (Sandbox Code Playgroud)

我们的问题略有不同,因为我们希望VS为我们整理构建顺序,因此我们在可能的情况下将任何dll引用转换为项目引用.

然后,我们通过COM自动化运行VS,将其自动化为构建过程.

这个解决方案是一个小希思罗宾逊,但有一个优势,VS正在进行编辑,所以我们的代码不依赖于sln文件的格式.当我们从VS 2005搬到2008年再到2010年时,这很有帮助.


Tar*_*aro 5

一切都很棒,但我也希望获得sln生成功能 - 在上面的代码快照中你只解析.sln文件 - 我想做类似的事情,除了能够通过稍微修改重新生成sln回到.sln文件.这种情况可以是例如为不同的.NET平台移植相同的项目.目前它只是sln重新生成,但后来我将它扩展到项目.

我想我还想展示正则表达式和本机接口的强大功能.(具有更多功能的更少代码)

更新4.1.2017 我已经创建了单独的svn存储库来解析.sln解决方案:https: //sourceforge.net/p/syncproj/code/HEAD/tree/

下面是我自己的代码示例代码段(前身).你可以自由使用其中任何一个.

未来基于svn的解决方案解析代码也可能会使用生成功能进行更新.

更新4.2.2017 SVN中的源代码也支持.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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)