The following Google Apps Script tool is designed to take a Google/Shared Drive folder full of files that you want to make a copy of and place into another set of existing Drive folders.You can decide if you want the subfolder name appending/prepending/ignoring in the file name of the new copy to help make it unique.
The tool will iterate through a single level of folders (not sub-subfolders) and for each one place a copy of the files into it.
Bulk add files to existing Google Drive folders |
Guidance
You provide the ID of the Google/Shared Drive folder which contains the files you wish to make copies of.
You provide the ID of the parent Google/Shared drive folder which contains a single-level of subfolders that you want the tool to iterate through and place a copy of each file in.
The options for naming the new file copies are:
- If you select ‘Append subfolder name’ it will look like: original file name - subfolder name
- If you select ‘Prepend subfolder name’ it will look like: subfolder name - original file name
- If you select ‘Do not include subfolder name’ it will look like: original file name
Speed - making a copy of a file in Google Drive does take a few seconds, this principle still applies here. The tool will inform you of the current subfolder it is working on but be patient depending on the number/size of your files. It is still much quicker than doing this manually and has an in-built feature to run for up to 5 minutes before saving your current progress and prompting you to resume.
The tool is unable to delete any of the files it has created - you would do this as normal via Google Drive.
Download
Bulk add files to existing Google Drive folders download (please use 'File' > 'Make a copy' for your own version).
/* | |
Global Variables that may need to be tweaked and are easier to access by placing here. | |
*/ | |
// Maximum runtime of script in minutes to prevent timeout (5 minutes) | |
var maxRuntime = 5 * 60 * 1000; | |
/* | |
This Function gets the necessary data from the spreadsheet. | |
*/ | |
function getSpreadsheetData() { | |
try { | |
// Log information | |
logEvent('Script has started'); | |
// get spreadsheet | |
var ss = SpreadsheetApp.getActiveSpreadsheet(); | |
// create toast popup to inform user | |
ss.toast('Task has now started ...', 'Start'); | |
// get 'Welcome' sheet | |
var welcomeSheet = ss.getSheetByName('Welcome'); | |
// get folder Id containing Files to be copied | |
var filesFolderId = welcomeSheet.getRange(5, 4).getValue().trim(); | |
// get folder Id of Parent folder containing subfolders to copy files into | |
var parentFolderId = welcomeSheet.getRange(9, 4).getValue().trim(); | |
// get value for including subfolder name | |
var includeName = welcomeSheet.getRange(13, 4).getValue().trim(); | |
// create name:value pair so array easier to work with | |
var sheetData = { ss: ss, filesFolderId: filesFolderId, parentFolderId: parentFolderId, includeName: includeName }; | |
// run Function to copy files into existing folders | |
var copyFilesToFoldersFlag = copyFilesToFolders(sheetData); | |
if (copyFilesToFoldersFlag) { | |
// no errors occurred during process | |
// Log information | |
logEvent('Script has finished without errors'); | |
} else { | |
// there were errors during process, relevant popup will already be displayed | |
// Log information | |
logEvent('Script completed with errors'); | |
} | |
} | |
catch (error) { | |
// there was a problem getting spreadsheet data | |
logEvent("Error with 'getSpreadsheetData' Function: " + error); | |
// create popup to inform user | |
var popupTitle = "'getSpreadsheetData' Function Error"; | |
var popupMessage = "Error message: " + error; | |
htmlPopup(popupTitle, popupMessage); | |
} | |
} | |
/* | |
This Function gets the Google Drive folder containing the files to be copied. | |
It get the Parent Drive folder containg the subfolders to iterate through. | |
It iterates through each subfolder and makes a copy of the files to be placed into it. | |
It then optionally appends the subfolder name to new file copy. | |
*/ | |
function copyFilesToFolders(sheetData) { | |
try { | |
// Log information | |
logEvent("Starting 'copyFilesToFolders' Function"); | |
// extract Sheet data from name:value pair array | |
var ss = sheetData['ss']; | |
var filesFolderId = sheetData['filesFolderId']; | |
var parentFolderId = sheetData['parentFolderId']; | |
var includeName = sheetData['includeName']; | |
// Log information | |
logEvent("Option to append subfolder name is: " + includeName); | |
// get Google Drive folder containing files to copy | |
var filesFolder = DriveApp.getFolderById(filesFolderId); | |
// get Parent Google Drive folder containing subfolders to iterate through | |
var parentFolder = DriveApp.getFolderById(parentFolderId); | |
} catch (error) { | |
// error getting Google Drive folders | |
logEvent('Error getting Google Drive folders: ' + error.stack); | |
// create popup to inform user | |
var popupTitle = "Error getting Google Drive folders"; | |
var popupMessage = "Error message: " + error.stack; | |
htmlPopup(popupTitle, popupMessage); | |
// return Flag to Parent Function | |
return false; | |
}; | |
// check there are files and subfolders to work through | |
var filesLength = filesFolder.getFiles().length; | |
logEvent('Number of files to copy is: ' + filesLength); | |
var parentFolderLength = parentFolder.getFolders().length; | |
logEvent('Number of subfolders is: ' + parentFolderLength); | |
try { | |
// proceed IF successfully got Google Drive folders | |
if (parentFolder) { | |
// Log information | |
logEvent("Got all Google Drive folders"); | |
// create Counter to log number of copied files | |
var copiedFilesCounter = 0; | |
// create Counter to log number of subfolders iterated through | |
var subfoldersCounter = 0; | |
// run Function to check Script Properties for 'Continuation Token' | |
var continuationToken = getScriptProperties(); | |
// check no error getting Script Properties before proceeding ************************ | |
if (continuationToken !== false) { | |
// get start time so can manage duration of script and avoid timeout | |
var startTime = new Date().getTime(); | |
} else { | |
// error occurred, return false flag to Parent Function | |
return false; | |
} | |
// check no error getting Script Properties before proceeding ************************ | |
// check if 'Continuation Token' exists to determine if continuing conversion | |
if (continuationToken === null) { | |
// no Token exists so proceed as first time running the script: | |
// log message in sheet | |
logEvent('No Continuation Token exists. Copying files ...'); | |
// get all subfolders in Parent Drive folder | |
var subFolders = parentFolder.getFolders(); | |
} else { | |
// Token does exist in Script Properties | |
// log message in sheet | |
logEvent('Continuation Token does exist. Copying files ...'); | |
// continue iterating through Drive subfolders with Token | |
var subFolders = DriveApp.continueFolderIterator(continuationToken); | |
} | |
// cycle through each subfolder | |
while (subFolders.hasNext()) { | |
// get subfolder | |
var subFolder = subFolders.next(); | |
// get subfolder name | |
var subFolderName = subFolder.getName(); | |
// create toast popup to inform user and log | |
ss.toast('Folder: ' + subFolderName, 'Copying files into it ...'); | |
logEvent('Folder: ' + subFolderName + ' Copying files into it ...'); | |
// iterate through files and copy into subfolder **************************************** | |
// get all files in folder - need to do here so 'while' loop refreshes after each iteration | |
var filesToCopy = filesFolder.getFiles(); | |
// cycle through each file | |
while (filesToCopy.hasNext()) { | |
// get file | |
var file = filesToCopy.next(); | |
// check what user selected for including the subfolder name in file ***************** | |
var fileName = file.getName(); | |
logEvent("File name to copy is: " + fileName); | |
if (includeName == 'Append subfolder name') { | |
// append file name | |
fileName = fileName + " - " + subFolderName; | |
} else if (includeName == 'Prepend subfolder name') { | |
// prepend file name | |
fileName = subFolderName + " - " + fileName; | |
} else { | |
// user did not select to include subfolder name to file | |
}; | |
// check what user selected for including the subfolder name in file ***************** | |
// make copy of file into subfolder | |
file.makeCopy(fileName, subFolder); | |
// increment Counter by 1 to log number of copied files | |
copiedFilesCounter++; | |
}; | |
// iterate through files and copy into subfolder **************************************** | |
// increment Counter by 1 to log number of subfolders iterated through | |
subfoldersCounter++; | |
// *************************** perform runtime check *************************** | |
// get current time | |
var endTime = new Date().getTime(); | |
// find elapsed time by subtracting from start time | |
var elapsedTime = endTime - startTime; | |
// check against maximum runtime | |
var timeLimitExceeded = elapsedTime >= maxRuntime; | |
// check status of runtime | |
if (timeLimitExceeded) { | |
// runtime has been met/exceeded, store 'Continuation Token' so can resume later | |
// log message in sheet | |
logEvent('Runtime has been met/exceeded.'); | |
// get Token from last subfolder | |
var continuationToken = subFolders.getContinuationToken(); | |
/* | |
run Function to add Token to Script Properies for use later. | |
capture in error variable as this is the last step in process anyway, | |
so no matter what will be using 'break' | |
*/ | |
setPropertyFlag = setScriptProperties(continuationToken); | |
if (setPropertyFlag) { | |
// successfully completed | |
// break out of loop to prevent script from continuing | |
break; | |
} else { | |
// problem occurred, return false Flag | |
return false; | |
} | |
} else { | |
// runtime has not been met/exceeded, script can continue looping through files | |
} | |
// *************************** perform runtime check *************************** | |
}; | |
// Log information | |
logEvent("Completed copying files into a total of " + subfoldersCounter + " subfolders"); | |
logEvent("Created " + copiedFilesCounter + " files from the copy"); | |
} else { | |
// do nothing as script will have terminated at this point | |
} | |
// check if there are still subfolders to iterate through | |
if (subFolders.hasNext()) { | |
// yes there are subfolders to iterate through | |
// log message in sheet | |
logEvent('Successfully iterated through ' + subfoldersCounter + ' subfolders.'); | |
// log message in sheet | |
logEvent('There are still subfolders left.'); | |
// no errors have occurred in the script | |
var popupTitle = 'Runtime reached - ' + subfoldersCounter + ' subfolders iterated through'; | |
var popupMessage = 'No errors, but there are still subfolders to iterate through, so you may wish to run this tool again.'; | |
htmlPopup(popupTitle, popupMessage); | |
// log message in sheet | |
logEvent('Script completed without errors.'); | |
// return success Flag | |
return true; | |
} else { | |
// no all subfolders have been iterated through | |
// delete Script Properties of Token as no longer required | |
deletePropertyFlag = deleteScriptProperties('CONTINUATION_TOKEN'); | |
// check no error deleting Script Property before proceeding *********** | |
if (deletePropertyFlag) { | |
// log message in sheet | |
logEvent('All subfolders have been iterated through.'); | |
// no errors have occurred in the script | |
var popupTitle = 'Tool complete - ' + subfoldersCounter + ' subfolders iterated through'; | |
var popupMessage = 'No errors. All files have been copied into all subfolders.'; | |
htmlPopup(popupTitle, popupMessage); | |
// return success Flag | |
return true; | |
} | |
else { | |
// error deleting Script Property - do nothing as popup will already be displayed | |
} | |
// check no error deleting Script Properties before proceeding *********** | |
} | |
} catch (error) { | |
// there was a problem during file copying | |
logEvent('Error during file copying: ' + error.stack); | |
// create popup to inform user | |
var popupTitle = "Error during file copying"; | |
var popupMessage = "Error message: " + error.stack; | |
htmlPopup(popupTitle, popupMessage); | |
return false; | |
} | |
} | |
/* | |
Function to look for 'Continuation Token' in Script Properties. | |
https://developers.google.com/apps-script/guides/properties | |
*/ | |
function getScriptProperties() { | |
try { | |
// log message in sheet | |
logEvent('Getting Script Properties.'); | |
// access Script Properties | |
var scriptProperties = PropertiesService.getScriptProperties(); | |
// look for 'Continuation Token' Property | |
var continuationToken = scriptProperties.getProperty('CONTINUATION_TOKEN'); | |
// return value to Parent Function | |
return continuationToken; | |
} | |
catch (error) { | |
// log error in sheet | |
logEvent('Problem getting Script Properties: ' + error); | |
// display user popup to inform of error | |
var popupTitle = 'Get Script Properties error'; | |
var popupMessage = 'Problem getting Script Properties: ' + error; | |
htmlPopup(popupTitle, popupMessage); | |
// return false to Parent Function | |
return false; | |
} | |
} | |
/* | |
Function to set 'Continuation Token' in Script Properties. | |
https://developers.google.com/apps-script/guides/properties | |
*/ | |
function setScriptProperties(continuationToken) { | |
try { | |
// log message in sheet | |
logEvent('Setting Script Properties.'); | |
// access Script Properties | |
var scriptProperties = PropertiesService.getScriptProperties(); | |
// set 'Continuation Token' Script Property | |
var continuationToken = scriptProperties.setProperty('CONTINUATION_TOKEN', continuationToken); | |
// log message in sheet | |
logEvent('Successfully set Script Properties.'); | |
// return true to Parent Function | |
return true; | |
} | |
catch (error) { | |
// log error in sheet | |
logEvent('Problem setting Script Property: ' + error); | |
// display user popup to inform of error | |
var popupTitle = 'Set Script Property error'; | |
var popupMessage = 'Problem setting Script Property: ' + error; | |
htmlPopup(popupTitle, popupMessage); | |
// return false to Parent Function | |
return false; | |
} | |
} | |
/* | |
Function to delete 'Continuation Token' in Script Properties. | |
https://developers.google.com/apps-script/guides/properties | |
*/ | |
function deleteScriptProperties(property) { | |
try { | |
// log message in sheet | |
logEvent('Deleting Script Properties.'); | |
// access Script Properties | |
var scriptProperties = PropertiesService.getScriptProperties(); | |
// delete 'Continuation Token' Property | |
scriptProperties.deleteProperty(property); | |
// log message in sheet | |
logEvent('Successfully deleted Script Properties.'); | |
// return true to Parent Function | |
return true; | |
} | |
catch (error) { | |
// log error in sheet | |
logEvent('Problem deleting Script Property: ' + error); | |
// display user popup to inform of error | |
var popupTitle = 'Delete Script Property error'; | |
var popupMessage = 'Problem deleting Script Property Token: ' + error; | |
htmlPopup(popupTitle, popupMessage); | |
// return false to Parent Function | |
return false; | |
} | |
} | |
/* | |
Reset all data within Google Sheet to be able to start afresh. | |
*/ | |
function reset() { | |
try { | |
// log that reset Function has been selected | |
logEvent("Initiated 'Reset' Function"); | |
// get Spreadsheet UI | |
var ui = SpreadsheetApp.getUi(); | |
// create Alert popup box | |
var result = ui.alert( | |
'Reset spreadsheet', | |
'Do you wish to remove all inputted information to start again?', | |
ui.ButtonSet.OK_CANCEL | |
); | |
// process user response from Popup Box | |
if (result == ui.Button.OK) { | |
// log selection | |
logEvent("Selected 'OK' to reset spreadsheet"); | |
// get current spreadsheet | |
var ss = SpreadsheetApp.getActiveSpreadsheet(); | |
// get Welcome sheet | |
var welcomeSheet = ss.getSheetByName('Welcome'); | |
// clear source folder ID box | |
welcomeSheet.getRange(5, 4).clearContent(); | |
// clear destination folder ID box | |
welcomeSheet.getRange(9, 4).clearContent(); | |
// clear delete select box | |
welcomeSheet.getRange(13, 4).clearContent(); | |
// delete Script Properties of Token | |
deletePropertyFlag = deleteScriptProperties('CONTINUATION_TOKEN'); | |
// log completion of Function | |
logEvent("Completed 'Reset' Function."); | |
// display alert popup to user to confirm completion | |
ui.alert('Successfully cleared all sheet data.'); | |
} | |
else { | |
// User clicked 'No' or 'X' in title bar so do nothing | |
logEvent("Selected 'CANCEL' to reset spreadsheet"); | |
} | |
} | |
catch (error) { | |
// problem clearing sheet data | |
logEvent("Problem with 'reset' Function" + error + error.stack); | |
// run Function to launch HTML popup | |
var popupTitle = "'reset' Function error"; | |
var popupMessage = "Unable to clear sheet data: " + error; | |
htmlPopup(popupTitle, popupMessage); | |
} | |
} | |
/* | |
Function to output messages to the 'Log' sheet. | |
Can be called anywhere else in script. | |
*/ | |
function logEvent(action) { | |
// get the user running the script | |
var theUser = Session.getActiveUser().getEmail(); | |
// get the relevant spreadsheet to output log details | |
var ss = SpreadsheetApp.getActiveSpreadsheet(); | |
var logSheet = ss.getSheetByName('Log'); | |
// create and format a timestamp | |
var dateTime = new Date(); | |
var timeZone = ss.getSpreadsheetTimeZone(); | |
var niceDateTime = Utilities.formatDate(dateTime, timeZone, "dd/MM/yy @ HH:mm:ss"); | |
// create array of data for pasting into log sheet | |
var logData = [niceDateTime, theUser, action]; | |
// append details into next row of log sheet | |
logSheet.appendRow(logData); | |
} | |
/* | |
This Function creates a menu item to run this script. | |
*/ | |
function onOpen() { | |
SpreadsheetApp.getUi() | |
.createMenu('Admin') | |
.addItem('Start now', 'getSpreadsheetData') // label for menu item, name of function to run. | |
.addItem('Reset sheet', 'reset') // label for menu item, name of function to run. | |
.addToUi(); | |
} |
No comments:
Post a Comment