Google Apps Scriptでよくハマる人が多い項目として共有設定や実行権限周りの設定が挙げられます。確かに分かりづらい部分ではありますが、なれてしまうとそれほど難しいことではありませんので、今回は設定によってシステムの挙動がどうなるのかを説明していきたいと思います。
いろいろなシチュエーションが想定できますので、記事は結構長めになると思いますが、気になる方は目を通して損はないと思います。最後に小ネタを含んでいますので、長い記事ですが最後まで読んでもらえたらと思います。
今回はいくつかのカテゴリ別に挙動をまとめて説明していきたいと思います。
Google Workspaceアプリ関連
シンプルトリガーの実行アカウントはどのアカウント?
以前『スプレッドシートのセルを編集する毎に起動するトリガーの作成方法』でも軽くご紹介しましたが、GASにはシンプルトリガーと呼ばれる実行トリガーがあります。
これは、関数名にonEditやonOpenなど、予めトリガーとして用意されている関数名を指定したときに自動的に編集時やファイルアクセス時に処理が実行されるトリガーになります。
このトリガーは関数名だけで簡単に設定できる反面、実行可能時間が最大で30秒間であったり、トリガーが設定されたファイル以外のファイルを参照できないなどいくつかの制限があります。
シンプルトリガーの制限
- 30秒以上の処理は実行できない
- シンプルトリガーが設定されているファイル以外を参照できない
今回は、このシンプルトリガーで実行されたプログラム処理がどのアカウントで実行されるのかということに焦点を当てて説明してみたいと思います。
今回は、このシンプルトリガーで実行されたプログラム処理がどのアカウントで実行されるのかということに焦点を当てて説明してみたいと思います。
まずはスプレッドシートにonEditやonOpenを設定する下記2つのシチュエーションを想定してみましょう。
- 自分のアカウントがオーナーとして作成したスプレッドシートファイル
- 他のアカウントがオーナーとして作成したスプレッドシートファイル
この2つのファイルがあったとして、onEditやonOpenの関数が起動した場合にどのアカウントが実行アカウントになるでしょうか?
シンプルトリガーの場合はその名の通りとてもシンプルな挙動をします。下記に表を作りました。
ファイルタイプ | 実行アカウント |
---|---|
自分がオーナーのファイル | 自分のアカウント |
他のユーザーがオーナーのファイル | 自分のアカウント |
シンプルトリガーの場合は、ファイルのオーナー権限に関わらず、スクリプトの実行者は編集をしたアカウントになります。
トリガー設定したスクリプトの実行アカウントはどのアカウント?
次に、シンプルトリガーとは反対に、能動的にトリガーを設定した、いわゆるインストーラブルトリガーを設定した場合で考えてみたいと思います。
インストーラブルトリガーについてもシンプルトリガーと同じように編集時に実行される関数やファイルアクセス時に実行されるようにトリガーを設定することができます。
シンプルトリガーでは関数名は予め決まったものを設定しましたが、インストーラブルトリガーの場合は任意の関数名を指定することができます。
先ほどと同じように、今回は編集時/ファイルアクセス時に起動するトリガーを設定する4つのシチュエーションを想定してみます。
- 自分のアカウントがオーナーとして作成したスプレッドシートに自分のアカウントでトリガーを設定する
- 他のアカウントがオーナーとして作成したスプレッドシートに自分のアカウントでトリガーを設定する
- 自分のアカウントがオーナーとして作成したスプレッドシートに他のアカウントでトリガーを設定する
- 他のアカウントがオーナーとして作成したスプレッドシートに他のアカウントでトリガーを設定する
赤字部分がそれぞれ条件として異なる部分になります。これらのファイルを編集やファイルをアクセスして、スクリプトが実行された場合、どのアカウントで実行されるでしょうか?
シンプルトリガーの場合より即答できる人は少ないのではないでしょうか。今回もまた表にまとめてみます。
ファイルタイプ | 実行アカウント |
---|---|
自分がオーナーで自分がトリガー設定 | 自分のアカウント |
他アカウントがオーナーで自分がトリガー設定 | 自分のアカウント |
自分がオーナーで他アカウントがトリガー設定 | 他のアカウント |
他アカウントがオーナーで他アカウントがトリガー設定 | 他のアカウント |
上記のような結果となり、イメージとしては下記のような仕組みとなります。
ここからわかることは、インストーラブルトリガーの場合は、トリガーを設定したアカウント = 実行アカウントであるということです。
インストーラブルトリガーの場合は、シンプルトリガーの制限が適用されないため、トリガーが設定された以外のファイルへのスクリプトからアクセスを行うことができますが、外部のファイルを参照するようなコードを組んでいる場合には、共有設定に少し注意を払う必要があります。
例えば、シンプルトリガーの場合は、関数名が設定されているスプレッドシート以外のファイルにはアクセスができませんので、トリガーが設定されたファイルへの編集権限が付与されていれば、スクリプトが実行する処理のすべてを自分のアカウントで実行できることを意味します。
しかしながら、インストーラブルトリガーを設定する場合は、外部ファイルに対しても処理を行うことが可能になりますので、スクリプトが参照しているファイルにアクセス権限がない場合は問題が生じます。例えば、下記の実行イメージをご覧ください。
スクリプトが参照するファイルを含めてトリガーを設定したアカウントに対してアクセス権限が付与されていればスクリプトは問題なく処理されますが、参照するファイルの中にアクセス権限の付与されていないファイルがある場合は上記の実行イメージの通りスクリプト実行中にエラーで処理が止まってしまいます。
これを回避するためには、スクリプトが参照するファイル/フォルダに対してもアクセス権限を付与することが必要となります。
ただ、この仕組みを応用することで通常ではできないような運用をすることができるようになります。
少し考え方が高度になりますが、このアクセス権限とスクリプトの関係ロジックを理解できていれば、スクリプト起動時に、編集者 / アクセス者(ユーザー)にはアクセス権限を付与していないファイルから情報を取得して結果を出力させるということができるようになります。
また、企業でGoogle Workspaceなどを利用している場合、ファイルのオーナーがドメイン内の個人アカウントに設定されるとその方が退職した場合などに処理が面倒になるケースがあります。
そういった場合に、上記の共有権限や実行アカウントの仕組みが頭に入っていれば、スクリプトを通してファイルを作成するアカウントを1つに集約することができるようになります。
トリガーを設定したアカウントが処理の実行者となるため、この仕組を利用してトリガー設定アカウントを1つのアカウントに集約することでファイルオーナーを一つのアカウントに集約することができます。以前企業でシステムを構築していた場合はシステム用のアカウントを一つ用意してそのアカウントにすべての処理を行わせていました。従業員は退職したり不測の事態が発生するといなくなってしまう可能性がありますが、システムは残り続けますので、このような運用をしていました。
スクリプトにトリガーを設定させる場合トリガー設定はどのアカウントか?
次の例では、トリガーの設定をスクリプトにやらせた場合を考えてみます。
スクリプトによっては、処理の途中でトリガーの設定をする場合があると思います。スクリプトにトリガーを設定させる処理を含ませた場合、どのアカウントがトリガーを設定したと認識されるのでしょうか?
これは比較的容易な質問かもしれませんが、答えはトリガー設定を含むスクリプトを実行したアカウントがトリガー設定アカウントになります。
初回にスクリプトを実行する際に基本的にはスクリプトに許可を与える必要があると思います。この許可は簡単に言うとスクリプトがあなたのアカウントの代わりにスクリプト内容の処理を行うことを許可しますか?という確認になります。
そのため、スクリプトによって設定されるトリガーは自分のアカウントが設定したトリガーとして設定され、そのトリガーによって実行された次の処理のスクリプトについても実行者は自分のアカウントということになります。
ここまでが、Google Workspaceアプリ(Googleドキュメントやスプレッドシートなど)で実行されるスクリプトの実行イメージになります。
ウェブアプリとして導入したケース
GASをウェブアプリとしてデブロイする場合、実行アカウントや公開設定を明示的に設定します。Google Workspaceユーザーと無料アカウントで若干の挙動の違いはあるのですが、核となる部分は同じです。
ウェブアプリとして導入する場合には「次のユーザーとして実行」という項目でスクリプトの実行者を明示的に指定します。
ここでは「自分」と「ウェブアプリケーションにアクセスしているユーザー」の2つの選択肢があります。これは読んで字のごとくですが、少し説明がいるかと思いますので下記に少し記載します。
また、デプロイ時の選択項目で「アクセスできるユーザー」という項目でウェブアプリにアクセスできるユーザーを設定できますが、この部分については、システムの挙動には影響せず、誰がアクセスできるかということでしかないので、説明は割愛します。
自分として実行する場合
自分として実行する場合には、ウェブアプリとしてデプロイする時点でスクリプトから許可の確認がされます。いわゆる、スクリプトがあなたの代わりに処理を実行しても良いですかという確認です。
これを許可することによってウェブアプリから処理を実行することが可能になります。
実行ユーザーに自分を選択した場合には、そのウェブアプリに誰がアクセスして処理を実行したとしても、自分として実行がされたことになります。
そのため、ウェブアプリの利用者は参照しているファイル/フォルダへのアクセス権限を付与されている必要がなく、データの内容や処理のスクリプトを公開する必要はありません。ただし、全てがデプロイしたアカウントとして実行されてしまうため、誰が行った処理なのかをあとから確認することは困難になります。もし履歴を残したいしたいのであれば、スクリプトで履歴管理をするための処理を組み込むことが必要です。
少し話がそれましたが、自分として実行を選択した場合、デプロイするアカウントにはスクリプトが参照したり編集したりするファイル/フォルダへのアクセス権限が必要になります。
開発アカウント(デプロイユーザー)にアクセス権限がないファイルを参照しようとするとエラーになりますので、デプロイするユーザーはスクリプトが参照しているファイル/フォルダに対して自分のアカウントが適切に共有設定をされているかの確認が必要です。
先程と重複しますが、企業で運用する場合には、システム用のアカウントを1つ作成してそのアカウントにデプロイさせることでウェブアプリを通して作成されるファイルなどのオーナーをそのアカウントに集約することが容易になりますし、見せたくないデータなどを多数の従業員に対して公開せずにウェブアプリから呼び出されたスクリプトに処理をさせることができます。管理という側面で考えれば、企業での運用ではシステム用アカウントを使って管理をすることをおすすめします。
アクセスしたユーザーとして実行する場合
「ウェブアプリケーションにアクセスしているユーザー」を設定した場合、ウェブアプリに初回アクセス時にウェブアプリからスクリプトがアクセスしたユーザーの代わりに処理を実行することを許可するかどうか確認がされます。
許可をするとウェブアプリケーションとして開発したページが表示され、その後の処理はすべてアクセスしているユーザーとして実行がされます。
自分として実行する設定ではデプロイユーザーにのみスクリプトが参照したり編集したりするすべてのファイル/フォルダへのアクセス権限を付与すればよかったですが、アクセスしているユーザーとして実行する場合は、利用者ユーザーにスクリプトが参照したり編集したりするすべてのファイル/フォルダへのアクセス権限を適切に設定する必要があります。そのため、利用者ユーザーであれば誰でもデータにアクセスできる状態になりますのでデータの秘匿性が失われます。また、実行ユーザー = 各利用者アカウントですので、ファイルの作成時などはファイルオーナーはそのユーザーになってしまいます。
上記イメージでも簡単に記載しましたが、各ユーザーが初回にウェブアプリにアクセスした際に、スクリプトが自分の代わりに処理をすることを許可するかどうかの確認画面が表示されます。ここで許可を行うことでそのユーザーがウェブアプリを実行できる状態になります。
ただ、1つのウェブアプリに対して何名でも利用者にできるかと言うとそうではなく、1つのウェブアプリにつき、基本的には100ユーザーまでしか許可設定ができないため、大人数アプリを使うことが前提の場合はこの運用ではうまくいかなくなるケースも出てくると思います。(申請をしてGoogleから承認されれば上限を変更(撤廃)することは可能なようですが、申請は少し手間がかかります)
デプロイをテスト(末尾devのURLにアクセス)する場合
デプロイをテストする場合には、上記のウェブアプリケーションにアクセスしているユーザーとして実行する設定に近いですが、テストを実行するユーザーとして実行がされるため、関連するすべてのファイル/フォルダに対して適切なアクセス権限付与が必要です。
また、当該のスクリプトへの編集権限の付与も必要ですので、忘れずに設定するようにしましょう。
実行ユーザーと挙動を応用する(24時間ぶっ通しでプログラム処理させる)
上記の『【日本語リファレンス】GASの日次割当と日次制限について』の記事でも取り上げましたが、GASでは1日にトリガーによる処理ができる上限時間が設定されています。そのため、1アカウントでは上限に到達するとエラーが出るようになってしまいます。この制限を回避して1日ぶっ通しで処理を実行する小ネタをご紹介しようと思います。
これから書くことはGoogleの仕様を無理やり無視するやり方なので、Google先生に怒られてしまうかもしれませんが、指摘が入るまで記載しようと思います。
必要なもの
日次割当や制限はアカウントごとに設定されているものになります。そのため、制限を回避するには実行アカウントを変更するしかありません。そのため、24時間ぶっ通しで処理を行うためには相当数のアカウントが必要になります。
無料アカウントであれば1日にトリガー起動された処理の合計時間が90分以上になるとエラーが発生します。有料アカウントの場合は処理時間の合計が6時間以上になるとエラーが発生します。
そのため、24時間処理を回し続けるためには最低でも下記のアカウント数が必要です。
アカウント種類 | アカウント数 | 算出根拠 |
---|---|---|
無料アカウント(gmail.comドメイン) | 16個 | 24時間 × 60分 ÷ 90分 |
有料アカウント(自社ドメイン) | 4個 | 24時間 × 60分 ÷ 360分 |
無料の場合で1日ぶっ通しで自動化トリガーを起動する場合は16個ものアカウントが必要になります。こんなに作ったらGoogle先生に怒られそうですが。。。
必要な事は上記アカウント数を確保するのみです。
24時間自動化GASシステムの処理の流れ
まずは、すべてのアカウントがメインとして処理することとなるスクリプトファイルを作成します。このファイルは対象のすべてのアカウントに対してアクセス権限を付与する必要がありますが、このファイルをライブラリ化します。ライブラリ化の方法については以前公開した『GASの実行可能APIとして公開の手順変更で少し焦った話』という記事を参考にしてください。
ライブラリの中に必要な処理は『トリガーの設定処理』『トリガーの削除処理』『実行したいメイン処理』『GET/POSTリクエストを送信する処理』この4つを用意します。そして、各アカウントでdoGet関数あるいは、doPost関数を設定し、「自分」として実行する処理としてだれでもアクセスすることができるようにウェブアプリケーション化します。
var maxRunTimeinMinutes = 90; //有料アカウントの場合は360
var maxTotalRunTime = 24 * 60 * maxRunTimeinMinutes; //処理上限時間(分)
var maxRunTime = 60 * 5; //1回の処理上限時間(分)
/*トリガーを設定する処理
*@param {string} functionName 各アカウントのメイン処理の関数名を引数として指定する
*@return {null}
*/
function setTrigger(functionName) {
ScriptApp.newTrigger(functionName)
.timeBased()
.everyMinutes(1)
.create();
}
/*トリガーを削除する処理
*@param {string} functionName 各アカウントのメイン処理の関数名を引数として指定する
*return {null}
*/
function deleteTriggers(functionName) {
var triggers = ScriptApp.getProjectTriggers();
for (var i in triggers) {
var trigger = triggers[i];
var triggerName = trigger.getHandlerFunction();
if (triggerName === functionName) {
ScriptApp.deleteTrigger(trigger);
}
}
}
/*メインの処理部分
*@param {string} url リクエスト先のウェブアプリURLを引数として指定する
*@param {string} functionName 各アカウントのメイン処理の関数名を引数として指定する
*@return {null}
*/
function mainFunction(url, functionName) {
deleteTriggers(functionName);
var startTime = Date.parse(new Date()) / 1000;
var properties = PropertiesService.getScriptProperties();
var totalRunTime = properties.getProperty("totalRunTime");
var data;
for (var i in data) {
var now = Date.parse(new Date()) / 1000;
if (totalRunTime + now - startTime > runTime - 30) {
sendPostRequest(url);
properties.deleteProperty("totalRunTime");
}
if (now - startTime >= maxRunTime) {
setTrigger(functionName);
/*
処理がどこまで進んだかを格納する処理
*/
var now = Date.parse(new Date()) / 1000;
totalRunTime += now - startTime;
properties.setProperty("totalRunTime", totalRunTime)
}
/*
メインの処理部分をここに記載する
*/
}
properties.deleteProperty("totalRunTime");
}
/*POSTリクエストを送信する処理
*@param {string} url リクエスト先のウェブアプリURLを引数として指定する
*@return {string}
*/
function sendPostRequest(url) {
var params = {
method: "POST", //GETでも良い
muteHttpExceptions: true
}
var response = UrlFetchApp.fetch(url, params);
return response;
}
ここまでで処理を行う下地が出来上がりました。実際の運用イメージを図解して下記に貼り付けておきます。
上記のイメージでは、有料アカウントの場合を例に挙げましたが、基本的な構成は無料でも有料でも変わらず、変わることは上限時間の違いにより必要なアカウント数が異なるということです。上記画像ではイメージをつかみやすくするために詳しい説明を割愛しておりますが、少し具体的に説明すると、1アカウントで実行した処理時間をコード内で累計として算出し、そのアカウントの制限時間に近くなった場合に自動的に処理を終わらせます。この際、処理の進捗状況をスプレッドシートなどに保存し、累計データを削除して次のアカウントのウェブアプリURLに対してGET/POSTリクエストを送信します。このGET/POSTリクエストを起点として、そのウェブアプリをデプロイしたユーザーとしてメイン処理が開始されます。あとはこの処理をアカウント間でループさせるという形で24時間ぶっ通しで処理をするためのGASシステムが出来上がります。
毎分定点的にデータを取得したいといった場合や、処理時間がかかるものを大量に処理したい場合など、制限に引っかかるようなシステムを構築する場合に活用できるのではないかと思います。
GASを開発する上では、避けることのできない制限という問題も、スクリプトの実行がどのように行われるのかということが理解できればいくらでも回避方法は思い浮かびそうですね。このような工夫を行って上限数を回避することが可能になります。これは上限時間に限らず、他の制限回避にも使える手段だと思いますが、ファイルの作成などを伴う場合、ファイルのオーナーが実行アカウントになってしまいますので、集約したい場合にはオーナーを変更する処理を入れてあげれば良いかと思います。
まとめ
だいぶ大作の記事を書いてしまいましたが、皆さんは今回の記事の内容を正確に把握していましたでしょうか?意識的に検証しないと掴みづらい内容かと思いますので、この記事が皆さんの知識の一つにになってより新しい活用方法が思い浮かんだら良いと思います。ひとつの頭で考えるよりも複数の頭で考えたほうが効率が良いですので、GAS開発者みんなでGASを更に盛り上げていきましょう!