Copying a spreadsheet copies all linked files as well - google-apps-script

I want to be able to only copy the spreadsheet and all it's sheets along with all defined sheet names, when I utilize the library method:
spreadSheet.copy(newSSName);
Or,
myFile.makeCopy(newNameOfFile);
Currently these methods copy all linked forms and scripts used in the forms. This is an unnecessary side effect for what I need and will result in a large mess in the Drive folder. Is there a way to do this quickly and efficiently without copying cell by cell, sheet by sheet? Or is that the only option?
Thanks.

How about this workaround? In this workaround, Sheets API is used for copying a Spreadsheet. In the case of copy() of Class Spreadsheet, makeCopy() of Class File and Files: copy of Drive API, the copied spreadsheet includes the bound scripts and linked forms. So I thought to use Sheets API. The flow of this workaround is as follows.
Retrieve object of source Spreadsheet using spreadsheets.get.
Create new Spreadsheet by including the retrieved object using spreadsheets.create.
By this flow, the copied Spreadsheet without including the bound scripts and linked forms can be created. The sample script is as follows. When you use this script, please enable Sheets API at Advanced Google Services and API console. You can see about how to enable Sheets API at here.
Sample script :
var fileId = "### fileId of source Spreadsheet ###"; // Please set here.
var obj = Sheets.Spreadsheets.get(fileId, {fields: "namedRanges,properties,sheets"});
Sheets.Spreadsheets.create(obj);
Note :
When you use this script, please set fileId of source Spreadsheet.
At spreadsheets.create of Sheets API, the Spreadsheet cannot be created in the specific folder. So the copied Spreadsheet is created to root folder. If you want to create it in the specific folder, please move it after the Spreadsheet was copied. Of course, you can do it using script.
If you want to include developerMetadata of Spreadsheet, please add it to fields.
References :
copy() of Class Spreadsheet
makeCopy() of Class File
Files: copy of Drive API
Method: spreadsheets.get of Sheets API
Method: spreadsheets.create of Sheets API
If this was not what you want, I'm sorry.

Copy Spreadsheet to Destination Folder without copying Scripts
function copyspreadsheetwithoutgoogleappsscript() {
const ssid = "";//source spreadsheet id
const fldrid = "";//destination folder id
Drive.Files.update({ parents: [{ id: fldrid }] }, Sheets.Spreadsheets.create(Sheets.Spreadsheets.get(ssid,{fields:"sheets,namedRanges,properties"})).spreadsheetId);
}
for sub properties use /
Working With Field Masks
available fields between spreadsheets and spreadsheet properties
Can also do it using the API endpoint if you wish:
function copyssurl() {
const ssid = "";
const dfldrid = "";
const url = `https://sheets.googleapis.com/v4/spreadsheets/${ssid}?fields=(sheets%2CnamedRanges%2Cproperties)`;
const options = { "method": "get", "muteHttpExceptions": true, "headers": { "Authorization": "Bearer " + ScriptApp.getOAuthToken() } };
const resp = UrlFetchApp.fetch(url,options);
const obj = JSON.parse(resp.getContentText());
Drive.Files.update({ parents: [{ id: dfldrid }] }, Sheets.Spreadsheets.create(obj).spreadsheetId);
}
%2C is a comma
%2F is a forward slash

Related

Name Images Exported From GoogleSheets With Specific ID Pulled From Corrisponding Cell

I have a Google Sheet spreadsheet containing personal data I collect from people who subscribe to my association. They have to complete an online form and sign it. The data is then sent to the spreadsheet and the signature is imported as a PNG in-cell-image.
I need to extract all the PNG signatures and assign them the specific ID found in the same row so I can later match the signature with the correct personal data when generating a PDF form with another script.
ID
Signature
1a2b3c4d
image.png
5e6f7g7h
image.png
I am currently using the following code I found online. It saves all the images to a folder as PNG files but it assigns names like "image-1", "image-2" in a random order.
Here is the code:
function myFunction() {
const spreadsheetId = SpreadsheetApp.getActiveSpreadsheet().getId();
const url = "https://docs.google.com/spreadsheets/export?exportFormat=xlsx&id=" + spreadsheetId;
const blob = UrlFetchApp.fetch(url, {headers: {authorization: `Bearer ${ScriptApp.getOAuthToken()}`}}).getBlob().setContentType(MimeType.ZIP);
const xlsx = Utilities.unzip(blob);
xlsx.forEach(b => {
const name = b.getName().match(/xl\/media\/(.+)/);
if (name) DriveApp.getFolderById("1mdJbbG_0aF8wjEIuVPsMr9jV31wPINRk").createFile(b.setName(name[1]));
});
}
How can I edit the code to name each file with the corresponding ID?
Thanks a lot!
EDIT:
I collect data from an online form which is displayed in the image below.
Online Form
When clicking on the signature field, a signature pad opens and allows the user to sign.
Signature Pad
Collected data are then sent to the following spreadsheet stored in Google Drive.
Spreadsheet
The script which sends data from the form to the spreadsheet should be the following
function submit(data) {
data = JSON.parse(data)
const headers = SETTINGS.HEADERS.map(({value}) => value)
const id = Utilities.getUuid()
const signatures = []
const values = SETTINGS.HEADERS.map(({key}, index) => {
if (key === "id") return id
if (key === "timestamp") return new Date()
if (!key in data) return null
if (Array.isArray(data[key])) return data[key].join(",")
if (data[key].startsWith("data:image")) {
signatures.push(index)
return SpreadsheetApp.newCellImage().setSourceUrl(data[key]).build().toBuilder()
}
return data[key]
})
const ws = SpreadsheetApp.getActive().getSheetByName(SETTINGS.SHEET_NAME.RESPONSES) || SpreadsheetApp.getActive().insertSheet(SETTINGS.SHEET_NAME.RESPONSES)
ws.getRange(1,1, 1, headers.length).setValues([headers])
const lastRow = ws.getLastRow()
ws.getRange(lastRow + 1, 1, 1, values.length).setValues([values])
signatures.forEach(index => {
ws.getRange(lastRow + 1, index + 1).setValue(values[index])
})
return JSON.stringify({success: true, message: `Grazie per la tua richiesta di iscrizione! ID: ${id}`})
}
The need is to rename the signature image with the submission ID.
In that way, in theory, when I run Tanaike's script to extract the images from the spreadsheet, they should be named with the ID of the corresponding form submission.
As of now, when I run Tanaike's script I get the following output.
Tanaike's script output
Thanks a lot!
I believe your goal is as follows.
You want to export the images of column "O" in Google Spreadsheet. In this case, you want to use the values of column "B" as the filename.
The image is put into the cells as CellImage.
Modification points:
Using XLSX data converted from Spreadsheet, when the image files are directly retrieved from XLSX data, unfortunately, the images cannot correspond to each cell coordinate. I thought that this is the reason of your issue. In this case, it is required to parse the XLSX data. But, I thought that in this case, the script might be a bit complicated. So, in order to retrieve the image data from the XLSX with the cell coordinate, I have created a Google Apps Script library. Ref
In this answer, I would like to propose a sample script using the library.
Usage:
1. Install Google Apps Script library.
Please install DocsServiceApp Google Apps Script library. You can see how to install it at here.
2. Sample script.
Please copy and paste the following script to the script editor of Spreadsheet. And, please set the variables of folderId and sheetName.
function myFunction() {
const folderId = "###"; // Please set folder ID you want to put the created files.
const sheetName = "Sheet1"; // Please set your sheet name.
// Retrieve image data.
const ss = SpreadsheetApp.getActiveSpreadsheet();
const res = DocsServiceApp.openBySpreadsheetId(ss.getId()).getSheetByName(sheetName).getImages();
// Retrieve IDs from from column "B"
const folder = DriveApp.getFolderById(folderId);
const sheet = ss.getSheetByName(sheetName);
const values = sheet.getRange("B1:B" + sheet.getLastRow()).getValues();
// Create files.
res.forEach(({ range, image }) =>
folder.createFile(image.blob.setName(`${values[range.row - 1][0]}.png` || image.blob.getName()))
);
}
When this script is run, the image data is retrieved and created as the image file using the filename retrieved from column "B".
Note:
In this sample script, from your provided sample image, it supposes that the image data and the filename are put in the columns "O" and "B", respectively. Please be careful about this.
Reference:
DocsServiceApp

Google Apps Script - How to change the code to create Google Sheets file in active Google Drive Shared folder rather than root (My Drive) folder?

I have some code below that takes all the tabs (excluding 2 of them) in a Google Sheets file and creates independent Google Sheets files for each of them. Right now, they are saving by default in root folder (My Drive), but I would like them to be created in the same active folder as the file from which I call my Google Apps Script. Does anyone know how I can do that with the below code? I also want to avoid scenarios where it's created first in My Drive and then moved to the Shared Drive. The reason for this is because other people may run this code and I want to avoid an instance where the folder in My Drive needs to be updated in the code based on who runs this script.
The simplified version of my above paragraph for brevity: How can I adapt the below code so that the Google Sheets files that are created show up in the active Google Shared Drive folder? I will enabling the Drive API.
My Current Code:
function copySheetsToSS(){
var ss = SpreadsheetApp.getActive();
for(var n in ss.getSheets()){
var sheet = ss.getSheets()[n];
var name = sheet.getName();
if(name != 'ControlTab' && name != 'RawData'){
var alreadyExist = DriveApp.getFilesByName(name);
while(alreadyExist.hasNext()){
alreadyExist.next().setTrashed(true);
}
var copy = SpreadsheetApp.create(name);
sheet.copyTo(copy).setName(name);
copy.deleteSheet(copy.getSheets()[0]);
}
}
}
In your situation, when your script is modified, how about the following modification?
Modified script:
In this modification, Drive API is used. So, please enable Drive API at Advanced Google services.
function copySheetsToSS() {
var ss = SpreadsheetApp.getActive();
var folderId = DriveApp.getFileById(ss.getId()).getParents().next().getId();
for (var n in ss.getSheets()) {
var sheet = ss.getSheets()[n];
var name = sheet.getName();
if (name != 'ControlTab' && name != 'RawData') {
var alreadyExist = DriveApp.getFilesByName(name);
while (alreadyExist.hasNext()) {
alreadyExist.next().setTrashed(true);
}
var newSS = Drive.Files.insert({ title: name, mimeType: MimeType.GOOGLE_SHEETS, parents: [{ id: folderId }] }, null, { supportsAllDrives: true });
var copy = SpreadsheetApp.openById(newSS.id);
sheet.copyTo(copy).setName(name);
copy.deleteSheet(copy.getSheets()[0]);
}
}
}
In this modification, first, the parent folder of Spreadsheet is retrieved. And, in the loop, the new Spreadsheet is created to the specific folder using Drive API.
Note:
In this case, it is required to have the write permission of the folder. Please be careful about this.
References:
getFileById(id)
getParents()
Files: insert

Get Number of Sheets inside Google Sheets using Google Apps Script

I'm new to using Google Apps Script, I still need to read the documentation.
I have a list of sheets that are inside a folder, in this list (all Excel files), and these fields (GDrive_ID, File_Name, Full_Path, URL), I would like a function to read either the ID of the file or URL and count the number of sheets within each Google Workbook.
I found this code on a forum and it served, only for the current worksheet, it would be possible to adapt it to search for this data inside the cell (which is actually the address for the worksheets), having with reference the ID, URL or name of the worksheet source?
I'm using this code in current file:
function getNumSheets() {
return SpreadsheetApp.getActiveSpreadsheet().getSheets().length;
}
Count Sheets and Sheets
Convert All Files to Google Sheets: https://stackoverflow.com/a/56073634/7215091
function countSheetsAndSheets() {
const folder = DriveApp.getFolderById("folderid");
const files = folder.getFilesByType(MimeType.GOOGLE_SHEETS);
const shts = [];
while (files.hasNext()) {
let file = files.next();
let ss = SpreadsheetApp.openById(file.getId());
shts.push({ "name": file.getName(), "id": file.getId, "sheets": ss.getSheets().length })
}
//Logger.log(JSON.stringify(shts));
return JSON.stringify(shts);
}

Google script API - How to create a google doc in a particular folder

Basically this question is giving an answer for what I'd like to do:
Google script to create a folder and then create a Google Doc in that folder
But it's 5 years old, and I was wondering if there was an easier way now. In my case I have lots of existing folders and I want to create a particular file in each of them via a script that runs at intervals.
Thx.
EDIT:
Ok, the code below works fine (using a modified sample script 1 in the answer) when the folder 'test folder' is in a shared drive.
function testFunction() {
const notesFileName = 'test doc';
const folderName = 'test folder';
const query = "title = '" + folderName + "'";
// Find all folders that match the query.
var folders = DriveApp.searchFolders(query);
while (folders.hasNext()) {
// In all the sub-folders, of each found folder, create a file with the same name.
var folder = folders.next();
Logger.log("Found a folder: " + folder);
var subFolders = folder.getFolders();
while( subFolders.hasNext() ) {
var subFolder = subFolders.next();
Logger.log('Folder id: ' + subFolder.getId());
const doc = Drive.Files.insert({title: notesFileName, mimeType: MimeType.GOOGLE_DOCS, parents: [{id: subFolder.getId()}]}, null, {supportsAllDrives: true});
}
}
}
I believe your goal as follows.
You want to create new Google Document to the specific folders.
You have a lot of folders you want to create new Google Document. So you want to reduce the process cost of the script.
In this case, I would like to propose to create the new Google Document using Drive API. When Drive API is used, the creation of Google Document to each folder can run with the asynchronous process. The sample script is as follows.
Sample script 1:
Before you use this script, please enable Drive API at Advanced Google services. In this sample script, the new Google Documents are created to the specific folders in a loop.
function sample1() {
const titleOfDocument = "sample document";
const folderIds = ["### folder ID1 ###", "### folder ID 2 ###",,,];
const documentIds = folderIds.map(id => Drive.Files.insert({title: titleOfDocument, mimeType: MimeType.GOOGLE_DOCS, parents: [{id: id}]}).id);
console.log(documentIds)
}
In this script, the created Document IDs are returned.
If the process time of this script was long in your actual situation, please test the following sample script 2.
Sample script 2:
Before you use this script, please enable Drive API at Advanced Google services. In this sample script, the new Google Documents are created to the specific folders using the batch request of Drive API using a Google Apps Script library. By this, the tasks are run with the asynchronous process.
Please install the Google Apps Script library for using the batch request. Ref
function sample2() {
const titleOfDocument = "sample document";
const folderIds = ["### folder ID1 ###", "### folder ID 2 ###",,,];
const requests = {
batchPath: "batch/drive/v3",
requests: folderIds.map(id => ({
method: "POST",
endpoint: `https://www.googleapis.com/drive/v3/files`,
requestBody: {name: titleOfDocument, mimeType: MimeType.GOOGLE_DOCS, parents: [id]},
})),
accessToken: ScriptApp.getOAuthToken(),
};
const result = BatchRequest.EDo(requests); // Using GAS library
const documentIds = result.map(({id}) => id);
console.log(documentIds)
}
Note:
For above sample scripts, if you want to retrieve the folder IDs under the specific folder, you can also use for folderIds as follows.
const topFolderId = "### top folder ID ###";
const folders = DriveApp.getFolderById(topFolderId).getFolders();
const folderIds = [];
while (folders.hasNext()) {
folderIds.push(folders.next().getId());
}
By the way, in the current stage, in order to move the file, you can use moveTo(destination). Using this, the script of this can be modified as follows.
function createDoc(folderID, name) {
var folder = DriveApp.getFolderById(folderID);
var doc = DocumentApp.create(name);
DriveApp.getFileById(doc.getId()).moveTo(folder);
return doc;
}
References:
Files: insert of Drive API v2
Files: create of Drive API v3
BatchRequest

Set Google Drive file modification time from Google App Script

I can't find a way to change modification date on a file on Google Drive using Google Apps Script.
After I do a file.makeCopy(newFile, newFolder), I would like to make the modification time on the new copy the same as the original file.
I can't find documented way to do this...
You want to modify the modified time of the file on Google Drive.
You want to achieve this using Google Apps Script.
If my understanding is correct, how about this answer? Please think of this as just one of several possible answers.
In this answer, I used the method of Files: update in Drive API v3.
Sample script:
Before you use this script, please set the variables of newModifiedTime and fileId.
function myFunction() {
var newModifiedTime = "2019-01-01T00:00:00.000Z"; // Please set the new modified time.
var fileId = "###"; // Please set the file ID you want to modify the modified time.
var url = "https://www.googleapis.com/drive/v3/files/" + fileId;
var params = {
method: "patch",
headers: {Authorization: "Bearer " + ScriptApp.getOAuthToken()},
payload: JSON.stringify({modifiedTime: newModifiedTime}),
contentType: "application/json",
};
UrlFetchApp.fetch(url, params);
// DriveApp.createFile(blob);
}
Note:
The last line of // DriveApp.createFile(blob); is used for automatically adding the scope of https://www.googleapis.com/auth/drive and enabling Drive API.
In this case, please use the date string of RFC 3339 date-time. It's like 2019-01-01T00:00:00.000Z.
Although I tested this for Drive API v2, it seems that Drive API v2 cannot modify the modified date of the file.
References:
Files: update(Drive API v3)
Class UrlFetchApp
If I misunderstood your question and this was not the result you want, I apologize.
There is a way to modify the modifiedTime with the advanced Drive service (don't forget to turn it on in the project settings).
function setModifiedDate(originalFile /* DriveApp.File */, copyFile /* DriveApp.File */) {
/* based on the Drive API v2 */
Drive.Files.patch({
modifiedDate: originalFile.getLastUpdated().toISOString()
}, copyFile.getId(), {
setModifiedDate: true
})
}
This changes the lastUpdated() date and the Last Modified date on the Drive. The file I'm using is just an ascii text file.
function modifyFile() {
var file=DriveApp.getFileById("fileId");
Logger.log(file.getLastUpdated());
var content=file.getBlob().getDataAsString();
content+='\nThis is a new line';
file.setContent(content);
Logger.log(file.getLastUpdated());
}
This changes the Last Modified Date of a Google Doc
function modifyADocFile() {
var file=DriveApp.getFileById("Document Id");
var doc=DocumentApp.openById(file.getId());
var body=doc.getBody();
var bodytext=body.getText();
bodytext+='\nThis is a new line';
body.setText(bodytext);
doc.saveAndClose();
}