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)
Related
On the 19th January 2022, the CellImageBuilder class was added to the Google Sheets Spreadsheet service.
This class now allows you to import an image into a cell in Google Sheets, previously you could only add an image above the cell.
Ive been trying to use this new class to take a URL link from a Google sheet, and then create an image in the cell next to the URL. This works perfectly fine if I can hard code the URL into the script (example below)
**function insertImageIntoCell()**
{
var sheet = SpreadsheetApp.getActiveSheet();
var url='Google_Docs_Image_URL'
let image = SpreadsheetApp.newCellImage().setSourceUrl(url).setAltTextDescription('TestImage').toBuilder().build();
SpreadsheetApp.getActive().getActiveSheet().getRange('C2').setValue(image);
}
The problem I am having is that once I create an array to iterate through the column the below script creates a valid array and posts it into the correct column and rows, but when it posts it back into the spreadsheet it only returns the URL and does not convert it into an image in a cell
**function insertImageIntoCell()**
{
var sheet = SpreadsheetApp.getActiveSheet();
var myStringArray = sheet.getRange('B2:B10');
var myStringArray = sheet.getRange('B2:B10').getValues();
//Logger.log(myStringArray)
let image = SpreadsheetApp.newCellImage().setSourceUrl(myStringArray).setAltTextDescription('test').toBuilder().build();
SpreadsheetApp.getActive().getActiveSheet().getRange('C2:C10').setValues(myStringArray);
}
Im using the followign code to create the initial table of data, this pull the file name and DownloadURL from a Google Drive location and then saves this into a sheet
/* modified from #hubgit and http://stackoverflow.com/questions/30328636/google-apps-script-count-files-in-folder
for this stackexchange question http://webapps.stackexchange.com/questions/86081/insert-image-from-google-drive-into-google-sheets by #twoodwar
*/
function listFilesInFolder(folderName) {
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(["Name","URL","Image"]);
//change the folder ID below to reflect your folder's ID (look in the URL when you're in your folder)
var folder = DriveApp.getFolderById("Google_Drive_Folder");
var contents = folder.getFiles();
let image=[];
var cnt = 0;
var file;
while (contents.hasNext()) {
var file = contents.next();
cnt++;
data = [
file.getName(),
file.getDownloadUrl(),
];
sheet.appendRow(data);
};
};
I am looking for the script to refresh the file information from Google Drive into sheets, then to save the image into a cell, it now appears that this functionality exists, but Im not able to get it to take an array of URL's
Suggestion
Perhaps you can try this sample implementation below.
Sample Tweaked Script
function listFilesInFolder(folderName){
var sheet = SpreadsheetApp.getActiveSheet();
sheet.appendRow(["Name","URL","Image"]);
//change the folder ID below to reflect your folder's ID (look in the URL when you're in your folder)
var folder = DriveApp.getFolderById("DRIVE_FOLDER_ID");
var contents = folder.getFiles();
let image=[];
var cnt = 0;
var file;
while (contents.hasNext()) {
var file = contents.next();
cnt++;
data = [
file.getName(),
file.getDownloadUrl(),
];
sheet.appendRow(data);
};
insertImageIntoCell(); //Insert the images on column C
};
function insertImageIntoCell(){
var sheet = SpreadsheetApp.getActiveSheet();
var row = 1;
sheet.getDataRange().getValues().forEach(url =>{
if(url[1] == "URL")return row += 1;
let image = SpreadsheetApp.newCellImage().setSourceUrl(url[1]).setAltTextDescription('TestImage').toBuilder().build();
SpreadsheetApp.getActive().getActiveSheet().getRange('C'+row).setValue(image);
row += 1;
});
}
Sample Drive Folder with sample images
Sample Result:
After running the function listFilesInFolder:
Update:
This issue is filed. Add a star to this issue for Google developers to prioritize fixing this.
Issue:
setValues() is NOT working with CellImage, while setValue() does.
If/when it starts working, You need to convert each value to cellImage using map :
function insertImagesIntoCell() {
const sheet = SpreadsheetApp.getActiveSheet(),
range = sheet.getRange('B2:B10');
range.setValues(
range.getValues().map(url => [
SpreadsheetApp.newCellImage()
.setSourceUrl(url[0])
.build(),
])
);
}
For anyone struggling with importing images from Google Drive you should know that you have to set the "Sharing" setting on every individual file for CellImageBuilder to work properly.
Like this:
const imageFileUrl = imageFolder.getFilesByName(filename).next()
.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW)
.getDownloadUrl();
const cellImage = SpreadsheetApp.newCellImage().setSourceUrl(imageFileUrl).build();
Additionally, there appears to be a rate limit on the drive download URLs, causing the '.build()' function to fail randomly on a valid URL. Retries might be necessary.
Also, the .toBuilder() call on CellImageBuilder is completely redundant.
I was wondering: is it even possible to use Logger.Log in Google Apps Script to log different string to be posted to a spreadsheet?
I have the following code:
var ss = SpreadsheetApp.openByUrl("spreadsheet url");
var sheet = ss.getSheetByName("spreadsheet sheet");
var DocNumber = e.parameter.DocNumber;
var folderId = "Folder ID 1";
var lastFileUrl = getLatestFile(folderId); // just a function that retrieves url of latest file in the folder
Logger.log(lastFileUrl);
var addUrl = sheet.getRange(1,2,sheet.getLastRow(),1);
var fileURL = "https://drive.google.com/uc?export=view&id="+lastFileUrl;
var folderId2 = "Folder ID 2";
var lastFileUrl2 = getLatestFile(folderId2); // same as above
Logger.log(lastFileUrl2);
var addUrl2 = sheet.getRange(1,3,sheet.getLastRow(),1);
var fileURL2 = "https://drive.google.com/uc?export=view&id="+lastFileUrl2;
sheet.appendRow([DocNumber,fileURL,fileURL2]);
}
When this get posted to the spreadsheet, it only posts the second url (fileURL2) - I assume because the last value in the log is this. But I was hoping to post both URL into the spreadsheet.
I tried setting it as a var first as well:
var URL2 = Logger.log(lastFileURL2);
but then the posted value will be https://drive.google.com/uc?export=view&id=Logger
I also tried using appendRow before the second URL logging but it still only takes the second url and disregard the first url.
Therefore, I was curios whether this is even possible at all?
And if not, what's the best way to achieve this without using Logger.log?
Spreadsheet output:
URL1 and URL2 is the URL from Google Drive folder.
Also, forgot to mention, I'm using the script as a Web App, used by an android app. Posting files into the Drive folder is okay, the only problem is fetching the links of the files in different folders.
These are the codes I used to get the latest file url from my folders:
function getLatestFile(folderId) {
var files = DriveApp.getFolderById("Folder_1_ID").getFiles();
var fileObj = [];
while (files.hasNext()) {
var file = files.next();
fileObj.push({id: file.getId(), date: file.getDateCreated()});
}
fileObj.sort(function(a, b) {return new Date(b.date) - new Date(a.date)});
return fileObj[0].id;
}
function getLatestFile(folderId2) {
var files2 = DriveApp.getFolderById("Folder_2_ID").getFiles();
var fileObj2 = [];
while (files2.hasNext()) {
var file2 = files2.next();
fileObj2.push({id: file2.getId(), date: file2.getDateCreated()});
}
fileObj2.sort(function(a, b) {return new Date(b.date) - new Date(a.date)});
return fileObj2[0].id;
}
Problem
Having two functions declared under the same name
Solution
Step by step:
Remove one of the functions (they are identical in terms in usage)
Make the remaining one use the parameter passed in it:
function getLatestFile(folderId) {
var files = DriveApp.getFolderById(folderId).getFiles();
var fileObj = [];
while (files.hasNext()) {
var file = files.next();
fileObj.push({id: file.getId(), date: file.getDateCreated()});
}
fileObj.sort(function(a, b) {return new Date(b.date) - new Date(a.date)});
return fileObj[0].id;
}
Change Logger to console - as of recently, all logs are sent to Stackdriver service, and thus there is no benefit in using Logger (besides by using console you make script more portable).
Commentary
What happens when you declare two or more functions under same name? Normally, the last one declared gets executed (basically, second declaration overwrites the first):
function clone(original) {
return `I am the clone of ${original}`;
}
function clone(cloned) {
return `I am a clone of ${cloned}'s clone`;
}
const elem = document.querySelector("#cloned");
elem.textContent = clone("Gary");
<h2 id="cloned"></h2>
I have a spreadsheet from which I've written code to populate with the names of folders (column 1) and their IDs (column 2).
I would like to populate each of the folders listed in that spreadsheet with a copy of each of the documents contained a separate folder (a Shared Drive folder, if that matters). When I execute the code below, a copy of each document is created in the source folder (the Shared Drive folder) instead of in the destination folder (aka the folders whose IDs are captured in the spreadsheet). If it matters, each copy is labelled with a folderID from the spreadsheet. Can someone please tell me how I can get this code to create the copies inside the appropriate destination folders instead of in the source folder?
function CopiestoFolder() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getDataRange() //Get all non-blank cells
.getValues() //Get array of values
.splice(1); //Remove header line
//Define column numbers for data. Array starts at 0.
var NAME = 0;
var FOLDERID = 1;
//For each folder ID listed in spreadsheet, create a copy of
//each item in the Resume Resources folder.
for (var i = 0; i < data.length; i++) {
var name = data[i][NAME];
var folderId = data[i][FOLDERID];
var srcFolder = DriveApp.getFolderById("folder ID");
var dstFolder = folderId;
var files = srcFolder.getFiles();
while (files.hasNext()) {
var file = files.next();
var f = file.makeCopy(dstFolder);
if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
dstFolder.addFile(file);
f.getParents().next().removeFile(file);
}
}
}
}
I had this problem with Script Files. Here's how I fixed. Or I should say here's how Tanaike fixed it. You will need to enable Drive API.
var res=file.makeCopy(copyName,subFldr);
if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
Drive.Files.update({"parents": [{"id": subFldr.getId()}]}, res.getId(), null, {"supportsTeamDrives":true}); // Added
}
The file.makeCopy() method has 3 overloads:
//Accepts no parameters
file.makeCopy();
//Accepts a name parameter as a string
file.makeCopy(name);
//Accepts a destination parameter as an instance of a Folder class
file.makeCopy(destination);
//Accepts 2 parameters, the first of which is name (String) and the second one is destination (Folder).
file.makeCopy(name, destination);
You are trying to pass folder id instead of the actual folder, which gets interpreted as a folder name (not destination). Also, the following code means that your 'dstFolder' parameter is a string but you try to call the 'addFile()' method on it:
var folderId = data[i][FOLDERID];
var dstFolder = folderId;
dstFolder.addFile(file);
If you want to copy a file to another folder via makeCopy, you should pass a Folder as a parameter, not a folder id (a string). If you provide a string (your id) as a parameter, the script will interpret this id as the name you want the copied file to have. So you should first get the Folder out of its id, via getFolderById(id). So you should change this line:
var dstFolder = folderId;
To this one:
var dstFolder = DriveApp.getFolderById(folderId);
You are making a copy of the file before checking if the file is a Google Apps Script project. I assume you just want GAS projects to be copied, so this should be included inside the if block. Also, inside the if block you are using addFile which can also be used to copy a file to another folder (it adds the file to the desired folder). So you don't need to use both functions, they are doing basically the same (copying your file to the desired folder).
You are using removeFile, which is not necessary if you want to keep the original files in the original folders.
So the while block could be something like:
while (files.hasNext()) {
var file = files.next();
if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
dstFolder.addFile(file);
}
}
Finally, your full code could be like:
function CopiestoFolder() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var data = ss.getDataRange() //Get all non-blank cells
.getValues() //Get array of values
.splice(1); //Remove header line
//Define column numbers for data. Array starts at 0.
var NAME = 0;
var FOLDERID = 1;
//For each folder ID listed in spreadsheet, create a copy of
//each item in the Resume Resources folder.
for (var i=0; i<data.length; i++) {
var name = data[i][NAME];
var folderId = data[i][FOLDERID];
var srcFolder = DriveApp.getFolderById("folder ID");
var dstFolder = DriveApp.getFolderById(folderId);
var files = srcFolder.getFiles();
while (files.hasNext()) {
var file = files.next();
if (file.getMimeType() == MimeType.GOOGLE_APPS_SCRIPT) {
dstFolder.addFile(file);
}
}
}
}
I hope this is of any help.
Many thanks for the comments and response. That code was a little too advanced for me and I ended up finding a very inelegant solution that I used because I ran out of time. I was able to get the code to list Folder and first level of subFolders with links, but I have not yet been able to get it to iterate through all levels of folders, mostly because I just need to back up and learn a lot of the basics. I was also able to get all folders to list using some code I found to create a tree, but I couldn't get it to format in a way that you could actually see the structure, or add links. I'm going to continue to try, and will post if I sort it out. Here is what I used, which was fine for our purposes because our shared drive is fairly limited.
For reference, this was the code I used to start with:
https://superuser.com/questions/1095578/list-of-subfolder-names-and-file-links-in-google-sheets-script
function listFolders(foldername) {
var ss = SpreadsheetApp.openById(ID);
var sheet = ss.getSheetByName("sheet name");
sheet.appendRow("Parent Folder", "Name", "Link" ]);
//change the folder ID below to reflect your folder's ID (look in the
URL when you're in your folder)
var folders = DriveApp.getFolderById(ID);
var contents = folders.getFolders();
var cnt = 0;
var folderD;
while (contents.hasNext()) {
var folderD = contents.next();
cnt++;
data = [
folders.getName(),
folderD.getName(),
folderD.getUrl(),
];
sheet.appendRow(data);
};
};
Original Post:
I am a beginner using script in google sheets and I am trying to create a list of folders in a google drive with many subfolders. Ideally it would be a tree form but I'd settle for a list at this point. I don't need to list all the files, just the folders. I have been trying to get the code below to work but it keeps hanging up at calling up the spreadsheet. Can anyone help?
I have tried calling up both the folders and the spreadsheet by both name and ID but it always tells me it can't execute the getactivespreadsheet command. I have also tried to modify the code referred to in another another question but I can't get that to work either: https://ctrlq.org/code/19923-google-drive-files-list
function generateFolderIndex(myfoldername) {
var folder = DriveApp.getFolderById('0B8vOJQUb-IIVTHdudlZSVkdtdE0');
var subFolders = folder.getFolders();
var childFolders = subFolders
var ss = SpreadsheetApp.getActiveSpreadsheet('1Trv9OtJFnD4AdSHrZKFfsSu6JMV9f78H6wwZNhF2_M4');
var sheet = ss.getSheetByName('Directory');
sheet.clear(directory);
sheet.appendRow([name, link]);
while (subFolders.hasNext())
{
var childFolder = childFolders.next();
var foldername = childFolder.getname();
var name = childFolder.getName()
var link = childFolder.getUrl()
var date = childFolder.getDateCreated()
data = [name, link]
sheet.appendRow(data);
}
};
I am trying to get a sheet that lists folders and subfolders with URL links. I am currently receiving the following error message:
[19-05-31 15:32:20:911 EDT] Execution failed: Cannot find method getActiveSpreadsheet(string). (line 5, file "Code") [0.432 seconds total runtime]
Or.. the easy way...
Use DRIVE or FS DRIVE APP for desktop in PC. Usea A CMD (windows)... AND THE FUNCTION
TREE >a.txt
The generated file a.txt will display all the tree.
IT SAVES HOURS OF RESEARCH.
SpreadsheetApp.getActiveSpreadsheet() doesn't have any parameters.
However
SpreadsheetApp.openById('ssid') does require and id. I think perhaps you meant to be using openById();
openById
getActiveSpreadsheet
This is a script that I'm currently working on but it generates a list of Spreadsheets and you can exclude folders by id and files by id.
function getAllSpreadsheets() {
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('FilesAndFolders');
if(sh.getLastRow()>0) {
sh.getRange(1,1,sh.getLastRow(),2).clear().clearDataValidations();
}
getFnF();
SpreadsheetApp.getUi().alert('Process Complete')
}
var level=0;
function getFnF(folder) {
var folder= folder || DriveApp.getRootFolder();
//var folder=DriveApp.getRootFolder();
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('FilesAndFolders');
var files=folder.getFilesByType(MimeType.GOOGLE_SHEETS)
while(files.hasNext()) {
var file=files.next();
if(isExcluded(file.getId(),'file')){continue;}
var firg=sh.getRange(sh.getLastRow() + 1,level + 1);
firg.setValue(Utilities.formatString('=HYPERLINK("%s","%s")',file.getUrl(),'FILE: ' + file.getName()));
firg.offset(0,1).insertCheckboxes('Exclude','Include');
}
var subfolders=folder.getFolders()
while(subfolders.hasNext()) {
var subfolder=subfolders.next();
if(isExcluded(subfolder.getId(),'folder')){continue;}
var forg=sh.getRange(sh.getLastRow() + 1,level + 1);
forg.setValue(Utilities.formatString('=HYPERLINK("%s","%s")',subfolder.getUrl(),'FOLDER: ' + subfolder.getName()));
//forg.offset(0,1).insertCheckboxes('Exclude','Include');
//level++;
getFnF(subfolder);
}
//level--;
}
function isExcluded(id,type) {//type: file or folder
var type=type||'Missing Input';
var xFldrIdA=['Excluded folder ids'];
var xFileIdA=['Excluded file ids'];
var type=type.toLowerCase();
switch(type) {
case 'file':
return xFileIdA.indexOf(id)>-1;
break;
case 'folder':
return xFldrIdA.indexOf(id)>-1;
break;
default:
throw(Utilities.formatString('Error: Invalid Type: %s in isExcluded.',type));
return true;//assume excluded
break;
}
}
Your welcome to use it, perhaps it will help.
Okay so the plan is to, When a certain cell contains a certain text, it will automaticlly move the file to a pacific folder. Here is what i have, also i am very new to this coding language so im learning at the same time!
Here what i got so far, bearing in mind this does work but ill explain more below:
function filemove(root_folder, dest_folderinactive, dest_folderactive) {
var root_folder = DriveApp.getFolderById('0B_Nz0cop3s-ITTlYM21SNGIwNDg');
var dest_folderinactive = DriveApp.getFolderById('0B_Nz0cop3s-IQVZEWUNiQ1d2TzQ');
var dest_folderactive = DriveApp.getFolderById('0B_Nz0cop3s-IalFITGhzN3lTTzg');
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var file = DriveApp.getFileById(spreadsheet.getId());
var s = SpreadsheetApp.getActiveSheet();
if( s.getName() == "StatsData" ) {
var range = s.getRange("E16")
var values = range.getValues();
for(var i in values){
if(values[i][0].match("Not Police")!=null){
dest_folderinactive.addFile(file);
root_folder.removeFile(file);
}
if(values[i][0].match("Police")!=null){
dest_folderactive.addFile(file);
root_folder.removeFile(file);
}
}
}
};
Okay so what this does is if cell "E16" contains "Not Police" it will move the file to a folder called "Inactive" (dest_folderinactive) and if the cell contains "Police" it will move it to (dest_folderactive) again these both come from the so called "root" folder - i know root means the very fist page of google drive but in this case the base folder which contains these files is named the root. This code works (Sometimes) sometimes you have to run it multiple time for it to actully work and its not very effichant.
My problem also comes when i would like it to do this, When a file is in the In-Active folder and the cell changes to "Police" i would like it to move from In-Active to Active and same for Active saying "non police" to go to inactive but i just can't seem to get this working. It would be greatly helpful if someone could help out, also knowing this code is kinda sheet as it doesn't also do it first time.
Try this code. It will move the file to the required folder depending upon the value of your target cell. It will also return the file to the nominated "root" folder if there is junk data in the target cell. You need to set the specific folder IDs at the top of the code: if you need to pass them in, then you'll need to add them as parameters after e. The parameter e is required to consume the onEdit trigger event data (which this function doesn't use, but you would need to account for if you are passing in the folder IDs).
function filemove(e){
// folder IDs
var root_folder = 'root_folder_id_string';
var dest_folderinactive = 'inactive_files_folder_id_string';
var dest_folderactive = 'active_files_folder_id_string';
// the specific destination folder on this run
var dest_folder;
// assume the file will move
var move_file = true;
// the spreadsheet & its parent folders
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var file = DriveApp.getFileById(spreadsheet.getId());
var parents = file.getParents();
var sheet = spreadsheet.setActiveSheet(spreadsheet.getSheetByName("StatsData"));
// now grab the value of E16 & look for the string "police"
// string.search(regEx) returns the position of the first match, otherwise -1
var values = sheet.getRange("E16").getValues();
switch(values[0][0].search(/police/i)){
case -1:
dest_folder = root_folder;
break;
case 0:
dest_folder = dest_folderactive;
break;
default:
dest_folder = dest_folderinactive;
}
// is the dest_folder in the parents array?
while(parents.hasNext()){
var fldr = parents.next().getId();
if(fldr == dest_folder){
// YES -- don't move the file
move_file = false;
} else{
// NO -- are the other folders in the parents array?
if(fldr == root_folder || fldr == dest_folderinactive || fldr == dest_folderactive){
// YES -- remove the file from that folder
DriveApp.getFolderById(fldr).removeFile(file);
}
}
}
// do we have to move the file?
if(move_file){
DriveApp.getFolderById(dest_folder).addFile(file);
}
}
I'm stepping over the parents array & comparing them to the destination folders so that you don't remove the file from another folder by mistake.
If the target cell has more free-form data & you are looking for the string "police" or "not police", then the switch() will not work. In this case you will need to use if(...){...} else if(...){...} else{...} to test.