mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-08 00:01:38 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea5a33856 | ||
|
|
3a7f84e49d | ||
|
|
5136c0a268 | ||
|
|
8e877a938e | ||
|
|
ed695b234f | ||
|
|
83179be184 | ||
|
|
8e105091d4 | ||
|
|
428be889a2 | ||
|
|
758519b1b3 | ||
|
|
47e2ba95eb | ||
|
|
881cbbea61 | ||
|
|
3ebfb98a98 | ||
|
|
0dbd95832f | ||
|
|
78acf64f7b | ||
|
|
4c7f03da7d | ||
|
|
3ac28ef231 | ||
|
|
7100abab12 | ||
|
|
1d531d13e4 | ||
|
|
df5af20b49 | ||
|
|
cfe6376c28 | ||
|
|
e271e18feb | ||
|
|
33d579985a | ||
|
|
887628ef87 | ||
|
|
4a969e716e | ||
|
|
363c041bbb | ||
|
|
2e151eb81b | ||
|
|
b7fe258aaf | ||
|
|
7a10f6627c |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -153,7 +153,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20251218
|
||||
key: gam-${{ matrix.jid }}-20260129
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
|
||||
@@ -12,7 +12,7 @@ authors = [
|
||||
dependencies = [
|
||||
"arrow>=1.3.0",
|
||||
"chardet>=5.2.0",
|
||||
"cryptography>=44.0.2",
|
||||
"cryptography==46.0.3",
|
||||
"distro; sys_platform=='linux'",
|
||||
"filelock>=3.18.0",
|
||||
"google-api-python-client>=2.167.0",
|
||||
|
||||
@@ -6888,6 +6888,7 @@ gam <UserTypeEntity> copy drivefile <DriveFileEntity>
|
||||
[skipids <DriveFileEntity>]
|
||||
[copysubfiles [<Boolean>]] [filenamematchpattern <REMatchPattern>]
|
||||
[filemimetype [not] <MimeTypeList>] [filemimetype category <MimeTypeNameList>]
|
||||
[([start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>])|(range <Date>|<Time> <Date>|<Time>)]|
|
||||
[copysubfilesownedby
|
||||
any|me|others|
|
||||
users <EmailAddressList>|
|
||||
@@ -6973,6 +6974,7 @@ gam <UserTypeEntity> info drivefile <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[includepermissionsforview published]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
[(showlabels details|ids)|(includelabels <ClassificationLabelIDList>)]
|
||||
@@ -7456,6 +7458,7 @@ gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[includepermissionsforview published]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
[(showlabels details|ids)|(includelabels <ClassificationLabelIDList>)]
|
||||
@@ -7569,6 +7572,7 @@ gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
|
||||
[sizefield quotabytesused|size] [minimumfilesize <Integer>] [maximumfilesize <Integer>]
|
||||
[filenamematchpattern <REMatchPattern>]
|
||||
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>] [pmfilter] [oneitemperrow]
|
||||
[includepermissionsforview published]
|
||||
[excludetrashed]
|
||||
[maxfiles <Integer>] [nodataheaders <String>]
|
||||
[countsonly [summary none|only|plus] [summaryuser <String>]
|
||||
@@ -8040,7 +8044,7 @@ gam <UserTypeEntity> signature|sig
|
||||
[html [<Boolean>]] [replyto <EmailAddress>] [default] [treatasalias <Boolean>]
|
||||
[name <String>]
|
||||
[primary]
|
||||
gam <UserTypeEntity> show signature|sig [compact|format|html]
|
||||
gam <UserTypeEntity> show signature|sig [compact|format|html|template]
|
||||
[primary|default] [verifyonly]
|
||||
gam <UserTypeEntity> print signature [compact]
|
||||
[primary|default] [verifyonly] [todrive <ToDriveAttribute>*]
|
||||
|
||||
@@ -1,3 +1,64 @@
|
||||
7.32.07
|
||||
|
||||
Added option `includepermissionsforview published` to `gam <UserTypeEntity> print filelist` and
|
||||
`gam <UserTypeEntity> show fileinfo`. From the Drive API documentation:
|
||||
```
|
||||
Specifies which additional view's permissions to include in the response. Only published is supported.
|
||||
```
|
||||
|
||||
7.32.06
|
||||
|
||||
Added options to `gam <UserTypeEntity> copy drivefile ... copysubfiles` to limit copying
|
||||
to files whose `modifiedTime` meets specified requirements.
|
||||
* `start|starttime <Date>|<Time>` - If specified, `modifiedTime` must be >= the value
|
||||
* `end|endtime <Date>|<Time>` - If specified, `modifiedTime` must be <= the value
|
||||
* `range <Date>|<Time> <Date>|<Time>` - first value <= `modifiedTime` <= second value
|
||||
|
||||
7.32.05
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print messages|threads ... headers <SMTPHeaderList>` where
|
||||
headers other than those specified in `<SMTPHeaderList>` were displayed.
|
||||
|
||||
Updated `gam info users <UserTypeEntity>` to display the following data when the Licensing API
|
||||
does not return data due to quota limits. Previously, no License data was displayed and
|
||||
there was no way to know if it was omitted due to API quota limits vs the user has no license?
|
||||
```
|
||||
Licenses: (1)
|
||||
Not available/incomplete
|
||||
```
|
||||
If a user has no licenses, this will be displayed.
|
||||
```
|
||||
Licenses: (0)
|
||||
```
|
||||
|
||||
You should use `license_skus = <SKUIDList>` in `gam.cfg` to list all of the licensing SKUs
|
||||
used in your workspace. Without this list, GAM has to make 70+ API calls to get the licenses
|
||||
for a user; this can cause quota limit errors.
|
||||
|
||||
7.32.04
|
||||
|
||||
Support for student groups in Google Classroom no longer requires Developer Preview membership.
|
||||
|
||||
Upgraded to OpenSSL 3.6.1.
|
||||
|
||||
7.32.03
|
||||
|
||||
Added option `template` as an additional formating option for `gam <UserTypeEntity> show signature`
|
||||
that displays just the HTML data; this simplifies capturing the data for use as input to GAM.
|
||||
```
|
||||
$ gam redirect stdout ./SigTemplate.html user user@domain.com show signature template
|
||||
$ more SigTemplate.html
|
||||
<div dir="ltr"><div dir="ltr"><div dir="ltr">
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Email: {Email}{/RT}</span></p>
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Phone: {Phone}{/RT}</span></p>
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Mobile: {Mobile}{/RT}</span></p>
|
||||
</div><br></div>\n</div>
|
||||
|
||||
```
|
||||
|
||||
7.32.02
|
||||
|
||||
Added variable `oauth2_txt_lock_mode` to `gam.cfg`, the default is 644 and valid values are: 644, 664, 666.
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.32.02'
|
||||
__version__ = '7.32.07'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
@@ -2308,10 +2308,10 @@ def getMatchSkipFields(fieldNames):
|
||||
|
||||
def checkMatchSkipFields(row, fieldnames, matchFields, skipFields):
|
||||
for matchField, matchPattern in matchFields.items():
|
||||
if (matchField not in row) or not matchPattern.search(row[matchField]):
|
||||
if (matchField not in row) or not matchPattern.search(str(row[matchField])):
|
||||
return False
|
||||
for skipField, matchPattern in skipFields.items():
|
||||
if (skipField in row) and matchPattern.search(row[skipField]):
|
||||
if (skipField in row) and matchPattern.search(str(row[skipField])):
|
||||
return False
|
||||
if fieldnames and (GC.Values[GC.CSV_INPUT_ROW_FILTER] or GC.Values[GC.CSV_INPUT_ROW_DROP_FILTER]):
|
||||
return RowFilterMatch(row, fieldnames,
|
||||
@@ -4820,7 +4820,7 @@ def getService(api, httpObj):
|
||||
triesLimit = 3
|
||||
for n in range(1, triesLimit+1):
|
||||
try:
|
||||
if api not in {API.CHAT, API.CLASSROOM} or not GC.Values[GC.DEVELOPER_PREVIEW_API_KEY]:
|
||||
if api not in {API.CHAT} or not GC.Values[GC.DEVELOPER_PREVIEW_API_KEY]:
|
||||
discoveryServiceUrl = DISCOVERY_URIS[v2discovery]
|
||||
else:
|
||||
discoveryServiceUrl = DEVELOPER_PREVIEW_DISCOVERY_URI
|
||||
@@ -46398,6 +46398,7 @@ def getUserLicenses(lic, user, skus):
|
||||
if sku_calls:
|
||||
if try_count >= 5:
|
||||
# give up and return what we have
|
||||
licenses.append('Not available/incomplete')
|
||||
return licenses
|
||||
time.sleep(5)
|
||||
return licenses
|
||||
@@ -47050,7 +47051,7 @@ def infoUsers(entityList):
|
||||
if max(seen_group_count.values()) > 1:
|
||||
printLine(f'{Ind.Spaces()}* {Msg.USER_HAS_MULTIPLE_DIRECT_OR_INHERITED_MEMBERSHIPS_IN_GROUP}')
|
||||
Ind.Decrement()
|
||||
if licenses:
|
||||
if getLicenses:
|
||||
printEntitiesCount(Ent.LICENSE, licenses)
|
||||
Ind.Increment()
|
||||
for u_license in licenses:
|
||||
@@ -52846,7 +52847,6 @@ def doCreateCourseStudentGroups():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
**kwargs)
|
||||
kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
|
||||
if not csvPF:
|
||||
@@ -52893,7 +52893,6 @@ def doUpdateCourseStudentGroups():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
**kwargs)
|
||||
kvList[-1] = f"{studentGroup['title']}({studentGroup['id']})"
|
||||
entityActionPerformed(kvList)
|
||||
@@ -52927,7 +52926,6 @@ def doDeleteCourseStudentGroups():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
**kwargs)
|
||||
entityActionPerformed(kvList, i, count)
|
||||
except GAPI.notFound as e:
|
||||
@@ -52972,7 +52970,6 @@ def doClearCourseStudentGroups():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
courseId=courseId)
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
|
||||
@@ -52993,7 +52990,6 @@ def doClearCourseStudentGroups():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
**kwargs)
|
||||
entityActionPerformed(kvList, j, jcount)
|
||||
except GAPI.notFound as e:
|
||||
@@ -53067,7 +53063,6 @@ def doPrintCourseStudentGroups(showMembers=False):
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
courseId=courseId)
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId], str(e))
|
||||
@@ -53102,7 +53097,6 @@ def doPrintCourseStudentGroups(showMembers=False):
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
courseId=courseId, studentGroupId=studentGroupId)
|
||||
if showItemCountOnly:
|
||||
itemCount += len(students)
|
||||
@@ -53170,7 +53164,6 @@ def doProcessCourseStudentGroupMembers():
|
||||
throwReasons=[GAPI.NOT_FOUND, GAPI.SERVICE_NOT_AVAILABLE,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
courseId=courseId, studentGroupId=studentGroupId)
|
||||
except GAPI.notFound as e:
|
||||
entityActionFailedWarning([Ent.COURSE, courseId, Ent.COURSE_STUDENTGROUP, studentGroupId], str(e))
|
||||
@@ -53206,7 +53199,6 @@ def doProcessCourseStudentGroupMembers():
|
||||
GAPI.SERVICE_NOT_AVAILABLE, GAPI.NOT_IMPLEMENTED,
|
||||
GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
|
||||
previewVersion='V1_20250630_PREVIEW',
|
||||
**kwargs)
|
||||
entityActionPerformed(kvList, i, count)
|
||||
except (GAPI.alreadyExists, GAPI.notFound) as e:
|
||||
@@ -57302,6 +57294,31 @@ FILEPATH_FIELDS = ','.join(FILEPATH_FIELDS_TITLES)
|
||||
|
||||
DRIVE_TIME_OBJECTS = {'createdTime', 'viewedByMeTime', 'modifiedByMeTime', 'modifiedTime', 'restrictionTime', 'sharedWithMeTime', 'trashedTime'}
|
||||
|
||||
def _getIncludeLabels(includeLabels):
|
||||
labelIds = getEntityList(Cmd.OB_CLASSIFICATION_LABEL_ID, shlexSplit=True)
|
||||
for labelId in labelIds:
|
||||
includeLabels.add(normalizeDriveLabelID(labelId))
|
||||
|
||||
def _finalizeIncludeLabels(includeLabels):
|
||||
if includeLabels:
|
||||
return ','.join(includeLabels)
|
||||
return None
|
||||
|
||||
DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES = ['published']
|
||||
|
||||
def _getIncludePermissionsForView(includePermissionsForView):
|
||||
ipfwList = getEntityList(Cmd.OB_STRING_LIST)
|
||||
for ipfw in ipfwList:
|
||||
if ipfw in DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES:
|
||||
includePermissionsForView.add(ipfw)
|
||||
else:
|
||||
invalidChoiceExit(ipfw, DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES, True)
|
||||
|
||||
def _finalizeIncludePermissionsForView(includePermissionsForView):
|
||||
if includePermissionsForView:
|
||||
return ','.join(includePermissionsForView)
|
||||
return None
|
||||
|
||||
def _getDriveFieldSubField(field, fieldsList, parentsSubFields):
|
||||
field, subField = field.split('.', 1)
|
||||
if field in DRIVE_SUBFIELDS_CHOICE_MAP:
|
||||
@@ -57328,7 +57345,8 @@ class DriveFileFields():
|
||||
self.allFields = False
|
||||
self.OBY = OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
|
||||
self.fieldsList = []
|
||||
self.includeLabels = []
|
||||
self.includeLabels = set()
|
||||
self.includePermissionsForView = set()
|
||||
self.parentsSubFields = {'id': False, 'isRoot': False, 'rootFolderId': None}
|
||||
|
||||
def SetAllParentsSubFields(self):
|
||||
@@ -57362,9 +57380,9 @@ class DriveFileFields():
|
||||
else:
|
||||
_getDriveFieldSubField(field, self.fieldsList, self.parentsSubFields)
|
||||
elif myarg == 'includelabels':
|
||||
labelIds = getEntityList(Cmd.OB_CLASSIFICATION_LABEL_ID, shlexSplit=True)
|
||||
for labelId in labelIds:
|
||||
self.includeLabels.append(normalizeDriveLabelID(labelId))
|
||||
_getIncludeLabels(self.includeLabels)
|
||||
elif myarg == 'includepermissionsforview':
|
||||
_getIncludePermissionsForView(self.includePermissionsForView)
|
||||
elif myarg.find('.') != -1:
|
||||
_getDriveFieldSubField(myarg, self.fieldsList, self.parentsSubFields)
|
||||
elif myarg == 'orderby':
|
||||
@@ -57424,6 +57442,7 @@ def _formatFileDriveLabels(showLabels, labels, result, printMode, delimiter):
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
# [(showlabels details|ids)|(includelabels <DriveLabelIDList>)]
|
||||
# [includepermissionsforview published]
|
||||
# [showparentsidsaslist] [followshortcuts [<Boolean>]]
|
||||
# [stripcrsfromname] [formatjson]
|
||||
# gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
@@ -57433,6 +57452,7 @@ def _formatFileDriveLabels(showLabels, labels, result, printMode, delimiter):
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
# [(showlabels details|ids)|(includelabels <DriveLabelIDList>)]
|
||||
# [includepermissionsforview published]
|
||||
# [showparentsidsaslist] [followshortcuts [<Boolean>]]
|
||||
# [stripcrsfromname] [formatjson]
|
||||
def showFileInfo(users):
|
||||
@@ -57495,7 +57515,8 @@ def showFileInfo(users):
|
||||
DFF.SetAllParentsSubFields()
|
||||
skipObjects = skipObjects.union(DEFAULT_SKIP_OBJECTS)
|
||||
showNoParents = True
|
||||
includeLabels = ','.join(DFF.includeLabels)
|
||||
includeLabels = _finalizeIncludeLabels(DFF.includeLabels)
|
||||
includePermissionsForView = _finalizeIncludePermissionsForView(DFF.includePermissionsForView)
|
||||
pathFields = FILEPATH_FIELDS
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
@@ -57534,12 +57555,14 @@ def showFileInfo(users):
|
||||
try:
|
||||
result = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.INVALID],
|
||||
fileId=fileId, includeLabels=includeLabels, fields=fields, supportsAllDrives=True)
|
||||
fileId=fileId, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
|
||||
fields=fields, supportsAllDrives=True)
|
||||
if followShortcuts and result['mimeType'] == MIMETYPE_GA_SHORTCUT:
|
||||
fileId = result['shortcutDetails']['targetId']
|
||||
result = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.INVALID],
|
||||
fileId=fileId, includeLabels=includeLabels, fields=fields, supportsAllDrives=True)
|
||||
fileId=fileId, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
|
||||
fields=fields, supportsAllDrives=True)
|
||||
if stripCRsFromName:
|
||||
result['name'] = _stripControlCharsFromName(result['name'])
|
||||
driveId = result.get('driveId')
|
||||
@@ -58867,6 +58890,7 @@ SIZE_FIELD_CHOICE_MAP = {
|
||||
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
# (showlabels details|ids)|(includelabels <DriveLabelIDList>)]
|
||||
# [includepermissionsforview published]
|
||||
# [showparentsidsaslist] [showpermissionslast]
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])* [delimiter <Character>]
|
||||
# [stripcrsfromname]
|
||||
@@ -59047,7 +59071,8 @@ def printFileList(users):
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.INVALID_QUERY, GAPI.INVALID,
|
||||
GAPI.BAD_REQUEST],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS+[GAPI.UNKNOWN_ERROR],
|
||||
q=q, orderBy=DFF.orderBy, includeLabels=includeLabels, fields=pagesFields,
|
||||
q=q, orderBy=DFF.orderBy, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
|
||||
fields=pagesFields,
|
||||
pageSize=GC.Values[GC.DRIVE_MAX_RESULTS], includeItemsFromAllDrives=True, supportsAllDrives=True)
|
||||
for childEntryInfo in children:
|
||||
childFileId = childEntryInfo['id']
|
||||
@@ -59064,8 +59089,12 @@ def printFileList(users):
|
||||
_printFileInfo(drive, user, childEntryInfo.copy(), stripCRsFromName)
|
||||
if childEntryInfo['mimeType'] == MIMETYPE_GA_FOLDER and (maxdepth == -1 or depth < maxdepth):
|
||||
_printChildDriveFolderContents(drive, childEntryInfo, user, i, count, depth+1)
|
||||
except (GAPI.invalidQuery, GAPI.invalid, GAPI.badRequest):
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE, None], invalidQuery(selectSubQuery), i, count)
|
||||
except (GAPI.invalidQuery, GAPI.invalid, GAPI.badRequest) as e:
|
||||
errMsg = str(e)
|
||||
if 'Invalid field selection' in errMsg or "Only a 'published' value is supported." in errMsg:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, None], errMsg, i, count)
|
||||
else:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE, None], invalidQuery(selectSubQuery), i, count)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userDriveServiceNotEnabledWarning(user, str(e), i, count)
|
||||
|
||||
@@ -59287,7 +59316,8 @@ def printFileList(users):
|
||||
if filepath and not countsOnly:
|
||||
csvPF.AddTitles('paths')
|
||||
csvPF.SetFixPaths(True)
|
||||
includeLabels = ','.join(DFF.includeLabels)
|
||||
includeLabels = _finalizeIncludeLabels(DFF.includeLabels)
|
||||
includePermissionsForView = _finalizeIncludePermissionsForView(DFF.includePermissionsForView)
|
||||
csvPF.RemoveTitles(['capabilities'])
|
||||
if DLP.queryTimes and selectSubQuery:
|
||||
for queryTimeName, queryTimeValue in DLP.queryTimes.items():
|
||||
@@ -59360,7 +59390,8 @@ def printFileList(users):
|
||||
GAPI.BAD_REQUEST, GAPI.FILE_NOT_FOUND,
|
||||
GAPI.NOT_FOUND, GAPI.TEAMDRIVE_MEMBERSHIP_REQUIRED],
|
||||
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS+[GAPI.UNKNOWN_ERROR],
|
||||
q=DLP.fileIdEntity['query'], orderBy=DFF.orderBy, includeLabels=includeLabels,
|
||||
q=DLP.fileIdEntity['query'], orderBy=DFF.orderBy,
|
||||
includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
|
||||
fields=pagesFields, pageSize=GC.Values[GC.DRIVE_MAX_RESULTS], **btkwargs)
|
||||
for files in feed:
|
||||
if showLabels is not None:
|
||||
@@ -59391,7 +59422,7 @@ def printFileList(users):
|
||||
DLP.GetLocationFileIdsFromTree(fileTree, fileIdEntity)
|
||||
except (GAPI.invalidQuery, GAPI.invalid, GAPI.badRequest) as e:
|
||||
errMsg = str(e)
|
||||
if 'Invalid field selection' in errMsg:
|
||||
if 'Invalid field selection' in errMsg or "Only a 'published' value is supported." in errMsg:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, None], errMsg, i, count)
|
||||
break
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, None], invalidQuery(DLP.fileIdEntity['query']), i, count)
|
||||
@@ -59423,8 +59454,9 @@ def printFileList(users):
|
||||
else:
|
||||
try:
|
||||
fileEntryInfo = callGAPI(drive.files(), 'get',
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS,
|
||||
fileId=fileId, includeLabels=includeLabels, fields=fields, supportsAllDrives=True)
|
||||
throwReasons=GAPI.DRIVE_GET_THROW_REASONS+[GAPI.INVALID],
|
||||
fileId=fileId, includeLabels=includeLabels, includePermissionsForView=includePermissionsForView,
|
||||
fields=fields, supportsAllDrives=True)
|
||||
if stripCRsFromName:
|
||||
fileEntryInfo['name'] = _stripControlCharsFromName(fileEntryInfo['name'])
|
||||
if showLabels is not None:
|
||||
@@ -59435,6 +59467,9 @@ def printFileList(users):
|
||||
_formatFileDriveLabels(showLabels, labels, fileEntryInfo, True, delimiter)
|
||||
if filepath:
|
||||
fileTree[fileId] = {'info': fileEntryInfo}
|
||||
except GAPI.invalid as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, fileId], str(e), j, jcount)
|
||||
continue
|
||||
except GAPI.fileNotFound:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.DRIVE_FILE_OR_FOLDER, fileId], Msg.NOT_FOUND, j, jcount)
|
||||
continue
|
||||
@@ -61962,6 +61997,8 @@ def initCopyMoveOptions(copyCmd):
|
||||
'copySubFilesOwnedBy': {},
|
||||
'copyPermissionRoles': set(DRIVEFILE_ACL_ROLES_MAP.values()),
|
||||
'copyPermissionTypes': set(DRIVEFILE_ACL_PERMISSION_TYPES),
|
||||
'checkModifiedTime': False,
|
||||
'startEndTime': StartEndTime(),
|
||||
}
|
||||
|
||||
DUPLICATE_FILE_CHOICES = {
|
||||
@@ -62120,6 +62157,9 @@ def getCopyMoveOptions(myarg, copyMoveOptions):
|
||||
copyMoveOptions['copySubFilesOwnedBy']['value'] = set(getString(Cmd.OB_EMAIL_ADDRESS_LIST).replace(',', ' ').lower().split())
|
||||
elif copyMoveOptions['copySubFilesOwnedBy']['mode'] in {'regex', 'notregex'}:
|
||||
copyMoveOptions['copySubFilesOwnedBy']['value'] = getREPattern(re.IGNORECASE)
|
||||
elif myarg in {'start', 'starttime', 'end', 'endtime', 'range'}:
|
||||
copyMoveOptions['startEndTime'].Get(myarg)
|
||||
copyMoveOptions['checkModifiedTime'] = True
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
@@ -62676,6 +62716,7 @@ copyReturnItemMap = {
|
||||
# notusers <EmailAddressList>|
|
||||
# regex <REMatchPattern>|
|
||||
# notregex <REMatchPattern>]
|
||||
# [([start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>])|(range <Date>|<Time> <Date>|<Time>)]|
|
||||
# [copysubfolders [<Boolean>]] [foldernamematchpattern <REMatchPattern>]
|
||||
# [copysubshortcuts [<Boolean>]] [shortcutnamematchpattern <REMatchPattern>]
|
||||
# [duplicatefiles overwriteolder|overwriteall|duplicatename|uniquename|skip]
|
||||
@@ -62912,6 +62953,14 @@ def copyDriveFile(users):
|
||||
return False
|
||||
if not copyMoveOptions['mimeTypeCheck'].Check(childMimeType):
|
||||
return False
|
||||
if copyMoveOptions['checkModifiedTime']:
|
||||
childModifiedTime = child.get('modifiedTime', None)
|
||||
if not childModifiedTime:
|
||||
return False
|
||||
childModifiedTime = formatLocalTime(childModifiedTime)
|
||||
if ((copyMoveOptions['startEndTime'].startTime is not None and childModifiedTime < copyMoveOptions['startEndTime'].startTime) or
|
||||
(copyMoveOptions['startEndTime'].endTime is not None and childModifiedTime > copyMoveOptions['startEndTime'].endTime)):
|
||||
return False
|
||||
nameMatchPattern = copyMoveOptions['fileNameMatchPattern']
|
||||
return not nameMatchPattern or nameMatchPattern.match(childName)
|
||||
|
||||
@@ -67404,8 +67453,6 @@ def infoDriveFileACLs(users, useDomainAdminAccess=False):
|
||||
def doInfoDriveFileACLs():
|
||||
infoDriveFileACLs([_getAdminEmail()], True)
|
||||
|
||||
DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES = ['published']
|
||||
|
||||
def getDriveFilePermissionsFields(myarg, fieldsList):
|
||||
if myarg in DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP:
|
||||
fieldsList.append(DRIVE_PERMISSIONS_SUBFIELDS_CHOICE_MAP[myarg])
|
||||
@@ -67460,7 +67507,7 @@ def printShowDriveFileACLs(users, useDomainAdminAccess=False):
|
||||
addTitle = None
|
||||
roles = set()
|
||||
oneItemPerRow = pmselect = showTitles = False
|
||||
includePermissionsForView = None
|
||||
includePermissionsForView = set()
|
||||
fieldsList = []
|
||||
OBY = OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
|
||||
PM = PermissionMatch()
|
||||
@@ -67495,13 +67542,14 @@ def printShowDriveFileACLs(users, useDomainAdminAccess=False):
|
||||
elif PM.ProcessArgument(myarg):
|
||||
pass
|
||||
elif myarg == 'includepermissionsforview':
|
||||
includePermissionsForView = getChoice(DRIVEFILE_PERMISSIONS_FOR_VIEW_CHOICES)
|
||||
_getIncludePermissionsForView(includePermissionsForView)
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
_checkFileIdEntityDomainAccess(fileIdEntity, useDomainAdminAccess)
|
||||
if fieldsList:
|
||||
if roles:
|
||||
fieldsList.append('role')
|
||||
includePermissionsForView = _finalizeIncludePermissionsForView(includePermissionsForView)
|
||||
fields = getItemFieldsFromFieldsList('permissions', fieldsList, True)
|
||||
printKeys, timeObjects = _getDriveFileACLPrintKeysTimeObjects()
|
||||
i, count, users = getEntityArgument(users)
|
||||
@@ -74827,7 +74875,10 @@ def printShowMessagesThreads(users, entityType):
|
||||
csvPF.SetTitles(sortTitles)
|
||||
else:
|
||||
sortTitles = ['User', 'threadId', 'id']
|
||||
sortTitles.extend(defaultHeaders)
|
||||
if show_all_headers:
|
||||
sortTitles.extend(defaultHeaders)
|
||||
elif headersToShow:
|
||||
sortTitles.extend([SMTP_HEADERS_MAP.get(header, header) for header in headersToShow])
|
||||
if show_size:
|
||||
sortTitles.append('SizeEstimate')
|
||||
if show_labels:
|
||||
@@ -76524,10 +76575,14 @@ def printShowLanguage(users):
|
||||
SIG_REPLY_HTML = 0
|
||||
SIG_REPLY_COMPACT = 1
|
||||
SIG_REPLY_FORMAT = 2
|
||||
SIG_REPLY_TEMPLATE = 3 # Does not go in MAP
|
||||
SIG_REPLY_OPTIONS_MAP = {'html': SIG_REPLY_HTML, 'compact': SIG_REPLY_COMPACT, 'format': SIG_REPLY_FORMAT}
|
||||
SMTPMSA_DISPLAY_FIELDS = ['host', 'port', 'securityMode']
|
||||
|
||||
def _showSendAs(result, j, jcount, sigReplyFormat, verifyOnly=False):
|
||||
if sigReplyFormat == SIG_REPLY_TEMPLATE:
|
||||
writeStdout(f"{escapeCRsNLs(result.get('signature', 'None'))}\n")
|
||||
return
|
||||
if result['displayName']:
|
||||
printEntity([Ent.SENDAS_ADDRESS, f'{result["displayName"]} <{result["sendAsEmail"]}>'], j, jcount)
|
||||
else:
|
||||
@@ -76792,7 +76847,7 @@ def printShowSendAs(users):
|
||||
|
||||
# gam <UserTypeEntity> print signature [compact]
|
||||
# [primary] [verifyonly] [todrive <ToDriveAttribute>*]
|
||||
# gam <UserTypeEntity> show signature|sig [compact|format|html]
|
||||
# gam <UserTypeEntity> show signature|sig [compact|format|html|template]
|
||||
# [primary] [verifyonly]
|
||||
def printShowSignature(users):
|
||||
csvPF = CSVPrintFile(['User', 'displayName', 'sendAsEmail', 'replyToAddress',
|
||||
@@ -76813,6 +76868,8 @@ def printShowSignature(users):
|
||||
verifyOnly = True
|
||||
elif (not csvPF and myarg in SIG_REPLY_OPTIONS_MAP) or (csvPF and myarg == 'compact'):
|
||||
sigReplyFormat = SIG_REPLY_OPTIONS_MAP[myarg]
|
||||
elif not csvPF and myarg == 'template':
|
||||
sigReplyFormat = SIG_REPLY_TEMPLATE
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
i, count, users = getEntityArgument(users)
|
||||
|
||||
@@ -12,33 +12,8 @@
|
||||
- [Display student group membership counts](#display-student-group-membership-counts)
|
||||
|
||||
## Notes
|
||||
These commands wera added in version 7.20.00.
|
||||
|
||||
To use these commands your project must be enrolled the Developer Preview program.
|
||||
* https://developers.google.com/workspace/preview
|
||||
|
||||
You will need your GAM project number.
|
||||
* Login as an existing super admin 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 Manage Resources
|
||||
* Click the three dots at the end of the line for your GAM project
|
||||
* Click Settings
|
||||
* You will see the Project number; save it
|
||||
|
||||
You will need an API key
|
||||
* In the upper left click the three lines to the left of Google Cloud and select APIs & Services
|
||||
* Under APIs & Services select Credentials
|
||||
* If you already have an API key, click Show key at the end of the line and save the value
|
||||
* If you don't have an API key, click +Credentials and select API key
|
||||
* Save the displayed API key
|
||||
* Click close
|
||||
|
||||
Issue the following GAM command:
|
||||
`gam config developer_preview_api_key <API Key Value> save`
|
||||
|
||||
Once you get an email from Google saying that your project has been registered you can use these commands.
|
||||
These commands were added in version 7.20.00 and required enrollment in the Developer Preview program.
|
||||
As of 7.32.04 Developer Preview enrollment is no longer required.
|
||||
|
||||
## API documentation
|
||||
* [Google Classroom API](https://developers.google.com/classroom/reference/rest)
|
||||
@@ -122,7 +97,7 @@ gam clear course-studentgroups
|
||||
(course|class <CourseEntity>)*|([teacher <UserItem>] [student <UserItem>] [states <CourseStateList>])
|
||||
```
|
||||
|
||||
When creating student groups, the API does not check for duplicate titles; you can have multiple student grpups
|
||||
When creating student groups, the API does not check for duplicate titles; you can have multiple student groups
|
||||
with the same title; they will have unique `<StudentGroupID>s`.
|
||||
|
||||
The `update|delete course-studentgroups` commands manage a specific course.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
- [Display user group member options](#display-user-group-member-options)
|
||||
- [Display group membership in CSV format](#display-group-membership-in-csv-format)
|
||||
- [Display group membership in hierarchical format](#display-group-membership-in-hierarchical-format)
|
||||
- [Display external users in groups with allowExternalMembers=False](#Display-external-users-in-groups-with-allowExternalMembers-False)
|
||||
- [Manage external users in groups with allowExternalMembers False](#manage-external-users-in-groups-with-allowexternalmembers-false)
|
||||
## API documentation
|
||||
* [Cloud Identity Groups Overview](https://cloud.google.com/identity/docs/groups)
|
||||
* [Cloud Identity Groups API - Groups](https://cloud.google.com/identity/docs/reference/rest/v1/groups)
|
||||
@@ -534,6 +534,40 @@ To show the structure of all groups you can do the following; it will be time co
|
||||
gam redirect stdout ./groups.txt show cigroup-members types group
|
||||
```
|
||||
|
||||
## Display external users in groups with allowExternalMembers=False
|
||||
When printing group membership, the option `verifyallowexternal` causes
|
||||
GAM to only display external users in groups with `allowExternalMembers=False'.
|
||||
## Manage external users in groups with allowExternalMembers False
|
||||
|
||||
* See: https://support.google.com/a/answer/16778447
|
||||
|
||||
Get external members of groups with allowExternalMembers = False
|
||||
```
|
||||
gam redirect csv ./ExternalMembersGroupsWithAEMFalse.csv print cigroup-members verifyallowexternal
|
||||
```
|
||||
|
||||
Update selected groups to allowExternalMembers = True
|
||||
* Add a column labelled `update` to ExternalMembersGroupsWithAEMFalse.csv
|
||||
* For groups to be updated, add an x to the `update` column for any member of the group to be updated
|
||||
```
|
||||
gam redirect stdout ./UpdateGroupsWithAEMFalseToAEMTrue.txt redirect stderr stdout update group csvkmd ExternalMembersGroupsWithAEMFalse.csv keyfield group matchfield update x allowexternalmembers true
|
||||
```
|
||||
|
||||
Update all groups to allowExternalMembers = True
|
||||
**Caution, be sure that this is what you want**
|
||||
```
|
||||
gam redirect stdout ./UpdateGroupsWithAEMFalseToEMTrue.txt redirect stderr stdout update group csvkmd ExternalMembersGroupsWithAEMFalse.csv keyfield group allowexternalmembers true
|
||||
```
|
||||
|
||||
Delete selected external members from groups with allowExternalMembers = False
|
||||
* Add a column labelled `delete` to ExternalMembersGroupsWithAEMFalse.csv
|
||||
* For exernal members to be deleted, add an x to the `delete` column
|
||||
* The `preview` option let's you verify what external members are to be deleted, remove it to do the deletions
|
||||
```
|
||||
gam redirect csv ./DeletedMembersFromGroupsWithAEMFalse.csv redirect stdout ./DeleteMembersFromGroupsWithAEMFalse.txt redirect stderr stdout update group csvkmd ExternalMembersGroupsWithAEMFalse.csv keyfield group matchfield delete x datafield email delete preview actioncsv csvdata email
|
||||
```
|
||||
|
||||
Delete all external members from groups with allowExternalMembers = False
|
||||
**Caution, be sure that this is what you want**
|
||||
* The `preview` option let's you verify what external members are to be deleted, remove it to do the deletions
|
||||
```
|
||||
gam redirect csv ./DeletedMembersFromGroupsWithAEMFalse.csv redirect stdout ./DeleteMembersFromGroupsWithAEMFalse.txt redirect stderr stdout update group csvkmd ExternalMembersGroupsWithAEMFalse.csv keyfield group datafield email delete preview actioncsv csvdata email
|
||||
```
|
||||
|
||||
|
||||
@@ -10,6 +10,64 @@ 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.32.06
|
||||
|
||||
Added options to `gam <UserTypeEntity> copy drivefile ... copysubfiles` to limit copying
|
||||
to files whose `modifiedTime` meets specified requirements.
|
||||
* `start|starttime <Date>|<Time>` - If specified, `modifiedTime` must be >= the value
|
||||
* `end|endtime <Date>|<Time>` - If specified, `modifiedTime` must be <= the value
|
||||
* `range <Date>|<Time> <Date>|<Time>` - first value <= `modifiedTime` <= second value
|
||||
|
||||
### 7.32.05
|
||||
|
||||
Fixed bug in `gam <UserTypeEntity> print messages|threads ... headers <SMTPHeaderList>` where
|
||||
headers other than those specified in `<SMTPHeaderList>` were displayed.
|
||||
|
||||
Updated `gam info users <UserTypeEntity>` to display the following data when the Licensing API
|
||||
does not return data due to quota limits. Previously, no License data was displayed and
|
||||
there was no way to know if it was omitted due to API quota limits vs the user has no license?
|
||||
```
|
||||
Licenses: (1)
|
||||
Not available/incomplete
|
||||
```
|
||||
If a user has no licenses, this will be displayed.
|
||||
```
|
||||
Licenses: (0)
|
||||
```
|
||||
|
||||
You should use `license_skus = <SKUIDList>` in `gam.cfg` to list all of the licensing SKUs
|
||||
used in your workspace. Without this list, GAM has to make 70+ API calls to get the licenses
|
||||
for a user; this can cause quota limit errors.
|
||||
|
||||
### 7.32.04
|
||||
|
||||
Support for student groups in Google Classroom no longer requires Developer Preview membership.
|
||||
|
||||
Upgraded to OpenSSL 3.6.1.
|
||||
|
||||
### 7.32.03
|
||||
|
||||
Added option `template` as an additional formating option for `gam <UserTypeEntity> show signature`
|
||||
that displays just the HTML data; this simplifies capturing the data for use as input to GAM.
|
||||
```
|
||||
$ gam redirect stdout ./SigTemplate.html user user@domain.com show signature template
|
||||
$ more SigTemplate.html
|
||||
<div dir="ltr"><div dir="ltr"><div dir="ltr">
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Email: {Email}{/RT}</span></p>
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Phone: {Phone}{/RT}</span></p>
|
||||
<p style="margin:0px;font-size-adjust:none;font-stretch:normal;font-size:12px;line-height:normal;font-family:Monaco;color:rgb(0,0,0)">
|
||||
<span style="background-color:rgb(82,208,206)">{RT}Mobile: {Mobile}{/RT}</span></p>
|
||||
</div><br></div>\n</div>
|
||||
```
|
||||
|
||||
### 7.32.02
|
||||
|
||||
Added variable `oauth2_txt_lock_mode` to `gam.cfg`, the default is 644 and valid values are: 644, 664, 666.
|
||||
This value is used to set the file permissions on the `oauth2.txt.lock` file. In very special cases where
|
||||
daemon processes, e.g. Apache/httpd, are running GAM, the value 666 may have to be used.
|
||||
|
||||
### 7.32.01
|
||||
|
||||
Added option `(addcsvdata <FieldName> <String>)*` to `gam <CrOSTypeEntity> issuecommand command <CrOSCommand> csv`
|
||||
|
||||
@@ -252,7 +252,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.32.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
macOS Tahoe 26.2 x86_64
|
||||
@@ -990,7 +990,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.32.01 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
Windows 11 10.0.26200 AMD64
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Users - Calendars
|
||||
Users - Calendars
|
||||
- [API documentation](#api-documentation)
|
||||
- [Definitions](#definitions)
|
||||
- [Calendar colors](#calendar-colors)
|
||||
@@ -74,8 +74,8 @@
|
||||
[coursestates <CourseStateList>])|
|
||||
(resource <ResourceID>)|
|
||||
(resources <ResourceIDList>)|
|
||||
((calendars <CalendarList>) | <FileSelector> | <CSVFileSelector> |
|
||||
<CSVkmdSelector> | <CSVDataSelector>)
|
||||
(calendars <CalendarList> | <FileSelector> | <CSVFileSelector> |
|
||||
<CSVkmdSelector> | <CSVDataSelector>)
|
||||
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Items
|
||||
|
||||
<UserCalendarEntity> ::=
|
||||
@@ -90,8 +90,8 @@
|
||||
[coursestates <CourseStateList>])|
|
||||
(resource <ResourceID>)|
|
||||
(resources <ResourceIDList>)|
|
||||
((calendars <CalendarList>) | <FileSelector> | <CSVFileSelector> |
|
||||
<CSVkmdSelector> | <CSVDataSelector>)|
|
||||
(calendars <CalendarList> | <FileSelector> | <CSVFileSelector> |
|
||||
<CSVkmdSelector> | <CSVDataSelector>)|
|
||||
<CalendarSelectProperty>*
|
||||
See: https://github.com/GAM-team/GAM/wiki/Collections-of-Items
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ gam <UserTypeEntity> copy drivefile <DriveFileEntity>
|
||||
notusers <EmailAddressList>|
|
||||
regex <REMatchPattern>|
|
||||
notregex <REMatchPattern>]
|
||||
[([start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>])|(range <Date>|<Time> <Date>|<Time>)]|
|
||||
[copysubfolders [<Boolean>]] [foldernamematchpattern <REMatchPattern>]
|
||||
[copysubshortcuts [<Boolean>]] [shortcutnamematchpattern <REMatchPattern>]
|
||||
[duplicatefiles overwriteolder|overwriteall|duplicatename|uniquename|skip]
|
||||
@@ -182,16 +183,18 @@ You can specify whether sub files, folders and shortcuts are copied. If sub fold
|
||||
* `copysubshortcuts false` - Sub shortcuts are not copied
|
||||
* `copysubshortcuts [true]` - Sub shortcuts are copied; this is the default
|
||||
|
||||
By default, GAM displays a message referencing files and folders not selected for copying by the options above.
|
||||
* `suppressnotselectedmessages false` - Do not suppress these messages; this is the default
|
||||
* `suppressnotselectedmessages [true]` - Suppress these messages
|
||||
|
||||
### By default, when copying sub files, all files, regardless of MIME type, are copied.
|
||||
You can specify restrictions on the MIME types to be copied.
|
||||
* `filemimetypes <MimeTypeList>` - Copy sub files with the specified MIME types
|
||||
* `filemimetypes not <MimeTypeList>` - Copy sub files with MIME types other than those specified
|
||||
* `filemimetypes category <MimeTypeNameList>` - Copy sub files with the specified MIME type categories
|
||||
|
||||
### By default, when copying sub files, all files, regardless of their `modifiedTime`, are copied.
|
||||
You can specify restrictions on the `modifiedTime` to be copied.
|
||||
* `start|starttime <Date>|<Time>` - If specified, `modifiedTime` must be >= the value
|
||||
* `end|endtime <Date>|<Time>` - If specified, `modifiedTime` must be <= the value
|
||||
* `range <Date>|<Time> <Date>|<Time>` - first value <= `modifiedTime` <= second value
|
||||
|
||||
### By default, when copying sub files, folders and shortcuts, all are copied.
|
||||
You can specify `<REMatchPattern>` patterns that limit the items copied based on their name.
|
||||
* `filenamematchpattern <REMatchPattern>` - Only files whose name matches `<REMatchPattern>` are copied
|
||||
@@ -207,6 +210,10 @@ You can specify `<REMatchPattern>` patterns that limit the items copied based on
|
||||
* `copysubfilesownedby regex <REMatchPattern>` - Only files owned by users whose email addresses match `<REMatchPattern>` are copied.
|
||||
* `copysubfilesownedby notregex <REMatchPattern>` - Only files owned by users whose email addresses do not match `<REMatchPattern>` are copied.
|
||||
|
||||
### By default, GAM displays a message referencing files and folders not selected for copying by the options above.
|
||||
* `suppressnotselectedmessages false` - Do not suppress these messages; this is the default
|
||||
* `suppressnotselectedmessages [true]` - Suppress these messages
|
||||
|
||||
### Specify a new name for the file/folder
|
||||
* `newfilename <DriveFileName>` - The copied file/folder will be named `<DriveFileName>`
|
||||
* If `stripnameprefix <String>` is specified, `<String>` will be stripped from the front of `<DriveFileName>`
|
||||
|
||||
@@ -116,7 +116,7 @@ gam <UserTypeEntity> show sendas [compact|format|html]
|
||||
```
|
||||
|
||||
These are the output formatting options:
|
||||
* `compact` - Escape carriage returns as \r and newlines as \n in original HTML; this format produces output that can be used as input to GAM
|
||||
* `compact` - Escape carriage returns as \r and newlines as \n in original HTML
|
||||
* `format` - Strip HTML keywords leaving basic printable information
|
||||
* `html` - Show original HTML; this is the default option; the output is human readable but cannot be used as input to GAM
|
||||
|
||||
@@ -184,12 +184,13 @@ If you have a current default signature, the API will update that, but if you de
|
||||
## Display signature
|
||||
### Display the signature as an indented list of keys and values.
|
||||
```
|
||||
gam <UserTypeEntity> show signature|sig [compact|format|html]
|
||||
gam <UserTypeEntity> show signature|sig [compact|format|html|template]
|
||||
[primary|default] [verifyonly]
|
||||
```
|
||||
|
||||
These are the output formatting options:
|
||||
* `compact` - Escape carriage returns as \r and newlines as \n in original HTML; this format produces output that can be used as input to GAM
|
||||
* `template` - Escape carriage returns as \r and newlines as \n in original HTML; this format produces output that can be used as input to GAM
|
||||
* `compact` - Escape carriage returns as \r and newlines as \n in original HTML
|
||||
* `format` - Strip HTML keywords leaving basic printable information
|
||||
* `html` - Show original HTML; this is the default option; the output is human readable but cannot be used an input to GAM
|
||||
|
||||
@@ -295,19 +296,10 @@ Use text like `{FirstName}` and `{Email}` in the locations where the actual valu
|
||||
|
||||
Once you're created the template signature, do the following:
|
||||
```
|
||||
$ gam user testuser@domain.com show signature compact > SimpleSig.html
|
||||
$ more SimpleSig.html
|
||||
SendAs Address: <testuser@domain.com>
|
||||
IsPrimary: True
|
||||
Default: True
|
||||
Signature: <div dir="ltr">--<div>Name: {FirstName} {LastName}<div>Phone: {Phone}</div><div>Email: {Email}</div></div><div><br></div><div>Company Name</div><div>Company Address</div><div><br></div></div>\n
|
||||
```
|
||||
Edit SimpleSig.html and delete all text from `SendAs ` through `Signature: `.
|
||||
The result should be:
|
||||
```
|
||||
$ gam redirect stdout ./SigTemplate.html user testuser@domain.com show signature template
|
||||
$ more SigTemplate.html
|
||||
<div dir="ltr">--<div>Name: {FirstName} {LastName}<div>Phone: {Phone}</div><div>Email: {Email}</div></div><div><br></div><div>Company Name</div><div>Company Address</div><div><br></div></div>\n
|
||||
```
|
||||
|
||||
This is a sample Users.csv file.
|
||||
```
|
||||
email,first,last,phone
|
||||
@@ -317,5 +309,20 @@ mjones@domain.com,Mary,Jones,510-555-1212 x 456
|
||||
|
||||
This command will update the user's signatures.
|
||||
```
|
||||
gam csv Users.csv gam user "~email" signature htmlfile SimpleSig.html replace FirstName "~first" replace LastName "~last" replace Phone "~phone" replace Email "~email"
|
||||
```
|
||||
gam csv Users.csv gam user "~email" signature htmlfile SigTemplate.html replace FirstName "~first" replace LastName "~last" replace Phone "~phone" replace Email "~email"
|
||||
```
|
||||
|
||||
You can also do the update with user attributes:
|
||||
|
||||
This is a sample Users.csv file.
|
||||
|
||||
```
|
||||
email
|
||||
bsmith@domain.com
|
||||
mjones@domain.com
|
||||
```
|
||||
|
||||
This command will update the user's signatures.
|
||||
```
|
||||
gam csv Users.csv gam user "~email" signature htmlfile SigTemplate.html replace FirstName field:name.givenname replace LastName field:name.familyname replace Phone field:phone.value.type.work replace Email field:email.primaryemail
|
||||
```
|
||||
|
||||
@@ -1018,6 +1018,10 @@ These existing options enable the display of additional information.
|
||||
* `(products|product <ProductIDList>)|(skus|sku <SKUIDList>)` - Display license information for a selected list of products/SKUs.
|
||||
* `schemas|custom|customschemas <SchemaNameList>` - Display all fields or selected fields of the specified custom schemas
|
||||
|
||||
You can use `license_skus = <SKUIDList>` in `gam.cfg` to list all of the licensing SKUs
|
||||
used in your workspace. Without this list or the `products|skus` options from above,
|
||||
GAM has to make 70+ API calls to get the licenses for a user; this can cause quota limit errors.
|
||||
|
||||
By default, Gam displays fields that only an adminstrator can view.
|
||||
* `userview` - Only display fields that other users in the domain can view.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.32.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
macOS Tahoe 26.2 x86_64
|
||||
@@ -15,7 +15,7 @@ Time: 2025-12-23T13:57:00-08:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.32.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
macOS Tahoe 26.2 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.32.01 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
macOS Tahoe 26.2 x86_64
|
||||
@@ -35,7 +35,7 @@ Path: /Users/Admin/bin/gam7
|
||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||
Time: 2025-12-23T13:57:00-08:00
|
||||
Your system time differs from admin.googleapis.com by less than 1 second
|
||||
OpenSSL 3.5.3 16 Sep 2025
|
||||
OpenSSL 3.6.1 27 Jan 2026
|
||||
arrow 1.3.0
|
||||
chardet 5.2.0
|
||||
cryptography 46.0.1
|
||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/Admin/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.32.01
|
||||
Latest: 7.32.06
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.32.01
|
||||
7.32.06
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -86,7 +86,7 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.32.01 - https://github.com/GAM-team/GAM
|
||||
GAM 7.32.06 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.2 64-bit final
|
||||
macOS Tahoe 26.2 x86_64
|
||||
|
||||
@@ -33,6 +33,7 @@ Notes and Information
|
||||
* [Questions? Visit the GAM Discussion Forum](https://groups.google.com/forum/#!forum/google-apps-manager)
|
||||
* [GAM Public Chat Room](GAM-Public-Chat-Room)
|
||||
* [Scripts](Scripts)
|
||||
* [Code Wiki](https://codewiki.google/github.com/gam-team/gam)
|
||||
* [Other Resources](Other-Resources)
|
||||
* [Drive REST API v3](Drive-REST-API-v3)
|
||||
* [BNF Syntax](BNF-Syntax)
|
||||
|
||||
@@ -463,6 +463,10 @@ oauth2_txt
|
||||
Path to oauth2.txt
|
||||
Default: GamConfigDir/oauth2.txt
|
||||
Environment variable: OAUTHFILE
|
||||
oauth2_txt_lock_mode
|
||||
Allowed values: 644, 664, 666
|
||||
File permissions for oauth2.txt.lock
|
||||
Default: 644
|
||||
oauth2service_json
|
||||
Path to oauth2service.json
|
||||
Default: GamConfigDir/oauth2service.json
|
||||
|
||||
Reference in New Issue
Block a user