💡 Full working example available on GitHub:
scrub-office-document-pii-dotnet
舊的做法很痛苦
流程大致如下:開啟文件 → 點選 File → Info → Check for Issues → Inspect Document,勾選方框 → Remove All,另存新檔 → 關閉 → 開啟下一個。四十個檔案之後,有人發現 Inspect Document 也把記錄索引鍵所依賴的 Title 刪除了,而一小時前發出的副本仍保留了 SharePoint 批核者 ID,因為該檔案是由另一個會把此欄位寫回的應用程式儲存的。
Metadata PII 移除是 GroupDocs.Metadata 在 .NET 上的功能,能以程式方式刪除 Office 文件中帶有身分資訊的屬性,並回報剩餘的內容。手動流程在三個方面失敗:無法處理大量檔案、對要刪除的欄位只能全有或全無、且不會留下任何被移除的紀錄。本文示範同樣的工作,只是改用 .NET 版,一次處理一個屬性群組。
先了解文件裡實際存了什麼很重要。經過審閱的 Word 檔通常會帶有 Author 與 LastSavedBy(儲存者的 Windows 帳號)、Manager 與 Company(企業範本提供)、修訂計數、TotalEditingTime、LastPrinted 時間戳記,以及評論串的計數。若再加入 SharePoint,還會出現批核者識別碼、工作流程路徑、內容類型 URI,以及建立文件時使用的範本。這些資訊都不會顯示在畫面上,卻全部儲存在同一個檔案內。
有更好的方法
GroupDocs.Metadata for .NET 的所有功能都透過同一個屬性搜尋引擎運作。RemoveProperties 接收一個針對 MetadataProperty 的 lambda,刪除 lambda 所接受的每個屬性,並回傳刪除的數量。FindProperties 以相同的 lambda 執行搜尋但不寫入。屬性同時帶有標籤(Tags),因此 Tags.Person.Creator 能在不同格式與封裝中辨識作者類型的欄位,而不必對每個產生應用程式的字面名稱逐一比對。
這樣就有了三種清理方式,而不是只有一個按鈕:針對身分欄位的標籤通過、針對如評論與修訂等族群的名稱通過,以及在什麼都不該留下時使用 Sanitize()。三者皆回傳數字,而這些數字正是讓清理具備稽核依據的關鍵。
新的做法:每個屬性群組一個 Predicate
步驟 1 - 清除姓名相關欄位
四個標籤檢查涵蓋身分群組。描述性欄位保持不變,這與 Document Inspector 的 Remove All 不同:
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.RemoveProperties(p =>
p.Tags.Contains(Tags.Person.Creator) ||
p.Tags.Contains(Tags.Person.Editor) ||
p.Tags.Contains(Tags.Person.Manager) ||
p.Tags.Contains(Tags.Corporate.Company));
metadata.Save(outputPath);
return affected;
}
FileFormat.Unknown 的檢查是防止回傳零值的保護機制:若沒有這個檢查,無法讀取的檔案與乾淨的檔案對呼叫端看起來會完全相同。
步驟 2 - 清除與它們相關的族群
評論、修訂與伺服器欄位沒有標籤,因此 Predicate 會比對名稱。編輯時間線是最常被遺忘的群組,也是說明文件如何產生的關鍵:
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.RemoveProperties(p =>
p.Name != null && (
p.Name.Contains("Revision") ||
p.Name.Contains("TrackedChange") ||
p.Name.Contains("LastPrinted") ||
p.Name.Contains("TotalEditingTime") ||
p.Name.Contains("EditTime")));
metadata.Save(outputPath);
return affected;
}
使用子字串匹配是刻意的:它同時捕捉 CommentsCount 與 Comment,以及 TotalEditingTime 與 EditTime,不必為每種格式維護完整的名稱清單。SharePoint 的通過則是同樣的呼叫,只加入 Server、Workflow、Approver、ContentType 與 Template。
步驟 3 - 全部清除,然後檢查結果
在信任邊界上,只需要一次呼叫即可取代前面的四個通過:
using (var metadata = new Metadata(inputPath))
{
if (metadata.FileFormat == FileFormat.Unknown) return 0;
var affected = metadata.Sanitize();
metadata.Save(outputPath);
return affected;
}
接著是手動流程無法對應的部分。驗證掃描透過 FindProperties 重新使用移除時的 Predicate,並將剩餘項目分成兩個清單:
foreach (var p in props)
{
var value = p.InterpretedValue?.ToString() ?? p.Value?.ToString() ?? string.Empty;
if (string.IsNullOrWhiteSpace(value)) continue;
if (value == "0" || value == "0.0") continue;
var entry = $"{p.Name}={value}";
var name = p.Name ?? string.Empty;
if (name.StartsWith("Comment") || name.StartsWith("Revision"))
report.ContentLevelLeaks.Add(entry);
else
report.MetadataLeaks.Add(entry);
}
MetadataLeaks 必須為空,檔案才算已完成清理。ContentLevelLeaks 僅作資訊用途:Word 的評論與追蹤變更作者位於 word/document.xml,屬於正文內容,若要清除這些資訊需要使用如 Aspose.Words 之類的內容編輯函式庫,而非純粹的 metadata API。
為什麼不直接對所有檔案呼叫 Sanitize()?
因為大多數文件仍在使用中。Sanitize() 會清除每個偵測到的封裝,包含 Title、Subject、Keywords——這些正是紀錄系統與搜尋索引所依賴的欄位。當檔案仍在內部流通時,使用有針對性的通過保留描述性 metadata,等到檔案真正離開組織時再執行完整清除。
前後對照
| 手動檢查 | GroupDocs.Metadata for .NET | |
|---|---|---|
| 選擇性 | Remove All,會刪除描述性欄位 | 每個屬性群組一個 predicate |
| 覆蓋範圍 | 對話框中顯示的欄位 | 函式庫偵測的所有封裝,包含自訂 OOXML 部分 |
| 紀錄 | 無 | 每次操作回傳受影響的計數 |
| 驗證 | 重新開啟檢視 | 使用 FindProperties 掃描,產生 metadata 與 content‑level 清單 |
| 200 檔案批次處理 | 200 次點擊 | 一個迴圈、五個操作、每檔案一行日誌 |
改變行為的那一列是「紀錄」。只要每個通過都回傳計數,清理就不再是需要有人記得執行的步驟,而是管線可以斷言的資料:測試的門檻、稽核表的欄位、或是夜間工作失敗的條件。這也是手動流程在任何紀律層級都無法產出的項目。
真實案例:發送前掛鉤
支援入口網站允許工作人員將文件附加到客戶工單。附件處理程式現在會在檔案儲存前先執行身分通過與伺服器通過,將兩個計數寫入工單,並對已儲存的副本執行洩漏檢查。只要 metadata‑leak 清單非空,就會以列出違規屬性的訊息拒絕上傳,讓上傳者立即得知問題,而不是等到客戶收到後才發現。
此掛鉤之所以實用,有兩個細節:通過寫入新路徑,原始檔仍保留在工作人員自己的儲存空間,避免自動化步驟直接破壞檔案;計數寫入工單的附件欄位,使「這份文件移除了什麼」的答案成為一個已存的數字,而不是對管線常見行為的猜測。
第一次在真實範本上測試此檢查時,回傳了 Manager 欄位的值——該值在身分通過幾秒前已被移除,卻在儲存時被企業範本寫回。移除呼叫本身運作如文件,問題出在管線的前後流程,而只有讀回來的結果才顯示出來。
GroupDocs.Metadata 還能做什麼?
相同的 Predicate 引擎也可用於讀取。比較同一文件兩個版本的屬性,可發現所有權變更與審閱流程之外的重新作者情形,而 metadata scrubbing overview 則說明了互動工具仍可在 API 驅動的通過旁邊發揮作用。因為標籤系統跨格式,本文的身分 Predicate 也能直接套用於 PDF、影像與音訊檔,無需修改。
這種可移植性值得事先規劃。以 MetadataProperty 為目標的 lambda 本身就是普通的 C#,因此可以放在共享函式庫中、對測試文件進行單元測試,並由任何需要的服務呼叫:匯出端點、排程紀錄工作、或是發佈前清理文件附件的建置步驟。規則只需維護在一處,呼叫端點則可自由變動。
結論
四個有針對性的通過、一個完整的 Sanitize、一次驗證掃描。這套組合涵蓋了 Office 文件的實務需求:文件流通時保留描述性 metadata,離開組織時全部清除,並且無論哪種情況都能證明結果。複製範例、對經過真實審閱的文件執行,即可看到受影響的計數。通常會高於預期,這正是此方法的核心目的。