FormsとPower AutomateでEntra IDのゲスト招待システム&棚卸システムをつくってみる(その8:棚卸編)

前回は棚卸期限が2週間に迫ったゲストを招待したユーザーに、ゲストをテナントに残すか解除するかのメールを送り、ワンクリックで返信された結果によって棚卸期限を90,180,360日のように延長する処理を作りました。

これまでとこれから

  1. その1)Formsでユーザーがゲスト招待申請を投稿する。
  2. その1)クラウドフその1,その2ローがSPOリストの追加によってトリガーされる。
  3. その2)入力されたゲストEmailがメールアドレスとして妥当か(正規表現が使える?)確認して、受付または却下メールを申請者へ送る(却下ならステータスを「却下」にして、備考に理由を書き込む。妥当ならステータスを「承認待ち」にして申請日を書き込む)。
  4. その2)承認者ユーザーに承認要求を送る。
  5. その3)承認が通ったら承認日時に書き込み、ステータスを「承認済み」に更新して次へ進む。
  6. その3)却下の場合は、承認者のコメントを含めたメールを申請者に送る。
  7. その3)14日間承認されない場合は、ステータスを「承認待ち14日期限切れ」に変えて申請者へメールを送る。
  8. その3)各ステータスをSPOリストに書き込む。
  9. その4)サービスプリンシパルを使ってHTTP要求で対象の外部ユーザーをEntra IDのゲストとして登録する。
  10. その5)すでにゲストがテナントに登録済みならば、申請者へ伝える。
  11. その5)登録が成功したら、SPOリストの棚卸期限列に、ゲスト招待期間の数字を加えた日付を登録する。
  12. その6)登録が成功したら、承認者と申請者にメールで通知する。
  13. その6)登録が失敗したら、管理者へ通知する(チャネル投稿)
  14. その7)棚卸期限2週間前になったら毎日申請者へ90,180,360日延長または終了の選択を求めるメール送信する。
  15. その7)メールの返信に応じたステータスをSPOリストに書き込む
  16. (今回)棚卸期限に達したゲストユーザーをEntra IDから削除して、ステータスをSPOリストに書き込む

処理はスコープに分ける

申請者がゲストの終了を選択した場合は、ゲストの状態を管理するSPOリストのステータスを「ゲスト削除予定」に更新する処理が動くようにしました。

ということは、「ゲスト削除予定」ステータスになっているユーザーを、テナントに登録されているユーザーのなかから削除すれば棚卸システムが完成しそうです。

この処理を別のクラウドフローに分けてもよいのですが、棚卸メールを送信する前回のクラウドフローも1日1回動作するので、同じクラウドフローに並列で処理を加えようと思います。

こういうときに便利なのが、みんな大好き「スコープ」です。処理をひとかたまりスコープの中にまとめておくと、あとで処理を追いやすくなります。エラーが出た場合の管理もしやすくなるのでおすすめです。

ひとまずこんな感じです。トリガーはこの段階ではテスト用に手動実行にしています。

SPOリストはこんな状態になっています。「ゲスト削除予定」ステータスが1行あります。

「延長しないゲストの削除処理」の中に、はSPOリストの一覧を取得するためのアクションを設定しました。ポイントはフィルタークエリに以下を設定しているところです。

ApprovalStatus eq 'ゲスト削除予定'

テスト実行して確かめますが、その前にもう一つのスコープの中のアクションの実行条件を変えて、こちらは動作しないようにしておきましょう。

ODATAクエリについては、コルネさんのブログ記事がとても参考になります。Power Automateだけでなく、REST APIではこう書けばよいのかと非常に勉強になります。

結果はこのようなJSONが返ってきます。ステータスが「ゲスト削除予定」の1件のみが取得できました。

さて、Entra IDからユーザーを削除してしまえばよいのだから、きっとそういうアクションがあるだろう? と思って探してみてもユーザー削除できそうなのが見当たりません。

そこで Microsoft のGraph APIに関するドキュメントを探してみると、こちらが見つかりました。REST APIへDELETEのリクエストを送れば削除できるようです。ドキュメントにもありますが、削除するには、User.ReadWrite.All の権限が最低でも必要です。

DELETE https://graph.microsoft.com/v1.0/users/{user-id}

サービスプリンシパルに権限を追加する

サービスプリンシパルについては、このシリーズの(その4)で、ゲストをEntra IDに追加するために作成済みです。しかし、その際にユーザーを削除する権限は与えていなかったので、追加する必要があります。

作成済みのサービスプリンシパル(アプリ登録)を開き、「APIのアクセス許可」をクリックします。やっぱり削除権限はありません。「+アクセス許可の追加」をクリックしたあと右に現れる、「Microsoft Graph」をクリックします。

「アプリケーションの許可」をクリックして、「User.ReadWrite.All」を検索してチェックマークを入れ、「アクセス許可の追加」をクリックします。

追加ができたら、「○○に管理者の同意を与えます」をクリックします。ユーザーを削除するような高権限は、管理者が同意しないと使えません。会社にもよりますが、一般ユーザーや開発者に割とゆるく「アプリ登録」の操作を許している場合、管理者以外も登録する場合があります。そのような状況でも、こうした高権限については管理者の同意が必要なのでこのようなステップが設けられているのだと思います。

「複数の項目の取得」から返ってくる結果は配列担っているので、削除対象のゲストユーザーの名前をURIの最後に加えてやります。その他に必要な項目3点については(その4)で説明しているので参考にしてください。

おやおや? そんなメールアドレスのユーザーはいないと叱られました。もうお気づきですね。(私はエラー出るまで気が付きませんでしたが)

ゲストメールアドレスを変換する

Entra IDに登録されたゲストユーザーのUPNは、

こちらは(その5)で紹介している変換方法を使います。

一覧から取得したゲストのEmailアドレスをテナント内のUPNへ変換するために以下のような式を使いそれをHTTP要求アクションに渡してやるように修正しました。

concat(replace(item()?['GuestEmail'],'@','_'),'#EXT#@0phny.onmicrosoft.com')

あれ? またダメ。 もしかしてURLのなかに#EXT#とか含まれているのが良くないのかしら。

UPNをオブジェクトIDに変換する

URLとしてふさわしくないのであれば、テナント内のゲストのUPNをオブジェクトIDに変換してやりましょう。テナント内のユーザーなのだから、「ユーザープロフィールの取得(V2)アクションが使えるはずです。

いったん渡した結果のJSONの動的コンテンツからIDをHTTP要求のお尻に加えてやります。

やっぱり! こんどはバッチリHTTP要求にエラーが出ずに終了しました。Microsoft365管理センターで確認すると、しっかりゲスト欄から消えていました。

SPOリストのステータスを削除済みにする

Entra IDからは削除されましたが、SPOリストのステータスも変更して置かなければ、何度も削除対象としてSPOリストの一覧取得に拾われてしまします。

「複数の項目の取得2」で拾われた「ゲスト削除予定」のユーザーは、Apply to each2で1人ずつ処理されます。あらかじめテスト実行の際に「複数の項目の取得2」の実行結果JSONをテキストエディタに貼り付けておきます。

必要な情報はここに揃っています。Apply to Eachやその他配列をアクションの中で処理する場合は item()?['○○’]のように指定します。

本来変更がない箇所は空欄でよいのですが、SPOリストで必須にした列は都度上書きしないといけません。列名どおりに入れてやります。もちろんステータスだけは「削除済み」にしてやります。

最後の列のは日本の現在時刻を表す関数です。

item()?['id']

item()?['GuestEmail']

item()?['GuestName']

item()?['GuestAffiliation']

item()?['ApplicationReason']

item()?['GuestInvitationPeriod']

convertTimeZone(utcNow(), 'UTC', 'Tokyo Standard Time', 'yyyy/MM/dd hh:mm')

すでに先程のテストでゲストを削除してしまったので、Microsoft 365管理センターで手動でゲスト追加をしてからテストに望みます。(そうしないとHTTP要求で多分エラーがでるとおもうので)

もしくは、HTTP要求のところに「静的な結果(プレビュー)」を設定しておいても良いかもしれません。

テスト実行を行った結果がこちらです。ステータスは「削除済み」に変わり、メモ欄に削除日付が記録されています。

なにか忘れているぞ・・・。

このクラウドフローの中で延長しない場合に行っている処理を振り返ってみます。

  • ステータスが「ゲスト」である招待ユーザーの中から、棚卸期限が2週間を切っているゲストを招待した申請者に延長か終了か選択させるメールを送る
  • 延長しない場合はステータスを「ゲスト」から「ゲスト削除予定」に変更する
  • ステータスが「ゲスト削除予定」の招待ユーザーをEntra IDから削除して、ステータスを「削除済み」に変更する

以上です。

これだと、棚卸期限を超えてもメール返信をしない招待したユーザーに対して棚卸依頼メールを送り続けてしまいますね。

もうひとつ大きな誤りは、「ゲスト削除予定」のステータスのユーザーを、そのまま削除しまっていることです。申請者が延長しないことを選択した場合も、棚卸期限まではゲストでいるべきでしたね。

延長しないゲスト一覧を作るアクションに棚卸期限を超えているという条件を加える

こちらが問題の箇所です。フィルタークエリには、ステータスが「ゲスト削除予定」しか条件が指定されていません。

ひとまず、申請者が終了を選択した場合のケース。ステータスが「ゲスト削除予定」になっていて、なおかつ棚卸期限が本日である場合です。SPOリストを条件に合うように修正してテストしてみると、一覧の中に対象ユーザーが取れていることを確認できました。

ApprovalStatus eq 'ゲスト削除予定' and InventoryDeadline lt '@{utcNow()}'

このクエリに、さらに現在は「ゲスト」のステータスで、棚卸期限に達した場合という条件を加えます。

(ゲスト削除予定 and 期限超え ) or (ゲスト and 期限超え) のような構成になっています。

(ApprovalStatus eq 'ゲスト削除予定' and InventoryDeadline lt '@{utcNow()}') or (ApprovalStatus eq 'ゲスト' and InventoryDeadline lt '@{utcNow()}')

ちなみに、(ゲスト or ゲスト削除予定) and (期限超え) を表す以下のような書き方でもOK。こっちのほうがシンプル。

(ApprovalStatus eq 'ゲスト' or  ApprovalStatus eq 'ゲスト削除予定' ) and (InventoryDeadline lt '@{utcNow()}')

おさらい

データに誤りさえなければ、ここまででゲストの棚卸システムほぼ完成です。

  • スコープをつかって処理を分類しました。2つの別の処理ですが、1つのタイマートリガーを使って、並列分岐で動作させました。
  • Entra IDからユーザーの削除は通常のアクションを使えないため、HTTP要求でGraph APIの usersエンドポイントに対してDELETEのリクエストを送る方法を使いました。
  • ゲストユーザーのUPNには#などの文字が含まれていて、そのままGraph APIのURLとしては使えないため、UPNを「ユーザープロフィールの取得(V2)」アクションを使ってユーザーアカウントのオブジェクトIDに変換して使いました。
  • テナントから削除するユーザーの一覧を取得するために、ステータスが「ゲスト」または「ゲスト削除予定」であり、なおかつ棚卸期限が本日よりも小さいという条件のODATAクエリフィルターを使いました。

こんな人が書いてます。

今回も、やってみないとわからないことがいろいろあって勉強になりました。

おそらく次回が最後。全体的なエラー処理を追加します。

QiitaやこのブログではPower Automateの話題を中心に投稿を行っていますので、ぜひ「いいね」してくださると励みになります。