社内で作成されまくったメーリングリストをGASで整理する（情シス向け）

#はじめに
多くの情報システム管理者が頭を抱える事の１つにメーリングリスト（以下ML）の管理があります。

うちの会社では**G Suite（法人向けGoogleApps）**を利用しているのですが、長年問題を抱えていたMLの管理について、少しのルールと簡単なGASのコードで大分マシになりましたので、共有させていただきます。


#会社のメーリングリスト多すぎ問題
会社にはおびただしい数のMLが存在します。初めてに数えたときは2000個近くありました。
働いているメンバーは自分がどれだけのMLに参加しているかなんて普通は考えません。

しかし、誰かがその管理を行う必要があり、整理されているべきです。
日々、整理を行うにあたって、下記の項目が整理されていないと一気に管理が面倒になります。

- 何を目的に作成されたのか？（何のPJなのか？）
- 管理者は誰なのか？
- メンバーは誰なのか？
- 現在使われてるのか？

幸い、GSuiteはMLとして使用するグループには名前やメモを入れる事ができるので、目的や担当者を登録できます。
せっかくの年末の棚卸し期なので、一覧化して必要でないものはガンガン削っていきましょう。

#スプレッドシートの用意
MLのメアド、MLの説明、ML名、メンバー数、メンバーのメアドを一覧化したいと思います。
スプレッドシートのスクリプトエディタからGASのコードを入力していきます。

####トークンとトリガー
このコードでキモとなるのが、トークンとトリガーの関係です。
2000件近くのML、1件につき、5人のメンバーが登録されていた場合、10000件近く処理することになります（実際はもっと多いですが）。
GASの仕様で、処理が6分を超えるとタイムアウトエラーが起こります。
なので、処理ごとにプロセストークンを発行し、4分を超えた場合に現在のプロセスを再開するトリガーを発行する必要があります。

####コードに関する注意点
情シス向けに作成しております。管理者権限が無いと動作しない部分があります。

```
/**
 * 全MLとユーザ一覧を取得
 */
function getGroupAddressAndUsers()
{
  // アクティブなシートを設定
  var ss = SpreadsheetApp.openById("ここにシートのIDを入力");
  var sheet = ss.getSheets()[0];
  var pageToken, prev_pageToken;
  //スクリプト情報を取得
  var Properties = PropertiesService.getScriptProperties();
  //現在のページトークンを取得。無かったら初期化
  var pageToken = Properties.getProperty("task_page");
  if(pageToken == 'undefined') {
    pageToken = null;
  }
  
  var rows = [];  
  var starttime = new Date(); //時刻格納用の変数
  //タイトルを設定
  sheet.getRange(1, 1, 1, 5).setValues([["メールアドレス", "説明", "名前", "メンバー数", "メンバーのアドレス"]]);

  do {
    //現在時刻と開始時刻の差分を取得
    var nowtime = new Date();
    var nowdiff = parseInt((nowtime.getTime()-starttime.getTime())/60000);
  
    // ”Admin Directory API”からグループ一覧を取得
    all_groups_page = AdminDirectory.Groups.list({
      domain: 'gaiax.com', //使用しているドメイン
      maxResults: 20,      //取得する件数
      pageToken: pageToken //トークン
    });
    var groups = all_groups_page.groups;

    //取得するグループが無い場合は終了
    if (!groups) {
      return;
    }
    
    // グループに所属するメンバーの取得し、配列に保存
    groups.forEach(function(group, i) {
      var members = AdminDirectory.Members.list(group.email).members;
      rows = addMemberRow(rows, group, members);
    })

    //現在時刻と開始時刻を比較して、差分が4分を超えていたら処理中断
    //（5分を超える処理はタイムアウトエラーになるため）
    if(nowdiff >= 4){
      
      // 配列の内容をシートへ書き込み
      sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 5).setValues(rows);
      rows = [];

      //次の処理のトリガーを設置、プロパティに現在のトークンを登録しプログラムを停止
      Properties.setProperty("task_page",pageToken);
      setTrigger();
                  
      return;
    }
    
    //新しいトークンを取得
    prev_pageToken = pageToken;
    pageToken = all_groups_page.nextPageToken;            
  } while (pageToken);  

  //トークンに変化がない場合、完了
  if(prev_pageToken == pageToken) {    
    //プロジェクトトリガーを全削除
    deleteTrigger();
    //トリガー用変数の初期化
    Properties.setProperty("task_page",'');

    // シートに書き込み
    sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 5).setValues(rows);

  //5分を超えずに終わった場合
  } else {
    // シートに書き込み
    sheet.getRange(sheet.getLastRow() + 1, 1, rows.length, 5).setValues(rows);
    rows = [];
    
    //次の処理のトリガーを設置,プロパティにトークンを登録しプログラムを停止
    Properties.setProperty("task_page",pageToken);
    setTrigger();
  }
  return;
}

//メンバーの配列データを返す
function addMemberRow(rows, group, members) {
  // グループに所属するメンバーの取得

  var nowtime = new Date();//////////////////////

  //メンバーが居なかった場合
  if(members == undefined || members == 0){
    var cols = [];
    cols.push(group.email);
    cols.push(group.description);
    cols.push(group.name);
    cols.push(group.directMembersCount);
    cols.push("-----");
    cols.push(nowtime.toLocaleString());
    
    rows.push(cols);
  //メンバーが居た場合
  } else {
    for (var j = 0; j < members.length; j++){
      var cols = [];      
      cols.push(group.email);
      cols.push(group.description);
      cols.push(group.name);
      cols.push(group.directMembersCount);
      cols.push(members[j].email);
      rows.push(cols);
      cols.push(nowtime.toLocaleString());

    }
  }
  return rows;
}

//トリガーを全削除する関数
function deleteTrigger() {
  var allTriggers = ScriptApp.getProjectTriggers();
  for(var i=0; i < allTriggers.length; i++) {
      ScriptApp.deleteTrigger(allTriggers[i]);
  }
}
 
//トリガーを設置する関数（getGroupAddressAndUsers 関数を実行予定として登録）
function setTrigger(){
   deleteTrigger();
   var triggerman = ScriptApp.newTrigger("getGroupAddressAndUsers")
                   .timeBased()
                   .everyMinutes(5)
                   .create();
}

//トリガーをリセット
function resetTrigger() {
  var Properties = PropertiesService.getScriptProperties();

  //プロジェクトトリガーを全削除
  deleteTrigger();
  //トリガー用変数の初期化
  Properties.setProperty("task_page",'');
}

```