Thursday, March 12, 2015
Domain user management with Apps Script
Managing the user accounts in a Google Apps domain can be a daunting task when you have hundreds or thousands of them and you have no tools to automate the process. The Google Apps Provisioning API allows developers to write user management applications in the programming language of their choice, but many system administrators prefer a script-based solution instead. The recently launched UserManager Apps Script service fills the gap, providing Google Apps domain administrators an easy way to automate tasks such as batch user creation or update.
With the new Apps Script service, creating a user will be as easy as writing a single line of code:
var user = UserManager.createUser("newuser", "John", "Smith", "mypassword"); The UserManager service also makes it easy to perform the same task on each account in the domain. The following sample shows how you can force all users to change their passwords at the next login:
var users = UserManager.getAllUsers();
for (var i in users) {
users[i].setChangePasswordAtNextLogin(true);
}
Calls to the UserManager service can also be scheduled to run hourly or daily, or in response to certain events thanks to Apps Script Triggers.
Interested in what else you can do with the UserManager service? Please check the documentation and get in touch with us on the the forum for any questions about its usage or to share more info about your project with the community.
![]() | Claudio Cherubino profile | twitter | blog Claudio is a Developer Programs Engineer working on Google Apps APIs and the Google Apps Marketplace. Prior to Google, he worked as software developer, technology evangelist, community manager, consultant, technical translator and has contributed to many open-source projects, including MySQL, PHP, Wordpress, Songbird and Project Voldemort. |
Wednesday, March 11, 2015
Sheetcaster 3D in Apps Script
Editor’s note: This is a guest post by Thomas Coudray, Amaury de la Vieuville, and Ahmed Bougacha. Thomas, Amaury, and Ahmed attended the Google Apps Script Hackathon in Paris, and in this post they are sharing their creative use of Google Apps Script to render a 3D scene in a Google Spreadsheet. -- Jan Kleinert
Recently, we heard about the Google Apps Script Hackathon arriving in Paris, France. We did not know much about Apps Script - heck, even JavaScript! Perfect occasion to learn something. We spent most of the event hacking around with the ever-growing collection of Google APIs. As a tribute to the folks over at id Software, we settled on one of the most fun (however useless) ways to use it: rendering a 3D scene in a spreadsheet.

The rendering is done using a technique called ray-casting, made popular by the 90s id Software game Wolfenstein 3D. Ray-casting is a really brilliant and straightforward algorithm:
First, we render the background: color the upper (sky) and lower (floor) halves of the screen in different colors. We store the pixel colors in a matrix, the screen buffer:
screen = new Array(SIZE_Y);
for (var lin = 0; lin < SIZE_Y; lin++) {
screen[lin] = new Array(SIZE_X);
for (var col = 0; col < SIZE_X; col++) {
screen[lin][col] = colorToString((lin < MID) ? UPPER_BG_COLOR
: LOWER_BG_COLOR);
}
}
Note that we draw the screen only once the buffer is fully colored, to avoid the overhead of coloring cells individually.
Then for each column of the screen:
- Cast a ray
- Move along the ray until hitting a wall, calculate the distance to that wall
- Draw a column whose height is inversely proportional to that distance
The trick is in the drawing: the upper and lower halves of the screen are symmetrical in shape, and the only computed value is the display height of the wall. The screen really is just a fancy formatting for an integer array of columns.
The camera is represented using:
- Its (real-valued) x/y coordinates in the map plane
- Its angle relative to some predefined direction
We store these 3 values at the bottom of the sheet, to ensure persistence (else, each refresh would bring us back to the start location!).
function Camera() {
this.x = CAMERA_X;
this.y = CAMERA_Y;
this.theta = CAMERA_THETA;
this.saveToSheet = function(sheet) {
// The player state has to be saved between each frame
sheet.getRange(STORE_LIN, 1, 1, 1).setValue(this.x);
sheet.getRange(STORE_LIN, 2, 1, 1).setValue(this.y);
sheet.getRange(STORE_LIN, 3, 1, 1).setValue(this.theta);
};
this.readFromSheet = function(sheet) {
this.x = sheet.getRange(STORE_LIN, 1, 1, 1).getValue();
this.y = sheet.getRange(STORE_LIN, 2, 1, 1).getValue();
this.theta = sheet.getRange(STORE_LIN, 3, 1, 1).getValue();
};
...
}
The map is a logical matrix, thus limiting us to discrete boxes for walls: for every cell, there either is (1), or is not (0), a wall:
// starting 10x10 map
var S = 10;
var map =
[
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 0, 1, 0, 0, 0, 0, 0, 0, 1],
[1, 0, 1, 0, 1, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 0, 0, 1, 0, 0, 1, 0, 1],
[1, 0, 1, 1, 1, 1, 0, 1, 0, 1],
[1, 0, 0, 0, 0, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
];

It is also possible to modify the map in real-time: write a character in the boxes you want to swap, then hit Refresh map.
Moving involves adding (or subtracting for backwards movements) to the xy coordinates, using basic trigonometry, but only after checking the validity of the move (i.e. that it will not collide with a wall):
function Camera() {
...
this.move = function(distance) {
// return whether valid move or not
x = this.x + Math.cos(this.theta) * distance;
y = this.y + Math.sin(this.theta) * distance;
if (isValidPos(x, y)) {
this.x = x;
this.y = y;
return true;
}
return false;
};
}
function moveUp() {
readMapFromSheet(sheet); // Retrieve the map from the sheet
var camera = new Camera();
camera.readFromSheet(sheet); // Retrieve the camera state from the sheet
camera.move(0.5);
raycast(camera);
}
Turning left (respectively right) is even simpler, adding (respectively subtracting) small constants to the camera angle (mod 2 PI):
function Camera() {
...
this.rotate = function(alpha) {
this.theta = (this.theta + alpha + 2 * Math.PI) % (2 * Math.PI);
};
}
function lookRight() {
readMapFromSheet(sheet);
var camera = new Camera();
camera.readFromSheet(sheet);
camera.rotate(-0.25);
raycast(camera);
}
Actual actions (moving/turning) are shown in a menu:
spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var subMenus = [
{name:"Reset",functionName:"onOpen"},
{name:"Refresh map",functionName:"refresh"},
{name:"Move forward",functionName:"up"},
{name:"Look left",functionName:"left"},
{name:"Look right",functionName:"right"},
{name:"Move backward",functionName:"down"},
{name:"Turn around",functionName:"turn"},
];
spreadsheet.addMenu("Sheetcaster", subMenus);
The ray is cast as follows:
- Its origin is the cameras 2D coordinates in the map plane
- Its direction is calculated off the cameras and the column index (the center column will have the exact same direction as the camera; the other columns directions depend on the field of view parameter)
/*
* Given a value on the x axis (screen column),
* return the ray that will be cast
*/
function getRay(camera, x) {
var cos = Math.cos(camera.theta);
var sin = Math.sin(camera.theta);
// from -1 to 1: 0 being when x is the middle column
var k = ((SIZE_X / 2) - x) / SIZE_X;
return new Vector_(
cos / 2 - k * sin * K_FOV,
sin / 2 + k * cos * K_FOV
);
}
Moving the ray is the most involved step:
- Calculate the distance to the next vertical and horizontal borders
- Move to the closest border
while (!hit) {
// Next potential wall is on the x axis
if (dist.x < dist.y) {
// Distance from the camera, delta:
/ Distance between each horizontal wall along the ray
dist.x += delta.x;
// step.x is either 1 or -1, depending on the ray direction
mapCoord.x += step.x;
hit = readMap_(mapCoord.x, mapCoord.y);
} else { // Next potential wall is on the y axis
dist.y += delta.y;
mapCoord.y += step.y;
hit = readMap_(mapCoord.x, mapCoord.y);
}
}
The height of the drawn column is nothing fancy: the further the wall, the smaller-looking the wall, hence the smaller the height of the column.
Again, nothing really complicated. However, the simplicity of this wall-height technique is the reason behind its major caveat: there is no clean way to look up or down: you can only turn left or right, and move forward or backward.
Displaying the rendered image is done using a spreadsheet. Each cell becomes a small square pixel, its color being the background color of the cell. We pass our scren buffer matrix to the handy setBackgroundColors:
sheet.getRange(1, 1, SIZE_Y, SIZE_X).setBackgroundColors(screen);
As you probably noticed, the low display density makes the sharp, jagged, edges really visible. Fear not, reader, for we also implemented anti-aliasing!
The anti-aliasing algorithm is even simpler:
- Accumulate the length of runs (successions of same-sized columns)
- Draw a gradient, from the background (wall and floor) to the wall, above (and below) the columns
When the runs are really small (< 5 columns), we attenuate the gradient intensity, as it would only add another pixel above (below) the column, thus rendering the antialiasing utterly useless.

Real-time was not an objective, the main problem being controlling the player/camera. Scripted movements should however be quite easy to implement with a fixed duration loop, restarting itself using an Apps Script recurrent time-driven trigger (a minute-long loop, repeated every minute). This is left as an exercise to the reader.
Please feel free to copy the script and walk around this Apps Script virtual world.
![]() | Thomas Coudray Thomas is interested in low level computing and application security. |
![]() | Amaury de la Vieuville Amaury is passionate about algorithmic problem-solving and software engineering. |
![]() | Ahmed Bougacha Ahmed is interested in kernels, compilers and theoretical computer science. |
Tuesday, March 10, 2015
Come Learn About Apps Script in Washington DC
Editors note: This has been cross-posted from the Google Code blog -- Jan Kleinert
Google Apps Script is a JavaScript cloud scripting language that provides easy ways to automate tasks across Google products and third party services. If you want to learn more about Google Apps Script, collaborate with other developers, and meet the Apps Script team, here’s your chance! We will be holding an Apps Script hackathon in Washington, DC on Wednesday, March 7 from 2pm - 8pm.
After we cover the basics of Apps Script, you can code along with us as we build a complete script, or you can bring your own ideas and get some help and guidance from the team. There will be food, power, and Apps Script experts available to help throughout the day. Just bring your laptop, ideas, enthusiasm, and basic knowledge of JavaScript. Check out out the details of the event and be sure to RSVP to let us know you’re coming.
![]() | Jan Kleinert profile | twitter Jan is a Developer Programs Engineer based in NYC, focusing on helping developers get the most out of Google Apps Script. Prior to Apps Script, she worked on Commerce, helping merchants integrate with Google Checkout and on Chrome, helping developers build great web apps. |
Monday, March 9, 2015
Control your Google Sites from Apps Script
// create a site, given the domain, site URL, site title and summary
var webpage = site.createWebPage("Jimmys Page", "JimmysPage", "Welcome to Jimmys site");
var annPage = site.createAnnouncementsPage("Team Announcements", "Announcements", "New announcements for the team will be posted here.");
site.createAnnouncement("Soccer Match #1", message, annPage);
Ryan Boyd
Building an Idea Bank using Google Apps Script
Editor’s Note: This is a guest post by Saqib Ali. Saqib is a Google Apps evangelist at Seagate. He has used Apps Script to create a number of applications that leverage Google Apps. His other interests include the curation of Lewis Carroll’s letter and translation of Urdu poetry to English. -- Ryan Boyd
What is an Idea Bank?
Idea Banks are repositories for innovative ideas that Seagate employees can submit, and others can vote on those ideas. Before Google Apps Script we had a custom built Idea Bank on the LAMP stack. With the release of the UI Services in the Google Apps Script, we wanted to port that Idea Bank to Google Apps to easily manage idea submissions in a Google Spreadsheet.
Designing the Idea Bank
A typical Idea Bank consists of three basic functions:
- Ability to submit and store ideas to a central database.
- Ability to vote on ideas.
- Ability to add description comment on ideas.
A traditional application would probably use a Relational Database like MySQL to store the ideas. However we found that using Google Spreadsheet to store the ideas provides two inherent benefits:
- Entered data can be easily managed using the Spreadsheet Editor;
- Revision history. Since Spreadsheet provides built-in revision history, we don’t have to create a system for tracking the changes to the submitted ideas.
The number of votes, and the voters are tracked using cells in the spreadsheet. For voters we used the Session.getUser().getEmail() to get the email address of the logged in user, and store them in the spreadsheet.
Since the Ideas Bank is embedded in a Google Site, we were able to simply use the Google Sites Page as a place holder to add description and comments to the ideas. Once the idea is submitted, a Google Sites page gets created corresponding to that idea from predefined template using the createPageFromTemplace() function. The submitter can then add detailed description in the template. Others can add comments to that Site pages.
Implementation Details
Using Spreadsheet Services to Manage Data
All the data is stored in a Google Spreadsheet, which makes it easy for the Idea Bank manager to manage (delete, remove, modify) the ideas using the Spreadsheets Editor.
Code snippet for adding new ideas to the spreadsheet:
var ss = SpreadsheetApp.openById(""); // Spreadsheet id goes here
SpreadsheetApp.setActiveSpreadsheet(ss);
ideas_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Ideas");
var last_row = ideas_sheet.getLastRow();
var next_empty_row = last_row+1;
ideas_sheet.setActiveCell("A"+next_empty_row);
ideas_sheet.getActiveCell().setValue(e.parameter.ideadescription);
ideas_sheet.setActiveCell("B"+next_empty_row);
ideas_sheet.getActiveCell().setValue(Session.getActiveUser().getUserLoginId());
ideas_sheet.setActiveCell("E"+next_empty_row);
ideas_sheet.getActiveCell().setValue(Session.getActiveUser().getEmail()); Code snippet to read the ideas from the Spreadsheet and display them:
var ss = SpreadsheetApp.openById(""); // Spreadsheet id goes here
SpreadsheetApp.setActiveSpreadsheet(ss);
ideas_sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Ideas");
var last_row = ideas_sheet.getLastRow();
var last_column = ideas_sheet.getLastColumn();
var sheet_array = ideas_sheet.getRange(2, 1, last_row, last_column).getValues();
var submitIdeaButton = app.createButton("I have another idea");
var submitIdeaButtonHandler = app.createServerClickHandler(showSubmitIdeaDialog);
submitIdeaButton.addClickHandler(submitIdeaButtonHandler);
applyCSS(submitIdeaButton, _submitideabutton);
var ideaContents = app.createGrid().resize(last_row,3);
ideaContents.setId("ideacontents");
ideaContents.setWidth("100%");
ideaContents.setCellSpacing(0);
scrollPanel.add(ideaContents);
app.add(scrollPanel);
for (var row_i = 0; row_i < last_row-1; row_i++) {
var ideaDescriptionLabel = app.createLabel(sheet_array[row_i][0]).setStyleAttribute("font","16px Sans-serif").setWordWrap(true);
var submitter = sheet_array[row_i][1].split("@");
var ideaAuthor = app.createLabel(submitter[0]).setStyleAttribute("font","10px Courier New, Courier, monospace").setStyleAttribute("color", "#CCC")
ideaContents.setWidget(row_i, 0, app.createVerticalPanel().add(ideaDescriptionLabel).add(ideaAuthor)).setStyleAttribute("overflow","visible").setStyleAttribute("white-space","normal !important");
//Button to display the voters
var numberOfVotesForm = app.createFormPanel().setId(numofvotesform);
var numberOfVotesFormContent = app.createVerticalPanel()
numberOfVotesForm.add(numberOfVotesFormContent);
numberOfVotesFormContent.add(app.createTextBox().setName(ideaID).setText(row_i + "").setVisible(false).setSize("0","0"));
numberOfVotesFormContent.add(app.createTextBox().setName(voters).setText(sheet_array[row_i][4]).setVisible(false).setSize("0","0"));
var numberOfVotesButton = app.createButton(countVotes(sheet_array[row_i][4]) + " vote(s)").setId("numberOfVotesButton"+row_i);
applyCSS(numberOfVotesButton, _numofvotesbutton);
var numberOfVotesButtonHandler = app.createServerClickHandler(showVotersDialog);
numberOfVotesButtonHandler.addCallbackElement(numberOfVotesFormContent);
numberOfVotesButton.addClickHandler(numberOfVotesButtonHandler);
numberOfVotesFormContent.add(numberOfVotesButton);
//Button to cast a vote
var voteForm = app.createFormPanel().setId(voteform);
var voteFormContent = app.createVerticalPanel();
voteForm.add(voteFormContent);
voteFormContent.add(app.createHidden(ideaID, row_i + "").setSize("0","0"))
// Identify the function schedule as the server click handler
var voteButton = app.createButton(I like this!).setId("voteButton"+row_i)
var voteButtonHandler = app.createServerClickHandler(casteVote);
voteButtonHandler.addCallbackElement(voteFormContent);
voteButton.addClickHandler(voteButtonHandler);
if (sheet_array[row_i][4].indexOf(Session.getActiveUser().getEmail())>-1)
voteFormContent.add(voteButton.setText("Thanks!").setEnabled(false));
else
voteFormContent.add(voteButton);
ideaContents.setWidget(row_i, 1, numberOfVotesForm);
ideaContents.setWidget(row_i, 2, voteForm);
}
app.add(submitIdeaButton); Using Ui Services to embed User Interface in Google Sites
Ui Service was used to build the front end for the app. UI Services are based on GWT, so it is a good idea to have a basic understanding of the GWT framework. The following were used in building this app:
- Horizontal panel to display each idea;
- Vertical panel to display the list of ideas;
- Simple button to cast votes and submit ideas;
- Form panel to accept new ideas.


A real live working example is available here. Full source code is available here.
But why Google Apps Script?
So why did I choose Google Apps Script? Well for one it is at no extra cost, comes with your Google Account, it is in the cloud (i.e. no servers required), integrates well with Google Sites and Spreadsheets, and most importantly it uses GWT UI widgets. Google Apps Script’s UI Services and the ability to easily integrate with any REST interface make Apps Script an easy choice.
![]() | Saqib Ali Saqib is a Google Apps evangelist at Seagate. He has used Apps Script to create a number of applications that leverage Google Apps. His other interests include the curation of Lewis Carroll’s letter and translation of Urdu poetry to English. |
Gmail and Document Services now available in Apps Script
GmailApp - read, label and send emails
The new GmailApp should be instantly familiar if you’re used to using Gmail. You have access to labels, threads, and messages, and you can do all the things you expect: change labels, add and remove stars, mark things as important or trash or spam. You can also use the GmailApp.search() method to do any search you can do in Gmail itself. This is the first full fidelity API to Gmail, and we’re excited to see what you all will do with it.To make sure your Gmail account remains private and secure, we are extra cautious about any script that uses
GmailApp. Any time you change the code for a script that accesses Gmail, we will require you to reauthorize the script to run. Keep that in mind when editing a script that runs on a trigger - if you don’t reauthorize it, it will fail the next time the trigger tries to run it.DocumentApp - create and edit Docs
The new DocumentApp lets you create new documents or open existing ones by id. The id of a document is always in the URL you see when visiting it, and document ids are consistent between DocumentApp and the existing DocsList service, so you can use DocsList.find() to search for a document, and then get its id from File.getId(). Once you have a document, you can access all of its individual elements. You can do search and replace (great for mail merge!), change styles or text, add or remove tables and lists, and much more. And when you are done, you can call
Document.saveAndClose(), which makes sure all of your changes to the document get saved. If you don’t call it, we’ll do it for you at the end of the script, but it can be useful to call it yourself if you’d like to do something with the document after making your edits.GmailApp and DocumentApp are available to all Apps Script users right now. Now the only question is, what are you going to build with them?Posted by Corey Goldfeder, Google Apps Script Team
Want to weigh in on this topic? Discuss on Buzz
XML changes in Apps Script
Many developers have come to prefer JSON for data serialization, but we recognize that good ol XML is still an important format for many Apps Script users. Our existing XML service is good at parsing XML, but has limited ability to create or alter existing documents. In order to provide a more complete and consistent experience, we have created a new XML service, which launches today. The new service is accessed using XmlService, in contrast to the old service which was simply called Xml.
Lets take a look at how you can use the new service to create an XML representation of the emails in your Gmail inbox.
function createXml() {
var root = XmlService.createElement(threads);
var threads = GmailApp.getInboxThreads();
for (var i = 0; i < threads.length; i++) {
var child = XmlService.createElement(thread)
.setAttribute(messageCount, threads[i].getMessageCount())
.setAttribute(isUnread, threads[i].isUnread())
.setText(threads[i].getFirstMessageSubject());
root.addContent(child);
}
var document = XmlService.createDocument(root);
var xml = XmlService.getPrettyFormat().format(document);
Logger.log(xml);
}
The code above logs XML that looks something like this:
<?xml version="1.0" encoding="UTF-8"?>
<threads>
<thread messageCount="1" isUnread="true">
Cant wait for the new XML service!
</thread>
<thread messageCount="1" isUnread="true">
50% off all widgets through Friday
</thread>
<thread messageCount="3" isUnread="false">
Dont forget about the picnic on Saturday
</thread>
</threads>
The new XML service has some notable advantages over the old service:
- The ability to alter parsed XML content and save it back to a string.
- Access to all entity types in the XML document (
CDATA sections,Comments, etc.) - More control over the formatting of the XML string.
With the launch of this new service, we are deprecating some of our older XML tools in Apps Script, specifically the old XML service, the SOAP service, and the JavaScript feature E4X. Calls to these services will continue to work, but we encourage you to start migrating your code to the new XML service for better long-term support. On February 1, 2014, these old services will no longer appear in auto-complete or in our documentation, per the Apps Script sunset schedule.





