📝

Google App ScriptでGoogle Driveフォルダを(共有ドライブに)コピーする

に公開

なぜこのようなスクリプトが必要なのか?

Google Workspace では、個人向け(無料版)のGoogleDriveには無い、「共有ドライブ」という機能がある。

この共有ドライブは、組織内のメンバーが共同でファイルを管理するための便利な機能である。

個人の「マイドライブ」でも、ファイルやフォルダへの読み取り・書き込みアクセス権限を、他のユーザーに付与することができるが、共有ドライブは、より高機能で、組織内でファイル共有する時に必要な機能が備わっている。

組織では、メンバーの離職によりアカウントが削除されることがあるが、マイドライブに保存されているファイルは、アカウントが削除されると同時に消えてしまう。

そのため、組織内でのファイル共有には、共有ドライブを利用することが推奨されている。

そこで、一括でファイルを移動させたいことが、ときどきあるのだが... GoogleDriveではフォルダのコピー・移動には制限が多い。

コピー元 → コピー先 ファイル単位の操作 フォルダ単位の操作
マイドライブ → 共有ドライブ コピー、移動とも可能 コピー、移動できない
マイドライブ → マイドライブ コピー、移動とも可能 移動のみ可能 ※ショートカット作成を促される

そこで、、Google App Scriptを使って、マイドライブ内のフォルダを、共有ドライブにコピーするスクリプトを作成した。

スクリプトの概要

このスクリプトは、Google Drive APIを使用して、指定したフォルダを再帰的に、別のフォルダにコピーする。

今回は、マイドライブ内のフォルダを、共有ドライブにコピーするためにスクリプトを書いたが、マイドライブ内へのコピーにも使える。

function copyAll(){
  doCopy('YYYYYYYYYYYYYYYYY', 'XXXXXXXXXXXXXXXXXX');
}


function doCopy(sourceFolderId, targetParentDriveOrFolderId) {
  copyFolderRecursively(
    DriveApp.getFolderById(sourceFolderId),
    targetParentDriveOrFolderId
  );
}


/**
 * (オプション) フォルダ構造を維持して再帰的にコピーする関数
 * copyMyDiveFolderToSharedDrive 関数の最後で copyFolderRecursively(sourceFolder, DEST_FOLDER_ID); を呼び出す
 *
 * @param {Folder} sourceFolder コピー元のフォルダオブジェクト
 * @param {string} targetParentDriveOrFolderId コピー先の親フォルダオブジェクト
 */
function copyFolderRecursively(sourceFolder, targetParentDriveOrFolderId) {
  const sourceFolderName = sourceFolder.getName();
  Logger.log(`フォルダ処理開始: "${sourceFolderName}" (ID: ${sourceFolder.getId()}) -> 親ID: ${targetParentDriveOrFolderId}`);

  // 1. コピー先に同名のフォルダを作成
  const targetFolder = createFolderInFolder(targetParentDriveOrFolderId, newFolderName=sourceFolder.getName());

  // 2. 作成したフォルダ内にファイルをコピー
  const files = sourceFolder.getFiles();
  while (files.hasNext()) {
    const file = files.next();
    const fileName = file.getName();
    const fileId = file.getId();
    try {
      const copiedFile = copyFileToFolder(fileId, targetFolder.getId(), fileName);
      Logger.log(`     -> コピー成功: "${copiedFile.getName()}" (新しいID: ${copiedFile.getId()})`);
    } catch (err) {
      Logger.log(`     -> エラー: ファイル "${fileName}" (ID: ${fileId}) のコピーに失敗。理由: ${err}`);
    }
    // Utilities.sleep(500); // 実行時間制限対策
  }

  // 3. サブフォルダに対して再帰的に処理
  const subFolders = sourceFolder.getFolders();
  while (subFolders.hasNext()) {
    const subFolder = subFolders.next();
    // 再帰呼び出し
    copyFolderRecursively(subFolder, targetFolder.getId()); // 新しく作ったフォルダを次の親IDとして渡す
  }
  Logger.log(`フォルダ処理完了: "${sourceFolderName}"`);
}

/**
 * 指定したフォルダIDのフォルダの下に新しいフォルダを作成する
 *
 * @param {string} parentFolderId 親フォルダのID
 * @param {string} newFolderName 作成するフォルダ名
 * @return {Folder} 作成したフォルダのオブジェクト
 */
function createFolderInFolder(parentFolderId, newFolderName) {
  try {
    var parentFolder = DriveApp.getFolderById(id=parentFolderId);
    var newFolder = parentFolder.createFolder(newFolderName);
    Logger.log('フォルダを作成しました: ' + newFolder.getName() + ' (ID: ' + newFolder.getId() + ')');
    return newFolder;
  } catch (e) {
    Logger.log('フォルダの作成に失敗しました: ' + e.toString());
    throw e;
  }
}

/**
 * 指定したファイルを指定のフォルダにコピーし、名前を変更して保存する
 *
 * @param {string} fileId コピー元ファイルのID
 * @param {string} destFolderId コピー先フォルダのID
 * @param {string} newFileName コピー後のファイル名
 * @return {File} コピーしたファイルのオブジェクト
 */
function copyFileToFolder(fileId, destFolderId, newFileName) {
  try {
    var file = DriveApp.getFileById(fileId);
    var destFolder = DriveApp.getFolderById(destFolderId);
    var copiedFile = file.makeCopy(newFileName, destFolder);
    Logger.log('ファイルをコピーしました: ' + copiedFile.getName() + ' (ID: ' + copiedFile.getId() + ')');
    return copiedFile;
  } catch (e) {
    Logger.log('ファイルのコピーに失敗しました: ' + e.toString());
    throw e;
  }
}

こういうのって生成AIが得意だよね?

...と考えていたのだが、GASのお勉強が足りなかったのか、ちっとも上手くいかなかった。(Gemini 2.5 Pro, o4-mini-high

結局、o4-mini-highに、関数1個ずつ書かせてテストして...を繰り返して、作った。

関数単位だと動くものを書かせることができるし、よくあるタイプのスクリプトだと思うのだが、一括で生成できないのは不思議。

Discussion