My current goal is a standalone script that I can run on multiple Sheets files in my Drive.
So I've setup a clean new unbound Project and added the Service Google Sheets API. I referred to the Google Docs and am starting with the "simple" first step, open the Sheet file. The unshared URL I'm working on is https://docs.google.com/spreadsheets/d/1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI/edit#gid=1242809349. I should clarify that it's shared with my account.
My question is how do I interpret these errors and address them in order to open the file. Is there any way to use a better debugger?
With a copy/pasted gid
function myFunction() {
var ss = SpreadsheetApp.openById("1242809349");
Logger.log(ss.getName());
}
Error message:
Exception: Unexpected error while getting the method or property openById on object SpreadsheetApp.
With the Script ID of the file
function myFunction() {
var ss = SpreadsheetApp.openById("1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI");
Logger.log(ss.getName());
}
Error message:
Exception: Service Spreadsheets failed while accessing document with id 1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI.
I believe your goal is as follows.
You want to check whether you have permission to open the file using the file ID.
You want to achieve this using Google Apps Script.
In this case, how about the following sample script?
Sample script:
function sample() {
const fileId = "###"; // Please set your file ID.
let file;
try {
file = DriveApp.getFileById(fileId);
// If you have permission for opening the file, you can use `file`.
const owner = file.getOwner().getEmail();
const editors = file.getEditors().map(e => e.getEmail());
const viewers = file.getViewers().map(e => e.getEmail());
console.log(owner);
console.log(editors);
console.log(viewers);
} catch ({message}) {
if (message == "No item with the given ID could be found. Possibly because you have not edited this item or you do not have permission to access it.") {
console.log("You have the permission for opening this file.");
} else if (message == "Unexpected error while getting the method or property getFileById on object DriveApp.") {
console.log("Invalid file ID.");
} else {
console.log(message);
}
}
}
When this script is run, if you don't have permission for opening the file, You have permission for opening this file. is shown. If the file ID is an invalid file ID, Invalid file ID. is shown.
If you have permission for opening the file, you can use file. In this sample, the owner, editors, and viewers are shown.
When DriveApp.getFileById is used, the files except for Google Spreadsheet can be checked. So, I used it.
Reference:
getFileById(id)
Added 1:
From your following reply,
If I do need to use DriveApp() instead, how do I get the Sheet data from there?
Sample script:
function sample() {
const fileId = "###"; // Please set your file ID.
let file;
try {
file = DriveApp.getFileById(fileId);
} catch ({message}) {
if (message == "No item with the given ID could be found. Possibly because you have not edited this item or you do not have permission to access it.") {
console.log("You have the permission for opening this file.");
} else if (message == "Unexpected error while getting the method or property getFileById on object DriveApp.") {
console.log("Invalid file ID.");
} else {
console.log(message);
}
return;
}
// You can use `file` by Spreadsheet as follows.
const spreadsheet = SpreadsheetApp.open(file);
}
Added 2:
From your following reply,
To summarize so far, I can run your first example with that id "1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI" and it runs but I get an error with the revised example: Exception: Service Spreadsheets failed while accessing document with id 1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI. sample # sameple_test.gs:19
From Exception: Service Spreadsheets failed while accessing document with id 1K3Iy7t1TufD9HRJBCqwFbcrfvCvpnqyI. sample # sameple_test.gs:19? and the length of the file ID, I'm worried that you are trying to use the file, which is not Spreadsheet, by SpreadsheetApp.open(file). If my understanding is correct, such error occurs. In this case, how about checking the mimeType as follows?
Sample script:
function sample() {
const fileId = "###"; // Please set your file ID.
let file;
try {
file = DriveApp.getFileById(fileId);
if (file.getMimeType() != MimeType.GOOGLE_SHEETS) {
console.log("This file is not Spreadsheet.")
return;
}
} catch ({ message }) {
if (message == "No item with the given ID could be found. Possibly because you have not edited this item or you do not have permission to access it.") {
console.log("You have the permission for opening this file.");
} else if (message == "Unexpected error while getting the method or property getFileById on object DriveApp.") {
console.log("Invalid file ID.");
} else {
console.log(message);
}
return;
}
// You can use `file` by Spreadsheet as follows.
const spreadsheet = SpreadsheetApp.open(file);
}
Related
This is a continuation from How to Use Advanced Drive Service to Upload Files.
My Webapp consists of an upload form for data files, which are then stored on Google Drive. (Full code in snippet below.)
I'm having a problem with the following line of code:
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
try {
//Get root folder and pull all existing folders, plus setup variables pulled from form
var dropbox = form.Country;
var timeStamp = new Date();
//Set file name slightly differently for Weekly Member Report (do not want to overright based on name just keep each extract so add timestamp to name)
if (form.reportType == "Member Weekly"){
var filename = form.reportType + timeStamp + ".xls";
}
else
{
var filename = form.reportType+".xls";
}
var rootfolder = DriveApp.getFolderById("0Byvtwn42HsoxfnVoSjB2NWprYnRiQ2VWUDZEendNOWwwM1FOZk1EVnJOU3BxQXhwU0pDSE0");
//Note root folder is Live Uploads Folder in Flatworld App folder structure
var folder, folders = rootfolder.getFoldersByName(dropbox);
//Check if folder exists and if not create
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = rootfolder.createFolder(dropbox);
}
var FolderURL = folder.getUrl(); // Retain URL of folder for final end message to user
//Check if file already exists and delete if it does
var file, files = folder.getFilesByName(filename);
while( files.hasNext()){
file = files.next();
file.setTrashed(true);
}
//New Code from Stackover Flow:
//Upload file and set various properties
var mediaData = form.myFile;
var resource = {
title: filename,
mimetype: 'application/vnd.ms-excel',
description: "Uploaded via BNI Upload Form by " + form.myName + " on: " + timeStamp
};
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
var fileId = file.id;
var DriveAppFile = DriveApp.getFileById(fileId); // retrieve file in DriveApp scope.
var FileURL = DriveAppFile.getUrl(); //Retain URL of file for final end message to user
DriveApp.removeFile(DriveAppFile); // remove new file from Users root My Drive
folder.addFile(DriveAppFile); // puts file in selected folder
//End of New code from Stackover Flow
//Success message displayed to user
return "Thanks! File uploaded successfully to: <br><br><b>Folder Location:</b> " + FolderURL + "<br>" + "<b>File:</b> " + FileURL + ". <br><br>For any queries please email user#example.com copying the URLs displayed here in your email. You can close this browser window now or use the link below to upload another file.<br><br>";
} catch (error) {
//Catch error return it to user and email with error details
Its throwing the error message "Empty Response" on the line of code above when we try and upload a large file (15MB) Do you have any suggestions. This is well inside the Files insert limit of 5120GB and the code works fine on smaller files.
I've now tried to add in a loop in function to try the upload a couple of times, still throwing the same error sadly:
//setup function that will return null if file is not uploaded correctly
function createDriveFile(resource_f, mediaData_f){
try{
var file = Drive.Files.insert(resource_f, mediaData_f); // create file using Drive API
return file;
} catch (e) {return null;}
}
//try upload and loop if null
var maxTries = 3;
var tries = 0;
do {
tries = tries + 1;
if (tries > 0) {
var file = createDriveFile(resource, mediaData);
Logger.log("I'm trying to upload, try number: " + tries);
}
} while ((file == null) && (tries < maxTries));
if (file == null) {
var file = Drive.Files.insert(resource, mediaData); // try one laste time to create file using Drive API - outside loop function so if error is thrown script stops
}
The error only seems to occur on a larger file, even if we reduce the size of the same file that solves error so do we need to adjust the upload process to account for a larger file. Is there a Google Apps Script equivalent of making the API upload request resumable?
Your file size is the determinant factor here. Referencing the documentation suggests the simple upload method used here is good for up to 5MB only.
https://developers.google.com/drive/web/manage-uploads
Your comments seem to confirm this is what is happening for you.
As you hinted, use the resumable upload method. Use the uploadType: resumable parameter flag – API docs on the insert method describes how.
You can also check the naming of the file, for me I had a slash in the name which is why it would not upload. So take away any special characters before uploading and it should work.
When I use DriveApp.getFolderById(someId) where someId belongs to a file and not a folder, the method returns the file instead of erring out. How can I ensure that the returned object is indeed a folder?
Right now, the only solution I can think of is with try/catch:
try {
const testFile = someFile.makeCopy('test', folder);
testFile.setTrashed(true);
} catch (err) {
return 'The folder is not really a folder';
}
Is there a cleaner solution?
In your situation, for example, how about checking the mimeType as follows?
Sample script:
const id = "###"; // Please set the file ID and folder ID.
const file = DriveApp.getFileById(id);
if (file.getMimeType() == MimeType.FOLDER) {
console.log(`This ID (${id}) is a folder.`);
const folder = DriveApp.getFolderById(id);
// Do something.
} else {
console.log(`This ID (${id}) is not a folder.`);
}
When this script is run, by checking the mimeType of id, you can see whether the ID is the folder.
Reference:
getMimeType()
Added:
From your following reply,
A folder doesn't have a getMimeType function to call, so I guess a check like if (!folder.getMimeType) that a returns true indicates that it's a folder.
If you want to check using const folder = DriveApp.getFolderById(id), how about the following sample script?
Sample script:
const id = "###"; // Please set the file ID and folder ID.
const folder = DriveApp.getFolderById(id);
if (folder.getUrl().split("/")[4] == "folders") {
console.log(`This ID (${id}) is a folder.`);
// In this case, you can directly use the variable of "folder".
// Do something.
} else {
console.log(`This ID (${id}) is not a folder.`);
}
In this sample script, by checking the URL of the object, it is checked whether id is the folder. For example, when id is Spreadsheet, the retrieved URL is like https://docs.google.com/spreadsheets/d/###/edit?usp=drivesdk. This script uses this situation.
I have a script that pulls fields from a Google Sheet and inserts them into an email template and sends the emails off. That works fine.
I recently wanted to include a PDF as an attachment to the emails. It would be the same PDF for every email. I uploaded the PDF into Google Drive. When I run the script, the first email is sent off fine with the attachment but the following emails are not sent because I encounter this error: "Cannot retrieve the next object: iterator has reached the end"
Pretty sure it has to deal with the attachment/file and me not handling the iteration correctly. Can someone help? Below is the code:
function send2Email()
{
var filename= 'even_overview2020.pdf';
var file = DriveApp.getFilesByName(filename);
var spread =SpreadsheetApp.getActiveSpreadsheet();
var contactSheet =spread.getSheetByName(contactSheetName);
var bodySheet =spread.getSheetByName(templateSheetName);
var contactData =contactSheet.getDataRange().getValues();
var bodyData =bodySheet.getDataRange().getValues();
var fname,company,sign,template,email,subject,body,sender,file;
for (var i =1;i<contactData.length;i++)
{
contactData[i][statusCol-1]="";
}
contactSheet.getDataRange().setValues(contactData);
for (var i =1;i<contactData.length;i++)
{
fname=trim_(contactData[i][fnameCol-1]);
company=trim_(contactData[i][companyCol-1]);
sign=trim_(contactData[i][signCol-1]);
template=trim_(contactData[i][templateCol-1]);
email=trim_(contactData[i][emailCol-1]);
sender=trim_(contactData[i][senderCol-1]);
Logger.log(email);
for(var j=1;j<bodyData.length;j++)
{
if(trim_(bodyData[j][tempRefCol-1]).toUpperCase()==String(template).toUpperCase())
{
body=bodyData[j][bodyCol-1];
subject=bodyData[j][subjectCol-1];
}
}
Logger.log(j+","+email+','+body+','+subject);
body=body.replace(/\n/g,"<br>");
body=body.replace("(w)",sign).replace("(x)",fname).replace("(y)",company).replace("(s)",sender.split(" ")[0]);
Logger.log(email+','+body+','+subject);
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file.next().getAs(MimeType.PDF)]});
contactSheet.getRange(i+1, statusCol).setValue('Y');
}
}
How about this modification?
Modification point:
In your script, attachments: [file.next().getAs(MimeType.PDF)]} is used in the loop. By this, the 1st loop works by file.next(). But after 2nd loop, an error occurs at file.next() because the file of filename is one file in Google Drive. I think that this is the reason of your issue.
In order to avoid this issue, how about the following modification?
Modified script:
From:
var file = DriveApp.getFilesByName(filename);
To:
var files = DriveApp.getFilesByName(filename);
var file;
if (files.hasNext()) {
file = files.next().getAs(MimeType.PDF);
} else {
throw new Error("No file");
}
And also, please modify as follows.
From:
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file.next().getAs(MimeType.PDF)]});
To:
MailApp.sendEmail({to:email,subject:subject,name:sender,htmlBody:body,attachments: [file]});
Reference:
Class FileIterator
This is a continuation from How to Use Advanced Drive Service to Upload Files.
My Webapp consists of an upload form for data files, which are then stored on Google Drive. (Full code in snippet below.)
I'm having a problem with the following line of code:
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
try {
//Get root folder and pull all existing folders, plus setup variables pulled from form
var dropbox = form.Country;
var timeStamp = new Date();
//Set file name slightly differently for Weekly Member Report (do not want to overright based on name just keep each extract so add timestamp to name)
if (form.reportType == "Member Weekly"){
var filename = form.reportType + timeStamp + ".xls";
}
else
{
var filename = form.reportType+".xls";
}
var rootfolder = DriveApp.getFolderById("0Byvtwn42HsoxfnVoSjB2NWprYnRiQ2VWUDZEendNOWwwM1FOZk1EVnJOU3BxQXhwU0pDSE0");
//Note root folder is Live Uploads Folder in Flatworld App folder structure
var folder, folders = rootfolder.getFoldersByName(dropbox);
//Check if folder exists and if not create
if (folders.hasNext()) {
folder = folders.next();
} else {
folder = rootfolder.createFolder(dropbox);
}
var FolderURL = folder.getUrl(); // Retain URL of folder for final end message to user
//Check if file already exists and delete if it does
var file, files = folder.getFilesByName(filename);
while( files.hasNext()){
file = files.next();
file.setTrashed(true);
}
//New Code from Stackover Flow:
//Upload file and set various properties
var mediaData = form.myFile;
var resource = {
title: filename,
mimetype: 'application/vnd.ms-excel',
description: "Uploaded via BNI Upload Form by " + form.myName + " on: " + timeStamp
};
var file = Drive.Files.insert(resource, mediaData); // create file using Drive API
var fileId = file.id;
var DriveAppFile = DriveApp.getFileById(fileId); // retrieve file in DriveApp scope.
var FileURL = DriveAppFile.getUrl(); //Retain URL of file for final end message to user
DriveApp.removeFile(DriveAppFile); // remove new file from Users root My Drive
folder.addFile(DriveAppFile); // puts file in selected folder
//End of New code from Stackover Flow
//Success message displayed to user
return "Thanks! File uploaded successfully to: <br><br><b>Folder Location:</b> " + FolderURL + "<br>" + "<b>File:</b> " + FileURL + ". <br><br>For any queries please email user#example.com copying the URLs displayed here in your email. You can close this browser window now or use the link below to upload another file.<br><br>";
} catch (error) {
//Catch error return it to user and email with error details
Its throwing the error message "Empty Response" on the line of code above when we try and upload a large file (15MB) Do you have any suggestions. This is well inside the Files insert limit of 5120GB and the code works fine on smaller files.
I've now tried to add in a loop in function to try the upload a couple of times, still throwing the same error sadly:
//setup function that will return null if file is not uploaded correctly
function createDriveFile(resource_f, mediaData_f){
try{
var file = Drive.Files.insert(resource_f, mediaData_f); // create file using Drive API
return file;
} catch (e) {return null;}
}
//try upload and loop if null
var maxTries = 3;
var tries = 0;
do {
tries = tries + 1;
if (tries > 0) {
var file = createDriveFile(resource, mediaData);
Logger.log("I'm trying to upload, try number: " + tries);
}
} while ((file == null) && (tries < maxTries));
if (file == null) {
var file = Drive.Files.insert(resource, mediaData); // try one laste time to create file using Drive API - outside loop function so if error is thrown script stops
}
The error only seems to occur on a larger file, even if we reduce the size of the same file that solves error so do we need to adjust the upload process to account for a larger file. Is there a Google Apps Script equivalent of making the API upload request resumable?
Your file size is the determinant factor here. Referencing the documentation suggests the simple upload method used here is good for up to 5MB only.
https://developers.google.com/drive/web/manage-uploads
Your comments seem to confirm this is what is happening for you.
As you hinted, use the resumable upload method. Use the uploadType: resumable parameter flag – API docs on the insert method describes how.
You can also check the naming of the file, for me I had a slash in the name which is why it would not upload. So take away any special characters before uploading and it should work.
I'm trying to change ownership of documents in my Drive, but I recieve the following error:
We're sorry, a server error occurred. Please wait a bit and try again. (line 12, file "Code")
function transferFiles() {
var files = DriveApp.getFiles();
while (files.hasNext()) {
var file = files.next();
var owner = file.getOwner().getEmail();
if (owner != 'adminuser#domain.co.uk'){
file.setOwner('adminuser#domain.co.uk');
}
Logger.log(file);
}
}
You've described a reported issue. See & star Issue 2756: Server error for illegal ACL change
Summary: Only the owner of a file can change ownership. Admin accounts don't own user's files, so they have no special privileges in this respect.
A possible work-around utilizing the Drive API (not Google Apps Script) to impersonate domain users is described in this answer, unfortunately without implementation details.
I think that the root cause of that error msg might not be something like your internet connection. I'm guessing that there is an error trying to set the permissions, which terminates your code, and then something happens with the server connection.
I'm not saying this is an answer to the problem, but I ran some code myself and I'm getting files that don't even show up in my Google Drive. I have no idea where these files are. These files that are showing up in my list of files have other owners, and are not in my Google Drive. So, I'm assuming that they are files that I previously downloaded, and probably gave permissions to, then deleted. I don't know.
I wanted to know which file was causing the error, what folder the file was in, and what the file count is. So, I created a counter and looked up the folder that the file is in. The files that show up in the list, also are in a folder that DOES NOT EXIST in my Google drive. So, again, DriveApp is getting files from somewhere, and from some folder that is unknown to me.
function transferFiles() {
var files = DriveApp.getFiles();
var cntFiles = 0;
while (files.hasNext()) {
cntFiles = cntFiles + 1;
Logger.log('File Count: ' + cntFiles)
var file = files.next();
Logger.log('file: ' + file);
var whatFolder = file.getParents();
while (whatFolder.hasNext()) {
var folder = whatFolder.next();
Logger.log('Folder Name: ' + folder.getName());
}
var owner = file.getOwner().getEmail();
Logger.log('owner: ' + owner);
//if (owner != 'dummyName#gmail.com') {
//file.setOwner('dummyName#gmail.com');
//}
}
}
I'm getting files returned that are from other Google accounts that are also mine. Weird. How is it doing that? Google must have somehow linked my different Google accounts. My point is, to figure out what this problem is, maybe you need to know exactly what file is giving the error.
You could add some error handling to your code, so that a failure doesn't break the code. You'd want to know what file failed to allow the permission to be changed. So You'd probably want to log that somehow.
Here is code that shows all the files that caused an error, and what the error was for that file:
function transferFiles() {
var files = DriveApp.getFiles();
var cntFiles = 0;
while (files.hasNext()) {
cntFiles = cntFiles + 1;
//Logger.log('File Count: ' + cntFiles)
var file = files.next();
//Logger.log('file: ' + file);
var whatFolder = file.getParents();
while (whatFolder.hasNext()) {
var folder = whatFolder.next();
//Logger.log('Folder Name: ' + folder.getName());
}
var owner = file.getOwner().getEmail();
//Logger.log('owner: ' + owner);
try {
if (owner != 'someEmail#gmail.com') {
file.setOwner('someEmail#gmail.com');
Logger.log('File that was changed: ' + file);
}
} catch (theError) {
Logger.log('There was an error for file: ' + theError);
Logger.log('File that caused error: ' + file);
};
}
}
I'm getting errors:
Action not allowed
Invalid argument: sharing.user
Domain administrators are not allowed to change file/folder property, only onwer can do that.
What you can do is to implement a webapp that will let the user (that is also the owner) automatically do that, based on time or any event you can trigger.
Take a look to:
Google Apps Script Web App Example, Take ownership of other users Drive-files using Google Apps Script