I am sharing a Google sheet with others with sharing enabled only with email ids. I have created a script that when run duplicates a master sheet and sets permission and protections as in the master sheet into the duplicated sheet. There is a button placed to execute this script in the master sheet.
When I (Owner) runs the script the protections get copied fine and other users are unable to access the protected cells but the problem arises when other editors of the sheet run the script the new sheet created gives them full access to edit all protected fields also.
The code i have written is as under:
function Protect() {
var spreadsheet = SpreadsheetApp.getActive();
var myValue = SpreadsheetApp.getActiveSheet().getSheetName();
spreadsheet.duplicateActiveSheet();
var totalSheets = countSheets() - 3;
myValue = "DO" + totalSheets;
SpreadsheetApp.getActiveSpreadsheet().renameActiveSheet(myValue);
var protection = spreadsheet.getActiveSheet().protect();
protection.setUnprotectedRanges([spreadsheet.getRange('C2:E5'), spreadsheet.getRange('C6:D6'), spreadsheet.getRange('F5:G5'), spreadsheet.getRange('F6:G6'), spreadsheet.getRange('B9:B18'), spreadsheet.getRange('C9:G18'), spreadsheet.getRange('D20:D22'), spreadsheet.getRange('G20:G22'), spreadsheet.getRange('B20:B22'), spreadsheet.getRange('E21'), spreadsheet.getRange('E20')])
.removeEditors(['user2#domain.com', 'user3#domain.com']);
spreadsheet.getRange('G2').activate();
spreadsheet.getRange('G2').setValue(myValue);
spreadsheet.getRange('G3').activate();
spreadsheet.getRange('G3').setValue(new Date()).setNumberFormat('dd-MMM-YYYY');
spreadsheet.getRange('C2').activate();
hideImage();
};
Related
I need a solution to enable all editors to leave their name stamps after they make changes in a sheet. I was able to come up with script code which does work ONLY FOR OWNER.
I tried to authorize the script by running the script manually from editors accounts - the app has the authorization but even though it doesn't work for Editors.
Norbert Wagner: "However authorizing from the script editor did not work for me. But as soon as I added a menu in the document and executed the function from there, I got an authorization request in the document. From then on also the onEdit function works, for all users. I did this as the documents owner though. " -
maybe this is the solution? But how can I run the onEdit from the
document? Simple edit doesn't work, but how about this menu? Is there
a way to execute ALL SCRIPTS AND FORCE AUTHORIZATION from document level?
function onEdit(e) {
// Your sheet params
var sheetName = "Arkusz1";
var dateModifiedColumnIndex = 3;
var dateModifiedColumnLetter = 'C';
var range = e.range; // range just edited
var sheet = range.getSheet();
if (sheet.getName() !== sheetName) {
return;
}
// If the column isn't our modified date column
if (range.getColumn() != dateModifiedColumnIndex) {
var row = range.getRow();
var user = Session.getActiveUser().getEmail();
user = String(user);
user = user.replace('#gmail.com', '');
var dateModifiedRange = sheet.getRange(dateModifiedColumnLetter + row.toString());
dateModifiedRange.setValue(user);
};
};
This function works only when using owner account.
Even after Editor has authorised script manually from script editor - the onEdit user stamp function doesn't trigger.
Below is another function which I used for testing. When I run it manually from editors account IT WORKS - it saves editor's name in A1.
function USERNAME(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheets()[0]; // this will assign the first sheet
var cell = sheet.getRange('A1');
var d = new Date();
d.setHours(d.getHours() +1);
var user = Session.getActiveUser().getEmail();
user = String(user);
user = user.replace('#gmail.com', '');
cell.setValue(user);
}
In fact you have to use installable trigger.
Rename your function onEdit(e) with another name for example customFunction(e)
Then you setup an installable trigger using this function :
function createTrigger(){
ScriptApp.newTrigger('customFunction')
.forSpreadsheet(SpreadsheetApp.openById('YOU_SHEET_ID'))
.onEdit()
.create();
}
Or you can setup the trigger manually by going to 'Edit >> Current project's triggers >> Click on + button (bottom right)'
By this way trigger will run each time there is an Edit no matter the user.
Stéphane
I'm trying to copy / transfer a sheet from one spreadsheet to another spreadsheet. I have tried various methods and .copyTo seems to be the best and most efficient way.
.copyTo works well but I'm struggling to send to a specific sheet...
Here is my code:
function TransferDataOut() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheetA = source.getSheets()[0]; //sheet source number
var destination = SpreadsheetApp.openById('the destination sheet');
var each = "Data_Incoming";
var ss = SpreadsheetApp.getActiveSpreadsheet();
sheetA.copyTo(destination); // I tried renaming .setName(each);
}
So if I only use sheetA.copyTo(destination); it simply creates a copy sheet, like Copy of the_souce_sheet_name.
If I try renaming to make it a specific name I will get error after running for the second time that the sheet already exists in destination spreadsheet.
What I really need to achieve is that the function from source spreadsheet copies data from the source sheet to always the same sheet in destination spreadsheet.
Perhaps .copyTo is not the right way to do it?
Any suggestions and code will help please!
The reason for receiving data on a exact sheet in the destination spreadsheet is because I have a trigger On change that executes another script to work with the new incoming data.
You want to overwrite the source sheet of Spreadsheet A to the destination sheet of Spreadsheet B.
You want to keep the sheet name of var each = "Data_Incoming".
If my understanding is correct, how about this answer? I would like to propose 2 samples. So please select one of them for your situation.
Sample script 1:
The flow of this sample script is as follows.
each sheet of Spreadsheet B is deleted.
Source sheet of Spreadsheet A is copied to the destination sheet of Spreadsheet B.
Sheet name of the copied sheet of Spreadsheet B is modified to each.
Modified script:
function TransferDataOut() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheetA = source.getSheets()[0]; //sheet source number
var destination = SpreadsheetApp.openById('the destination sheet');
var each = "Data_Incoming";
var destSheet = destination.getSheetByName(each);
if (destSheet) {
destination.deleteSheet(destSheet);
}
sheetA.copyTo(destination).setName(each);
}
Sample script 2:
The flow of this sample script is as follows.
Copy the source sheet of Spreadsheet A to Spreadsheet B.
If the sheet with the sheet name of each is not existing in Spreadsheet B, the copied sheet is renamed to each.
If the sheet with the sheet name of each is existing in Spreadsheet B, the source sheet is copied to the Spreadsheet B as Copy of ###.
each sheet is cleared.
All values, formulas and formats of the copied sheet are copied to each sheet.
Delete the copied sheet.
Modified script:
function TransferDataOut() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheetA = source.getSheets()[0]; //sheet source number
var destination = SpreadsheetApp.openById('the destination sheet');
var each = "Data_Incoming";
var copiedSheet = sheetA.copyTo(destination);
var destSheet = destination.getSheetByName(each);
if (destSheet) {
destSheet.clear();
var srcRange = copiedSheet.getDataRange();
srcRange.copyTo(destSheet.getRange(srcRange.getA1Notation()));
destination.deleteSheet(copiedSheet);
} else {
copiedSheet.setName(each);
}
}
Note:
In this sample script, each sheet of the Spreadsheet B (destination Spreadsheet) is deleted. So please be careful this.
So at first, as a test, I recommend to use a sample Spreadsheet.
References:
copyTo(spreadsheet) of Class Sheet
copyTo(destination) of Class Range
deleteSheet(sheet)
If I misunderstood your question and this was not the result you want, I apologize.
Added:
You want to run the script of destination Spreadsheet when the source values are copied to the destination Spreadsheet using the script in the source Spreadsheet.
The following sample script is for achieving above situation.
Script of destination Spreadsheet:
At first, the script of destination Spreadsheet is prepared.
Script:
function doGet() {
sample(); // This is the function that you want to run when the source values are copied.
return ContentService.createTextOutput();
}
After copy and paste above script to the script editor of destination Spreadsheet, please do the following flow.
Deploy Web Apps.
On the script editor, Open a dialog box by "Publish" -> "Deploy as web app".
Select "Me" for "Execute the app as:".
Select "Anyone, even anonymous" for "Who has access to the app:".
Click "Deploy" button as new "Project version".
Automatically open a dialog box of "Authorization required".
Click "Review Permissions".
Select own account.
Click "Advanced" at "This app isn't verified".
Click "Go to ### project name ###(unsafe)"
Click "Allow" button.
Copy "Current web app URL:".
It's like https://script.google.com/macros/s/#####/exec.
Click "OK".
Script of source Spreadsheet:
As the next step, the script of source Spreadsheet is prepared as follows. In this sample, the sample 2 was used. I think that you can also use the sample 1.
Script:
function TransferDataOut() {
var source = SpreadsheetApp.getActiveSpreadsheet();
var sheetA = source.getSheets()[0]; //sheet source number
var destination = SpreadsheetApp.openById('the destination sheet');
var each = "Data_Incoming";
var copiedSheet = sheetA.copyTo(destination);
var destSheet = destination.getSheetByName(each);
if (destSheet) {
destSheet.clear();
var srcRange = copiedSheet.getDataRange();
srcRange.copyTo(destSheet.getRange(srcRange.getA1Notation()));
destination.deleteSheet(copiedSheet);
} else {
copiedSheet.setName(each);
}
// Added
var url = "https://script.google.com/macros/s/###/exec"; // Please set the retrieved URL of Web Apps.
UrlFetchApp.fetch(url);
}
Note:
By above settings, when TransferDataOut() of the source Spreadsheet was run, doGet() of the destination Spreadsheet is run by UrlFetchApp.fetch(url).
References:
Web Apps
Taking advantage of Web Apps with Google Apps Script
When sharing a Google Sheet with a button that runs a Google Script I run into the following problem:
- When anyone but myself clicks the button the script will return the following error:
"You are trying to edit a protected cell or object. Please contact the spreadsheet owner to remove protection if you""
I had a couple protected ranges (deleted them now) in the sheet, but not one even close to the button.
I've tried to add a button on one of the shared user's account and copied the script into a new script file (re-linking the script created/copied by the shared user to the button created by the shared user), but to no avail.
Anyone who knows the solution to this problem ?
Removing the protection doesn't work just by deleting the cell values. There's a sample code in Class Protection which shows adding and removing protection in sheet ranges:
// Remove all range protections in the spreadsheet that the user has permission to edit.
var ss = SpreadsheetApp.getActive();
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
I'm trying to permanently lock/protect certain cells on 14 different sheets (1 hidden from the workers for formula stuff). I have them all locked and no one can edit if I add them to it as an editor. But it is the template, I make copies of it for each client (and new clients) for the staff. The staff that works on the sheet and the employees are only allowed to edit certain cells for the work they do.
The problem is if I have Workbook1 with X cells locked on the different sheets, make a copy, rename it to Workbook - Client#ID, then add them employees John and Jane, who will be working on this client, as editors; they can now edit every cell, including the protected ones (they get added as editors to the protected cells too). It doesn't do this on the original, it only happens to the copy made of the template. I then have to go through all 13 sheets and remove them from the protected cells.
I'm trying to quickly remove them automatically with a script add-on that I want to turn into a button or something later...
Or is there a better way to fix this bug?
Google has an example of removing users and keeping sheet protected and I have tried to add in what I need to make it work, but it doesn't do anything when I run the test as an add-on for the spreadsheet. I open a new app script project from my spreadsheet and enter in the example code from google
// Protect the active sheet, then remove all other users from the list of editors.
var sheet = SpreadsheetApp.setActiveSheet(January);
var protection = sheet.protect().setDescription('Activity Log');
var unprotected = sheet.getRange('A2:N7');
protection.setUnprotectedRanges([unprotected]);
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
For this you can write a script function to set the protection ranges and add editors for the sheets as well.
Please check the sample apps script code to add protection for a range in a sheet below:
function addProtection()
{
// Protect range A1:B10, then remove all other users from the list of editors.
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('A1:B10');
var protection = range.protect().setDescription('Sample protected range');
// var me = Session.getEffectiveUser();
// array of emails to add them as editors of the range
protection.addEditors(['email1','email2']);
// array of emails to remove the users from list of editors
protection.removeEditors(['email3','email4']);
}
Hope that helps!
Adding on #KRR's answer.
I changed the script to be dynamic.
function setProtection() {
var allowed = ["example#gmail.com,exmaple2#gmail.com"];
addProtection("Sheet1","A1:A10",allowed);
}
function editProtection(sheetname,range,allowed,restricted) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(sheetname);
var range = sheet.getRange(range);
//Remove previous protection on this range
var protections = sheet.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0;i<protections.length;i++) {
if (protections[i].getDescription() === sheetname + range){
protections[i].remove();
}
}
//Set new protection
var protection = range.protect().setDescription(sheetname + range);
// First remove all editors
protection.removeEditors(protection.getEditors());
// Add array of emails as editors of the range
if (typeof(allowed) !== "undefined") {
protection.addEditors(allowed.toString().split(","));
}
}
You can add as many options as you want and make them run onOpen. Set your variable and call editProtection as many times as you need.
You can get the emails dynamically from spreadsheet editors.
Also you might want to add another script to protect the whole sheet and set you as the owner.
Hope this helps.
It MUST be run as SCRIPT and NOT as an add-on.
If you have already locked your sheets and made your exceptions you can easily use Google's example code. We can use a for loop to find all the sheets and names. Then add a button to the script to load at start.
function FixPermissions() {
// Protect the active sheet, then remove all other users from the list of editors. Get all sheets in the workbook into an array
var sheets = SpreadsheetApp.getActiveSpreadsheet().getSheets();
//Use a for loop to go through each sheet and change permissions and label it according to the name of the sheet
for (var i=0; i < sheets.length; i++) {
var name = sheets[i].getSheetName()
var protection = sheets[i].protect().setDescription(name);
// Ensure the current user is an editor before removing others. Otherwise, if the user's edit
// permission comes from a group, the script will throw an exception upon removing the group.
var me = Session.getEffectiveUser();
protection.addEditor(me);
protection.removeEditors(protection.getEditors());
if (protection.canDomainEdit()) {
protection.setDomainEdit(false);
}
}
}
//A special function that runs when the spreadsheet is open, used to add a custom menu to the spreadsheet.
function onOpen() {
var spreadsheet = SpreadsheetApp.getActive();
var menuItems = [
{name: 'Fix Permission', functionName: 'FixPermissions'}
];
spreadsheet.addMenu('Permissions', menuItems);
}
Now in the menu bar you will see a new item when you reload/load the spreadsheet labeled Permissions
I have a Google Sheet that is updated via "Zapier" from my CRM (Capsule) application. I need to auto trigger an email to a given address whenever the spreadsheet is updated. The CRM software successfully adds a new record to the end of the spreadsheet when a new organization is created. I have a script that monitors the last row and sends an email to the addressee by an on change event but this only happens if I go in and change the spreadsheet data myself.
function sendEmail() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRow = sheet.getLastRow();
var emailCell = sheet.getRange(dataRow, 4);
var emailAdd = emailCell.getValues();
var newRecordSource = sheet.getRange(dataRow, 1, 1, sheet.getLastColumn());
var newRecord = newRecordSource.getValues();
//var message = "This record has just been added:" +newRecord;
var message = "New record added to Workflow Sheet!: " +newRecord;
var subject = "Test from Workflow Sheet!";
MailApp.sendEmail(emailAdd, subject, message);
}
Can anyone help?
If the emails will be sent to a Gmail address then you can do this without Apps Script: Log into the account that will receive the emails, open the relevant spreadsheet, and select "Notification rules..." from the Tools menu and set it up as desired.
If you must do it from Apps Script, you will probably need to use a time-based trigger to monitor for changes, which is considerably more complicated than using onChange, unfortunately.