1

Full-Code RPA - 使用 .NET 6 搜尋 Outlook 收件匣

 1 year ago
source link: https://blog.darkthread.net/blog/search-outlook-inbox-by-dotnet/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

使用 .NET 6 搜尋 Outlook 收件匣-黑暗執行緒

這些年 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:

Fig1_638108642362806983.png

找到並參考 Microsoft Outlook 16.0 Object Library:

Fig2_638108642364638024.png

.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");
    }
}

實測成功!

Fig3_638108642366501399.png

掌握以上技巧,我們就能寫程式在 Outlook 快速找到特定郵件、連絡人、行事曆,玩出更多有趣的應用。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK