※本記事はプロモーションを含みます
目次
スプレッドシートの共同編集現場でマクロや業務ツールを動かすときには、「誰がこのボタンを押したのか」や「誰の権限で確定処理が走ったのか」を正確に突き止めたい場面は無数にあります。なぜなら、監査ログへの打刻やステータス変更者の自動記入や処理実行者への完了通知メールなど、実行者の特定は社内ツールの信頼性を支える土台だからです。ただし、Google Apps Script(GAS)で実行ユーザーのメールアドレスを取得しようとすると、テスト環境では問題なく取れていたアドレスが本番のシート運用に入った途端にまったく取れません。なので、空文字のまま処理が進んでしまうトラブルによく直面します。というのは、この挙動の背景にはGASのセキュリティモデルと実行コンテキストごとの強い制限が存在しているからです。
getActiveUserとgetEffectiveUserの混同と空白の罠
実行者を取得しようとして真っ先に調べる際には、GASのAPIリファレンスには似たような役割を持つメソッドが並んでいます。代表的なツールがSession.getActiveUser()とSession.getEffectiveUser()です。ただし、この二者の意味を混同したまま実装を進めると、設計意図とまったく異なる人物のメールアドレスが記録されたり不可解な空白文字に悩まされたりすることになります。
Session.getActiveUser()は、まさに今スクリプトを直接起動している人間を指します。なので、スプレッドシートを開いてボタンをクリックしたりカスタムメニューを選択したりした張本人を特定できます。一方のSession.getEffectiveUser()は、そのスクリプトが実質的に誰の権限で動いているかを返します。というのは、スクリプトの所有者やデプロイ権限者を返しているからです。コンテナバインドスクリプトで一般の編集者がボタンを押した場合、アクティブユーザーは編集者自身です。ただし、エフェクティブユーザーは状況によってスクリプトオーナーを指すことがあります。なので、この違いを理解しないままログ記録を組むと、誰が押しても常にスクリプト作成者のアドレスばかりがシートに刻まれます。というのは、誰が更新したのか追跡できない事故につながるからです。
さらに厄介なのは、Session.getActiveUser().getEmail()を呼んでもエラーを吐かずに平然と空文字("""")を返してくる仕様です。例外が発生して処理が中断すれば開発者もすぐに異常に気付けます。ただし、返り値の型自体はStringであり中身だけが空っぽです。なので、そのままシートの最終行に空白が追記されます。というのは、後から監査ログを見直したときに初めて「記録が一切残っていない」という事態が発覚するからです。この空文字問題は文法ミスではありません。なので、Googleのプライバシー保護機能によって意図的にマスクされている結果です。
特に、Google Workspaceの組織内ではなく個人のGmailアカウント(@gmail.com)が混在している環境や共有ドライブでの権限付与形態、さらには外部共有設定の組み合わせによって、この情報遮断は容赦なく発動します。ただし、単にメソッドを呼ぶコードを書くだけでは実務に耐えられません。なので、どのようなコンテキストであれば安全にアドレスが取得できるのかを把握しておきましょう。
スクリプトを実行しているユーザーのアドレスを特定する基本記述
まずは基本となる構文を確認しましょう。スプレッドシート上でボタンに割り当てた関数を実行した際、操作中のGoogleアカウントを特定してログに残したり特定のセルに転記したりするための最小構成です。
function recordExecutionUser() {
// 現在スクリプトを実行しているユーザーの情報を取得
const activeUser = Session.getActiveUser();
const userEmail = activeUser.getEmail();
// 取得結果をログに出力して確認
Logger.log("実行ユーザー: " + userEmail);
// 空文字判定を行い、正しく取得できた場合のみ記録を進める
if (userEmail) {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
sheet.appendRow([new Date(), "データ確定処理", userEmail]);
} else {
Logger.log("ユーザーのメールアドレスが取得できませんでした(権限またはドメイン制約)。");
}
}
このコードは、スプレッドシートのメニューや図形ボタンから直接呼び出されることを想定しています。実行されるとSession.getActiveUser().getEmail()によって、現在ブラウザを開いて操作している人物のアドレスが文字列として取り出されます。なので、スプレッドシートの末尾に実行時刻と処理内容とメールアドレスが追加されます。
ただし、ここで注意しなければならないのは、上記のコードが正常に動く前提条件です。この処理が意図通りにアドレスを返す前提条件は、同一のGoogle Workspaceドメインに所属するユーザー同士が社内共有されたスプレッドシートを操作している場合に限られます。たとえば、自社のドメインがexample.comであり、操作者もexample.comのアカウントでログインしている状態を考えます。なので、GASはプライバシー侵害のリスクが低いと判断し、正規のアドレスを返してくれます。
一方で、開発者がローカル環境やテスト用の個人アカウントから動作確認をしていたり社外の協力会社メンバーが自身のGoogleアカウントでシートに入って操作したりした場合には、セキュリティ境界を越えたとみなされます。なので、userEmailには空文字が代入されます。ただし、コードの後続部分でif (userEmail)のように空チェックを挟んでおかないと、宛先が空のままメール送信関数(GmailApp.sendEmailなど)へ渡されます。というのは、実行時エラーでクラッシュする原因になるからです。
トリガー実行時にアドレスが取れなくなる境界条件
GASで実行ユーザーのメールアドレスを取得する処理において、最も開発者が嵌まりやすい罠はシンプルトリガーとインストーラブルトリガーの境界です。スプレッドシートには、セルを編集した瞬間に動くonEdit(e)やファイルを開いたときに動くonOpen(e)といったシンプルトリガーが用意されています。ただし、これらの中でユーザーアドレスを取得しようとすると高い確率で失敗します。
シンプルトリガーは、ユーザーの明示的な認証ダイアログ(OAuth認証画面)を経由せずにバックグラウンドで即座に実行される特性を持っています。悪意のあるスクリプト作成者がスプレッドシートを不特定多数に閲覧させ、誰かがセルを触った瞬間にその人のメールアドレスを密かに盗み取るような攻撃が存在します。なので、攻撃を防ぐためにGoogleはシンプルトリガー内での個人情報アクセスを厳格に遮断しています。ただし、onEdit(e)の中でSession.getActiveUser().getEmail()を実行しても、同一ドメイン内であっても空文字しか返ってきません。というのは、イベントオブジェクトに含まれるe.userを参照した場合でも同様に制限を受けるからです。
この問題を回避して編集者のアドレスを取得するためには、手動で設定するインストーラブルトリガー(編集時トリガー)に移行しましょう。インストーラブルトリガーは設定時に管理者が明示的にスクリプトのアクセス権限を承認します。なので、より広い権限が付与されます。ただし、ここにも大きな落とし穴が存在します。インストーラブルトリガーであっても、別ドメインのユーザーや一般公開されたリンクからアクセスしてきたユーザーが編集した場合にはプライバシー保護の壁が働きます。なので、アドレスはマスクされます。
また、インストーラブルトリガーを設定したユーザー自身がトリガーの作成者となります。なので、コード内でSession.getEffectiveUser()を呼んでしまうと、誰がシートを編集したかに関係なく常にトリガーを作った管理者のアドレスが返ってきます。編集者を特定したいのであれば、インストーラブルトリガー環境下で確実にSession.getActiveUser()を評価しましょう。ただし、評価の際には編集者が組織内のドメインアカウントであることを担保しておきましょう。
アドレス取得が空になる環境での代替アプローチ
同一ドメイン外のユーザーが関わる運用や外部委託先と共有しているスプレッドシートでは、技術仕様上Session.getActiveUser().getEmail()だけに依存したユーザー特定は破綻します。APIが仕様として値を渡さない以上、コードの書き方をどれほど工夫してもシステム側から強制的にメールアドレスを抜き出すことは不可能です。なので、このような環境で更新ログや操作ログを残したい場合は設計レベルでの回避策を導入しましょう。
現実的かつ堅牢なアプローチの一つは、Googleフォームを経由させる設計です。不特定多数や社外メンバーに入力を行わせる場合、スプレッドシートの直接編集ではなくGoogleフォームの回答送信を使います。Googleフォームの設定で「メールアドレスを収集する」を有効にしておけば、フォーム送信時にGoogle側の基盤によって確実に回答者のアカウントが収集されます。なので、連携されたスプレッドシートのタイムスタンプの隣に正確なアドレスが記録されます。GASのフォーム送信時トリガーを使えば、その記録されたアドレスをトリガー内で安全に読み出せます。なので、後続の業務ロジックをスムーズに回せます。
もう一つのアプローチは、HTML Serviceを利用したダイアログやサイドバーでの明示的な入力インターフェースです。マクロ実行ボタンを押した際には、いきなりバックグラウンドで処理を完結させません。なので、画面上に小さな入力フォームを表示させ、担当者名や社員番号や連絡先アドレスを入力または選択させてから送信させます。外部アカウントであっても、本人が明示的に送信した値であれば取得上の制限は発生しません。ただし、悪意のあるなりすましが懸念されるクリティカルな業務でない場合に限られます。なので、業務運用上のルール付けと入力ダイアログの組み合わせで十分に実用的な監査ログを構築できます。
さらに、Webアプリケーション(Web Apps)としてGASをデプロイし、実行ユーザーの権限でアクセスさせる方式もあります。「次のユーザーとしてアプリケーションを実行:アクセスしているユーザー」としてデプロイされたWebアプリであれば、ブラウザ上で個々の認証が要求されます。なので、組織内アクセスであれば確実に実行者を識別できます。ただし、組織外ユーザーに一般公開した場合はアクセス権の管理が複雑化します。なので、運用のセキュリティ要件と照らし合わせて慎重に選定しましょう。
運用ログとしてシートへ安全に記録するための防衛設計
同一ドメイン内で無事にメールアドレスが取得できる環境が整ったとしても、ログ記録の運用にはもう一段の防御策が必要です。スプレッドシートの更新履歴にマクロ実行者のログを追記していく際、ただ配列を作ってシートに書き込むだけではリスクがあります。というのは、ネットワークの瞬断やシートの同時更新競合や予期しない権限エラーによって業務プロセス全体が停止してしまう危険があるからです。
第一に、メールアドレスが空文字だった場合のフォールバック処理を必ず用意しましょう。もし外部メンバーが例外的にボタンを押してしまった場合、アドレスが取れないからといってスクリプトが例外エラーで落ちてしまうと大変です。というのは、メインの集計処理やステータス更新処理までロールバックされ業務が止まってしまうからです。メールアドレスが取れなかった場合は"不明なユーザー(外部または未認証)"といった代替文字列をログ変数に代入します。なので、主処理そのものは継続させるか、あるいは明示的にユーザー向けのアラートを出して安全に終了させる分岐を書いておきましょう。
第二に、ロックサービスの併用です。複数人が同時にスプレッドシートを開いてボタンを押すような運用では、最終行の取得(getLastRow())と書き込み(appendRow())の間に競合が発生します。なので、ログ行が上書きされたり順番が前後したりすることがあります。LockService.getDocumentLock()を用いて排他制御をかけ、ログの書き込み中は他の実行者を待機させる設計にしておきましょう。というのは、排他制御によって実行履歴の抜け漏れや破壊を防ぐことができるからです。

