💡 完全な動作例はGitHubで入手可能です:
scrub-office-document-pii-dotnet

以前の方法は苦痛だった

手順は次の通りです。ドキュメントを開き、[ファイル] → [情報] → [問題のチェック] → [ドキュメントの検査] を選び、チェックボックスにチェックを入れ、[すべて削除] を実行し、別名で保存し、閉じて、次のファイルを開く。40 ファイル後に、[ドキュメントの検査] がレコードのインデックスキーとなる Title も削除したことに気づき、1 時間前に送信されたコピーには SharePoint の承認者 ID が残っていたことが判明しました。これは、別のアプリケーションから保存された際にそのフィールドが書き戻されたためです。

Metadata PII 削除は .NET 用 GroupDocs.Metadata の機能で、Office ドキュメントから身元情報を含むプロパティをプログラムで削除し、残ったものを報告します。手動の手順は次の 3 つの点で失敗します。ファイルが数個を超えるとスケールしない、削除するフィールドの選択が全か無かである、削除した内容の記録が残らない。この文章では、同じ作業を .NET 版で、プロパティ グループごとに実行する方法を示します。

実際に何が入っているかを知っておくと便利です。レビュー ラウンドを経た Word ファイルには、保存したユーザーの Windows アカウントから取得した AuthorLastSavedBy、企業テンプレートからの ManagerCompany、リビジョン カウンタ、TotalEditingTimeLastPrinted のタイムスタンプ、コメント スレッドのカウンタが通常含まれます。さらに SharePoint を加えると、承認者識別子、ワークフロー パス、コンテンツ タイプ URI、作成元テンプレートが追加されます。これらはページ上には表示されず、すべて同じファイル内に保存されています。

より良い方法があります

GroupDocs.Metadata for .NET のすべては、1 つのプロパティ検索エンジンを通して実行されます。RemovePropertiesMetadataProperty に対するラムダ式を受け取り、ラムダが受け入れたすべてのプロパティを削除し、削除した件数を返します。FindProperties は同じラムダ式を実行しますが、書き込みは行いません。プロパティにはタグが付与されているため、Tags.Person.Creator はフォーマットやパッケージが異なっても、リテラル名が異なる場合でも、作者に相当するフィールドを特定できます。

これにより、1 つのボタンではなく 3 種類のクリーンアップが可能になります。身元情報フィールド用のタグパス、コメントやリビジョンなどのファミリ用の名前パス、そして何も残さない Sanitize()。3 つとも件数を返し、その件数がパスの監査可能性を提供します。

新しい方法: プロパティ グループごとの述語

手順 1 – 名前をクリアする

身元グループをカバーするタグチェックは 4 つです。記述的フィールドはそのまま残ります。これは Document Inspector の「すべて削除」との違いです。

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 – それらに関連するファミリをクリアする

コメント、リビジョン、サーバー フィールドにはタグが付いていないため、述語は名前でマッチさせます。編集タイムラインは最も忘れられがちなグループで、ドキュメントがどのように作成されたかを示す重要な情報です。

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;
}

部分文字列マッチは意図的です。CommentsCountCommentTotalEditingTimeEditTime を同時に捕捉でき、フォーマットごとに正確な名前リストを維持する必要がありません。SharePoint 用のパスは同じ呼び出しに ServerWorkflowApproverContentTypeTemplate を加えるだけです。

手順 3 – すべてを削除し、結果を確認する

信頼境界では、1 回の呼び出しで 4 つのパスを置き換えます。

using (var metadata = new Metadata(inputPath))
{
    if (metadata.FileFormat == FileFormat.Unknown) return 0;
    var affected = metadata.Sanitize();
    metadata.Save(outputPath);
    return affected;
}

次に、手動手順に相当するものがありません。検証スキャンは FindProperties を通じて削除述語を再利用し、生存プロパティを 2 つのリストに分類します。

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 などのコンテンツ編集ライブラリが必要で、メタデータ API だけでは不十分です。

なぜすべてに対して Sanitize() を呼び出さないのか?

ほとんどのドキュメントはまだ使用中だからです。Sanitize() は検出されたすべてのパッケージをクリアし、Title、Subject、Keywords など、レコード システムや検索インデックスが依存するフィールドも削除します。ファイルが社内で循環している間は対象パスを使用し、記述的メタデータは残し、組織を出るコピーに対してのみ完全な削除を行います。

並列比較: 前後

手動検査 GroupDocs.Metadata for .NET
選択性 「すべて削除」、記述的フィールドも含む プロパティ グループごとの述語
カバレッジ ダイアログが表示するフィールド ライブラリが検出するすべてのパッケージ、カスタム OOXML パーツも含む
記録 なし 操作ごとに返される影響件数
検証 再度開いて目視 FindProperties スキャンとメタデータ・コンテンツレベルのリスト
200 ファイルのバッチ処理 200 回クリック 1 ループ、5 操作、ファイルごとに 1 行のログ

動作が変わる行は「記録」です。すべてのパスが件数を返すようになると、サニタイズは「誰かが覚えておくべきステップ」から「パイプラインが検証できるデータ」へと変わります。テストのしきい値、監査テーブルのフィールド、夜間ジョブの失敗条件などに利用できます。これは手動手順ではどんなに厳格にやっても得られない結果です。

実例: 送信前フック

サポート ポータルでは、スタッフが顧客チケットにドキュメントを添付できます。添付ハンドラは現在、ファイルが保存される前に身元パスとサーバーパスを実行し、両方の件数をチケットに記録し、保存されたコピーでリークチェックを行います。メタデータリークリストが空でない場合、問題のプロパティ名を示すメッセージでアップロードが拒否されるため、添付者は顧客に届く前にすぐに問題に気付くことができます。

このフックが実用的になるための 2 つのポイント。パスは新しいパスに書き込むため、元のファイルはスタッフの個人ストレージに残り、自動ステップで破壊されません。また、件数は添付ファイルの横にチケットレコードとして保存されるため、「このドキュメントから何が削除されたか」の答えは推測ではなく保存された数値になります。

最初に実際のテンプレートでこのチェックを行ったとき、Manager 値が返ってきました。これは身元パスが数秒前に削除し、企業テンプレートが保存時にすぐ書き戻したものです。削除呼び出しはドキュメント通りに機能していましたが、パイプライン側に問題があり、読み戻しで初めてそれが判明したのです。

GroupDocs.Metadata で他に何ができる?

同じ述語エンジンは読み取りにも使えます。ドキュメントの 2 つのバージョン間でプロパティを比較すると、所有権の変更やレビュー プロセス外での再執筆が浮き彫りになります。また、metadata scrubbing overview では、インタラクティブ ツールが API 主導のパスとどのように共存できるかが解説されています。タグシステムはフォーマットを横断するため、ここで書いた身元述語は PDF、画像、音声ファイルにも変更なしで適用できます。

この移植性は計画時に重要です。MetadataProperty に対するラムダとして記述されたクリーンアップ ルールは普通の C# であるため、共有ライブラリに格納し、フィクスチャ ドキュメントでユニットテストし、必要なサービス(エクスポート エンドポイント、定期レコード ジョブ、リリース前にドキュメント添付をサニタイズするビルド ステップ)から呼び出すことができます。ルールは一箇所に保管され、呼び出し側だけが変わります。

結論

4 つの対象パス、1 つの完全サニタイズ、1 つの検証スキャン。このセットは Office ドキュメントの実務的な範囲をカバーします。ファイルが循環中は記述的メタデータを保持し、外部に出るときはすべてをクリアし、結果を証明します。サンプルをクローンし、実際のレビュー ラウンドを経たドキュメントで実行して、影響件数を確認してください。件数は予想以上に多いことが多く、これが本稿のポイントです。

追加リソース