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 库,才能使用这个方法
本文所有代码放在 github 和 gitee 欢迎小伙伴访问
除了读取启动项目,还可以读取断点等内容,读取 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 修改编译
dotnet Roslyn 通过读取 suo 文件获取解决方案的启动项目的更多相关文章
- Java 读取网络资源文件 获取文件大小 MD5校验值
Java 读取网络资源文件 获取文件大小 MD5校验值 封装一个文件操作工具类: package c; import java.io.*; import java.net.HttpURLConnect ...
- [获取行数]php读取大文件提供性能的方法,PHP的stream_get_line函数读取大文件获取文件的行数的方...
背景: 下面是获取文件的行数的方法: 一个文件如果知道有几行的话,就可以控制获取一定的行数的数据,然后放入数据库.这样不管的读取大文件的性能,还是写入数据库的性能,都能得到很大的提高了. 下面是获取文 ...
- 浅谈JS中的!=、== 、!==、===的用法和区别 JS中Null与Undefined的区别 读取XML文件 获取路径的方式 C#中Cookie,Session,Application的用法与区别? c#反射 抽象工厂
浅谈JS中的!=.== .!==.===的用法和区别 var num = 1; var str = '1'; var test = 1; test == num //tr ...
- 强悍的Python读取大文件的解决方案
这是一道著名的 Python 面试题,考察的问题是,Python 读取大文件和一般规模的文件时的区别,也即哪些接口不适合读取大文件. 1. read() 接口的问题 f =open(filename, ...
- 基于.NET的程序读取Excel文件的解决方案
目录 0. 前言 1. 使用NPOI库读取Excel文件 2. 使用OleDbConnection 3. 相关参考 shanzm-2020年12月8日 23:48:11 0. 前言 以前基于 .NET ...
- java通过读取本地文件获取反射方法参数,执行对象方法
运用到的知识点 IO流, 集合properties 反射 在工程目录下新建file config.properties #one time only can run one method cl ...
- C#读取DLL文件获取所有类
说明 调用Web.dll 文件,获取其中的所有的WebService 参考 https://blog.csdn.net/huoliya12/article/details/78873123 流程 使用 ...
- 读取 xml 文件 获取其中保存的数据信息
建立一个存储过程来返回要读取的数据形成结果集: CREATE PROC dbo.getValuesFromXmlByPath@fileName NVARCHAR(128)asDECLARE @T XM ...
- Mybatis 的 xml 文件语法错误,启动项目时控制台一直循环解析但是不打印错误
重写SqlSessionFactoryBean的buildSqlSessionFactory方法: eg: package com.slp; import java.io.IOException; i ...
- Maven01-maven打包Web项目成war文件-tomcat脱机运行启动项目
1 执行package 2 复制 3 catalina run ,打开cmd窗口 4 输入网址 5注意要配置tomcat的 Application context为工程名字
随机推荐
- 如何在Docker容器启动时自动运行脚本
本文分享自华为云社区<如何在Docker容器启动时自动运行脚本>,作者: 皮牙子抓饭. 如何在Docker容器启动时自动运行脚本 在使用Docker构建应用程序时,有时我们希望在启动Doc ...
- Java内存马2-Spring内存马
Spring内存马 目录 Spring内存马 1.Spring&Spring MVC简介 2.环境搭建 3.Controller内存马 4.踩坑日记 5.Interceptor内存马 1.Sp ...
- KingbaseES 等待事件之 - Client ClientWrite
等待事件含义 Client:ClientWrite等待事件指数据库等待向客户端写入数据. 在正式业务系统中,客户端必然和数据库集群之间有数据交互,这里指的是数据接收,发送.数据库集群在向客户端发送更多 ...
- KingbaseES Json 系列十:Json数组构造函数
KingbaseES Json 系列十--Json数组构造函数(ARRAY_TO_JSON,JSONB_BUILD_ARRAY,JSON_ARRAY,JSON_BUILD_ARRAY) JSON 数据 ...
- P图神器Lama下载介绍,一键移除图片中任何不想要的元素
Lama是一个完全自托管的图像处理工具,基于最前沿的AIGC模型,它可以从图片中删除任何不需要的物体.缺陷或对象,却看不到一点修改痕迹~ 在以前,我们想将图片中的文字.水印去除,可以使用ps,但ps的 ...
- 使用fiddler抓取HTTPS的数据包(抓取App端的数据包)
众所周知,我们在做接口测试的时候有两种情况: 第一种是先拿到接口测试规范文档,再去做接口测试. 第二种是没有接口文档,只有通过自己抓包. 那么说到抓包,就不得不说抓包工具,对于浏览器web端,我们只需 ...
- markdown 常用表情符号 (github emoji)
markdown 常用表情(emoji) 官网[非笔者维护,仅做引用] Face Smiling 咧嘴笑 grinning 汗颜笑 sweat_smile 爆笑 rofl 眨眼笑 wink innoc ...
- ET介绍——分布式Actor模型
Actor模型 Actor介绍 在讨论Actor模型之前先要讨论下ET的架构,游戏服务器为了利用多核一般有两种架构,单线程多进程跟单进程多线程架构.两种架构本质上其实区别不大,因为游戏逻辑开发都需要用 ...
- #线性dp#洛谷 5999 [CEOI2016]kangaroo
题目 问有多少个长度为 \(n\) 的排列满足首项为 \(st\),末项为 \(ed\), 并且 \(\forall i\in (1,n),\left[a_{i-1}<a_i \oplus a_ ...
- #树上启发式合并,位运算#CF570D Tree Requests
题目 给定一个以 \(1\) 为根的 \(n\) 个结点的树,每个点上有一个字母\((a-z)\),每个点的深度定义为该节点到 \(1\) 号结点路径上的点数. 每次询问 \(a, b\) 查询以 \ ...