Full-Code RPA - 使用 .NET 6 搜尋 Outlook 收件匣
1 |
這些年 RPA(Robotic Process Automation) 是個熱門話題,日常瑣事的機械化動作丟給機器人處理,讓人類脫離手工作業地獄,怎麼想都是個好主意。不過,業界在談的 RPA 多指採購廠商開發的軟體,強調介面友善功能豐富又容易上手(甚至具備機器學習等 AI 功能),讓不會寫程式或的使用者也能將日常作業自動化,實現 Low-Code 甚至 No-Code 目標,擺脫對程式開發人員的依賴。
不過,身為程式開發老人,我有不一樣的看法,認為若真要充分發揮 RPA 精神,很難不走上寫程式這條路。所以程式開發人員倒也不用擔心被取代,自己寫機器人程式做自動化(對照 Low-Code/No-Code ,就叫它 Full-Code RPA 吧)在功能及效率上能輕易擊敗通用 RPA 軟體,還是有機會專攻講求整合性、客製化及執行效率的高端市場。 (延伸閱讀:關於 RPA (機器人流程自動化),我說的其實是...)
信件處理是很常見的人工作業瓶頸,而 Outlook 是許多企業常用的信件軟體,這篇就來研究如何寫支 .NET 6+ 程式搜尋 Outlook 的收信匣,找尋特定郵件,相信在許多 RPA 情境中能派上用場。
.NET 6 程式在桌面執行,存取 Outlook 最簡便的管道是透過 COM+ 介面。.NET Core/.NET 5+ 開始跨平台,但在 Windows 執行仍能存取 COM+ 元件。做法是在 Visual Studio 的 Solution Exploere 點選 Add COM Reference:
找到並參考 Microsoft Outlook 16.0 Object Library:
.csproj 將新增以下內容:
<ItemGroup>
<COMReference Include="Microsoft.Office.Interop.Outlook">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>6</VersionMinor>
<VersionMajor>9</VersionMajor>
<Guid>00062fff-0000-0000-c000-000000000046</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
然後 .NET 6+ 程式中,先 using Outlook = Microsoft.Office.Interop.Outlook,之後使能 new Outlook.Application() 連上執行中的 Outlook,存取其 DOM 模型。
想在 Outlook 搜尋郵件、連絡人,需要一些背景知識,官方文章的 Filtering Items 是不錯的入門。以下是我整理搜尋收件匣的原理:
- 先取得收件匣 Folder 物件,使用類似 SQL 的查詢語法對 Items 項目集合進行篩選,若筆數少可使用 Items.Find()/FindNext() 逐筆取回,Items.Restrict() 則可一次傳回符合條件的集合。
- 要找到特定信件,還有個笨方法是對 Items 跑迴圈一筆一筆撈出來讀屬性比對(若查詢邏輯很特殊,這是唯一解),篩選功能可用類 SQL 語法查資料夾,更簡便且有效率。
- Find()/Restrict() 用的類 SQL 查詢語法有兩種格式:Jet Query Language 及 DAV Searching and Locating(DASL),Jet 格式為 [Subject] = '...'、DASL 則為 @SQL=urn:schemas:httpmail:subject = '...',兩種查詢都支援 AND/OR 及一些簡單運算,但二者不能混用,而且只有 DASL 才支援 LIKE 查詢,要做到關鍵字查詢,只能用 DASL。
- DASL 語法(@SQL=urn:schemas...) 會用到欄位對映 urn,可參考文件 Exchage Store Schema 取得。
- 找到 MailItem 物件後,可由 Subject、Body、Attachments 讀取主旨、內文及附件,也可呼叫 Delete()、Move()、Reply()、Forward() 進行刪除、搬移、回覆及轉寄等動作,做出各種花式應用。
我寫了一個簡單範例,展示用寄件者名稱、主旨關鍵字、收件時間區間... 等條件搜尋 Outlook 收件匣:
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Outlook = Microsoft.Office.Interop.Outlook;
Action<string> printTitle = (s) =>
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine();
Console.WriteLine(s);
Console.ResetColor();
};
printTitle("Test 1. Sender = 'Windows'");
SearchInbox(senderName: "Windows");
printTitle("Test 2. Subject LIKE '%Microsoft Learn%'");
SearchInbox(subjectKeywd: "Microsoft Learn");
printTitle("Test 3: Sender = 'Microsoft Azure', Subject LIKE '%Azure%', Date > 2023/1/1");
SearchInbox("Microsoft Azure", "Azure", new DateTime(2023, 1, 1));
printTitle("Test 4: Subject LIKE '%Azure%', Date > 2023/1/1 AND < '2023/1/20");
SearchInbox(null, "Azure", new DateTime(2023, 1, 1), new DateTime(2023, 1, 20));
void SearchInbox(string senderName = null, string subjectKeywd = null, DateTime? startTime = null, DateTime? endTime = null)
{
if (Process.GetProcessesByName("OUTLOOK").Length > 0)
{
var app = new Outlook.Application();
var ns = app.GetNamespace("MAPI");
var inbox = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox);
var items = inbox.Items;
var conds = new List<string>();
Func<string, string> escape = (s) => s.Replace("'", "''");
if (!string.IsNullOrEmpty(senderName))
{
conds.Add(@$"(""urn:schemas:httpmail:sendername"" = '{escape(senderName)}')");
}
if (!string.IsNullOrEmpty(subjectKeywd))
{
conds.Add(@$"(""urn:schemas:httpmail:subject"" LIKE '%{escape(subjectKeywd)}%')");
}
if (startTime != null)
{
conds.Add(@$"(""urn:schemas:httpmail:datereceived"" > '{startTime.Value:yyyy-MM-dd HH:mm:ss}')");
}
if (endTime != null)
{
conds.Add(@$"(""urn:schemas:httpmail:datereceived"" < '{endTime.Value:yyyy-MM-dd HH:mm:ss}')");
}
var filterString = "@SQL=" + string.Join(" AND ", conds.ToArray());
var filterd = items.Restrict(filterString);
if (filterd.Count == 0)
{
Console.WriteLine("Not Found");
}
else
{
foreach (var item in items.Restrict(filterString))
{
var mailItem = item as Outlook.MailItem;
if (mailItem != null)
{
Console.WriteLine($"{mailItem.SentOn:yyyy-MM-dd HH:mm:ss} / {mailItem.SenderName} / {mailItem.Subject}");
}
}
}
}
else
{
Console.WriteLine("Outlook is not running");
}
}
實測成功!
掌握以上技巧,我們就能寫程式在 Outlook 快速找到特定郵件、連絡人、行事曆,玩出更多有趣的應用。
Tutorial of using .NET 6 and Outlook COM reference to search Outlook inbox folder.
Comments
# by Jackson2847
995 老板聽了 RPA 感覺很神,希望行政人員也能用 RPA 減輕工作!! 讓行政人員學 RPA 倒不如叫 MIS 振作點...怎麼可能不寫 code 想要網路爬蟲,怎麼可能完全不懂 HTML 跟 JS