エンジニアになりたい人募集!X(旧Twitter)からフォローしたらリプライで質問常時OK!

Googleドライブのフォルダを一括コピー!GASで実現する自動化手順

この記事はで読むことができます。

はじめに

GoogleDrive内のフォルダはドラッグ・アンド・ドロップや切り取り+貼り付けで簡単に移動できますが、コピーは制限がかかっているため、まとめて複製を行うことができません
今回はGASを使って指定フォルダ以下の全階層をまるっとコピーするコードをご紹介します。

この記事では以下のステップでコードを解説しています。

1.仕組み

指定されたフォルダの中身を再帰的に取得します。
フォルダの場合、同一の構造・名称でコピー先にフォルダを作成します。
ファイルの場合、ファイルIDとコピー先のフォルダIDをセットにしてリストを作成し、スプレッドシートに出力します。

出力したデータを読み取り、1件ずつコピー先のフォルダへファイルをコピーし、コピー後のファイルIDをスプレッドシートに出力します。
GASの実行制限に収まるよう、実行開始から5分以上経過した場合はそこで処理を終了します。
再度ファイルコピーの関数を動かすと、処理済の行はスキップし、続きからコピーを再開します。

※ 今回のコードでは各工程の関数実行は手動で行うことを前提にしています

2.コード一覧

下記いずれかの方法でGASファイルを用意し、「コード.gas」の中身を書き換えてください。

スプレッドシートとGASファイルを各々作成

作業用のスプレッドシートとGASファイルを別々で管理したい方は下記をそのまま使用してください

gas
const folderIdOld = "{コピー元のフォルダID}";
const folderIdNew = "{コピー先のフォルダID}";
const ssId = "{スプレッドシートのID}";

function doCopyFolder() {
  const folderOld = DriveApp.getFolderById(folderIdOld);
  const folderNew = DriveApp.getFolderById(folderIdNew);
  let fileIds = copyFolder(folderOld, folderNew);
  const output = [["旧フォルダ名", "旧ファイル名", "旧フォルダID", "旧ファイルID", "新フォルダID", "新ファイルID"]].concat(fileIds);
  const ss = SpreadsheetApp.openById(ssId);
  ss.getSheets()[0]
    .getRange(1, 1, output.length, output[0].length)
    .setValues(output);
}

function copyFolder(folderOld, folderNew) {
  let fileIds = [];
  const files = folderOld.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    fileIds.push([folderOld.getName(), file.getName(), folderOld.getId(), file.getId(), folderNew.getId(), ""]); 
  }
  const folders = folderOld.getFolders();
  while (folders.hasNext()) {
    const folder = folders.next();
    const folderCreated = folderNew.createFolder(folder.getName());
    const fileIdsChildFolder = copyFolder(folder, folderCreated);
    fileIds = fileIds.concat(fileIdsChildFolder);
  }
  return fileIds;
}

function doCopyFile() {
  const timeStart = new Date();
  const ss = SpreadsheetApp.openById(ssId);
  const sht = ss.getSheets()[0];
  const fileIds = sht.getDataRange().getValues();
  const clm = {fileId:3, folderId:4, copiedId:5};
  for (let i = 1; i < fileIds.length; i++){
    if (fileIds[i][clm.copiedId]){ 
      continue;
    }
    const folder = DriveApp.getFolderById(fileIds[i][clm.folderId]);
    const file = DriveApp.getFileById(fileIds[i][clm.fileId]);
    const fileCopied = file.makeCopy(file.getName(), folder);
    const parentFolder = fileCopied.getParents();
    if(parentFolder.next() != folder){
      fileCopied.moveTo(folder);
    }
    sht.getRange(i + 1, clm.copiedId + 1).setValue(fileCopied.getId());

    timeElapsed = new Date() - timeStart;
    if(timeElapsed >= 5*60*1000){
      break;
    }
  }
}

スプレッドシートからApps Scriptを選択して作成

作業用のスプレッドシートとGASファイルを紐づけて管理したい方は上記のコードをコピーしたあと、以下を変更してください

  • const ssId = "{スプレッドシートのID}"; を削除
  • const ss = SpreadsheetApp.openById(ssId);
    const ss = SpreadsheetApp.getActive(); へ置換

3.使い方

GASの編集画面でコードを入力
固定値を設定

フォルダIDはフォルダを開いた際のURLから確認
https://drive.google.com/drive/folders/{フォルダID}/

ファイルIDはスプレッドシートのURLから確認
https://docs.google.com/spreadsheets/d/{ファイルID}/

doCopyFolder() を実行

スプレッドシートにリストが出力されたことを確認

doCopyFile() を実行

関数の実行完了後にスプレッドシートを確認し、すべてのファイルに新ファイルIDが付与されるまで手動で関数実行を繰り返す

4.コード解説

※ JavaScriptの挙動については詳細は割愛しています

関数外:固定値設定

各関数で使用する固定値を関数外で設定
 ※ スプレッドシートからApps Scriptを選択して作成した場合、ssIdは不要

gas
const folderIdOld = "{コピー元のフォルダID}";
const folderIdNew = "{コピー先のフォルダID}";
const ssId = "{スプレッドシートのID}";
doCopyFolder():フォルダコピーの起動・データの出力

DriveAppクラスのgetFolderById(id)メソッドでコピー元のフォルダとコピー先のフォルダのFolderオブジェクトを生成

gas
const folderOld = DriveApp.getFolderById(folderIdOld);
const folderNew = DriveApp.getFolderById(folderIdNew);

関数copyFolderにFolderオブジェクトを引数として渡し、戻り値にヘッダーを追加したものを変数outputに格納

gas
let fileIds = copyFolder(folderOld, folderNew);
const output = [["旧フォルダ名", "旧ファイル名", "旧フォルダID", "旧ファイルID", "新フォルダID", "新ファイルID"]].concat(fileIds);

出力対象のスプレッドシートをSpreadsheetAppクラスのopenById(id)メソッドで指定
 ※ スプレッドシートからApps Scriptを選択して作成した場合、getActive()メソッドを使用
SpreadsheetクラスのgetSheets()メソッドで全シート取得し、index:0のシートを指定
SheetクラスのgetRange(row, column, numRows, numColumns)メソッドで1行目1列目のセルから変数outputの配列の要素数分拡張した範囲を指定
RangeクラスのsetValues(values)メソッドで変数outputの値を出力

gas
const ss = SpreadsheetApp.openById(ssId);
ss.getSheets()[0]
  .getRange(1, 1, output.length, output[0].length)
  .setValues(output);
copyFolder(folderOld, folderNew):同一フォルダの作成とファイル情報のリスト化

戻り値を事前に配列として定義

gas
let fileIds = [];

FolderクラスのgetFiles()メソッドでコピー元フォルダ内のすべてのファイルをコレクション形式のFileIteratorオブジェクトで取得
FileIteratorオブジェクト内に次のアイテムがある限りループするようwhileを設定

FileIteratorクラスのnext()メソッドはコレクション内の次のアイテムを取得する
FileIteratorクラスのhasNext()メソッドはFileIteratorオブジェクトに対してnext()を呼び出した場合にアイテムを返すかどうかが返る(戻り値がBoolean型)

gas
const files = folderOld.getFiles();
while (files.hasNext()) {
  //後述
}

コレクション内の次のファイルアイテムを取得
変数fileIdsにコピー元フォルダ名コピー元ファイル名コピー元フォルダIDコピー元ファイルIDコピー先フォルダIDコピー後ファイルID用の空白を配列として格納
フォルダはFolderクラスのgetName()メソッドでフォルダ名、getId()メソッドでIDを取得
フォルダはFileクラスのgetName()メソッドでファイル名、getId()メソッドでIDを取得

gas
//while (files.hasNext()) {}の中身

const file = files.next();
fileIds.push([folderOld.getName(), file.getName(), folderOld.getId(), file.getId(), folderNew.getId(), ""]); 

FolderクラスのgetFolders()メソッドでコピー元フォルダ内のすべてのフォルダをコレクション形式のFolderIteratorオブジェクトで取得
FolderIteratorオブジェクト内に次のアイテムがある限りループするようwhileを設定

FolderIteratorクラスのnext()メソッドはコレクション内の次のアイテムを取得する
FolderIteratorクラスのhasNext()メソッドはFolderIteratorオブジェクトに対してnext()を呼び出した場合にアイテムを返すかどうかが返る(戻り値がBoolean型)

gas
const folders = folderOld.getFolders();
while (folders.hasNext()) {
  //後述
}

コレクション内の次のフォルダーアイテムを取得
FolderクラスのcreateFolder(name)メソッドでコピー先フォルダーに同一名称のフォルダーを作成
フォルダ名はFolderクラスのgetName()メソッドで取得
関数copyFolderに作成したコピー先フォルダーのFolderオブジェクトを引数として渡し、戻り値を変数fileIdsに追加

親フォルダ内で子フォルダを探索>子フォルダ内で孫フォルダを探索>…と各フォルダの最下層まで探索される

gas
//while (folders.hasNext()) {}の中身

const folder = folders.next();
const folderCreated = folderNew.createFolder(folder.getName());
const fileIdsChildFolder = copyFolder(folder, folderCreated);
fileIds = fileIds.concat(fileIdsChildFolder);

ファイル一覧を返す

gas
return fileIds;
doCopyFile():ファイルのコピー

開始時刻を変数timeStartに格納
作業用のスプレッドシートをSpreadsheetAppクラスのopenById(id)メソッドで指定
 ※ スプレッドシートからApps Scriptを選択して作成した場合、getActive()メソッドを使用
SpreadsheetクラスのgetSheets()メソッドで全シート取得し、index:0のシートを指定
SheetクラスのgetDataRange()メソッドでデータがあるセルを指定
RangeクラスのgetValues()メソッドで範囲内の全てのセルの値を配列として取得
取得したデータの列操作をわかりやすくするため変数clmに列名と列数を定義

gas
const timeStart = new Date();
const ss = SpreadsheetApp.openById(ssId);
const sht = ss.getSheets()[0];
const fileIds = sht.getDataRange().getValues();
const clm = {fileId:3, folderId:4, copiedId:5};

取得したセルの値をforで1行ずつ処理

すでに新ファイルID(コピー後のファイルID)がある場合は処理をスキップ
DriveAppクラスのgetFolderById(id)メソッドでコピー先フォルダのFolderオブジェクトを生成
DriveAppクラスのgetFileById(id)メソッドでコピー元ファイルのFileオブジェクトを生成
FileクラスのmakeCopy(name, destination)メソッドでコピー元ファイルの複製をコピー先フォルダに作成

FileクラスのgetParents()メソッドで生成したファイルの保存されているフォルダのコレクションを取得
取得したフォルダコレクション内のフォルダーアイテムがコピー先フォルダと一致していない場合、FileクラスのmoveTo(destination)メソッドでコピー先フォルダに移動

ファイルの種類によってはルートフォルダに作成されるため生成後に再確認を行う

SheetクラスのgetRange(row, column)メソッドで添字とコピー後ID列を元にセルを指定
RangeクラスのsetValue(value)メソッドでFileクラスのgetId()メソッドで取得したコピー後のファイルIDを出力

現在時刻と開始時刻を比較し、5分以上経過している場合はforを抜ける

gas
  for (let i = 1; i < fileIds.length; i++){
    if (fileIds[i][clm.copiedId]){ 
      continue;
    }
    const folder = DriveApp.getFolderById(fileIds[i][clm.folderId]);
    const file = DriveApp.getFileById(fileIds[i][clm.fileId]);
    const fileCopied = file.makeCopy(file.getName(), folder);
    const parentFolder = fileCopied.getParents();
    if(parentFolder.next() != folder){
      fileCopied.moveTo(folder);
    }
    sht.getRange(i + 1, clm.copiedId + 1).setValue(fileCopied.getId());
    if(new Date() - timeStart >= 5*60*1000){
      break;
    }
  }

【番外編】一括で複製

前述した方法はファイル数が多い場合に抜け落ちずに処理できる・リスト化したものを別途利用できるというメリットがある反面、スプレッドシートを用意したり関数ごとに実行が必要であったりと少し手間がかかります。
ファイル数が多くなく大きなファイルでもない場合は下記コードで一括で処理ができます。

gas
const folderIdOld = "{コピー元のフォルダID}";
const folderIdNew = "{コピー先のフォルダID}";

function doCopyFolderAndFile() {
  const folderOld = DriveApp.getFolderById(folderIdOld);
  const folderNew = DriveApp.getFolderById(folderIdNew);
  copyFolderAndFile(folderOld, folderNew);
}

function copyFolderAndFile(folderOld, folderNew) {
  const files = folderOld.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    const fileCopied = file.makeCopy(file.getName(), folderNew);
    const parentFolder = fileCopied.getParents();
    if(parentFolder.next() != folderNew){
      fileCopied.moveTo(folderNew);
    }
  }
  const folders = folderOld.getFolders();
  while (folders.hasNext()) {
    const folder = folders.next();
    const folderCreated = folderNew.createFolder(folder.getName());
    copyFolderAndFile(folder, folderCreated);
  }
}

まとめ

GoogleDrive内のフォルダとファイルをまとめてコピーする方法を解説しました。
これでバックアップ等で複製が必要になった場合に悩まされることがなくなります…!

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)