专栏首页林德熙的博客dotnet Roslyn 通过读取 suo 文件获取解决方案的启动项目

dotnet Roslyn 通过读取 suo 文件获取解决方案的启动项目

本文来告诉大家一个黑科技,通过 .suo 文件读取 VisualStudio 的启动项目。在 sln 项目里面,都会生成对应的 suo 文件,这个文件是 OLE 格式的文件,文件的格式没有公开,本文的方法适合用在 VisualStudio 2019 上,对于其他版本的 VisualStudio 也许会不适合

感谢 Simon Cropp 大佬提供的方法

默认在 sln 解决方案文件的相同文件夹里面,将会存放 .vs\{解决方案名}\v{VS版本}\.suo 文件,如解决方案文件名为 HairhechallchujurKairbilairlem.sln 在 VisualStudio 2019 下将会存放 .vs\HairhechallchujurKairbilairlem\v16\.suo 文件

这个 .suo 文件是包含了 VisualStudio 解决方案的一些配置,如启动项目。关多关于此文件,请参阅 Solution User Options (.Suo) File 文档

预计这个 suo 格式文件基本不会更改,在 1995 年的时候就开始使用这个格式

读取 .suo 需要使用到 Open MCDF 库。这是一个完全由 C# 实现的读取 OLE 格式文档的库,我在做 OFFICE 组件也用到这个库

在 suo 文件里面,通过 SolutionConfiguration 内容存放当前的启动项,这里面的内容是使用 UTF-16 编码的字符串,读取的方法如下

            using (var fileStream = new FileStream(suoFilePath, FileMode.Open))
            {
                using CompoundFile compoundFile = new CompoundFile(fileStream, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors);
                var cfStream = compoundFile.RootStorage.GetStream("SolutionConfiguration");
                var byteList = cfStream.GetData();
                var encoding = Encoding.GetEncodings()
                    .Single(x => string.Equals(x.Name, "utf-16", StringComparison.OrdinalIgnoreCase));
                var text = encoding.GetEncoding().GetString(byteList);
            }

这里的 text 的内容大概如下

"\u0011\0MultiStartupProj\0=\u0003\0\0;4\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.dwStartupOpt\0=\u0003\0\0;\u000f\0StartupProject\0=\b&\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B};A\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.Release|Any CPU.fBatchBld\0=\u0003\0\0;?\0{45171CDC-EDAC-4D0B-BDF8-63DE2D4F947B}.Debug|Any CPU.fBatchBld\0=\u0003\0\0;4\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.dwStartupOpt\0=\u0003\0\0;A\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.Release|Any CPU.fBatchBld\0=\u0003\0\0;?\0{AE3577E5-5D4E-44F8-B181-88A31B92584A}.Debug|Any CPU.fBatchBld\0=\u0003\0\0;4\0{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}.dwStartupOpt\0=\u0003\0\0;\n\0ActiveCfg\0=\b\r\0Debug|Any CPU;"

通过读取 StartupProject 后续的内容即可找到当前的启动项目的 GUID 值,以下是我写的正则

                var text = encoding.GetEncoding().GetString(byteList);

                const char nul = '\u0000';
                const char dc1 = '\u0011';
                const char etx = '\u0003';
                const char soh = '\u0001';

                var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
                var startupProjectMatch = startupProjectRegex.Match(text);
                if (startupProjectMatch.Success)
                {
                    var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
                }

上面代码拿到的 guid 就是启动项目的 guid 内容

咱可以采用 Simon Cropp 大佬的开源项目 https://github.com/SimonCropp/SetStartupProjects 来辅助读取当前 sln 里面包含的 csproj 的 GUID 和路径

代码如下

var projectList = SetStartupProjects.SolutionProjectExtractor.GetAllProjectFiles(solutionFile.FullName).ToList();

通过 guid 获取当前的 csproj 项目文件路径方法如下

                    var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
                    var project = projectList.FirstOrDefault(temp => new Guid(temp.Guid) == guid);

我封装了方法,传入的是 sln 文件,返回启动项目的路径

        private static FileInfo GetStartupProject(FileInfo solutionFile)
        {
            var solutionFilePath = solutionFile.FullName;
            var solutionDirectory = solutionFile.DirectoryName;

            var solutionName = Path.GetFileNameWithoutExtension(solutionFilePath);
            var suoDirectoryPath = Path.Combine(solutionDirectory, ".vs", solutionName, "v16");
            Directory.CreateDirectory(suoDirectoryPath);
            var suoFilePath = Path.Combine(suoDirectoryPath, ".suo");

            var projectList = SetStartupProjects.SolutionProjectExtractor.GetAllProjectFiles(solutionFile.FullName).ToList();
            using (var fileStream = new FileStream(suoFilePath, FileMode.Open))
            {
                using CompoundFile compoundFile = new CompoundFile(fileStream, CFSUpdateMode.ReadOnly, CFSConfiguration.SectorRecycle | CFSConfiguration.EraseFreeSectors);
                var cfStream = compoundFile.RootStorage.GetStream("SolutionConfiguration");
                var byteList = cfStream.GetData();
                var encoding = Encoding.GetEncodings()
                    .Single(x => string.Equals(x.Name, "utf-16", StringComparison.OrdinalIgnoreCase));
                var text = encoding.GetEncoding().GetString(byteList);

                const char nul = '\u0000';
                const char dc1 = '\u0011';
                const char etx = '\u0003';
                const char soh = '\u0001';

                var startupProjectRegex = new Regex(@$"StartupProject{nul}={'\b'}&{nul}(.{'{'}{38}{'}'});A");
                var startupProjectMatch = startupProjectRegex.Match(text);
                if (startupProjectMatch.Success)
                {
                    var guid = Guid.Parse(startupProjectMatch.Groups[1].Value);
                    var project = projectList.FirstOrDefault(temp => new Guid(temp.Guid) == guid);
                    return new FileInfo(project.FullPath);
                }
            }

            return null;
        }

需要先在项目安装 SetStartupProjects 库,才能使用这个方法

本文所有代码放在 githubgitee 欢迎小伙伴访问

除了读取启动项目,还可以读取断点等内容,读取 suo 里面的所有内容的方法如下

                compoundFile.RootStorage.VisitEntries(item =>
                {
                    if (item.IsStream)
                    {
                        Console.WriteLine(item.Name);

                        var stream = item as CFStream;
                        byteList = stream.GetData();
                        text = encoding.GetEncoding().GetString(byteList);
                    }
                }, true);

当然了,获取到的内容不一定使用 UTF-16 编码格式,还需要自己尝试,里面的数据只是二进制而是,上面代码的转换字符串只是用来调试

更多请看

SimonCropp/SetStartupProjects: Setting Visual Studio startup projects by hacking the suo

Solution User Options (.Suo) File

更多编译相关请看手把手教你写 Roslyn 修改编译


本文会经常更新,请阅读原文: https://blog.lindexi.com/post/dotnet-Roslyn-%E9%80%9A%E8%BF%87%E8%AF%BB%E5%8F%96-suo-%E6%96%87%E4%BB%B6%E8%8E%B7%E5%8F%96%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%E7%9A%84%E5%90%AF%E5%8A%A8%E9%A1%B9%E7%9B%AE.html ,以避免陈旧错误知识的误导,同时有更好的阅读体验。

如果你想持续阅读我的最新博客,请点击 RSS 订阅,推荐使用RSS Stalker订阅博客,或者前往 CSDN 关注我的主页

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。欢迎转载、使用、重新发布,但务必保留文章署名林德熙(包含链接: https://blog.lindexi.com ),不得用于商业目的,基于本文修改后的作品务必以相同的许可发布。如有任何疑问,请 与我联系

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • dotnet 配合 Gitlab 做自动推 Tag 时打包 NuGet 包

    我现在的团队内部用的是 Gitlab 工具,在此工具上提供了 Gitlab CI CD 用于做自动化测试和构建。对于 CBB 来说,发布就是打出 NuGet 包...

    林德熙
  • .NET Core全面扫盲贴

    标签: .NETCore Asp.NETCore 1. 前言 .NET发行至今已经过了十四个年头。随着版本的不断迭代更新,.NET在Windows平台上的表现也...

    潘成涛
  • .NET Core全面扫盲贴

    潘成涛
  • dotnet 配合 GitHub 的 Action 做自动推 Tag 时打包 NuGet 包

    被微软收购的 GitHub 越来越好用,拥有大量免费的工具资源和构建服务器资源,再加上私有项目的无限制使用,我有大量的项目都在向 GitHub 迁移。通过 Gi...

    林德熙
  • Roslyn 通过 Nuget 管理公司配置

    本文来告诉大家如何用 Roslyn 管理配置,在开一个新的项目的时候经常需要添加公司,版权等,但是这些信息不想每次都添加于是我就想用 Nuget 管理所有配置,...

    林德熙
  • dotnet 自动迁移 VS 2017 以前的 csproj 转为 dotnet core 的 SDK Style 风格工具

    本文来安利大家一个特别好用的工具,可以自动将 VisualStudio 2017 以前版本创建的 Franken-proj 格式 的 csproj 项目文件转换...

    林德熙
  • 手把手教你写 Roslyn 修改编译

    在写 Roslyn 的时候,经常需要辅助编译的工具,而这些工具需要传入一些参数,在项目很大的时候,会发现自己传入的参数比微软限制控制台可以传入的参数大很多,这时...

    林德熙
  • Roslyn 读取 PackageReference 的版本号和内容

    在写 msbuild 的预编译逻辑,如果想要拿到项目安装的 NuGet 库和版本,可以通过获取 PackageReference 的方法获取

    林德熙
  • dotnet core 2.1 使用阶梯编译

    在 dotnet core 2.1 可以使用阶梯编译的方法,从 dotnet framework 开始,在代码的所有方法在第一次进入的时候就需要使用 JIT 进...

    林德熙

扫码关注云+社区

领取腾讯云代金券