Protecting script Google Spreadsheet through standalone Apps Script - google-apps-script

I have a spreadsheet that I share with other people trough a link to make a copy.
In that spreadsheet I have a script and I don't want that other people can see my script.
To protect my script from this spreadsheet I moved it to a standalone spreadsheet.
In my spreadsheet I use a Trigger.
My standalone script starts with:
function onEdit(e) {
...
This script changes some cells when cell G13 changes.
My script in my spreadsheet is:
function createSpreadsheetOpenTrigger() {
const ss = SpreadsheetApp.openById("1Ik3ywr00UIQH9lwvEPYI1wi6xVpBDN8QVtZabtG96gY");
ScriptApp.newTrigger('onEdit')
.forSpreadsheet(ss)
.onEdit()
.create();
}
If I change cell G13 in my spreadsheet, nothing happens.
What do I do wrong?
Documentation: https://developers.google.com/apps-script/guides/triggers/installable#managing_triggers_programmatically

In your script of createSpreadsheetOpenTrigger() in the container-bound script of Spreadsheet, onEdit is put in a standalone script. In this case, unfortunately, onEdit in the standalone script cannot be run from Spreadsheet. I think that this might be the reason for your current issue.
Pattern 1:
In this pattern, your showing script is modified. I thought that this pattern might be close to your expected situation.
1. Container-bound script side.
In this case, please remove your script from your container-bound script. In this pattern, only a standalone script is used.
Also, in this case, even when Spreadsheet has no container-bound script, this pattern can be used. Only the below standalone script can be used.
2. Standalone script side.
Please modify your standalone script as follows. Please set the spreadsheet ID of your Spreadsheet to spreadsheetId and save the script.
function createSpreadsheetOpenTrigger() {
const spreadsheetId = "###"; // Please set the spreadsheet ID of your Spreadsheet.
const ss = SpreadsheetApp.openById(spreadsheetId);
ScriptApp.newTrigger('installedOnEdit').forSpreadsheet(ss).onEdit().create();
}
// In this pattern, please set your script in this function.
function installedOnEdit(e) {
e.range.setValue(JSON.stringify(e));
}
3. Install OnEdit trigger.
In order to install OnEdit trigger, please run createSpreadsheetOpenTrigger. By this, the OnEdit trigger is installed.
4. Testing.
Please edit the cell in your Spreadsheet. By this, the script of the standalone script is run, and you can see the event object in the edited cell.
And, in this case, the other users cannot see your standalone script. I thought that this might be your expected situation.
Note:
As an important point, it supposes the following sample situation.
User "A" is the owner of Google Spreadsheet.
User "B" has the writer permission for the Spreadsheet of user "A".
Under this condition, when user "A" creates a standalone script including the following script. And, run createSpreadsheetOpenTrigger.
function createSpreadsheetOpenTrigger() {
const spreadsheetId = "###"; // Spreadsheet ID of the owner's spreadsheet.
const ss = SpreadsheetApp.openById(spreadsheetId);
ScriptApp.newTrigger('installedOnEdit').forSpreadsheet(ss).onEdit().create();
}
function installedOnEdit(e) {
e.range.setValue("ok1");
}
And, when user "B" creates a standalone script including the following script. And, run createSpreadsheetOpenTrigger.
function createSpreadsheetOpenTrigger() {
const spreadsheetId = "###"; // Spreadsheet ID of the owner's spreadsheet.
const ss = SpreadsheetApp.openById(spreadsheetId);
ScriptApp.newTrigger('installedOnEdit').forSpreadsheet(ss).onEdit().create();
}
function installedOnEdit(e) {
e.range.offset(0, 1).setValue("ok2");
}
In the above situation, when user "A" or "B" edit a cell of the Spreadsheet, the values of ok1 and ok2 are put into the edited cell and the right side of the edited cell. From this situation, it seems that both script are run to the same Spreadsheet. It is considered that when other users have the write permission, when the above method is used, the edited information can be retrieved.
Pattern 2:
In this pattern, a library is used.
From your situation, if you have both the standalone script and the container-bound script of Spreadsheet, how about using the standalone script as a library? When the standalone script is used as the library and it is loaded on the container-bound script of Spreadsheet, the script of the standalone script can be seen by only you. Other users cannot see it. I thought that this might be your expected situation.
When this is reflected in a sample script, please do the following flow.
1. Prepare a standalone script.
Please copy and paste the following script to the script editor of the standalone script.
function myFunction(e) {
return {message: "From hidden script.", object: e};
}
If you want to hide the script, please write it to this project.
2. Deploy standalone script as a library.
Please deploy the standalone script as a library. You can see the official document at here. In this case, it is not required to share the standalone script. I think that this point is for your goal.
3. Prepare a container-bound script.
Please copy and paste the following script to the script editor of the container-bound script of Spreadsheet. And, please install the library. Ref And, in this sample, please install the library as Lib. By this, the library is used.
And, please install the OnEdit trigger to the function installedOnEdit.
function installedOnEdit(e) {
const res = Lib.myFunction(e);
e.range.setValue(JSON.stringify(res));
}
4. Testing.
Please edit a cell of the Spreadsheet. By this, installedOnEdit is run by the installable OnEdit trigger, and you can see message: "From hidden script." in a cell. And, installedOnEdit uses the script of Lib. And, other users cannot see the library. I thought that this might be your expected situation.
References:
Installable Triggers
Libraries

Related

Adding an existing app script to google sheets

Is it possible to add an existing app script to a newly created googlesheet using app script? And automatically assigning it to a trigger?
For example I have a spreadsheet call spreedsheetA. Then my form will create spreedsheetB, is it possible to automatically add my app script to spreedsheetA to spreedsheetB with out copy + pasting it manually. All by using the appscript in my form.
As a workaround to what #Tanaike proposed you can use the Drive API to make the copy. Because bounded scripts are also copied when you perform a makeCopy() operation.
Steps:
Create a new sheet and add a bounded script via Extensions > Apss Script. Take note of the ID, in the example I will call it SSA_ID
Add your script. As probe of concept I just added this simple one:
function onOpen(e) {
SpreadsheetApp
.getUi().alert('Hi from bounded script')
}
Create a new script, and paste this code inside:
function copySpreadSheet() {
const file = DriveApp.getFileById(SSA_ID)
const newFile = file.makeCopy(`SpreadSheetCopy_${new Date().toISOString()}`)
Logger.log(newFile.getUrl())
}
Run the script, grab the url and copy paste it in your browser. You will see that it contains a copy of the bounded script.
From there you can manipulate the copy and add it to an Installable Trigger, for example:
ScriptApp.newTrigger('copySpreadSheet')
.timeBased()
.everyHours(6)
.create();
Documentation:
newTrigger(functionName)
ClockTriggerBuilder

How to access data on different Google Spreadsheet through Google Apps Script for user without Editor access?

I have 2 Spreadsheets, the 1st will Search on the 2nd spreadsheet with data using a google script function.
I want to keep the 2nd spreadsheet with data to be hidden (no editor access) from a user, but he/she will be able to Search on it via the google script function only.
I'm using google script Openbyurl to do it, but it won't let this user to run the Openbyurl unless he/she has editor access to the 2nd spreadsheet.
how should I deal with this?
Below function is in the 1st Spreadsheet, openByUrl links to 2nd Spreadsheet:
function onSearch(SN) {
var ss = SpreadsheetApp.openByUrl('docs.google.com/spreadsheets/...');
var sheets = ss.getSheets();
// search for data in ss sheets . . .
// return array of found data
}
I believe your goal is as follows.
You have 2 Google Spreadsheets "A" and "B".
You want to make the user retrieve values from Spreadsheet "B" with the script of Spreadsheet "A".
You don't want to share Spreadsheet "B" while Spreadsheet "A" is shared with the user.
In this case, as a workaround, how about accessing Spreadsheet "B" using Web Apps? By this, you can make the user access Spreadsheet "B" without sharing the Spreadsheet with the user. When this is reflected in your script, it becomes as follows.
Flow:
1. For Spreadsheet "B":
Please copy and paste the following script to the script editor of Spreadsheet "B".
function doGet(e) {
var SN = e.parameter.sn; // You can use the value of SN here.
var ss = SpreadsheetApp.getActiveSpreadsheet(); // Your 2nd Spreadsheet.
var sheets = ss.getSheets();
// search for data in ss sheets . . .
// return array of found data
var returnValues = ["sample"]; // Please replace this value for your actual script.
return ContentService.createTextOutput(JSON.stringify(returnValues));
}
2. Deploy Web Apps.
Please run this flow on the script editor of Spreadsheet "B". The detailed information can be seen at the official document.
On the script editor, at the top right of the script editor, please click "click Deploy" -> "New deployment".
Please click "Select type" -> "Web App".
Please input the information about the Web App in the fields under "Deployment configuration".
Please select "Me" for "Execute as".
This is the importance of this workaround.
Please select "Anyone" for "Who has access".
In this case, the user is not required to use the access token. So please use this as a test case.
Of course, you can also access your Web Apps using the access token. Please check this report.
Please click "Deploy" button.
Copy the URL of the Web App. It's like https://script.google.com/macros/s/###/exec.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
3. For Spreadsheet "A":
Please copy and paste the following script to the script editor of Spreadsheet "A". And, set your Web Apps URL. In this case, please give the value to SN.
function onSearch(SN) {
var url = "https://script.google.com/macros/s/###/exec"; // Please set your Web Apps URL here.
var res = UrlFetchApp.fetch(`${url}?sn=${SN}`);
var ar = JSON.parse(res.getContentText()); // This is the returned value from Spreadsheet "B".
// do something.
}
4. Testing:
When you run the script of onSearch, the value of SN is sent to Spreadsheet "B" and run the script of Spreadsheet "B", and the result values are returned. By this flow, you can retrieve the values from Spreadsheet "B" without sharing Spreadsheet "B" with the user.
Note:
In this sample script, when you directly run onSearch, the value of SN is not declared. So please be careful about this.
When you modified the Google Apps Script, please modify the deployment as a new version. By this, the modified script is reflected in Web Apps. Please be careful this.
You can see the detail of this in the report of "Redeploying Web Apps without Changing URL of Web Apps for new IDE".
My proposed script is a simple script. So please modify it for your actual situation.
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script

Run Apps Script on a Sheet not in Current Workbook - Google Sheets / Apps Script

I would like to be able to run a script in another spreadsheet outside of the current spreadsheet I am in.
I tried the below script but the script is not in the current spreadsheet editor
function RunScriptInAnotherSpreadsheet() {
var target = SpreadsheetApp.openById("sheetID");
//runs the below script
relocationtomaster();
}
am I dreaming ?
I understand a trigger would do that in the target sheet - just wanted to do it manually
any help would be appreciated
Publish the main script(with implementation of relocationtomaster) as library. Then add this library as dependency to the script you want this code run.
Function relocationtomaster will be available in context of dependant script.
Example main script(publish as Library)
function relocationtomaster(id){
var ss = SpreadsheetApp.open(id);
Logger.log(ss.getActiveSheet().getDataRange().getValues())
}
Example dependant script(include above script as library)
function run(){
relocationtomaster('1MXsIX_SprLSimqNDUlDkEvWhtQp8Kz0By1IaA5JfkSA'); // Sheet id
}
You can pass spreadsheetid as parameter.
AFAIK you have to publish them as a Library and call it from there, you can have a look here how to run google app script function from another project
Mind that that id could affect to the performance of the script.

Google script that triggers copy of a range to another spreadsheet - NOT WORKING for me

I have an issue with Google Apps Script that should trigger the copy of a range to another Google spreadsheet. The strange thing here is that the script is perfectly working if started through the Script Editor. But it's not working through the trigger (onEdit).
The script itself is much longer and everything related to data processing within one and the same spreadsheet is triggered successfully. Only the copy of a specific range to another spreadsheet is not working. But as I mentioned, it's working if I run it through the Script Editor.
Please see below a simple representation of my script:
function onEdit(e){
var sourceFile1 = SpreadsheetApp.getActiveSpreadsheet();
var sheet = sourceFile1.getActiveSheet();
var selectedRange = sheet.getRange(44,1, 1, 29);
var sourceData = selectedRange.getValues();
var destinationFile = SpreadsheetApp.openById(" ");
var destinationSheet = destinationFile.getSheetByName("Orders");
var lastRow = destinationSheet.getLastRow();
lastRow = lastRow + 1;
destinationSheet.getRange(lastRow, 1, 1, 29).setValues(sourceData);
}
In your script, when you run onEdit() with the script editor, it works.
But when the function is run as the OnEdit trigger, it doesn't work.
In this situation, you are owner of Spreadsheet and Script.
If my understanding is correct, how about this answer?
I think that in your script, when the OnEdit event is run, an error related to the authorization occurs at var destinationFile = SpreadsheetApp.openById(" ");. When you want to confirm this, please check the execution transcript of script editor after the function is run as the OnEdit trigger.
So as a solution, how about installing the function as the installable Triggers?
Note:
In your case, because the error of authorization occurs, you can install the function name onEdit() as the installable Triggers. But I recommend to modify the function name from onEdit() to other. For example, it is like installed_onEdit().
Because the function of function name of onEdit() is run as a simple trigger, when onEdit() is installed as the installable Triggers, onEdit() run 2 times as the simple trigger and the installable Triggers.
References:
Execution transcript
Managing triggers manually
If I misunderstood your question and this was not the solution of your issue, I apologize.

How to run or link an OnEdit standalone trigger in a google spreadsheet

I'm developing new applications in google.
I have a function that is running without any problem if it's bound in the spreadsheet but when it is offline it doesn't work. That's why I saved this script in google drive in order to be called from my google spreadsheet even if it's offline.
I don't know how I link this function to my spreadsheet in order to run both offline.
function onEdit(event)
{
var DistSheet = event.source.getActiveSheet();
...
}
If you want to use a simple trigger like onEdit, it is unfortunately not possible (see the documentation: onEdit is a Simple Trigger, which is restricted to Bound scripts).
However, you can accomplish this with Installable Triggers.
Code for stand alone script
ScriptApp.newTrigger('myFunction')
// insert the file ID for the spreadsheet you'd like to edit
.forSpreadsheet('1234567890abcdefghijklmnopqrstuvwxyz')
.onEdit()
.create();
function myFunction() {
var DistSheet = event.source.getActiveSheet();
// continue script
}
Once you've added this to your script, click the right arrow ("play") icon. It will ask for the proper authorization and then should work after that.
You can see the documentation on Installable Triggers for more information.