Skip to content

Instantly share code, notes, and snippets.

@phillypb
Last active February 10, 2025 05:14
Show Gist options
  • Save phillypb/4e6c3a5cbb67ff79e415629ea8af2339 to your computer and use it in GitHub Desktop.
Save phillypb/4e6c3a5cbb67ff79e415629ea8af2339 to your computer and use it in GitHub Desktop.
/**
* A number of global variables that are re-used throughout this script.
*/
// 5 minute maximum runtime
var maxRuntime = 5 * 60 * 1000;
var ss = SpreadsheetApp.getActiveSpreadsheet();
var timeZone = ss.getSpreadsheetTimeZone();
var logSheet = ss.getSheetByName('Log');
var statusColumnNo = 9;
/**
* @OnlyCurrentDoc
*
* Creates Menu item.
*
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Admin')
.addItem('Select a folder of folders', 'pickerPopup')
.addItem('Get folder names', 'getFolderNames')
.addItem('Append folder permissions', 'setPermissions')
.addToUi();
};
function setPermissions() {
main();
};
function getFolderID(id) {
pickerFolderID(id);
};
function getFolderNames() {
folderNames();
};
/**
* It performs all of the functions for the Append Drive folder permissions system.
*/
function main() {
try {
logEvent("Starting 'main' Function.");
// get start time so can manage duration of script and avoid timeout
var startTime = new Date().getTime();
// create variable for capturing results
var errorFlag = false;
// create regular expression for testing valid email address
var emailAddressRegEx = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/gm;
// get Folders Sheet
var foldersSheet = ss.getSheetByName('Folders');
logEvent("Successfully got the 'Folders' Sheet.");
// get Column and call Function to retrieve last row number, then subtract 2 for Headings
var columnToCheck = foldersSheet.getRange("A:A").getValues();
var lastRowNo = getLastRowSpecial(columnToCheck) - 2;
// check no error with previous Function
if (lastRowNo !== false) {
// proceed as no error
// get the data in the Folders sheet
var lastCol = foldersSheet.getLastColumn() - 1;
var data = foldersSheet.getRange(3, 1, lastRowNo, lastCol).getValues();
var dataLength = data.length;
// create counter for number of rows actioned
var rowsActionedCounter = 0;
// loop through spreadsheet data ********************************
for (var i = 0; i < dataLength; i++) {
// create flag to determine if a folder has been updated
var aFolderHasBeenUpdated = false;
// log row number
var rowNo = i + 3;
logEvent("Current row number is: " + rowNo);
// get value of 'Folder ID or URL' and 'Status' columns to see if skipping row
var folderIDUrl = data[i][0];
var status = data[i][8];
logEvent("folderIDUrl column is: " + folderIDUrl);
logEvent("status column is: " + status);
if ((folderIDUrl != "") && (status == "")) {
// proceed with current row
// check folder / Shared drive reachable for user running script ************************
try {
// get the folder
var folderID = folderIDUrl.match(/[-\w]{19,}/);
var folder = DriveApp.getFolderById(folderID);
var folderName = folder.getName();
if (folderName == "Drive") {
// use the Drive API to get the Shared drive
var sharedDrive = Drive.Drives.get(folderID);
// get the name of the Shared drive
var folderName = sharedDrive.name;
logEvent("Successfully got Shared drive: " + folderName);
} else {
logEvent("Successfully got folder: " + folderName);
};
} catch (error) {
logEvent("Unable to get Drive folder: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Google Drive folder error";
var popupMessage = "Please check you have provided the correct folder ID/URL.<br/><br/>Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
// check folder reachable for user running script ************************
// extract row data
var managerEmails = data[i][2];
var contentManagerEmails = data[i][3];
var editorEmails = data[i][4];
var commenterEmails = data[i][5];
var viewerEmails = data[i][6];
var sendNotificationEmail = data[i][7];
// check if notification emails need sending
if (sendNotificationEmail == "Yes") {
logEvent("Option to send notification emails is Yes.");
var optionalArgs = {
sendNotificationEmails: true,
supportsAllDrives: true
};
} else {
logEvent("Option to send notification emails is No.");
var optionalArgs = {
sendNotificationEmails: false,
supportsAllDrives: true
};
};
// add Managers *****************************************
if (managerEmails.length != 0) {
var managerEmailAddresses = managerEmails.split(", ");
var managerEmailAddressesLength = managerEmailAddresses.length;
for (var j = 0; j < managerEmailAddressesLength; j++) {
// extract individual email address
var singleEmailAddress = managerEmailAddresses[j];
// test valid email address against RegEx
var checkEmailAddress = singleEmailAddress.match(emailAddressRegEx);
if (checkEmailAddress) {
// valid email address, proceed
var resource = {
value: singleEmailAddress,
type: 'user',
role: 'organizer'
};
Drive.Permissions.insert(resource, folderID, optionalArgs);
} else {
// invalid email addresss
logEvent("Invalid email address: " + singleEmailAddress);
// run Function to launch HTML popup
var popupTitle = "Invalid email address";
var popupMessage = "There is a problem with the following email address, please ensure you have entered it correctly into the Google Sheet with a comma and space.<br/><br/>Email address: " + singleEmailAddress;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
};
logEvent("Completed Manager email addresses.");
// update flag so know that a file has been updated
aFolderHasBeenUpdated = true;
} else {
logEvent("No Manager email addresses to add.");
};
// add Managers *****************************************
// add Content Managers *****************************************
if (contentManagerEmails.length != 0) {
var contentManagerEmailAddresses = contentManagerEmails.split(", ");
var contentManagerEmailAddressesLength = contentManagerEmailAddresses.length;
for (var j = 0; j < contentManagerEmailAddressesLength; j++) {
// extract individual email address
var singleEmailAddress = contentManagerEmailAddresses[j];
// test valid email address against RegEx
var checkEmailAddress = singleEmailAddress.match(emailAddressRegEx);
if (checkEmailAddress) {
// valid email address, proceed
var resource = {
value: singleEmailAddress,
type: 'user',
role: 'fileOrganizer'
};
Drive.Permissions.insert(resource, folderID, optionalArgs);
} else {
// invalid email addresss
logEvent("Invalid email address: " + singleEmailAddress);
// run Function to launch HTML popup
var popupTitle = "Invalid email address";
var popupMessage = "There is a problem with the following email address, please ensure you have entered it correctly into the Google Sheet with a comma and space.<br/><br/>Email address: " + singleEmailAddress;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
};
logEvent("Completed Content Manager email addresses.");
// update flag so know that a file has been updated
aFolderHasBeenUpdated = true;
} else {
logEvent("No Content Manager email addresses to add.");
};
// add Content Managers *****************************************
// add Editors *****************************************
if (editorEmails.length != 0) {
var editorEmailAddresses = editorEmails.split(", ");
var editorEmailAddressesLength = editorEmailAddresses.length;
for (var j = 0; j < editorEmailAddressesLength; j++) {
// extract individual email address
var singleEmailAddress = editorEmailAddresses[j];
// test valid email address against RegEx
var checkEmailAddress = singleEmailAddress.match(emailAddressRegEx);
if (checkEmailAddress) {
// valid email address, proceed
var resource = {
value: singleEmailAddress,
type: 'user',
role: 'writer'
};
Drive.Permissions.insert(resource, folderID, optionalArgs);
} else {
// invalid email addresss
logEvent("Invalid email address: " + singleEmailAddress);
// run Function to launch HTML popup
var popupTitle = "Invalid email address";
var popupMessage = "There is a problem with the following email address, please ensure you have entered it correctly into the Google Sheet with a comma and space.<br/><br/>Email address: " + singleEmailAddress;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
};
logEvent("Completed Editor email addresses.");
// update flag so know that a file has been updated
aFolderHasBeenUpdated = true;
} else {
logEvent("No Editor email addresses to add.");
};
// add Editors *****************************************
// add Commenters *****************************************
if (errorFlag === false) {
if (commenterEmails.length != 0) {
var commenterEmailAddresses = commenterEmails.split(", ");
var commenterEmailAddressesLength = commenterEmailAddresses.length;
for (var j = 0; j < commenterEmailAddressesLength; j++) {
// extract individual email address
var singleEmailAddress = commenterEmailAddresses[j];
// test valid email address against RegEx
var checkEmailAddress = singleEmailAddress.match(emailAddressRegEx);
if (checkEmailAddress) {
// valid email address, proceed
var resource = {
value: singleEmailAddress,
type: 'user',
role: 'reader',
additionalRoles: ["commenter"]
};
Drive.Permissions.insert(resource, folderID, optionalArgs);
} else {
// invalid email addresss
logEvent("Invalid email address: " + singleEmailAddress);
// run Function to launch HTML popup
var popupTitle = "Invalid email address";
var popupMessage = "There is a problem with the following email address, please ensure you have entered it correctly into the Google Sheet with a comma and space.<br/><br/>Email address: " + singleEmailAddress;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
};
logEvent("Completed Commenter email addresses.");
// update flag so know that a file has been updated
aFolderHasBeenUpdated = true;
} else {
logEvent("No Commenter email addresses to add.");
};
} else {
// do nothing as errors have occurred
};
// add Commenters *****************************************
// add Viewers *****************************************
if (errorFlag === false) {
if (viewerEmails.length != 0) {
var viewerEmailAddresses = viewerEmails.split(", ");
var viewerEmailAddressesLength = viewerEmailAddresses.length;
for (var j = 0; j < viewerEmailAddressesLength; j++) {
// extract individual email address
var singleEmailAddress = viewerEmailAddresses[j];
// test valid email address against RegEx
var checkEmailAddress = singleEmailAddress.match(emailAddressRegEx);
if (checkEmailAddress) {
// valid email address, proceed
var resource = {
value: singleEmailAddress,
type: 'user',
role: 'reader'
};
Drive.Permissions.insert(resource, folderID, optionalArgs);
} else {
// invalid email addresss
logEvent("Invalid email address: " + singleEmailAddress);
// run Function to launch HTML popup
var popupTitle = "Invalid email address";
var popupMessage = "There is a problem with the following email address, please ensure you have entered it correctly into the Google Sheet with a comma and space.<br/><br/>Email address: " + singleEmailAddress;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
};
logEvent("Completed Viewer email addresses.");
// update flag so know that a file has been updated
aFolderHasBeenUpdated = true;
} else {
logEvent("No Viewer email addresses to add.");
};
} else {
// do nothing as errors have occurred
};
// add Viewers *****************************************
if (aFolderHasBeenUpdated === true) {
// update Status Column
foldersSheet.getRange(rowNo, statusColumnNo).setValue("Completed");
// increment rows actioned counter by 1
rowsActionedCounter++;
} else {
// no folder has been updated
};
} else {
logEvent("Skipping row.");
};
// perform runtime check ************************************************
// get current time
var endTime = new Date().getTime();
// find elapsed time by subtracting from start time
var elapsedTime = endTime - startTime;
// check against maximum runtime
var timeLimitExceeded = elapsedTime >= maxRuntime;
// check status
if (timeLimitExceeded) {
// runtime has been met/exceeded
logEvent('Runtime has been met/exceeded.');
// run Function to launch HTML popup
var popupTitle = "Time limit reached";
var popupMessage = "The maximum Google runtime has been reached but there are still folders to action. To continue please close this dialogue box and select to run the tool again from the Menu.";
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop to prevent script from continuing
break;
} else {
// runtime has not been met/exceeded, script can continue looping through rows
};
// perform runtime check ************************************************
};
// loop through spreadsheet data ********************************
} else {
// error occured, do nothing as popup already displayed
errorFlag = true;
};
// determine final popup message
if (errorFlag === false) {
// no errors have occured
// run Function to launch HTML popup
var popupTitle = "Successfully completed";
var popupMessage = rowsActionedCounter + " folders have been appended with permissions.";
htmlPopup(popupTitle, popupMessage);
} else {
// errors have occured, do nothing as popup box already being displayed
};
logEvent("Completed 'main' Function.");
} catch (error) {
logEvent("Error with 'main' Function: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Error with 'main' Function";
var popupMessage = "Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
};
};
/**
* Function to get last row number from specified column.
* Used as future additions to the Google Sheet may include tickboxes which affect 'getRange()'.
*/
function getLastRowSpecial(columnToCheck) {
try {
logEvent("Started 'getLastRowSpecial' Function.");
// reset variables before using in loop below
var rowNum = 0;
var blank = false;
// loop through the array and check the value in the cell ****************************
for (var row = 0; row < columnToCheck.length; row++) {
// check if cell value is empty AND 'blank' variable is not false
var rowValue = columnToCheck[row][0];
if ((rowValue == "") && (!blank)) {
// if true then set row number variable to value of loop and flag it's true
rowNum = row;
blank = true;
}
else if (rowValue != "") {
// if the cell value is not empty (contains data) then flag it's not blank
blank = false;
}
}
// loop through the array and check the value in the cell ****************************
logEvent("Last Row is: " + rowNum);
logEvent("Completed 'getLastRowSpecial' Function.");
return rowNum;
} catch (error) {
logEvent("Error with 'getLastRowSpecial' Function: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Error with 'getLastRowSpecial' Function";
var popupMessage = "Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
// return error flag
return false;
};
};
/**
* Display a dialog box to prompt user if they wish to open Google Picker.
*/
function pickerPopup() {
var ui = SpreadsheetApp.getUi();
var result = ui.alert(
'Open Folder Picker',
'Select this option if you have a single Google Drive folder full of folders (no sub-folders). Only click once to select your folder (do not double-click it). It make take up to 10 seconds to load.',
ui.ButtonSet.YES_NO
);
// Process the user's response.
if (result == ui.Button.YES) {
// User clicked "Yes".
showPicker();
} else {
// User clicked "No" or X in the title bar.
};
};
function showPicker() {
var html = HtmlService.createHtmlOutputFromFile('Picker.html')
.setWidth(650)
.setHeight(500)
.setSandboxMode(HtmlService.SandboxMode.IFRAME);
SpreadsheetApp.getUi().showModalDialog(html, 'Select Folder');
};
function getOAuthToken() {
DriveApp.getRootFolder();
return ScriptApp.getOAuthToken();
};
<!DOCTYPE html>
<html>
<head>
<!-- Add the standard Google Style Sheet. -->
<link rel="stylesheet" href="https://ssl.gstatic.com/docs/script/css/add-ons1.css">
<script type="text/javascript">
var DIALOG_DIMENSIONS = {
width: 650,
height: 500
};
var pickerApiLoaded = false;
function onApiLoad() {
gapi.load('picker', {
'callback': function() {
pickerApiLoaded = true;
}
});
google.script.run.withSuccessHandler(createPicker).withFailureHandler(showError).getOAuthToken();
}
function createPicker(token) {
if (pickerApiLoaded && token) {
var docsView = new google.picker.DocsView()
.setParent('root')
.setIncludeFolders(true)
.setMode(google.picker.DocsViewMode.LIST)
.setMimeTypes('application/vnd.google-apps.folder')
.setSelectFolderEnabled(true);
var picker = new google.picker.PickerBuilder()
.addView(docsView)
.hideTitleBar()
.setSize(DIALOG_DIMENSIONS.width - 2, DIALOG_DIMENSIONS.height - 2)
.setOAuthToken(token)
.setCallback(pickerCallback)
.setOrigin("https://docs.google.com")
.build();
picker.setVisible(true);
} else {
showError('Unable to load the file picker.');
};
};
/*
Function used as SuccessHandler for 'pickerCallback' so will only close dialogue
box if that codes runs correctly.
*/
function folderInserted(){
google.script.host.close();
}
/**
* A callback function that extracts the chosen document's metadata from the
* response object. For details on the response object, see
* https://developers.google.com/picker/docs/result
*
* @param {object} data The response object.
*/
function pickerCallback(data) {
var action = data[google.picker.Response.ACTION];
if (action == google.picker.Action.PICKED) {
var doc = data[google.picker.Response.DOCUMENTS][0];
var id = doc[google.picker.Document.ID];
/*
Runs Apps Script Function 'insertFolderURL' and passes in item Id from Google Picker.
A failure to run will trigger below 'showError' Function.
A success run will trigger above 'folderInserted' Function and close dialogue box.
*/
google.script.run.withSuccessHandler(folderInserted).withFailureHandler(showError).getFolderID(id);
}
else if (action == google.picker.Action.CANCEL) {
google.script.host.close();
}
}
function showError(message) {
document.getElementById('result').innerHTML = 'Error: ' + message;
}
</script>
</head>
<body>
<div>
<p id='result'></p>
</div>
<script type="text/javascript" src="https://apis.google.com/js/api.js?onload=onApiLoad"></script>
</body>
</html>
/**
* Take Google Drive Folder ID passed from Google Picker.
*/
function pickerFolderID(id) {
try {
logEvent("Starting 'pickerFolderID' Function.");
logEvent("Google Drive folder ID from Picker is: " + id);
// create emtpy array for pushing folder data into to append to Sheet at very end
var folderDetailsArray = [];
// create variable to use as Counter for index of folders
var folderCounter = 0;
// get Google Drive folder of folders
var sourceFolder = DriveApp.getFolderById(id);
// get folders inside of folder
var folders = sourceFolder.getFolders();
while (folders.hasNext()) {
var childFolder = folders.next();
// create clickable folder name link
var folderName = childFolder.getName();
var folderUrl = childFolder.getUrl();
var hyperlink = '=HYPERLINK("' + folderUrl + '","' + folderName + '")';
// get folder ID
var folderId = childFolder.getId();
// push data into beginning of array for later adding back into Google Sheet
folderDetailsArray.unshift([folderId, hyperlink]);
// increment foler Counter by 1
folderCounter++;
};
logEvent("Number of folders searched through is: " + folderCounter);
// get Folders Sheet
var foldersSheet = ss.getSheetByName('Folders');
logEvent("Successfully got the 'Folders' Sheet.");
// get Column and call Function to retrieve last row number, then subtract 2 for Headings
var columnToCheck = foldersSheet.getRange("A:A").getValues();
var lastRowNo = getLastRowSpecial(columnToCheck);
// check no error with previous Function
if (lastRowNo !== false) {
// now append row data to Google Sheet in one go
var arrayLength = folderDetailsArray.length;
var arrayWidth = folderDetailsArray[0].length;
foldersSheet.getRange(lastRowNo + 1, 1, arrayLength, arrayWidth).setValues(folderDetailsArray);
logEvent("Completed 'pickerFolderID' Function.");
} else {
// error occured, do nothing as popup already displayed
};
} catch (error) {
logEvent("Error with 'pickerFolderID' Function: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Error with 'pickerFolderID' Function";
var popupMessage = "Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
};
};
/**
* Loop through Google Sheet, collate folder names and input back into Sheet.
*/
function folderNames() {
try {
logEvent("Starting 'folderNames' Function.");
// create variable for capturing errors
var errorFlag = false;
// get Folders Sheet
var foldersSheet = ss.getSheetByName('Folders');
logEvent("Successfully got the 'Folders' Sheet.");
// get Column and call Function to retrieve last row number, then subtract 2 for Headings
var columnToCheck = foldersSheet.getRange("A:A").getValues();
var lastRowNo = getLastRowSpecial(columnToCheck) - 2;
// check no error with previous Function
if (lastRowNo !== false) {
// get the data in the Folders sheet
var data = foldersSheet.getRange(3, 1, lastRowNo, 2).getValues();
var dataLength = data.length;
// create variable to use as Counter for index of folders
var folderCounter = 0;
// loop through spreadsheet data ********************************
for (var i = 0; i < dataLength; i++) {
// get value of 'Folder ID or URL' and 'Folder Name' column to see if skipping row
var folderIDUrl = data[i][0];
var existingFolderName = data[i][1];
if ((folderIDUrl != "") && (existingFolderName == "")) {
// proceed with current row
var rowNo = i + 3;
// check folder reachable for user running script ************************
try {
// get the folder
var folderID = folderIDUrl.match(/[-\w]{19,}/);
var folder = DriveApp.getFolderById(folderID);
var folderName = folder.getName();
// a Shared drive root will return 'Drive' as its name, so use a different method
if (folderName == "Drive") {
// use the Drive API to get the Shared drive
var sharedDrive = Drive.Drives.get(folderID);
// get the name of the Shared drive
var folderName = sharedDrive.name;
} else {
// do nothing
};
var folderUrl = folder.getUrl();
var hyperlink = '=HYPERLINK("' + folderUrl + '","' + folderName + '")';
} catch (error) {
logEvent("Unable to get Drive folder: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Google Drive folder error";
var popupMessage = "Please check you have provided the correct folder ID/URL<br/><br/>. Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
errorFlag = true;
// break out of loop
break;
};
// check folder reachable for user running script ************************
// append folder name to Google Sheet
foldersSheet.getRange(rowNo, 2).setValue(hyperlink);
// increment folder Counter by 1
folderCounter++;
} else {
// skip row as blank
};
};
// determine final popup message
if (errorFlag === false) {
// no errors have occured
// run Function to launch HTML popup
var popupTitle = "Successfully completed";
var popupMessage = folderCounter + " folder names have been collated.";
htmlPopup(popupTitle, popupMessage);
} else {
// errors have occured, do nothing as popup box already being displayed
};
logEvent("Completed 'folderNames' Function.");
} else {
// error occured, do nothing as popup already displayed
};
} catch (error) {
logEvent("Error with 'folderNames' Function: " + error.stack);
// run Function to launch HTML popup
var popupTitle = "Error with 'folderNames' Function";
var popupMessage = "Message: " + error.stack;
htmlPopup(popupTitle, popupMessage);
};
};
/**
* Function to output messages to the 'Log' sheet.
* Can be called anywhere else in script.
*/
function logEvent(action) {
// get the user running the script
var theUser = Session.getActiveUser().getEmail();
// create and format a timestamp
var dateTime = new Date();
var niceDateTime = Utilities.formatDate(dateTime, timeZone, "dd/MM/yyyy HH:mm:ss");
// create array of data for pasting into log sheet
var logData = [niceDateTime, theUser, action];
// append details into next row of log sheet
logSheet.appendRow(logData);
};
/**
* Display a modal dialog box with custom HtmlService content.
* Does not suspend the script.
*/
function htmlPopup(popupTitle, popupMessage) {
var htmlOutput = HtmlService
.createHtmlOutput(popupMessage)
.setWidth(380)
.setHeight(180);
SpreadsheetApp.getUi().showModalDialog(htmlOutput, popupTitle);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment