ExtendScript│スクリプト情報を取得してスプレッドシートに記録する方法

ExtendScript(JSX)を使ってスクリプトを実行した「ユーザー名」「スクリプト名」「実行時間」を、Googleスプレッドシートに自動記録する仕組みを構築してみました。

ただし、ソケット通信による直接送信は困難だったため、バッチ+VBS+Curlによる力技構成で実現しています。

目次

目的:スクリプトの実行履歴をログとして残したい

  • 誰がどのスクリプトをいつ実行したかを記録しておきたい
  • 管理用ログやチーム共有用の履歴をスプレッドシートに保存したい

スプレッドシートでログ台帳を用意

Googleスプレッドシートに以下の3項目の列を作成:

GAS(Google Apps Script)のコード

Googleスプレッドシートの ツール > スクリプトエディタ から以下のコードを記述:

function doGet(e) {
  var rowData = {};  
  if (e.parameter == undefined) {
      var getvalue = "undefined"
      rowData.value = getvalue;
      var result = JSON.stringify(rowData);
      return ContentService.createTextOutput(result);
  }else{
      var id = 'スプレットシートのID';
      var sheet = SpreadsheetApp.openById(id).getSheetByName("シート1");
      var array = [ e.parameter.p1 , e.parameter.p2 , e.parameter.p3];
      sheet.appendRow(array);
      var getvalue = "ok"
      rowData.value = getvalue;
      var result = JSON.stringify(rowData);
      return ContentService.createTextOutput(result);
  }
}

GASのデプロイ

  1. メニュー「公開 > ウェブアプリケーションとして導入」
  2. 新しいバージョンを作成、アクセス権を「全員」に設定
  3. 発行されたURLを控えておく(https://script.google.com/macros/s/.../exec
“https://script.google.com/macros/s/発行されたID/exec?p1=スクリプト名.jsx&p2=使用者名&p3=使用日時”

ESTKからバッチを使ってスプレッドシートに値を送信する方法

ExtendScriptから直接Googleスプレッドシートに値を送信するには制限があり、ソケット通信も上手く機能しませんでした。

そこで、ExtendScript → バッチ(.bat) → VBS → Curl → GAS という手順で、強引ながらもスクリプトの実行情報を送信できる仕組みを構築しました。


Curlの導入

まずは以下の公式サイトから Windows版のCurl をダウンロードします。

解凍後、以下のパスに配置します:

C:\Program Files (x86)\curl-7.65.3-win64-mingw

スクリプト実行ログを送信するExtendScript

  • スクリプト名
  • 使用者名(PCのログインユーザー)
  • 使用日時

これらを取得してURLにパラメータとして付与し、Curlで送信します。

また、バッチファイルを生成してCurlコマンドを組み立て、コマンドプロンプトが表示されないようにVBSで非表示実行します。

//スクリプト名取得
var scriptName = decodeURI(File($.fileName));
var scriptName = getName(scriptName, 1);
var scriptName = encodeURI(scriptName);

//使用者名取得
var userName = $.getenv("HOMEPATH");
var userName = getName(userName, 8);
$.writeln(userName);

//名前の抽出
function getName(str, delStr) {
    var str = new String(str).substring(str.lastIndexOf('/') + delStr);
    return str
dObj = new Date();
}

//スクリプト使用日取得
var dObj = new Date();
var y = dObj.getFullYear();
var m = dObj.getMonth() + 1;
var d = dObj.getDate();
var h = dObj.getHours();
var s  = dObj.getMinutes();
var usedDate = y + "/" + m + "/" + d+ "/" + h+ ":" + s;

var webhookUrl = "https://script.google.com/macros/s/******/exec?"+"p1="+scriptName +"&p2="+userName+"&p3="+usedDate;
//変数をダブルクォーテーションで囲う
webhookUrl = "\"" + webhookUrl+ "\"";

function createBatchFile(webhookUrl) {
    const CR = String.fromCharCode(13);
    const BATCH_FILE = new File(Folder.temp + "/tmp.bat");       
    BATCH_FILE.open("w");
    BATCH_FILE.write("@echo off" + CR);
    BATCH_FILE.write("setlocal" + CR);
    BATCH_FILE.write("cd C:/Program Files (x86)/curl-7.65.3-win64-mingw/bin/" + CR);
    BATCH_FILE.write("set URL= " +webhookUrl+CR);       
    BATCH_FILE.write("curl -L %URL%"+CR);  
    BATCH_FILE.write("exit"+CR);  
    //BATCH_FILE.write("pause" + CR);
    BATCH_FILE.close();
    //BATCH_FILE.execute();
}

//実行コマンド
createBatchFile(webhookUrl);

//コマンドプロンプト開きたくないので、VBSをかます
var cmd = "C:\\Library\\Bat\\test.vbs";
var file = new File(cmd);
file.execute();
実際にスクリプトにつけて計測する

JSX(実際に使用するスクリプト)

//--------------------------------------------------
// スクリプト計測
//--------------------------------------------------
#include 'C:\\Library\\ESTK\\scriptLog.jsx';

var script = $.fileName;
var user = $.getenv("HOMEPATH");
var date = new Date();
$.writeln(script + user + date);
try {
    scriptLog(script, user, date);
} catch (e) {}

//--------------------------------------------------
// 実際の処理    
//--------------------------------------------------
alert("テスト!");

JSX(参照先)

function scriptLog(script,user,date){

    var scriptName = decodeURI(File(script));
    var scriptName = getName(scriptName, 1);
    var scriptName = encodeURI(scriptName);

    var userName = user;
    var userName = getName(userName, 8);

    function getName(str, delStr) {
        var str = new String(str).substring(str.lastIndexOf('/') + delStr);
        return str
        dObj = new Date();
    }

    var dObj = date;
    var y = dObj.getFullYear();
    var m = dObj.getMonth() + 1;
    var d = dObj.getDate();
    var h = dObj.getHours();
    var s = dObj.getMinutes();
    var usedDate = y + "/" + m + "/" + d + "/" + h + ":" + s;

    var webhookUrl = "https://script.google.com/macros/s/******/exec?" + "p1=" + scriptName + "&p2=" + userName + "&p3=" + usedDate;
    var webhookUrl = webhookUrl.replace( /%/g , "%%" ) ;   
    var webhookUrl = "\"" + webhookUrl + "\"";

    function createBatchFile(webhookUrl) {
        const CR = String.fromCharCode(13);
        const BATCH_FILE = new File(Folder.temp + "/tmp.bat");
        BATCH_FILE.open("w");
        BATCH_FILE.write("@echo off" + CR);
        BATCH_FILE.write("setlocal" + CR);
        BATCH_FILE.write("cd C:/Program Files (x86)/curl-7.65.3-win64-mingw/bin/" + CR);
        BATCH_FILE.write("set URL= " + webhookUrl + CR);
        BATCH_FILE.write("curl -L %URL%" + CR);
        BATCH_FILE.write("exit" + CR);
        //BATCH_FILE.write("pause" + CR);
        BATCH_FILE.close();
        //BATCH_FILE.execute();
    }

    createBatchFile(webhookUrl);
    var cmd = "C:\\Library\\Bat\\test.vbs";
    var file = new File(cmd);
    file.execute();
}

▶ VBSの内容(test.vbs

Set ws = CreateObject("Wscript.Shell") 
ws.run "cmd /c C:\Users\min\Desktop\tmp.bat", vbhide 

実行結果

この仕組みを導入すれば、ExtendScriptの実行時にログ情報が自動的にスプレッドシートへ送信され、記録として残すことができます。

補足:日本語のスクリプト名が文字化けする問題

当初、日本語名のスクリプトファイルがURLエンコードしても正常に送信されませんでしたが、
バッチファイル内で %%% に置換することで解決しました。

var webhookUrl = webhookUrl.replace( /%/g , "%%" ) ;   

[補足] BridgeTalkでの送信方法(GAS連携)

Bridgeを経由して通信することで、Curlやバッチを使わずに通信も可能です。

ただし、実行にはBridgeが常駐している必要があります。

BridgeTalk.prototype.sendSynch = function(timeout) {
	var self = this;
	self.onResult = function(res) {
	this.result = res.body;
	this.complete = true;
	}
	self.complete = false;
	self.send();

	if (timeout) {
		for (var i = 0; i < timeout; i++) {
			BridgeTalk.pump(); // process any outstanding messages
			if (!self.complete) {
				$.sleep(1000);
			} else {
				break;
			}
		}
	}
	var res = self.result;
	self.result = self.complete = self.onResult = undefined;
	return res;
}
// for typos, provide an alias
BridgeTalk.prototype.sendSync = BridgeTalk.prototype.sendSynch;

function loadUrl(url, timeout) {
    var bt = new BridgeTalk();
    bt.target = 'bridge';
    var httpTimeout = timeout;
 
    var script = '';
    script += "if ( !ExternalObject.webaccesslib )\n";
    script += "  ExternalObject.webaccesslib = new ExternalObject('lib:webaccesslib');\n";
    script += "var response = null;\n";
    script += "var retry = true;\n";
    script += "while (retry) {\n";
    script += "  var http = new HttpConnection('" + url + "') ; \n";
    script += "  http.timeout  = " + httpTimeout + ";\n";
    script += "  http.execute() ;\n";
    script += "  try{\n";
    script += "    response = http.response;\n";
    script += "    retry = false;\n";
    script += "  } catch (e){\n";
    script += "    BridgeTalk.bringToFront('bridge');\n";
    script += "    if (!confirm('There was an error communicating with the server. Would you like to retry?'))\n";
    script += "      retry = false;\n";
    script += "  }\n";
    script += "}\n";
    script += "response;\n";
 	
    bt.body = script;
    return bt.sendSynch(timeout);
}

alert(loadUrl('http://rpc.geocoder.us/service/csv?address=1600+Pennsylvania+Ave,+Washington+DC',50));

おまけ:Webからファイルをダウンロードするサンプル

ソケット通信でWEBからデータをダウンロードするスクリプトです。

function getHttpResponse(requests) {
    var parseUrl = function(url) {
        var urlObj = {};
        //[url, scheme, slash, host, port, path, query, fragment] via O'REILLY JavaScript: The Good Parts  
        var url_re = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
        var m = url_re.exec(url);
        urlObj.host = m[3];
        urlObj.port = m[4] || '80';
        urlObj.path = m[5];
        urlObj.query = (m[6]) ? '?' + m[6] : '';
        urlObj.frag = (m[7]) ? '#' + m[7] : '';
        return urlObj;
    }
    var urlObj = parseUrl(requests.url);
    var encoding = requests.encoding || 'UTF-8';
    var method = requests.method || 'GET';
    var auth = (requests.basic_auth != undefined) ? 'Authorization: Basic ' + requests.basic_auth + '\r\n' : '';
    var conn = new Socket;
    conn.timeout = 10;
    if (conn.open(urlObj.host + ':' + urlObj.port, encoding)) {  
        conn.write(method + ' /' + urlObj.path + urlObj.query + urlObj.frag + ' HTTP/1.0\r\n' + 'Host: ' + urlObj.host + '\r\n' + 'User-Agent: ' + 'InDesign/6.0' + '(Macintosh; U; Intel Mac OS X 10_5_6; ja-jp)' + '\r\n' + auth + '\r\n');
        var reply = conn.read(999999);
        conn.close();
        return reply; //ヘッダ込みで返す  
    } else {
        return conn.error;
    }
}

//Webからファイルをダウンロードする  
function downloadFile(url, localFile) {
    var rep = getHttpResponse({
        method: 'GET',
        url: url,
        encoding: 'BINARY',
    });
    if (rep.match(/HTTP.*\d{3}/).toString().indexOf('200') != -1) {
        //レスポンスのヘッダを除去   
        var body = rep.slice(rep.indexOf('\r\n\r\n') + 4);
        //ファイルに書き出し  
        var f = new File(localFile);
        if (f.open("w")) {
            f.encoding = 'BINARY';
            f.write(body);
        }
        f.close();
        return f;
    } else {
        return false;
    }
}

//ソースコードをダウンロード
//var f = downloadFile('https://www.cg-method.com/blog/', '~/Desktop/cg-method.txt');  

//スクリプトをダウンロード
//var f = downloadFile('https://www.cg-method.com/download/5652/', '~/Desktop/CGM_SavePng.jsx');  

//画像をダウンロード
var f = downloadFile('https://secure.gravatar.com/avatar/077a21e2303f1efa3d3a5ecbf5e5193d?s=100&d=mm&r=g', '~/Desktop/suimin.png');

if (f) {
    alert(f.name + 'を保存しました。');
}

まとめ

  • ExtendScript単体でのネット通信は制限が多い
  • GASとCurlを使えばスプレッドシート連携が可能
  • ソケット通信やBridgeTalkを使えば改善の余地あり
  • バッチとVBSの併用で裏側での自動記録も可能
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次