GAS

GASで契約管理! 管理シートを自動参照&通知日に自動メール通知!

0.はじめに

期日管理、自動化していますか?

「期日の〇ヶ月前になったら通知する」等は定型的で非常に自動化しやすい部分です。
その一例としてシート例とコード例をご紹介します。
もし業務の補助になるなら、ぜひチャレンジしてみてください。

1.スプレッドシート

こんな構成にしました。

契約管理更新シートのサンプル画像

E2セルにはこんな関数が入っています。

=ARRAYFORMULA(IF((D2:D=””)+(F2:F=””)+(F2:F=”-“),””,EDATE(D2:D,F2:F*-1)))

IF文でD列が空白orF列が空白orF列が-の場合は空に、
それ以外の場合はD列からF列の月数をマイナスした日付が入ります。

GASを学ぶとつい全てコードでやってしまいそうになりますが、
関数を適切に活用することでコードがシンプルになり可読性や保守性が向上します。

2.コード

//最初にライブラリdayjsを追加しておくこと
//スクリプトIDは1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB
//メイン処理
function alertContractMain() {
  const targets = getAlertTargets();
  if(targets.length == 0) return;

  for(let t of targets){
    let body = getHtmlBody(t);
    alert(t,body);
  }
}

//アラートメール送信
function alert(target,body){
  let mailArgs = {htmlBody : body};
  GmailApp.sendEmail(target.AlertTo, `契約更新通知 ${target.Title}`,"",mailArgs);
}

//HTMLメール本文を作成する
function getHtmlBody(target){
  const body = `下記、契約更新通知の時期となりましたため通知いたします。<br>
                <br>
                対象:${target.Title}<br>
                カテゴリ:${target.Type}<br>
                契約満了日:${dayjs.dayjs(target.EndDate).format("YYYY/MM/DD")}<br>
                自動更新有無:${target.AutoUpdate}<br>
                コメント:${target.Detail}<br>
                <br>
                必要に応じて更新検討および更新手続きをお願いします。`;
  return body;
}

//本日アラート日のものを返す
function getAlertTargets(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName("契約管理");
  const vals = sh.getRange("A2:H").getValues().filter(x => x[0]);
  const obj = vals.map(x => new Contract(x));

  return obj.filter(x => dayjs.dayjs(x.AlertDate).isSame(dayjs.dayjs(),"day"));
}

//SS情報をオブジェクト化するためのクラス
class Contract{
  constructor(row){
    this.Title      = row[0]; //項目名
    this.Type       = row[1]; //カテゴリ
    this.AutoUpdate = row[2]; //自動更新
    this.EndDate    = row[3]; //契約満了日
    this.AlertDate  = row[4]; //通知日
    this.AlertTiming= row[5]; //通知時期
    this.AlertTo    = row[6]; //通知先
    this.Detail     = row[7]; //詳細
  }
}

 

3.ポイント

3-1.day.js

ライブラリ、day.jsを使っています。
使わなくても日付比較はできますが、使うと非常に楽ですので使用をオススメします。

使い方は、エディタ左側のライブラリのところで「+」を押し
下記のスクリプトIDを入力、追加してください。

1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB
day.js公式

3-2.トリガー設定

通知日=本日のデータがないかチェックしてメール送信する仕組みですので、
毎日起動する必要があります。

業務都合に合うお好きな時間帯を日次設定してください。

3-3.テンプレートリテラルについて

htmlBodyを作成する際、変数の値と文字列を組み合わせて作成しています。
文字列と変数を+で結合しても問題ありません。
ただ、当コードで使用しているテンプレートリテラルを活用すると
途中で柔軟に改行できることから可読性と保守性が増す場合が多いです。

使用方法としては、まず文字列を ` で囲います。
変数や定数の値を使用したい場合は`の中で${変数名}と書くと展開されます。

今回のような長めの文字列を扱う場合、有効ですので是非この機会に慣れておいてください。

4.さいごに

結構シンプルですぐ作れる内容でありながら、
業務にはバッチリ貢献してくれそうな例だと思います。

期日管理系ですと類似のものでこんな記事もありますので興味があれば参照してみてください。
GASでシートの入社日や退職日に合わせて自動でメールを送信する事例

ご活用いただけますと幸いです。ありがとうございました。

GASでシートの入社日や退職日に合わせて自動でメールを送信する事例

0.はじめに

従業員の入社や退職に際して自動的に連絡をしたい、
そんな時に活用できる例をご紹介します。

1.一覧スプレッドシートについて

今回使用するスプレッドシートはこんな形式だとします。
列構成を変えたい場合などもコードを少し変更すれば対応可能ですので柔軟に考えてください。
1行に1名の情報が入っていると楽です。

入社管理スプレッドシート

 

2.設定シートについて

メールの宛先や件名、内容などを簡単に管理できるように
スプレッドシート上に設定情報を持たせておきます。

メール設定シート

これをやると簡単に設定変更が可能になり汎用的なコードに近づきますが、
必要に応じてシートの編集権限を絞る、等の工夫が必要な場合もあるかと思います。

3.コード

ではこれらの内容を取得して、送信に繋げていきます。

//最初にライブラリdayjsを追加しておくこと
//スクリプトIDは1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB

/**
 * メイン処理
 * 入社日が本日のものだけを対象にするため、日次トリガーを設定する必要がある
 */
function sendEntryMail() {
  //シート情報を取得
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const targets = getEntryDatas(ss);
  if(targets.length == 0) return; //本日入社が無ければ終了
  
  //メール設定を取得
  const mailSetting = getMailSetting(ss, "入社");
  
  //1人ずつ送信
  for(let person of targets){
    //送信設定情報を作成
    let mailArgs = { cc: mailSetting.Cc,
                     bcc: mailSetting.Bcc,
                     from: mailSetting.FromAddress,
                     name: mailSetting.FromName,
                     htmlBody: mailSetting.Body.replace(/%対象者%/g,person.Name) //置換
                    };
    //送信
    GmailApp.sendEmail(person.Mail,mailSetting.Subject,"",mailArgs);
  }
}

/**
 * 本日入社のデータを取得する
 * @param {object} ss スプレッドシートオブジェクト
 * @return {object} todayEntry 本日入社のデータ 無ければ空配列 
 */
function getEntryDatas(ss){
  //スプレッドシートデータを取得
  const sh = ss.getSheetByName("入社");
  const datas = sh.getRange("A2:G").getValues().filter(x => x[0]);
  const obj = datas.map(x => new Entry(x));
  
  //本日入社分にフィルタ
  const todayEntry = obj.filter(x => dayjs.dayjs(x.Date).isSame(dayjs.dayjs(),"day"));
  return todayEntry;
}

//入社者のデータを整理するためのクラス
class Entry{
  /**
   * @param {array} row 入社シート1行分の配列
   */
  constructor(row){
    this.Date = row[0];
    this.Number = row[1];
    this.Mail = row[2];
    this.Name = row[3];
    this.Div = row[4];
    this.Prace = row[5];
    this.Notes = row[6];
  }
}

/**
 * 引数で指定したメール設定を返す
 * @param {object} ss スプレッドシートオブジェクト
 * @param {string} type 設定シートのどの項目を取得するか文字列で指定
 * @return {object} typeに応じた設定object
 */
function getMailSetting(ss,type){
  const sh = ss.getSheetByName("メール設定");
  const datas = sh.getRange("A2:H").getValues().filter(x => x[0]);
  const obj = datas.map(x => new MailSetting(x));

  const targetObj = obj.find(x => x.Type == type);
  return targetObj;
}

//メール設定を整理するためのクラス
class MailSetting{
  /**
   * @param {array} row メール設定シート1行分の配列
   */
  constructor(row){
    this.Type        = row[0];
    this.Cc          = row[1];
    this.Bcc         = row[2];
    this.FromAddress = row[3];
    this.FromName    = row[4];
    this.Subject     = row[5];
    this.Body        = row[6];
    this.Notes       = row[7];
  }
}

ポイントをいくつか挙げていきます。

3-1.day.jsについて

ライブラリ、day.jsを使っています。
使わなくても日付比較はできますが、使うと非常に楽ですので使用をオススメします。

使い方は、エディタ左側のライブラリのところで「+」を押し
下記のスクリプトIDを入力、追加してください。

1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB

day.js公式

3-2.トリガーについて

本日入社者がいないかチェックしてメール送信する仕組みですので、
毎日起動する必要があります。

社風によって適切な時間帯が異なると思いますので
お好きな時間帯を設定してください。

3-3.クラスとmapについて

スプレッドシート情報を取得した後、mapの中でnewしています。
各行の内容を用いてインスタンスオブジェクトを作成することで
効率的にキー付きの扱いやすいオブジェクトを作成できます。

非常に便利で、メイン処理を汚さないコードを書きやすくなります。
今回のコードはかなりシンプルで分かりやすいため、
クラスの理解がまだの方はじっくりと動きを確かめてみてください。

3-4.エラー処理について

この手の処理はあまりサーバー系のエラーが起きないので何もしていません。
が、こういう油断が足元をすくうのでtry catchで囲っておくべきです。

「サーバーエラーの場合try catchでも拾えないじゃない」と思う方は、
実行ログを作成して履行チェックを実施するGASを作成してください。
履行チェックGASは下記記事で使っていますので、よければ参照してみてください。
フォームのデータを取得できませんでした。のエラーに対処した話

4.さいごに

いかがでしたでしょうか。

私の会社ではシート上にもっと情報が多く、処理項目も多いため
メール送信部分だけを切り出してご紹介しました。

日次に応じてひな形に沿った対応を行う、という業務は
非常に自動化しやすい業務ですので、見つけたらチャンスです。

ぜひ活用してみてください。

GASでGoogleグループのエイリアス一覧を取得するコードと解説

0.はじめに

前回記事、
GASでGoogleグループのメンバー一覧を取得するコードと解説
のエイリアスバージョンです。

Admin画面からエイリアス設定を参照するためには、
少し操作ステップが必要になります。
スプレッドシートに一覧化しておけば参照が楽になり、
また編集履歴から古い情報も確認可能になります。

今回もドメイン名とシート名以外はコピペで使えます。
ぜひ参考にして、活用してみてください。

1.コード

function getGroupListAll(){  
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName('test');

  let output = ([["グループ名", "アドレス", "エイリアス"]]);
  let domainList = ["ドメインを入力","管理ドメインが複数ある場合は続けて入力"];
  //ドメインに@は不要

  domainList.forEach(x=> pushArray(output,getGroupInfo(x)));
  inputSpace(output);
  sh.clear();
  sh.getRange(1,1,output.length,output[0].length).setValues(output);
}


//グループ情報を取得
function getGroupInfo(domain){
  let maxResults = 200;  
  let token = '';
  let result = []
  do{
    if(token==""){
      var groupsList = AdminDirectory.Groups.list({domain: domain, maxResults: maxResults})
    }else{
      var groupsList = AdminDirectory.Groups.list({domain: domain, maxResults: maxResults, pageToken: token})
    }
    if(groupsList.groups) {
      for(let g of groupsList.groups){
        let groupName = g.name//グループ名
        let groupMail = g.email//メールアドレス
        
        let nonEditable = g.nonEditableAliases; //自動付与設定されているエイリアス
        let editable = g.aliases; //自動付与以外に個別設定したエイリアス
        let concatedAliase = concatAliase(nonEditable, editable); //結合

        let forPush = [groupName,groupMail];
        result.push(forPush.concat(concatedAliase));
        }
      }
      token = groupsList.nextPageToken
    }while(token)
  return result
}


//エイリアス配列を必要に応じて結合して返す
function concatAliase(nonEditable, editable){
  if(nonEditable && editable){
    return nonEditable.concat(editable);
  }else if(nonEditable && !editable){
    return nonEditable;
  }else if(!nonEditable && editable){
    return editable;
  }else{
    return [];
  }
}


//破壊的に二次元配列に二次元配列を結合する
function pushArray(array1, array2){
    for(let arr2 of array2){
      array1.push(arr2);
  }
}


//長さの違う2次元配列に空白を入力する
function inputSpace(array){
  let wide = 0;
  for(let arr of array){
    if(arr.length>wide){
      wide = arr.length;
    }
  }
  for(let arr of array){
    while(arr.length<wide){
      arr.push('');
    }
  }
}

 

2.出力結果

こんな感じでスプレッドシートに出力されます。

エイリアスリスト取得結果

それぞれのエイリアス設定件数に応じて横に伸びていく形になります。

3.解説

3-1.AdminDirectory

グループ情報取得処理でAdminDirectoryを使用します。
エディタ左側の「サービス」から「AdminSDK」を追加しないと使えません。

Groups.listの公式リファレンス

3-2.構成

以下のような流れになっています。

ヘッダ、ドメインリストを作成

グループ情報を取得する中でエイリアス情報も一緒に取得
└2種類のエイリアスを結合

ヘッダにグループ情報を結合

行ごとの長さが異なるジャグ配列なので、これを貼り付け可能な二次元配列に整形

シートクリアして貼り付け

3-3.ポイント

3-3-1.取得上限

Groups.listは1リクエスト当たりの上限があります。
200件が上限のため200件以上の場合はトークンを取得して
次ページ分の情報取得へ進んでいく必要があります。

コード内で.nextPageToken()してトークンを取得、
do while(token)でトークンがある場合は次のページを取得しています。

3-3-2.nonEditableAliaseとaliaseについて

エイリアスには2種類あります。
アカウント作成と同時に固定的に生成するエイリアスを設定している場合、
そのエイリアスはnonEditableAliaseとして扱われます。

尚、nonEditableAliaseもaliaseも0件の場合はundefinedが返ってきますので、
concatAliase()でundefinedを除外して結合するようにしています。

4.活用方法

オススメの活用方法は「日次トリガーで参照用マスタにする」です。
Admin画面からエイリアス設定を参照するのは少し手間がかかりますし、
Admin画面を参照できる権限者にしかできない事です。

毎日深夜に更新されるよう設定しておけば工数ゼロなので、是非どうぞ。

5.さいごに

エイリアス参照が楽になる方法をお伝えしてきました。

今回の方法は、エイリアスを大量に設定していない限りは
グループメンバー一覧化と異なり情報量が少ないと思うので
タイムアウトやセル数上限についても気にしなくて良いかと思います。

ぜひ気軽に導入してみてくださいね。

GASでGoogleグループのメンバー一覧を取得するコードと解説

0.はじめに

GoogleWorkspaceを管理されている方々なら
「Googleグループを一覧化したい、マスタ管理したい」
そう思う事が絶対にあると思います。

そんな方に向けて、グループメンバーを一覧化するコードをご紹介します。
簡単に解説もしていきますので、ぜひ参考にしてみてくださいね。

1.コード

サンプルコードです。


//メイン処理
function getGroupListAll(){  
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName('test');

  let domainList = ["ドメインを入力","ドメインを入力"];
  let output = ([["グループ名", "メールアドレス", "メンバー数", "グループメンバー"]]);

  //outputに対して一気にグループ情報とメンバー情報を付加
  domainList.forEach(x=> pushArray(output,getGroupInfo(x)));

  //2次元配列で貼り付けるための整形
  inputSpace(output);

  //シートクリア
  sh.clear();

  //貼り付け
  sh.getRange(1,1,output.length,output[0].length).setValues(output);
}


//ドメインのグループを全件取得する
function getGroupInfo(domain){
  let maxResults = 200;  
  let token = '';
  let result = []
  do{
    if(token==""){
      var groupsList = AdminDirectory.Groups.list({domain: domain, maxResults: maxResults})
    }else{
      var groupsList = AdminDirectory.Groups.list({domain: domain, maxResults: maxResults, pageToken: token})
    }
    if(groupsList.groups) {
      for(let i in groupsList.groups){
        let groupName = groupsList.groups[i].name//グループ名
        let groupMail = groupsList.groups[i].email//メールアドレス
        let groupNumber = groupsList.groups[i].directMembersCount//メンバー数
        let groupMember = getMemb(groupsList.groups[i].email)
        let forPush = [groupName,groupMail,groupNumber]
        result.push(forPush.concat(groupMember))
        }
      }
      token = groupsList.nextPageToken
    }while(token)
  return result
}


//addressのメンバーを1次元配列で取得
function getMemb(address){
  let rv = [];
  let member = ''
  let token = '';

  do{
    if(token == ''){
      member = AdminDirectory.Members.list(address,{maxResults:200});
    }else{
      member = AdminDirectory.Members.list(address,{maxResults:200, pageToken: token});
    }
    if(member.members){
      member.members.forEach(x => rv.push(x.email));
    }
  token = member.nextPageToken;
  }while(token)

  return rv;
}


//破壊的に二次元配列に二次元配列を結合する
function pushArray(array1, array2){
  for(let arr2 of array2){
    array1.push(arr2);
  }
}


//長さの違う2次元配列に空白を入力する
function inputSpace(array){
  let wide = 0;
  for(let arr of array){
    if(arr.length>wide){
      wide = arr.length;
    }
  }
  for(let arr of array){
    while(arr.length<wide){
      arr.push('');
    }
  }
}

多分もっと分かりやすく、綺麗に書く事もできると思います。
ちなみにAIチャットボットChatGPTに書いてもらうとこんな感じになります。
AIチャットボットにGASを書いてもらったら実用レベルで絶望した件

2.出力結果

こんな感じにスプレッドシートに出力されます。
メンバーリストの出力結果画像

3.解説

3-1.AdminDirectory

グループ情報取得、メンバー情報取得の両方の処理でAdminDirectoryを使用します。
エディタ左側の「サービス」から「AdminSDK」を追加しないと使えません。

Groups.listの公式リファレンス
Members.listの公式リファレンス

3-2.構成

大きな流れとしては、こんな感じになっています。

貼り付け用の二次元配列とヘッダを用意

各ドメインをループし全グループを取得
└ループ内で各グループのメンバーを取得

行ごとに長さの違うジャグ配列になっているため、
列数を揃えるためにinputSpace()で整形

シートをクリアして貼り付け

3-3.ポイント

Groups.list、Members.list両方とも1リクエスト当たりの上限があります。
200件が上限のため200件以上の場合はトークンを取得して
次ページ分の情報取得へ進んでいく必要があります。

コード内で.nextPageToken()してトークンを取得、
do while(token)でトークンがある場合は次のページを取得しています。

4.注意点

4-1.セル数上限に注意

「3000人入っているグループが5000件ある・・・」等、
ボリュームがある場合はスプレッドシートのセル数上限に注意してください。

セル数上限は昨年引き上げられてスプレッドシート当たり1000万になりましたが、
人数分、右方向にセルを使う仕様のため要注意です。

対策したい場合、1行100列までにして次の行に折り返す工夫などが必要です。

4-2.タイムアウトに注意

グループを全件取得し、更にメンバーを全件取得しています。
グループ2000×メンバー3000だと単純に掛け算して6,000,000。
処理数が多くなるので時間がかかります。

対策としては、ループをラムダ式に置き換え高速化を図るか、
タイムアウトしそうになったら一度処理を終了して
続きから実行するトリガーを設定するような手法が必要になります。

何にせよ、一度ご自身の環境で実行して対策の要否を検討してみてください。
私の環境では2000グループ×2000名ほどですが、何とかなっています。

5.さいごに

いかがでしたでしょうか。
これがあると、Admin画面を見られない方にも参照して頂けます。
メンバーをユーザー名で表示したい場合、
こちらの記事も参照してユーザーリストと照合する仕組みにしてください。
GASで全ユーザーのリストをスプレッドシートに日次取得する方法
ぜひ活用してみてくださいね。

GASでGoogleドキュメントを効率化!書き込む時の重要操作方法一覧!

0.はじめに

GAS活用ではシートやドライブ、フォームにスポットが当たりがちですが
Googleドキュメントも扱えます。

基本的な書き込みや改ページ、書式操作などをご紹介します。
また活用事例も簡単にご紹介します。

1.最初にbodyを取得

必ず最初にこの手順を行ってください。

基本的にここで取得したbodyに対して変更を重ねることになります。
また上書き保存や改ページはdocに対して実行します。

const doc = DocumentApp.openById(docid);
let body = doc.getBody();

2.ドキュメント内容を削除する

1つのドキュメントを繰り返しリセットして編集する場合、処理の頭で実行しておきましょう。

body.clear();

3.末尾に行を追加

ドキュメント末尾にテキストを追加します。

body.appendParagraph("追記テキスト");

4.右揃えや中央揃えを設定する

//右揃え
body.appendParagraph("追記テキスト").setAlignment(DocumentApp.HorizontalAlignment.RIGHT);
//左揃え
body.appendParagraph("追記テキスト").setAlignment(DocumentApp.HorizontalAlignment.LEFT);

//中央揃え
body.appendParagraph("追記テキスト").setAlignment(DocumentApp.HorizontalAlignment.CENTER);

5.文字サイズを設定する

body.appendParagraph("追記テキスト").editAsText().setFontSize('14');

6.太字・下線を設定する

//太字
body.appendParagraph("追記テキスト").editAsText().setBold(true);
//下線
body.appendParagraph("追記テキスト").editAsText().setUnderline(true);

7.文字色・背景色を設定する

他のメソッドと違って引数が複数あるので注意です。
開始位置、終了位置、色コードを設定します。

//文字色 1文字目~3文字目を黄色に
body.appendParagraph("text").editAsText().setForegroundColor(0,2,"#ffff00");

//背景色 2文字目~5文字目を赤背景に
body.appendParagraph("text").editAsText().setBackgroundColor(1,4,"#e60033");

8.ドキュメントに改ページを挿入する

項1で定義したドキュメントオブジェクトに対して実行します。

body.appendPageBreak();

9.ドキュメントを保存して閉じる

編集が終わった後に保存する必要がある場合、実行してください。

doc.saveAndClose()

10.メソッドチェーン

「太字にしてサイズ変えて右揃えにして・・・」を一気に書けます。

const text = "テストですよ";
body.appendParagraph(text)
         .setAlignment(DocumentApp.HorizontalAlignment.RIGHT)
         .editAsText().setFontSize('18')
         .editAsText().setForegroundColor(0,text.length-1,"#e60033")
         .editAsText().setBackgroundColor(0,text.length-1,"#ffff00")
         .editAsText().setBold(true)
         .editAsText().setUnderline(true);

11.番外:PDF保存

作成したドキュメントをPDFとしてドライブに保存します。
スプレッドシートをシート指定してPDF化するより安定します。

//ドキュメントをPDF形式でエクスポートするURL
const url = `https://docs.google.com/document/d/${ドキュメントのID}/export?format=pdf`;

//Access Tokenを取得
const token = ScriptApp.getOAuthToken();
    
//PDF形式でドライブに保存
const res = UrlFetchApp.fetch(url, {headers: {'Authorization': 'Bearer ' +  token}}).getBlob().setName("保存PDF名称.pdf");
const folder = DriveApp.getFolderById("保存先フォルダID");
const pdfFile = folder.createFile(res);

12.活用例

  • 安定したPDF作成の仕組みを作る(SS⇒PDF作成より安定する)
  • 定型フォーマットのDocを作成するGASを作り、可変部分は変数にしておき
    その時々で必要な値を入力した定型のドキュメントをワンクリックで作成可能にする

私も実務で1つ目のパターンを活用しています。
スプレッドシートからシート指定でPDF作成すると本当に安定しないのでこれで代用しています。

13.さいごに

ドキュメント作成に関するGoogle公式リファレンスはこちらです。
もっと細かく設定したい方は、公式情報も参照しつつ色々試してみてください!

GASでAsanaAPIを使ってタスクを自動的に作成するためのコード

0.はじめに

タスク管理ツールAsanaに対して
GASからAPIを叩いてタスク作成する方法をご紹介します。

1.コードサンプル

まずコードをどうぞ。

/**
 * asanaAPIでタスク登録
 */
function test(){
  //個人アクセストークンの取得方法はこちらhttps://asana.com/ja/guide/help/api/api
  const headers = {
    "Authorization": "Bearer " + "実行するアカウントの個人アクセストークン"
  };

  //Asanaに登録する基本的な内容 他の項目や詳細はこちらのcreate-a-task https://developers.asana.com/docs/create-a-task
  const payload = {
    "data":{
        "workspace"  : "ワークスペースのID",             //ワークスペースID
        "projects"   : ["プロジェクトID"],               //プロジェクトのURLにあります
        "name"       : "title",                         //タスクタイトル
        "assignee"   : "担当者メールアドレス",            //タスク担当者メールアドレス
        "html_notes" : "body",                    //htmlで本文作成したい場合bodyタグで括る必要あり
        "due_on"     : "2023-01-23",                    //タスク期日 YYYY-MM-DD形式
        "followers"  : ["~~~~@ドメイン.jp"]            //コラボレーターアドレス 配列でいいはず 組織外アドレスは追加できなさそうです
    }
  };

  const options = {
      "method"              : "post",
      "contentType"         : "application/json",
      "headers"             : headers,
      "payload"             : JSON.stringify(payload),
      "muteHttpExceptions"  : true
  };

  //タスク生成
  const response = UrlFetchApp.fetch("https://app.asana.com/api/1.0/tasks", options);  
  
  //これで実行結果が取れます
  console.log(response.getContentText());

  //実行結果からタスクIDを取得、タスクURLを作成 このURLをSlackに飛ばすなり何なり
  const taskId  = JSON.parse(response).data.gid;
  const taskUrl  = "https://app.asana.com/0/" + "プロジェクトID" +  "/" + taskId;
}

2.事前取得すべき情報について

いくつか事前に情報を取得する必要があります。

2-1.AsanaワークスペースIDの取得

契約している会社ごとのIDの事です。コードの最初の方で使っていますね。
ご自身の会社のIDを取得する必要があります。

Asanaにログインして、その状態で下記のリンクに飛んでください。
https://app.asana.com/api/1.0/workspaces

そうするとシンプルなテキストだけのページが表示されます。
その中のgidがワークスペースIDになります。

2-2.アクセストークンの取得方法

こちらは下記のリンクを参照しながら値を取得してみてください。
アクセストークンの取得方法

2-3.リクエストやレスポンスについて

リクエストの細かい設定方法やレスポンス内容の公式リファレンスはこちらです。
create-a-taskに関する公式情報

3.注意すべき点

上記のリンクやコードを細かく見た方はお分かりになると思いますが、
htmlでタスクbodyを作成する時のルールや、
組織外のアドレスを追加できない仕様など細かい条件があります。

上手くいかない場合、その辺りの条件が正しいか公式情報を確認してみてください。

4.活用アイデア

Googleフォームで依頼を受けて自動的にタスク化する

Asanaというとプロジェクト管理等で使われれる印象です。
が、このサイトはバックオフィスがテーマなので、バックオフィス寄りの提案です。

Googleフォームの回答があった際にGASを自動発動できるので、
その設定を利用してGASでフォームの情報をAsanaに飛ばします。
フォームからは回答者のメールアドレスや回答内容を取得可能なので、
回答者をコラボレーターに追加し、自部署担当者をAsanaタスク担当者に設定しましょう。

依頼種別ごとに複数のGoogleフォームを作成し、
内容によってタスク担当者やコラボレーターが変わるようにすると更に活用範囲が広がります。

フォームの種類があまりにも多い場合、それぞれのフォーム内のGASに対して
担当者情報等をハードコードするのはオススメできません。
担当者が変更になる時のメンテナンスコストが非常に高くなるからです。
どのフォームに誰を紐づけるか管理するスプレッドシートを用意し、
全てのフォーム⇒AsanaのGASがそのシートを参照してからタスクを作成する。
そんな仕組みにすると良いでしょう。

5.おわりに

フォームからAsanaに飛ばしたり、シート情報を元に生成したり、
GASで作成したwebアプリからタスク生成したり、色々できそうですよね。

Saasツールはこういう機能をフル活用してこそ、効果が上がると感じます。
ご活用いただけると幸いです。

脱ハードコード!問い合わせ削減!GASで設定をシート上に持つ方法

0.はじめに

GASをある程度深めた方。
管理の質に着目した事はありますか?

例えばノンプログラマーに対して業務ツールを提供したとします。
自動メールのテンプレートやCC宛先を変更するために、毎回ユーザーから依頼が入ります。
これ、運用管理者側で変更してもらえると楽ですよね。

今回はその1つとして、スプレッドシート上で設定値を保持して
それらの情報を使用する際に取得する
ところまでご紹介します。

1.【事例1】メールテンプレート設定

1-1.スプレッドシート側

まず、スプレッドシート側の具体例です。
画像をご確認ください。
設定シート

1-2.GAS側

ではこの設定情報をどうやって取得するか、GASコードを見ていきます。

function myFunction(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName("setting");
  const vals = sh.getRange("A2:H").getValues().filter(x => x[0]); //A列が空でないvaluesにfilter
  const settingObj = vals.map(x => new MailSetting(x));
  console.log(settingObj);
  /**
   * settingObjの中身
  [ 
    { Id: '01',
      Subject: '【test1】住所変更申請を受け付けました【%日時%】',
      Body: '長いので省略しますが、本文列の内容が入ってます。',
      To: [ '%申請者%' ],
      Cc: [ 'jusho_sys@test.test.jp', 'jibunnoemail@jibunnoemail.com' ],
      Bcc: [],
      FromName: '住所申請システム',
      FromAddress: 'jusho_sys@test.test.jp' },
    { Id: '02',
      Subject: '【test1】緊急連絡先変更申請を受け付けました【%日時%】',
      Body: '長いので省略しますが、本文列の内容が入ってます。',
      To: [ '%申請者%' ],
      Cc: [ 'kinkyu_sys@test.test.jp', 'jibunnoemail@jibunnoemail.com' ],
      Bcc: [],
      FromName: '緊急連絡先申請システム',
      FromAddress: 'kinkyu_sys@test.test.jp' } ]
   */

  //settingObjの中身から使う時は
  const setting02 = settingObj.find(x => x.Id == "02");

  //body等の%申請者%や%日次%はreplaceを使って置換します。
  const body = setting02.Body.replace("%申請者%","山中田村丸太郎乃介");
}

class MailSetting{
  constructor(rowArray){
    this.Id          = rowArray[0];
    this.Subject     = rowArray[1];
    this.Body        = rowArray[2];
    this.To          = rowArray[3].split(",").filter(x => x);
    this.Cc          = rowArray[4].split(",").filter(x => x);
    this.Bcc         = rowArray[5].split(",").filter(x => x);
    this.FromName    = rowArray[6];
    this.FromAddress = rowArray[7];
  }
}

お分かりいただけたでしょうか。
クラスとmapを活用して一気にobject配列を作成しています。

Googleフォームに対して自動返信をするとした時、
全てのGoogleフォームがこのスプレッドシートを見て自動返信してくれたとしたら、
非常に管理が楽になりますよね。

コードを直接触る必要も無くなり、メンテミスの可能性が下がります。
本文を編集するには若干htmlの知識が必要ですが、
それでも大部分は運用管理者が実務に合わせてメンテナンスできると思います。
「システム担当にわざわざ頼むの面倒・・・」という事もなくなりますね。

2.【事例2】発動日を指定したい

2-1.スプレッドシート側

超シンプルです。こんなのもアリです。

設定シート

2-2.GAS側

function myFunction2(){
  const ss = SpreadsheetApp.getActiveSpreadsheet();
  const sh = ss.getSheetByName("setting2");
  const targetDates = sh.getRange("B2").getValue().split(',').filter(x => x);

  //day.jsを使います 左のライブラリに追加してください
  //スクリプトID 1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB
  const today = dayjs.dayjs();
  //毎日
  if(targetDates.find(x => x == today.date())){
    //毎日トリガーで実行した上で、設定シート上の日付と一致する場合だけメイン処理を実行する、等
  }
}

コード欄にもコメントで説明を書いていますが、
毎日起動するトリガー設定をした上で、設定シート上の日付の場合だけ処理を行うようなコードです。

ライブラリday.jsを使用しているので、コピペで使う場合はライブラリに追加してください。
追加に必要なIDは「1ShsRhHc8tgPy5wGOzUvgEhOedJUQD53m-gd8lG2MOgs-dXC_aCZn9lFB」です。

3.おわりに

いかがでしたでしょうか。

GASは当然Googleサービスとの繋がりが強いので、
設定を外部化するための1つの方法としてスプレッドシート活用が有効な候補になります。
小規模開発でもメンテナンス性を上げるために、活用しない手はないでしょう。

また今回の記事で活用したmapやfindについてこちらの記事で解説しています。
GAS初心者がfor文やif文の次に学ぶと急激にレベルアップする技術7選
興味のある方は併せてご覧ください。

それではまた。ありがとうございました。

GAS初心者がfor文やif文の次に学ぶと急激にレベルアップする技術7選

0.はじめに

GAS初心者の方々、こんな悩みはありませんでしょうか。

「次に何を学べばいいのか分からない」
「とりあえずforとifで色々できるようになったけど、ネストだらけ・・・」

そんな方々へ、次の1歩として覚えると非常に便利になり
またコードを書くのがずっと簡単に、楽しくなる技術を紹介します。

1.for in

まずはfor inです。先にサンプルをどうぞ。

function myFunction() {
  const test = [0,1,2,3,4,5,6,7,8];

  //これをループする時、一番最初に学ぶfor文ではこう書きます。
  for(let i = 0; i<test.length; i++){
    console.log(test[i]);
  }

  //for inを使うと、こうなります。
  for(let i in test){
    console.log(test[i]);
  }
}

MDNリファレンス for-in

はい、短くなりましたね!
配列をループする時、配列の最初から最後までのインデックスを加算しつつループしてくれます。
長ったらしいfor文を書くよりスッキリして楽に書けてGoodです。

2.for of

続いてfor ofです。私も非常によく使います。


function myFunction() {
  const items = [0,1,2,3,4,5,6,7,8];

  //これをループする時、一番最初に学ぶfor文ではこう書きます。
  for(let i = 0; i<items.length; i++){
    console.log(items[i]);
  }

  //for ofを使うと、こうなります。
  for(let item of items){
    console.log(item);
  }
}

MDNリファレンス – for of

はい。お分かり頂けましたでしょうか?
for of文は、インデックスナンバーをループするわけではなく
配列内の中身を順番に取り出してくれます。

for inとの使い分けですが、

処理中にインデックスナンバーが必要な場合はfor in、
配列の中身だけが必要な場合はfor of

という風に考えるとよいでしょう。

実際にコピペして動かしてみると実感がわきますよ。

3.連想配列

これはプログラミングを深めるに当たっては避けて通れない技術です。
次項からの内容と絡めると大いに役立つので、ぜひ理解してください。

function myFunction() {
  let player = {name:"田中", age:20, team:"腹八分目"};

  console.log(player.name); //田中が出力される
  console.log(player.team); //腹八分目が出力される

  //チームを移籍したら・・・これで変更できる。
  player.team = "満腹党";
  console.log(player.team); //満腹党が出力される
}

これだけだと「何のためにあるの?」と思うかもしれません。すいません。

playerについて氏名や年齢の他にも、所属履歴や家族構成、緊急連絡先、身体測定の結果などを管理したい場合、
普通の配列だと「index12番目は家族構成で・・・」と、かなり運用に難が出ます。
そんな時、連想配列を使うと簡単に目的のデータに辿り着く事ができます。

4.JSON形式

ジェイソンと呼びます。嘘じゃないです。
何となく専門的な雰囲気がありますが、配列みたいなもんです。難しく考えないでください。


function myFunction() {
  let players = [
                  {name:"田中", age:25, team:"腹八分目", mail:"tanaka@harahachi_test.co.jp"},
                  {name:"田村", age:20, team:"腹八分目", mail:"tamura@harahachi_test.co.jp"},
                  {name:"田村中", age:23, team:"満腹党", mail:"tamuranaka@manpuku_test.co.jp"}
                ];

  //普通の配列と同じようにpushできる。
  players.push({name:"田井中", age:26, team:"満腹党", mail:"tainaka@manpuku_test.co.jp"});

  for(let player of players){
    console.log(player.mail);
    //1人ずつ順番にメールを送る処理などができる。
  }
}

前項では1名分しか管理しなかった内容について複数のデータをplayersという1つの変数で管理できます。
また配列にいくつかのデータが入っているようなものなので、ループも回せます。

例えばスプレッドシートに管理している数十人のデータを取得してJSON形式にしたら、
この例のように実務でも扱いやすそうなイメージが湧いてきませんか?

5.filter

ここからが本番です!
filterとの文字通り、配列の中身をフィルタリングします。

function myFunction(){
  //例1
  const numbers = [0,55,23,45,899,6443,23,456,47];
  const under100 = numbers.filter(num => num<100);
  console.log(under100);
  //[ 0, 55, 23, 45, 23, 47 ]が出力される

  //例2
  const players = [
                  {name:"田中", age:25, team:"腹八分目", mail:"tanaka@harahachi_test.co.jp"},
                  {name:"田村", age:20, team:"腹八分目", mail:"tamura@harahachi_test.co.jp"},
                  {name:"田村中", age:23, team:"満腹党", mail:"tamuranaka@manpuku_test.co.jp"},
                  {name:"田井中", age:26, team:"満腹党", mail:"tainaka@manpuku_test.co.jp"}
                ];
  const team8 = players.filter(player => player.team == "腹八分目");
  console.log(team8);
  /**
  [ { name: '田中', age: 25, team: '腹八分目', mail: 'tanaka@harahachi_test.co.jp' },
    { name: '田村', age: 20, team: '腹八分目', mail: 'tamura@harahachi_test.co.jp' } ]
  が出力される。
   */

  //その後の活用例
  for(let player of team8){
    let mail = player.mail;
    //teamが腹八分目のメンバーだけにメールを送ったりできる!
  }
}

MDNリファレンス – filter

イメージ頂きやすいように書きましたが、サンプルを見るだけで理解できたら凄いです。

例1は、数字配列を100未満のものだけにフィルタしています。
filter(num => num<100)の部分ですが、numの中に配列の中身が順番に入って
100未満かどうかを確かめている、という風に理解してください。
for ofに100未満という条件が付いた感じに近いかもしれません。

例2ではJSON形式に対して実行してみました。これも業務で非常によく使うパターンです。
もし練習したい場合、25歳以上の人だけを取り出してconsole.logで確認してみてください!

6.find

filterを理解できた方、素晴らしいです。
findも非常に似ていますので、似た感覚で見てください。

function myFunction(){
  const players = [
                  {name:"田中", age:25, team:"腹八分目", mail:"tanaka@harahachi_test.co.jp"},
                  {name:"田村", age:20, team:"腹八分目", mail:"tamura@harahachi_test.co.jp"},
                  {name:"田村中", age:20, team:"満腹党", mail:"tamuranaka@manpuku_test.co.jp"},
                  {name:"田井中", age:26, team:"満腹党", mail:"tainaka@manpuku_test.co.jp"}
                ];
  const team8 = players.find(player => player.age == 20);
  console.log(team8);
  //  {name:"田村", age:20, team:"腹八分目", mail:"tamura@harahachi_test.co.jp"}が出力される。
}

MDNリファレンス – find

age==20のメンバーは2人いるのに1人しか出力されない点に注目してください。
filterでは、条件に一致した時点でそれを返す、という事が行われます。
ですのでfilterと違って、1つしか値が返ってきません。

これだけだと「filterでよくない?何に使うの?」と思う方も多いかもしれません。
個人的には、次の次の項で紹介する複合的な用途でかなり使います。

7.map

filter,findと似た文法でも処理内容は全く異なる超便利メソッド、mapです。
配列の中身を一定の内容に応じて操作し別の変数に格納します。


function myFunction(){
  //例1
  const numbers = [1,2,3,4,5,6,7,8,9];
  const numbers3 = numbers.map(x => x*3);
  console.log(numbers3);
  //	[ 3, 6, 9, 12, 15, 18, 21, 24, 27 ] が出力される

  
  //例2
  const players = [
                  {name:"田中", age:25, team:"腹八分目", mail:"tanaka@harahachi_test.co.jp"},
                  {name:"田村", age:20, team:"腹八分目", mail:"tamura@harahachi_test.co.jp"},
                  {name:"田村中", age:20, team:"満腹党", mail:"tamuranaka@manpuku_test.co.jp"},
                  {name:"田井中", age:26, team:"満腹党", mail:"tainaka@manpuku_test.co.jp"}
                ];

  //名前だけの配列を作りたい場合
  const names = players.map(x => x.name);
  console.log(names);
  //	[ '田中', '田村', '田村中', '田井中' ] が出力される

}

MDNリファレンス – map

例1では配列の各要素に対して3倍した配列が生成されています。
例2ではname属性の中身だけの配列が生成されています。

配列の各要素に対して処理を行った結果を配列として返す、という技術になります。

練習したい場合、
例1ではnumbersの各要素に対して10足した配列を作成、
例2ではplayersのmailだけの配列を作成してみてください。

8.複合技

集大成です。今までの内容を複雑に組み合わせた例を作ってみました。
少々理解に手間取るかもしれません。私自身も最初は脳ミソ爆発しそうでした。


function myFunction(){
  //スプレッドシート1から取得したプレイヤー情報
  const players = [
                  {id:0001 ,name:"田中", age:25},
                  {id:0002, name:"田村", age:20},
                  {id:0003, name:"田村中", age:20},
                  {id:0004, name:"田井中", age:26},
                  {id:0123, name:"田牧", age:32},
                  {id:0222, name:"田中", age:26},
                  {id:0333, name:"田坂", age:16}
                ];

  //スプレッドシート2から取得したチーム1のメンバーIDリスト
  const team1Master = [0003,0005,0222];

  //上記の2つを用いて、playersを元にチーム1のメンバー配列を作りたい
  const team1Players = players.filter(x => team1Master.find(y => y == x.id));
  
  console.log(team1Players);
  /**
  [ { id: 3, name: '田村中', age: 20 },
    { id: 146, name: '田中', age: 26 } ]
  が出力される。
   */
}

あちこちにデータがありマスタ情報と紐づけて必要な情報を生成する際にかなり使います。
業務上、散らかったデータを集めてどうにかするシーンって結構ありますよね。

今回の例ではplayersの1人ずつについて順番に検証し、
team1Masterの中でIDを検索して見つかった人、のみにフィルタした形です。

9.注意点

for文やif文だけでも書こうと思えば書けますが、これらの技術を使うと楽に書けます。
ただ、1つだけ注意点もあります。

8で紹介した複合技ですが、やり過ぎると非常に分かりにくいコードになります。
業務で実際に一度、filterやfind、mapを連結させて50行以上にしてしまった事があります。

確かにそれでも一気に書く事ができますし、処理途中で使用する変数の数は減ります。
しかし、不具合が起きた時に何がおかしいのか検出するのが困難になります。

なぜならfor文の中にはconsole.logを書けますが、filterの中にconsole.logは書けません。
また途中で変数に一度代入したりステップを踏むことで、不具合発生の段階を切り分け易くなります。

ですので「これはやり過ぎだな」と思ったら、途中で処理を区切ったり
別の関数に分けることを考えてみてください。

10.さいごに

いかがでしたでしょうか。難しかったでしょうか。
難しくても、苦しんで突破するだけの価値のある技術群です。

私も長い間、for文やif文だけでGASを業務活用していましたが、
やはりその頃のコードを見ると非常にムダが多いなと感じます。
ただfilterやmap、特に複合技は最初まったく意味不明で大変でしたが・・・。

また、forやifを多用する書き方から一歩先に進む方法も全然分かっていなかったので、
これらの技術を知った時に苦しみながらも、光明が見えたような感覚もありました。

これらの技術は確実にレベルを上げますし、
GASを発展させwebアプリ化する際のJavaScriptでも非常によく使います。
ぜひ習得してみてくださいね。

GASで全カレンダーリソースの名称とアドレスをスプレッドシートに取得する方法

1.はじめに

GoogleWorkspaceを使っている方で、
「当社管理のカレンダーリソースを全件取得したい!」
などと思っている方、居られるのではないでしょうか。

そんな方々へ、簡単コピペで実現できるGASコードをご紹介します。
これらのデータの活用方法も交えながら解説していきます!

2.コード

ではコードをどうぞ。
実行すると、シートA列に指名、B列に対応するメールアドレスがズラっと羅列されます。
シート名やアドレスはご自身の環境に合った内容に変更してくださいね。

function getResourcesAll(){

  const customerId = AdminDirectory.Users.get("あなたのメールアドレス").customerId;
  let pageToken = '';
  let result = [["名称","アドレス"]];

  do{
      let resources = AdminDirectory.Resources.Calendars.list(customerId, {pageToken: pageToken});
      let items = resources.items;

      for(let item of items){
        result.push([item.resourceName, item.resourceEmail]);
        /**
         * itemから取得可能なプロパティは以下の通り
         * resourceEmail
         * capacity
         * etags
         * resourceName
         * resourceCategory
         * generatedResourceName
         * resourceDescription
         * resourceId
         * buildingId
         * kind
         */
      }
      pageToken = resources.nextPageToken;      
    }while(pageToken);
  
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1");
  sheet.clear();
  SpreadsheetApp.flush();
  sheet.getRange(1,1,result.length, result[0].length).setValues(result);
}

3.解説-前提

3-1.スクリプトエディタについて

まず、リストを載せるスプレッドシートを用意してください。
スプレッドシートの拡張機能タブからエディタを開いてください。
拡張機能タブからAppsScriptを選択

3-2.権限について

前回記事でユーザーリストを取得した時と同じ内容になります。

まず、AdminDirectoryを利用できるユーザーでないと実行できません。
GoogleWorkspaceの特権管理者だと実行可能ですが、
部分的に権限を持つことも可能です。
ここは社内の管理者に付与してもらうしかありません。

なぜなら、AdminDirectoryはリソース情報の編集など、影響の大きい行為を実行できるからです。
誰しもがそんな権限を行使できたら・・・恐ろしいですよね。

ということで、情シスの方に
「GASでAdminDirectoryっての使いたいんだけど、権限もらえる?」
と聞いてみてください。相手が管理者なら分かるはずです。

3-3.AdminDirectoryサービスについて

こちらも前回記事と同じですね。

スクリプトエディタの左側に「サービス」という項目があります。
こちらに「AdminDirectory」との表示がないと、このGASは動きません。

サービスの横の+ボタンをクリックし、
「Admin SDK API」を選択、追加ボタンをクリックしてください。
すると、サービスの欄に追加されます。
AdminSDKを追加

3-4.トリガーについて

日次更新したい場合、日次のトリガー設定を行ってください。
後でも解説しますが、毎回データを削除して再取得した内容に洗い替えるコードになっています。

4.解説-コード

4-1.customerIdについて

最初で定義するcustomerId、ご自身のメールアドレスから取得と書いていますが、
必要なカレンダーリソースをGoogleカレンダーに登録しているアカウントにしてください。

実際に実務で検証してみたところ、
そのアカウントがカレンダー上に表示可能なリソースとして登録している分だけを取得できるようです。

例えば会議室A,B,Cがあったとして、
自身の「マイカレンダー」「その他のカレンダー」にAとBしか登録していなかった場合、
customerIdに自身を設定するとCの情報は取得できません。
必要なカレンダーを予め「マイカレンダー」か「その他のカレンダー」に登録しておくか、
必要なカレンダーが登録されているアカウントをcustomerIdに設定してください。
「マイカレンダー」「その他のカレンダー」の設定方法はこちらです。

4-2.各リソースから取得可能な値について

for文内のコメントに記載していますが、
各カレンダーリソースから様々な情報を取得可能です。

例えばcapacity。
会議室に収容人数を設定していた場合、その値が取得できます。

他にもbuildingIdなど、活用すると拠点情報と紐づけできそうです。
例えばGASを使って空き状況を可視化したい場合などを想定すると、
拠点ごとに検索をする必要がありますから、ビルディングの設定は必須です。

取得する項目を変更する場合、冒頭で定義しているヘッダ項目名を変更するなど
二次元配列の列構成に気を付けてコードを変更してくださいね。

4-3.シートクリアについて

貼り付ける前にシート内容を「sheet.clear()」でクリアしています。
もしコードを書き替えて、差分情報だけ貼り付けしたい場合などは
この部分を削除してください。

5.活用方法のアイデア

様々な活用方法があると思います。

5-1.社員に見せるためのマスタ情報とする

「リソースのブラウジング」という機能があるので、
一般ユーザーが普通にカレンダー登録する際には特にマスタは必要ありません。

ですが例えばcapacity属性に登録した収容人数情報がシートに自動連携され、
一覧性のある状態になっていれば便利そうじゃないですか?

また各社員での開発が盛んな企業の場合、
リソースIDを公開しておくことにも意義があります。

5-2.空き会議室検索ツール用のマスタ

実務で作成しました。
開始時刻と終了時刻、対象の拠点を選択して、
その時間帯に空いている会議室を表示するアプリを開発できます。

Googleカレンダー上でも予定を作成する画面上で空き部屋を検索可能ですが、
「予定作成画面をわざわざ開かずに見たい」というユーザーには好評です。
まあ、UI/UXの好みの問題でしょうね。

5-3.リソース管理/更新用アプリを開発する

マスタ上に「ビルディング/リソースの作成/変更/削除」機能をGASで持たせましょう。
GoogleWorkspace上で操作しなくても、マスタを参照しながらマスタシート上で
管理/更新を一括管理するアプリを開発できます。

あまり手間をかけずに更に拡張するなら、
総務系の拠点管理担当からリソース作成等の依頼を飛ばせる依頼用UIを開発しましょう。
その依頼内容を以て、承認できるようなUIも作成。
承認したら作成等の処理とマスタ更新が走ります。
更新メニューを開く事のできるアカウントを制限するとより良いでしょう。
GASでのwebアプリ開発経験者にとっては、あまり難しくないと思います。

6.さいごに

いかがでしたでしょうか。
たかがカレンダーリソースですが、
固く運用するのであれば開発のし甲斐がありそうですよね。

ご参考になれば幸いです。ありがとうございました。

GASで全ユーザーのリストをスプレッドシートに日次取得する方法

1.はじめに

GoogleWorkspaceを使っている方で、
「当社ドメインのユーザーアドレスを全件取得したい!」
「ユーザーアドレスのマスタを日次更新したい!」

などと思っている方、居られるのではないでしょうか。

そんな方々へ、簡単コピペで実現できるGASコードをご紹介します。

2.GASコード

早速コードをどうぞ。
実行すると、シートA列に指名、B列に対応するメールアドレスがズラっと羅列されます。

function listAllUsers(){
  let pageToken;
  let page;
  let result = [];
  
  do {
    page = AdminDirectory.Users.list({
      domain: 'leverages.jp',
      orderBy: 'givenName',
      maxResults: 500,
      pageToken: pageToken
    });
    let users = page.users;
    if (users) {
      users.forEach(x=> result.push([x.name.fullName,x.primaryEmail]))
    }
    pageToken = page.nextPageToken;
  }while (pageToken)
  
  const sh = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("シート1");
  sh.clear();
  SpreadsheetApp.flush();
  const menu = ["氏名","アドレス"];
  result.unshift(menu);
  sh.getRange(1,1,result.length,result[0].length).setValues(result);      
  SpreadsheetApp.flush();
}

3.解説-前提

3-1.スクリプトエディタについて

まず、リストを載せるスプレッドシートを用意してください。
スプレッドシートの拡張機能タブからエディタを開いてください。
拡張機能タブからAppsScriptを選択

3-2.権限について

まず、AdminDirectoryを利用できるユーザーでないと実行できません。
GoogleWorkspaceの特権管理者だと実行可能ですが、
部分的に権限を持つことも可能です。
ここは社内の管理者に付与してもらうしかありません。

なぜそんな面倒か、と言いますと、
AdminDirectoryはユーザー情報の編集など、影響の大きい行為を実行できるからです。
誰しもがそんな権限を行使できたら・・・恐ろしいですよね。

ということで、情シスの方に
「GASでAdminDirectoryっての使いたいんだけど、権限もらえる?」
と聞いてみてください。相手が管理者なら分かるはずです。

3-3.AdminDirectoryサービスについて

スクリプトエディタの左側に「サービス」という項目があります。
こちらに「AdminDirectory」との表示がないと、このGASは動きません。

サービスの横の+ボタンをクリックし、
「Admin SDK API」を選択、追加ボタンをクリックしてください。
すると、サービスの欄に追加されます。
AdminSDKを追加

3-4.トリガーについて

日次更新したい場合、日次のトリガー設定を行ってください。
後でも解説しますが、毎回データを削除して再取得した内容に洗い替えるコードになっています。

4.解説-コード

4-1.上限突破について

メイン処理のAdminDirectory.Users.listですが、
1回の取得上限が500件までと制限されています。
ですので、do while文を使ってループしています。

page.nextPageTokenで次ページの存在を確認し、
存在確認が取れなくなるまで500件ずつ取得しています。
これで1000件2000件と、500件以上のユーザー数にも対応できます。

4-2.日次更新について

毎日更新することを前提にしているため、
ユーザーリストを取得して貼り付けする前に
シートのデータを全削除する、というコードになっています。

sh.clear() の部分で、シート情報を全てクリアしています。
もしコードを書き替えて、差分情報だけ貼り付けしたい場合などは
この部分を削除してください。

4-3.取得する情報の種類について

今回はA列に指名、B列にメールアドレスを取得しています。
ですが、GoogleWorkspaceからは他にも情報を取得できます。

例えば、nameでもfamilyNameを取得したり、
GoogleWorkspaceの管理者なのかどうかを取得したり、
エイリアスのアドレスを取得したり、です。

取得できる項目は公式ドキュメントにありますので、参照してみて下さい。

5.Usersの活用アイデア

AdminDirectory.Usersの活用アイデアを提案します。
実際に私の環境で使っているものもあります。

5-1.入退社リストと紐づける

入退社リストにアドレス列と処理状態列を作成します。
処理状態に「作成」「削除」をプルダウン入力可能にします。
プルダウン状態を取得し、作成/削除し状態列を作成完了/削除完了にするGASを開発します。
実行時にユーザーマスタシートも最新情報に更新します。

尚、同姓同名の場合にアドレス被りが発生するとエラーになりますので、
エラー時にはアドレス末尾に_1を付けて再実行、
それもダメだったら_2を付けて再度、等の工夫をすると良いでしょう。

5-2.Googleグループマスタと関連づける

メーリングリストとして活用できるGoogleグループ。そのマスタがあったとします。
GASでGoogleグループからメンバ取得する際、メールアドレスしか取得できません。
GASでユーザー名まで取得したい場合、adminDirectory.Usersの情報を紐づけましょう。

関数が得意な場合は、グループマスタとユーザーマスタを関数で紐づけても良さそうです。
件数が多い場合は処理が重くなるので、基本的にはGASでの日次取得がオススメです。
この方法は私も実務で活用しています。

5-3.管理権限等のマスタ及び操作ツールを開発する

取得したUserについて、様々な属性を付与または取得する事ができます。
例えばisAdminを取得すれば、そのアカウントが特権管理者かどうか分かります。
同様にarchivedを取得すれば、アーカイブ状態かどうかが分かります。

また、それらの情報は、updateメソッドによって更新可能です。
よって、GoogleWorkspace上のユーザー情報を管理、編集するための
GASによるwebアプリを開発できるという事になります。

取得可能な情報と実行可能なメソッドはこちら
ユーザー情報の更新についてはこちらに載っています。
Googleの公式ドキュメントです。

 

6.さいごに

いかがでしたでしょうか。
ぶわっと結果が表示された瞬間、ワクワクしませんか?
「何かのマスタがある」という状態は、様々な改善への第一歩となります。
ここから関数やGASを派生させて他の何かを管理するも自由です。
ぜひチャレンジしてみてください。

またチャレンジの過程で分からないことがあれば、コメントください。
ありがとうございました。