Google Apps ScriptとMicrosoft Graph APIの連携 – Outlook編【GAS】

Microsoft365を導入しても、それだけでは実は殆ど今までの仕事の置き換えでしかなく、自動化をしたいと思ってもGoogle Apps Scriptのような手軽な開発環境は用意されていません(ローカルだとVBAがありますが、非常に古い上に開発難易度が高いです)。

とりわけ、一括でのメール送信などはVBAでも可能ですが、制限値に引っかかる為、なかなか大量に送るとなると難しい面があるのは否めません。

そこで今回Microsoft Graph APIを利用して、社内需要が高いメール送信スケジュールの取得、ついでにプレゼンスの取得も行ってみたいと思います。

今回使用するスプレッドシート等

事前にAzure ADにてプロジェクトの作成が必要になっています。また、今回はユーザ権限だけで実行できる範疇のみを扱っているので、管理者権限は不要です。

事前準備

利用する為にはAzure ADでアプリ登録を行い、Client IDとClient Secretの2つ取得しておく必要があります。微妙に以前とは取得方法が異なっている為、改めてここで2020年6月現在の取得方法を記述しておきたいと思います。現在は、Azure ADも無償で利用が可能になっているので、フリーアカウントの場合でも構築する事が可能になっています(ただしフリーアカウントで認証を実行できるようにするには、ちょっと手順が必要です。)。この為だけにMicrosoft365契約するのはちょっとね・・・

GAS側の事前準備

ライブラリの追加

以下の手順でOAuth2 for Apps Scriptライブラリを追加しましょう。

  1. スクリプトエディタを開きます。
  2. メニューより「リソース」⇒「ライブラリ」を開きます。
  3. ライブラリを追加欄に「1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF」を追加します。
  4. 今回はバージョンは38を選択してみます。
  5. 保存ボタンを押して完了

これで、OAuth2.0認証にまつわる様々な関数を手軽に利用できるようになります。

図:ライブラリを追加した様子

コールバックURLを取得する

コールバックURLとは、認証を完了しAccess Tokenを取得したら戻るべきURLを指定するものです。これは、スクリプトIDをもとに作られているので、スクリプトIDを取得して組み立てます。

  1. スクリプトエディタのメニューより「ファイル」⇒「プロジェクトのプロパティ」を開く
  2. 情報の中にある「スクリプトID」を控えておく。
  3. https://script.google.com/macros/d/スクリプトID/usercallback として組み立てる。これがコールバックURLとなる。

図:スクリプトIDはファイル毎に異なるのです。

Azureでプロジェクトを作成

  1. アプリの登録にて登録を開始する
  2. 新規登録をクリックする
  3. 名前を入力(今回はoutlookmanと入力しました)、リダイレクトURIは「webを選択しhttps://script.google.com/macros/d/スクリプトID/usercallback」を入力
  4. 登録ボタンをクリックする
  5. 出てきた中で、「アプリケーション(クラと書かれているのがクライアントID」なので、このコードをメモしておく
  6. 左サイドバーより、「証明書とシークレット」をクリック
  7. 新しいクライアントシークレット」をクリックする
  8. 今回は特に有効期限を設けないで追加をクリック
  9. これで値に「クライアントシークレット」が生成されて手に入りました。このシークレットはこの時だけしか表示されないので、注意してください。
  10. つづけて、左サイドバーより「APIのアクセス許可」をクリックする
  11. Microsoft APIの中にある「Microsoft Graph」をクリックする。
  12. 委任されたアクセス許可」をクリックする
  13. デフォルトでUser.ReadがすでにONなので、今回はoffline_access、User.ReadBasic.All、Calendars.Read、Calendars.ReadWrite、Presence.Read、Presence.Read.All、Mail.Sendを検索してONにしましょう。
  14. アクセス許可の追加をクリックする
  15. 追加出来たら、xxxxxに管理者の同意を与えますをクリックします。すると、状態が緑色になります。今回は管理者の権限を要求するものは無いのでしなくても大丈夫だと思う
  16. 次に左サイドバーより「認証」をクリック
  17. 暗黙の付与にて、「アクセストークン」にチェックを入れる
  18. サポートされているアカウントの種類に於いては、「マルチテナント」にしておきました。
  19. 保存をクリック
  20. 概要のエンドポイントをクリックすると、いろいろなエンドポイントURLが出る。
  21. 概要のディレクトリ(テナントの数値はメモっておきます。あとでプログラム中で使用します。
  22. デフォルトでは組織アカウントでなければOAuth2.0認証が出来ません。フリーのMicrosoftアカウントでも認証できるようにするためには以下の手順が必要です。
  23. 左サイドバーのマニフェストをクリックします。
  24. マニフェストエディタが起動します。その中にあるsignInAudienceの値を「AzureADandPersonalMicrosoftAccount」へ変更して保存をクリック。これで例えばhotmailアカウントでも認証が通るようになります。

※3.でWebを選ばないSPAを選んでしまうと、Proof Key for Code Exchange by OAuth Public Clientsといったエラーが出てしまい認証ができませんので注意。

※個人アカウントでうまく動作しないなぁと思った場合には、Azure Portalのユーザ画面にてonmicrosoft.comのアカウントを作成してそれで認証作業を行うと良い。この時、グループとロールにおいては「アプリケーション管理者、クラウド アプリケーション管理者」の2つが割り当てられてればOKです。個人的にはこの作業をオススメします。以降、ログイン認証等もこのアカウントで行います。

図:アプリの登録から全ては始まります。

図:Graphを選択する

図:アクセス権限付与した状態

図:認証の設定変更に注意

図:フリーアカウントでも可にするとこういう表示になる

図:onmicrosoft.comアカウントのロール

処理に必要なユーザ情報を取得する

今回、メール送信・スケジュール取得・プレゼンス取得に必要な共通のデータとして「ユーザID」があります。この情報はメールアドレスがわかれば取得が出来る。しかし、Graph APIはメールアドレスではなくこのユーザIDを基準にして処理を行うので、実際の処理でもまずはメアドからユーザIDを取得する為にGraph APIを叩くか?予めデータベース化しておいて、対象の人間のユーザIDを格納しておいて取得するような仕組みが必要です。

  1. Graph Explorerに行き、ログインしておく
  2. サンプルクエリにて「メール」で検索。ユーザの中にあるメールごとのユーザをクリック
  3. 取得するURLは、https://graph.microsoft.com/v1.0/users/メールアドレスといったようなスタイル
  4. 組み立てたURLを入れてクエリ実行をする
  5. まだこの段階では取れていません。ここでアクセス許可の修正をクリック
  6. 出てきたUser.ReadとUser.ReadBasic.Allだけで十分なのでアクセス一覧の同意ボタンをクリックする
  7. 無事に取得できると、OK – 200が返ってきて対象のメアドの持ち主の情報が取得できます。betaと1.0とでは取得できる情報量が異なるのと、同意するアクセス許可によって取得出来る出来ないの差があるので注意
  8. レスポンスデータの中のidが今回の目的のユーザ情報になります。

図:求められたアクセス許可

図:認証が通るとユーザ情報がJSONで取得される

Outlook サービスの制限

いわゆるQuotaと呼ばれるAPI実行制限値です。Google Apps Scriptなどでもおなじみですね。Microsoft Graph APIの場合、APIの実行制限は1クライアントIDと特定のメールボックスやグループ単位で判断されます。そのアプリ内で特定メールボックスに対しての制限値を超えても、別のメールボックスに対しての処理は制限されない特徴があります。

大量の処理を出来るように設計されているというだけあって、VBAでOutlookを操作して送るような事例よりも制限が緩やかなようです。主な制限値は以下の通り。

  • Graph API自体、アプリ毎に1秒間2000リクエストまでの制限がある
  • 10分間で10,000件のAPIリクエスト
  • 一度の要求で最大4つまで同時処理可能
  • 30秒間で合計15MBのアップロード(メソッドは、POST, PUT, PATCH)
  • スケジュールの場合1度で要求できる数は20個まで
  • スケヂュールの場合検索できる期間は42日間

リミットに達してしまった場合にはエラーコードは429が返ってきます。エラーレスポンスのヘッダにあるRetry-Afterの値分だけ待機させる事で再試行が可能です。

主な使い所は、差し込みメール送信のような個別の内容をメール一括送信するようなケースが考えられます。また、スケジュールデータを連続で取得するようなケースも考えられますね。一括メール送信する場合には、onmicrosoft.comとしてグループアドレスを作成するほうが管理はしやすくなります。

※Node.jsで処理を行う場合、注意すべきは2つ目の一度の要求で4つまで同時処理の部分。Node.jsは非同期で処理を行ってしまうので、どんどんリクエストを投げてしまうと引っかかります。Promiseなどで1通ずつリクエストを送るように構築しないと、account throttle limitとエラーが出てしまいます。使用毎、アプリ毎、ユーザ毎にこれはカウントされているので、同じアプリであれば複数名が使っているケースでは、合計で計算されますので要注意。

認証を行う処理を作成する

OAuth2 for Apps Scriptのページの「Create the OAuth2 Service」にあるコードを元に、Google Apps Script側で構築をします。この時、Microsoft365側で取得したアプリケーションIDやシークレットを使います。また、今回はいつもよりも要求するアクセス権限が多い点と、利用するAPIはBetaを利用する点に注意が必要です

GAS側コード

  • 今回利用するGraph APIのエンドポイントはhttps://graph.microsoft.com/betaとなります。
  • 要求する権限はscopeに半角スペースで区切って、Azure AD側で用意したものと同じものを設定します。
  • startoauthを実行して認証を実行すれば、スクリプトプロパティにAccess Tokenが格納されます。

HTML側コード

template.htmlというダイアログ用のファイルを用意します。ここでアクセス承認を実行し、ログインをすると、アクセストークンその他が取得可能になります。

  • 実際にこれらのコードで、startoauthを実行すると、スプレッドシート上で認証用のダイアログが出ます。
  • 認証でMicrosoft365アカウントにログインします(もしくは作成したonmicrosoft.comのアカウント
  • 取得したAccess Tokenほかはスクリプトプロパティのoauth2.Graphという項目にガッツリ値が格納されます。ここにはAccess Token, Refresh Token, expire_inのタイムなどが入っています。
  • reset関数はログアウトされて、再度認証ができるようになります。
  • Chrome v83.xを利用している場合、認証実行時にリダイレクトURLにジャンプ出来ずにエラーになることがあります。認証用URLの中のredirect_uriの文字がオカシナ文字に置き換わっていてredirect_uriが違うと怒られるケースがあります。その場合はURLを直接リダイレクトURIの部分を書き換えてください。

図:無事に認証画面へ到達出来た

図:スクリプトプロパティにAccess Tokenが格納された

ソースコード

Graph APIを叩く関数

スケジュール取得およびプレゼンス取得で共通で利用する、Graph APIを叩く関数は以下のとおりです。メール送信は送信メソッドが異なる為この関数は使いません。

  • Graph APIのURLと実行するメソッドを受け取って、レスポンスデータをJSONで返します。

メールを送信するサンプル

今回はスプレッドシートの送信一覧を元に簡単なメールを送るプログラムを作っています。実際にはCCであったり、複数送信先であったり、また添付ファイルを付けたりとかなり細かい設定を行う事ができるので、公式リファレンスを参照して送信用のJSONを構築して送り込んでみてください。

ただし、連続送信の場合Graph API側はほぼ問題ないのですがGAS側はUrlfetchAppのQuotaに引っかかる可能性があるため、実際にはそこまで多用は出来ないかなと思います。実際に活用する場合にはNode.jsなどの他の言語を利用してアプリケーションとして構築すると一瞬で大量のメールを送ることが可能になります。

  • メール送信はPOSTメソッドを使って送る必要があります。
  • JSON形式で送信用のデータをスプレッドシートから構築して送り込みます。
  • saveToSentItemsオプションを利用すると、送信済みアイテムにきちんとデータが残るようになります。
  • urlfetchAppのQuotaはGsuite Basicの場合、100,000回/日呼び出し以外にも、連続して利用する場合には同一アカウントで実行時に10秒間に2アクセスが限界(それ以上になると、3回目のアクセスで403エラーや429エラーが発生する)。sleep処理が必須です。
  • Cloud FunctionsやNode.js/Electronなどならば、ガッツリ1万件くらいメールを送ることが可能かも(ドキュメント上は10分間に10,000件呼び出しまでOKみたい)
  • 他にも細かなオプションが多数用意されています。
  • 宛先を複数入れたい場合は、例えばtoRecipientsならば、ここが配列なので、以下のようにemailAddressの項目を複数pushした状態にしておけば良いです。

図:送信済みトレイにもキッチリ残ってる

スケジュールを取得するサンプル

予定表に登録してある個人のスケジュールデータを取得します。ただし、事前にメインの予定表についてAPIを叩く人のアカウントに対して共有をしておかなければ取得は出来ません。共有されている状態であれば、そのアカウントからは全データを取得する事が可能になります。

Outlookのカレンダーはひどく使いにくく、受付を担当する庶務の人が全員の電話の取次をするような場合、サイボウズのようなカレンダー形式ではないため、一覧表記が出来ません。しかし、今回のようなAPIでデータを取得できれば、あとはjQueryのTimelineライブラリでも使えば、サイボウズ風の一覧タイプカレンダーを作ることが可能です。

実行するコード

  • 今回、開始日付と終了日付は面倒だったので、固定値にしています
  • ユーザIDをまずメールアドレスから取得し、そのIDをもって指定期間内のイベントを取得させています。
  • Microsoft365 Enterpriseの場合、onlineMeetingというプロパティがあり、ここにTeamsのウェブ会議リンクが入ります。
  • フリーアカウントの場合、Teamsで会議予約を行うとlocationにTeamsのウェブ会議リンクが入りますが、onlineMeetingのプロパティはFalseのままです
  • 最後にスプレッドシートに書き出しています。

図:イベントを書き出せました

返ってきたJSON

  • valueの配列に指定期間のイベントがいくつも入ってるイメージです

プレゼンスを取得するサンプル

プレゼンスに関するサンプルはGraph Explorerには掲示されていませんが、URLを入力すれば実行しテストすることが可能です。

実行するコード

  • メールアドレスからまずuseridを取り出すためにGraph APIを実行する
  • 取り出したuseridを元に今度はプレゼンスのURLを叩いて、プレゼンス状態を取得しています。
  • プレゼンス取得のURLは、https://graph.microsoft.com/beta/users/取得したuserid/presenceとなります。

返ってきたJSON

返ってきたプレゼンスはTeamsなどの右上にある連絡可能などの状態を変更すると、取得される状態も変わります。この値ですが、以下のような対応になります。

  • offline = オフライン状態。連絡は取れません
  • Available = 連絡可能。緑色の状態ですね
  • Busy = 取り込み中。赤色の状態です。
  • DoNotDisturb = 応答不可。会議などで出ることの出来ない状態にあります。
  • BeRightBack = 一時退席中。現在目の前にいない状態です。
  • Away = 退席中。戻ってくるのはいつになるかわからない状態。

図:これを変更するとプレゼンス情報が変わる

unable to verify the first certificateエラー

連続してGraph APIに対してリクエストを投げていた時に、1度だけ不可解なエラーに遭遇。それが、unable to verify the first certificateエラー。どうやらリクエスト先のURLの証明書設定がオカシイと出るとか。しかし、このエラーは必ず再現するわけではないので、対処が面倒。たまたまロギングしていたので捕捉できましたが、処理が止まる上に最初からやり直しとなると。

ということで、このエラーの対処法ですが、Node.jsの場合は、以下のコードを入れて、証明書の検証を行わないようにするとOK。ただし本来はオフにすべき項目ではないので、自分の場合は、エラーをスローして次の処理にcontinueするようにしています。

関連リンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)