Trying to change owner on files in Google Drive - google-apps-script

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

Related

unable to copy the gmail file to google sheets. It throws the error as:API call to drive.files.insert failed with error: Invalid mime type provided [duplicate]

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.

Automatic Convert and Delete Filetype Server Error (Timing Out?)

I am trying to create a script that will convert xls documents in a folder to sheets versions, then delete the xls versions. The script works, but because I have plenty of xls files in this folder, I believe it's timing out because it is throwing up, "We're sorry, a server error occurred. Please wait a bit and try again. (line 6, file "Convert XLS")" error a little while after I start running it. Is there a way to execute this without the server timing out or something?
function convertXLS() {
var folder = DriveApp.getFolderById('File_ID');
var files = folder.getFilesByType(MimeType.MICROSOFT_EXCEL_LEGACY);
while (files.hasNext()) {
var file = files.next();
Drive.Files.copy({}, file.getId(), {convert: true});
}
// while (files.hasNext()) {
// Drive.Files.remove( file.getId());
//}
}
Update:
So I reconfigured the code to this:
function importXLS(){
var folder = DriveApp.getFolderById('File_ID');// you can also use a folder as starting point and get the files in that folder... use only DriveApp method here.
var files = folder.getFilesByType(MimeType.MICROSOFT_EXCEL_LEGACY);
while(files.hasNext()){
var xFile = files.next();
var name = xFile.getName();
if (name.indexOf('.xls')>-1){ // this check is not necessaey here because I get the files with a search but I left it in case you get the files differently...
var ID = xFile.getId();
var xBlob = xFile.getBlob();
var newFile = { title : name+'_converted',
key : ID,
'parents':[{"id":"File_ID"}]
}
file = Drive.Files.insert(newFile, xBlob, {
convert: true,
});
}
}
}
But now it's giving me this error, "API call to drive.files.insert failed with error: Internal Error (line 14, file "Convert XLS")" after about two minutes of it running. I don't know what to do :(
What you are facing is a time-out due to the execution limit for Apps Script.
To solve this, create a function that converts and deletes a single file, then run it multiple times.
To do that, you will use getFilesByType in conjunction with Advanced Drive Services with the Drive.files.insert function with options as {convert:true}.
Running it multiple times can be done with a time-based trigger and/or manually.
Since you will be getting files only of a specific MIME Type, when you run out of XLS files you should have no problems.
Hope this helps!

Google Apps Script: Downloading files from Drive (same user)

I'm trying to write a Google Apps Script to download all files in a particular Drive folder (likely .csv files). I have found the getDownloadUrl() method but I haven't been able to figure out what to do with it. I'm currently trying the following code, where files is the list of the files in the folder:
while(files.hasNext()) {
var response = UrlFetchApp.fetch(files.next().getDownloadUrl());
Logger.log(response.getContentText());
}
When I try to run the code, however, I get a 401 error which I guess means I lack the proper authorization? But I was under the impression that I wouldn't need to go through all of the OAuth2 steps if everything was taking place within my one Google account. The Google guide to connecting to external APIs makes it look like I should be able to just fetch the url. I've already gotten access to my Drive files, because the download URL does exist when I run that method. What am I missing here? I'm really new to all of this so maybe it's something basic.
Thanks!
EDIT:
I managed to fix the 401 error by modifying the code as follows:
while(files.hasNext()) {
var response = UrlFetchApp.fetch(files.next().getDownloadUrl(),{headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()}});
Logger.log(response.getContentText());
}
But the issue remains that this only returns the contents to me, rather than downloading the file. How can I initiate a download from the results of this fetch call?
Besides listing all download links, I guess original poster also wants to download files to user's computer (according to earlier discussion).
To do this, encode blob with base 64 in server side (e.g. Google App Script) and download with data URI in client's browser. Below are code for this, with help of this answer.
Google App Script
...
function getBlobInBase64(fileId){
// omit authorization code if any
var file = DriveApp.getFileById(fileId);
var blob = file .getBlob();
return {
file_name: file.getName(),
mime: file.getMimeType(),
b64: Utilities.base64Encode(blob.getBytes());
}
...
Javascript that serve with index.html
...
function getFile(fileId){
google.script.run.withSuccessHandler((data) => {
var uri = 'data:' + data.mime + ';charset=ISO-8859-1;base64,' + encodeURIComponent(data.b64);
downloadURI(uri, data.file_name);
}).withFailureHandler((err) => {
console.log(err);
}).getBlobInBase64();
}
...
NOTE: I haven't run this code but the method should work as used in my other project.
This will log the file names & URLS for any files available for downloading (first 100 in root drive):
function myFunction() {
var files = DriveApp.getFiles();
var c = 0;
while (files.hasNext() && c<100) {
var file = files.next();
Logger.log("File Name: " + file.getName());
Logger.log(" Download URL: " + file.getDownloadUrl());
c++;
}
}
My answer might be a bit off but I think you have a better chance downloading files from Google Drive using the webContentLink as it is the method I commonly use. I obtain webContentLink by using Files.list and ask for webContentLink in the fields parameter. I run that link through the browser and it downloads the file.
If you are trying to download Google Drive files to local computer using Google Apps Script, Then please understand that Google Apps Script is a server side scripting language. It can't download and save files to your local drive.
Here is a webapp that may be helpful for you. It does not do exactly what you are looking for but you may be able to edit it and get a result. Hope it helps!
CODE:
function doGet(e) { // main function
var template = HtmlService.createTemplateFromFile('index.html'); // filename always!
return template.evaluate().setTitle('Search Drive').setSandboxMode(HtmlService.SandboxMode.IFRAME);
}
// Process the form
function processForm(searchTerm) {
var resultToReturn;
Logger.log('processForm was called! ' + searchTerm);
resultToReturn = SearchFiles(searchTerm); // Call to the search files function to search files on google drive
Logger.log('resultToReturn: ' + resultToReturn);
return resultToReturn; // return the results
}
function SearchFiles(searchTerm) {
var searchFor ="title contains '" + searchTerm + "'"; //single quotes are needed around searchterm
var owneris ="and 'YOUREmail#email.com' in Owners"; //email address to search for
var names = [];
Logger.log(searchFor + " " + owneris);
var files = DriveApp.searchFiles(searchFor + " " + owneris);
while (files.hasNext()) {
var file = files.next();
var fileId = file.getId();// To get FileId of the file
var lm = file.getLastUpdated();
var name = file.getName()+"|~|"+fileId+"|~|"+lm; // Im concatenating the filename with file id separated by |~|
names.push(name); // adding to the array
}
return names; // return results
}
INDEX.html
<html>
<head>
<base target="_top">
<script>
function displayMessage() {
var searchTerm;
searchTerm = document.getElementById('idSrchTerm').value;
console.log('searchTerm: ' + searchTerm );
// Below call means: call to processForm passing the searchTerm value (previously escaped) and after finish call the handleResults function
google.script.run.withSuccessHandler(handleResults).processForm(searchTerm.replace("'","\'"));
}
function handleResults(results){
console.log('Handle Results was called! ');
document.writeln('BACK<br/><br/>');
var length=results.length; // total elements of results
for(var i=0;i<length;i++)
{
var item=results[i];
item=item.split("|~|"); // split the line |~|, position 0 has the filename and 1 the file id
document.writeln("<b><a href='https://docs.google.com/document/d/"+item[1]+"' target='_blank'>"+item[0]+"</b></a> (Last modified: "+item[2]+")<br/><br/>"); // write result
}
document.writeln("End of results...");
}
</script>
</head>
<body><center><br/>
Search: <input type="text" id="idSrchTerm" name="search">
<input type="button" value="search files on Google Drive" name="submitButton" onclick="displayMessage()"/>
</center>
</body>
</html>

CryptoLocker - restore Drive file version with Google Apps Scripts

long story short I got infected by the CryptoLocker Virus. My “normal” local files are not the problem because these files I backup. But I was using the Google Drive Sync client and all my Drive files got encrypted. I didn’t back them up because I thought Google Drive is save and my data is stored all over the world (my fault I know).
Now I can see that Google Drive provides versioning. This means my old uploads are still on the server. I can restore the previous version file by file but by several thousand files, good luck.
I contacted the Google G Suite support team (I’m using Google G Suite for my business) and asked them if they can restore the latest version in one bulk action. The answer was “no you have to do it file by file”. Therefore I was checking the internet for scripts, tools etc.
I found a Google Apps Script in the Google Drive help forum “https://productforums.google.com/forum/#!topic/drive/p08UBFYgFs0https://productforums.google.com/forum/#!topic/drive/p08UBFYgFs0”.
1) I added the “Google Apps Script” app to my drive.
2) I created a new app and past the script:
function testSmallFolder() {
var smallFolder = DriveApp.getFolderById('FOLDER_ID_HERE');
var files = smallFolder.getFiles();
while (files.hasNext())
{
file = files.next();
deleteRevisions(file);
}
var childFolders = smallFolder.getFolders();
while(childFolders.hasNext())
{
var childFolder = childFolders.next();
Logger.log(childFolder.getName());
var files = childFolder.getFiles();
while (files.hasNext())
{
file = files.next();
deleteRevisions(file);
}
getSubFoldersAndDelete(childFolder);
}
}
function deleteRevisions(file)
{
var fileId = file.getId();
var revisions = Drive.Revisions.list(fileId);
if (revisions.items && revisions.items.length > 1)
{
for (var i = 0; i < revisions.items.length; i++)
{
var revision = revisions.items[i];
var date = new Date(revision.modifiedDate);
var startDate = new Date();
var endDate = new Date(revision.modifiedDate);
var fileName = Drive.Files.get(fileId);
if(revision.modifiedDate > "2017-02-16T10:00:00" && revision.modifiedDate < "2017-02-18T10:00:00" && revision.lastModifyingUserName == "ENTER_MODIFIED_USERNAME_HERE]]" && file.getName() !== "HELP_DECRYPT.URL" && file.getName() !== "HELP_DECRYPT.PNG" && file.getName() !== "HELP_DECRYPT.HTML")
{
Logger.log(' %s, Date: %s, File size (bytes): %s',file.getName(),
date.toLocaleString(),
revision.fileSize);
return Drive.Revisions.remove( fileId, revision.id);
}
}
} else
{
Logger.log('No revisions found.');
}
}function getSubFoldersAndDelete(parent)
{
parent = parent.getId();
var childFolders = DriveApp.getFolderById(parent).getFolders();
while(childFolders.hasNext())
{
var childFolder = childFolders.next();
var files = childFolder.getFiles();
while (files.hasNext())
{
file = files.next();
deleteRevisions(file);
}
getSubFoldersAndDelete(childFolder);
}
return;
}
3) The script provides 3 functions “testSmallFolder” / “deleteRevisions” / “getSubFoldersAndDelete”. Looks like the function “festSmallFolder” can just work on a certain folder. Line 2: FOLDER_ID_HERE
4) I created a folder and moved my files into this folder. Afterwards I got the folder ID (URL) and added it to the script.
5) In line 37 you can add the start and end date of the modification. I also adjusted the username in the same line.
6) I saved the script and ran the “testSmallFolder” function.
7) I get an error message: “ReferenceError: "Drive" is not defined. (line 27, file "Code")“.
Line 27 looks like this: „var revisions = Drive.Revisions.list(fileId);”.
I contacted again the Google G Suite support and asked them for help regarding this error. Their answer was “Sorry we do not support scripts.”
Now I’m here guys and asking you for help. Maybe we can get this script running so that I can restore the latest working version of my files.
I really appreciate any help you can provide.
You first of all must be sure to enable Advance Drive Service as documented here:
https://developers.google.com/apps-script/advanced/drive
Go in your script page, click on Resources --> then click on Google Advances Services, then enable (must be GREEN!) Drive API.
Maybe an alert open you a page where you MUST abilitate the script to access at your drive and folders (I think only the first time).
After do that, if you put a breakpoint on the 2nd row of the script and run the debugger istruction after istruction, you pass the 27 line without any problem, and you can see in the variable stack all the string for the first file, then for the second etc....
Stop then and Run normally this time and all will be ok.
Have a good night.
Have a look at https://gitlab.com/strider/delockyfier . This is a single page app in JavaScript which you could probably run as is, or easily convert to Apps Script if that's where you would prefer to run it from.

Advanced Drive Service returning Empty Response Error when inserting file

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.