脆弱なコードは情報漏洩の火種となる:PowerPoint VBAによる「堅牢な機密情報マスキング・エンジン」の設計思想
エンジニア諸君、まず最初にこれだけは肝に銘じておいてほしい。
「単に文字列を置換するだけのマクロ」は、業務効率化ツールではない。それは、セキュリティホールを自動生成するだけの「時限爆弾」だ。
外部パートナーへ資料を共有する際、「念のため機密ワードを伏せ字にしよう」という安易な考えで、`Replace` 関数を並べただけのコードを書く者がいる。しかし、スライド内のテキストは、単なるテキストボックスに留まらない。グループ化された図形、表(Table)、SmartArt、そして複雑なテキスト階層……。これらを網羅できないコードは、検知漏れという致命的なバグを必然的に引き起こす。
本稿では、プロフェッショナルとして、単なる「コピペ用スクリプト」ではなく、オブジェクトのライフサイクルと再帰的探索を理解した、プロダクションレベルのマスキング・エンジンの設計と実装を伝授する。
1. アーキテクチャ設計:なぜ「単純なループ」では足りないのか
中級者が陥る最大の罠は、`For Each shp In ActivePresentation.Slides.Shapes` という単純なループで処理を完結させようとすることだ。これでは、「グループ化された図形の中にあるテキスト」や「表の中のセル」を見落とす。
堅牢な設計には、以下の3つの原則が不可欠である。
① 再帰的探索(Recursive Traversal)の導入
図形の中に図形がある(GroupShape)場合、その階層を深く潜り抜けてテキストを探索する再帰関数が必要だ。
② 外部定義による機密情報の管理(Externalized Configuration)
機密キーワードをVBAのソースコード内にハードコード(直書き)するのは、保守性の観点から最悪の設計だ。キーワードは外部のテキストファイルやCSVから読み込み、ロジックとデータを分離せよ。これにより、エンジニアの手を介さずに、非エンジニア(管理者)が検知リストを更新できる。
③ 非破壊的処理(Immutability)
元のファイル(Source)を直接書き換えるな。必ず「別名保存(SaveAs)」を前提としたワークフローを組み、万が一の処理失敗時にも、オリジナルの機密資料が破損しないように設計せよ。
2. プロダクション実装:正規表現を用いたマスキング・エンジン
以下に、実務でそのまま投入可能なレベルのコードを示す。このコードは、`VBScript.RegExp` を利用して高度なパターンマッチングを行い、グループ化された図形や表に対しても再帰的に作用する。
事前準備
VBAエディタの [ツール] -> [参照設定] から、「Microsoft VBScript Regular Expressions 5.5」 にチェックを入れておくこと。これがなければ、正規表現のパワーは引き出せない。
Option Explicit
' ======================================================================================
' Module: PrivacyShieldEngine
' Description: スライド内の機密情報を正規表現で検知し、マスキングを行う堅牢なエンジン
' Author: Chief Architect
' ======================================================================================
Sub ExecutePrivacyMasking()
Dim sourcePres As Presentation
Dim targetPres As Presentation
Dim keywordFilePath As String
Dim patterns As Collection
Dim savePath As String
' --- 設定領域 ---
' 本来はダイアログで選択させるべきだが、ここでは設計例としてパスを指定
keywordFilePath = ThisWorkbook.Path & "\keywords.txt" ' 1行1パターンで記載したテキストファイル
savePath = ThisWorkbook.Path & "\Masked_Presentation_" & Format(Now, "yyyymmdd_hhnnss") & ".pptx"
' ----------------
On Error GoTo ErrorHandler
Set sourcePres = ActivePresentation
' 1. 機密パターン(正規表現)のロード
Set patterns = LoadRegexPatterns(keywordFilePath)
If patterns.Count = 0 Then
MsgBox "検知パターンが読み込めませんでした。ファイルを確認してください。", vbCritical
Exit Sub
End If
' 2. 非破壊的処理のため、ファイルをコピーして新しいプレゼンテーションとして開く
' ※ 本来はSaveCopyAs等を用いて別プロセスで扱うのが理想的だが、ここではシンプルに構成
sourcePres.SaveCopyAs savePath
Set targetPres = Presentations.Open(savePath, WithWindow:=msoTrue)
' 3. 全スライドに対して再帰的スキャンを実行
Dim sld As Slide
Dim regEx As RegExp
For Each sld In targetPres.Slides
For Each regEx In patterns
ProcessSlide regEx, sld
Next regEx
Next sld
' 4. 保存して完了
targetPres.Save
MsgBox "マスキング処理が完了しました。" & vbCrLf & "保存先: " & savePath, vbInformation
Exit Sub
ErrorHandler:
MsgBox "致命的なエラーが発生しました: " & Err.Description, vbCritical
End Sub
' --------------------------------------------------------------------------------------
' 関数: LoadRegexPatterns
' 役割: 外部ファイルから正規表現パターンを読み込む
' --------------------------------------------------------------------------------------
Private Function LoadRegexPatterns(filePath As String) As Collection
Dim col As New Collection
Dim fso As Object
Dim txtStream As Object
Dim line As String
Dim reg As RegExp
Set fso = CreateObject("Scripting.FileSystemObject")
If Not fso.FileExists(filePath) Then
Set LoadRegexPatterns = New Collection
Exit Function
End If
Set txtStream = fso.OpenTextFile(filePath, 1) ' ForReading
Do Until txtStream.AtEndOfStream
line = Trim(txtStream.ReadLine)
If line <> "" Then
Set reg = New RegExp
reg.Pattern = line
reg.Global = True
reg.IgnoreCase = True
col.Add reg
End If
Loop
txtStream.Close
Set LoadRegexPatterns = col
End Function
' --------------------------------------------------------------------------------------
' サブルーチン: ProcessSlide
' 役割: スライド内の全てのShapeを走査する
' --------------------------------------------------------------------------------------
Private Sub ProcessSlide(ByRef regEx As RegExp, ByRef sld As Slide)
Dim shp As Shape
For Each shp In sld.Shapes
RecursiveMasking shp, regEx
Next shp
End Sub
' --------------------------------------------------------------------------------------
' サブルーチン: RecursiveMasking
' 役割: 図形の種類を判別し、再帰的にテキストを探索する(Group/Table対応)
' --------------------------------------------------------------------------------------
Private Sub RecursiveMasking(ByRef shp As Shape, ByRef regEx As RegExp)
Dim i As Integer, j As Integer
Dim targetTable As Table
' ケース1: グループ化された図形の場合(再帰呼び出し)
If shp.Type = msoGroup Then
Dim gShp As Shape
For Each gShp In shp.GroupItems
RecursiveMasking gShp, regEx
Next gShp
' ケース2: 表(Table)の場合
ElseIf shp.HasTable Then
Set targetTable = shp.Table
For i = 1 To targetTable.Rows.Count
For j = 1 To targetTable.Columns.Count
MaskTextRange targetTable.Cell(i, j).Shape.TextFrame.TextRange, regEx
Next j
Next i
' ケース3: 通常のテキストフレームを持つ図形
ElseIf shp.HasTextFrame Then
If shp.TextFrame.HasText Then
MaskTextRange shp.TextFrame.TextRange, regEx
End If
End If
End Sub
' --------------------------------------------------------------------------------------
' サブルーチン: MaskTextRange
' 役割: TextRangeに対して正規表現置換を実行する
' --------------------------------------------------------------------------------------
Private Sub MaskTextRange(ByRef tr As TextRange, ByRef regEx As RegExp)
' 注意: TextRange.Replace は正規表現を直接サポートしていないため、
' 一旦文字列として取得し、正規表現で置換した後に書き戻す。
' ※ 書式(色や太字)を完全に維持するには、Characters単位の精緻な制御が必要だが、
' プライバシー保護の文脈では「安全な文字列置換」を優先する。
Dim originalText As String
Dim maskedText As String
originalText = tr.Text
If regEx.Test(originalText) Then
maskedText = regEx.Replace(originalText, "[CONFIDENTIAL]")
tr.Text = maskedText
End If
End Sub
3. プロフェッショナルが教える、運用上のクリティカルな注意点
このコードを実戦投入するにあたり、以下の3点を必ず考慮せよ。
① 書式情報の欠落問題(Trade-off)
上記のコードでは、`tr.Text = maskedText` という手法をとっている。これは、テキスト全体を一度文字列として扱い、置換後に書き戻す手法だ。
利点: 実装がシンプルで、複雑な正規表現パターンにも確実に反応する。
欠点: 置換対象のテキストの中に「一部だけ太字」や「色付き」などの書式があった場合、その書式がリセットされる。
もし、書式の保持が絶対条件であるならば、`tr.Characters` をループで回し、マッチした開始位置と長さを特定して、一文字ずつ、あるいは範囲ごとに置換を行う、より高度な(そして重い)ロジックを組む必要がある。しかし、機密情報の保護という目的においては、「書式が崩れても、情報は守る」という設計判断が正しい。
② パフォーマンスの重み
スライドが数百枚、図形が数千個あるプレゼンテーションに対し、大量の正規表現パターンをぶつけると、処理時間は指数関数的に増大する。
`LoadRegexPatterns` で `RegExp` オブジェクトをあらかじめコンパイル(`reg.Pattern = …`)しておくのは、この計算量を抑えるための必須テクニックだ。
③ 外部ファイル(keywords.txt)の管理
`keywords.txt` には、以下のように正規表現を直接記述できる。
- `Project-[A-Z]{3}-\d{4}` (プロジェクトコード:Project-ABC-1234 など)
- `\d{3}-\d{4}-\d{4}` (電話番号パターン)
- `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}` (メールアドレス)
このように、単なる単語リストではなく、「パターン」を定義できることこそが、このツールの真価である。
結論
自動化エンジニアの仕事は、単に「動くもの」を作ることではない。「失敗しない仕組み」を作ることだ。
今回紹介した再帰的探索と正規表現による設計は、PowerPointという複雑なオブジェクトモデルに対峙するための、一つの正解である。
このコードをベースに、自社の要件(ログ出力の追加、PDF変換の自動化など)を肉付けしていけ。それが、真のアーキテクトへの第一歩だ。
