Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save szhu/3835be972f71878a511a1a09bd0d4be2 to your computer and use it in GitHub Desktop.
Save szhu/3835be972f71878a511a1a09bd0d4be2 to your computer and use it in GitHub Desktop.

Update!

I just found rclone, which seems to do what my script does, and more:

  • No need to set up sharing a particular way
  • Won't time out, you can just leave it running until it's done
  • Seems to run much much faster
  • Keeps modified times on files the same

The only drawback is that it takes a bit longer to setup.

Steps:

  1. Install rclone. On Mac: brew install rclone

  2. Set up your Google Drive accounts: Type rclone config and follow the instructions. The instructions are long but each step is simple.

    • Add a config for your personal Google Drive called drive-personal.
    • Add a config for your school Google Drive called drive-school.
  3. But then THAT'S IT! You can now run something like:

    rclone -v copyto --drive-server-side-across-configs --drive-copy-shortcut-content \
          drive-school:'' \
          drive-personal:'Copied from school GDrive via rclone'
    

    to copy your entire school Drive into a folder in your personal Drive.

If you use rclone, you can ignore the rest of this post.


My orignal script is still included below in case you have a special use case and still need it:

// @ts-check
// Script: Deep-copy Google Drive folder
// Homepage: https://gist.github.com/szhu/3835be972f71878a511a1a09bd0d4be2
// If you're trying to use this script to migrate content from a school Google
// account to a personal one, here is the recommended workflow:
//
// 01. First, share the folders you want to copy this way:
//
// 02. In your school Google Drive, share all the folders you want to copy
// with your personal Google account. View-only access is sufficient.
//
// 03. In your personal Google Drive, create a new, empty folder that you
// will use just for this migration process.
//
// For example, you can call it "Files shared from school GDrive", or
// you can choose a different name.
//
// 04. In your personal Google Drive, go to "Shared with me" and find all
// the folders that you just shared with your personal Google account.
// Drag all of them into the folder you created in step 3.
//
// FYI, this step doesn't actually copy the folders, otherwise we
// wouldn't need this script! It just creates shortcuts to the shared
// folders.
//
// 05. Then, set up this script:
//
// 06. Go to script.google.com. Check if you're logged in with your personal
// Google account, and use the account switcher if needed.
//
// 07. Create a new project.
//
// 08. Copy and paste the contents of this file into the new project.
//
// 09. Make sure to set the `sourceFolderName` and `targetFolderName`
// variables below correctly.
// - `sourceFolderName` should be the name of the folder you created in
// step 3. If you used the recommended name, no change is needed.
// - `targetFolderName` should be a folder that doesn't exist yet in
// your personal Google Drive. No change is needed, but you can change
// it if you want.
//
// 10. Now you're ready to run the script!
//
// 11. Click the play button in the toolbar.
//
// 12. When the script is running, you'll see a log showing the progress.
// Make sure to review any lines that say "ERROR". Those are Drive
// shortcuts whose targets can no longer be found.
//
// All other kinds of errors will crash the script, so it'll be more
// obvious if anything else went wrong.
//
// 13. If you are using this script, you most likely have more than a
// trivial number of files, and it'll probably take more than Google
// Script's maximum run time to copy them all. If you get an error that
// says "Exceeded maximum execution time", just run the script again,
// and it'll skip copying files that are already copied.
//
// If the script takes too long in the beginning looking at folders you
// know are already copied, you can add their paths to the
// `folderPathsToSkip` variable.
//
// 14. Here's a summary of how the script does copying:
//
// - If the script sees a folder whose copy destination already exists,
// it does still look inside the folder to see if there are files or
// folders that need to be copied.
//
// - If the script sees a file whose copy destination already exists, it
// skips copying that file. It does not check if the contents of the
// files have changed.
//
// - If the script sees a shortcut to a file or folder, it copies the
// target of the shortcut, not the shortcut itself. If there is an
// error getting the target of the shortcut, it logs an error and
// continues.
//
// This means that if you know any files or folders have changed and
// want to re-copy them, you should delete the outdated copies and run
// the script again.
//
// 15. The script will log "Done!" when it's finished.
//
// 16. If you have any questions, please reach out to the group chat or mailing
// list where you found this script.
//
// Or, leave a comment here:
// https://gist.github.com/szhu/3835be972f71878a511a1a09bd0d4be2
var sourceFolderName = "Files shared from school GDrive";
// ^ Name of the folder to copy from.
// - This folder must be in the top level of your Google Drive.
var targetFolderName = "Files copied from school GDrive";
// ^ Name of the folder to copy to.
// - You don't need to create the folder.
// - This folder must be in the top level of your Google Drive.
var folderPathsToSkip = new Set([
// Examples below. You can leave or remove them, it doesn't matter.
"My Drive/path/to/folder/to/skip",
"My Drive/path/to/another/folder/to/skip",
]);
// ^ Paths to skip over. When the script sees a folder whose path is below, it
// will skip copying it and its contents.
// - This is helpful if you need to re-run the script but you know some folders
// were already copied over successfully. Or you can use it to skip folders
// you don't want copied for any other reason.
// - These must be full paths. You can find them in the log output when you run
// the script.
// -- Do not edit below this line --
// This script is based on https://www.labnol.org/code/19979-copy-folders-drive
//
// Major added features:
// - Log progress.
// - If a shortcut is encountered, copy its target rather than the shortcut
// itself.
// - Allow setting folder paths to skip.
// - If the target already exists, don't copy it again.
const FolderMimeType = /** @type {any} */ (MimeType).FOLDER;
function start() {
var source = DriveApp.getFoldersByName(sourceFolderName);
var target = getOrCreateFolder(DriveApp, targetFolderName);
if (source.hasNext()) {
copyFolder(source.next(), target);
}
Logger.log("Done!");
}
/**
* @param {GoogleAppsScript.Drive.DriveApp | GoogleAppsScript.Drive.Folder} parent
* @param {string} name
*/
function getOrCreateFolder(parent, name) {
var matches = parent.getFoldersByName(name);
var folder;
if (matches.hasNext()) {
folder = matches.next();
} else {
folder = parent.createFolder(name);
}
var path = getFilePath(folder);
if (folderPathsToSkip.has(path)) {
Logger.log("Skipping folder: " + path);
return /** @type {const} */ ("skipping");
} else {
Logger.log("Folder: " + path);
return folder;
}
}
/**
* @param {GoogleAppsScript.Drive.File} source
* @param {GoogleAppsScript.Drive.Folder} targetFolder
* @param {string} targetName
*/
function getOrCopyFile(source, targetFolder, targetName) {
var matches = targetFolder.getFilesByName(targetName);
if (matches.hasNext()) {
// Logger.log("Already copied: " + getFilePath(source));
return matches.next();
} else {
Logger.log("Copying: " + getFilePath(source));
return source.makeCopy(targetName, targetFolder);
}
}
/**
* @param {GoogleAppsScript.Drive.Folder} folder
* @param {string} fileName
* @param {string} path
* @param {string} [linkedFileId]
*/
function writeShortcutErrorInfoFile(folder, fileName, path, linkedFileId) {
const errorFileName = fileName + " - GDRIVE_COPY_ERROR.txt";
var info = [
"ERROR: Unable to copy shortcut",
"Path: " + path,
linkedFileId
? `Linked file not found: ${linkedFileId}\nhttps://drive.google.com/open?id=${linkedFileId}`
: "No linked file ID found",
].join("\n");
const existingFiles = folder.getFilesByName(errorFileName);
if (existingFiles.hasNext()) {
var file = existingFiles.next();
Logger.log("ERROR: Error file already exists: " + getFilePath(file));
} else {
var file = folder.createFile(errorFileName, info);
Logger.log("Wrote error to: " + getFilePath(file));
}
}
/**
* @param {GoogleAppsScript.Drive.Folder} source
* @param {GoogleAppsScript.Drive.Folder | "skipping"} target
*/
function copyFolder(source, target) {
if (target === "skipping") return;
var folders = source.getFolders();
var files = source.getFiles();
while (files.hasNext()) {
var file = files.next();
if (file.getMimeType() === "application/vnd.google-apps.shortcut") {
var linkedFileId = file.getTargetId();
if (!linkedFileId) {
Logger.log("ERROR: No linked file ID found for: " + getFilePath(file));
writeShortcutErrorInfoFile(target, file.getName(), getFilePath(file));
continue;
}
var linkedFile;
try {
linkedFile = DriveApp.getFileById(linkedFileId);
} catch {
Logger.log("ERROR: Linked file not found for: " + getFilePath(file));
writeShortcutErrorInfoFile(
target,
file.getName(),
getFilePath(file),
linkedFileId,
);
continue;
}
if (linkedFile.getMimeType() == FolderMimeType) {
var linkedFolder = DriveApp.getFolderById(linkedFileId);
var targetFolder = getOrCreateFolder(target, file.getName());
copyFolder(linkedFolder, targetFolder);
} else {
getOrCopyFile(linkedFile, target, file.getName());
}
} else {
getOrCopyFile(file, target, file.getName());
}
}
while (folders.hasNext()) {
var subFolder = folders.next();
var folderName = subFolder.getName();
var targetFolder = getOrCreateFolder(target, folderName);
copyFolder(subFolder, targetFolder);
}
}
var FilePathCache = new Map();
/**
* @param {GoogleAppsScript.Drive.Folder | GoogleAppsScript.Drive.File} file
*/
function getFilePath(file) {
var path = [];
/** @type {GoogleAppsScript.Drive.Folder | GoogleAppsScript.Drive.File} */
var folder = file;
while (folder) {
var id = folder.getId();
if (FilePathCache.has(id)) {
path.unshift(FilePathCache.get(id));
break;
} else {
path.unshift(folder.getName());
if (folder.getParents().hasNext()) {
folder = folder.getParents().next();
} else {
break;
}
}
}
var filePath = path.join("/");
FilePathCache.set(file.getId(), filePath);
return filePath;
}
/bun.lockb
/node_modules
/package-lock.json
/yarn.lock
{
"plugins": ["prettier-plugin-packagejson"]
}
{
"dependencies": {},
"devDependencies": {
"@types/google-apps-script": "^1.0.83",
"prettier": "3.3.3",
"prettier-plugin-packagejson": "2.5.1"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment