「この記事は、Power Automate Advent Calendar 2023 12月13日担当分の記事です。」
概要
メールに添付されたPDFを統合するちょっとしたフローを作るときの思考過程をなるべく文字にしました。
Generate script with Copilot PREVIEWを後半、少し使いました。作文が上手くなるとよいことがありそうです。
注意事項
- 主観が多分に含まれます。
- 普段は省略していることを書いているので長文です。
- GUI 操作を極力使わないように考えるスタイルです。
- Windows 11 Pro 23H2
- M365 Business Premium
- Power Automate for desktop 2.38.182.23307
- Power Automate Premium 試用環境
- Generate script with Copilot PREVIEW を使うには Power PlatformにUS 環境が必要です。
- 2023 年 12 月 3 日時点の情報です。
- 自己責任でおねがいします。
お題
センパイ「メール添付されてくる請求書が「かがみ」と「明細」に分かれている。ペア同士ひとつのファイルにしたい。しかし手ではやりたくない」
私「何枚くらいあるんです?」
センパイ「30 くらいかな、お知らせとかも混ざってる」
私「・・・・」
情報を集める
- メールアプリは何を使っているのか
- 手持ちで使えるツールは何か
- クラウドでの実装か、オンプレミスでの実装か
- トリガー(発火点)はどこがよいか
内容を整理する
- メールアプリ:Outlook( オンプレ版)
- PDF 専用アプリなし
- 月一なので、受信に気づいた時、手動発火
- Power Automate Premium が使える環境
- PDF の統合完了までで OK
単純化して書く
私自身はフロー図を落書きしたり、コメントだけ先に書くことをします。後者の方が最近多い気がします。
最初に、単純化した作文をことで目的を明確にして、後で見たときも何をやっているのかわかるようにします。
聞き取った内容から使えそうなアクションを当てはめてみます。
このまま保存すると、当然エラーがでます。そこでこのアクションを使うために「準備」が必要なことがわかります。
添付ファイルを取得するのに必要な情報を書き出す
「Outlook からメールメッセージを取得」アクションで添付ファイルを保存するためにはつぎの準備が必要です。
- メールアカウント情報
- 受信フォルダ名の情報
- 送信者のメールアカウント情報
- 添付ファイルの保存先
- ついでに結合した PDF ファイルの保存先も作成
- Outlook を起動
- Outlook を閉じる
各種情報は変数にして準備しておきます。直接アクションに書いてもよいのですが後から見た人のことを考慮したほうが良いと考えています。
フローを動かすと添付ファイルを取得できます。
PDF ファイルを統合アクションを調べる
統合したい PDF ファイルの指定は
- 統合したいファイルのパスをダブルクォーテーションで囲い、区切り記号で記述する
- ファイルパスのリストを記述する
2 パターンあるようです。
また保存先とファイル名を指定する必要があります。
今回のフローでは、ペアをどのようにつくるかがポイントになりそうです。
ファイルパスのペア作成方法を検討
ファイル名のルールを調べます。
「数値化された日付-金額-meisai or kagami.pdf」であらわされていました。
そこで思いついたのは次の 2 つです。
- ファイル名で並び替えて 2 個づつパスを取得しながら統合する
- ファイル名のルールでパスをグループ化したリストから統合する
作成が簡単なのは 1 なので、とりあえず 1 で作成します。
ファイルパスのペアで PDF を統合
余分なファイルを除いたファイルリストを作成します
添付ファイルフォルダーからお知らせなど余分なファイルは除いたリストを作成します。それには*kagami.pdf;*meisai.pdf
でフィルターします。また PDF 統合順を考慮して、ファイル名を降順に並び替えておきます。昇順の場合、想像とは逆の並びで(この場合は meisai が先に)統合されるからです。
ファイルパスをペアを作成
2 ファイル単位でパスを取得するためループを増分 2 で行います。終了はファイルパスリストの数-1 とします。Index 指定では 0 から開始されるので実数 (Files.Count)から-1 が終了になるからです。
PDF ファイルの統合アクションにパスと保存先を指定します。
PDF ファイルのパスはダブルクォーテーションで囲ってカンマ区切りで記述します。"%Files[LoopIndex]%","%Files[LoopIndex + 1]%"
現在の LoopIndex 番目と LoopIndex+1 番目のパスでペアにします。
とりあえず保存先ファイルの指定は準備したフォルダーに適当な名前にして動作が正常か確認します。
うまく統合されていますが、ファイル名が仮のままなので「日付+金額」になるように工夫します。
テキストの解析アクションをつかって LoopIndex 番目ファイルのファイル名から正規表現をつかって「日付+金額」を抜き出します。
解析するテキスト%Files[LoopIndex]['NameWithoutExtension']%
検索するテキスト(.*)(?=-meisai)
仮で PDF ファイルの統合アクションで適当に aaa.pdf とファイル名をつけていたので%Match%.pdf
とします。
しかしこれでは終わらなかった
数か月後の話です。
センパイ「PDF くっつけるやつうまくいかなくなったんだけど」
私「え!?」
センパイ「いやさ、「明細」が 2 枚ついてるのがあってね」
私「3枚組もあるってことですか?」
センパイ「うむ」
私「・・・・」
kagami と meisai の単純なペアだけでないことが判明しました。
別の方法を考える
グルーピングをするアイデアでフローの一部作り直します。
「日付+金額」の部分を正規表現で抜きだしてグルーピングすれば何とかいけそうです。
グルーピング処理は PowerShell が楽なので PowerShell スクリプトの実行アクションを使います。そしてここで Generate script with Copilot PREVIEW を試しました。
今のところ US テスト環境のみ試用可能ですが、Prompt は日本語で書いても問題ありません。数回調整(作文)は必要ですが、ここまで出力されます。また内容を Windows の Copilot(プレビュー) にの解説してもらいました。
このPowerShellスクリプトの解説をします。
まず、変数$folderPathにフォルダのパスを代入します。このフォルダには、-kagami.pdfや-meisai.pdfという接尾辞を持つPDFファイルが含まれていると仮定します。
次に、変数$regexに正規表現を代入します。この正規表現は、接尾辞を除いたファイル名の部分をキャプチャするものです。例えば、test-kagami.pdfというファイル名なら、testという部分をキャプチャします。
次に、Get-ChildItemコマンドレットを使って、$folderPathで指定したフォルダ内のすべてのファイルを取得します。その中から、Where-Objectコマンドレットを使って、$regexにマッチするファイル名を持つものだけを絞り込みます。Sort-Objectコマンドレットを使って、ファイル名の降順で並び替えます。この結果を変数$filesに代入します。
次に、$filesをGroup-Objectコマンドレットに渡して、キャプチャした部分(つまり、接尾辞を除いたファイル名の部分)でグループ化します。例えば、test-kagami.pdfとtest-meisai.pdfは同じグループになります。この結果を変数$groupedFilesに代入します。
次に、空の配列$outputを作ります。$groupedFilesの各要素に対して、以下の処理を行います。
新しいオブジェクト$groupObjectを作ります。
$groupObjectにGroupというプロパティを追加し、値にグループ名(キャプチャした部分)を設定します。
$groupObjectにFilesというプロパティを追加し、値にグループに属するファイルのフルパスの配列を設定します。
$outputに$groupObjectを追加します。
最後に、$outputをConvertTo-Jsonコマンドレットに渡して、JSON形式に変換します。
このスクリプトの目的は、フォルダ内のPDFファイルを接尾辞でグループ化し、JSON形式で出力することです。
フォルダーへのパスを書き換えれば、このまま使えますが、Copilot の解説にもあるようにファイルパスを抽出するとき、このスクリプトでは「日付+金額」でマッチングさせているので"-kagami\.pdf|-meisai\d?\.pdf"
でフィルターがかかるように修正します。また個人的には ForEach-Object を使用したほうがスッキリするので少し修正しました。
# This code has been generated by AI. Original prompt:
# フォルダ内のファイルパスリストを取得します。
# ファイルは{-kagami.pdfまたは-meisai\d?.pdf}でフィルターをかけてく降順にしてください。。
# リストは正規表現で{-kagami.pdfまたは-meisai\d?.pdf}の前の文字でグルーピングしてJSONで出力してください。
# グループごとにファイルパスリストが出力されるようにしてください。
$folderPath = "%RecivedPDF%" # replace with your folder path
$filter = "-kagami\.pdf|-meisai\d?\.pdf"
$regex = "(.*)(-kagami\.pdf|-meisai\d?\.pdf)"
$files = Get-ChildItem -Path $folderPath -Recurse -File | Where-Object { $_.Name -match $filter } | Sort-Object -Property Name -Descending
$groupedFiles = $files | Group-Object -Property { if ($_.Name -match $regex) { $matches[1] } }
$jsonOutput = $groupedFiles | ForEach-Object {
[PSCustomObject]@{
GroupName = $_.Name
FilePaths = $_.Group.FullName
}
} | ConvertTo-Json
Write-Output $jsonOutput
スクリプトを実行すると次のような JSON を取得できます。
あとはカスタムオブジェクトに変換し、For each でグループごとのファイルパスリストを使用してPDF ファイルを統合すれば完成です。
少し面倒な処理をPowerShellスクリプト実行アクションに任せることでスッキリしました。
これでmeisaiファイルが増えても大丈夫そうです。
感想とまとめ
メールに添付されたPDFを統合するちょっとしたフローを作るときの思考過程を書きました。
要件をひろう→簡単な作文→使えそうなアクションの仕様を調べる=仕組みを考える→組立てる→動かす→うまくいかなかったら直す のような流れで作成しています。「アクションの仕様を調べる=仕組みを考える」は同時に行っているイメージです。前提知識として持っているものは調べてない場合もあります。簡単な作文時点でぼんやり完成形を思い浮かべてることが多いです。
また、実務ではもう少し変数名を意味のあるものにしたほうが、後から見た人が読みやすくなると思います。
Generate script with Copilot についてはとりあえずPromptに作文してみるというのはどうでしょうか。そのさい、具体的な聞き方をするとかなり良い感じです。具体的な聞き方をするためには目的をはっきり言語化することが求められます。今のところスクリプトの中身がわからないま手放しで使うのは難しいですが、得られたスクリプトの内容を Learn で調べて確認していくと自ずと理解が深まると思います。私自身はそんな付き合い方してみようかなと思いました。
この記事公開日でまたひとつ歳を重ねました。
おしまい