UPD:
I have a published application as Spreadsheets addon and a few spreadsheets, that use this addon and execute time driven trigger:
ScriptApp.newTrigger("myFunction")
.timeBased()
.everyHours(1)
.create();
All works fine, but I'm a little confused, in what scope does this trigger work?
Since this is a published addon, the script is one and the time driven function is also one, right? As Diego said: "It works with the user scopes of the user who initiated the trigger". Okay, but what about spreadsheets?
I need to write (if trigger sees only this spreadsheet):
function myFunction(){
var ss = SpreadsheetApp.getActive();
}
Or (if it runs without binding to a specific spreadsheet):
function myFunction(){
var ss = SpreadsheetApp.openById('<some id>');
}
In other words, when I set the trigger, do I set it only for the current document or for all?
Thanks!
It works with the user scopes of the user who initiated the trigger. Here's how you can check:
Create a User property value i and set it to zero
In a new function, get the user property i, write it to a sheet, increment i and save it back to the property.
Create a trigger so that function runs every minute.
View the spreadsheet as a different user.
As the different user, repeat steps 1 & 2 to see that now i has started again from zero, but the 1-minute trigger is still iterating the original i value (because the different user can't access the first user's properties).
Here's the code:
function getAndPrintUserProperty() {
var userProperties = PropertiesService.getUserProperties();
var i = userProperties.getProperty("i");
i++;
SpreadsheetApp.getActive().getSheetByName("Sheet1").appendRow([i]);
userProperties.setProperty("i", i);
}
function addUserProperty() {
PropertiesService.getUserProperties().setProperty("i", 0);
}
Related
This question already has an answer here:
Google Spreadsheet - Show sheets depending on type of user
(1 answer)
Closed 2 years ago.
I have a google sheets document with two tabs one called called internal and the other called external. How can i hide the internal tab from other users? the lock function already avialble is not good enough I only want people from my company to be able to see both tabs, clients should only be able to see the external tab.
function validUsers() {
String[] adminUsers = {”email1#gmail.com”,”email2#gmail.com”,”email3#gmail.com”};
if (adminUsers.indexOf(Session.getEffectiveUser().getEmail()) >= 0) {
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').showSheet()
else
SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Internal').hideSheet()
}
}
Issue:
You want to hide or show a sheet in your spreadsheet depending on which user is accessing the spreadsheet.
Solution:
You could do the following:
Install an onOpen trigger which executes a function (let's call it fireOnOpen) every time a user opens the spreadsheet.
The function fireOnOpen should check which user is accessing the spreadsheet, and hide or show a certain sheet (called Internal) depending on this.
In order to check the current user accessing the spreadsheet, you can use getActiveUser() (instead of getEffectiveUser(), which will return the user who installed the trigger).
Workflow:
The trigger can be installed either manually or programmatically. To do it programmatically, copy this function to your script editor and execute it once:
function createOnOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("fireOnOpen")
.forSpreadsheet(ss)
.onOpen()
.create();
}
This will result in fireOnOpen being executed every time a user accessed the spreadsheet. The fireOnOpen function could be something like this:
function fireOnOpen() {
const adminUsers = ["email1#gmail.com","email2#gmail.com","email3#gmail.com"];
const currentUser = Session.getActiveUser().getEmail();
const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
if (adminUsers.includes(currentUser)) internalSheet.showSheet();
else internalSheet.hideSheet();
}
Important notes:
You cannot hide sheets for some users but not for others. A hidden sheet is hidden for all users, and a visible sheet is visible for all users. Therefore, this will only work if internal and external users don't access the spreadsheet at the same time. If they do, external users might be able to access the Internal sheet.
getActiveUser() is not always populated, as you can see on this answer, so please make sure that all admin users are from the same G Suite domain. Otherwise, this won't work.
If the privacy of the Internal sheet is critical and there is a possibility of internal and external users accessing the spreadsheet at the time, I would not recommend this solution.
Edit:
As mentioned in comments, a possible workaround for the occasions when admin and non-admin users access the file at the time could be the following:
When an admin user accesses the file, store the time in which that happened.
Create a time-driven trigger to execute a function periodically (every 5 minutes, let's say), which will check if an admin accessed the file a short time ago (let's say 30 minutes). If the admin has done that, remove the Permissions for the different non-admin domains. If that's not the case, add these Permissions back.
Enabling the Drive Advanced Service would be required in this case.
Updated code sample:
function fireOnOpen() {
const adminUsers = ["email1#gmail.com","email2#gmail.com","email3#gmail.com"];
const currentUser = Session.getActiveUser().getEmail();
const internalSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Internal");
if (adminUsers.includes(currentUser)) {
internalSheet.showSheet();
const documentProperties = PropertiesService.getDocumentProperties();
documentProperties.setProperty("lastAdminAccess", new Date().getTime()); // Store time of admin access
} else internalSheet.hideSheet();
}
function createOnOpenTrigger() {
var ss = SpreadsheetApp.getActive();
ScriptApp.newTrigger("fireOnOpen")
.forSpreadsheet(ss)
.onOpen()
.create();
}
function updatePermissions() {
const fileId = SpreadsheetApp.getActive().getId();
const lastAdminAccess = PropertiesService.getDocumentProperties().getProperty("lastAdminAccess"); // Last time of admin access in ms
const now = new Date().getTime(); // Current time in milliseconds
const thirtyMinutes = 1000 * 60 * 30; // 30 minutes in milliseconds
if (now - lastAdminAccess < thirtyMinutes) {
const currentPermissions = Drive.Permissions.list(fileId)["items"];
const publicPermissionIds = currentPermissions.filter(permission => permission["type"] === "anyone")
.map(permission => permission["id"]);
publicPermissionIds.forEach(permissionId => Drive.Permissions.remove(fileId, permissionId));
} else {
const resource = {
type: "anyone",
role: "reader"
}
Drive.Permissions.insert(resource, fileId);
}
}
function createTimeTrigger() {
ScriptApp.newTrigger("updatePermissions")
.timeBased()
.everyMinutes(5)
.create();
}
As soon as you share a sheet you should assume that anyone can see the data in it. Even if someone shouldn't be able to see the internal tab, they can always e.g. make a copy of the sheet and thus get to the data.
You could try creating a separate sheet and using =IMPORTRANGE() to refer to the original one. But know that once you allow the connection between the two sheets, anyone with access to the second one might be able to access anything in the first one. Maybe get around that using three sheets:
Internal + External - your current sheet
A sheet-in-the-middle that only you can access. It has a single tab Internal that uses =IMPORTRANGE() to access data from 1)
The External sheet for clients. Linked to 2) through =IMPORTRANGE()
This way 3) only has access to the data in 2) which in turn only includes a link to 1).
I do not promise that this will make the data safe from those who shouldn't see it. But it will at least be safer.
I have 2 lists with ranges C2:F21, which should be available to fill for all users in a certain period.
Range C2:F21 in the list A shouldn't be locked from 01.01.2020 to 05.01.2020, but in another time it should be locked for all users except me.
In the list B range C2:F21 shouldn't be locked only from 01.02.2020 to 05.02.2020.
I will be very grateful for any answer
The most "secure" way to handle this would be to actually save the data as a separate spreadsheet (only you have permission to edit) on the expiration date(s) which you could also automate with a script. You could then use a series of importrange function(s) to create a list of read only information if you need the users to see it in the master sheet. Anyone who is an editor of the spreadsheet has the ability (perhaps not the knowledge) to get around your cell protection in various creative ways.
If you still insist on using cell protection to accomplish this it is very simple, use the Protection class and add a trigger to the project using the little clock in the script editor, Select event source Time-driven and time based trigger Specific date and time.
First you would need to protect the ranges, something that you can do easily through the web UI by selecting the range you want to protect, right-clicking and selecting Protect range. You can do it too via Apps Script, by running a function like this one:
function protectRangeA() {
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Change accordingly
var sheetA = ss.getSheetByName("your-sheet-name"); // Change accordingly
var protection = sheetA.getRange("C2:F21").protect();
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
This function gets a sheet with a certain name from a spreadsheet with a certain id (as others said before me, for security reasons - to avoid people messing with your script - it would be better if it was a standalone script, not bound to your spreadsheet), and protects the range C2:F21, using protect(), and you will be the only editor.
Second, you want to unprotect this range at a certain date, an protect it again at another date. To do that, you could create time-driven triggers. You could, for example, use an atDate trigger, that will run the function you specify at the year, month and day you specify. So, if you want to unprotect a range at 01.01.2020, you could, for example, have this function:
function createUnprotectTriggerA() {
ScriptApp.newTrigger("unprotectRangeA")
.timeBased()
.atDate(2020, 1, 1)
.create();
}
This will fire a function called unprotectRangeA near midnight of 01.01.2020 (+/- 15 minutes). The function that will be run at this time, then, should unprotect the desired range. It could be something on the following lines:
function unprotectRangeA() {
var ss = SpreadsheetApp.openById("your-spreadsheet-id"); // Change accordingly
var sheetA = ss.getSheetByName("your-sheet-name"); // Change accordingly
var protections = sheetA.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
}
This function uses getProtections to get all range protections that exist in the sheet and, before checking if the current user can edit them via canEdit(), it removes these with the use of remove().
And to protect it again at 05.01.2020, have this trigger, which will fire the function protectRangeA (previously defined) at this date:
function createProtectTriggerA() {
ScriptApp.newTrigger("protectRangeA")
.timeBased()
.atDate(2020, 1, 5)
.create();
}
Finally, because you want to protect/unprotect several ranges and you would need several triggers, you could call all the functions that create triggers in the same function, which you would have to run once to set all the triggers you need (I didn't define createProtectTriggerB and createUnprotectTriggerB but it would be the same as with the other range):
function createTriggers() {
createProtectTriggerA();
createUnprotectTriggerA();
createProtectTriggerB();
createUnprotectTriggerB();
}
Please beware that, as CodeCamper said, if users have permission to edit the spreadsheet, they could potentially use a script to mess up with your protections. So, depending on the people who are editing this, maybe you should have a copy of the spreadsheet that only you can access.
I hope this is of any help.
I'm trying to update a spreadsheet from a script running on another spreadsheet.
Nothing seems to have any effect on the table (SetValue(), SetBackgroundRGB(), etc.).
I've checked the scope, it includes "https://www.googleapis.com/auth/spreadsheets" permission; besides, this same script has no problem writing to another spreadsheet that it creates in runtime.
function updateAnotheSpreadsheet() {
var targetSpreadsheet = SpreadsheetApp.openById('<target spreadsheet id>');
var sheet = targetSpreadsheet.getSheetByName('<sheet name>');
Browser.msgBox(sheet.getRange("A1").getValue()); // Here I see that my getSheetByName worked
sheet.getRange("A1").setValue('Test value'); // But this does nothing
}
There are no errors but also no effect: nothing changes in the target spreadsheet.
Hi I was testing and was able to do what you are trying to do. You can try to declare a variable for the message, here is what I was able to do, I hope this resolves your inquiry.
function updateAnotherSpreadsheet() {
//Opening the second Spreadsheet by ID
var targetSpreadSheet = SpreadsheetApp.openById("<SpreadSheetId>");
var sheet = targetSpreadSheet.getSheetByName("<SheetName>");
//Getting the range to show on msgBox.
var msg = sheet.getRange("A1:A1").getValue();
//Displaying old data on A1:A1 from the secondary Spreadsheet.
Browser.msgBox("Old data on secondary spreadsheet: " + msg);
//Getting the range and setting new values
sheet.getRange("A1:A1").setValue('Test value').setBackground("teal").setFontColor("white");
}
I would suggest to check for more information here https://developers.google.com/apps-script/guides/sheets.
I hope this helps, greetings.
I found the problem.
A function called from OnEdit() can't ask for permissions. I needed to first update that other spreadsheet from any function called by something "active", like a button; then, after the script asks for permission once, it can do its thing from OnEdit() the next time it runs.
Please help. This is all new to me.
I have a protected sheet in a google spreadsheet that I want to leave protected. However, my script deletes rows and adds rows to that protected sheet. It works great for me, but other other users cannot run the script.
I tried creating an onOpen trigger that calls a function which uses the addEditor method for those users. However, that did not work. To test my trigger, I changed it to the following which calls a function called UserInfoFunction, also shown below , wherein I get a messagebox telling be who the Effective user is and who the Active User is. I thought the Effective user should have been me since I created the script. But when others run this the message box shows they are the Effective user which, I'm assuming is why my addEditor function is not working since the Effective user wouldn't have permissions to change the protection...What am I doing wrong.
function createSpreadsheetOpenTrigger() {
var ss = SpreadsheetApp.openById(".......");
ScriptApp.newTrigger(UserInfoFunction())
.forSpreadsheet(ss)
.onOpen()
.create();
}
function UserInfoFunction() {
var me = Session.getEffectiveUser();
var them = Session.getActiveUser();
SpreadsheetApp.getUi().alert('me is equal to...'+ me + ' user is equal to ' + them);
}
So I'm using a script on my googledoc spreadsheet that goes through a column and counts a certain number of occurrences of specified formatting. (i.e. in my spreadsheet "=myFunction()")
The function works fine, but my problem is despite setting up a trigger to run the script "onEdit", it never does it. I have to open the script up and save it each time I want it to update on my spreadsheet.
I've been looking up for hours but no one seems to have my question. There are no errors sent by the notifications. The code (though I don't think it's terribly relevant) for my function is:
function CountIfNotStrikeThrough2()
{
var ss = SpreadsheetApp.getActiveSpreadsheet();
var mysheet = ss.getActiveSheet();
var mydatarange = mysheet.getRange(1,1,390,1);
var numRows = mydatarange.getLastRow();
var rowindex = mydatarange.getRowIndex();
var columnindex = mydatarange.getColumnIndex();
var total =0;
for(i=rowindex;i<=numRows;i++)
{
if(mydatarange.offset(i-1, columnindex-1, 1, 1).isBlank() != true && mydatarange.offset(i-1, columnindex-1, 1, 1).getFontLine() != "line-through")
{
total++;
}
}
return total;
}
I know there's a thread here that has a comprehensive list of when the onEdit trigger doesn't activate. (here) For example, if you setup Data Validation and select an item from the list of values in the validation, it doesn't trigger the onEdit function.
Besides that, could you try a simple trigger instead of installable? This is to say, instead of explicitly associating CountIfNotStrikeThrough2() to the onEdit trigger, please try re-naming the function to "onEdit()" instead and see if that works. This creates an implicit or simple trigger. I've had trouble in the past with installable vs. simple triggers.
Also, please share the exact action you're performing to test the trigger.
Reference: https://developers.google.com/apps-script/understanding_triggers