見出し画像

【Googleスプレッドシート / GAS】 自作関数で共有メンバーを出力 & 共有操作

Googleドライブの共有に関しての note 2回目です。

前回は 初心者向け内容ってことで、Googleドライブの基本から 共有の際の注意点、共有名人になるためのポイントなどを書きました。珍しく GASもスプレッドシート関数も登場しない回

今回は GASを使った 共有状況の取得や共有設定の操作について書いていきます。




Googleドライブの共有ダイアログの不便な点

前回書きましたが、Googleドライブの共有は フォルダ単位、もしくは複数ファイルを 選択した状態で 一括で共有設定の操作が可能です。

では、なぜGoogleドライブの共有や操作で GASを必要とするのか?

それは ファイル共有設定のダイアログ(ポップアップ画面)での操作が、イマイチ不便だからです。



共有メンバー全員のメールをさくっと取得できない

メアドだけコピペが出来ない

複数人のメンバーと共有しているファイルAと、まだ誰とも共有していない ファイルBがあって、このファイルBも ファイルAの共有メンバーと共有したい場合。

ベストなのは前回書いたとおり、そのメンバーと共有用のフォルダを用意する方法です。

しかし ファイルAを単体で共有してしまった場合は、フォルダ共有に移行するにしても、一度 ファイルAの共有メンバーを抜き出す必要があります。

これが、実は少し面倒で 上のようにダイアログの画面からコピーしようとすると余計なものまでコピーされてしまいます。

また、この画面では共有メンバーが数十人以上いると、 編集権限者が 何人、閲覧者が何人 といったことが パッと見わかりません。これが結構不便だったりします。



「共同編集者にメール」機能はメールを送らなくても便利

完全にやりたいことが実現できるわけではありませんが、これには一応対応策があります。

それは「共同編集者にメールを送信」という機能です。


こちらを立ち上げると、このように共有メンバーにメールを送れる画面が立ち上がるのですが、ここで

編集者、コメント可の閲覧者、閲覧のみ の権限のそれぞれの人数が確認できます。


さらに チェックのON / OFF で 編集者のみを表示、閲覧者のみを表示と切り替え、簡単に 権限ごとのメンバーを確認することが出来ます。

ただし、各メールアドレスがチップ化しており、人によっては メールアドレスではなく 設定した名前で表示される為、ここからも 共有メンバーのメアドをサクッとコピペが出来ません

この共有メンバーのメールアドレスをサクッと取得する 方法が無いので、GASで作っちゃえば便利そう というのがGASを使うべき1つ目の理由です。



登録済みの共有メンバーは一括操作ができない

もう1つの不便な点は、共有メンバーを複数選択して一括で 権限変更が出来ないことです。

改行区切りやカンマ区切りで複数メアドを一括で共有メンバーに追加はできるんですが、共有メンバー登録済みの人たちに対しては 一括での操作方法がありません。

編集者を閲覧者に変えたり、共有をやめたくて全員のアクセス権を削除といった操作は一人ずつ変更していくしかないのです。

これも GASで対応できると便利ですね。


メンバーの再追加で 権限アップは可能

こちらも限定的ですが解決法は一応あって、 いまより上の権限への変更であれば、既に共有メンバーに存在するアカウントを 共有メンバーに再追加する方法で一括変更可能がです。

権限は

(下) 権限なし ⇒ 閲覧者 ⇒ 閲覧者(コメント可) ⇒ 編集者 (上)

の順により権限が強い(上である)と言えます。

つまりこの方法では、 編集者 ⇒ 閲覧者 や、閲覧者 ⇒ 権限なし といった 下の権限への変更は出来ません。

これは 後で触れますが、実はGAS(Spreadsheet Service) を使った場合も同様の挙動となりますので覚えておきましょう。



Spreadsheet Service から共有情報を取得する

共有を操作するファイルはスプレッドシートとは限りませんし、共有の設定はどちらかというと Googleドライブの管轄なんで、共有を制御する GASは Drive Service を使った方が高度な管理ができます。


でも、Driveサービスは初心者にはちと難しい部分もあるので、今回は 共有操作の対象とするファイルを Googleスプレッドシートに限定して、GASの中でも Spreadsheetサービスで 使える 共有メソッドについて 検証していきましょう。



Spreadsheet Serviceの共有関連メソッド

スプレッドシートサービス、はスプレッドシートを扱う為の GAS類です。

スプレッドシートを操作する SpreadsheetApp クラス から使える共有関連のGASは 必要最低限といった感じですが、一応上のように 共有メンバーの 取得、追加、削除が揃っています。

共有メンバーの権限変更をするメソッドはありません。

さらに残念ながら、リンク共有閲覧者(コメント可)については、設定情報の取得や操作が出来ません。

これらは Driveサービスの方の共有関連メソッドを使う必要があるってことです。


上の表を見ていただくとわかりますが、共有メンバーの追加は

addEditors(emailAddresses)
addViewers(emailAddresses)

※どちらも String[] 追加するユーザーのメールアドレスの配列 を受け取れる

と最後に sが付いた複数を一気に処理可能なメソッドが用意されていますが、メンバーからの削除に関しては

removeEditor(emailAddress)
removeViewer(emailAddress)

※配列指定はできない

単体処理のみです。手動でやる時と一緒ですねw

もちろん GASであればコードで繰り返し処理を書けば対応可能です。

ちなみに 同じメソッドでも

removeEditor(emailAddress)
removeEditor(user)

と引数が emailAddressuser とありますが、 この userの方はユーザーオブジェクト(ユーザー情報の塊のようなもの)です。

userから getEmail()で emailAddressを取得できますが、メアドを取り出さなくても GAS処理内で 取得した userをそのまま使って 共有の削除や追加処理ができるってことですね。

(ピンとこない方は、後で実例がでるので安心を)




GASでスプレッドシートの共有メンバーを取得する

それでは、まずは get系を使って 、今開いているスプレッドシートの 共有状態、つまり ↑ この情報を取得してみましょう。

取得で使えるメソッドは ↓ この3つですね。

メニューの 拡張機能 > AppScript からスクリプトエディタを開いて。

function memberCheck(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  //オーナーを取得
  const ownerEmail = ss.getOwner().getEmail();
  
  //編集者を取得
  const editorsEmail = ss.getEditors().map(e => e.getEmail());

  //閲覧者を取得
  const viewersEmail = ss.getViewers().map(v => v.getEmail());

  console.log("オーナー",ownerEmail);
  console.log("編集者",editorsEmail);
  console.log("閲覧者",viewersEmail);
}

とりあえずこんなコードを入れてみましょう。

※初回実行時は スクリプトへの権限の承認が必要となります。

少しコードを解説します。

ss.getOwner() で取得できるのは、先ほども登場した USERオブジェクト(ユーザー情報の塊みたいなもの)なんで、そのままログに出力しても意味がありません。

そこから getEmail() で、Emailアドレスを取得して 文字列としてログに出力する必要があります。

オーナーは1名なので、これは直接

  //オーナーを取得
  const ownerEmail = ss.getOwner().getEmail();

このように getEmail()できます。


getEditors()、getViewers() の方は USERの配列を返してくるので、

const editorsEmail = ss.getEditors().getEmail() とは出来ません。

[user1, user2, user3 ….]

[user1のEmail, user2のEmail, user3のEmail…]

このように 配列の個々の Userオブジェクトを e-mailにしてあげる必要があります。

配列の個々の要素に処理を行うのは 配列メソッドの mapが便利ですね。

  //編集者を取得
  const editorsEmail = ss.getEditors().map(e => e.getEmail());

  //閲覧者を取得
  const viewersEmail = ss.getViewers().map(v => v.getEmail());


これを実行してみると・・・

それぞれ メールアドレスが取得できました!

ちなみに 閲覧者(コメント可)は、スプレッドシートサービスから取得した場合は、普通の閲覧者と同じ扱いで「閲覧者」として取得されます。




Spreadsheetサービス の getViewers は全共有メンバーを返す

「あれれー、おかしいぞー??」

密室殺人発生ではありません。

GASのログをよく見ると、違和感があります。

オーナーは mirなんでいいとして、編集者の中にはオーナーも含まれていて、閲覧者にはオーナーも編集者も含まれていますね。

なんでだ?

実はこれ、コードの記述ミスとか不具合ではなく 仕様っぽいんですよね。

ただし、公式では 特にそのようなことは明言されていません。

編集者も含むとは書いてない


しかし、実際に試してみると Spreadsheet Service の GASから共有メンバーを取得した場合

getEditors()
スプレッドシートの編集が可能なユーザー全て(オーナー含む)

getViewers()
スプレッドシートの閲覧が可能なユーザー全て(オーナー、編集者含む)

※アカウント指定で追加したメンバーのみが対象

このような結果を返します。

つまり、getViewers() で得られる ユーザーは、リンク共有を除いた そのスプレッドシートにアクセスできる全員ってことですね。

※ 2024年1月時点での 挙動であって、今後変更される可能性はあります

この理解を前提にコードを書かないと正しい操作が出来ません。



GASで閲覧者のみを取得したい

では、オーナーや編集者を除いた 本当の閲覧権限者のみ(画像の場合だと 桃太郎チームのみ)を取得したい場合は、どうすればよいでしょうか?

※ここでは コメント可も 閲覧権限として区分されます。

これは 2つの配列を比較した差分を取得と考えましょう。

getViewers() で得られるの 要素(アクセスできる全員)のうち、getEditors() で得られる要素(編集権限以上)に存在しない(含まれない)ものを抽出(絞り込み)すれば、閲覧権限者のみを取得できそうです。

配列同士を比較して、含まれないものを抽出。なんか スプレッドシートのお題にもありそうですね!

絞り込みといえば、シート関数でも同じみの filter が配列メソッドにもあり、今回はこちらが使えます!

filterは 配列の個々の要素に対して コールバック関数を実行し、 true を返したものだけに絞り込んだ配列を返します。

簡単な例を書くと

※ vは 引数として適当においてるだけなんで nでも numでもなんでもよい

こんな感じの使い方になります。

今回の場合は 編集者以上の Emailの配列 editorsEmail に「含まれない」場合 TRUEとしたいので、配列に含まれる場合 TRUEを返す includes を使って

その結果を !(論理否定)で反転させて、

要素が含まれる場合 false
要素が含まれない場合 true

とすればよさそうです。

//閲覧者のみを取得
  const viewersOnly = viewersEmail.filter(v => !editorsEmail.includes(v));

さきほどのコードに加筆してみましょう。

function memberCheck(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  //オーナーを取得
  const ownerEmail = ss.getOwner().getEmail();
  
  //編集者を取得
  const editorsEmail = ss.getEditors().map(e => e.getEmail());

  //閲覧者を取得
  const viewersEmail = ss.getViewers().map(v => v.getEmail());

  console.log("オーナー",ownerEmail);
  console.log("編集者以上",editorsEmail);
  console.log("閲覧者以上",viewersEmail);

  //閲覧者のみを取得
  const viewersOnly = viewersEmail.filter(v => !editorsEmail.includes(v));

  console.log("閲覧のみ",viewersOnly);

}


閲覧のみのユーザーが取得できましたー。



GASをカスタム関数化して 誰でも使えるようにしたい

先ほど作成したコードを さらに改良して カスタム関数(自作関数)にして、編集者もシート上で気軽に使えるようにしてみましょう。

カスタム関数化のポイントは、以前 シート名を返す関数作成の回で触れていますが、

最後に return で結果を返すことです。

これでシートに結果を出力させることが出来ます。スプレッドシートのカスタム関数は、結果が配列だった場合は自動でスピるのも便利です。

/**
 * このスプレッドシートの共有メンバーを返す GAS関数
 * @param {a} a 出力切り替え 0(初期値) 全ユーザー、1 オーナー、2 編集者、 3 閲覧者
 * @customfunction
 */
function memberCheck2(a=0) {
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  const owner = ss.getOwner().getEmail();
  const users = ss.getViewers().map(u => u.getEmail());
  const editors = ss.getEditors().map(u => u.getEmail());

  switch(a){
    case 0:
    return users; //全ユーザー出力

    case 1:
    return owner; //オーナーのみ出力
    
    case 2:
    return editors.filter(u => u != owner); //編集者のみ出力

    case 3:
    return users.filter(u => !editors.includes(u)); //閲覧者のみ出力

    default:
    return "0~3で指定してください"

  }

自作関数化の際の 分岐としては switch文を使ってみました。

switch といえば break とセットで使うことが多いんですが、今回は各ケースで return で処理を終わらせてるので breakは不要です。

この切り替えには 引数 a の値を使っており、さらに

function memberCheck2(a=0)

とすることで、aの初期値(入力しなかった場合)を 0として 前メンバー出力としています。


また、↓ このようにシート上で 関数の候補として表示させる + 説明表示させる オートコンプリート機能を実装する為に、

コードの上の部分に

/**
 * このスプレッドシートの共有メンバーを返す GAS関数
 * @param {a} a 出力切り替え 0(初期値) 全ユーザー、1 オーナー、2 編集者、 3 閲覧者
 * @customfunction
 */

を加筆しています。

これも過去のnote シート情報取得のGAS関数の回で触れています。

シート上で関数を動かしてみましょう。

GAS自作関数で 共有メンバーの書き出しが出来ました~。

シートに書き出された共有メンバーのメアドは、そのままコピーしてまるっと 他のファイルの共有メンバーとしてペースト可能です。



他のスプレッドシートの 共有状況を取得する

シート上で使える 自作関数は GASの初回承認が不要で、シートの編集者であれば誰でも使える気軽さが魅力です。

ただし、その権限(出来ること)は非常に制限されており、他のスプレッドシートの情報を取得するといったことも出来ません。

たとえばコードを修正して、他のスプレッドシートのIDを第1引数で指定して、指定したスプレッドシートの共有情報を返す自作関数を作成しても、このように

「 You do not have perission to call (権限がありません!)」

とエラーになってしまいます。

他のスプレッドシートの共有情報を取得したい場合は、シート上でカスタム関数を使う方法は諦めて、エディタ上(もしくは UIから)実行する方法にしましょう。

//他のスプレッドシートの共有情報を取得する関数
function memberCheck3(ssId,a=0) {
  const ss = SpreadsheetApp.openById(ssId);

  const owner = ss.getOwner().getEmail();
  const users = ss.getViewers().map(u => u.getEmail());
  const editors = ss.getEditors().map(u => u.getEmail());

  switch(a){
    case 0:
    return users.toString();

    case 1:
    return owner.toString();
    
    case 2:
    return editors.filter(u => u != owner).toString();

    case 3:
    return users.filter(u => !editors.includes(u)).toString();

    default:
    return "0~3で指定してください"

  }

}

function test(){
  const ssId = "共有情報を取得したいスプレッドシートのID";

  console.log(memberCheck3(ssId,2));
}

たとえば、このように memberCheck3 として、スプレッドシートIDを引数として受け取ることで、共有情報を取得したいスプレッドシートをIDで指定できるコードに改良したものを用意します。

下の test関数で シートIDと 出力モードを指定すると、

このようにログに 共有情報を出力することができます。

ちなみに toString() で出力するコードを文字列化していますが、これは

toStoring()をつけない場合

toStrigをつけない状態でログに出力すると、このような表示になってしまうい コピペで使えないからです。

これをこのままコピーして 共有追加に入れると シングルクォートが付いてる為エラーになります。


IDで指定したスプレッドシートの共有情報も取得することができました。

取得に関してはこれくらいで良いでしょう。




スプレッドシートの共有をGASで操作する

共有メンバーのメールをさくっと取得は、上の方法で出来ました。

コンテナバインドでスクリプトを書いた 自分自身の スプレッドシートに限定はされますが、カスタム関数を作成することで、シート上に関数で共用メンバーを出力することが出来ました。

では、共有メンバーの操作(削除、追加、変更)はどうでしょうか?

これは、カスタム関数ではなく スクリプトを実行する必要があります。ありそうなケースに対応する為のサンプルコードを 3つほど紹介します。



スプレッドシートの共有を解除するGAS

1つ目のあるあるネタとして、 設定画面からだと 1人ずつ 「アクセス権を削除」に切り替えが必要な、一括共有メンバー削除(つまり 共有解除) をGASでやってみましょう。

つかうメソッドは remove系ですね。

今回も公式には

と書いてありますが、先ほどのGASによる共有メンバーの情報取得で確認した通り、Spreadsheetサービスにおける Viewerは 閲覧権限以上、つまり全メンバーとなるので、実はremoveViewer は 編集者にも (削除)効力がありますw

つまり、アクセス権を削除するなら removeViewer() だけ使えばOKです。

コードは

//スプレッドシートの共有を解除するスクリプト(自分がオーナーのファイルを想定)
//リンク共有の解除は不可
function removeMembers() {
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  //他のスプレッドシートを操作する時はこっちを使う
  //const ss = SpreadsheetApp.openById("操作したいスプレッドシートのID");

  //スプレッドシートにアクセスできるユーザーを取得
  const viewers = ss.getViewers();

  //権限を削除
  viewers.forEach(viewer => ss.removeViewer(viewer));

}

こんな感じになります。

const viewers = ss.getViewers();

まずは スプレッドシートから getViewsers()で、アクセス可能な全員を取得。
あとで権限削除に使う removeViewer は、

removeViewer(user)

このように userオブジェクトを指定できるので、わざわざ Emailを抜き出す必要はありません

ただし、removeViewerは配列を受け取れないので、一人ずつ処理を繰り返す(ループする)必要があります。

配列の個々の要素に対して処理を繰り返す・・・  定番の forEach ですね!

  //権限を削除
  viewers.forEach(viewer => ss.removeViewer(viewer));

これだけです。

オーナーを考慮しなくていいの?

と気にされる方もいるかもしれませんが、オーナーの権限は同然はく奪することは出来ないので放置でよいです。

実行すると・・・

このように編集者だろうが閲覧者だろうが 関係なく、共有メンバーからオーナー以外は全て削除されました。

公式リファレンスに書いてあることと違うんですよね。。ま、とりあえず現状ではこれで出来るってことです。

もちろん、Spreadsheetサービス では操作できない リンク共有の解除は出来ません



スプレッドシートの共有メンバーを、そのまま別のスプレッドシートにコピー

これもあるあるですが、今度は スプレッドシートの共有メンバーと各ロールを、そのまま 別のスプレッドシートに引き継いで(コピーして)みましょう。

コピー元のスプレッドシートからの共有メンバー、ロールの取得は先ほどやった方法で良いですね。

その取得したメンバーとロールを add系で コピー先のスプレッドシートに設定します。

コードはこんな感じ。

//シートの共有設定を別のスプレッドシートにコピー(両方とも自分がオーナーを想定)
//コメント可、細かい設定、リンク共有設定は対応不可
function copyMembers(){
  const fromSS = SpreadsheetApp.openById("コピー元のシートID");
  const toSS = SpreadsheetApp.openById("コピー先のシートID");

  //コピー元から 全ユーザーと編集者を取得
  const users = fromSS.getViewers().map(u => u.getEmail());
  const editors = fromSS.getEditors().map(u => u.getEmail());

  //コピー先の共有メンバーに追加
  toSS.addViewers(users);
  toSS.addEditors(editors);
}

addViewers(emailAddresses) と addEditors(emailAddresses) は 先ほどの remove系と違って EmailのString配列を渡すことで一括処理できるので、

  //コピー元から 全ユーザーと編集者を取得
  const users = fromSS.getViewers().map(u => u.getEmail());
  const editors = fromSS.getEditors().map(u => u.getEmail());

先に コピー元の viewsers(全ユーザー)と editors(編集者 とオーナー)を取得し、mapで配列の中身を Email化しておきます。

  //コピー先の共有メンバーに追加
  toSS.addViewers(users);
  toSS.addEditors(editors);

あとはこれを addViewser、addEditors に入れるだけ。

ちなみに 編集者 や オーナーは usersにもeditorsにも含まれていますが、こちらは気にしなくても良いです。

この addViewsers()、addEditors() による 既に追加済みのメンバーに対する変更(追加)は、冒頭で書いた通り 手動による再追加の場合と一緒で、より上の権限への 変更のみが適用され 下の権限は無視されます。

そして、Spreadsheetサービス のaddViewsers()、addEditors() による メンバーの追加は、相手に共有のメール通知が飛びません。(通知を飛ばす設定はありません)

これは 大量に共有処理する際にはありがたいですね。

実行後

ただし、コードにも書いていますが、Spreadsheetサービスでは扱えない、

  • 閲覧者(コメント可)

  • 歯車ボタンから設定する コピーや印刷の制限などの設定

  • リンク共有の設定

には対応できません。



スプレッドシートの共有メンバーを 全員 閲覧者に変更する

Spreadsheetサービス の add 系による 共有メンバーの追加は、手動と一緒で既に共有メンバーに登録済みのメンバーに対しては、上の権限への変更しか出来ないと書きました。

じゃあ、既存メンバーの下の権限への変更は GASではどうすればよいか?

たとえば、編集権限メンバーを含め 現在の共有メンバーを全員閲覧者としたい場合は、一度 remove で共有メンバーから削除してから 再追加するという方法になります。

もちろん、通知は飛ばないのでこっそり処理することは出来るんですが、一度削除ってのは気分的にはあまりよくないんですよね。。

ここは仕方ないと割り切りましょう。

//共有メンバーを全員 閲覧者に変更する
function changeViewers(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();

  //他のスプレッドシートを操作する時はこっちを使う
  //const ss = SpreadsheetApp.openById("操作したいスプレッドシートのID");

  //全ユーザーを取得
  const users = ss.getViewers().map(u => u.getEmail());

  //全ユーザーの権限を削除
  users.forEach(user => ss.removeViewer(user));

  //閲覧者として全ユーザーを追加
  ss.addViewers(users);
}

ただ、コードとしてはなかなか面白くて、この 共有メンバーの権限を下げる変更処理は、

getViewers() ,removeViewer() , addViewers()

共有系メソッド3連発なんですよね。

メンバーの共有権限の「変更」というメソッドがない為、取得したものを全削除して再追加、このような処理になります。

実行してみると・・・

このように オーナ―には影響はなく、他は全メンバーが 閲覧者となりました。(コメント可も 普通の閲覧者となります)

※こちらも リンク共有に関しては権限変更はできません。



GAS スプレッドシートの世界でも、共有変更はある程度出来る

今回は スプレッドシートの共有をGASで出力、操作するってことで、カスタム関数による共有メンバーの出力他、あるあるなスプレッドシートの共有権限の操作 3例を紹介しました。

割と簡単なコードで出来る共有相手に通知が飛ばない、というメリットは感じますが、

  • スプレッドシート以外のファイルの共有操作は扱えない

  • リンク共有に関しては扱えない

  • 閲覧者(コメント可)など細かい設定は出来ない

といった不満が残りますね。

特に、Googleドライブ内の 様々なファイルのうち、スプレッドシートだけの共有操作ではちと不便です。

次回は、Googleドライブ共有ネタの最後。Driveサービス(DriveApp)での共有操作について書きたい・・・と思いつつ、連続で飽きてきたんで他のネタ挟むかも。。


この記事が気に入ったらサポートをしてみませんか?