🐡

GASでフォルダを複製する

2023/03/10に公開

はじめに

SEVENRICH GROUP、DELTA所属の伊藤です。

以前DELTAのアドベントカレンダーで『超初心者がGASで死活監視するよ。』を書きました。(こちらで自己紹介もちらっとしております)
https://zenn.dev/delta/articles/79147951470266
前職スーパーの魚屋の私が初めてGASを使ってみた所感を綴りましたが、それから社内でご縁があり、再びGASを触ることになりました。

課題

同じ形式のフォームやスプレッドシートを多数用意しなければならず、1つのフォルダにまとめてフォルダごとコピー出来れば簡単なんですが、残念ながらGoogleドライブはフォルダコピーできない仕様なので、GASでまとめてファイルを複製できるようにしたいという課題でした。

①「とりあえずフォルダごと複製してみる」

最初は取り合えずシンプルなところから。
ということでテンプレートとなるフォルダを用意して中に複数ファイルを用意し、複製してみることに。
こちらのサイトを参考にやってみました。
https://utelecon.adm.u-tokyo.ac.jp/articles/gas/copy#フォルダの複製方法

コード(全体)

function foldercopy() {

  const folderIdSrc = "コピー元のフォルダid";    // コピー元のフォルダid
  const folderNameDest = "新しいフォルダ";  // コピー後のフォルダ名

  const folderSrc = DriveApp.getFolderById(folderIdSrc);  //元のフォルダ
  const folderDest = folderSrc.getParents().next().createFolder(folderNameDest);//新しい名前で一番外側のフォルダを作る

  copyFolder(folderSrc, folderDest);//copyFolderに渡す
}

//----------------フォルダを複製していくぅっ!!--------------------//
function copyFolder(src, dest) {
  const folders = src.getFolders();//元のフォルダを取得
  const files = src.getFiles();  //元のファイルを取得
 //---------内部ファイルをコピー--------//
  while (files.hasNext()) {
    const file = files.next();      //ファイルを取得して、
    file.makeCopy(file.getName(), dest);//コピーを作成
  }
//----------内部フォルダをコピー--------//
  while (folders.hasNext()) {
    const subFolder = folders.next();           //フォルダを取得して、
    const folderName = subFolder.getName();       //フォルダ名を取得
    const folderDest = dest.createFolder(folderName); //新しい名前でフォルダを作る
    copyFolder(subFolder, folderDest);          //copyFolderに渡す
  }
}

とにかく初心者に毛が生えただけなので、コピーして貼り付けが基本です。
なにがどうなっているか、調べたり、目で追いながら一行一行コメントを全部書くのが一番でした。(雑な部分は目をつむって頂けると幸いです……)

一回の実行で数秒かかりますが、ファイルを一個一個コピーして作るよりは圧倒的に速いし楽です。
簡単なフォルダのコピーは意外とあっさり出来てしまったため、ここから少しずつ機能を追加していきます。

②「ファイルのタイトルを変えたい」

一番外側のフォルダ名の変更は出来ましたが、内部のファイルのタイトルはコピー元のままです。
お客様(プロジェクト)ごとに新規作成するので企業名くらいは付けたいです。
テンプレートのファイルたちに『○○_企業名』と付けておきます。
企業名というところを取って、全部のファイルタイトルを新規作成する企業名にリプレイスします。
追加した要素
・企業名を定義(company)
・新規作成したファイル名を取得(copy_filename)
・タイトルのリプレイス(newFileName)

コード(一部)

//----------------フォルダを複製していくぅっ!!--------------------//
function copyFolder(src, dest) {
  const folders = src.getFolders();//元のフォルダを取得
  const files = src.getFiles();  //元のファイルを取得
  const company = "株式会社DELTA"   //企業名

  //---------内部ファイルをコピー--------//
  while (files.hasNext()) {
    const file = files.next();      //ファイルを取得して、
    const newfile = file.makeCopy(file.getName(), dest);//コピーを作成

    var copy_filename = newfile.getName();            //すべてのファイル名を取得
    const newFileName = copy_filename.replace("企業名", company); //置き換える
    newfile.setName(newFileName);                  //新しいタイトルをセットする
  }

~つまずきポイント~
最初はそもそもファイルのタイトルをどこから取ればいいのか分からなかったです。
こうやって後から見返してみれば見りゃわかるじゃんって思うんですが、「全部日本語で説明書いちゃえ」となるまでは、"ファイルを取得して、コピーを作成"で止まってしまっていたので、そのコピーしたファイルはどこにいったん?って感じでした……。
なのでとにかく見れば分かると思っても、全部一行一行日本語で説明書くのが初心者なりのやり方です。

"企業名"の部分が変更されている

③「一回で複数作りたい」

一回で複数企業分作れたらもっと時短になるので、スプレッドシートに作りたい分の企業名をリストにしてもらって、複数企業分一気にフォルダを作れるようにしたいと思います。
まずはスプレッドシートを以下のように用意します。

A列:フォルダの先頭に番号を振るためのNo.
B列:企業名をリストにし、上から作っていく
C列:作成日を入力しておくことで、次回新たに追加した分だけを作成するようにする

追加した要素
・使用中のスプレッドシート(シート)の取得(data_sh)
 ※シート名は「入力シート」にしています
・今日の日付の取得⇒最後まで行ったら日付をスプレッドシートに入れる(formatDate)
・企業名の最後の行を取得(lastRow)
・for文でスプレッドシートから企業名を順に取れるようにする
・フォルダナンバーの取得(folder_number)
・企業名の取得(folder_company)
・コピー後のフォルダ名の変更(folderNameDest)
 (01_企業名の形式で作成される)
・作成日の取得(date)
 (スプレッドシート入力用)
・function copyFolder内のcompanyはスプレッドシートから取得したfolder_companyに置き換える(companyは削除)

コード(全体)

function foldercopy() {
  const spreadsheet = SpreadsheetApp.getActive();
  const data_sh = spreadsheet.getSheetByName('入力シート');

  const formatDate = Utilities.formatDate(new Date(), 'JST', 'yy年M月d日');
  const lastRow = data_sh.getRange('B2').getNextDataCell(SpreadsheetApp.Direction.DOWN).getRow();


  //----------------フォルダを複製していくぅっ!!--------------------//

  for (let i = 3; i <= lastRow; i++) {//i(スタート行)から、lastRow(最後の行)まで繰り返し

    const folder_number = data_sh.getRange(i, 1).getValue();//フォルダNa.(1列目)
    const folder_company = data_sh.getRange(i, 2).getValue();//フォルダ企業名(2列目)
    const folderNameDest = (folder_number + '_' + folder_company) // コピー後のフォルダ名
    const folderIdSrc = "コピー元のフォルダid"";    // コピー元のフォルダid
    const date = data_sh.getRange(i, 3).getValue();  //日付の有無確認用


    if (folder_company != "" & date == "") { //企業名が空じゃなくて、日付がなかったら
      const folderSrc = DriveApp.getFolderById(folderIdSrc);  //元のフォルダ
      const folderDest = folderSrc.getParents().next().createFolder(folderNameDest);//新しい名前で一番外側のフォルダを作る
      copyFolder(folderSrc, folderDest);//copyFolderに渡す
    }

    function copyFolder(src, dest) {
      const folders = src.getFolders();//元のフォルダを取得
      const files = src.getFiles();  //元のファイルを取得

      //---------内部ファイルをコピー--------//
      while (files.hasNext()) {
        const file = files.next();      //ファイルを取得して、
        const newfile = file.makeCopy(file.getName(), dest);//コピーを作成

        var copy_filename = newfile.getName();  //すべてのファイル名を取得

        const newFileName = copy_filename.replace("企業名", folder_company);//置き換える
        newfile.setName(newFileName);        //新しいタイトルをセットする
      }
      //---------内部フォルダをコピー--------//
      while (folders.hasNext()) {
        const subFolder = folders.next();      //フォルダを取得して、
        const folderName = subFolder.getName();  //フォルダ名を取得
        const folderDest = dest.createFolder(folderName);//新しい名前でフォルダを作る
        copyFolder(subFolder, folderDest);     //copyFolderに渡す
      }
    }
    data_sh.getRange(i, 3).setValue(formatDate);  //終わったら日付をセルに入力する
  }
}

(多分もろもろ突っ込みたいところは満載かと思いますがご容赦ください)
1.からコードがだいぶ伸びた感じがします。


C列(作成日)にちゃんと日付が入ってます

新規作成したフォルダのタイトルもしっかり変更できていますね

さて、ここまでくれば課題はほぼ解決です。
スプレッドシートに企業名を入力してポチっとするだけで、フォルダが自動で作成されて、良い感じに企業ごとにファイルが用意されます。
でもさらに便利にするべく機能を追加していきます。

④「ファイルのIDを取りたい」

新規作成したファイルの内容をカスタマイズしたいため、フォルダ作成後に編集していきます。
ファイルIDを取得⇒一旦スプレッドシートに入力⇒スプレッドシートからIDを取得してコピーされたファイルを編集する
という流れです。

②の時点でファイル名を取得しているので、ファイルタイトルを指定して、それぞれスプレッドシートにIDを入力します。
スプレッドシートの方は「入力シート」の隣に『idシート』を新規に作りました。『idシート』の方に取得したIDを一覧化します。

『idシート』A2にQUERY関数で「入力シート」に記載の情報を引っ張って来ています。

こんな感じでそれぞれのファイルIDを取得後スプレッドシートに入力していきます。

追加した要素
・『idシート』の取得(id_sh)
・新規作成したファイルIDを取得(copy_fileId)
・各ファイル名からIDを特定⇒スプレッドシートに記入

コード(一部)


  // =================省略==========================

 //---------内部ファイルをコピー--------//
      while (files.hasNext()) {
        const file = files.next();      //ファイルを取得して、
        const newfile = file.makeCopy(file.getName(), dest);//コピーを作成

        var copy_filename = newfile.getName(); //すべてのファイル名を取得
        const copy_fileId = newfile.getId();   //すべてのファイルIDを取得

        //---名前を指定して新規のファイルidをとってくる(スプシに入力する)---//
        //form
        if (copy_filename == "フォームA_企業名") {
          id_sh.getRange(i, 4).setValue(copy_fileId);
        }
        if (copy_filename == "フォームB_企業名") {
          id_sh.getRange(i, 5).setValue(copy_fileId);
        }
        //SS
        if (copy_filename == "スプレッドシートA_企業名") {
          id_sh.getRange(i, 6).setValue(copy_fileId);
        }
        if (copy_filename == "スプレッドシートB_企業名") {
          id_sh.getRange(i, 7).setValue(copy_fileId);
        }
        if (copy_filename == "スプレッドシートC_企業名") {
          id_sh.getRange(i, 8).setValue(copy_fileId);
        }
        if (copy_filename == "スプレッドシートD_企業名") {
          id_sh.getRange(i, 9).setValue(copy_fileId);
        }
        //dc
        if (copy_filename == "ドキュメントA_企業名") {
          id_sh.getRange(i, 10).setValue(copy_fileId);
        }
        if (copy_filename == "ドキュメントB_企業名") {
          id_sh.getRange(i, 11).setValue(copy_fileId);
        }

        const newFileName = copy_filename.replace("企業名", folder_company);//置き換える
        newfile.setName(newFileName);    //新しいタイトルをセットする
      }

~つまずきポイント~
ここも初歩的な部分ですが、どうやってIDをそれぞれで取得しようと悩んでいたところ、「ifでタイトル指定して取る」という天の声に助けられました。
そしてタイトル指定したは良いけど、上記②ですでに企業名をリプレイス変更してしまっていたため、どこに挟みこめばいいのか混乱。頭が固いなぁと落ち込みました。


『idシート』のほうにファイルIDが入力されています。

~ちなみに~
今回は使いませんでしたがフォルダIDも取得できました。
①で出てきたfolderDestが一番外側の親フォルダ、folderDestが内部フォルダなので、getIdで取得できます。
フォルダもファイルと同じように名前を指定してIDを個別で取得することが出来ます。

  // =================省略==========================
  if (folder_company != "" & date == "") {//企業名が空じゃなくて、日付がなかったら
      const folderSrc = DriveApp.getFolderById(folderIdSrc);//元のフォルダ
      const folderDest = folderSrc.getParents().next().createFolder(folderNameDest);//新しい名前で一番外側のフォルダを作る
      //↓親フォルダのidを取得
      const parentfolder_id = folderDest.getId(); //親フォルダのidを取得
      console.log(parentfolder_id);
      id_sh.getRange(i, 12).setValue(parentfolder_id);       //親フォルダのidをスプシに記入

      copyFolder(folderSrc, folderDest);//copyFolderに渡す
    }
     
     // =================省略==========================
     //---------内部フォルダをコピー--------//
      while (folders.hasNext()) {
        const subFolder = folders.next();   //フォルダを取得して、
        const folderName = subFolder.getName();//フォルダ名を取得
        const folderDest = dest.createFolder(folderName);//新しい名前でフォルダを作る
        //↓フォルダのidを取得
        const subfolderId = folderDest.getId();
        console.log(subfolderId);

        if (folderName == "フォルダ①") {
          id_sh.getRange(i, 13).setValue(subfolderId);
        }
        if (folderName == "フォルダ②") {
          id_sh.getRange(i, 14).setValue(subfolderId);
        }
        if (folderName == "フォルダA") {
          id_sh.getRange(i, 15).setValue(subfolderId);
        }
        if (folderName == "フォルダB") {
          id_sh.getRange(i, 16).setValue(subfolderId);
        }

        copyFolder(subFolder, folderDest);   //copyFolderに渡す
      }


フォルダIDが取れました

⑤「IDさえ分かればなんでもできる」

④でスプレッドシートに入力されたIDから新規作成されたスプレッドシートを呼び出して、それぞれに指定の動きをさせることが出来ます。
例として、「スプレッドシートA」の特定のセルに「ドキュメントA」のIDを入力する動きをしてみます。

    if (folder_company != "" & date == "") {//企業名が空じゃなくて、日付がなかったら
      const folderSrc = DriveApp.getFolderById(folderIdSrc);//元のフォルダ
      const folderDest = folderSrc.getParents().next().createFolder(folderNameDest);//新しい名前で一番外側のフォルダを作る
      copyFolder(folderSrc, folderDest);//copyFolderに渡す


      //------スプシに記入したIDを使って内部ファイルの処理をする------//
      //--スプシA処理--//
      const ssA_Id = id_sh.getRange(i, 6).getValue();   //スプシAid取得
      const ssA = SpreadsheetApp.openById(ssA_Id);//スプシA呼び出し
      const ssA_sheet1 = ssA.getSheetByName("シート1"); //シートを指定
      const docA_id = id_sh.getRange(i, 10).getValue();//ドキュメントAid取得
      //ドキュメントidの入力//
      ssA_sheet1.getRange(1, 2).setValue(docA_id); //B1にドキュメントidをセット

    }


B1に企業ごとのドキュメントAのIDが入力されています

~つまずきポイント~
実は最初for文の最後に内部ファイルの処理命令を入れてました。それでもちゃんとフォルダの複製は出来たので気づかなかったんですが、作成日時がすでに入っている状態で間違えて実行してしまったところ、if (folder_company != "" & date == "")の条件から外れてしまったため、ここの処理だけ実行しようとしていたためエラーが出て気づきました。

最後に

①から⑤までのステップでフォルダ複製のGASを書きました。一つ一つコピーして、ファイル名を変更して……なんて手間がなくなったのですごく有意義ですね。
つまずきポイントも多く、分からないことだらけでかなり時間を費やしましたが、また一つレベルアップした感じです。
もし同じようにフォルダの複製に悩んでいた方がいらっしゃれば参考にいて頂ければと思います。

We're Hiring!

最後までお読みくださり、ありがとうございます。
現在DELTA では一緒に働いてくださる仲間を募集中です。
https://note.com/delta_sevenrich/n/n4ff4ff4c9793
昨年にはDELTA代表の書いた↑このような記事も出ていますので、
ご興味お持ちいただけたら、ぜひフォームからご連絡ください!
https://docs.google.com/forms/d/e/1FAIpQLSfQuWNU1il5lq2rVdICM0tSK_jTsjqwc52LYEwUxBq7_ImtrQ/viewform

DELTAテックブログ

Discussion