So I hate permissions, they are awful. BUT I also hate it when people over write things they didn't realise were important... I was really excited to find out that I could automate the entire granting permissions ordeal using google scripting.
So I tested things out in a small dummy sheet, and got it working the way I wanted to. However! When I use it on my test template of the actual sheet I will need to be using it on, it will (for some reason) add the other person I shared the file with into all the levels of permissions.
This is not what I want. The other person is currently me (at SHOULDN'TBEINEVERYTHING#WHYYYYY.com in this script), but that account is supposed to have the lower level of permissions so I can make sure that everything is working the way I want it to. Here's a dummy version of the code I'm using:
function plus10Protection() {
var ss = SpreadsheetApp.getActive();
// Removes protections from Ranges
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
//removes sheet protections
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.SHEET);
for (var i = 0; i < protections.length; i++) {
var protection = protections[i];
if (protection.canEdit()) {
protection.remove();
}
}
//SHEET1
var sh7 = ss.getSheetByName("Sheet1");
var protection = sh7.protect().setDescription('Sheet1 - Wizards');
//protects whole sheet
protection.addEditors(['wizard1#test.com',
'wizard2#test.com']);
//adds email addresses to WIZARD range - SHEET1
var unprotected = sh7.getRange('A1:D');
protection.setUnprotectedRanges([sh7.getRange("B1:B1"),
sh7.getRange("B3:D")]);
//unprotects
var range = sh7.getRange('B1:B1'); //selects the range
var protection = range.protect().setDescription('Sheet1 - Part Number');
//names the range
var me = Session.getEffectiveUser();
protection.addEditors(['wizard1#test.com',
'wizard2#test.com',
'supervisor1#test.com']);
//put emails here SHEET1 SUPERVISORS
var range = sh7.getRange('B3:D'); //selects the range
var protection = range.protect().setDescription('Sheet1 - Workers');
//names the range
var me = Session.getEffectiveUser();
protection.addEditors(['wizard1#test.com',
'wizard2#test.com',
'supervisor1#test.com'
'worker1#test.com'
'worker2#test.com'
'SHOULDN'TBEINEVERYTHING#WHYYYYY.com']);
//put emails here SHEET1 WORKERS
Repeated for about fifteen other tabs. I was really wanting this to solve the problem with updating permissions when people leave or join, since this script removes old permissions and then puts new ones on. However, the sheet is already shared with everyone at the company, and if suddenly everyone has Wizard level access, it's pretty much the worst.
I tested it again by sharing with another person, and they were added in to everything as well. Is there some code snippet that specifies only sharing with the emails I've provided, and not everyone it could possibly share with? (stop getting over excited, google.)
I don't want to have to un-share it with everyone and then re-share, because that seems like a huge problem. I'm hoping there's something in the code that I stole from somewhere online (I should have bookmarked it!!!) is actually calling everyone with edit permissions.... Maybe the getEffectiveUser? Though I'm running it from my test1#test.com account, not from the other which is open in a different browser! (Firefox, and I'm scripting in Chrome.)
I also tested to see if maybe it was because I was signed in and looking at the sheet with my SHOULDN'TBEINEVERYTHING#WHYYYYY.com account, but nope, that's not it.
Is it possible to script sharing? So I could have it first unshare with everyone, clear the permissions, set the new permissions, then re-share? If that is the only solution? The less finicky I can make this the better. :S
I'm trying to get answers to this problem over on the google sheets forums, but no bites so far. I was hoping to cast a wider net.
Still haven't figured out how to change a user's permission level, but thanks to some code I found (which I thought I had bookmarked, but now can't find) I can remove everyone's permissions, run my protection script, then share with everyone again.
//removes all Editors
var SpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var editors = SpreadSheet.getEditors();
for (var i = 0; i < editors.length; i++) {
SpreadSheet.removeEditor(editors[i]);
};
and then
//Adds all editors back.
var SpreadSheet = SpreadsheetApp.getActiveSpreadsheet();
var editors = SpreadSheet.getEditors();
SpreadSheet.addEditors(['PUT EMAILS HERE', 'separated by commas']);
I need to find out how to add the person running the script to the permissions specifically, just in case it isn't the owner running the script. A task for tonight!
Related
I am creating a sheet that can create a series of sheets that interact to help run a simulation. Part of this project involves creating a series of sheets- individual score sheets- that can only be accessed by single players in the game. The code I'm hoping to use is below, but I'm running into a series of issues. The first is thatit says I don't have permission to run SpreadsheetApp.create. I have spent about an hour looking around online for how to cure this, but just don't understand it and can't find a good answer. The second, and potentially broader issue, is that it seems incredibly complicated to use GAS to interact with different spreadsheets (i.e. not within the same spreadsheet). At a later point in the project, we will have to retrieve information from these sheets, etc. and if it is going to be difficult to do through scripts, may have to think of a workaround.
/** #OnlyCurrentDoc */
function individualSheets(){
var playerarray = playerArray();
var spreadsheet = SpreadsheetApp.getActive();
var spreadsheetlinks = []
//creates individual sheet for each player, then adds the link of their sheet to a list
for(i=0;i<playerarray.length;i++){
var spreadsheetname = playerarray[i];
var newspreadsheet = SpreadsheetApp.create(spreadsheetname);
spreadsheetlinks.push(DriveApp.getUrl(newspreadhseet));
}
//pasting the links of each player in the appropriate sheet
for(i=0;i<spreadsheetlinks.length;i++){
spreadsheet.setActiveSheet("PlayerInfo").getRange('D'+(i+1)).activate()
spreadsheet.getCurrentCell.setValue(spreadhseetlinks[i])
}
}
Apps Script doesn't have difficulty to use several Spreadsheets in the same script.
I would add another array to store the Ids, so you won't have to get them from the urls:
var spreadsheetlinks = []
var spreadsheetIds = [];
//creates individual sheet for each player, then adds the link of their sheet to a list
for(i=0;i<playerarray.length;i++){
var spreadsheetname = playerarray[i];
var newspreadsheet = SpreadsheetApp.create(spreadsheetname);
spreadsheetlinks.push(DriveApp.getUrl(newspreadhseet));
spreadsheetIds.push(newspreadsheet.getId);
}
You can open the Spreadsheets using openById, so you can use an unlimited amount of them:
var sprSheet1 = SpreadsheetApp.openById('id 1');
var sprSheet2 = SpreadsheetApp.openById('id 2');
var sprSheet3 = SpreadsheetApp.openById('id 3');
...
or even better:
var sprSheets = [];
for (var j = 0; j < spreadsheetIds.length; j++){
sprSheets.push(SpreadsheetApp.openById(spreadsheetIds[j]);
}
Regarding the SpreadsheetApp.create issue, make sure you authorized the needed scopes:
Scripts that use this method require authorization with one or more of
the following scopes:
https://www.googleapis.com/auth/spreadsheets.currentonly
https://www.googleapis.com/auth/spreadsheets
You can check them in Apps Script View > Manifest file. If you never touched this, they won't show up. You can add them manually:
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/drive.file"
]
In case you still get the permission issue when creating the Spreadsheet, you might want to check the playerArray function to see if it's actually returning a String.
I'm new to Apps Script and have been slowing teaching myself. I have searched Stack Over Flow and found similar questions and tried to work through the issue using the information found but thus far with no success. Hopefully someone here can give a newbie some guidance.
I have a spreadsheet that I am sharing with other users. There are a few ranges with formulas in them that I am trying to protect. I have written a script that can change the forumlas within the protected ranges. It runs perfectly under my account but the shared users are unable to run the script because they "don't have permission".
I have tried to write a bit of code that tries to grant temporary access to edit the protected ranges. My thought was to have three different functions. The first function removes the protections. The second function performs the changes to the now unprotected ranges. The third function then protects the ranges again. The code below works perfectly under my account. However when logged in as a shared user it causes an alert saying that "You do not have permission to perform.."
Can someone please tell me where I am going wrong and point me in the right direction?
function myFunction() {
//add protections back
var ss = SpreadsheetApp.getActive();
var sheet = ss.getSheetByName('Spray Template');
var range = sheet.getRange('B15:E15');
var range2 = sheet.getRange('G15:H15');
var range3 = sheet.getRange('D16:E16');
var ranges = [range,range2,range3];
for (var j = 0; j<3; j++){
ranges[j].protect().removeEditor('email_here#gmail.com');
}
}
function myFunction2(){
//remove protections
var ss = SpreadsheetApp.getActive();
var ss2= ss.getSheetByName('Spray Template');
var protections = ss2.getProtections(SpreadsheetApp.ProtectionType.RANGE);
f
or (var i = 0; i < protections.length; i++) {
var protection = protections[i];
protection.addEditor('email_here#gmail.com');
SpreadsheetApp.flush();
protection.remove();
}
}
function test() {
myFunction2();
functionThatChangesRange();
myFunction();
}
I imagine that without permission to edit/access those protected ranges, other people will also lack permission to edit the protection itself (even through scripts).
This is because the script inherits its permissions from the user that is running it (ie the script runs under that person's account).
Obviously, you can't both protect a range, and also allow anyone to change the protection - that would defeat the whole purpose!
I saw it suggested elsewhere to leave one cell unprotected and setup a script within the document that runs 'onedit'. Whenever the user changes the value of that cell the script should run.
One can use onEdit to run the script or installable trigger. However, I have not identified a way in which a script can be called from a custom menu or a button and edit successfully protected ranges.
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
Recently ability to manipulate protection for a range programatically has been added. But is there a way to add multiple ranges all together when they have same protection to apply protection instead of processing each range one at a time? The following code is from Google sample script. Currently I am running the following code as many as ranges I have. Is there a better way to do it all at once?
var ss = SpreadsheetApp.getActive();
var range = ss.getRange('A1:B10');
var protection = range.protect().setDescription('Sample protected range');
// Protect the active sheet except B2:C5, then remove all other users from the list of editors.
var sheet = SpreadsheetApp.getActiveSheet();
var protection = sheet.protect().setDescription('Sample protected sheet');
var unprotected = sheet.getRange('B2:C5');
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);
}
Thanks.
The brackets[] in:
protection.setUnprotectedRanges([unprotected]);
suggests that an array is expected. Have you tried:
protection.setUnprotectedRanges([sheet.getRange("B2:B5"),
sheet.getRange("F2:F5"),
sheet.getRange("H2:H5")]);
It is not possible to manipulate non-contiguous cells in Google Spreadsheet (yet?), but there are optimiziations that can be done, setting the ranges asynchronously with HTML calls, threaded, protecting all then unprotecting some, all depends on your case, can you give an example on how your using it, how much cells you're protecting?
Updating:
To do asynchronous HTML calls you would first create a sideBar or a modelessDialog, and make a button that execute many google.script.run functions. Since the code in the HTML page is asynchronous (it continous to run even though the function in google servers isn't done), you can make dozens of calls, E.G.:
In javascript HTML:
function protectOdds(){
for(var i = 0; i < 10; i++)
google.script.run.protectCells( i );
}
This would run alot faster than calling trough the .gs code to protect one at a time.
It might be a little late, but here is what I did.
Var ranges = [ss.getRange('A1:B10'), ss.getRange('C1:E10')];
for (var i = 0; i < ranges.length; i++){
var protection = ranges[i].protect().setDescription('Sample protected range');
}
I've looked everywhere and it seems that GAS hasn't caught up with Google Spreadsheet. Is there a brute force method for setting protection on certain ranges in a sheet..? (And making a protected sheet with all the formulas and referring to them won't help me.)
I found this through google: https://code.google.com/p/google-apps-script-issues/issues/detail?id=1721
I even commented at the bottom. (More of a complaint than anything useful.) But the guy above me there posted this code:
//Function to Protect Target Sheet
function ProtectTargetSheet() {
//Enter ID for each Worksheet
var IDs = ["Sheeet_1", "Sheet_2"]
//Enter Page to protect, in order of WorkSheets
var Sheet_names = ["Page_1", "Page_2"]
//For each sheet in the array
for ( i = 0; i < IDs.length; i ++) {
//id of sheet
var sheet = IDs[i]
//activate dedicated sheet
var ActiveSheet = SpreadsheetApp.openById(sheet)
//Find last row and column for each sheet
var LastRow = ActiveSheet.getSheetByName(Sheet_names[i]).getLastRow();
var LastCol = ActiveSheet.getSheetByName(Sheet_names[i]).getLastColumn();
//Name of protectSheet
var Name = "Protect_Sheet";
//Range for Protection
var Named_Range = ActiveSheet.getSheetByName(Sheet_names[i]).getRange(1, 1, LastRow, LastCol);
//Impletment Protect Range
var protected_Range = ActiveSheet.setNamedRange(Name, Named_Range);
}
}
I don't see how this can work to give protection to a range when shared. It seems that it would just create a Named Range. He does say to set the permissions manually first. But I can't figure what exactly he meant.
Anyways, I was hoping that someone had found a way by now to do this until Google syncs GAS with its counterpart.
My wish is to, through 100% code, select a range in a sheet, within a spreadsheet, and make it so that when I share this whole spreadsheet to a person, that he or she can't edit that range. There will be other parts in that sheet that they have to be able to edit. Just not that range. It is easy to do this manually, but when having to create 100s of spreadsheets, it would be help to be able to do this through GAS.
Thanks.
Part of your question asked about protecting a sheet. Please have a look here: setProtected(protection)
As for programmatically protecting a range no. However, you could protect a sheet, does not need to be in the same spreadsheet and then create an onEdit trigger that would replace any change in your "protected" range with the original source data.
Something like this:
function onLoad() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var protected = ss.getSheets()[1].getRange("A1:B2").getValues(); //You could use openByID to get from a different ss
var target = ss.getSheets()[0].getRange("A1:B2").setValues(protected);
}
function onEdit(){
onLoad();
}
Every time a change is made to the spreadsheet the script will rewrite the data in the sheet for the range you specify.
The easiest approach I've found is to use Data Validation: that is, write a script which will examine each cell to be 'protected' and create and apply a validation rule which enforces entry of the existing content and rejects anything else. [Of course, this also implies that you have designed your spreadsheet such that all entered data is on sheet or sheets separate from those which have formula embedded. On these you use normal sheet protection.]
According to Control protected ranges and sheets in Google Sheets with Apps Script, posted on February 19, 2015, now is possible to protect a Google Sheets range.
From https://developers.google.com/apps-script/reference/spreadsheet/protection
Class Protection
Access and modify protected ranges and sheets. A protected range can
protect either a static range of cells or a named range. A protected
sheet may include unprotected regions. For spreadsheets created with
the older version of Google Sheets, use the PageProtection class
instead.
// 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');
// 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);
}
If you are sending 100s of copies of the same sheet. Then create a template sheet, protect the ranges in it manually and send a copy of the template. It will retain the protection.
Sorry but as others have said there is not script method of setting protection at a sub-sheet level.