Moving Files In Google Drive Using Google Script - google-apps-script

I'm trying to create documents using information posted through Google forms, then once the document is created I would like to move the document into a shared folder for people to view.
At the moment I have the script taking all of the information from the Google Forms linked spreadsheet.
Using that information I'm using the following code to create the document:
var targetFolder = DriveApp.getFolderById(TARGET_FOLDER_ID);
var newDoc = DocumentApp.create(requestID + " - " + requestSummary);
This is creating the document successfully in my Google Drive root folder, but I can't seem to move it where I want to move it to.
I've seen a lot of posts suggesting use stuff like targetFolder.addFile(newDoc) but that doesn't work, similarly I've seen examples like newDoc.addToFolder(targetFolder) but again this isn't working for me.
It seems that all the online questions people have already asked about this are using the previous API versions that are no longer applicable and these methods do not apply to the new DriveApp functionality.
What I would like, if possible, is to create the new document as above so that I can edit the contents using the script, then be able to move that file to a shared folder. (From what I understand there is no 'move' function at present, so making a copy and deleting the old one will suffice).

If we make a copy of the file and trash the original, it would change the file URL and also the file sharing settings won't be preserved.
In Drive, it is possible to add a file to multiple folders with the .addFolder() method of DriveApp service. You can add the file to the target folder and then remove the file from the immediate parent folder.
function moveFiles(sourceFileId, targetFolderId) {
var file = DriveApp.getFileById(sourceFileId);
var folder = DriveApp.getFolderById(targetFolderId);
file.moveTo(folder);
}

This is my first post! I know this has been answered a few times, but I actually came across this question while working on my project, and while reviewing the Apps Script documentation, I figured out a concise way to do it. A variation of some1's answer.
var file = DriveApp.getFileById(fileid);
DriveApp.getFolderById(folderid).addFile(file);
DriveApp.getRootFolder().removeFile(file);
Hope it helps!

There is no direct method in the File or Folder Classes to move files from one folder in Google Drive to another. As you mentioned you can copy the file to another folder with the method makeCopy() and then delete it with setTrashed(), the code should look like this:
var targetFolder = DriveApp.getFolderById(TARGET_FOLDER_ID);
var newDoc = DocumentApp.create(requestID + " - " + requestSummary); // Creates the Document in the user's Drive root folder
// Modify the new document here, example:
// var body = newDoc.getBody();
// body.appendParagraph("A paragraph.");
// newDoc.saveAndClose();
var driveFile = DriveApp.getFileById(newDoc.getId()); // Gets the drive File
driveFile.makeCopy(newDoc.getName(), targetFolder); // Create a copy of the newDoc in the shared folder
driveFile.setTrashed(true); // sets the file in the trash of the user's Drive
EDIT:
In a second thought and taking into account Ruben's comments. I agree that it is a better practice to implement Amit's answer.

It looks like there is now a moveTo() function with the Drive API (advanced services) that makes it easy to move files:
moveTo(destination)
Moves this item to the provided destination folder.
The current user must be the owner of the file or have at least edit
access to the item's current parent folder in order to move the item
to the destination folder.
Here is some code I used to move all files from the "screenshot input" folder to the "screenshot processed" folder:
var inputFolder = DriveApp.getFolderById(SCREENSHOT_INPUT_FOLDER_ID);
var processedFolder = DriveApp.getFolderById(SCREENSHOT_PROCESSED_FOLDER_ID);
var files = inputFolder.getFiles();
while (files.hasNext()) {
var file = files.next();
file.moveTo(processedFolder);
}

A bit safer approach compared to the previous ones:
If you remove link to the file first, then you will not be able to addFile.
If file is already located in the target folder, then the approach provided by Amit (https://stackoverflow.com/a/38810986/11912486) only removes file.
So, I suggest to use the following approach:
function move_file(file_id, target_folder_id) {
var source_file = DriveApp.getFileById(file_id);
var source_folder = source_file.getParents().next();
if (source_folder.getId() != target_folder_id) {
DriveApp.getFolderById(target_folder_id).addFile(source_file);
source_folder.removeFile(source_file);
}
}
can be improved by:
javascript camel style
multiple locations validation

Use File.moveTo(destination).
var newFileId = newDoc.getId();
var newFile = DriveApp.getFileById(newFileId);
newFile.moveTo(targetFolder);

Try this:
var file = DriveApp.getFileById(newDoc.getId());
targetFolder.addFile(file);
//DriveApp.getFolderById('root').removeFile(file); // remove from root

This question has been answered, but here is a slightly different configuration:
function moveFile(parameterObject) {
var currentFolderID,file,fileToMoveID,sourceFolder,targetFolder,targetFolderID;
fileToMoveID = parameterObject.fileToMoveID;
currentFolderID = parameterObject.currentFolderID;
targetFolderID = parameterObject.targetFolderID;
file = DriveApp.getFileById(fileToMoveID);//Get the file to move
if (!file) {
functionToHandleThisKindOfThing("there is no file");
return;
}
if (currentFolderID) {//The folder ID holding the current file was passed in
sourceFolder = DriveApp.getFolderById(currentFolderID);
} else {//No ID for the current folder
sourceFolder = file.getParents();
if (sourceFolder) {
if (sourceFolder.hasNext()) {
sourceFolder = sourceFolder.next();
}
}
}
targetFolder = DriveApp.getFolderById(targetFolderID);
targetFolder.addFile(file);
sourceFolder.removeFile(file);
}
function testCode() {
var o;
o = {
'fileToMoveID':"File ID of file to Move",
"targetFolderID":"ID of folder to Move to"
}
moveFile(o);
}

The script transfers all your personal files to a shared disk (Team drive). Saves the folder structure.
DRIVE_FOLDER_ID = '111aaa'; // Folder ID on the shared drive
function start() {
var files = DriveApp.searchFiles('"me" in owners');
while (files.hasNext()) {
var file = files.next();
newPath = fileMoveWithPath(file, DRIVE_FOLDER_ID);
console.info("New path: ", getFullPath(newPath));
}
}
function fileMoveWithPath(file, root) {
var folders = [],
parent = file.getParents();
// Проходим по иерархии папок текущего файла до корня
while (parent.hasNext()) {
parent = parent.next();
folders.push(parent);
parent = parent.getParents();
}
console.info("Old path: ", getFullPath(file));
if (folders.length > 0)
targetPath = makeNewPath(folders, DriveApp.getFolderById(root));
else
targetPath = DriveApp.getFolderById(root);
if (targetPath) {
targetFile = file.moveTo(targetPath);
return targetFile;
};
return;
}
function makeNewPath(folders, newroot) {
var f = folders.pop();
var query = "'" + newroot.getId() + "' in parents and title = '" + f.getName() + "' and mimeType='application/vnd.google-apps.folder' "
var targetFolder = DriveApp.searchFolders(query);
if (targetFolder.hasNext())
targetFolder = targetFolder.next()
else
targetFolder = newroot.createFolder(f.getName());
if (folders.length > 0)
return makeNewPath(folders, targetFolder)
else
return targetFolder;
}
function getFullPath(file) {
var folders = [],
parent = file.getParents();
while (parent.hasNext()) {
parent = parent.next();
folders.push(parent.getName());
parent = parent.getParents();
}
if (folders.length) {
return '> /' + folders.reverse().join("/") + '/' + file.getName();
}
return '> /' + file.getName();
}

Related

Move all files from one folder

I'm super bad in apps script in Google drive ... I'm upset.
I need your help.
I have folders like this in my root drive :
students
__kev
___math
___english
__donald
___math
___english
__tony
___math
___english
transfer
__math
I want to automate an operation: copy or move all the contents of the "transfer" folder to each student folder.
I can do this task easily on PowerShell or bash with a sync client but I need it directly on drive now.
Any idea ? I want to add a menu on a file with the tool script.
The algorithm is quite simply, but there is one trick. You need to decide what should the script do in case a destination folder already has a file with the same name. By default Google Drive allows to have many files with the same name within a folder, which can be confusing.
My script checks a target folder and removes a file with the same name (it's supposed that there is one file) before copying.
function main() {
const root_folder_ID = "###"; // here is ID of your folder
const root_folder = DriveApp.getFolderById(root_folder_ID);
const transfer_folder = root_folder.getFoldersByName("transfer").next(); // transfer folder
// get array of subjects from transfer folder
const subjects_generator = transfer_folder.getFolders();
var subjects = [];
while (subjects_generator.hasNext()) {
var subj = subjects_generator.next();
subjects.push(subj);
}
// get students folders (a generator)
const students_folder = root_folder.getFoldersByName("students").next();
const students = students_folder.getFolders();
// copy all subject folders (with content) into students folders
while(students.hasNext()) {
var student = students.next();
for (var s in subjects) {
Logger.log("folder '" + subjects[s] + "' started to copy to '" + student + "'");
copy_folder(subjects[s], student);
Logger.log("---");
}
}
}
function copy_folder(source_folder, target_folder) {
// pick or create subject subfolder within target folder
try {
var subfolder = target_folder.getFoldersByName(source_folder.getName()).next();
} catch(e) {
var subfolder = target_folder.createFolder(source_folder.getName());
}
// get all files from source folder
var files = source_folder.getFiles();
// copy the files into the subfolder
while(files.hasNext()) {
var file = files.next();
var file_name = file.getName();
// try to remove old file with the same name in the target folder
try {
var old_file = subfolder.getFilesByName(file_name).next();
old_file.setTrashed(true);
Logger.log("old file '" + old_file + "' was removed")
} catch(e) {}
file.makeCopy(file.getName(), subfolder);
Logger.log("file '" + file + "' is copied")
}
}
To run this script you have to create a new document somewhere on Google Drive, go the menu 'Tools' and click on 'Script Editor'.
Then you need to paste the text of the script into the editor and click the 'Run' icon:
But first you need to paste your folder ID into the second line of the script of course.
Alternatively you can make a custom menu in your document and run the script from there, without Script editor.
To make a custom menu just add this function at the end (or start) of the script in Script editor:
// custom menu
function onOpen() {
DocumentApp.getUi().createMenu('Scripts')
.addItem('📁 Copy files', 'main')
.addToUi();
}
And you'll get the custom menu 'Script' after reload your document:
This should fit your needs. Adapted from: this site. Change the id's of the two folders.
function start() {
var transfertFolder = DriveApp.getFolderById("xxxxxxx-YhSCZXh1PDeqpE8mXjHHJJF");
var studentFolder = DriveApp.getFolderById("xxxxxxx_cQgd3DZV9Ukt4yI_-jTS_86Z").getFolders();
while (studentFolder.hasNext()){
const student = studentFolder.next()
copyFolder(transfertFolder,student)
}
}
function copyFolder(source, target) {
var folders = source.getFolders();
var files = source.getFiles();
while (files.hasNext()) {
var file = files.next();
file.makeCopy(file.getName(), target);
}
while (folders.hasNext()) {
var subFolder = folders.next();
var folderName = subFolder.getName();
var targetFolder = target.createFolder(folderName);
copyFolder(subFolder, targetFolder);
}
}

Copy files from one folder to another on Drive with Apps Script

Team members upload content (regardless of file type) into a folder on Drive. I need to copy this content into another folder automatically with a trigger, and be able to move it around from there.
I cannot use a "MoveFile" function as I am not the owner of the original content.
I have already tried to copy files automatically into the destination folder, and this works, using the code below:
function CopyFiles() {
var srcFldr = DriveApp.getFolderById("***ID***");
var srcFiles = srcFldr.getFiles();
var desFldr = DriveApp.getFolderById("***ID***");
var desFiles = desFldr.getFiles();
var dfnA = [];
while (desFiles.hasNext()) {
var df = desFiles.next();
dfnA.push(df.getName());
}
while (srcFiles.hasNext()) {
var sf = srcFiles.next();
if (dfnA.indexOf(sf.getName()) == -1) {
sf.makeCopy(sf.getName(), desFldr);
}
}
}
However, I need to move this copied content into other files throughout the day, yet every time I do, the same file gets copied back into the destination folder above with the new trigger, creating a permanent loop.
Is there a way of either:
moving the files from the original source folder despite not being the owner of those files?
copying contents only once, upon upload or modification?
Or 3) another, better, smarter way of doing this?
Thanks for your help!
I'd suggest the following workflow:
For every file that is copied to the destination folder, store the fileId. You could use Properties Service for this.
When copying files from one folder to the other, check the fileId has not been stored before.
Code snippet:
function CopyFiles() {
var srcFldr = DriveApp.getFolderById("***ID***");
var srcFiles = srcFldr.getFiles();
var desFldr = DriveApp.getFolderById("***ID***");
var desFiles = desFldr.getFiles();
var dfnA = [];
var key = "fileIDs";
var scriptProperties = PropertiesService.getScriptProperties();
var property = scriptProperties.getProperty(key); // Retrieve fileIDs property
// Get array of fileId, or empty array if no file has been copied before:
var arrayIDs = property ? JSON.parse(property) : [];
while (desFiles.hasNext()) {
var df = desFiles.next();
dfnA.push(df.getName());
}
while (srcFiles.hasNext()) {
var sf = srcFiles.next();
// Check not only file name, but also whether fileId has been stored before:
if (dfnA.indexOf(sf.getName()) == -1 && arrayIDs.indexOf(sf.getId()) == - 1) {
sf.makeCopy(sf.getName(), desFldr);
arrayIDs.push(sf.getId()); // Add fileId to array of IDs
}
}
scriptProperties.setProperty(key, JSON.stringify(arrayIDs)); // Store updated array
}
Reference:
Properties.getProperty(key)
Properties.setProperty(key, value)

Google Apps Script - Impossible operation with an element inside a shared drive

I need your help on something.
I did a function which goal is to make a copy of a template file and put it in a folder in a shared drive. The problem is that the program returns :
"Exception: This operation cannot be performed on an item in a shared Drive"
Yet, I have all the permissions in that shared drive so I don't understand.
I did several tweaks and I've founded that removeFile and addFile are parts of the problem. If I don't run them, the folder is created and the copy is made. But I still need the file to be moved.
I hope someone can help me with this.
PS : You can fin my code below.
function makeCopyFile(folderID, fileID, newName) {
var getFolder = DriveApp.getFolderById(folderID);
var file = DriveApp.getFileById(fileID);
var copyFile = file.makeCopy();
var newFile = copyFile.setName(newName);
// Remove the file from all parent folders
var parents = newFile.getParents();
while (parents.hasNext()) {
var parent = parents.next();
parent.removeFile(newFile);
}
getFolder.addFile(newFile);
};
The problem is that you are trying to delete a file on a shared drive with DriveApp
It will work if you do it instead with the Advanced Drive service where you can specify "supportsAllDrives": true
So, after enabling the Advanced Service: replace
var parents = newFile.getParents();
while (parents.hasNext()) {
var parent = parents.next();
parent.removeFile(newFile);
}
through
var id = copyFile.getId();
// or alternatively `var id = newFile.getId();` - because it is the same
Drive.Files.remove(id,{"supportsAllDrives": true})
As for getFolder.addFile(newFile);
Once you remove a file, you cannot add it back anymore. Also, I do not understand your motivation for it - copyFile.setName(newName); is enough to rename the file - you do not need to delete the file with the old name and insert the one with the new name.
UPDATE
If your goal is to copy a file to a team drive folder, you can do it easily as following with the Drive API:
function makeCopyFile(folderID, fileID, newName){
var resource = {
"parents": [
{
"id": folderID
}
],
"title": newName
}
Drive.Files.copy(resource, fileID, {"supportsAllDrives": true})
}

Google Apps - How to get all files name from current directory?

How I can get all files name from current directory?
I have a code
function showAllFollderFronRoot() {
// get all files from the ROOT folder
var files = parentFolder;
while (files.hasNext()) {
var file = files.next();
// Logger.log(file.getName());
DocumentApp.getUi().alert(file.getName());
}
}
But it work only with ROOT dir.
How I can get all file names in array in current dir?
UPDATE:
I have a file structure: MT/MT.100-107/MT.100-1007.1001.doc
I need make code, if somebody open New Template from Docs - script need automatically safe this file with true structure - with next filename + 1 (example MT.100-1007.1002.doc, next new file from Template - MT.100-1007.1003.doc ...)
Script need to find all filenames => show last bigger count (1002.doc) => count + 1 => save this file with new filename MT.100-1007.1003.doc
My code work, but it make tmp file in Root dir & not work perfect, because it not calculate last bigger count in current dir and if I delete file, example MT.100-1007.1003.doc in dir MT, and make new file in dir UA - count be MT.100-1007.1004.doc no matter what the names of the files are in the folder UA.
These script with mistakes, how I can fix it?
/**
* #OnlyCurrentDoc
*/
function saveFilename() {
// Get current file name
const ui = DocumentApp.getUi(),
doc = DocumentApp.getActiveDocument(), //Added
thisFileId = doc.getId(),
thisFileName = doc.getName();
const thisFile = DriveApp.getFileById(thisFileId);//Modified from getFolderById
const parentFolder = thisFile.getParents();
const currentFolder = parentFolder.next();//Modified from currentFolderName
const currentFolderName = currentFolder.getName();//Added
//ui.alert(currentFolderName);
/*Store a init file in root to getLatestFileNumber*/
var initIter = DriveApp.getFilesByName(currentFolderName + 'init00'),
initBool = initIter.hasNext(),
init;
if (!initBool) {
init = DriveApp.createFile(currentFolderName + 'init000', '0');
} else {
init = initIter.next();
}
/*Get current Number and format it to 4 digits*/
var currentNum = init.getBlob().getDataAsString() * 1 + 1,
formatNum = ('0000' + currentNum).substr(-3);
/*If filename already contains folderName, do nothing*/
if (!(thisFileName.search(currentFolderName) + 1)) {
doc.setName(currentFolderName +'.' + formatNum).saveAndClose();
init.setContent(currentNum);
}
// delete TMP file from ROOT dir
DriveApp.getFileById(init.getId()).setTrashed(true)
}
You want to retrieve filenames of all files in the parent folder of the active document.
If my understanding is correct, how about this answer?
Flow:
The flow of this script is as follows.
Retrieve file ID of the active document.
Retrieve parent folder ID of the active document.
Retrieve files in the parent folder ID.
Retrieve filenames of files.
Modified script:
function showAllFollderFronRoot() {
var fileId = DocumentApp.getActiveDocument().getId();
var parentFolderId = DriveApp.getFileById(fileId).getParents().next().getId();
var files = DriveApp.getFolderById(parentFolderId).getFiles();
while (files.hasNext()) {
var file = files.next();
Logger.log(file.getName())
}
}
Note:
This sample script supposes as follows.
The parent of the active document is only one.
All files in the parent folder of the active document are retrieved. But the folders are not retrieved.
References:
getId()
getParents()
getFolderById(id)
getFiles()
If I misunderstand your question, please tell me. I would like to modify it.
I fix this problem, but idk how to parse last 4 digits in filename and find MAX from it. Do you have any idea? method slice(4) not working in apps script :(
function currFiles() {
const ui = DocumentApp.getUi(),
doc = DocumentApp.getActiveDocument(),
thisFileId = doc.getId(),
thisFileName = doc.getName();
const thisFile = DriveApp.getFileById(thisFileId);
const parentFolder = thisFile.getParents();
const currentFolder = parentFolder.next();
const currentFolderName = currentFolder.getName();
const currentFolderId = currentFolder.getId();
// get all files in currentFolder
var folderId = currentFolderId;
// Log the name of every file in the folder.
var files = DriveApp.getFolderById(folderId).getFiles();
while (files.hasNext()) {
var file = files.next();
Logger.log(file.getName());
}
}

How do I locate the path of the folder in which the current script file is located?

How do I locate the path of the current folder? I just want to be able to get the path of the folder so that I can manipulate the files in the folder without typing the path into the scripts.
For a Spreadsheet I found this to work:
thisFileId = SpreadsheetApp.getActive().getId();
var thisFile = DriveApp.getFileById(thisFileId);
var parentFolder = thisFile.getParents()[0].getName();
Thanks to Corey's answer and to Thomas'one, here is a "full featured" version that shows the folder tree in the logger and every parents id as well... just for fun ;-)
function getScriptFolderTree() {
var thisScript = getThisScriptInDrive();
var names = []
var Ids = []
var folder = thisScript.getParents()[0];
while (folder.getName() != "Root"){
names.unshift(folder.getName());
Ids.unshift(folder.getId());
var parents = folder.getParents();
var folder = parents[0];
}
Logger.log('Root/'+names.join().replace(/,/g,'/'))
Ids.unshift(DriveApp.getRootFolder().getId())
Logger.log(Ids)
}
function getThisScriptInDrive() {
return DriveApp.getFileById("poiuytrezazertyujhgfdsdcvcxyydryfhchfh");
}
(ID's are truncated intentionally)
Note that this script is working nicely but it strangely stops working if the 'random string' is modified... I imagine that the search engine in drive doesn't like the change but I have no serious explanation (comments welcome) but after a few minutes it works again ;-)
Add a function like this to your script
function getThisScriptInDrive() {
return DriveApp.find("some unique string that wont be anywhere else")[0];
}
This will search Drive and find this script itself because it contains that string - right there in the function call! - no need to declare it anywhere else. As long as you use an obscure enough string - i'd recommend mashing a few hundred chars on your keyboard - it will be unique across drive and therefore just work.
Once you have a File for this script, you can call getParents() etc.
I tweaked Serge's code to write a generic function
function getThisFilePath() {
//let thisFile = DriveApp.getFileById(DocumentApp.getActiveDocument().getId()); // for a Doc
let thisFile = DriveApp.getFileById(SpreadsheetApp.getActiveSpreadsheet().getId()); // for a Sheet
let path = {name: '', folder: []};
let folder = thisFile.getParents().hasNext() ? thisFile.getParents().next() : undefined;
while (folder) {
path.folder.unshift({name: folder.getName(), id: folder.getId()});
folder = folder.getParents().hasNext() ? folder.getParents().next() : undefined;
}
path.folder.forEach(folder => path.name += '/' + folder.name);
Logger.log(path.name);
return path;
}
Logging output
11:02:57 AM Info /My Drive/Technology/Development/2022
You could do this:
function myFunction() {
var thisScript = getThisScriptInDrive();
var folder = thisScript.getParents()[0];
while (folder.getName() != "Root"){
var parents = folder.getParents();
for (var i in parents){
var folder = parents[i];
Logger.log(folder.getName());
}
}
}
function getThisScriptInDrive() {
return DocsList.find("`<jj!?=(<DW+.W/m7SBF:sgu/#B(&Cs3:{ajA~ys#KmN4&]ujhpZ~z[Tv?+dk}MpK,8pY=w&dny8N'74:.9H:~uCgY=7pRt4[Tn5")[0];
}
This only works good if the folder has only 1 parent because it only takes 1 path.
edit: Thanks to Corey G
I was able to get the folder containing the script that was running:
//
// PrintScriptFolder -- print the name of the folder in which the running script resides.
//
function PrintScriptFolder()
{
var scriptId = ScriptApp.getScriptId();
console.info('scriptId = ' + scriptId);
var file = DriveApp.getFileById(scriptId);
var folders = file.getParents();
if (folders.hasNext())
{
var folder = folders.next();
var name = folder.getName();
console.info('script folder name = ' + name);
}
}
The trick is using ScriptApp to get the ID of the running script. From there it's pretty straightforward: use DriveApp to get the file ID, getParents() to get a list of (one) parent folder, next() to get it, and getName() to get its name.
First answer, long time lurker (be kind ;-)
Looking for a simple getPath, I went for a more generic approach, with limitations:
//simple test stub
function test_getFilePath() {
const scriptId = ScriptApp.getScriptId();
const fullPath = getFilePath(scriptId);
console.log(fullPath);
}
/**
* getFilePath
* #fileId id of file to find path for
*
* NB: google allows for multiple parents, which is a PITA
* so I'm just taking the first one I find up the tree
*/
function getFilePath(fileId) {
const file = DriveApp.getFileById(fileId);
const rootFolder = DriveApp.getRootFolder()
const rootFolderId = rootFolder.getId();
const rootFolderName = rootFolder.getName();
let folder = file.getParents().next();
let folderNames = [];
while (folder.getId() !== rootFolderId){
folderNames.unshift(folder.getName());
folder = folder.getParents().next();
}
const fullPath = rootFolderName+'/'+folderNames.join().replace(/,/g,'/');
return fullPath;
}
console:
11:36:40 AM Info My Drive/GAS
Had I more time/inclination, I'd have writen a recursive function to capture and return an array of possible parent paths, but there's no use case for me and who the hell wants to keep a the same file in multiple folders anyway??
(I'm sure there are use cases, eg, equivalent to using symlinks, but it stil makes me feel dirty ;-)