GAS: Range Protection? - google-apps-script

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.

Related

Script to cause an ImportRange function to run on demand

first, let me say, i am not a programmer, but i understand and can build spreadsheet ok. I'm newer to doing so in Google Sheets. I am using an Importrange function to copy date from one spreadsheet tab to another spreadsheet. I'm finding that there is a delay in the updating. I checked my Settings for recalculations and Google Sheets is set to refresh when data is changed. However, its not doing so. So, i wanted to create a button the user can hit and force the ImportRange refresh.
I found some code that appears to accomplish this, but I can't get it to work:
function myFunction() {
SpreadsheetApp.getActive().getRange('A1').setValue('IMPORTRANGE('https://docs.google.com/spreadsheets/d/abcd123abcd123', 'sheet1!A:B')')
}
Here is my import range which is in CELL C4 of a sheet named CALCS:
=importrange("https://docs.google.com/spreadsheets/d/1B5PEI5f4-TAhS77-poCYTiWI6w2t2BEzfiP5kXVq9lk/edit#gid=1661941505","PO TRACKING!A4:T1000")
Welcome to the wonderful world of scripts! There is so much you can do with your projects when using Google Apps Script.
Your question is a good one but I took a different approach. Rather than using an import range that needs to be recalculated, I just wrote a script that would do a similar thing. If you need to collect a specific range of data instead of the entire sheet, then just make one small change to the script provided.
//CHANGE
var poData = poTab.getDataRange().getValues();
//TO
var poData = poTab.getRange('INSERT A1 NOTATION HERE').getValues();
That should allow you to select a specifc range if needed.
//TRANSFER DATA FROM ONE TAB TO ANOTHER
function transferFromSameSpreadsheet() {
//GETS ACTIVE SPREADSHEET
var ss = SpreadsheetApp.getActiveSpreadsheet();
//GETS SPECIFIC SHEET BY NAME WHERE DATA IS STORED
var poTab = ss.getSheetByName('Po Tracking');
//STORES ALL DATA FROM SHEET
var poData = poTab.getDataRange().getValues();
//GETS SPECIFIC SHEET BY NAME WHERE DATA IS SENT
var transferTo = ss.getSheetByName('insert sheet name here');
//CLEARS OLD DATA
transferTo.clearContents();
//UPDATES SHEET WITH NEW DATA
transferTo.getRange(1,1,poData.length,poData[0].length).setValues(poData);
}
//TRANSFER DATA FROM A MASTER SHEET TO ANOTHER WORKBOOK. RUN SCRIPT IN ACTIVE WORKBOOK, NOT MASTER.
function transferFromDifferentSpreadsheet() {
//OPENS SPREADSHEET BY ID
var ms = SpreadsheetApp.openById('insert spreadsheet id here');
//GETS ACTIVE SPREADSHEET
var ss = SpreadsheetApp.getActiveSpreadsheet();
//GETS SPECIFIC SHEET BY NAME WHERE DATA IS STORED
var poTab = ms.getSheetByName('Po Tracking');
//STORES ALL DATA FROM SHEET
var poData = poTab.getDataRange().getValues();
//GETS SPECIFIC SHEET BY NAME WHERE DATA IS SENT
var transferTo = ss.getSheetByName('insert sheet name here');
//CLEARS OLD DATA
transferTo.clearContents();
//UPDATES SHEET WITH NEW DATA
transferTo.getRange(1,1,poData.length,poData[0].length).setValues(poData);
}
Good luck!
Get range A1 notation documentation: https://developers.google.com/apps-script/reference/spreadsheet/sheet#getrangea1notation
What your code is doing is just setting the cell value as the string you input in the setValue()
If you want to input formula you can either include "=" when using setValue() or use setFormula() method
Sample:
function myFunction() {
SpreadsheetApp.getActive().getRange('A1').setFormula('IMPORTRANGE("https://docs.google.com/spreadsheets/d/xxxxx", "sheet1!A:B")');
SpreadsheetApp.getActive().getRange('D1').setValue('=IMPORTRANGE("https://docs.google.com/spreadsheets/d/xxxxx", "sheet1!A:B")');
}
Note:
I also fixed the string format in your code so that it will provide a string input to the setValue() and setFormula()
References:
Range.setValue()
Range.setFormula()

Protect a range from unhiding but not editing (Google Apps script)

Is it possible to prevent people from manually unhiding hidden rows in a spreadsheet?
The person has edit access to the sheet.
How to prevent that with code?
This code below doesn't work (I test it as another user and was able to unhide rows):
//initially hidden range (from chapter 2 down)
var hiddenRange = ss.getRangeByName("CourseProgressHiddenRange")
//get email of a person logged in sheet
var sessionEmail = Session.getActiveUser().getEmail()
//student email from sheet
var studentEmail = ss.getRangeByName("CourseProgressStudentEmail").getValue()
//if student logged in
if (sessionEmail === studentEmail) {
//protect rows from that email account
var protection = hiddenRange.protect().setDescription('Hidden rows');
}
Try this:
function test(){
var ss = SpreadsheetApp.getActiveSheet();
var range = ss.getRange("1:6");
ss.unhideRow(range);
var protections = ss.getProtections(SpreadsheetApp.ProtectionType.RANGE);
protections[0].remove();
}
This code will unhide (Documentation here) the rows of the specified range. After unhiding them it gets all protections in the sheet with getProtections() (Documentation here) and it removes the necessary ones (Documentation here).
I don't know how your sheet is organized, but using this approach you can unhide the rows you want at the right time, while they are protected, the users cannot expand them. The most important part is that you loop to through the protections in the right way, maybe a method that looks for a particular one, or have the protections named.

After Three Days of Experimenting and Searching, I STILL Can't Get .getValue Function to Work

Sorry for the long subject line, but this site wouldn't accept a more concise statement.
I have a Google Sheet, that has nothing more than my name in Cell A1.
Then I went to Tools / Script Editor, and have a "Code.gs" script that says:
function onEdit(e) {
Logger.log("We're in the function.");
var ss = Spreadsheet.App.getActiveSpreadsheet();
var sheet = ss.getSheets()[0];
var valueOfCell = sheet.getRange(1,1).getDisplayValue();
Logger.log("Value of Cell is '" + valueOfCell + "'.");
}
When I edit my very simple sheet, and check the log (View / Logs), I get:
[19-02-28 08:58:10:182 CST] We're in the function.
That's it. I've tried every permutation I can think of or suss from three days of web-searching, and I simply can not make the .getValue() (or .getDisplayValue()) work.
What am I doing wrong?
Thanks!
/Kent
"Spreadsheet.App" should be "SpreadsheetApp".
Additional permutations that work:
Logger.log("We're in the function.");
var ss = SpreadsheetApp.getActiveSheet();
var valueOfCell = ss.getRange(1,1).getDisplayValue;
and
Logger.log("We're in the function.");
var valueOfCell = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getRange(1,2).getDisplayValue();
and
Logger.log("We're in the function.");
var valueOfCell = SpreadsheetApp.getActiveSheet().getRange(1,2).getDisplayValue();
After four days, I think I finally understand some of this. For others as dense as I, I leave this documentation here:
Spreadsheet is the "workbook", the collection of Sheet tabs
Sheet is the individual tab'd sheet in a Spreadsheet "workbook".
Seems like simple enough information, but I couldn't find this documented anywhere.
SpreadsheetApp is the Class, the "parent" of all the Spreadsheets. While probably not technically accurate, for purposes of my thinking, I think of "SpreadsheetApp" as being the "Sheets" application within Google Docs, as opposed to "Docs" or "Slides".
So,
var ThisApp = SpreadsheetApp;
// Sets the variable "ThisApp" to "SpreadsheetApp", which I guess is the name/identification of the spreadsheet app.
var TheActiveWorkBook = SpreadsheetApp.getActive();
// Sets "TheActiveWorkBook" to the identifier for the current collection of sheets (collection of tabs within a spreadsheet).
var TheActiveWorkBook = SpreadsheetApp.getActiveSpreadsheet();
// Identical to above; just a different name for it.
var ActiveWorkBookName = SpreadsheetApp.getActive().getName();
// Sets the variable to the *name* of the workbook/spreadsheet (like, "Fred's Spreadsheet").
var ActiveSheetInWB = SpreadsheetApp.getActiveSpreadsheet();getActiveSheet();
// Sets the variable to the identifier of the active tab in the spreadsheet.
var ActiveSheetInWB = SpreadsheetApp.getActive();getActiveSheet();
// Identical to above.
var ActiveSheetInWB = SpreadsheetApp.getActiveSheet();
// Identical to above. The active sheet is the active sheet, regardless of whether we know the active spreadsheet, so the extra step of including the active spreadsheet, as we did in the previous two examples, is unnecessary. (The system knows that the active spreadsheet is the one that contains the active sheet.)
var ActiveSheetInWB = SpreadsheetApp.getActiveSheet().getName();
// Gets the *name* of the sheet, as it shows on that sheet's tab.
var sheet = SpreadsheetApp.getActiveSheet();
var ActiveSheetInWB = sheet.getName();
// Shortens the long "SpreadsheetApp.getActiveSheet()" to a short reference to save typing in subsequent uses. Otherwise identical to above example.
I think I'm finally getting the hang of this....

Class Protection Script

I've taken the information from here
to create a script that will protect a range of cells within sheets from being edited but also excludes areas where a user can enter data. My script looks like this.
function myFunction() {
// 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('2016-07-21 Material Request Form Template');
var unprotected = sheet.getRange('C9:D9');
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);
}
}
On the line that reads var unprotected = sheet.getRange('C9:D9'); this will indeed unlock this range as expected within the protected sheet but I also have other ranges within this document that I'd like to restrict. How can I enter or modify this line so that I can lock out multiple ranges i.e. C9:D9 and A12:E12 and A24:K24
If I simply copy this line and paste it with a new cell range, it overwrites the previous unprotected range and activates the new line I just pasted.
Thank you.
For multiple unprotected ranges you need to pass all of the ranges as the array argument in one call:
var range1 = sheet.getRange("A1:A6");
var range2 = sheet.getRange("B1:B6");
protection.setUnprotectedRanges([range1, range2]);
Your question seems to switch and you appear to end up asking how to restrict access to multiple ranges, rather than unprotecting them? In order to do this, protect the whole sheet and then unprotect the relevant ranges to achieve what you need.

How to Remove Editors from Protected Cells or Permanently Protect Cells in Google Sheets

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