【Power Automate】Exchange 予定表のトリガーについて

## はじめに
Power Platform を活用する場合、Exchange と連携したいシーンが多そうに感じますが、Exchange 予定表に関する操作をフローのトリガーとした際に、めちゃくちゃややこしかったので忘れないように纏めておきます。

（主に Exchange の挙動や「イベントが追加、更新、削除されたとき (V3)」トリガーの内容が中心となるため、正直 Automate の内容は薄いです。）

::: note warn
正確ではない情報が多々ありそうなので予めご承知おきをお願いします…。
:::

## 前提
Exchange で予定（以下、 **Exchange イベント** と呼称）が「追加・更新・削除」されたタイミングでフローを実行する場合、「自動化したクラウドフロー」の下記のトリガーを使用します。

**[イベントが追加、更新、または削除されたとき (V3) - Office 365 Outlook]**

https://learn.microsoft.com/ja-jp/connectors/office365/#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%8C%E8%BF%BD%E5%8A%A0%E3%80%81%E6%9B%B4%E6%96%B0%E3%80%81%E3%81%BE%E3%81%9F%E3%81%AF%E5%89%8A%E9%99%A4%E3%81%95%E3%82%8C%E3%81%9F%E3%81%A8%E3%81%8D-(v3)

**[フロー作成時のイメージ]**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/0e613308-9e65-d9c3-36ec-a1f649e1534d.png)
**[フロー処理のイメージ]**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/56890c27-b026-4bc6-1828-6a9e73079423.png)
自身の予定表にイベントが「追加・更新・削除」されたときに実行されて、対象の Exchange イベントの情報を取得できるようになります。

## Exchange イベントの取得情報
上記のフローをテスト実行すると、以下イメージのように Exchange イベントの取得情報を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/47109e76-7578-641d-331a-c98de6137bf0.png)
**[Exchange イベント情報の出力内容例]**　※ 一部マスク済み
```json:Sample
{
    "headers": {
        "Pragma": "no-cache",
        "Transfer-Encoding": "chunked",
        "Retry-After": "3600",
        "Vary": "Accept-Encoding",
        "x-ms-request-id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
        "Strict-Transport-Security": "max-age=31536000; includeSubDomains",
        "X-Content-Type-Options": "nosniff",
        "X-Frame-Options": "DENY",
        "Cache-Control": "no-store, no-cache",
        "Location": "https://japan-001.azure-apim.net/apim/office365/XXXXXXXXXXXXXX/datasets/calendars/v3/tables/XXXXXXXXXXXXX",
        "Set-Cookie": "ARRAffinity=XXXXXXXXXXX;Path=/;HttpOnly;Secure;Domain=office365-jw.azconn-jw.p.azurewebsites.net,ARRAffinitySameSite=XXXXXXXXXX;Path=/;HttpOnly;SameSite=None;Secure;Domain=office365-jw.azconn-jw.p.azurewebsites.net",
        "Timing-Allow-Origin": "*",
        "x-ms-apihub-cached-response": "true",
        "x-ms-apihub-obo": "false",
        "Date": "Tue, 04 Oct 2022 02:52:23 GMT",
        "Content-Type": "application/json; charset=utf-8",
        "Expires": "-1",
        "Content-Length": "2622"
    },
    "body": {
        "ActionType": "added",
        "IsAdded": true,
        "IsUpdated": false,
        "subject": "件名",
        "start": "2022-10-04T03:00:00.0000000",
        "end": "2022-10-04T04:00:00.0000000",
        "startWithTimeZone": "2022-10-04T03:00:00+00:00",
        "endWithTimeZone": "2022-10-04T04:00:00+00:00",
        "body": "<html>\r\n<head>\r\n<meta http-equiv=\"Content-Type\" content=\"text/html; ・・・～",
        "isHtml": true,
        "responseType": "none",
        "responseTime": "0001-01-01T00:00:00+00:00",
        "id": "AAXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXAAA=",
        "createdDateTime": "2022-10-04T02:52:22.5862695+00:00",
        "lastModifiedDateTime": "2022-10-04T02:52:13.701+00:00",
        "organizer": "a.uemura@xxxxxxxxx.co.jp",
        "timeZone": "UTC",
        "seriesMasterId": "AAXXXXXXXXXXXXXXXXX-XXXXXXXXXXXXXXXAAA=",
        "iCalUId": "000000000000000000000000000000XXXXXXXXXXXXXXXXXXXXXXXX",
        "categories": [],
        "webLink": "https://outlook.office365.com/owa/?itemid=AAXXXXXXXXXXXXXXXXXXAAA%3D&exvsurl=1&path=/calendar/item",
        "requiredAttendees": "a.uemura@xxxxxxxxx.co.jp",
        "optionalAttendees": "",
        "resourceAttendees": "",
        "location": "",
        "importance": "normal",
        "isAllDay": false,
        "recurrence": "none",
        "reminderMinutesBeforeStart": 15,
        "isReminderOn": true,
        "showAs": "busy",
        "responseRequested": true,
        "sensitivity": "normal"
    }
}
```
上記の **body** の内容は「GraphCalendarEventClientWithActionType」モデルで定義されているものです。（恐らく・・・）

https://learn.microsoft.com/ja-jp/connectors/office365/#graphcalendareventclientwithactiontype

### トリガー発火のパターン
Exchange イベントの「追加・更新・削除」に伴うトリガー発火のパターンとして、基本的には大きく２つを意識する必要があると思います。

* 自身が開催した予定（以下、自開催予定 と呼称）
* 出席者として自身が追加された、他者が開催した予定（以下、他者開催予定 と呼称）

:::note warn
他にも「代理人アクセス」や「共有メールボックス」といった設定などによっても変わってくるかもしれませんが、そこまでは検証できていないので割愛・・・
:::

上記２パターンに対する「追加・更新・削除」時の挙動は以下となります。
<font color="DarkGray">（実際に検証して１つ１つ目視で差異を洗い出しました・・・）</font>

| パターン | 操作 | 自予定のトリガーの挙動（アクション／回数） |
|:-|:-|:-|
| 自開催予定  |  追加 |  追加が２回、更新が複数回<br><font color="red"> **※※「AD場所」の指定時 ※※** <br>```location”（AD場所名）```は最初から追加されているが```resourceAttendees”（AD場所メールアドレス）```は最初空文字で、後から更新時に追加される</font> |
|   |  変更 |  更新が複数回 |
|   | 削除  |  削除が１回（出力される項目が減る） |
| 他者開催予定  | 追加  | 自開催と同じ（追加が２回、更新が複数回）  |
|   |  変更 | 自開催と同じ（更新が複数回）  |
|   |  開催者が自身の予定から削除 | 更新が１回（削除はされず、削除待ち扱いで更新）<br>・件名に取り消し線が表示される<br>・件名の先頭に “Canceled: ” または “キャンセル済み: ” が付く<br>⇒　ユーザー個別の地域／言語設定などが影響？

それぞれのアクションとトリガーの発火に伴う各項目の変化は以下となります。
<font color="Gray">（追加時や更新時にトリガーが何度も発火していますが、出力内容だけ見ても差がない場合もあるので、複数回発火している理由はよく分かりません。）</font>

| アクション | トリガー<br>発火回数 | ActionType | IsAdded | IsUpdated | importance | showAs
|:-|:-:|:-:|:-:|:-:|:-:|:-:|
|  予定追加時 | １回目  | **added**  | **true**  | **false**  |  normal |  busy |
|   |  ２回目 | **added**  | **true**  | true  | normal  | busy  |
| <font color="Gray">（”resourceAttendees” の更新はこのタイミング）</font>  |  ３回目以降 |  updated |  false |  true | normal  |  busy |
| 予定更新時  | １回目以降  |  updated | false  |  true | normal  | busy  |
|  他者開催の予定キャンセル時 | １回  | updated  |  false |  true | **high**  | **free**  |
| 自予定削除時  |  １回 |  **deleted** | false  | **false** | **low**  |  free |

### 項目別の特記事項
上記に記載した内容と重複する部分もありますが、各項目の特記事項を纏めます。

| 項目名 | 特記事項 |
|:-|:-|
| ActionType<br>（アクションの種類）  | 予定追加時：added<br>予定更新時：updated<br>他者開催の予定キャンセル時：updated<br>自予定から削除時：deleted  |
| IsAdded<br>（追加されたか）  | 予定追加時：true<br>上記以外：false  |
| IsUpdated<br>（更新されたか）  |  予定追加時と自予定から削除時：false<br>上記以外：true |
|subject（件名） |出席者として自身が追加された、他者が開催した予定がキャンセルされた場合：<br>・件名に取り消し線が表示される<br>・件名の先頭に “Canceled: ” または “キャンセル済み: ” が付く<br>（※ 自予定から削除時は項目ごと出力されない）
|start（開始日時） |（※ 自予定から削除時は項目ごと出力されない）
|end（終了日時） |（※ 自予定から削除時は項目ごと出力されない）
|startWithTimeZone<br>（タイムゾーン付きの開始日時） |（※ 自予定から削除時は項目ごと出力されない）
|endWithTimeZone<br>（タイムゾーン付きの終了日時） |（※ 自予定から削除時は項目ごと出力されない）
| body（本文）|（※ 自予定から削除時は項目ごと出力されない）
|isHtml（HTMLか） |-
|responseType<br>（応答の種類） |-
|responseTime<br>（応答した日時） |他者開催予定の招待に、承諾や辞退などを返信した日時<br>（※ 自予定から削除時は項目ごと出力されない）
|id |一意なイベント識別子。他のユーザーを出席者として共有している同じイベントの場合でも、それぞれ異なるＩＤとなる。
|createdDateTime<br>（作成日時） |（※ 自予定から削除時は項目ごと出力されない）
|lastModifiedDateTime<br>（更新日時） |（※ 自予定から削除時は項目ごと出力されない）
|organizer（イベント<br>開催者のメールアドレス） |（※ 自予定から削除時は項目ごと出力されない）
|timeZone<br>（タイムゾーン） |（※ 自予定から削除時は項目ごと出力されない）
|seriesMasterId |定期的なイベント間の一意な識別子。<br>（※ 自予定から削除時は項目ごと出力されない）
|iCalUId |カレンダー間のイベントの一意の識別子。他ユーザーと共有している同じイベントの場合は同じＩＤとなる。<br>（※ 自予定から削除時は項目ごと出力されない）
|categories（分類） |リボンの [予定]タブ -> [タグ] グループ 内にある “分類”<br>　⇒　”黄の分類”, “オレンジの分類” などが定義されている<br>（※ 自予定から削除時は項目ごと出力されない）
|webLink |Outlook Web App の対象イベントの URL<br>（※ 自予定から削除時は項目ごと出力されない）
|requiredAttendees<br>（必須出席者） |複数のメールアドレスをカンマ区切りで格納。<br>予定追加時には反映されず空文字が入り、追って更新時に反映される。<br>（※ 自予定から削除時は空文字）
|optionalAttendees<br>（任意出席者） |複数のメールアドレスをカンマ区切りで格納。<br>（※ 自予定から削除時は空文字）
|resourceAttendees<br>（リソース出席者） |複数のメールアドレスをカンマ区切りで格納。<br>（※ 自予定から削除時は空文字）
|location（場所名） |複数の場所名をカンマ区切りで格納。<br>ADに定義されていない場所も自由入力可能。<br>（※ 自予定から削除時は項目ごと出力されない）
|importance（重要度） |他者開催の予定キャンセル時：high<br>自予定から削除時：low<br>上記以外：指定した重要度<br>（通常時：normal）
|isAllDay<br>（終日イベントか） |-
|recurrence<br>（繰り返しの周期） |なし、毎日、毎週、毎月、毎年<br>（none, daily, weekly, monthly or yearly）
|recurrenceEnd<br>（繰り返しの終了日） |（※ 定期的な予定で「終了日」を指定した場合のみ有る項目）<br>（※ 自予定から削除時は項目ごと出力されない）
|numberOfOccurences<br>（繰り返しの反復回数） |（※ 定期的な予定で「反復回数」を指定した場合のみ有る項目）<br>（※ 自予定から削除時は項目ごと出力されない）
|reminderMinutesBeforeStart<br>（何分前に通知するか） |（※ 自予定から削除時は項目ごと出力されない）
|isReminderOn<br>（通知するか） |（※ 自予定から削除時は項目ごと出力されない）
|showAs<br>（ユーザーの状況） |他者開催の予定招待時：tentative（保留）<br>他者開催の予定キャンセル時：free（空き時間）<br>自予定から削除時：free（空き時間）<br>上記以外：指定した公開方法<br>（通常時：busy（取り込み中））
|responseRequested<br>（応答を要求するか） |イベントの承諾または拒否時に送信者の応答を希望するか<br>（※ 自予定から削除時は項目ごと出力されない）
|sensitivity（機密度） |通常時：normal（標準）<br>非公開設定時：private

### 要注意事項
#### AD場所の指定について
「場所」ダイアログからAD登録された場所を選択すると、下記イメージのように [必須] と [場所] の２箇所にAD場所の名称が追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/6c1d7f42-a720-17d9-2f40-304a57c9a25e.png)
Exchange イベントの変化の流れとしては、先ず追加（または更新）時に ``location`` にAD場所名（”会議室１”）が指定されます。その後、追って ``resourceAttendees`` にAD場所メールアドレス（例：”room1@xxxxx.co.jp”）が更新されます。

しかし、手動でAD場所名だけを消して登録すると “会議室１” は出席者として扱われてしまいます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/3ce2acd2-f1af-716e-42f1-722281e308e8.png)
この場合は、Exchange イベントの追加（または更新）時に ``location`` や　``resourceAttendees`` には内容が反映されず、”requiredAttendees“（必須出席者）にメールアドレスが反映されてしまう点に注意してください。
~~（何でこんなややこしい仕組みになっているのかは分かりません・・・）~~

#### Exchange イベントの”出席者のみ”の変更時
Outlook から Exchange イベントを更新した際、出席者全員に影響のない内容の更新（”必須出席者”を新たに１人追加したなど）をした場合、以下の確認ダイアログが表示されます。

**[“出席者に更新を送信する” 確認ダイアログ]**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241051/ee4fdbdb-a9fb-680d-bc74-6556fabb5173.png)
このダイアログで「追加または削除された出席者にのみ更新を送信する」が選択されると、それ以外の出席者の Exchange イベントではトリガーが発火しないため注意が必要です。
（出席者として共有している予定でも、ユーザー個々の Exchange イベントは独立しているため）

#### 定期的な（繰り返しの）予定について
定期的な予定を作成した場合、大きく「開始時の予定」と「それ以降の予定」とで区別されるようです。開始時の予定は【親イベント】、それ以降の繰り返しの予定は【子イベント】のような扱いとなり、**【子イベント】のトリガーで出力される情報は【親イベント】のものとなってしまう模様。**

https://note.com/kernel_91/n/nad612b8e61c8

そのため、通常の Exchange イベントを処理するものとは別に、定期的な予定については意識して処理を実装する必要があります。

## さいごに
Exchange は普段から触れているものの、改めて挙動を確認すると非常にややこしかったです。
また各種の権限に関わるものや、WEB版Outlook（OWA：Outlook Web App）まで意識する必要がでてきたりなど、思ったよりも知識が必要となり時間がとられました。。。

「イベントが追加、更新、削除されたとき (V3)」トリガーによって自動化したフローを実行する場合は、意図せず複数回の呼び出しが行われることを前提に、冪等性が担保される（何回呼ばれても同じ結果が得られる）ようにするか、前回呼び出し時の内容と比較して変更がなければスキップするなどを考慮した設計をする必要がありそうです。

また、ユニフェイスでは新しいことにチャレンジしたい人材を募集しておりますので、気になる方はぜひご応募お願いします😊
[技術ブログ](https://hub.uni-face.co.jp/)も公開していますのでこちらもチェックしてみてください！

https://uni-face.co.jp/