Copying multiple files in Apps Script - google-apps-script

I have a script of which finds all files by name then copies and renames them, I want it to make a copy of multiple files (all of which have the same name) then rename them to the same original name. Currently, it mostly functions as it finds the files by name but it only copies the first file and ignores the rest. My current script is as follows
function copyDocs() {
var file = DriveApp.getFilesByName('PlaceHolder').next();
file.makeCopy('PlaceHolder');
}
I myself am not the most educated on this type of language or any coding language, to be honest, so I'm sorry if this is an easy solution or rookie mistake.

DriveApp.getFilesByName(...) returns a FileIterator. You need to iterate over all of the found files.
function copyDocs()
{
var files = DriveApp.getFilesByName('PlaceHolder');
while(files.hasNext())
{
files.next().makeCopy("PlaceHolder");
}
}

My issue was solved with help from #Cooper and #IMTheNachoMan. I simply needed to use the while(files.hasNext) iterator before placing makeCopy("PlaceHolder"). The end script is as follows
function copyDocs() {
var files = DriveApp.getFilesByName('PlaceHolder');
while (files.hasNext()) {
files.next().makeCopy('PlaceHolder');
}
}

Related

Run a Google Apps Script from another file within the same project

I would like to run the script "xGen_FilterRemove" from my "Generic Macros.gs" file in my "Test" Script located in "Macro.gs" as per below, but am know sure how to correctly reference it?
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
'Generic Macros.gs'.xGen_FilterRemove()
}
};
Just run it like this
function Test() {
var aCurrentBook = SpreadsheetApp.getActive();
xGen_FilterRemove()
}
All of the functions in a project are accessible universally no matter what file they are in. That's why all functions must have a unique name. The files are provided to make it easier to organize them. You can have as many functions in a file as you wish

DriveApp.getFolders() not working as expected

I have a Google Apps Script for Sheets (running as a script under Sheets) that is supposed to return all the folders in my Google drive, however, the following code is stuck in an endless loop and only shows 1 of 3 folders in the drive - over and over again.
//this doesn't work -only shows 1 folder and repeats indefinitely
function getMyFolders() {
var f;
while (DriveApp.getFolders().hasNext()) {
f = DriveApp.getFolders().next();
console.log("f: " +f.getName());
}
}
The code returns the name of only one of my folders (hasNext() is clearly not working or there is a bug in Google Apps Scripts - since the while condition is never false (I ran it for several minutes and it never stopped!)
Could this be a security issue? This is just one of the problems I've run into. The other is that the 3 folders are subfolders of the same parent folder. No sure why getFolders() is not returning just the parent (that would make more sense).
Get All My Files
function getAllMyFiles(folder=DriveApp.getRootFolder()) {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName('Sheet0');
let files = folder.getFiles();
while(files.hasNext()) {
let file = files.next()
sh.appendRow([file.getName(),file.getId()]);
}
let sfldrs = folder.getFolders();
while(sfldrs.hasNext()) {
let sfldr = sfldrs.next();
getAllMyFiles(sfldr)
}
}
Try this as your function instead to return just the parent folders!
function getMyFolders() {
let folders = DriveApp.getFolders();
while (folders.hasNext()) {
console.log("f: " +folders.next().getName());
}
}
If you want child folders as well, you would need to use recursion. Are you familiar with the concept? Happy to update my answer with that information if it's helpful.
Explanation:
You were calling DriveApp.getFolders(); on every iteration, so hasNext() was always retrieving the same item (the first folder in the iterator). Hence, you had an infinite loop.
I rewrote Cooper's answer to get all folders underneath a given parent.
In my case the parent folder is called "Gill".
function getAllMyFolders(folder=DriveApp.getFoldersByName("Gill").next()) {
let sfldrs = folder.getFolders();
while(sfldrs.hasNext()) {
let sfldr = sfldrs.next();
console.log("folder name: " +sfldr.getName());
getAllMyFolders(sfldr)
}
}
Ok - I played around with this code and discovered recursion is completely unnecessary, so here's code that will return all folders at all levels, then you can test the level with getParents()
function getMyFolders() {
let folders = DriveApp.getFolders();
while (folders.hasNext()) {
let folder = folders.next();
Logger.log(folder.getName() + ", Parent Folder: " + folder.getParents().next().getName());
}
}
I had a hunch that using a recursive function was the wrong way to go (and it looks terrible plus adds unnecessary overhead) but was confused why, my first attempt in this question did not work. The answer was the way my code was written - apparently, you need to assign DriveApp.getFolders() to a variable only once. Simply putting it in more than once, seems to reset it. That is, checking for DriveApp.getFolders().hasNext() and followed by DriveApp.getFolders().next() will cause the endless loop! Lesson here: assign it to a variable and then check for hasNext as calling it changes the state of the iterator which is reset again if getFolders is called again.
That was my actual bug here.

How to retrieve list of shareable links?

For the following, all files are in Google Sheet format.
My problem :
My work environment is structured as follows (folders / sub-folders):
RACINE :
--> ID4522
--> ID7852
--> ID5864
--> ....
The tracking file "FOLLOW_UP" is located in the RACINE folder. The ID4522, ID7852 and ID5864 folders are subfolders of the RACINE folder.
We find in the ID4522 sub-folder the following Google sheet file "Entry_Form_ID4522", in the ID7852 sub-folder the following Google sheet file "Entry_Form_ID7852",…
Important clarification: The number of sub-files (of the form "IDxxxx") can vary at any time without warning.
My wish
Most likely via a macro in javascript, retrieve in the tracking file ("FOLLOW_UP") from cell B3 and down, the list of shareable links for each of the files "Entry_Form_IDxxxx". List of subfolders can change at any time (for example when my client adds a folder with the associated "Entry_Form_IDxxxx" file).
Thank you by advance.
Regards.
Jérôme
Recurse Folders for File URLs
I'm assuming that you have only one folder named RACINE and there is only one file in it that is named "FOLLOW UP" and that the sheet where your data is located in is the left most sheet. Assuming as that is true then this function will extract the urls out of =HYPERLINK() cells found in column B from row 3 on down to the end of the sheet.
Originally I jumped to the conclusion that there we're links in column B and that you wanted a list of URL's so this function is probably not want you want. Please let me know and I'll remove it.
function getLinks() {
const folder=DriveApp.getFoldersByName("RACINE").next();//assumes only one folder with that name
const file=folder.getFilesByName("FOLLOW UP").next();//assumes only one file by that name
const ss=SpreadsheetApp.openById(file.getId());
const sh=ss.getSheets()[0];//the most left sheet in the spreadsheet
const vs=sh.getRange(3,2,sh.getLastRow()-2,1).getFormulas();
var links=[];
vs.forEach(function(r,i){
let v=r[0].match(/([^"]+)/g);
links.push(v[1]);
})
var ui=HtmlService.createHtmlOutput(links.join('<br />'));
SpreadsheetApp.getUi().showModelessDialog(ui, "URLS");
return links;//as an array
}
After jumping to conclusions
I think this is what you really want and that is a list of urls to the files in your working subdirectories that meet a certain naming format as explained in the following paragraphs. I tested this on some data I generated with the last two functions in this answer. After fixing a couple of typos it ran very nice and producted url in the correct location.
The first function is basically the starter. It assumes that you have only one folder named "RACINE" on your google drive. It calls a recursive function which essentially crawls through all of the RACINE's subfolders looking for files of the form "Entry_Form_IDxxxx" where xxxx are all numbers between 0-9. When it finds a file name like that it loads that url into the next empty cell at the bottom of columnB in sheet[0] of "FOLLOW UP". It also searches for subdirectories of the form "IDxxxx" where xxxx are all numbers from 0-9. When it finds those subfolders in recurses into them by calling getFnF() from inside of getFnF(). It can get hard to follow this process so if your new to it you may very well want to hire some to help you.
function getFileUrlsIntoB3() {
const folder=DriveApp.getFoldersByName("RACINE").next();//assumes only one folder with that name
getFnF(folder);
}
The following function gets the Id for the follow up file into the recursive function so that data can be written into the most left hand sheet of file "FOLLOW UP". It uses the cache service so that all calls after the first happen considerably faster since the file id is taken directly from cache. The cache will hold this value for upto 3 minutes but you can adjust for more if you wish.
function getFollowUpId() {
const cs=CacheService.getScriptCache();
const cached=cs.get('FOLLOW UP ID');
if(cached) {
return cached;
}else{
let folder=DriveApp.getFoldersByName("RACINE").next();
let file=folder.getFilesByName("FOLLOW UP").next();
cs.put('FOLLOW UP ID',file.getId(),180);//3 minute cache time
return file.getId();
}
}
And this is the recursive function. Which just means that it's a function that calls itself.
function getFnF(folder) {
const ss=SpreadsheetApp.openById(getFollowUpId());
var sh=ss.getSheets()[0];
var files=folder.getFiles();
while(files.hasNext()) {
var file=files.next();
if(file.getName().match(/^Entry_Form_ID\d{4}/)) {
sh.getRange(getColumnHeight(2,sh,ss)+1,2).setValue(file.getUrl());
}
}
var subfolders=folder.getFolders()
while(subfolders.hasNext()) {
var subfolder=subfolders.next();
if(subfolder.getName().match(/^ID\d{4}/)) {
getFnF(subfolder);
}
}
}
And finally this is the function that calculates the current height of columnB in Sheet[0] of spreadsheet "FOLLOW UP"
function getColumnHeight(col,sh,ss){
var ss=ss||SpreadsheetApp.getActive();
var sh=sh||ss.getActiveSheet();
var col=col||sh.getActiveCell().getColumn();
var v=sh.getRange(1,col,sh.getLastRow(),1).getValues().map(function(r){return r[0];});
var s=0;
var h=0;
v.forEach(function(e,i){if(e==''){s++;}else{s=0;}h++;});
return (h-s);
}
TESTING
I used the following two functions to create some folders and files and found that testing the code was a breeze. It had only 3 typo type failures and then ran just fine. It's just creating ascii text files not spreadsheets but they're files none the less.
function generateFoldersAndFiles() {
const folder=DriveApp.getFolderById('RACINE FolderId');
for(let i=0;i<4;i++) {
let subfldr=folder.createFolder(getStrings("ID",4));
for(let j=0;j<4;j++) {
subfldr.createFile(getStrings("Entry_Form_ID",4),"Text Content");
}
}
}
function getStrings(prefix,length) {
const nA=[0,1,2,3,4,5,6,7,8,9];
var s=prefix;
for(let i=0;i<length;i++) {
s+=nA[Math.floor(Math.random()*nA.length)];
}
return s;
}
File Structure:
Yes there is only one folder named RACINE and one file named "FOLLOW_UP". I do not understand by the way that there can be several folders and several files with the same name? I remind you that I am not a developer.
The names of subfolders (of type IDxxxx) are also unique. I specify that the name of the sub-folders can also take the form IDxxxx_x (for example ID4522_1).
The IDxxxx and IDxxxx_x subfolders are all located in the RACINE folder.
There is an Entry_Form_IDxxxx (or Entry_Form_IDxxxx_x) file in each IDxxxx (and IDxxxx_x) sub-folder. There is only one Entry_Form_IDxxxx and Entry_Form_IDxxxx_x file.
For example in the ID4522 sub-folder there is the file "Entry_Form_ID4522" (there is only one file "Entry_Form_ID4522").
For example in the ID4522_1 sub-folder there is the file "Entry_Form_ID4522_1" (there is only one file "Entry_Form_ID4522_1").
In each IDxxxx or IDxxxx_x folder there is only one Entry_Form_... file.
I would like to put screenshots to better explain my problem but I do not know how to do it?
The file in which I would like to retrieve the results (that is, the shareable links of the "Entry_Form_IDxxxx" files) is called "FOLLOW_UP" ("feuille 1" tab).
In the "FOLLOW_UP" file ("feuile 1" tab), here is what I would like to obtain:
For example in cell B3:
https://docs.google.com/spreadsheets/d/1VUf_t1gkv2lddfgsfgds7UhJeunrNzo2-QGcRe24/edit?usp=sharing
This link would correspond to the link of the first file found, for example "Entry_Form_ID4522"
In cell B4:
https://docs.google.com/spreadsheets/d/1VUf_jkghkdxjfghgjgjzo2-QGcRe24/edit?usp=sharing
This link would correspond to the link of the second file found, for example "Entry_Form_ID7852"

Google Apps Scripts: Insert additional text into a text file

I'm trying to develop a method for adding Google Tasks to my running task list named todo.txt and is housed in Google Drive. I've found a way to grab the tasks. Of course, the data will nee to be manipulated as well (e.g., concatenate date formats to conform to todo.txt rules) but I'll deal with that next.
The question I have is how to insert them to the top of the todo.txt without overwriting the existing text contained within it. I have read in other circumstances, that I may need to read the current text out, add the new info and then write the whole thing back as an overwrite. Is this necessary? And, if so, how do I do it?
Below is the code I've written thus far, substituting logger.log for the destination file since I don't know how to do it. I;m not a programmer, so polease forgive any ignorance here. Thanks.
function listTasks(taskListId) {
var taskListId = '12345'; //this is the ID of my Google Tasks (the source data)
var name2="Text Journals"; //this is the folder in which my todo.text resides
var name="todo.txt"; //this is the name of my todo file (the destination)
var dir = DriveApp.getFoldersByName(name2).next()
var tasks = Tasks.Tasks.list(taskListId);
if (tasks.items) {
for (var i = 0; i < tasks.items.length; i++) {
var task = tasks.items[i];
Logger.log('Task with title "%s" and dute: "%s" and notes "%s" and status "%s" was found.',
task.title, task.due, task.notes, task.status);
}
} else {
Logger.log('No tasks found.');
}
}

How to delete/overwrite CSV file using google apps script?

My google apps script imports a csv file [test.csv] into a new tab, manipulates some values, then saves the manipulated tab as a csv file [test.csv]. When it saves the new version, it simply makes another copy [test(1).csv]. I wish instead to overwrite the previous file (or delete the old one then export/write the new version.) Help please?
I am using reference code from the Interacting With Docs List Tutorial
I know this is an old question, but much of the information in the accepted answer has been deprecated by Google since then. DocsList is gone, as are the clear() and append() methods on a file object.
I use the following function to create or overwrite a file:
// Creates or replaces an existing file
function updateFile (folder, filename, data) {
try {
// filename is unique, so we can get first element of iterator
var file = folder.getFilesByName(filename).next()
file.setContent(data)
} catch(e) {
folder.createFile(filename, data)
}
}
For reference, here's some code for doing the same for a folder. It assumes we're operating in the parent folder of the current sheet and want a folder
object for a new or existing folder there.
// Get folder under current folder. Create it if it does not exist
function getOrCreateFolder(csvFolderName) {
var thisFileId = SpreadsheetApp.getActive().getId();
var thisFile = DriveApp.getFileById(thisFileId);
var parentFolder = thisFile.getParents().next();
try {
// csvFolderName is unique, so we can get first element of iterator
var folder = parentFolder.getFoldersByName(csvFolderName).next();
// asking for the folder's name ensures we get an error if the
// folder doesn't exist. I've found I don't necessarily catch
// an error from getFoldersByName if csvFolderName doesn't exist.
fname = folder.getName()
} catch(e) {
var folder = parentFolder.createFolder(csvFolderName);
}
return folder
}
You could do DocsList.find(fileName) which gives you a list of files that have that name. If file names are unique, then you can just do var file = DocsList.find(fileName)[0].
If you are a Google Apps user, you can use file.clear() to remove all the contents of the old file, and then file.append() to insert all of the new contents.
Otherwise, you will have to file.setTrashed(true) and then DocsList.createFile() to make the new one.