Using Directory API in Google-Apps-Script, how to

2019-02-05 15:02发布

问题:

I'm working on a script that gets all goups in my domain and lists all users in these groups.

I rewrote this script because GroupsManager is deprecated so my old script will stop working soon.

All is working well (although this API is quite slow...) but the issue I have is that some groups have not only users but include also other groups... some of them have only subgroups (and no users (way of speaking !)

I can't find any way to catch that situation, the list method return users, it ignores completely groups inside.

So my question is :

Is there a way to get the groups inside a group ? Am I missing something obvious ?

Below and for info is the code I use to list all members in groups in a spreadsheet, 1 sheet / group so that I can manipulate these data faster and easier later on. I used background color flags as a track keeper to handle a (relatively) small batch operation and a timer trigger that make it work until it's done. (yes, we have a lot of groups in our domain ;-)

function listGroupMembers() {
  var start = new Date().getTime();
  var ss = SpreadsheetApp.openById('1k-o4IVKEhW2zkk_________f2rH4UUt3OAC8I0ZoM');// set your SS ID (runs with timer trigger > needs to open by ID
  var pageToken, page;
  var count=0;
  var groupList = [];
  do {
    page = AdminDirectory.Groups.list({
      domain: 'xxxxx.be',
      maxResults: 100,
      pageToken: pageToken
    });
    var gr = page.groups;
    if (gr) {
      for (var i = 0; i < gr.length; i++) {
        var group = gr[i];
        count++;
        //Logger.log(group);
        groupList.push([group.email,count])
      }
    } else {
      Logger.log('No group found.');
    }
    pageToken = page.nextPageToken;
  } 
  while (pageToken);
  try{
    var GroupAddress = ss.getSheetByName('GroupAddress');// if GroupAddress Sheet already exist, open it
    Logger.log('Use existing sheet "'+GroupAddress.getName()+'"');
  }catch(err){
    Logger.log('create sheet "GroupAddress"');
    ss.deleteSheet(ss.getSheets()[0]);
    var GroupAddress = ss.insertSheet('GroupAddress',0); //else create it as first sheet 
  }
  GroupAddress.getDataRange().clearContent();
  ss.getSheetByName('GroupAddress').getRange(1,1,groupList.length,groupList[0].length).setValues(groupList);

  var groupNames = ss.getSheetByName("GroupAddress").getDataRange().getValues();
  var groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
  var progress;
  for(var n in groupNames){
    if(groupDone[n][0] != 'white' || (new Date().getTime()-start)/60 > 5000){ // limit to 5 seconds + 1 group
      continue;
    }
    try{
      var outputSheet = ss.insertSheet(groupNames[n][0],Number(n)+1)
      }catch(err){
        var outputSheet = ss.getSheetByName(groupNames[n][0]);
      }
    var group = GroupsApp.getGroupByEmail(groupNames[n][0]);
    //Logger.log('groupNames[n][0] = '+groupNames[n][0]);
    try{
      var users = group.getUsers();
      var output = [];
      for (var i in users){ 
        var user = AdminDirectory.Users.get(users[i].getEmail());
        output.push([Number(i)+1,user.name.fullName, user.primaryEmail,user.aliases!=null?user.aliases:'',new Date(user.creationTime),user.isAdmin,new Date(user.lastLoginTime)]);
      }
      ss.getSheetByName("GroupAddress").getRange(Number(n)+1,1).setBackground('#fff2cc');
      SpreadsheetApp.flush();
      if(output.length>0){
        output.push(['durée :',parseInt((new Date().getTime()-start)/60),'millisecondes','','','','']);
        outputSheet.getRange(1,1,output.length,output[0].length).setValues(output);
      }
    }catch(err){
      continue}
  }
  groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
  for(progress = groupDone.length-1;progress>=0;progress--){
    if(groupDone[progress][0]!='white'){break};
  }
  Logger.log(n+'='+progress+'?');
  if(n==progress){
    MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'All jobs done in GroupList','All tasks completed on '+new Date().toLocaleString());
    var trig = ScriptApp.getProjectTriggers();
    for(var t in trig){
      try{
      ScriptApp.deleteTrigger(trig[t]);
      }catch(e){}
    }
  }else{
    ScriptApp.newTrigger('listGroupMembers').timeBased().after(3000).create();// wait 3 secs and continue
  }
}

EDIT

Thanks to Göran's answer I have a version that gets all members of all groups including subgroups.

I changed (very) slightly his code to fit my requirements and I also needed to limit the number of cells in each sheet to what I needed because the total (1000*26 cells/sheet) was exceeding the 200000 cells limit (I have more than 100 sheets).

Full code below for anybody who would be interested (including menu and a few utilities):

function onOpen() {
  var menu = SpreadsheetApp.getUi().createMenu("Utilitaires INSAS")
  .addItem("reset colors", "resetColors")
  .addItem("create groupUser lists", "listGroupMembers")
  .addItem("delete all sheets", "delsheets");
  menu.addToUi();
}

function listGroupMembers() {
  var start = new Date().getTime();
  var ss = SpreadsheetApp.openById('1k-o4IVKEhW2zkkEu0gAvb92rQf2rH4UUt3OAC8I0ZoM');// set your SS ID (runs with timer trigger > needs to open by ID
  var pageToken, page;
  var count=0;
  var groupList = [];
  do {
    page = AdminDirectory.Groups.list({
      domain: 'insas.be',
      maxResults: 100,
      pageToken: pageToken
    });
    var gr = page.groups;
    if (gr) {
      for (var i = 0; i < gr.length; i++) {
        var group = gr[i];
        count++;
        //Logger.log(group);
        groupList.push([group.email,count])
      }
    } else {
      Logger.log('No group found.');
    }
    pageToken = page.nextPageToken;
  } 
  while (pageToken);
  try{
    var GroupAddress = ss.getSheetByName('GroupAddress');// if GroupAddress Sheet already exist, open it
    Logger.log('Use existing sheet "'+GroupAddress.getName()+'"');
  }catch(err){
    Logger.log('create sheet "GroupAddress"');
    ss.deleteSheet(ss.getSheets()[0]);
    var GroupAddress = ss.insertSheet('GroupAddress',0); //else create it as first sheet 
  }
  GroupAddress.getDataRange().clearContent();
  ss.getSheetByName('GroupAddress').getRange(1,1,groupList.length,groupList[0].length).setValues(groupList);

  var groupNames = ss.getSheetByName("GroupAddress").getDataRange().getValues();
  var groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
  var progress;
  for(var n in groupNames){
    if(groupDone[n][0] != 'white' || (new Date().getTime()-start)/60 > 5000){ // limit to 5 seconds + 1 group
      continue;
    }
    Logger.log('groupNames[n][0] = '+groupNames[n][0]);
    try{
      var outputSheet = ss.getSheetByName(groupNames[n][0]);
      Logger.log('sheet '+outputSheet.getName()+' created');
      }catch(err){
        var outputSheet = ss.insertSheet(groupNames[n][0],ss.getSheets().length+1);
      }
    try{
      var users = getUsersInGroup(groupNames[n][0]);
      var output = [];
      for (var i in users){ 
        var user = users[i];
//        Logger.log(user);
        output.push([Number(i)+1,user.email,user.role,user.groupName]);
      }
      ss.getSheetByName("GroupAddress").getRange(Number(n)+1,1).setBackground('#fff2cc');
      SpreadsheetApp.flush();
      if(output.length>0){
        output.push(['Ex.T = '+parseInt((new Date().getTime()-start)/60)+' mS','','','']);
        outputSheet.getRange(1,1,output.length,output[0].length).setValues(output);
        outputSheet.deleteRows(output.length+2, outputSheet.getMaxRows()-output.length-4);
        outputSheet.deleteColumns(6, outputSheet.getMaxColumns()-6);
      }
    }catch(err){
      continue}
  }
  groupDone = ss.getSheetByName("GroupAddress").getDataRange().getBackgrounds();
  for(progress = groupDone.length-1;progress>=0;progress--){
    if(groupDone[progress][0]!='white'){break};
  }
  Logger.log(n+'='+progress+'?');
  if(n==progress){
    MailApp.sendEmail(Session.getEffectiveUser().getEmail(),'All jobs done in GroupList','All tasks completed on '+new Date().toLocaleString());
    var trig = ScriptApp.getProjectTriggers();
    for(var t in trig){
      try{
      ScriptApp.deleteTrigger(trig[t]);
      }catch(e){}
    }
  }else{
    ScriptApp.newTrigger('listGroupMembers').timeBased().after(10000).create();// wait 3 secs and continue
  }
}

function resetColors(){
  var ss = SpreadsheetApp.getActive();
  ss.getSheets()[0].getDataRange().setBackground(null);
}

function getUsersInGroup(rootGroup) {
  var groupTreeUsers = [];
  var groups = [];
  groups.push(rootGroup);

  while (groups.length > 0) {
    var currentGroup = groups.pop();
    var groupName = AdminDirectory.Groups.get(currentGroup).name;
    var groupMembers = getAllMembers_(currentGroup);

    for (var i in groupMembers) {
      if (groupMembers[i].type == 'USER') {
        var groupMember = groupMembers[i];
        groupMember['groupName'] = currentGroup;
        groupTreeUsers.push(groupMember)
      }
      else if (groupMembers[i].type == 'GROUP') {
        groups.push(groupMembers[i].email)
      }
    }
  }
  Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, rootGroup);
  return groupTreeUsers;
}

function getAllMembers_(group) {
  var memberPageToken, memberPage;
  var members = [];
  do {
    memberPage = AdminDirectory.Members.list(group, {
      maxResults: 200,
      pageToken: memberPageToken
    });
    var pageMembers = memberPage.members;
    if (pageMembers) {
      for (var j =0; j < pageMembers.length; j++) {
        members.push(pageMembers[j]);
      }
    }
    memberPageToken = memberPage.nextPageToken;
  } while (memberPageToken);
  return members;
}

function delsheets(){
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var numSheets = ss.getNumSheets();// check how many sheets in the spreadsheet
  for (var pa=numSheets-1;pa>0;pa--){ 
    if(ss.getSheets()[pa].getSheetName()!='GroupAddress'){
      ss.deleteSheet(ss.getSheets()[pa]); // delete sheets begining with the last one
      Utilities.sleep(100);
    }
  }
  SpreadsheetApp.flush();
} 

回答1:

AdminDirectory MembersCollection might be what you are looking for.

Example snippet:

var members = AdminDirectory.Members.list('testgroup@example.com').members;
for (var i = 0; i < members.length; i++) {
  var member = members[i];
  switch (member.type) {
    case 'USER':
      Logger.log('%s is a user', member.email);
      break;
    case 'GROUP':
      Logger.log('%s is a group', member.email);
      break;
    default:
      Logger.log('This will never happen');
  }

Some of my customers have groups containing groups contaning groups... forming a group tree with arbitrary depth. I use this code to get all user members in such a group tree:

function walkTreeStack() {
  var rootGroup = 'testgroup@example.com';
  var groupTreeUsers = [];
  var groups = [];
  groups.push(rootGroup);

  while (groups.length > 0) {
    var currentGroup = groups.pop();
    var groupName = AdminDirectory.Groups.get(currentGroup).name;
    var groupMembers = getAllMembers_(currentGroup);

    for (var i in groupMembers) {
      if (groupMembers[i].type == 'USER') {
        groupTreeUsers.push([groupName, groupMembers[i].email])
      }
      else if (groupMembers[i].type == 'GROUP') {
        groups.push(groupMembers[i].email)
      }
    }
  }

  Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, groupName);
}

function getAllMembers_(group) {
  var memberPageToken, memberPage;
  var members = [];
  do {
    memberPage = AdminDirectory.Members.list(group, {
      maxResults: 200,
      pageToken: memberPageToken
    });
    var pageMembers = memberPage.members;
    if (pageMembers) {
      for (var j =0; j < pageMembers.length; j++) {
        members.push(pageMembers[j]);
      }
    }
    memberPageToken = memberPage.nextPageToken;
  } while (memberPageToken);
  return members;
}

It's slightly faster if it's done recursively but I suspect the script might crash due to memory constraints if there are a large number of sub groups and user members:

function getGroupTreeMembers() {
  var rootGroup = 'testgroup@example.com';
  var groupTreeUsers = [];

  walkTreeRecursive_(rootGroup, groupTreeUsers);

  var groupName = AdminDirectory.Groups.get(rootGroup).name;  
  Logger.log('There are %s user members in %s (including sub groups)', groupTreeUsers.length, groupName);
}

function walkTreeRecursive_(rootGroup, groupTreeUsers) {
  var groupName = AdminDirectory.Groups.get(rootGroup).name;
  var groupMembers = getAllMembers_(rootGroup);
  var groups = [];
  for (var i in groupMembers) {
    if (groupMembers[i].type == 'USER') {
      groupTreeUsers.push([groupName, groupMembers[i].email])
    }
    else if (groupMembers[i].type == 'GROUP') {
      groups.push(groupMembers[i].email)
    }
  }
  for (var i in groups) {
    walkTreeRecursive_(groups[i], groupTreeUsers);
  }
}

For more information please visit:

https://developers.google.com/admin-sdk/directory/v1/reference/members#resource