M365 予定表を Power Automate で Googleカレンダー へ同期する（追加・編集・削除 全対応）トリガーV3更新版

当記事は No Code・Low Code で Microsoft 365（M365）/Office 365(O365)の Exchange Online 予定表を、Google カレンダー へ同期するフロー ～2020年改訂版～ です。ただし「繰り返しの予定」以外、という前提で。

[以前に紹介したタイミング](https://qiita.com/yamad365/items/830b64954dcdfcb91fe2) から時間も経過しておりますし、トリガーがバージョンアップしているので、2020年版として手順を更新してまとめなおす目的です。

特筆すべき点は、今回は「Apply to each」（繰り返し処理）の撲滅です。その結果、以前のフローと比較して実行速度が約10倍の改善になっています(当方比較)。もし旧版を参考にしたフローを利用している方がいたら、当バージョンで実行した結果を比べてみてください。感動しますよ（たぶん。

# Special Thanks & 参考URL
Power Automate の「Apply to each 撲滅」に尽力されている（まいどおなじみ）[Hiro](https://twitter.com/mofumofu_dance) さんのナレッジにより、当フローは高速化、シンプル化が実現できました。まいどありがとうございます。

[Apply to each のパフォーマンス改善 (配列加工の場合)](https://mofumofupower.hatenablog.com/entry/2020/08/12/093000)

# 前提条件
「繰り返し」の予定には対応ていしません。理由は[以前](https://qiita.com/yamad365/items/830b64954dcdfcb91fe2#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6)と変わりありません。同様に、365 → Googleカレンダーの一方通行です。

当方の経験則ですが下記のような点が初学者の方には”躓きポイント”になるかな、と個人的に考えております。

■初学者が躓きそうなポイント
 1. 以前のフローと比較してチョッとだけ関数を利用するシーンが多い
 2. 「配列」の概念が無いと、何してるの？ってなる箇所があるかも

上記の困りそうなポイントについては、調べたり、試してみれば把握できる内容だと思います。基礎学習は自力で実施を推奨したいと当方は考えていますので、当記事で詳細に基礎を解説することはありません。その他、当記事へ質問等コメントいただいても全て回答するとは限りませんので、予めご了承ください。

是非、ご自身で学習を。

自分のちからで小さな改善ができると、とても気持ちが良いですよ。「どうしてもできない・・・」という場合は、関連コミュニティで質問するか、お仕事としてご依頼可能なところ（当方含めて）へご相談ください。
# 全体像
全体のイメージはこんな感じです。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/9c7184d5-237d-bd6b-5ef1-d31bd5f1aa16.png)

## 動作イメージ
動作確認をわかりやすくするために「テスト実行 → 予定表操作」という手順で撮影しています。実際に利用する際は、テスト実行をしていなくても予定表を更新すればフローが着火します。ご安心ください。

### 新規追加（Added）
<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/PowerAutomate?src=hash&amp;ref_src=twsrc%5Etfw">#PowerAutomate</a> ExO sync to Google calendar. Added <a href="https://t.co/6GbVderUPc">pic.twitter.com/6GbVderUPc</a></p>&mdash; やま (Yama) (@yamad365) <a href="https://twitter.com/yamad365/status/1330486123701923841?ref_src=twsrc%5Etfw">November 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### 更新（Updated）
<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/PowerAutomate?src=hash&amp;ref_src=twsrc%5Etfw">#PowerAutomate</a> ExO sync to Google calendar. Updated <a href="https://t.co/DLDWfBnphm">pic.twitter.com/DLDWfBnphm</a></p>&mdash; やま (Yama) (@yamad365) <a href="https://twitter.com/yamad365/status/1330486322478268416?ref_src=twsrc%5Etfw">November 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
### 削除（Deleted）
<blockquote class="twitter-tweet"><p lang="en" dir="ltr"><a href="https://twitter.com/hashtag/PowerAutomate?src=hash&amp;ref_src=twsrc%5Etfw">#PowerAutomate</a> ExO sync to Google calendar. Deleted <a href="https://t.co/F5ACUPK7e4">pic.twitter.com/F5ACUPK7e4</a></p>&mdash; やま (Yama) (@yamad365) <a href="https://twitter.com/yamad365/status/1330486533279809537?ref_src=twsrc%5Etfw">November 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
# 解説
ある程度 Step by Step で作成手順を説明していきます。が、式の書き方や設定の仕方など細かな点は割愛することがありますのでご留意ください。また、以前の手順を参照いただければ把握できるポイントについても画像や解説を割愛する場合があります。

## 全体像と解説の番号
前述のフロー全体イメージと解説は下記の番号でリンクさせるようにしておきます。
![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/87da59ff-c3e9-36b6-b7d4-b106dc74318c.png)

## ①：[イベントが追加、更新、削除されたとき (V3)]トリガーを指定
フローを新規作成してください。トリガーは「Office 365 Outlook」を選択後に表示される[イベントが追加、更新、削除されたとき (V3)]を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/2dcf017f-3046-8159-f66a-1808aadd5bf5.png)

■参考URL
[When an event is added, updated or deleted (V3)](https://docs.microsoft.com/ja-jp/connectors/office365/?WT.mc_id=BA-MVP-5004053#when-an-event-is-added,-updated-or-deleted-(v3))

## ②：「繰り返しの予定」を回避
予定表で登録された情報が「繰り返しの予定」（毎週月曜、など）だった場合、処理を中断します。「制御」→ [条件] を選択します。条件の左側（値の選択）で「イベントが追加、更新、削除されたとき」→ [繰り返し] を選択します。

条件の真ん中は”次の値に等しくない”を選択し、条件の右側に"none"を指定します。

■設定

| 設定箇所 | 設定値       | 備考                |
|------|-----------|-------------------|
| 左辺   | 繰り返し      | トリガーの値            |
| 比較   | 次の値に等しくない |                   |
| 右辺   | none      | "none" ＝ 繰り返しではない |

条件が指定できたら「はいの場合」に [終了] アクションを”成功”で追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/34d57f57-8951-a108-7ec6-4349c149811b.png)

なお、毎週、毎月などの繰り返しがどういったパラメーターなのか？が知りたい方は、下記の参考URLから “Recurrence” を参照ください。

■参考URL
[CalendarEventClientReceive](https://docs.microsoft.com/ja-jp/connectors/office365/?WT.mc_id=BA-MVP-5004053#calendareventclientreceive)

## ③：同期する先の Googleカレンダー を取得する
同期対象になるGoogleカレンダーの予定情報を取得します。「Googleカレンダー」コネクターを選択 → [カレンダーのイベントの一覧を表示する] を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/222ef8dc-a0a6-c450-cc04-76ff9f093cd0.png)

Googleカレンダー から取得する範囲（最小時間 ～ 最大時間）を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/837b785b-1593-18ca-0306-7a7ab0bec2d6.png)

 ■最小時間
　下記の場合、現在日時（UTC）から7日前になります。

```
addDays(utcNow(), -7)
```

 ■最大時間
　下記の場合、現在日付（UTC）から30日先になります。

```
addDays(utcNow(), 30)
```
ここでは「本日-n日前 ～ 本日＋m日後」(上記の場合だと「本日-7日前～本日＋30日後」)までという取得範囲を指定しています。この範囲を逸脱した対象は取得できないため意図しない動作を招く場合があります。「これぐらいの範囲なら大丈夫だろう」という指定を各位で実施ください。

## ④：Google側に予定があるか？の判定準備(アレイのフィルター処理)
ここでは、Googlカレンダー に“現在処理中の365側予定が既に存在しているか否か？”を確認する準備をします。[アレイのフィルター処理]を利用します。

「データ操作」 → [アレイのフィルター処理]で追加します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/98114f50-cde3-ed68-cbb6-0b7d1aa69916.png)

“差出人”（フィルターを適用するアレイ）で、Googlカレンダーの [イベント リスト アイテム] を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/4c9fe972-ee60-47cd-83a2-78c603cffea4.png)

条件の左側（値の選択）で [イベント リスト 説明]（description） を指定。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/9ac8cf4d-7f82-df1c-c8ab-e8210c1ccc20.png)

判定（真ん中）は“次の値を含む”を指定し、条件の右側（値の選択）はトリガーの [ID] を指定します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/86010e5a-d983-8260-9f70-3f3591a07c6a.png)

まとめると、設定は下記ようになります。

### ■設定

| 設定箇所 | 設定値           | 備考                    |
|------|---------------|-----------------------|
| 差出人  | イベント リスト アイテム | Googlカレンダーから取得した一覧    |
| 左辺   | イベント リスト 説明   | Googlカレンダーの本文         |
| 比較   | 次の値を含む        | 365側のIDがあるのか？をチェックしたい |
| 右辺   | ID            | トリガーの ID               |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/fa72fa60-8aed-791b-ed23-50f791f44d43.png)

■参考URL
[フィルタ配列アクションを使用する](https://docs.microsoft.com/ja-jp/power-automate/data-operations?WT.mc_id=BA-MVP-5004053#use-the-filter-array-action)


## ⑤：Google側に予定があるか？の判定
上記④の結果をもとに、Googlカレンダー側へ既に同期済みなのか？新規作成なのか？を判定していきます。つまり、この条件分岐で ”更新か削除” OR "新規追加" を区別します。

条件分岐で関数を利用します。下記のように「式」を選択して入力欄に関数を記載したうえでOKボタンで確定です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/38d4763e-98ce-968e-9567-2a924bceb074.png)

設定については下記を参照ください。
### ■設定

| 設定箇所 | 設定値                        | 備考         |
|------|----------------------------|------------|
| 左辺   | empty(body('アレイのフィルター処理')) | ④の結果が空っぽか？ |
| 比較   | 次の値に等しい                    |            |
| 右辺   | false                      |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/3be110e8-e661-7208-0245-9d1c9e95de14.png)

■参考URL
[empty 式を使用する](https://docs.microsoft.com/ja-jp/power-automate/use-expressions-in-conditions?WT.mc_id=BA-MVP-5004053#use-the-empty-expression)

## ⑥：削除 or 更新 の判定
前述の判定が“はい”の場合は、削除 or 更新です。そのため、さらに条件分岐を追加して削除なのか？更新なのか？を判定します。

### ■設定
| 設定箇所 | 設定値      | 備考             |
|------|----------|----------------|
| 左辺   | アクションの種類 | トリガーの値から選択     |
| 比較   | 次の値に等しい  |                |
| 右辺   | deleted  | "deleted" = 削除 |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/67d9f76b-c53f-d794-4b87-ec9131b9a405.png)
※こちらの分岐に入った段階で”新規追加”はありえません。フロー作成後に365側で予定新規作成時にコチラの処理へ分岐してしまう場合は、何らかのミスをしています。もう一度全体の処理をチェックしてみましょう。

## ⑦：Googleカレンダー の予定を削除
Office 365側で削除された予定だった場合、IDが同一 ＝ 同期した予定を削除します。「Googleカレンダー」→[イベントの削除]を追加し、カレンダーIDの指定をします。つぎにイベントIDへ下記の関数を指定します。

```
first(body('アレイのフィルター処理'))?['id']
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/60b6d36d-91b9-9b07-7724-3dd28b34e4b7.png)
[アレイのフィルター処理]を利用した結果は配列になっているため、単純に指定すると繰り返し処理になってしまいます。ご注意ください。

■参考URL
[first](https://docs.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference?WT.mc_id=BA-MVP-5004053#first)

## ⑧：Googleカレンダー の予定を更新
Googleカレンダーに 同一のID が存在し、かつ 365側で削除された予定ではない場合 ＝ 365側で編集された予定です。なので、Googleカレンダー側へ更新をします。

[HTML to text2]を追加し、[本文]を設定します。意図しない文字化け等を回避するためですが、不要な方は無くても問題ありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/7cb06743-8d86-9582-0a88-607ab64d82d2.png)
「Googleカレンダー」→ [イベントの更新] を追加し、発見したイベントIDの予定を更新します。

### 設定（※重要箇所のみ説明）
前述⑦同様、IDを指定する場合は first 関数を利用する必要があります。また、開始時刻・終了時刻の選択肢が画面上に表示されない場合は [Power Automate 現時点の不具合を回避する方法](https://qiita.com/yamad365/items/1f226348fc0ab33a199c) を参考に検索を利用してください。

| 設定箇所     | 設定値                               | 備考                              |
|----------|-----------------------------------|---------------------------------|
| カレンダー ID | 同期先のGooglカレンダー                    |                                 |
| イベント ID  | first(body('アレイのフィルター処理'))?['id'] |                                 |
| 開始時刻     | [開始時刻]z                           | トリガーの値から[開始時刻]を選択して、"z"を文字列で追記  |
| 終了時刻     | [終了時刻]z                           | トリガーの値から[終了時刻]を選択して、"z"を文字列で追記  |
| 説明       | HTML to Text の結果<BR />ID          | ***ID の指定は超重要です。忘れずに指定しましょう。*** |

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/78e194ef-67e0-46a3-5f43-efb5a5731f0e.png)

**※重要：
[説明]の箇所へトリガーの値から [ID] を必ず指定してください。
このIDが埋め込まれていないと、削除と編集が正しく動作しません。**

## ⑨：Googleカレンダー へ新規予定を追加
フローの⑤条件分岐で“いいえの場合”、新規作成のため Googlカレンダー側にも新規予定を追加します。予定の更新⑧と同様に[HTML to text2]を追加し、[本文]を設定します。さらに「Googleカレンダー」→「イベントの作成」を追加し、必要事項を設定すれば完成です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/42345037-21fc-125e-1e3d-d32a07913476.png)
***※重要：
ここでも[説明]の箇所へトリガーの値から [ID] を必ず指定してください。
このIDが埋め込まれていないと、削除と編集が正しく動作しません。***

以上でフローの作成は完了です。おつかれさまでした。

# おまけ：新・旧 速度の比較
当バージョンと旧の速度比較です。厳密に同じ情報を取り扱っているワケでも、同一条件下での測定はありませんが、参考までに実行履歴を比べてみます。

## 当バージョン（Apply to each 撲滅版）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/b50b5be4-e801-9bbc-4b5c-2974225787e0.png)

## 旧バージョン（Apply to each 利用版）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/86f80e78-3581-bb58-07a9-5cc37574e0c0.png)

実行時間が、およそ 1/10 になっているのが把握できるかと思います。

# まとめ
- 365側の ***ID*** を埋め込むのがポイントです。
    - コレを忘れて「更新・削除がされない！」って方、多いです
- Apply to each の抹殺手段を覚えると良いです
    - フローがシンプルになります
    - 処理速度が向上します
    - 配列と関数の知識が必要になります。学習しましょ！
- 繰り返しの予定に対応するのはメンドクサイのでしません

何度も言いますが365・ExO側の予定「ID」を Googlカレンダー側に埋め込むのが重要です。そのIDをもとに更新・削除を判定しています。前回Verの記事でも複数回「IDを埋め込むのがポイント」と繰り返していたにもかかわらず・・・それを守ってなくて「追加はできるんですが、更新や削除ができません」ってリアルで当方に相談してきたメンバーが居るので、今回はさらにシツコイぐらい言っておきますね。[ID]が重要ですよ！お気をつけください。

それでは、皆さま。素晴らしい Power Platform Life を！