Office 365 予定表を MS Flow でGoogleカレンダーへ同期する（追加・編集・削除 全対応）

― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―
2020/11/22 追記
***重要***
当記事は古いトリガーを利用した旧式の手法です。
2020年11月時点で最新の手法は下記になります。
https://qiita.com/yamad365/items/4e64d068e97c6cb3726b
― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―（追記ここまで）

ただし「繰り返しの予定」以外、という前提で。

Office 365（または、Microsoft 365）ご利用されていますか？されてますよね？
Exchange Online（ExO）の予定表、PCでも、スマホでも、ブラウザーでも同じデータがいつでも、どこでも確認できます。便利ですよね。

当方のプライベート携帯はAndroid端末でGoogleカレンダーを利用しております。プライベートの予定は、すべてAndroid経由でGoogleカレンダーへ登録して管理しています。前述どおり、仕事の予定は Office 365 の ExO 予定表を利用している。こういう環境の方、結構おみえかと思います。そして、個人携帯では業務用の365アカウントは投入していない（あるいは”出来ない”）。
こーなると「仕事の予定を、プライベートのGoogleカレンダーへ転記して個人予定と仕事のスケジュールを管理している」という方、少なからずいるはず。かく言う当方も、Flowに出会う前はそうでした。”仕事の予定をわざわざGoogleカレンダーへ転記する”なんて、メンドクサイですよね・・・。なので、Office 365の予定表を自動でGoogleカレンダーへ同期したいなぁ、という欲望が出てきます。でてきますよね？

と、いうことで、今回はそんな感じで Microsoft Flow による予定表から他カレンダーソリューション連携を実現する方法です。

― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―
2020/03/20 追記
当記事作成時点から、VerUpによる変更点があります。上手く動作しない、等の場合は下記記事も参考にしてください。
[【2020年3月時点の補足】Office 365 予定表を Power Automate（旧Flow）でGoogleカレンダーへ同期する](https://qiita.com/yamad365/items/4765b701a9dd2c46367b)
― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―― ― ― ― ― ― ― ―

# 前提条件
今回の仕組みは「繰り返しの予定」には対応しておりません。
理由としては"たいへん"だから、です。なぜ、たいへんなのか？は実際にアプローチいただけると実感できるかと思います。ですので、Let's Try！

また、365 → Googleカレンダー の片道通行での反映になります。双方向（365 ←→ Googleカレンダー）ではありません。双方向にするぐらいであれば、どちらかのカレンダーへすべての情報を集約するべきだ、と個人的には考えています。

# 全体像
作成するMicrosoft Flow（以降、Flow）の全体はこんな感じ。
![01-全体像.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/5453f43a-df32-e91b-bdf3-b6ef85ae6ca4.png)

# 動作イメージ
動画取得用に テスト実行 → 予定表操作という手順で実施しています。Flow の着火にラグがあるため、なので、普段利用の場合は365の予定表を操作するだけで自動的にFlowが実行されます。
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/Office365?src=hash&amp;ref_src=twsrc%5Etfw">#Office365</a> 予定表 → <a href="https://twitter.com/hashtag/MicrosoftFlow?src=hash&amp;ref_src=twsrc%5Etfw">#MicrosoftFlow</a> → <a href="https://twitter.com/hashtag/Google%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC?src=hash&amp;ref_src=twsrc%5Etfw">#Googleカレンダー</a> 同期<br>追加/更新/削除(added, updated or deleted.)全対応。※繰り返し未対応 <a href="https://t.co/CEECwpc4eP">pic.twitter.com/CEECwpc4eP</a></p>&mdash; やま (@yamad365) <a href="https://twitter.com/yamad365/status/1141315771944935424?ref_src=twsrc%5Etfw">2019年6月19日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

### ポイント
Office 365 Outlookの予定表に登録された予定アイテムは、必ず一意のIDを持っています。そのIDをGoogleカレンダーなど同期する対象へ追加する際に何らかのカタチで記録しておきます。そうすることで、次に編集や削除をしたい場合、そのIDを保持している対象を操作することができるようになります。

# 解説
ある程度 Step by Step で作成手順を説明していきますが、式の書き方や設定の仕方など細かな点は割愛することがありますのでご留意ください。

### 0. 全体像と解説の番号
前述の全体像へ解説とリンクさせる番号を付与したイメージが下記になります。以降の手順で「いま、どこの解説だよ」を付与していきますね。
![01-全体像No入り.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/11d3c829-615a-d8ee-b59a-9d7ad92c9faa.png)

### 1. [イベントが追加、更新、削除されたとき]トリガー（全体①の箇所）
Flow を新規作成してください。トリガーは「Office 365 Outlook」を選択後に表示される[イベントが追加、更新、削除されたとき]を選択します。
【参考URL】[When an event is added, updated or deleted](https://docs.microsoft.com/ja-jp/connectors/office365/#when_an_event_is_added,_updated_or_deleted)
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/4a431827-dc04-18fa-ec6f-b7d6e9f6ed65.png)

### 2. 「繰り返しの予定」を回避（全体②の箇所）
予定表で登録された情報が「繰り返しの予定」（毎週月曜、など）だった場合、処理を中断します。「制御」→ [条件]を選択します。条件の左側（値の選択）で「イベントが追加、更新、削除されたとき」→ [繰り返し] を選択します。
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/f1f14c70-9f2e-8492-c9b1-9ad20bb8ab5c.png)
条件の真ん中は”次の値に等しくない”を選択し、条件の右側に 0 （数字のゼロ）を指定します。
[公式サイト](https://docs.microsoft.com/ja-jp/connectors/office365)によると"The recurrence pattern for the event (None - 0, Daily - 1, Weekly - 2, Monthly - 3, Yearly - 4)"です。つまり、ゼロ以外＝繰り返しではない、ということですね。
"はいの場合”へ[終了]アクションを追加し”成功”で終了するように設定します。”いいえの場合”は空っぽで問題ありません。
![06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/528151ee-4f9a-882a-f83f-bee968fdc77a.png)

### 3. 同期する先のGoogleカレンダーを取得する（全体③の箇所）
更新、または削除を反映したいため、同期対象になるGoogleカレンダーの予定情報を取得します。
「Googleカレンダー」コネクターを選択 → [カレンダーのイベントの一覧を表示する]を選択します。
![07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/025608c1-e764-516d-d573-766602467298.png)
同期する対象のGoogleカレンダーアカウントを指定した後で[最小時間]へ次の式を設定してください。

    ```
    addDays(utcNow(),-7) 
    ```
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/3f58f1a3-7065-3c12-a9cd-4ba0eb4aafcb.png)
同様に[最大時間]へ次の式を設定します。

    ```
    addDays(utcNow(),30) 
    ```
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/a2c130b9-69c2-fdc0-2735-2d85689652cd.png)
ここでは「本日-n日前 ～ 本日＋m日後」(上記の場合だと「本日-7日前～本日＋30日後」)までという取得範囲を指定しています。全てのカレンダー情報を取得してしまうと処理が遅くなってしまいますので、概ねこの程度の範囲で反映できればいいかな、って範囲で指定しています。もちろん、その範囲を超過した対象は取得されませんので、この後の処理で反映対象になりません。ご注意ください。
### 4. フラグ変数を設定（全体④の箇所）
この後の全体⑤の箇所で、Googleカレンダーの予定削除、または編集をします。その際に「削除、または編集をしたか否か」を③の箇所以降で判断するための変数を事前に作成しておきます。変数名はお好みで結構ですが、当方は下記のように設定しています。
   - 名前 ： isProcessed
   - 種類 ： ブール値
   - 値   ： False
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/e446c4ab-b7b7-7061-151b-545d703e2d8c.png)

### 5. Googleカレンダーの予定削除、または編集（全体⑤の箇所）
まず「制御」→[Apply to each]を選択し、取得済みのGoogleカレンダー[イベント リスト アイテム]を選択します。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/54ec12b7-a409-7a68-f99e-200d556b2086.png)
「制御」→ [条件]を選択します。条件の左側（値の選択）でGoogleカレンダーの[イベント リスト イベント] を選択します。
![12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/f3170e1f-c842-28c7-d7ac-27966e96a00b.png)

---------------------------------------
*2019/07/15 追記 うまく編集・削除が同期されない場合*
Googleカレンダーの「イベント リスト イベント」で意図した動作をしない場合「イベント リスト イベント 説明」を選択してみてください。詳細までは調査しておりませんが、どちらかで判定されることを確認しています。
![図1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/9d06b2d1-7ef6-ceba-b934-aacec652e4b8.png)
～追記ここまで～

---------------------------------------
真ん中の判定を”次の値を含む”にして、右側を Office 365 Outlook（トリガー）の[ID]を選択します。
これで『Googleカレンダーの取得した予定を1つずつループさせて、IDが埋め込まれていたら”はい”の処理にいく」という分岐が完成しました。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/93fb0985-f632-a39d-64e9-4dbc7230924f.png)
 
### 6. Googleカレンダーの予定を削除
Office 365側で削除された予定だった場合、IDが同一 ＝ 同期した予定を削除します。
”はいの場合”へ条件を追加し、左側で[アクションの種類]を選択。
![14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/9a91c415-6e43-c394-c687-930e9d5e0767.png)
判定を”次の値に等しい”にして、右側に”deleted”を設定します。
その判定の下にある”はいの場合”へ「Googleカレンダー」→[イベントの削除]を追加し、カレンダーIDの指定、および[イベント リスト イベント ID]を選択します。
![15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/e23d0e7b-8199-2f7c-fd0f-97ae3c0a50a3.png)
今回利用しているトリガーは、予定の追加・更新・削除、全てで発動するため[アクション]の値で”何が行われたか？”を判断しなければなりません。各値は[公式情報 CalendarEventClientWithActionType](https://docs.microsoft.com/ja-jp/connectors/office365/#calendareventlistwithactiontype)にもありますが、added, updated, deleted になります。
### 7. Googleカレンダーの予定を更新
Googleカレンダーに同一IDが存在し、かつOffice 365側で**削除された予定ではない**場合 ＝ Office 365側で編集された予定をGoogleカレンダー側に更新します。
”いいえの場合”に[HTML to text2]を追加し、[本文]を設定します。これは、Office 365の予定表にHTMLで登録されている情報がある場合、文字化けなどを引き起こす可能性があるので、それを回避するための工夫です。
![16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/7c1147d8-164b-aa75-2572-3adfdb0757f2.png)
「Googleカレンダー」→[イベントの更新]を追加し、発見したイベントIDの予定を更新します。
![17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/fc87ce2e-dc93-d768-a3a9-aa87eaf12041.png)
**※重要：
[説明]の箇所へOffice 365 Outlookの[ID]を必ず指定してください。
このIDが埋め込まれていないと、削除と編集が正しく動作しません。**
### 8. フラグ変数の更新
削除、または編集が完了したので「変数」→[変数の設定]を条件分岐の合流する箇所へ追加しフラグ変数を"true"にします。
![18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/b56a8e0c-5497-99cc-3050-de2e615de3c7.png)

### 9. Googleカレンダーへ新規予定を追加（全体⑥の箇所）
さて、仕上げに新規追加を実装します。ここまでの手順で下記の処理が完了しています。
    - 繰り返しの回避（処理中断）
    - Googleカレンダーへの削除反映（フラグ変数がtrue）
    - Googleカレンダーへの編集反映（フラグ変数がtrue）
    
このタイミングでフラグ変数が false だった場合 ＝ GoogleカレンダーへOffice 365 の予定が登録されていない ＝ 新規追加が必要、というコトになります。「制御」→[条件]を Apply to each の外側へ追加します。左側にフラグ変数、真ん中の判定は”次の値に等しい”、右側は"false”に設定します。
![19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/1c25e9f2-8ab3-494c-ff6a-ce79a5c78d30.png)
”はいの場合”へ、前述No.8のように[HTML to text2]を追加し、[本文]を設定します。さらに「Googleカレンダー」→「イベントの作成」を追加し、必要事項を設定すれば完成です。
![20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/215892/760d252d-61e8-3464-c634-d61ae856e191.png)
**※重要：
ここでも[説明]の箇所へOffice 365 Outlookの[ID]を必ず指定してください。
このIDが埋め込まれていないと、削除と編集が正しく動作しません。**

おつかれさまでした。これで完成です。

# 繰り返しの予定についての補足
厳密なテストを実施したワケではありませんので保障はできませんが・・・上記の手順で作成したFlowは、当方が確認した限りでは「繰り返しの予定」の”追加”と”編集”はGoogleカレンダーへ反映されます。繰り返しの予定でよくある”毎週月曜日の13時から1時間の定例なんだけど、x日だけは水曜日に移動した」などの特殊パターンも反映され・・・るハズです。
ただし！削除がうまく動作しません。とくにOffice 365で単一の予定を削除すると同時に「ほかの繰り返しも全部削除」ってやられるとダメです。ここらへんは奥が深いので、ぜひ興味のある方は解析してみてください。

■参考URL
　[Outlook calendar API overview](https://docs.microsoft.com/en-us/graph/outlook-calendar-concept-overview)
　[Appointment Class(Microsoft.Exchange.WebServices)](https://docs.microsoft.com/ja-jp/dotnet/api/microsoft.exchange.webservices.data.appointment?view=exchange-ews-api)

上記、URLで全社のGraph APIや、EWS（Exchange WebServices）Managed Api まわりで調べると仕組みがわかる・・・かもしれません。

# おまけ
このFlowですが、予定表に作成される（繰り返し以外の）全ての予定が同期されます。
そうではなく「ある特定の予定だけGoogleカレンダーへ同期したい」などの場合は、キーワードや、差出人などで判定するようにしてください。実際に当方が運用してあるテナントで仕掛けているFlowは、一番最初の「繰り返しの回避」ではなくて「特定の文字列が本文に記載されていたら同期する」という仕組みにしてあります。こうしておけば、特定の文字列が入力されていない予定は同期されません。

# まとめ
- Office 365 Outlookの予定表は「追加・更新・削除」まとめて１つのトリガーで着火
- 予定のアイテムを一意（ユニーク）にする **ID** が重要
    - IDが把握できれば、関連性の無いソリューションでも相手が特定できる(Googleカレンダーへ同期した予定など）
    - IDが把握できれば、削除や編集も簡単に実施可能
- 繰り返しの予定はメンドクサイ


今回はGoogleカレンダーへ同期しておりますが、「AというソリューションからBソリューションへ同期」等の際は概ね同じような発想で実現できると思います。とくに、編集と削除に関しては「一意になる値で特定できること」が重要です。なお、テナントの設定でコネクターが制限されていたり、そもそもFlowが非アクティブ等の場合は管理者の方へご相談くださいませ。

今回は珍しくPowerApps成分ゼロのFlowな話しでしたがいかがだったでしょうか。少なくとも、ここをご確認いただいている、ということは全部閲覧いただいた、ってことでしょうかね。長文にお付き合いいただきありがとうございました。
それでは、皆さま。素晴らしい Power Platform Life を！