Update gam print courses, gam update group, gam print mobile

This commit is contained in:
Ross Scroggs
2016-08-01 15:35:38 -07:00
parent 785073eb87
commit a9dc255979
3 changed files with 96 additions and 67 deletions

View File

@ -551,7 +551,7 @@ Summary of printing:
gam print cros gam print cros
Prints a header row and deviceId for all CrOS devices. Prints a header row and deviceId for all CrOS devices.
gam <CrOSTypeEntity> print cros gam <CrOSTypeEntity> print cros
Prints no header row and deviceId for specified CrOS devices Prints no header row and deviceId for specified CrOS devices.
gam print cros ... basic|full gam print cros ... basic|full
Prints a header row and selected fields for specified CrOS devices. Prints a header row and selected fields for specified CrOS devices.
@ -566,14 +566,13 @@ If timeranges is specified as a field, each pair of values for activeTimeRanges
gam update mobile <MobileItem> <MobileAttributes>+ gam update mobile <MobileItem> <MobileAttributes>+
gam delete mobile <MobileItem> gam delete mobile <MobileItem>
gam info mobile <MobileItem> gam info mobile <MobileItem>
gam print mobile [todrive] [query <QueryMobile>] gam print mobile [todrive] [query <QueryMobile>] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
[orderby <MobileOrderByFieldName> [ascending|descending]]
gam create group <EmailAddress> <GroupAttributes>* gam create group <EmailAddress> <GroupAttributes>*
gam update group <GroupItem> [admincreated <Boolean>] [email <EmailAddress>] <GroupAttributes>* gam update group <GroupItem> [admincreated <Boolean>] [email <EmailAddress>] <GroupAttributes>*
gam update group <GroupItem> add [owner|manager|member] <UserTypeEntity> gam update group <GroupItem> add [owner|manager|member] [notsuspended] <UserTypeEntity>
gam update group <GroupItem> delete|remove [owner|manager|member] <UserTypeEntity> gam update group <GroupItem> delete|remove [owner|manager|member] <UserTypeEntity>
gam update group <GroupItem> sync [owner|manager|member] <UserTypeEntity> gam update group <GroupItem> sync [owner|manager|member] [notsuspended] <UserTypeEntity>
gam update group <GroupItem> update [owner|manager|member] <UserTypeEntity> gam update group <GroupItem> update [owner|manager|member] <UserTypeEntity>
gam delete group <GroupItem> gam delete group <GroupItem>
gam info group <GroupItem> [nousers] [noaliases] [groups] gam info group <GroupItem> [nousers] [noaliases] [groups]
@ -597,7 +596,7 @@ gam create resource <ResourceID> <Name> [description <String>] [type <String>]
gam update resource <ResourceID> [name <Name>] [description <String>] [type <String>] gam update resource <ResourceID> [name <Name>] [description <String>] [type <String>]
gam delete resource <ResourceID> gam delete resource <ResourceID>
gam info resource <ResourceID> gam info resource <ResourceID>
gam print resources [todrive] [allfields] [id] [description] [email] [type] gam print resources [todrive] [allfields] [id] [name] [description] [email] [type]
gam create schema|schemas <SchemaName> <SchemaFieldDefinition>+ gam create schema|schemas <SchemaName> <SchemaFieldDefinition>+
gam update schema <SchemaName> <SchemaFieldDefinition>+ gam update schema <SchemaName> <SchemaFieldDefinition>+
@ -622,7 +621,7 @@ Summary of printing:
gam print users gam print users
Prints a header row and primaryEmail for all users. Prints a header row and primaryEmail for all users.
gam <UserTypeEntity> print users gam <UserTypeEntity> print users
Prints no header row and primaryEmail for specified users Prints no header row and primaryEmail for specified users.
gam create verify|verification <DomainName> gam create verify|verification <DomainName>
gam update verify|verification <DomainName> cname|txt|text|site|file gam update verify|verification <DomainName> cname|txt|text|site|file
@ -632,7 +631,7 @@ gam create course id|alias <CourseAlias> [teacher <UserItem>] <CourseAttributes>
gam update course <CourseID> <CourseAttributes>+ gam update course <CourseID> <CourseAttributes>+
gam delete course <CourseID> gam delete course <CourseID>
gam info course <CourseID> gam info course <CourseID>
gam print courses [todrive] [teacher] [student] [alias|aliases] gam print courses [todrive] [teacher] [student] [alias|aliases] [delimiter <String>]
gam course <CourseID> add alias <CourseAlias> gam course <CourseID> add alias <CourseAlias>
gam course <CourseID> delete alias <CourseAlias> gam course <CourseID> delete alias <CourseAlias>

View File

@ -2282,20 +2282,25 @@ def doPrintCourses():
teacherId = None teacherId = None
studentId = None studentId = None
get_aliases = False get_aliases = False
aliasesDelimiter = u' '
i = 3 i = 3
while i < len(sys.argv): while i < len(sys.argv):
if sys.argv[i].lower() == u'teacher': myarg = sys.argv[i].lower()
if myarg == u'teacher':
teacherId = sys.argv[i+1] teacherId = sys.argv[i+1]
i += 2 i += 2
elif sys.argv[i].lower() == u'student': elif myarg == u'student':
studentId = sys.argv[i+1] studentId = sys.argv[i+1]
i += 2 i += 2
elif sys.argv[i].lower() == u'todrive': elif myarg == u'todrive':
todrive = True todrive = True
i += 1 i += 1
elif sys.argv[i].lower() in [u'alias', u'aliases']: elif myarg in [u'alias', u'aliases']:
get_aliases = True get_aliases = True
i += 1 i += 1
elif myarg == u'delimiter':
aliasesDelimiter = sys.argv[i+1]
i += 2
else: else:
print u'ERROR: %s is not a valid argument for "gam print courses"' % sys.argv[i] print u'ERROR: %s is not a valid argument for "gam print courses"' % sys.argv[i]
sys.exit(2) sys.exit(2)
@ -2315,7 +2320,7 @@ def doPrintCourses():
my_aliases = [] my_aliases = []
for alias in course_aliases: for alias in course_aliases:
my_aliases.append(alias[u'alias'][2:]) my_aliases.append(alias[u'alias'][2:])
course.update(Aliases=u' '.join(my_aliases)) course.update(Aliases=aliasesDelimiter.join(my_aliases))
writeCSVfile(csvRows, titles, u'Courses', todrive) writeCSVfile(csvRows, titles, u'Courses', todrive)
def doPrintCourseParticipants(): def doPrintCourseParticipants():
@ -6928,36 +6933,39 @@ def doUpdateGroup():
group = group[4:] group = group[4:]
elif group.find(u'@') == -1: elif group.find(u'@') == -1:
group = u'%s@%s' % (group, GC_Values[GC_DOMAIN]) group = u'%s@%s' % (group, GC_Values[GC_DOMAIN])
if myarg in [u'add', u'update']: checkNotSuspended = False
role = sys.argv[5].upper() if myarg == u'add':
i = 6 role = ROLE_MEMBER
if role not in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: i = 5
role = ROLE_MEMBER if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]:
i = 5 role = sys.argv[i].upper()
i += 1
if sys.argv[i] == u'notsuspended':
checkNotSuspended = True
i += 1
if sys.argv[i].lower() in usergroup_types: if sys.argv[i].lower() in usergroup_types:
users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1]) users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1], checkNotSuspended=checkNotSuspended)
else: else:
users_email = [sys.argv[i],] users_email = [sys.argv[i],]
body = {u'role': role}
for user_email in users_email: for user_email in users_email:
if user_email != u'*' and user_email.find(u'@') == -1: if user_email != u'*' and user_email.find(u'@') == -1:
user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN])
sys.stderr.write(u' %sing %s %s...\n' % (myarg, role.lower(), user_email)) sys.stderr.write(u' adding %s %s...\n' % (role.lower(), user_email))
try: try:
if myarg == u'add': body = {u'role': role, u'email': user_email}
body = {u'role': role, u'email': user_email} callGAPI(cd.members(), u'insert', soft_errors=True, groupKey=group, body=body)
callGAPI(cd.members(), u'insert', soft_errors=True, groupKey=group, body=body)
else:
callGAPI(cd.members(), u'update', soft_errors=True, groupKey=group, memberKey=user_email, body=body)
except googleapiclient.errors.HttpError: except googleapiclient.errors.HttpError:
pass pass
elif myarg == u'sync': elif myarg == u'sync':
role = sys.argv[5].upper() role = ROLE_MEMBER
i = 6 i = 5
if role not in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]: if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]:
role = ROLE_MEMBER role = sys.argv[i].upper()
i = 5 i += 1
users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1]) if sys.argv[i] == u'notsuspended':
checkNotSuspended = True
i += 1
users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1], checkNotSuspended=checkNotSuspended)
users_email = [x.lower() for x in users_email] users_email = [x.lower() for x in users_email]
current_emails = getUsersToModify(entity_type=u'group', entity=group, member_type=role) current_emails = getUsersToModify(entity_type=u'group', entity=group, member_type=role)
current_emails = [x.lower() for x in current_emails] current_emails = [x.lower() for x in current_emails]
@ -6985,6 +6993,25 @@ def doUpdateGroup():
user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN]) user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN])
sys.stderr.write(u' removing %s\n' % user_email) sys.stderr.write(u' removing %s\n' % user_email)
callGAPI(cd.members(), u'delete', soft_errors=True, groupKey=group, memberKey=user_email) callGAPI(cd.members(), u'delete', soft_errors=True, groupKey=group, memberKey=user_email)
elif myarg == u'update':
role = ROLE_MEMBER
i = 5
if sys.argv[i].upper() in [ROLE_OWNER, ROLE_MANAGER, ROLE_MEMBER]:
role = sys.argv[i].upper()
i += 1
if sys.argv[i].lower() in usergroup_types:
users_email = getUsersToModify(entity_type=sys.argv[i], entity=sys.argv[i+1])
else:
users_email = [sys.argv[i],]
body = {u'role': role}
for user_email in users_email:
if user_email != u'*' and user_email.find(u'@') == -1:
user_email = u'%s@%s' % (user_email, GC_Values[GC_DOMAIN])
sys.stderr.write(u' updating %s %s...\n' % (role.lower(), user_email))
try:
callGAPI(cd.members(), u'update', soft_errors=True, groupKey=group, memberKey=user_email, body=body)
except googleapiclient.errors.HttpError:
pass
else: # clear else: # clear
roles = [] roles = []
i = 5 i = 5
@ -9137,7 +9164,7 @@ def doPrintMobileDevices():
todrive = False todrive = False
titles = [u'resourceId',] titles = [u'resourceId',]
csvRows = [] csvRows = []
query = orderBy = sortOrder = None query = projection = orderBy = sortOrder = None
i = 3 i = 3
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower() myarg = sys.argv[i].lower()
@ -9161,13 +9188,16 @@ def doPrintMobileDevices():
elif myarg in SORTORDER_CHOICES_MAP: elif myarg in SORTORDER_CHOICES_MAP:
sortOrder = SORTORDER_CHOICES_MAP[myarg] sortOrder = SORTORDER_CHOICES_MAP[myarg]
i += 1 i += 1
elif myarg in PROJECTION_CHOICES_MAP:
projection = PROJECTION_CHOICES_MAP[myarg]
i += 1
else: else:
print u'ERROR: %s is not a valid argument for "gam print mobile"' % sys.argv[i] print u'ERROR: %s is not a valid argument for "gam print mobile"' % sys.argv[i]
sys.exit(2) sys.exit(2)
sys.stderr.write(u'Retrieving All Mobile Devices for organization (may take some time for large accounts)...\n') sys.stderr.write(u'Retrieving All Mobile Devices for organization (may take some time for large accounts)...\n')
page_message = u'Got %%num_items%% mobile devices...\n' page_message = u'Got %%num_items%% mobile devices...\n'
all_mobile = callGAPIpages(cd.mobiledevices(), u'list', u'mobiledevices', page_message=page_message, all_mobile = callGAPIpages(cd.mobiledevices(), u'list', u'mobiledevices', page_message=page_message,
customerId=GC_Values[GC_CUSTOMER_ID], query=query, customerId=GC_Values[GC_CUSTOMER_ID], query=query, projection=projection,
orderBy=orderBy, sortOrder=sortOrder, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS]) orderBy=orderBy, sortOrder=sortOrder, maxResults=GC_Values[GC_DEVICE_MAX_RESULTS])
for mobile in all_mobile: for mobile in all_mobile:
mobiledevice = {} mobiledevice = {}
@ -9405,8 +9435,6 @@ def doPrintResourceCalendars():
fieldsTitles = {} fieldsTitles = {}
titles = [] titles = []
csvRows = [] csvRows = []
for field in RESCAL_DFLTFIELDS:
addFieldToCSVfile(field, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles)
i = 3 i = 3
while i < len(sys.argv): while i < len(sys.argv):
myarg = sys.argv[i].lower() myarg = sys.argv[i].lower()
@ -9426,6 +9454,9 @@ def doPrintResourceCalendars():
else: else:
print u'ERROR: %s is not a valid argument for "gam print resources"' % sys.argv[i] print u'ERROR: %s is not a valid argument for "gam print resources"' % sys.argv[i]
sys.exit(2) sys.exit(2)
if not fieldsList:
for field in RESCAL_DFLTFIELDS:
addFieldToCSVfile(field, RESCAL_ARGUMENT_TO_PROPERTY_MAP, fieldsList, fieldsTitles, titles)
sys.stderr.write(u"Retrieving All Resource Calendars for your account (may take some time on a large domain)\n") sys.stderr.write(u"Retrieving All Resource Calendars for your account (may take some time on a large domain)\n")
page_message = u'Got %%total_items%% resources: %%first_item%% - %%last_item%%\n' page_message = u'Got %%total_items%% resources: %%first_item%% - %%last_item%%\n'
resources = callGAPIpages(cd.resources().calendars(), u'list', u'items', resources = callGAPIpages(cd.resources().calendars(), u'list', u'items',
@ -9777,7 +9808,7 @@ def doUploadAuditKey():
audit = getAuditObject() audit = getAuditObject()
callGData(audit, u'updatePGPKey', pgpkey=auditkey) callGData(audit, u'updatePGPKey', pgpkey=auditkey)
def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=False, member_type=None): def getUsersToModify(entity_type=None, entity=None, silent=False, member_type=None, checkNotSuspended=False):
got_uids = False got_uids = False
if entity_type == None: if entity_type == None:
entity_type = sys.argv[1].lower() entity_type = sys.argv[1].lower()
@ -9802,13 +9833,10 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
sys.stderr.write(u"Getting %s of %s (may take some time for large groups)...\n" % (member_type_message, group)) sys.stderr.write(u"Getting %s of %s (may take some time for large groups)...\n" % (member_type_message, group))
page_message = u'Got %%%%total_items%%%% %s...' % member_type_message page_message = u'Got %%%%total_items%%%% %s...' % member_type_message
members = callGAPIpages(cd.members(), u'list', u'members', page_message=page_message, members = callGAPIpages(cd.members(), u'list', u'members', page_message=page_message,
groupKey=group, roles=member_type, fields=u'nextPageToken,members(email,id)') groupKey=group, roles=member_type, fields=u'nextPageToken,members(email)')
users = [] users = []
for member in members: for member in members:
if return_uids: users.append(member[u'email'])
users.append(member[u'id'])
else:
users.append(member[u'email'])
elif entity_type in [u'ou', u'org']: elif entity_type in [u'ou', u'org']:
got_uids = True got_uids = True
ou = entity ou = entity
@ -9820,14 +9848,12 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n") sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n")
page_message = u'Got %%total_items%% users...' page_message = u'Got %%total_items%% users...'
members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,id,orgUnitPath)', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended,orgUnitPath)',
query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS]) query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS])
for member in members: for member in members:
if ou.lower() != member[u'orgUnitPath'].lower(): if ou.lower() != member[u'orgUnitPath'].lower():
continue continue
if return_uids: if not checkNotSuspended or not member[u'suspended']:
users.append(member[u'id'])
else:
users.append(member[u'primaryEmail']) users.append(member[u'primaryEmail'])
if not silent: if not silent:
sys.stderr.write(u"%s users are directly in the OU.\n" % len(users)) sys.stderr.write(u"%s users are directly in the OU.\n" % len(users))
@ -9842,12 +9868,10 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n") sys.stderr.write(u"Getting all users in the Google Apps organization (may take some time on a large domain)...\n")
page_message = u'Got %%total_items%% users..' page_message = u'Got %%total_items%% users..'
members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,id)', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended)',
query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS]) query=u"orgUnitPath='%s'" % ou, maxResults=GC_Values[GC_USER_MAX_RESULTS])
for member in members: for member in members:
if return_uids: if not checkNotSuspended or not member[u'suspended']:
users.append(member[u'id'])
else:
users.append(member[u'primaryEmail']) users.append(member[u'primaryEmail'])
if not silent: if not silent:
sys.stderr.write(u"done.\r\n") sys.stderr.write(u"done.\r\n")
@ -9858,12 +9882,10 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
sys.stderr.write(u"Getting all users that match query %s (may take some time on a large domain)...\n" % entity) sys.stderr.write(u"Getting all users that match query %s (may take some time on a large domain)...\n" % entity)
page_message = u'Got %%total_items%% users...' page_message = u'Got %%total_items%% users...'
members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, members = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,id)', customer=GC_Values[GC_CUSTOMER_ID], fields=u'nextPageToken,users(primaryEmail,suspended)',
query=entity, maxResults=GC_Values[GC_USER_MAX_RESULTS]) query=entity, maxResults=GC_Values[GC_USER_MAX_RESULTS])
for member in members: for member in members:
if return_uids: if not checkNotSuspended or not member[u'suspended']:
users.append(member[u'id'])
else:
users.append(member[u'primaryEmail']) users.append(member[u'primaryEmail'])
if not silent: if not silent:
sys.stderr.write(u"done.\r\n") sys.stderr.write(u"done.\r\n")
@ -9928,13 +9950,10 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
page_message = u'Got %%total_items%% users...' page_message = u'Got %%total_items%% users...'
all_users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message, all_users = callGAPIpages(cd.users(), u'list', u'users', page_message=page_message,
customer=GC_Values[GC_CUSTOMER_ID], customer=GC_Values[GC_CUSTOMER_ID],
fields=u'nextPageToken,users(primaryEmail,suspended,id)', maxResults=GC_Values[GC_USER_MAX_RESULTS]) fields=u'nextPageToken,users(primaryEmail,suspended)', maxResults=GC_Values[GC_USER_MAX_RESULTS])
for member in all_users: for member in all_users:
if member[u'suspended'] == False: if not member[u'suspended']:
if return_uids: users.append(member[u'primaryEmail'])
users.append(member[u'id'])
else:
users.append(member[u'primaryEmail'])
if not silent: if not silent:
sys.stderr.write(u"done getting %s users.\r\n" % len(users)) sys.stderr.write(u"done getting %s users.\r\n" % len(users))
elif entity.lower() == u'cros': elif entity.lower() == u'cros':
@ -9967,12 +9986,6 @@ def getUsersToModify(entity_type=None, entity=None, silent=False, return_uids=Fa
full_users.append(user) full_users.append(user)
else: else:
full_users = users full_users = users
if return_uids and not got_uids:
new_full_users = list()
for user in full_users:
user_result = callGAPI(cd.users(), u'get', userKey=user, fields=u'id')
new_full_users.append(user_result[u'id'])
full_users = new_full_users
return full_users return full_users
def OAuthInfo(): def OAuthInfo():

View File

@ -170,6 +170,10 @@ gam <UserTypeEntity> get photo [drivedir|(targetfolder <FilePath>)]
Added noshow argument to gam get photo to suppress displaying of photo data Added noshow argument to gam get photo to suppress displaying of photo data
gam <UserTypeEntity> get photo [drivedir|(targetfolder <FilePath>)] [noshow] gam <UserTypeEntity> get photo [drivedir|(targetfolder <FilePath>)] [noshow]
Changed gam print cros to match gam print users. Previously, gam print cros produced a full listing of CrOS devices
and gam print users produced a listing of primaryEmail addresses. Now, gam print cros produces a listing of deviceIds.
To get the previous behavior, say gam print cros full. See GamCommands.txt for a summary of CrOS and User printing.
Commands that produce CSV file output have been changed to make the leftmost column(s) be the key fields. Commands that produce CSV file output have been changed to make the leftmost column(s) be the key fields.
If you have scripts that process the CSV files as flat files, expecting the columns to be in a particular If you have scripts that process the CSV files as flat files, expecting the columns to be in a particular
order, they will have to be updated. If your scripts process the CSV files by column header, no changes should be required. order, they will have to be updated. If your scripts process the CSV files by column header, no changes should be required.
@ -182,6 +186,19 @@ Added drivefilename argument to allow downloading file by name.
<FileFormatList> ::= '<FileFormat>(,<FileFormat)*' <FileFormatList> ::= '<FileFormat>(,<FileFormat)*'
gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>) [format <FileFormatList>] [targetfolder <FilePath>] [revision <Number>] gam <UserTypeEntity> get drivefile (id <DriveFileID>)|(drivefilename <DriveFileName>)|(query <QueryDriveFile>) [format <FileFormatList>] [targetfolder <FilePath>] [revision <Number>]
2016/08/01
Added delimiter <String> argument to gam print courses to allow choice of delimiter to separate aliases.
gam print courses [todrive] [teacher] [student] [alias|aliases] [delimiter <String>]
Added notsuspended argument to gam update group add/sync to prevent adding suspended users to a group
when specifying all users, org <OrgUnitPath> or query <QueryUser> for <UserTypeEntity>.
gam update group <GroupItem> add [owner|manager|member] [notsuspended] <UserTypeEntity>
gam update group <GroupItem> sync [owner|manager|member] [notsuspended] <UserTypeEntity>
Added basic|full argument to gam print mobile to allow specification of output detail desired.
gam print mobile [todrive] [query <QueryMobile>] [basic|full] [orderby <MobileOrderByFieldName> [ascending|descending]]
GAM 3.65 GAM 3.65
-fix vacation issues (Ross) -fix vacation issues (Ross)
-fix Windows path issues (Ross) -fix Windows path issues (Ross)