如何使用C# VSTO插件从Outlook的OLE附件中提取原始文件数据?
解决方案:提取Outlook OLE附件的原始文件数据
一、通过C#直接读取OLE附件的原始数据
OLE嵌入附件本质是复合文档(Structured Storage),可以通过PropertyAccessor获取原始字节流,再用OpenMcdf解析复合文档结构,提取内嵌的原始文件。
步骤1:判断附件类型并获取OLE数据
首先筛选出类型为olEmbeddedItem(枚举值6)的附件,然后通过PropertyAccessor读取PR_ATTACH_DATA_BIN属性(属性标签:http://schemas.microsoft.com/mapi/proptag/0x37010102)获取复合文档的字节数组:
using Microsoft.Office.Interop.Outlook; using System.IO; // 假设当前处理的邮件项为MailItem mailItem foreach (Attachment att in mailItem.Attachments) { if (att.Type == OlAttachmentType.olEmbeddeditem) { // 获取OLE复合文档的字节数组 PropertyAccessor pa = att.PropertyAccessor; byte[] oleData = (byte[])pa.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x37010102"); // 后续解析oleData... } }
步骤2:用OpenMcdf解析OLE复合文档
OLE嵌入的Office文件通常存储在\1Ole10Native流中,这个流包含原始文件的元数据和内容。解析该流的格式即可提取原始文件:
using OpenMcdf; public static byte[] ExtractOleNativeData(byte[] oleData) { using (var compoundFile = new CompoundFile(new MemoryStream(oleData))) { // 定位Ole10Native流 CFStream oleStream = compoundFile.RootStorage.GetStream("\x01Ole10Native"); if (oleStream == null) throw new InvalidDataException("未找到Ole10Native流"); byte[] streamData = oleStream.GetData(); using (var ms = new MemoryStream(streamData)) using (var reader = new BinaryReader(ms)) { // Ole10Native流格式: // 0-3字节:标志(通常为0) // 4-7字节:原始文件路径的长度(Unicode) // 8-...:文件路径(Unicode) // 接着是文件大小(4字节),然后是原始文件数据 reader.ReadInt32(); // 跳过标志 int pathLength = reader.ReadInt32(); reader.ReadBytes(pathLength * 2); // 跳过Unicode路径 int fileSize = reader.ReadInt32(); return reader.ReadBytes(fileSize); // 返回原始文件字节数据 } } }
调用这个方法后,得到的字节数组就是原始Word/Excel文件的内容,可直接写入文件:
byte[] originalFileData = ExtractOleNativeData(oleData); File.WriteAllBytes(@"C:\temp\extracted.docx", originalFileData);
二、在OLE附件添加时即时处理(替代“转换前读取”)
Outlook没有直接提供“转换为OLE对象前”的事件,但可以通过AttachmentAdd事件在附件刚添加到邮件时立即提取数据,此时OLE结构已生成但还未被用户修改:
示例:绑定Inspector的AttachmentAdd事件
private void ThisAddIn_Startup(object sender, System.EventArgs e) { Application.Inspectors.NewInspector += Inspectors_NewInspector; } private void Inspectors_NewInspector(Inspector Inspector) { if (Inspector.CurrentItem is MailItem mailItem) { mailItem.AttachmentAdd += MailItem_AttachmentAdd; } } private void MailItem_AttachmentAdd(Attachment att) { if (att.Type == OlAttachmentType.olEmbeddeditem) { // 立即提取原始数据,避免后续操作导致结构变化 try { PropertyAccessor pa = att.PropertyAccessor; byte[] oleData = (byte[])pa.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x37010102"); byte[] originalData = ExtractOleNativeData(oleData); // 处理原始数据,比如保存到指定位置 string tempPath = Path.Combine(Path.GetTempPath(), $"extracted_{Guid.NewGuid()}.tmp"); File.WriteAllBytes(tempPath, originalData); } catch (Exception ex) { // 异常处理 System.Windows.Forms.MessageBox.Show($"提取失败:{ex.Message}"); } } }
注意事项
- 确保引用
OpenMcdfNuGet包(版本建议用最新稳定版)。 - 部分特殊OLE对象(如非Office文件)的流结构可能不同,需根据实际情况调整解析逻辑。
- 处理大附件时,注意内存占用,可分块读取流数据。
内容的提问来源于stack exchange,提问作者Aziz K.
相关产品推荐
相关产品推荐

