mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-25 16:41:35 +00:00
Compare commits
23 Commits
20250626.1
...
20250702.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f87f000be2 | ||
|
|
08f6f86d10 | ||
|
|
e70bfca92a | ||
|
|
2be5d40f44 | ||
|
|
170e188f1f | ||
|
|
60d6188769 | ||
|
|
626eb1eadc | ||
|
|
db40ada5d0 | ||
|
|
ba8c27339e | ||
|
|
822488dce5 | ||
|
|
bbd76ec23f | ||
|
|
e2a6f8badf | ||
|
|
2462aa7dcb | ||
|
|
d7a0da6e52 | ||
|
|
9922ed4994 | ||
|
|
3a0c52d8eb | ||
|
|
f3e7c46561 | ||
|
|
b42c916516 | ||
|
|
4fd7172f7a | ||
|
|
e670cf3e6a | ||
|
|
e305cc0789 | ||
|
|
d7b9d43c63 | ||
|
|
75e3ae8144 |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -126,7 +126,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20250611
|
||||
key: gam-${{ matrix.jid }}-20250701
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -457,10 +457,6 @@ jobs:
|
||||
- name: Custom wheels for Win arm64
|
||||
if: runner.os == 'Windows' && runner.arch == 'ARM64'
|
||||
run: |
|
||||
latest_lxml_whl=$(curl https://api.github.com/repos/GAM-team/lxml-wheel/releases/latest -s | jq -r .assets.[0].browser_download_url)
|
||||
echo "Downloading ${latest_lxml_whl}..."
|
||||
curl -O -L "$latest_lxml_whl"
|
||||
"$PYTHON" -m pip install lxml*.whl
|
||||
latest_crypt_whl=$(curl https://api.github.com/repos/jay0lee/cryptography/releases/latest -s | jq -r .assets.[0].browser_download_url)
|
||||
echo "Downloading ${latest_crypt_whl}..."
|
||||
curl -O -L "$latest_crypt_whl"
|
||||
|
||||
2
.github/workflows/pushwiki.yml
vendored
2
.github/workflows/pushwiki.yml
vendored
@@ -5,6 +5,8 @@ on:
|
||||
push:
|
||||
paths:
|
||||
- 'wiki/**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
pushwiki:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -279,6 +279,7 @@ If an item contains spaces, it should be surrounded by ".
|
||||
geminiedu | 1010470004 | Gemini Education |
|
||||
geminiedupremium| 1010470005 | Gemini Education Premium |
|
||||
geminient| duetai | 1010470001 | Gemini Enterprise |
|
||||
geminiultra | 1010470008 | Google AI Ultra for Business |
|
||||
gsuitebasic | gafb | gafw | basic | Google-Apps-For-Business |
|
||||
gsuitebusiness | gau | gsb | unlimited | Google-Apps-Unlimited |
|
||||
gsuitebusinessarchived | gsbau | businessarchived | 1010340002 | Google Workspace Business - Archived User |
|
||||
@@ -3119,8 +3120,15 @@ gam delete courses <CourseEntity> [archive|archived]
|
||||
gam course <CourseID> create|add alias <CourseAlias>
|
||||
gam course <CourseID> delete alias <CourseAlias>
|
||||
|
||||
gam course <CourseID> create announcement
|
||||
text <String> [scheduledtime <Time>] [state draft|published]
|
||||
gam course <CourseID> remove announcement <CourseAnnouncementID>
|
||||
gam course <CourseID> update announcement <CourseAnnouncementID>
|
||||
[text <String>] [scheduledtime <Time>] [state published]
|
||||
|
||||
gam course <CourseID> create|add topic <CourseTopic>
|
||||
gam course <CourseID> delete topic <CourseTopicID>
|
||||
gam course <CourseID> update topic <CourseTopicID> <CourseTopic>
|
||||
|
||||
gam course <CourseID> create|add teachers [makefirstteacherowner] <UserItem>
|
||||
gam course <CourseID> create|add students <UserItem>
|
||||
@@ -3132,8 +3140,15 @@ gam course <CourseID> sync students [addonly|removeonly] <UserTypeEntity>
|
||||
gam courses <CourseEntity> create|add alias <CourseAliasEntity>
|
||||
gam courses <CourseEntity> delete alias <CourseAliasEntity>
|
||||
|
||||
gam courses <CourseEntity> create announcement
|
||||
text <String> [scheduledtime <Time>] [state draft|published]
|
||||
gam courses <CourseEntity> remove announcement <CourseAnnouncementIDEntity>
|
||||
gam courses <CourseEntity> update announcement <CourseAnnouncemntIDEntity>
|
||||
[text <String>] [scheduledtime <Time>] [state published]
|
||||
|
||||
gam courses <CourseEntity> create|add topic <CourseTopicEntity>
|
||||
gam courses <CourseEntity> delete topic <CourseTopicIDEntity>
|
||||
gam courses <CourseEntity> update topic <CourseTopicIDEntity> <CourseTopic>
|
||||
|
||||
gam courses <CourseEntity> create|add teachers [makefirstteacherowner] <UserTypeEntity>
|
||||
gam courses <CourseEntity> create|add students <UserTypeEntity>
|
||||
@@ -4816,11 +4831,13 @@ gam print shareddrives [todrive <ToDriveAttribute>*]
|
||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[formatjson [quotechar <Character>]]
|
||||
gam show shareddrives
|
||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
[fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[formatjson] [noorgunits [<Boolean>]]
|
||||
|
||||
gam print shareddriveorganizers [todrive <ToDriveAttribute>*]
|
||||
@@ -4901,12 +4918,14 @@ gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
(role|roles <SharedDriveACLRoleList>)*
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||
gam <UserTypeEntity> show shareddrives
|
||||
[teamdriveadminquery|query <QueryTeamDrive>]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
(role|roles <SharedDriveACLRoleList>)*
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[guiroles [<Boolean>]] [formatjson]
|
||||
|
||||
<PermissionMatch> ::=
|
||||
|
||||
@@ -1,10 +1,44 @@
|
||||
7.11.00
|
||||
|
||||
Added commands to manage classroom/course announcements.
|
||||
|
||||
* See: https://github.com/GAM-team/GAM/wiki/Classroom-Courses#manage-course-announcements
|
||||
|
||||
Upgraded to OpenSSL 3.5.1.
|
||||
|
||||
7.10.10
|
||||
|
||||
Added choices `text` and `hyperlink` to option `showwebviewlink` in `gam [<UserTypeEntity>] print|show shareddrives`.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Displays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
7.10.09
|
||||
|
||||
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
||||
displays the web view link for the Shared Drive: `https://drive.google.com/drive/folders/<SharedDriveID>`.
|
||||
|
||||
7.10.08
|
||||
|
||||
Fixed bug in commands that modify messages where the `labelids <LabelIdList>` option
|
||||
could not be used unless one of these options was also specified: `query`, `matchlabel`, `ids`;
|
||||
it can be now be used by itself.
|
||||
|
||||
7.10.07
|
||||
|
||||
Updated `gam <UserTypeEntity> copy|move drivefile` to hanndle additional instances of
|
||||
the `cannotModifyInheritedPermission` error.
|
||||
|
||||
Added license SKU `Google AI Ultra for Business`
|
||||
* ProductID - 101047
|
||||
* SKUID - 1010470008 | geminiultra
|
||||
|
||||
7.10.06
|
||||
|
||||
"gam print devices clientstates" to include client states in device output
|
||||
Added option `clientstates` to `gam print devices` to include client states in device output.
|
||||
|
||||
7.10.05
|
||||
|
||||
Google renamed an error: cannotModifyInheritedTeamDrivePermission became cannotModifyInheritedPermission.
|
||||
Google renamed an error: `cannotModifyInheritedTeamDrivePermission` became `cannotModifyInheritedPermission`.
|
||||
GAM will now handle the new error.
|
||||
|
||||
7.10.04
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.10.06'
|
||||
__version__ = '7.11.00'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -5187,6 +5187,12 @@ def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True
|
||||
return (0, None, None)
|
||||
else:
|
||||
systemErrorExit(HTTP_ERROR_RC, eContent)
|
||||
requiredScopes = ''
|
||||
wwwAuthenticate = e.resp.get('www-authenticate', '')
|
||||
if 'insufficient_scope' in wwwAuthenticate:
|
||||
mg = re.match(r'.+scope="(.+)"', wwwAuthenticate)
|
||||
if mg:
|
||||
requiredScopes = mg.group(1).split(' ')
|
||||
if 'error' in error:
|
||||
http_status = error['error']['code']
|
||||
reason = ''
|
||||
@@ -5256,6 +5262,8 @@ def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True
|
||||
elif 'the authenticated user cannot access this service' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.SERVICE_NOT_AVAILABLE, message)
|
||||
elif status == 'PERMISSION_DENIED' or 'the caller does not have permission' in lmessage or 'permission iam.serviceaccountkeys' in lmessage:
|
||||
if requiredScopes:
|
||||
message += f', {Msg.NO_SCOPES_FOR_API.format(API.findAPIforScope(requiredScopes))}'
|
||||
error = makeErrorDict(http_status, GAPI.PERMISSION_DENIED, message)
|
||||
elif http_status == 404:
|
||||
if status == 'NOT_FOUND' or 'requested entity was not found' in lmessage or 'does not exist' in lmessage:
|
||||
@@ -8864,6 +8872,7 @@ class CSVPrintFile():
|
||||
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
|
||||
GAPI.cannotModifyInheritedPermission,
|
||||
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility,
|
||||
GAPI.invalidSharingRequest, GAPI.fileNeverWritable, GAPI.abusiveContentRestriction) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.SPREADSHEET, title,
|
||||
@@ -29957,7 +29966,7 @@ DEVICE_ORDERBY_CHOICE_MAP = {
|
||||
# [orderby <DeviceOrderByFieldName> [ascending|descending]]
|
||||
# [all|company|personal|nocompanydevices|nopersonaldevices]
|
||||
# [nodeviceusers|oneuserperrow]
|
||||
# [clientstates]
|
||||
# [clientstates]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
# [showitemcountonly]
|
||||
def doPrintCIDevices():
|
||||
@@ -46767,6 +46776,13 @@ COURSE_STATE_MAPS = {
|
||||
'published': 'PUBLISHED',
|
||||
'deleted': 'DELETED',
|
||||
},
|
||||
Cmd.OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST: {
|
||||
'draft': 'DRAFT',
|
||||
'published': 'PUBLISHED',
|
||||
},
|
||||
Cmd.OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST: {
|
||||
'published': 'PUBLISHED',
|
||||
},
|
||||
Cmd.OB_COURSE_WORK_STATE_LIST: {
|
||||
'draft': 'DRAFT',
|
||||
'published': 'PUBLISHED',
|
||||
@@ -48914,7 +48930,7 @@ def doPrintCourseParticipants():
|
||||
csvPF.SetSortTitles(COURSE_PARTICIPANTS_SORT_TITLES)
|
||||
csvPF.writeCSVfile('Course Participants')
|
||||
|
||||
def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
|
||||
def _batchAddItemsToCourse(croom, courseId, i, count, addItems, addType):
|
||||
_ADD_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
|
||||
GAPI.ALREADY_EXISTS: Msg.DUPLICATE,
|
||||
GAPI.FAILED_PRECONDITION: Msg.NOT_ALLOWED}
|
||||
@@ -48930,7 +48946,7 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
|
||||
else:
|
||||
errMsg = getHTTPError(_ADD_PART_REASON_TO_MESSAGE_MAP, http_status, reason, message)
|
||||
if (reason == GAPI.PERMISSION_DENIED) and (ri[RI_ROLE] in {Ent.STUDENT, Ent.TEACHER}) and ('CannotDirectAddUser' in errMsg):
|
||||
errMsg += f' Add external user with: gam user {ri[RI_ITEM]} create classroominvitation courses {ri[RI_ENTITY]} role {Ent.Singular(ri[RI_ROLE])}'
|
||||
errMsg += f' Add external user with: gam user {ri[RI_ITEM]} create classroominvitation courses {ri[RI_ENTITY]} addType {Ent.Singular(ri[RI_ROLE])}'
|
||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], errMsg, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
return
|
||||
waitOnFailure(1, 10, reason, message)
|
||||
@@ -48952,39 +48968,44 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
|
||||
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
|
||||
if role == Ent.STUDENT:
|
||||
if addType == Ent.STUDENT:
|
||||
service = croom.courses().students()
|
||||
attribute = 'userId'
|
||||
elif role == Ent.TEACHER:
|
||||
elif addType == Ent.TEACHER:
|
||||
service = croom.courses().teachers()
|
||||
attribute = 'userId'
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
elif addType == Ent.COURSE_ALIAS:
|
||||
service = croom.courses().aliases()
|
||||
attribute = 'alias'
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif addType == Ent.COURSE_TOPIC:
|
||||
service = croom.courses().topics()
|
||||
attribute = 'name'
|
||||
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
service = croom.courses().announcements()
|
||||
attribute = 'text'
|
||||
method = getattr(service, 'create')
|
||||
Act.Set(Act.ADD)
|
||||
jcount = len(addParticipants)
|
||||
jcount = len(addItems)
|
||||
noScopeCourseId = removeCourseIdScope(courseId)
|
||||
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, role, i, count)
|
||||
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, addType, i, count)
|
||||
Ind.Increment()
|
||||
svcargs = dict([('courseId', courseId), ('body', {attribute: None}), ('fields', '')]+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
dbatch = croom.new_batch_http_request(callback=_callbackAddItemsToCourse)
|
||||
bcount = 0
|
||||
j = 0
|
||||
for participant in addParticipants:
|
||||
for addItem in addItems:
|
||||
j += 1
|
||||
svcparms = svcargs.copy()
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
svcparms['body'][attribute] = cleanItem = normalizeEmailAddressOrUID(participant)
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
svcparms['body'][attribute] = addCourseAliasScope(participant)
|
||||
if addType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
svcparms['body'][attribute] = cleanItem = normalizeEmailAddressOrUID(addItem)
|
||||
elif addType == Ent.COURSE_ALIAS:
|
||||
svcparms['body'][attribute] = addCourseAliasScope(addItem)
|
||||
cleanItem = removeCourseAliasScope(svcparms['body'][attribute])
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
svcparms['body'][attribute] = cleanItem = participant
|
||||
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, role))
|
||||
elif addType == Ent.COURSE_TOPIC:
|
||||
svcparms['body'][attribute] = cleanItem = addItem
|
||||
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
svcparms['body'] = cleanItem = addItem
|
||||
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, addType))
|
||||
bcount += 1
|
||||
if bcount >= GC.Values[GC.BATCH_SIZE]:
|
||||
executeBatch(dbatch)
|
||||
@@ -48994,7 +49015,7 @@ def _batchAddItemsToCourse(croom, courseId, i, count, addParticipants, role):
|
||||
dbatch.execute()
|
||||
Ind.Decrement()
|
||||
|
||||
def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, role):
|
||||
def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeItems, removeType):
|
||||
_REMOVE_PART_REASON_TO_MESSAGE_MAP = {GAPI.NOT_FOUND: Msg.DOES_NOT_EXIST,
|
||||
GAPI.FORBIDDEN: Msg.FORBIDDEN,
|
||||
GAPI.PERMISSION_DENIED: Msg.PERMISSION_DENIED}
|
||||
@@ -49015,7 +49036,7 @@ def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, r
|
||||
try:
|
||||
callGAPI(service, 'delete',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED,
|
||||
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE],
|
||||
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE, GAPI.FAILED_PRECONDITION],
|
||||
retryReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE], triesLimit=0 if reason != GAPI.NOT_FOUND else 3,
|
||||
courseId=addCourseIdScope(ri[RI_ENTITY]),
|
||||
body={attribute: ri[RI_ITEM] if ri[RI_ROLE] != Ent.COURSE_ALIAS else addCourseAliasScope(ri[RI_ITEM])},
|
||||
@@ -49026,42 +49047,47 @@ def _batchRemoveItemsFromCourse(croom, courseId, i, count, removeParticipants, r
|
||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.FORBIDDEN, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
except GAPI.permissionDenied:
|
||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], Msg.PERMISSION_DENIED, int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e:
|
||||
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable, GAPI.failedPrecondition) as e:
|
||||
entityActionFailedWarning([Ent.COURSE, ri[RI_ENTITY], ri[RI_ROLE], ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
|
||||
|
||||
if role == Ent.STUDENT:
|
||||
if removeType == Ent.STUDENT:
|
||||
service = croom.courses().students()
|
||||
attribute = 'userId'
|
||||
elif role == Ent.TEACHER:
|
||||
elif removeType == Ent.TEACHER:
|
||||
service = croom.courses().teachers()
|
||||
attribute = 'userId'
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
elif removeType == Ent.COURSE_ALIAS:
|
||||
service = croom.courses().aliases()
|
||||
attribute = 'alias'
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif removeType == Ent.COURSE_TOPIC:
|
||||
service = croom.courses().topics()
|
||||
attribute = 'id'
|
||||
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
|
||||
service = croom.courses().announcements()
|
||||
attribute = 'id'
|
||||
method = getattr(service, 'delete')
|
||||
Act.Set(Act.REMOVE)
|
||||
jcount = len(removeParticipants)
|
||||
jcount = len(removeItems)
|
||||
noScopeCourseId = removeCourseIdScope(courseId)
|
||||
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, role, i, count)
|
||||
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, removeType, i, count)
|
||||
Ind.Increment()
|
||||
svcargs = dict([('courseId', courseId), ('fields', ''), (attribute, None)]+GM.Globals[GM.EXTRA_ARGS_LIST])
|
||||
dbatch = croom.new_batch_http_request(callback=_callbackRemoveItemsFromCourse)
|
||||
bcount = 0
|
||||
j = 0
|
||||
for participant in removeParticipants:
|
||||
for removeItem in removeItems:
|
||||
j += 1
|
||||
svcparms = svcargs.copy()
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
svcparms[attribute] = cleanItem = normalizeEmailAddressOrUID(participant)
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
svcparms[attribute] = addCourseAliasScope(participant)
|
||||
if removeType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
svcparms[attribute] = cleanItem = normalizeEmailAddressOrUID(removeItem)
|
||||
elif removeType == Ent.COURSE_ALIAS:
|
||||
svcparms[attribute] = addCourseAliasScope(removeItem)
|
||||
cleanItem = removeCourseAliasScope(svcparms[attribute])
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
svcparms[attribute] = cleanItem = participant
|
||||
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, role))
|
||||
elif removeType == Ent.COURSE_TOPIC:
|
||||
svcparms[attribute] = cleanItem = removeItem
|
||||
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
|
||||
svcparms[attribute] = cleanItem = removeItem
|
||||
dbatch.add(method(**svcparms), request_id=batchRequestID(noScopeCourseId, 0, 0, j, jcount, cleanItem, removeType))
|
||||
bcount += 1
|
||||
if bcount >= GC.Values[GC.BATCH_SIZE]:
|
||||
executeBatch(dbatch)
|
||||
@@ -49091,9 +49117,28 @@ def _updateCourseOwner(croom, courseId, owner, i, count):
|
||||
entityActionPerformedMessage([Ent.COURSE, removeCourseIdScope(courseId), Ent.TEACHER, owner], Msg.ALREADY_WAS_OWNER, i, count)
|
||||
Act.Set(action)
|
||||
|
||||
ADD_REMOVE_PARTICIPANT_TYPES_MAP = {
|
||||
def getCourseAnnouncement(createCmd):
|
||||
body = {}
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'text':
|
||||
body['text'] = getString(Cmd.OB_STRING, minLen=1, maxLen=30000)
|
||||
elif myarg == 'scheduledtime':
|
||||
body['scheduledTime'] = getTimeOrDeltaFromNow()
|
||||
elif myarg == 'state':
|
||||
body['state'] = getChoice(COURSE_STATE_MAPS[Cmd.OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST if createCmd else Cmd.OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST],
|
||||
mapChoice=True)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if createCmd and 'text' not in body:
|
||||
missingArgumentExit('text <String>')
|
||||
return body
|
||||
|
||||
ADD_REMOVE_UPDATE_ITEM_TYPES_MAP = {
|
||||
'alias': Ent.COURSE_ALIAS,
|
||||
'aliases': Ent.COURSE_ALIAS,
|
||||
'announcement': Ent.COURSE_ANNOUNCEMENT,
|
||||
'announcements': Ent.COURSE_ANNOUNCEMENT,
|
||||
'student': Ent.STUDENT,
|
||||
'students': Ent.STUDENT,
|
||||
'teacher': Ent.TEACHER,
|
||||
@@ -49114,6 +49159,10 @@ PARTICIPANT_EN_MAP = {
|
||||
|
||||
# gam courses <CourseEntity> create alias <CourseAliasEntity>
|
||||
# gam course <CourseID> create alias <CourseAlias>
|
||||
# gam courses <CourseEntity> create announcement
|
||||
# text <String> [scheduledtime <Time>] [state draft|published]
|
||||
# gam course <CourseID> create announcement
|
||||
# text <String> [scheduledtime <Time>] [state draft|published]
|
||||
# gam courses <CourseEntity> create topic <CourseTopicEntity>
|
||||
# gam course <CourseID> create topic <CourseTopic>
|
||||
# gam courses <CourseEntity> create students <UserTypeEntity>
|
||||
@@ -49122,35 +49171,39 @@ PARTICIPANT_EN_MAP = {
|
||||
# gam course <CourseID> create teacher [makefirstteacherowner] <EmailAddress>
|
||||
def doCourseAddItems(courseIdList, getEntityListArg):
|
||||
croom = buildGAPIObject(API.CLASSROOM)
|
||||
role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True)
|
||||
if role == Ent.TEACHER:
|
||||
addType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
|
||||
if addType == Ent.TEACHER:
|
||||
makeFirstTeacherOwner = checkArgumentPresent(['makefirstteacherowner'])
|
||||
else:
|
||||
makeFirstTeacherOwner = False
|
||||
if not getEntityListArg:
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
if addType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
addItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
elif addType == Ent.COURSE_ALIAS:
|
||||
addItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif addType == Ent.COURSE_TOPIC:
|
||||
addItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC)
|
||||
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
addItems = [getCourseAnnouncement(True)]
|
||||
courseParticipantLists = None
|
||||
else:
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
if addType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
_, addItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS,
|
||||
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]},
|
||||
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[addType]},
|
||||
isSuspended=False, isArchived=False)
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
elif addType == Ent.COURSE_ALIAS:
|
||||
addItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True)
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif addType == Ent.COURSE_TOPIC:
|
||||
addItems = getEntityList(Cmd.OB_COURSE_TOPIC_ENTITY, shlexSplit=True)
|
||||
else: # addType == Ent.COURSE_ANNOUNCEMENT:
|
||||
addItems = getCourseAnnouncement(True)
|
||||
courseParticipantLists = addItems if isinstance(addItems, dict) else None
|
||||
if courseParticipantLists is None:
|
||||
firstTeacher = None
|
||||
if makeFirstTeacherOwner and addItems:
|
||||
firstTeacher = normalizeEmailAddressOrUID(addItems[0])
|
||||
checkForExtraneousArguments()
|
||||
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, role == Ent.COURSE_TOPIC,
|
||||
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, addType in {Ent.COURSE_TOPIC, Ent.COURSE_ANNOUNCEMENT},
|
||||
addCIIdScope=courseParticipantLists is None)
|
||||
for courseId, courseInfo in coursesInfo.items():
|
||||
i += 1
|
||||
@@ -49160,43 +49213,51 @@ def doCourseAddItems(courseIdList, getEntityListArg):
|
||||
if makeFirstTeacherOwner and addItems:
|
||||
firstTeacher = normalizeEmailAddressOrUID(addItems[0])
|
||||
courseId = addCourseIdScope(courseId)
|
||||
_batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, role)
|
||||
_batchAddItemsToCourse(courseInfo['croom'], courseId, i, count, addItems, addType)
|
||||
if makeFirstTeacherOwner and firstTeacher:
|
||||
_updateCourseOwner(courseInfo['croom'], courseId, firstTeacher, i, count)
|
||||
|
||||
# gam courses <CourseEntity> remove alias <CourseAliasEntity>
|
||||
# gam course <CourseID> remove alias <CourseAlias>
|
||||
# gam courses <CourseEntity> remove announcement <CourseAnnouncementIDEntity>
|
||||
# gam course <CourseID> remove announcement <CourseAnnouncementID>
|
||||
# gam courses <CourseEntity> remove topic <CourseTopicIDEntity>
|
||||
# gam course <CourseID> remove topic <CourseTopicID>
|
||||
# gam courses <CourseEntity> remove teachers|students [owneracccess] <UserTypeEntity>
|
||||
# gam course <CourseID> remove teacher|student [owneracccess] <EmailAddress>
|
||||
def doCourseRemoveItems(courseIdList, getEntityListArg):
|
||||
croom = buildGAPIObject(API.CLASSROOM)
|
||||
role = getChoice(ADD_REMOVE_PARTICIPANT_TYPES_MAP, mapChoice=True)
|
||||
removeType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
|
||||
if not getEntityListArg:
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
if removeType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
useOwnerAccess = GC.Values[GC.USE_COURSE_OWNER_ACCESS]
|
||||
if checkArgumentPresent(OWNER_ACCESS_OPTIONS):
|
||||
useOwnerAccess = True
|
||||
removeItems = getStringReturnInList(Cmd.OB_EMAIL_ADDRESS)
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
elif removeType == Ent.COURSE_ALIAS:
|
||||
useOwnerAccess = False
|
||||
removeItems = getStringReturnInList(Cmd.OB_COURSE_ALIAS)
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif removeType == Ent.COURSE_TOPIC:
|
||||
useOwnerAccess = True
|
||||
removeItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID)
|
||||
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
|
||||
useOwnerAccess = True
|
||||
removeItems = getStringReturnInList(Cmd.OB_COURSE_ANNOUNCEMENT_ID)
|
||||
courseParticipantLists = None
|
||||
else:
|
||||
if role in {Ent.STUDENT, Ent.TEACHER}:
|
||||
if removeType in {Ent.STUDENT, Ent.TEACHER}:
|
||||
useOwnerAccess = checkArgumentPresent(OWNER_ACCESS_OPTIONS)
|
||||
_, removeItems = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS,
|
||||
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[role]})
|
||||
elif role == Ent.COURSE_ALIAS:
|
||||
typeMap={Cmd.ENTITY_COURSEPARTICIPANTS: PARTICIPANT_EN_MAP[removeType]})
|
||||
elif removeType == Ent.COURSE_ALIAS:
|
||||
useOwnerAccess = False
|
||||
removeItems = getEntityList(Cmd.OB_COURSE_ALIAS_ENTITY, shlexSplit=True)
|
||||
else: # role == Ent.COURSE_TOPIC:
|
||||
elif removeType == Ent.COURSE_TOPIC:
|
||||
useOwnerAccess = True
|
||||
removeItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True)
|
||||
else: # removeType == Ent.COURSE_ANNOUNCEMENT:
|
||||
useOwnerAccess = True
|
||||
removeItems = getEntityList(Cmd.OB_COURSE_ANNOUNCEMENT_ID_ENTITY, shlexSplit=True)
|
||||
courseParticipantLists = removeItems if isinstance(removeItems, dict) else None
|
||||
checkForExtraneousArguments()
|
||||
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess,
|
||||
@@ -49206,7 +49267,72 @@ def doCourseRemoveItems(courseIdList, getEntityListArg):
|
||||
if courseParticipantLists:
|
||||
removeItems = courseParticipantLists[courseId]
|
||||
courseId = addCourseIdScope(courseId)
|
||||
_batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, role)
|
||||
_batchRemoveItemsFromCourse(courseInfo['croom'], courseId, i, count, removeItems, removeType)
|
||||
|
||||
# gam courses <CourseEntity> update announcement <CourseAnnouncemntIDEntity>
|
||||
# [text <String>] [scheduledtime <Time>] [state published]
|
||||
# gam course <CourseID> update announcement <CourseAnnouncementID>
|
||||
# [text <String>] [scheduledtime <Time>] [state published]
|
||||
# gam courses <CourseEntity> update topic <CourseTopicIDEntity> <CourseTopic>
|
||||
# gam course <CourseID> update topic <CourseTopicID> <CourseTopic>
|
||||
def doCourseUpdateItems(courseIdList, getEntityListArg):
|
||||
croom = buildGAPIObject(API.CLASSROOM)
|
||||
updateType = getChoice(ADD_REMOVE_UPDATE_ITEM_TYPES_MAP, mapChoice=True)
|
||||
if not getEntityListArg:
|
||||
if updateType == Ent.COURSE_TOPIC:
|
||||
useOwnerAccess = True
|
||||
updateItems = getStringReturnInList(Cmd.OB_COURSE_TOPIC_ID)
|
||||
body = {'name': getString(Cmd.OB_COURSE_TOPIC)}
|
||||
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
|
||||
useOwnerAccess = True
|
||||
updateItems = getStringReturnInList(Cmd.OB_COURSE_ANNOUNCEMENT_ID)
|
||||
body = getCourseAnnouncement(False)
|
||||
courseItemLists = None
|
||||
else:
|
||||
if updateType == Ent.COURSE_TOPIC:
|
||||
useOwnerAccess = True
|
||||
updateItems = getEntityList(Cmd.OB_COURSE_TOPIC_ID_ENTITY, shlexSplit=True)
|
||||
body = {'name': getString(Cmd.OB_COURSE_TOPIC)}
|
||||
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
|
||||
useOwnerAccess = True
|
||||
updateItems = getEntityList(Cmd.OB_COURSE_ANNOUNCEMENT_ID_ENTITY, shlexSplit=True)
|
||||
body = getCourseAnnouncement(False)
|
||||
courseItemLists = updateItems if isinstance(updateItems, dict) else None
|
||||
checkForExtraneousArguments()
|
||||
i, count, coursesInfo = _getCoursesOwnerInfo(croom, courseIdList, useOwnerAccess,
|
||||
addCIIdScope=courseItemLists is None)
|
||||
for courseId, courseInfo in coursesInfo.items():
|
||||
i += 1
|
||||
if courseItemLists:
|
||||
updateItems = courseItemLists[courseId]
|
||||
courseId = addCourseIdScope(courseId)
|
||||
jcount = len(updateItems)
|
||||
noScopeCourseId = removeCourseIdScope(courseId)
|
||||
if updateType == Ent.COURSE_TOPIC:
|
||||
service = courseInfo['croom'].courses().topics()
|
||||
else: # updateType == Ent.COURSE_ANNOUNCEMENT:
|
||||
service = courseInfo['croom'].courses().announcements()
|
||||
entityPerformActionNumItems([Ent.COURSE, noScopeCourseId], jcount, updateType, i, count)
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for updateItem in updateItems:
|
||||
j += 1
|
||||
try:
|
||||
callGAPI(service, 'patch',
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED,
|
||||
GAPI.QUOTA_EXCEEDED, GAPI.SERVICE_NOT_AVAILABLE],
|
||||
retryReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE],
|
||||
courseId=addCourseIdScope(courseId), id=updateItem, updateMask=','.join(body.keys()), body=body, fields='')
|
||||
entityActionPerformed([Ent.COURSE, courseId, updateType, updateItem], j, jcount)
|
||||
except GAPI.notFound:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.DOES_NOT_EXIST, j, jcount)
|
||||
except GAPI.forbidden:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.FORBIDDEN, j, jcount)
|
||||
except GAPI.permissionDenied:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], Msg.PERMISSION_DENIED, j, jcount)
|
||||
except (GAPI.quotaExceeded, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId, updateType, updateItem], str(e), j, jcount)
|
||||
Ind.Decrement()
|
||||
|
||||
# gam courses <CourseEntity> clear teachers|students
|
||||
# gam course <CourseID> clear teacher|student
|
||||
@@ -59522,6 +59648,7 @@ def _copyPermissions(drive, user, i, count, j, jcount,
|
||||
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
|
||||
GAPI.cannotModifyInheritedPermission,
|
||||
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility, GAPI.abusiveContentRestriction) as e:
|
||||
entityActionFailedWarning(kvList, str(e), k, kcount)
|
||||
break
|
||||
@@ -60629,6 +60756,7 @@ def _updateMoveFilePermissions(drive, user, i, count,
|
||||
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
|
||||
GAPI.cannotModifyInheritedPermission,
|
||||
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility, GAPI.abusiveContentRestriction) as e:
|
||||
entityActionFailedWarning(kvList, str(e), k, kcount)
|
||||
break
|
||||
@@ -63883,6 +64011,7 @@ def createDriveFileACL(users, useDomainAdminAccess=False):
|
||||
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
|
||||
GAPI.cannotModifyInheritedPermission,
|
||||
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility,
|
||||
GAPI.invalidSharingRequest, GAPI.fileNeverWritable, GAPI.abusiveContentRestriction) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, fileName, Ent.PERMISSION_ID, permissionId], str(e), j, jcount)
|
||||
@@ -64118,6 +64247,7 @@ def createDriveFilePermissions(users, useDomainAdminAccess=False):
|
||||
GAPI.fileOrganizerNotYetEnabledForThisTeamDrive,
|
||||
GAPI.fileOrganizerOnFoldersInSharedDriveOnly,
|
||||
GAPI.fileOrganizerOnNonTeamDriveNotSupported,
|
||||
GAPI.cannotModifyInheritedPermission,
|
||||
GAPI.teamDrivesFolderSharingNotSupported, GAPI.invalidLinkVisibility,
|
||||
GAPI.invalidSharingRequest, GAPI.fileNeverWritable, GAPI.abusiveContentRestriction,
|
||||
GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
@@ -65787,7 +65917,7 @@ def _showSharedDrive(user, shareddrive, j, jcount, FJQC):
|
||||
printKeyValueList(['hidden', shareddrive['hidden']])
|
||||
if 'createdTime' in shareddrive:
|
||||
printKeyValueList(['createdTime', formatLocalTime(shareddrive['createdTime'])])
|
||||
for setting in ['backgroundImageLink', 'colorRgb', 'themeId', 'orgUnit', 'orgUnitId']:
|
||||
for setting in ['backgroundImageLink', 'colorRgb', 'themeId', 'orgUnit', 'orgUnitId', 'webViewLink']:
|
||||
if setting in shareddrive:
|
||||
printKeyValueList([setting, shareddrive[setting]])
|
||||
if 'role' in shareddrive:
|
||||
@@ -65857,11 +65987,14 @@ SHAREDDRIVE_ACL_ROLES_MAP = {
|
||||
'writer': 'writer',
|
||||
}
|
||||
|
||||
SHOWWEBVIEWLINK_CHOICES = {'text', 'hyperlink'}
|
||||
|
||||
# gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
||||
# [asadmin [shareddriveadminquery|query <QuerySharedDrive>]]
|
||||
# [matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
# (role|roles <SharedDriveACLRoleList>)*
|
||||
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||
# [showwebviewlink [text|hyperlink]]
|
||||
# [guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||
# [showitemcountonly]
|
||||
# gam <UserTypeEntity> show shareddrives
|
||||
@@ -65869,6 +66002,7 @@ SHAREDDRIVE_ACL_ROLES_MAP = {
|
||||
# [matchname <REMatchPattrn>] [orgunit|org|ou <OrgUnitPath>]
|
||||
# (role|roles <SharedDriveACLRoleLIst>)*
|
||||
# [fields <SharedDriveFieldNameList>] [noorgunits [<Boolean>]]
|
||||
# [showwebviewlink [text|hyperlink]]
|
||||
# [guiroles [<Boolean>]] [formatjson]
|
||||
# [showitemcountonly]
|
||||
def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
@@ -65877,6 +66011,11 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
td_ouid = shareddrive.get('orgUnitId')
|
||||
if td_ouid:
|
||||
shareddrive['orgUnit'] = orgUnitIdToPathMap.get(f'id:{td_ouid}', UNKNOWN)
|
||||
if showWebViewLink:
|
||||
if showWebViewLink == 'text':
|
||||
shareddrive['webViewLink'] = 'https://drive.google.com/drive/folders/'+shareddrive['id']
|
||||
else:
|
||||
shareddrive['webViewLink'] = '=HYPERLINK("https://drive.google.com/drive/folders/'+shareddrive['id']+'", "'+shareddrive['name']+'")'
|
||||
if not showFields:
|
||||
return shareddrive
|
||||
sshareddrive = {}
|
||||
@@ -65895,6 +66034,7 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
showOrgUnitPaths = True
|
||||
orgUnitIdToPathMap = None
|
||||
guiRoles = showItemCountOnly = False
|
||||
showWebViewLink = ''
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -65924,6 +66064,8 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
showOrgUnitPaths = not getBoolean()
|
||||
elif myarg == 'guiroles':
|
||||
guiRoles = getBoolean()
|
||||
elif myarg == 'showwebviewlink':
|
||||
showWebViewLink = getChoice(SHOWWEBVIEWLINK_CHOICES)
|
||||
elif myarg == 'showitemcountonly':
|
||||
showItemCountOnly = True
|
||||
showOrgUnitPaths = False
|
||||
@@ -65948,6 +66090,14 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
orgUnitIdToPathMap = getOrgUnitIdToPathMap(cd)
|
||||
if showFields:
|
||||
showFields.add('orgUnit')
|
||||
if showWebViewLink:
|
||||
if showFields:
|
||||
showFields.add('webViewLink')
|
||||
if csvPF:
|
||||
csvPF.AddTitle('webViewLink')
|
||||
if FJQC.formatJSON:
|
||||
csvPF.AddJSONTitles(['webViewLink'])
|
||||
csvPF.MoveJSONTitlesToEnd(['JSON'])
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
@@ -66016,6 +66166,8 @@ def printShowSharedDrives(users, useDomainAdminAccess=False):
|
||||
row = {'User': user, 'id': shareddrive['id'], 'name': shareddrive['name']}
|
||||
if not useDomainAdminAccess:
|
||||
row['role'] = shareddrive['role'] if not guiRoles else SHAREDDRIVE_API_GUI_ROLES_MAP[shareddrive['role']]
|
||||
if showWebViewLink:
|
||||
row['webViewLink'] = shareddrive['webViewLink']
|
||||
row['JSON'] = json.dumps(cleanJSON(shareddrive, timeObjects=SHAREDDRIVE_TIME_OBJECTS), ensure_ascii=False, sort_keys=True)
|
||||
csvPF.WriteRow(row)
|
||||
else:
|
||||
@@ -70242,14 +70394,16 @@ def _finalizeMessageSelectParameters(parameters, queryOrIdsRequired):
|
||||
for queryTimeName, queryTimeValue in iter(parameters['queryTimes'].items()):
|
||||
parameters['query'] = parameters['query'].replace(f'#{queryTimeName}#', queryTimeValue)
|
||||
_mapMessageQueryDates(parameters)
|
||||
elif queryOrIdsRequired and parameters['messageEntity'] is None:
|
||||
missingArgumentExit('query|matchlabel|ids')
|
||||
elif queryOrIdsRequired and parameters['messageEntity'] is None and not parameters['labelIds']:
|
||||
missingArgumentExit('query|matchlabel|ids|labelids')
|
||||
else:
|
||||
parameters['query'] = None
|
||||
parameters['maxItems'] = parameters['maxToProcess'] if parameters['quick'] and not parameters['labelMatchPattern'] else 0
|
||||
|
||||
# gam <UserTypeEntity> archive messages <GroupItem>
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
def archiveMessages(users):
|
||||
def _processMessageFailed(user, idsList, errMsg, j=0, jcount=0):
|
||||
@@ -70534,39 +70688,59 @@ def _processMessagesThreads(users, entityType):
|
||||
csvPF.writeCSVfile(f'{Act.ToPerform()} Messages')
|
||||
|
||||
# gam <UserTypeEntity> delete message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> modify message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
|
||||
# (addlabel <LabelName>)* (removelabel <LabelName>)*
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> spam message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> trash message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> untrash message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
def processMessages(users):
|
||||
_processMessagesThreads(users, Ent.MESSAGE)
|
||||
|
||||
# gam <UserTypeEntity> delete thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_delete <Number>])|(ids <ThreadIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> modify thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_modify <Number>])|(ids <ThreadIDEntity>)
|
||||
# (addlabel <LabelName>)* (removelabel <LabelName>)*
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> spam thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_spam <Number>])|(ids <ThreadIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> trash thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_trash <Number>])|(ids <ThreadIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
# gam <UserTypeEntity> untrash thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <ThreadIDEntity>)
|
||||
# [csv [todrive <ToDriveAttribute>*]]
|
||||
def processThreads(users):
|
||||
_processMessagesThreads(users, Ent.THREAD)
|
||||
@@ -71988,7 +72162,9 @@ def printShowMessagesThreads(users, entityType):
|
||||
csvPF.writeCSVfile('Message Counts' if not show_labels else 'Message Label Counts')
|
||||
|
||||
# gam <UserTypeEntity> print message|messages [todrive <ToDriveAttribute>*]
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <MessageIDEntity>)
|
||||
# [labelmatchpattern <REMatchPattern>] [sendermatchpattern <REMatchPattern>]
|
||||
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
|
||||
# [showlabels] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
|
||||
@@ -71998,7 +72174,9 @@ def printShowMessagesThreads(users, entityType):
|
||||
# [showattachments [noshowtextplain]]]
|
||||
# (addcsvdata <FieldName> <String>)*
|
||||
# gam <UserTypeEntity> show message|messages
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
|
||||
# [labelmatchpattern <REMatchPattern>] [sendermatchpattern <REMatchPattern>]
|
||||
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
|
||||
# [showlabels] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
|
||||
@@ -72011,7 +72189,9 @@ def printShowMessages(users):
|
||||
printShowMessagesThreads(users, Ent.MESSAGE)
|
||||
|
||||
# gam <UserTypeEntity> print thread|threads [todrive <ToDriveAttribute>*]
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
|
||||
# [labelmatchpattern <REMatchPattern>]
|
||||
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
|
||||
# [showlabels] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
|
||||
@@ -72021,7 +72201,9 @@ def printShowMessages(users):
|
||||
# [showattachments [noshowtextplain]]]
|
||||
# (addcsvdata <FieldName> <String>)*
|
||||
# gam <UserTypeEntity> show thread|threads
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
|
||||
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
|
||||
# [labelids <LabelIDList>]
|
||||
# [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
|
||||
# [labelmatchpattern <REMatchPattern>]
|
||||
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
|
||||
# [showlabels] [showbody] [showhtml] [showdate] [showsize] [showsnippet]
|
||||
@@ -77100,6 +77282,7 @@ COURSE_SUBCOMMANDS = {
|
||||
'add': (Act.ADD, doCourseAddItems),
|
||||
'clear': (Act.REMOVE, doCourseClearParticipants),
|
||||
'remove': (Act.REMOVE, doCourseRemoveItems),
|
||||
'update': (Act.UPDATE, doCourseUpdateItems),
|
||||
'sync': (Act.SYNC, doCourseSyncParticipants),
|
||||
}
|
||||
|
||||
|
||||
@@ -226,15 +226,15 @@ _INFO = {
|
||||
CHROMEMANAGEMENT_TELEMETRY: {'name': 'Chrome Management API - Telemetry', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHROMEMANAGEMENT},
|
||||
CHROMEPOLICY: {'name': 'Chrome Policy API', 'version': 'v1', 'v2discovery': True},
|
||||
CHROMEVERSIONHISTORY: {'name': 'Chrome Version History API', 'version': 'v1', 'v2discovery': True},
|
||||
CLOUDCHANNEL: {'name': 'Channel Channel API', 'version': 'v1', 'v2discovery': True},
|
||||
CLOUDIDENTITY_DEVICES: {'name': 'Cloud Identity Devices API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_GROUPS: {'name': 'Cloud Identity Groups API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_GROUPS_BETA: {'name': 'Cloud Identity Groups API', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_INBOUND_SSO: {'name': 'Cloud Identity Inbound SSO API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity OrgUnits API', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity Policy API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity User Invitations API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDCHANNEL: {'name': 'Cloud Channel API', 'version': 'v1', 'v2discovery': True},
|
||||
CLOUDIDENTITY_DEVICES: {'name': 'Cloud Identity API - Devices', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_GROUPS: {'name': 'Cloud Identity API - Groups', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_GROUPS_BETA: {'name': 'Cloud Identity API - Groups Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_INBOUND_SSO: {'name': 'Cloud Identity API - Inbound SSO Settings', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS: {'name': 'Cloud Identity API - OrgUnits', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_ORGUNITS_BETA: {'name': 'Cloud Identity API - OrgUnits Beta', 'version': 'v1beta1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_POLICY: {'name': 'Cloud Identity API - Policy', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDIDENTITY_USERINVITATIONS: {'name': 'Cloud Identity API - User Invitations', 'version': 'v1', 'v2discovery': True, 'mappedAPI': 'cloudidentity'},
|
||||
CLOUDRESOURCEMANAGER: {'name': 'Cloud Resource Manager API v3', 'version': 'v3', 'v2discovery': True},
|
||||
CONTACTS: {'name': 'Contacts API', 'version': 'v3', 'v2discovery': False},
|
||||
CONTACTDELEGATION: {'name': 'Contact Delegation API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
@@ -258,13 +258,13 @@ _INFO = {
|
||||
LICENSING: {'name': 'License Manager API', 'version': 'v1', 'v2discovery': True},
|
||||
LOOKERSTUDIO: {'name': 'Looker Studio API', 'version': 'v1', 'v2discovery': True, 'localjson': True},
|
||||
MEET: {'name': 'Meet API', 'version': 'v2', 'v2discovery': True},
|
||||
MEET_BETA: {'name': 'Meet API', 'version': 'v2beta', 'v2discovery': True, 'localjson': True, 'mappedAPI': MEET},
|
||||
MEET_BETA: {'name': 'Meet API Beta', 'version': 'v2beta', 'v2discovery': True, 'localjson': True, 'mappedAPI': MEET},
|
||||
OAUTH2: {'name': 'OAuth2 API', 'version': 'v2', 'v2discovery': False},
|
||||
ORGPOLICY: {'name': 'Organization Policy API', 'version': 'v2', 'v2discovery': True},
|
||||
PEOPLE: {'name': 'People API', 'version': 'v1', 'v2discovery': True},
|
||||
PEOPLE_DIRECTORY: {'name': 'People Directory API', 'version': 'v1', 'v2discovery': True, 'mappedAPI': PEOPLE},
|
||||
PEOPLE_OTHERCONTACTS: {'name': 'People API - Other Contacts', 'version': 'v1', 'v2discovery': True, 'mappedAPI': PEOPLE},
|
||||
PRINTERS: {'name': 'Directory API Printers', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
PRINTERS: {'name': 'Directory API - Printers', 'version': 'directory_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
PUBSUB: {'name': 'Pub / Sub API', 'version': 'v1', 'v2discovery': True},
|
||||
REPORTS: {'name': 'Reports API', 'version': 'reports_v1', 'v2discovery': True, 'mappedAPI': 'admin'},
|
||||
RESELLER: {'name': 'Reseller API', 'version': 'v1', 'v2discovery': True},
|
||||
@@ -362,29 +362,29 @@ _CLIENT_SCOPES = [
|
||||
'subscopes': READONLY,
|
||||
'offByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/apps.order'},
|
||||
{'name': 'Cloud Identity Groups API',
|
||||
{'name': 'Cloud Identity API - Groups',
|
||||
'api': CLOUDIDENTITY_GROUPS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'},
|
||||
{'name': 'Cloud Identity Groups API Beta (Enables group locking/unlocking)',
|
||||
{'name': 'Cloud Identity API - Groups Beta (Enables group locking/unlocking)',
|
||||
'api': CLOUDIDENTITY_GROUPS_BETA,
|
||||
'subscopes': [],
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.groups'},
|
||||
{'name': 'Cloud Identity - Inbound SSO Settings',
|
||||
{'name': 'Cloud Identity API - Inbound SSO Settings',
|
||||
'api': CLOUDIDENTITY_INBOUND_SSO,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.inboundsso'},
|
||||
{'name': 'Cloud Identity OrgUnits API',
|
||||
{'name': 'Cloud Identity API - OrgUnits Beta',
|
||||
'api': CLOUDIDENTITY_ORGUNITS_BETA,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.orgunits'},
|
||||
{'name': 'Cloud Identity - Policy',
|
||||
{'name': 'Cloud Identity API - Policy',
|
||||
'api': CLOUDIDENTITY_POLICY,
|
||||
'subscopes': READONLY,
|
||||
'roByDefault': True,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.policies'
|
||||
},
|
||||
{'name': 'Cloud Identity User Invitations API',
|
||||
{'name': 'Cloud Identity API - User Invitations',
|
||||
'api': CLOUDIDENTITY_USERINVITATIONS,
|
||||
'subscopes': READONLY,
|
||||
'scope': 'https://www.googleapis.com/auth/cloud-identity.userinvitations'},
|
||||
@@ -833,3 +833,27 @@ def getSvcAcctScopesList(userServiceAccountAccessOnly, svcAcctSpecialScopes):
|
||||
|
||||
def hasLocalJSON(api):
|
||||
return _INFO[api].get('localjson', False)
|
||||
|
||||
def findAPIforScope(scopesList):
|
||||
def checkScopeMatch(scope, cscope):
|
||||
if cscope['scope'] == scope:
|
||||
requiredAPIs.append(cscope['name'])
|
||||
return True
|
||||
if cscope['subscopes'] == READONLY and cscope['scope']+'.readonly' == scope:
|
||||
requiredAPIs.append(cscope['name']+' (supports readonly)')
|
||||
return True
|
||||
return False
|
||||
|
||||
requiredAPIs = []
|
||||
for scope in scopesList:
|
||||
for cscope in _CLIENT_SCOPES:
|
||||
if checkScopeMatch(scope, cscope):
|
||||
break
|
||||
else:
|
||||
for cscope in _SVCACCT_SCOPES:
|
||||
if checkScopeMatch(scope, cscope):
|
||||
break
|
||||
if not requiredAPIs:
|
||||
requiredAPIs = scopesList
|
||||
return ' or '.join(requiredAPIs)
|
||||
|
||||
|
||||
@@ -875,8 +875,11 @@ class GamCLArgs():
|
||||
OB_CONTACT_GROUP_ITEM = 'ContactGroupItem'
|
||||
OB_COURSE_ALIAS = 'CourseAlias'
|
||||
OB_COURSE_ALIAS_ENTITY = 'CourseAliasEntity'
|
||||
OB_COURSE_ANNOUNCEMENT_ID = "CourseAnnouncementID"
|
||||
OB_COURSE_ANNOUNCEMENT_ID_ENTITY = "CourseAnnouncementIDEntity"
|
||||
OB_COURSE_ANNOUNCEMENT_STATE_LIST = "CourseAnnouncementStateList"
|
||||
OB_COURSE_ANNOUNCEMENT_ADD_STATE_LIST = "CourseAnnouncementAddStateList"
|
||||
OB_COURSE_ANNOUNCEMENT_UPDATE_STATE_LIST = "CourseAnnouncementUpdateStateList"
|
||||
OB_COURSE_ENTITY = 'CourseEntity'
|
||||
OB_COURSE_ID = 'CourseID'
|
||||
OB_COURSE_MATERIAL_ID_ENTITY = 'CourseMaterialIDEntity'
|
||||
|
||||
@@ -229,6 +229,7 @@ DRIVE3_CREATE_ACL_THROW_REASONS = [BAD_REQUEST, INVALID, INVALID_SHARING_REQUEST
|
||||
FILE_ORGANIZER_NOT_YET_ENABLED_FOR_THIS_TEAMDRIVE,
|
||||
FILE_ORGANIZER_ON_FOLDERS_IN_SHARED_DRIVE_ONLY,
|
||||
FILE_ORGANIZER_ON_NON_TEAMDRIVE_NOT_SUPPORTED,
|
||||
CANNOT_MODIFY_INHERITED_PERMISSION,
|
||||
TEAMDRIVES_FOLDER_SHARING_NOT_SUPPORTED, INVALID_LINK_VISIBILITY, ABUSIVE_CONTENT_RESTRICTION]
|
||||
DRIVE3_GET_ACL_REASONS = DRIVE_USER_THROW_REASONS+[FILE_NOT_FOUND, FORBIDDEN, INTERNAL_ERROR,
|
||||
INSUFFICIENT_ADMINISTRATOR_PRIVILEGES, INSUFFICIENT_FILE_PERMISSIONS,
|
||||
|
||||
@@ -184,8 +184,8 @@ ALREADY_EXISTS_IN_TARGET_FOLDER = 'Already exists in {0}: {1}'
|
||||
ALREADY_EXISTS_USE_MERGE_ARGUMENT = 'Already exists; use the "merge" argument to merge the labels'
|
||||
API_ACCESS_DENIED = 'API access Denied'
|
||||
API_CALLS_RETRY_DATA = 'API calls retry data\n'
|
||||
API_CHECK_CLIENT_AUTHORIZATION = 'Please make sure the Client ID: {0} is authorized for the appropriate API or scopes:\n{1}\n\nRun: gam oauth create\n'
|
||||
API_CHECK_SVCACCT_AUTHORIZATION = 'Please make sure the Service Account Client ID: {0} is authorized for the appropriate API or scopes:\n{1}\n\nRun: gam user {2} update serviceaccount\n'
|
||||
API_CHECK_CLIENT_AUTHORIZATION = 'Please make sure the Client ID: {0} is authorized for the appropriate API or scopes: {1}\n\nRun: gam oauth create\n'
|
||||
API_CHECK_SVCACCT_AUTHORIZATION = 'Please make sure the Service Account Client ID: {0} is authorized for the appropriate API or scopes: {1}\n\nRun: gam user {2} update serviceaccount\n'
|
||||
API_ERROR_SETTINGS = 'API error, some settings not set'
|
||||
ARE_BOTH_REQUIRED = 'Arguments {0} and {1} are both required'
|
||||
ARE_MUTUALLY_EXCLUSIVE = 'Arguments {0} and {1} are mutually exclusive'
|
||||
@@ -425,7 +425,7 @@ NO_LABELS_TO_PROCESS = 'No Labels to process'
|
||||
NO_MESSAGES_WITH_LABEL = 'No Messages with Label'
|
||||
NO_PARENTS_TO_CONVERT_TO_SHORTCUTS = 'No parents to convert to shortcuts'
|
||||
NO_REPORT_AVAILABLE = 'No {0} report available.'
|
||||
NO_SCOPES_FOR_API = 'There are no scopes authorized for the {0}'
|
||||
NO_SCOPES_FOR_API = 'There are no scopes authorized for the API(s): {0}'
|
||||
NO_SERIAL_NUMBERS_SPECIFIED = 'No serial numbers specified'
|
||||
NO_SSO_PROFILE_MATCHES = 'No SSO profile matches display name {0}'
|
||||
NO_SSO_PROFILE_ASSIGNED = 'No SSO profile assigned to {0} {1}'
|
||||
|
||||
@@ -107,6 +107,8 @@ _SKUS = {
|
||||
'product': '101047', 'aliases': ['aisecurity'], 'displayName': 'AI Security'},
|
||||
'1010470007': {
|
||||
'product': '101047', 'aliases': ['aimeetingsandmessaging'], 'displayName': 'AI Meetings and Messaging'},
|
||||
'1010470008': {
|
||||
'product': '101047', 'aliases': ['geminiultra'], 'displayName': 'Google AI Ultra for Business'},
|
||||
'1010490001': {
|
||||
'product': '101049', 'aliases': ['eeu'], 'displayName': 'Endpoint Education Upgrade'},
|
||||
'1010500001': {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
- [Python Regular Expressions](Python-Regular-Expressions)
|
||||
- [Definitions](#definitions)
|
||||
- [Manage Projects](#manage-projects)
|
||||
- [Authorize a super admin to create projects](#authorize-a-super-admin-to-create-projects)
|
||||
- [Authorize a user to create projects](#authorize-a-user-to-create-projects)
|
||||
- [Authorize Service Account Key Uploads](#authorize-service-account-key-uploads)
|
||||
- [Authorize GAM to create projects](#authorize-gam-to-create-projects)
|
||||
- [Create a new GCP project folder](#create-a-new-gcp-project-folder)
|
||||
@@ -74,11 +74,6 @@ Verify that all scopes are available:
|
||||
* Select "ON for everyone"
|
||||
* Click "SAVE"
|
||||
|
||||
Verify that internal apps are trusted.
|
||||
* Access the admin console and go to Security -> Access and data control -> API Controls
|
||||
* Check that "Trust internal, domain-owned apps" is present in the **Settings** section
|
||||
* Click "SAVE"
|
||||
|
||||
If you run a Google Workspace Education SKU, verify that Classroom API is enabled if required.
|
||||
* Access the admin console and go to Apps -> Google Workspace - Classroom
|
||||
* Expand "Data access"
|
||||
@@ -110,12 +105,13 @@ Verify whether the super admin you'll be using is in an OU where reauthenticatio
|
||||
* Access the admin console and go to Security -> Overview
|
||||
* Scroll down and open Google Cloud session control section
|
||||
* Select the OU containing the super admin
|
||||
* If Require reauthentication is selected and Exempt Trusted apps is not checked, you'll have to do `gam oauth create` at whatever frequency is specified
|
||||
* If that sounds unappealing, check Exempt Trusted apps
|
||||
* Click "OVERRIDE"
|
||||
* If Require reauthentication is selected, you'll need either:
|
||||
* uncheck Google Cloud Storage and any other GCP APIs that you selected on `gam oauth create` (reauth is only necessary for GCP APIs)
|
||||
* enable "Exempt Trusted apps"
|
||||
* rerun `gam oauth create` at whatever frequency is specified
|
||||
|
||||
Additional steps may be required if errors are encountered.
|
||||
* [Authorize a super admin to create projects](#authorize-a-super-admin-to-create-projects)
|
||||
* [Authorize a user to create projects](#authorize-a-user-to-create-projects)
|
||||
* [Authorize Service Account Key Uploads](#authorize-service-account-key-uploads)
|
||||
* [Authorize GAM to create projects](#authorize-gam-to-create-projects)
|
||||
|
||||
@@ -169,8 +165,8 @@ For `print|show projects`, you can eliminate the password prompt and authenticat
|
||||
gam print projects admin admin@domain.com
|
||||
```
|
||||
|
||||
## Authorize a super admin to create projects
|
||||
If you try to create a project and get an error saying that the admin you specified is not authorized to create projects,
|
||||
## Authorize a user to create projects
|
||||
If you try to create a project and get an error saying that the user you specified is not authorized to create projects,
|
||||
perform these steps and then retry the create project command.
|
||||
|
||||
* Login as an existing super admin at console.cloud.google.com
|
||||
@@ -184,13 +180,12 @@ perform these steps and then retry the create project command.
|
||||
* Click in the Select a role box
|
||||
* Type project creator in the Filter box
|
||||
* Click Project Creator
|
||||
* Click + Add Another Role
|
||||
* Type orgpolicy.policyAdmin in the Filter box
|
||||
* Click Organization Policy Administrator
|
||||
* Click Save
|
||||
|
||||
## Authorize Service Account Key Uploads
|
||||
|
||||
*IMPORTANT:* Google best practice is to NOT use service account keys. Rather than overriding Google's default policy please consider [running GAM on Google Compute Engine Securely](https://github.com/GAM-team/GAM/wiki/l-Running-GAM-on-Google-Compute-Engine-(GCE)-Securely) so that service account keys are not necessary.
|
||||
|
||||
If you try to create a project and get an error saying that Constraint `constraints/iam.disableServiceAccountKeyUpload violated for service account projects/gam-project-xxxxx`,
|
||||
perform these steps and then you should be able to authorize and use your project.
|
||||
|
||||
|
||||
@@ -211,6 +211,7 @@ gam print devices [todrive <ToDriveAttribute>*]
|
||||
[orderby <DeviceOrderByFieldName> [ascending|descending]]
|
||||
[all|company|personal|nocompanydevices|nopersonaldevices]
|
||||
[nodeviceusers|oneuserperrow]
|
||||
[clientstates]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, all devices are displayed; use the query options to limit the display.
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
|
||||
## Query documentation
|
||||
* [Cloud Identity Groups API - Search Dynamic Groups](https://cloud.google.com/identity/docs/reference/rest/v1/groups#dynamicgroupquery)
|
||||
* [Member REstrictions](https://cloud.google.com/identity/docs/reference/rest/v1/SecuritySettings#MemberRestriction)
|
||||
* [Dynamic Groups Member Attributes](https://cloud.google.com/identity/docs/how-to/dynamic-groups-attributes)
|
||||
* [Member Restrictions](https://cloud.google.com/identity/docs/reference/rest/v1/SecuritySettings#MemberRestriction)
|
||||
|
||||
## Notes
|
||||
|
||||
|
||||
@@ -12,14 +12,28 @@
|
||||
|
||||
## Notes
|
||||
To use these commands you must update your client access authentication.
|
||||
You'll enter 19R to turn on the Cloud Identity Policy scope; then continue
|
||||
You'll enter 20r to turn on the Cloud Identity Policy scope; then continue
|
||||
with authentication.
|
||||
```
|
||||
gam oauth delete
|
||||
gam oauth create
|
||||
...
|
||||
[R] 19) Cloud Identity - Policy
|
||||
[R] 20) Cloud Identity - Policy (supports readonly)
|
||||
```
|
||||
You must enable access to policies in the GCP cloud console.
|
||||
|
||||
* Login at console.cloud.google.com
|
||||
* In the upper left click the three lines to the left of Google Cloud and select IAM & Admin
|
||||
* Under IAM & Admin select IAM
|
||||
* Click in the box to the right of Google Cloud
|
||||
* Click the three dots at the right and select IAM/Permissions
|
||||
* Now you should be at "Permissions for organization ..."
|
||||
* Click on Grant Access
|
||||
* Enter the GAM project creator address in Principals
|
||||
* Click in the Select a role box
|
||||
* Type orgpolicy.policyAdmin in the Filter box
|
||||
* Click Organization Policy Administrator
|
||||
* Click Save
|
||||
|
||||
## Definitions
|
||||
```
|
||||
|
||||
@@ -10,9 +10,39 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.10.10
|
||||
|
||||
Added choices `text` and `hyperlink` to option `showwebviewlink` in `gam [<UserTypeEntity>] print|show shareddrives`.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
### 7.10.09
|
||||
|
||||
Added option `showwebviewlink` to `gam [<UserTypeEntity>] print|show shareddrives` that
|
||||
displays the web view link for the Shared Drive: `https://drive.google.com/drive/folders/<SharedDriveID>`.
|
||||
|
||||
### 7.10.08
|
||||
|
||||
Fixed bug in commands that modify messages where the `labelids <LabelIdList>` option
|
||||
could not be used unless one of these options was also specified: `query`, `matchlabel`, `ids`;
|
||||
it can be now be used by itself.
|
||||
|
||||
### 7.10.07
|
||||
|
||||
Updated `gam <UserTypeEntity> copy|move drivefile` to hanndle additional instances of
|
||||
the `cannotModifyInheritedPermission` error.
|
||||
|
||||
Added license SKU `Google AI Ultra for Business`
|
||||
* ProductID - 101047
|
||||
* SKUID - 1010470008 | geminiultra
|
||||
|
||||
### 7.10.06
|
||||
|
||||
Added option `clientstates` to `gam print devices` to include client states in device output.
|
||||
|
||||
### 7.10.05
|
||||
|
||||
Google renamed an error: cannotModifyInheritedTeamDrivePermission became cannotModifyInheritedPermission.
|
||||
Google renamed an error: `cannotModifyInheritedTeamDrivePermission` became `cannotModifyInheritedPermission`.
|
||||
GAM will now handle the new error.
|
||||
|
||||
### 7.10.04
|
||||
|
||||
@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin$ gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
@@ -989,7 +989,7 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
| Gemini Education | 1010470004 | geminiedu |
|
||||
| Gemini Education Premium | 1010470005 | geminiedupremium |
|
||||
| Gemini Enterprise | 1010470001 | geminient | duetai |
|
||||
| Google AI Ultra for Business | 1010470008 | geminiultra |
|
||||
| Google Apps Message Security | Google-Apps-For-Postini | postini |
|
||||
| Google Chrome Device Management | Google-Chrome-Device-Management | cdm |
|
||||
| Google Drive Storage 16TB | Google-Drive-storage-16TB | 16tb |
|
||||
|
||||
@@ -372,26 +372,38 @@ By default, Gam displays the information as an indented list of keys and values.
|
||||
gam [<UserTypeEntity>] show shareddrives
|
||||
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
[fields <SharedDriveFieldNameList>] [formatjson]
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[formatjson]
|
||||
```
|
||||
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
||||
* `teamdriveadminquery|query <QueryTeamDrive>` - Use a query to select Shared Drives
|
||||
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
||||
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
||||
|
||||
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
```
|
||||
gam [<UserTypeEntity>] print shareddrives [todrive <ToDriveAttribute>*]
|
||||
[adminaccess|asadmin] [teamdriveadminquery|query <QueryTeamDrive>]
|
||||
[matchname <REMatchPattern>] [orgunit|org|ou <OrgUnitPath>]
|
||||
[fields <SharedDriveFieldNameList>] [formatjson [quotechar <Character>]]
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, all Shared Drives are displayed; use the following options to select a subset of Shared Drives:
|
||||
* `teamdriveadminquery|query <QueryTeamDrive>` - Use a query to select Shared Drives
|
||||
* `matchname <REMatchPattern>` - Retrieve Shared Drives with names that match a pattern.
|
||||
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
|
||||
|
||||
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format,
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
|
||||
@@ -318,23 +318,34 @@ gam <UserTypeEntity> show shareddriveinfo <SharedDriveEntity>
|
||||
gam <UserTypeEntity> show shareddrives
|
||||
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[guiroles [<Boolean>] [formatjson]
|
||||
```
|
||||
By default, Gam displays all Teams Drives accessible by the user.
|
||||
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
||||
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
||||
|
||||
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
```
|
||||
gam <UserTypeEntity> print shareddrives [todrive <ToDriveAttribute>*]
|
||||
[matchname <REMatchPattern>] (role|roles <SharedDriveACLRoleList>)*
|
||||
[fields <SharedDriveFieldNameList>] [formatjson [quotechar <Character>]]
|
||||
[fields <SharedDriveFieldNameList>]
|
||||
[showwebviewlink text|hyperlink]
|
||||
[guiroles [<Boolean>]] [formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, Gam displays all Teams Drives accessible by the user.
|
||||
* `matchname <REMatchPattern>` - Display Shared Drives with names that match a pattern.
|
||||
* `(role|roles <SharedDriveACLRoleList>)*` - Display Shared Drives where the user has one of the specified roles.
|
||||
|
||||
Use option `showwebviewlink` to display the web view link for the Shared Drive.
|
||||
* `showwebviewlink text` - Displays `https://drive.google.com/drive/folders/<SharedDriveID>`
|
||||
* `showwebviewlink hyperlink` - Dsiplays `=HYPERLINK("https://drive.google.com/drive/folders/<SharedDriveID>", "<SharedDriveName>")`
|
||||
|
||||
The Google Drive API does not list roles for Shared Drives so GAM generates a role from the capabilities:
|
||||
* `commenter - canComment: True, canEdit: False`
|
||||
* `reader - canComment: False, canEdit: False`
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
@@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.10.05
|
||||
Latest: 7.10.10
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -72,7 +72,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.10.05
|
||||
7.10.10
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -82,7 +82,7 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.10.05 - https://github.com/GAM-team/GAM
|
||||
GAM 7.10.10 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.5 64-bit final
|
||||
MacOS Sequoia 15.5 x86_64
|
||||
|
||||
Reference in New Issue
Block a user