Compare commits

..

15 Commits

Author SHA1 Message Date
Jay Lee
1f9624ad5c add 2 Gemini SKUs 2024-05-31 19:36:56 +00:00
Jay Lee
9c9ddff973 gam report vault 2024-05-31 19:36:12 +00:00
Ross Scroggs
f1636c7768 Added fromgmail to <EventType> 2024-05-31 12:26:06 -07:00
Ross Scroggs
0ebefda760 Updated gam update|delete|info adminrole to handle the following error:
ERROR: 400: failedPrecondition - Precondition check failed.
2024-05-29 19:13:46 -07:00
Ross Scroggs
5a335fb57b Updated <SchemaNameList> to allow schema fields 2024-05-29 10:49:31 -07:00
Ross Scroggs
db95cbcfa4 Fixed control-C bug 2024-05-28 13:06:19 -07:00
Ross Scroggs
33d9949283 MacOS swig: it's here/it's not 2024-05-26 17:35:09 -07:00
Ross Scroggs
41078d5ff6 Fix Windows build, cleanup 2024-05-26 16:21:06 -07:00
Ross Scroggs
52316774ad Disable Chat Admin APIs 2024-05-25 09:36:34 -07:00
Ross Scroggs
ce545ad062 Chat Admin APIs default to off 2024-05-25 09:02:10 -07:00
Ross Scroggs
2e5df12df1 Update print messages and print drivesettings 2024-05-25 08:35:11 -07:00
Jay Lee
46b9de642d actions: remove fullGamTest logic
Ensure live Google API tests run on test runners so we are exercising our code against Python versions other than the version used by GAM binaries.

These runners generally finish fastest anyway since they never need to compile OpenSSL, Python or PyInstaller.
2024-05-22 21:29:04 -04:00
Jay Lee
a9d600234c [no ci] actions: macOS runner now ships with rust, gnupg and swig 2024-05-22 21:15:33 -04:00
Jay Lee
5c8b69e8b7 actions: move PyInstaller back to latest to see what happens 2024-05-22 21:08:58 -04:00
Ross Scroggs
29792677d7 Added option showusagebytes to gam <UserTypeEntity> print|show drivesettings 2024-05-22 13:18:02 -07:00
19 changed files with 447 additions and 199 deletions

View File

@@ -35,13 +35,11 @@ jobs:
goal: build
arch: x86_64
openssl_archs: linux-x86_64
fullGamTest: yes
- os: [self-hosted, linux, arm64]
jid: 2
goal: build
arch: aarch64
openssl_archs: linux-aarch64
fullGamTest: yes
- os: ubuntu-20.04
jid: 3
goal: build
@@ -59,13 +57,11 @@ jobs:
goal: build
arch: x86_64
openssl_archs: darwin64-x86_64
fullGamTest: yes
- os: macos-14
jid: 6
goal: build
arch: aarch64
openssl_archs: darwin64-arm64
fullGamTest: yes
- os: macos-14
jid: 7
goal: build
@@ -76,7 +72,6 @@ jobs:
goal: build
arch: Win64
openssl_archs: VC-WIN64A
fullGamTest: yes
- os: ubuntu-22.04
goal: test
python: "3.8"
@@ -119,7 +114,7 @@ jobs:
with:
path: |
cache.tar.xz
key: gam-${{ matrix.jid }}-20240416
key: gam-${{ matrix.jid }}-20240526
- name: Untar Cache archive
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
@@ -200,8 +195,9 @@ jobs:
bash ./rust.sh -y
source $HOME/.cargo/env
# Install needed packages
brew update
brew install gpg swig
# brew update
# brew install gpg
brew install swig
- name: Windows Configure VCode
uses: ilammy/msvc-dev-cmd@v1
@@ -223,12 +219,6 @@ jobs:
GAM_ARCHIVE_ARCH="x86_64"
WIX_ARCH="x64"
CHOC_OPS=""
elif [[ "${arch}" == "Win32" ]]; then
PYEXTERNALS_PATH="win32"
PYBUILDRELEASE_ARCH="Win32"
GAM_ARCHIVE_ARCH="x86"
WIX_ARCH="x86"
CHOC_OPS="--forcex86"
fi
if [[ "${RUNNER_OS}" == "macOS" ]]; then
MAKE=make
@@ -498,22 +488,20 @@ jobs:
cd pyinstaller
export latest_release=$(git tag --list | grep -v dev | grep -v rc | sort -Vr | head -n1)
#V6.0.0 causes errors on staticx
if [[ "${staticx}" == "yes" ]]; then
git checkout "v5.13.2"
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
git checkout "v5.13.2"
elif [[ "${RUNNER_OS}" == "macOS" ]]; then
git checkout "v5.13.2"
else
git checkout "${latest_release}"
fi
#if [[ "${staticx}" == "yes" ]]; then
# git checkout "v5.13.2"
#elif [[ "${RUNNER_OS}" == "Windows" ]]; then
# git checkout "v5.13.2"
#elif [[ "${RUNNER_OS}" == "macOS" ]]; then
# git checkout "v5.13.2"
#else
git checkout "${latest_release}"
#fi
# remove pre-compiled bootloaders so we fail if bootloader compile fails
rm -rvf PyInstaller/bootloader/*-*/*
cd bootloader
export PYINSTALLER_BUILD_ARGS=""
case "${arch}" in
"Win32")
export PYINSTALLER_BUILD_ARGS="--target-arch=32bit"
;;
"Win64")
export PYINSTALLER_BUILD_ARGS="--target-arch=64bit"
;;
@@ -540,7 +528,7 @@ jobs:
elif [[ "${RUNNER_OS}" == "Windows" ]]; then
# Work around issue where PyInstaller picks up python3.dll from other Python versions
# https://github.com/pyinstaller/pyinstaller/issues/7102
export PATH="/usr/bin"
export PATH="$(dirname ${PYTHON}):/usr/bin"
else
export gampath=$(realpath "${gampath}")
fi
@@ -618,15 +606,16 @@ jobs:
if: runner.os != 'Windows' && matrix.goal == 'build'
run: |
if [[ "${RUNNER_OS}" == "macOS" ]]; then
GAM_ARCHIVE="gam-${GAMVERSION}-macos-${arch}.tar.xz"
GAM_ARCHIVE="gam-${GAMVERSION}-macos-${arch}.tar.xz"
elif [[ "${RUNNER_OS}" == "Linux" ]]; then
if [[ "${staticx}" == "yes" ]]; then
libver="legacy"
else
libver="glibc$(ldd --version | awk '/ldd/{print $NF}')"
fi
GAM_ARCHIVE="gam-${GAMVERSION}-linux-$(arch)-${libver}.tar.xz"
if [[ "${staticx}" == "yes" ]]; then
libver="legacy"
else
libver="glibc$(ldd --version | awk '/ldd/{print $NF}')"
fi
GAM_ARCHIVE="gam-${GAMVERSION}-linux-$(arch)-${libver}.tar.xz"
fi
echo "GAM Archive ${GAM_ARCHIVE}"
tar -C dist/ --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam
- name: Windows package
@@ -661,7 +650,7 @@ jobs:
echo "We successfully compiled Python ${this_python} and OpenSSL ${this_openssl}"
- name: Live API tests push only
if: (github.event_name == 'push' || github.event_name == 'schedule') && matrix.fullGamTest == 'yes'
if: (github.event_name == 'push' || github.event_name == 'schedule')
env:
PASSCODE: ${{ secrets.PASSCODE }}
run: |
@@ -748,7 +737,7 @@ jobs:
$gam info group $newgroup
$gam info cigroup $newgroup membertree
# confirm mailbox is provisoned before continuing
$gam user $newuser waitformailbox
$gam user $newuser waitformailbox retries 20
$gam user $newuser imap on
$gam user $newuser show imap
$gam user $newuser show delegates
@@ -762,7 +751,7 @@ jobs:
$gam user $gam_user insertemail subject "GHA insert $newbase" file gam.py labels INBOX,UNREAD # yep body is gam code
$gam user $gam_user sendemail subject "GHA send $gam_user $newbase" file gam.py recipient admin@pdl.jaylee.us
$gam user $gam_user draftemail subject "GHA draft $newbase" message "Draft message test"
$gam csvfile sample.csv:email waitformailbox
$gam csvfile sample.csv:email waitformailbox retries 20
$gam user $newuser delegate to "${newbase}-bulkuser-1" || if [ $? != 50 ]; then exit $?; fi # expect a 50 return code (delegation failed)
$gam users "$gam_user $newbase-bulkuser-1 $newbase-bulkuser-2 $newbase-bulkuser-3" delete messages query in:anywhere maxtodelete 99999 doit || if [ $? != 60 ]; then exit $?; fi # expect a 60 return code (no messages)
$gam users "$newbase-bulkuser-4 $newbase-bulkuser-5 $newbase-bulkuser-6" trash messages query in:anywhere maxtotrash 99999 doit || if [ $? != 60 ]; then exit $?; fi # expect a 60 return code (no messages)

View File

@@ -837,7 +837,7 @@ gam update sakey
(algorithm KEY_ALG_RSA_1024|KEY_ALG_RSA_2048)|
(localkeysize 1024|2048|4096 [validityhours <Number>])|
(yubikey yubikey_pin yubikey_slot AUTHENTICATION|SIGNATURE yubikey_serialnumber <Number>)
gam rotate sakey replace_existing
gam rotate sakey replace_current
(algorithm KEY_ALG_RSA_1024|KEY_ALG_RSA_2048)|
(localkeysize 1024|2048|4096 [validityhours <Number>])|
(yubikey yubikey_pin yubikey_slot AUTHENTICATION|SIGNATURE yubikey_serialnumber <Number>)

View File

@@ -457,6 +457,7 @@
<ResellerID> ::= <String>
<ResourceID> ::= <String>
<SchemaName> ::= <String>
<SchemaNameField> ::= <SchemaName>.<FieldName>
<Section> ::= <String>
<SendAsContent> ::=
(sig|signature|htmlsig <String>)|
@@ -513,6 +514,7 @@
<Title> ::= <String>
<ToDriveAttribute> ::=
(tdaddsheet [<Boolean>])|
(tdalert <EmailAddress>)*|
(tdbackupsheet (id:<Number>)|<String>)|
(tdcellnumberformat text|number)|
(tdcellwrap clip|overflow|wrap)|
@@ -520,17 +522,20 @@
(tdcopysheet (id:<Number>)|<String>)|
(tddescription <String>)|
(tdfileid <DriveFileID>)|
(tdfrom <EmailAddress>)|
(tdlocalcopy [<Boolean>])|
(tdlocale <Locale>)|
(tdnobrowser [<Boolean>])|
(tdnoemail [<Boolean>])|
(tdnoescapechar [<Boolean>])|
(tdnotify [<Boolean>])|
(tdparent (id:<DriveFolderID>)|<DriveFolderName>)|
(tdretaintitle [<Boolean>])|
(tdshare <EmailAddress> commenter|reader|writer)*|
(tdsheet (id:<Number>)|<String>)|
(tdsheettimestamp [<Boolean>] [tdsheettimeformat <String>])
(tdsheettitle <String>)|
(tdsubject <String>)|
([tdsheetdaysoffset <Number>] [tdsheethoursoffset <Number>])|
(tdtimestamp [<Boolean>] [tdtimeformat <String>]
[tddaysoffset <Number>] [tdhoursoffset <Number>])|

View File

@@ -178,6 +178,7 @@ Client access works when accessing Resource calendars.
<EventType> ::=
default|
focustime|
fromgmail|
outofoffice|
workinglocation
<EventTypeList> ::= "<EventType>(,<EventType>)*"

View File

@@ -182,7 +182,7 @@ gam user testuser show fileinfo anydrivefilename "Test File"
gam user testuser show fileinfo anydrivefilename:"Test File"
```
## Select file ownership
By default, files the user owns are sisplayed; you can select the ownership characteristic.
By default, files the user owns are displayed; you can select the ownership characteristic.
```
anyowner|(showownedby any|me|others)
```

View File

@@ -10,6 +10,42 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation
### 6.76.10
Added `fromgmail` to `<EventType>` that can be used in `gam calendars <CalendarEntity> print|show events ... eventtype fromgmail`.
* See: https://workspaceupdates.googleblog.com/2024/05/google-calendar-api-event-type-fromgmail.html
### 6.76.09
Updated `gam update|delete|info adminrole` to handle the following error:
```
ERROR: 400: failedPrecondition - Precondition check failed.
```
### 6.76.08
Updated `<SchemaNameList>` to `"<SchemaName>|<SchemaFieldName>(,<SchemaName>|<SchemaFieldName>)*"`
that allows `schemas <SchemaNameList>` in `gam info user` and `gam print users` to display all fields or selected fields
of the specified custom schemas.
### 6.76.07
Fixed bug where control-C was not recognized when GAM had processed all rows in a CSV file
and was `Waiting for N running processes to finish before terminating`.
### 6.76.06
Fixed bug in `gam <UserTypeEntity> print messages ... positivecountsonly` where message counts with value 0 were deiplayed.
Added option `addcsvdata <FieldName> <String>` to `gam <UserTypeEntity> print|messages` that adds
additional columns of data to the CSV file output.
Added option `showusagebytes` to `gam <UserTypeEntity> print|show drivesettings` that displays
the following fields in bytes ```usageBytes,usageInDriveBytes,usageInDriveTrashBytes```
in addition to the fields in their formatted form with units: ```usage,usageInDrive,usageInDriveTrash```.
This will be most useful with `print` as the rows can be sorted based on the `usagexxxBytes` columns.
### 6.76.05
Added options `deletefromoldowner`, `addtonewowner <CalendarAttribute>*` and `nolistmessages`

View File

@@ -335,7 +335,7 @@ writes the credentials into the file oauth2.txt.
admin@server:/Users/admin/bin/gamadv-xtd3$ rm -f /Users/admin/GAMConfig/oauth2.txt
admin@server:/Users/admin/bin/gamadv-xtd3$ ./gam version
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
GAMADV-XTD3 6.76.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.76.10 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
MacOS Sonoma 14.4.1 x86_64
@@ -1009,7 +1009,7 @@ writes the credentials into the file oauth2.txt.
C:\GAMADV-XTD3>del C:\GAMConfig\oauth2.txt
C:\GAMADV-XTD3>gam version
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
GAMADV-XTD3 6.76.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.76.10 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
Windows-10-10.0.17134 AMD64

View File

@@ -85,7 +85,7 @@
<QueryMobileList> ::= "<QueryMobile>(,<QueryMobile>)*"
<QueryUserList> ::= "<QueryUser>(,<QueryUser>)*"
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
<SchemaNameList> ::= "<SchemaName>(,<SchemaName>)*"
<SchemaNameList> ::= "<SchemaName>|<SchemaFieldName>(,<SchemaName>|<SchemaFieldName>)*"
<SerialNumberList> ::= "<SerialNumber>(,<SerialNumber>)*"
<ServiceAccountKeyList> ::= "<ServiceAccountKey>(,<ServiceAccountKey>)*"
<SiteACLScopeList> ::= "<SiteACLScope>(,<SiteACLScope>)*"

View File

@@ -243,6 +243,7 @@
<EventType> ::=
default|
focustime|
fromgmail|
outofoffice|
workinglocation
<EventTypeList> ::= "<EventType>(,<EventType>)*"

View File

@@ -141,10 +141,16 @@ The `quotechar <Character>` option allows you to choose an alternate quote chara
```
gam <UserTypeEntity> print drivesettings [todrive <ToDriveAttribute>*]
[allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
[delimiter <Character>]
[delimiter <Character>] [showusagebytes]
gam <UserTypeEntity> show drivesettings
[allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
[delimiter <Character>]
[delimiter <Character>] [showusagebytes]
```
If no fields are selected, these fields will be displayed:
`name,appInstalled,largestChangeId,limit,maxUploadSize,permissionId,rootFolderId,usage,usageInDrive,usageInDriveTrash`
By default, these fields are displayed in formatted form with units: ```usage,usageInDrive,usageInDriveTrash```.
The option `showusagebytes` also displays the following fields in bytes ```usageBytes,usageInDriveBytes,usageInDriveTrashBytes```.
This will be most useful with `print` as the rows can be sorted based on the `usagexxxBytes` columns.

View File

@@ -1036,6 +1036,10 @@ When `allfields` is specified (or no fields are specified), use `showshareddrive
when shared drives are queried/selected. In this case, the Drive API returns the permission IDs
but not the permissions themselves so GAM makes an additional API call per file to get the permissions.
By default, when `showimimetype` and `filepath|fullpath`are both specified, GAM locally filters files by MimeType;
this may be slow if the user has a large number of files. Adding the option `mimetypeinquery` or `mimetypeinquery true`
causes GAM to have Google filter files by MimeType; this will increase performance.
See [Select files for Display file counts, list, tree](#select-files-for-display-file-counts-list-tree)
## File selection by name and entity shortcuts for Display file list

View File

@@ -110,6 +110,11 @@ queries "`"orgUnitPath=\'/Students/Lower\ School/2027\'`",`"orgUnitPath=\'/Stude
<QueryUser> ::= <String>
See: https://developers.google.com/admin-sdk/directory/v1/guides/search-users
<FieldName> ::= <String>
<SchemaName> ::= <String>
<SchemaNameField> ::= <SchemaName>.<FieldName>
<SchemaNameList> ::= "<SchemaName>|<SchemaFieldName>(,<SchemaName>|<SchemaFieldName>)*"
<StorageBucketName> ::= <String>
<StorageObjectName> ::= <String>
<StorageBucketObjectName> ::=
@@ -488,7 +493,7 @@ clearschema <SchemaName>
```
Clear a specific field in a schema:
```
clearschema <SchemaName>.<FieldName>
clearschema <SchemaNameField>
```
## Create a user
@@ -614,7 +619,7 @@ gam update user <UserItem> [ignorenullpassword] <UserAttribute>*
[updateoufromgroup <FileName> [charset <Charset>]
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]
[fields <FieldNameList>] [keyfield <FieldName>] [datafield <FieldName>]]
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName>|<SchemaNameField>]
[createifnotfound] [notfoundpassword random|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -635,7 +640,7 @@ gam update users <UserTypeEntity> [ignorenullpassword] <UserAttribute>*
[updateoufromgroup <FileName> [charset <Charset>]
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]
[fields <FieldNameList>] [keyfield <FieldName>] [datafield <FieldName>]]
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName>|<SchemaNameField>]
[createifnotfound] [notfoundpassword random|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -656,7 +661,7 @@ gam <UserTypeEntity> update users [ignorenullpassword] <UserAttribute>*
[updateoufromgroup <FileName> [charset <Charset>]
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]
[fields <FieldNameList>] [keyfield <FieldName>] [datafield <FieldName>]]
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName>|<SchemaNameField>]
[createifnotfound] [notfoundpassword random|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -954,7 +959,7 @@ Starting in version `5.23.01`, the variable `quick_info_user` was added to `gam.
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 the specified custom schemas
* `schemas|custom|customschemas <SchemaNameList>` - Display all fields or selected fields of the specified custom schemas
By default, Gam displays fields that only an adminstrator can view.
* `userview` - Only display fields that other users in the domain can view.
@@ -1064,8 +1069,8 @@ By default, Gam displays only the primary email address for each user.
* `allfields|basic` - Display all non custom schema fields for each user.
* `full` - Display all fields including all custom schema fields for each user.
* `<UserFieldName>* [fields <UserFieldNameList>]` - Only display selected fields.
* `schemas|custom all` - Get custom schema information for all schemas.
* `schemas|custom <SchemaNameList>` - Get custom schema information for a selected list of schemas.
* `schemas|custom all` - Display custom schema information for all schemas.
* `schemas|custom <SchemaNameList>` - Display all fields or selected fields of the specified custom schemas
By default, when aliases are displayed, all aliases are displayed. Use `aliasmatchpattern <RegularExpression>`
to limit the display of aliases to those that match `<RegularExpression>`.

View File

@@ -1,9 +1,9 @@
# Version and Help
\# Version and Help
Print the current version of Gam with details
```
gam version
GAMADV-XTD3 6.76.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.76.10 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
MacOS Sonoma 14.4.1 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
GAMADV-XTD3 6.76.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.76.10 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
MacOS Sonoma 14.4.1 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
GAMADV-XTD3 6.76.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.76.10 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
MacOS Sonoma 14.4.1 x86_64
@@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/Admin/bin/gamadv-xtd3
Version Check:
Current: 5.35.08
Latest: 6.76.05
Latest: 6.76.10
echo $?
1
```
@@ -72,7 +72,7 @@ echo $?
Print the current version number without details
```
gam version simple
6.76.05
6.76.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 6.76.05 - https://github.com/taers232c/GAMADV-XTD3
GAM 6.76.10 - https://github.com/taers232c/GAMADV-XTD3
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.3 64-bit final
MacOS Sonoma 14.4.1 x86_64

View File

@@ -546,6 +546,7 @@ If an item contains spaces, it should be surrounded by ".
<ResellerID> ::= <String>
<ResourceID> ::= <String>
<SchemaName> ::= <String>
<SchemaNameField> ::= <SchemaName>.<FieldName>
<Section> ::= <String>
<SendAsContent> ::=
(sig|signature|htmlsig <String>)|
@@ -626,7 +627,7 @@ If an item contains spaces, it should be surrounded by ".
(tdsubject <String>)|
([tdsheetdaysoffset <Number>] [tdsheethoursoffset <Number>])|
(tdtimestamp [<Boolean>] [tdtimeformat <String>]
([tddaysoffset <Number>] [tdhoursoffset <Number>])|
[tddaysoffset <Number>] [tdhoursoffset <Number>])|
(tdtimezone <TimeZone>)|
(tdtitle <String>)|
(tdupdatesheet [<Boolean>])|
@@ -725,7 +726,7 @@ If an item contains spaces, it should be surrounded by ".
<QueryMobileList> ::= "<QueryMobile>(,<QueryMobile>)*"
<QueryUserList> ::= "<QueryUser>(,<QueryUser>)*"
<ResourceIDList> ::= "<ResourceID>(,<ResourceID>)*"
<SchemaNameList> ::= "<SchemaName>(,<SchemaName>)*"
<SchemaNameList> ::= "<SchemaName>|<SchemaFieldName>(,<SchemaName>|<SchemaFieldName>)*"
<SerialNumberList> ::= "<SerialNumber>(,<SerialNumber>)*"
<ServiceAccountKeyList> ::= "<ServiceAccountKey>(,<ServiceAccountKey>)*"
<SiteACLScopeList> ::= "<SiteACLScope>(,<SiteACLScope>)*"
@@ -4220,7 +4221,8 @@ gam report usage customer [todrive <ToDriveAttribute>*]
rules|
saml|
token|tokens|oauthtoken|
useraccounts
useraccounts|
vault
gam report <ActivityApplicationName> [todrive <ToDriveAttribute>*]
[(user all|<UserItem>)|(orgunit|org|ou <OrgUnitPath> [showorgunit])|(select <UserTypeEntity>)]
@@ -4991,7 +4993,7 @@ gam <UserTypeEntity> show teamdriveacls
(field:<UserReplacementField>)|
(field:<UserReplacementFieldSubfield>)|
(field:<UserReplacementFieldSubfieldMatchSubfield>)|
(schema:<SchemaName>.<FieldName>)|
(schema:<SchemaNameField>)|
<String>
# Vault/Takeout
@@ -5256,9 +5258,9 @@ gam download storagefile <StorageBucketObjectName>
(recoveryemail <EmailAddress>)|
(recoveryphone <string>)|
(suspend|suspended <Boolean>)|
(<SchemaName>.<FieldName> [scalarnonempty|
[multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]]]
<String>)
(<SchemaNameField> [scalarnonempty|
[multivalued|multivalue|value|multinonempty [type home|other|work|(custom <String>)]]]
<String>)
<UserMultiAttribute> ::=
(address type home|other|work|(custom <String>)
@@ -5347,7 +5349,7 @@ gam update user <UserItem> [ignorenullpassword] <UserAttribute>*
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]
[fields <FieldNameList>] [keyfield <FieldName>] [datafield <FieldName>]]
[immutableous <OrgUnitEntity>]|
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName> | <SchemaNameField>]
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -5382,7 +5384,7 @@ gam update users <UserTypeEntity> [ignorenullpassword] <UserAttribute>*
[updateoufromgroup <FileName> [charset <CharSet>]
[columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]
[fields <FieldNameList>] [keyfield <FieldName>] [datafield <FieldName>]]
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName>|<SchemaNameField>]
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -5415,7 +5417,7 @@ gam <UserTypeEntity> update users [ignorenullpassword] <UserAttribute>*
[verifynotinvitable] [noactionifalias]
[updateprimaryemail <RegularExpression> <EmailReplacement>]
[updateoufromgroup <CSVFileInput> [keyfield <FieldName>] [datafield <FieldName>]]
[clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
[clearschema <SchemaName> | <SchemaNameField>]
[createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
[alias|aliases <EmailAddressList>]
@@ -6904,8 +6906,12 @@ gam <UserTypeEntity> print|show driveactivity [todrive <ToDriveAttribute>*]
usageindrivetrash
<DriveSettingsFieldNameList> ::= "<DriveSettingsFieldName>(,<DriveSettingsFieldName>)*"
gam <UserTypeEntity> print drivesettings [todrive <ToDriveAttribute>*] [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)] [delimiter <Character>]
gam <UserTypeEntity> show drivesettings [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)] [delimiter <Character>]
gam <UserTypeEntity> print drivesettings [todrive <ToDriveAttribute>*]
[allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
[delimiter <Character>] [showusagebytes]
gam <UserTypeEntity> show drivesettings
[allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
[delimiter <Character>] [showusagebytes]
gam <UserTypeEntity> print emptydrivefolders [todrive <ToDriveAttribute>*]
[select <DriveFileEntity>]
@@ -7222,9 +7228,10 @@ gam <UserTypeEntity> print messages|threads [todrive <ToDriveAttribute>*]
[countsonly|positivecountsonly] [useronly]
[headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String> [dateheaderconverttimezone [<Boolean>]]]
[showlabels] [delimiter <Character>] [showbody] [showdate] [showsize] [showsnippet]
[convertcrnl] [delimiter <Character>]
[[attachmentnamepattern <RegularExpression>]
[showattachments [noshowtextplain]]]
[convertcrnl]
(addcsvdata <FieldName> <String>)*
# Users - Gmail - Profile

View File

@@ -2,6 +2,42 @@
Merged GAM-Team version
6.76.10
Added `fromgmail` to `<EventType>` that can be used in `gam calendars <CalendarEntity> print|show events ... eventtype fromgmail`.
* See: https://workspaceupdates.googleblog.com/2024/05/google-calendar-api-event-type-fromgmail.html
6.76.09
Updated `gam update|delete|info adminrole` to handle the following error:
```
ERROR: 400: failedPrecondition - Precondition check failed.
```
6.76.08
Updated `<SchemaNameList>` to `"<SchemaName>|<SchemaFieldName>(,<SchemaName>|<SchemaFieldName>)*"`
that allows `schemas <SchemaNameList>` in `gam info user` and `gam print users` to display all fields or selected fields
of the specified custom schemas.
6.76.07
Fixed bug where control-C was not recognized when GAM had processed all rows in a CSV file
and was `Waiting for N running processes to finish before terminating`.
6.76.06
Fixed bug in `gam <UserTypeEntity> print messages ... positivecountsonly` where message counts with value 0 were deiplayed.
Added option `addcsvdata <FieldName> <String>` to `gam <UserTypeEntity> print|messages` that adds
additional columns of data to the CSV file output.
Added option `showusagebytes` to `gam <UserTypeEntity> print|show drivesettings` that displays
the following fields in bytes ```usageBytes,usageInDriveBytes,usageInDriveTrashBytes```
in addition to the fields in their formatted form with units: ```usage,usageInDrive,usageInDriveTrash```.
This will be most useful with `print` as the rows can be sorted based on the `usagexxxBytes` columns.
6.76.05
Added options `deletefromoldowner`, `addtonewowner <CalendarAttribute>*` and `nolistmessages`

View File

@@ -4163,6 +4163,8 @@ def SetGlobalVariables():
GC.Values[GC.PRINT_CROS_OUS] = GM.Globals[GM.PRINT_CROS_OUS]
if not GC.Values[GC.PRINT_CROS_OUS_AND_CHILDREN]:
GC.Values[GC.PRINT_CROS_OUS_AND_CHILDREN] = GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN]
GC.Values[GC.SHOW_GETTINGS] = GM.Globals[GM.SHOW_GETTINGS]
GC.Values[GC.SHOW_GETTINGS_GOT_NL] = GM.Globals[GM.SHOW_GETTINGS_GOT_NL]
# customer_id, domain and admin_email must be set when enable_dasa = true
if GC.Values[GC.ENABLE_DASA]:
errors = 0
@@ -9574,6 +9576,7 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
csvHeaderForce,
csvRowFilter, csvRowFilterMode, csvRowDropFilter, csvRowDropFilterMode,
csvRowLimit,
showGettings, showGettingsGotNL,
args):
global mplock
@@ -9612,6 +9615,8 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
GM.Globals[GM.PRINT_CROS_OUS] = printCrosOUs
GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN] = printCrosOUsAndChildren
GM.Globals[GM.SAVED_STDOUT] = None
GM.Globals[GM.SHOW_GETTINGS] = showGettings
GM.Globals[GM.SHOW_GETTINGS_GOT_NL] = showGettingsGotNL
GM.Globals[GM.SYSEXITRC] = 0
GM.Globals[GM.PARSER] = None
if mpQueueCSVFile:
@@ -9690,16 +9695,18 @@ def MultiprocessGAMCommands(items, showCmds):
GM.Globals[GM.MULTIPROCESS_EXIT_PROCESSING] = True
def signal_handler(sig, frame):
controlC['trapped'] = True
nonlocal controlC
controlC = True
def handleControlC(source):
nonlocal controlC
batchWriteStderr(f'Control-C (Multiprocess-{source})\n')
setSysExitRC(KEYBOARD_INTERRUPT_RC)
batchWriteStderr(Msg.BATCH_CSV_TERMINATE_N_PROCESSES.format(currentISOformatTimeStamp(),
numItems, poolProcessResults[0],
PROCESS_PLURAL_SINGULAR[poolProcessResults[0] == 1]))
pool.terminate()
controlC['trapped'] = False
controlC = False
if not items:
return
@@ -9753,7 +9760,7 @@ def MultiprocessGAMCommands(items, showCmds):
else:
mpQueueCSVFile = None
# signal.signal(signal.SIGINT, origSigintHandler)
controlC = {'trapped': False}
controlC = False
signal.signal(signal.SIGINT, signal_handler)
batchWriteStderr(Msg.USING_N_PROCESSES.format(currentISOformatTimeStamp(),
numItems, numPoolProcesses,
@@ -9764,7 +9771,7 @@ def MultiprocessGAMCommands(items, showCmds):
for item in items:
if GM.Globals[GM.MULTIPROCESS_EXIT_PROCESSING]:
break
if controlC['trapped']:
if controlC:
break
if item[0] == Cmd.COMMIT_BATCH_CMD:
batchWriteStderr(Msg.COMMIT_BATCH_WAIT_N_PROCESSES.format(currentISOformatTimeStamp(),
@@ -9818,6 +9825,7 @@ def MultiprocessGAMCommands(items, showCmds):
GC.Values[GC.CSV_OUTPUT_ROW_DROP_FILTER],
GC.Values[GC.CSV_OUTPUT_ROW_DROP_FILTER_MODE],
GC.Values[GC.CSV_OUTPUT_ROW_LIMIT],
GC.Values[GC.SHOW_GETTINGS], GC.Values[GC.SHOW_GETTINGS_GOT_NL],
item])
poolProcessResults[0] += 1
if parallelPoolProcesses > 0:
@@ -9833,7 +9841,7 @@ def MultiprocessGAMCommands(items, showCmds):
break
time.sleep(1)
processWaitStart = time.time()
if not controlC['trapped']:
if not controlC:
if GC.Values[GC.PROCESS_WAIT_LIMIT] > 0:
waitRemaining = GC.Values[GC.PROCESS_WAIT_LIMIT]
else:
@@ -9851,6 +9859,9 @@ def MultiprocessGAMCommands(items, showCmds):
for p in completedProcesses:
del poolProcessResults[p]
if poolProcessResults[0] > 0:
if controlC:
handleControlC('SIG')
break
time.sleep(5)
if GC.Values[GC.PROCESS_WAIT_LIMIT] > 0:
delta = int(time.time()-processWaitStart)
@@ -12358,7 +12369,7 @@ def _formatOAuth2ServiceData(service_data):
GM.Globals[GM.OAUTH2SERVICE_JSON_DATA] = service_data.copy()
return json.dumps(GM.Globals[GM.OAUTH2SERVICE_JSON_DATA], indent=2, sort_keys=True)
def doProcessSvcAcctKeys(mode, iam=None, projectId=None, clientEmail=None, clientId=None):
def doProcessSvcAcctKeys(mode=None, iam=None, projectId=None, clientEmail=None, clientId=None):
def getSAKeyParms(body, new_data):
nonlocal local_key_size, validityHours
while Cmd.ArgumentsRemaining():
@@ -12380,7 +12391,7 @@ def doProcessSvcAcctKeys(mode, iam=None, projectId=None, clientEmail=None, clien
new_data['yubikey_serial_number'] = getInteger()
else:
unknownArgumentExit()
def waitForCompletion(i):
sleep_time = i*5
if i > 3:
@@ -12390,6 +12401,8 @@ def doProcessSvcAcctKeys(mode, iam=None, projectId=None, clientEmail=None, clien
local_key_size = 2048
validityHours = 0
body = {}
if mode is None:
mode = getChoice(['retainnone', 'retainexisting', 'replacecurrent'])
if iam is None or mode == 'upload':
if iam is None:
_, iam = buildGAPIServiceObject(API.IAM, None)
@@ -13284,6 +13297,7 @@ REPORT_CHOICE_MAP = {
'user': 'user',
'users': 'user',
'useraccounts': 'user_accounts',
'vault': 'vault',
}
REPORT_ACTIVITIES_UPPERCASE_EVENTS = {
@@ -16175,12 +16189,12 @@ def doCreateUpdateAdminRoles():
customer=GC.Values[GC.CUSTOMER_ID], body=body, fields='roleId,roleName')
else:
result = callGAPI(cd.roles(), 'patch',
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND],
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION],
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId, body=body, fields='roleId,roleName')
entityActionPerformed([Ent.ADMIN_ROLE, f"{result['roleName']}({result['roleId']})"])
except GAPI.duplicate as e:
entityActionFailedWarning([Ent.ADMIN_ROLE, f"{body['roleName']}"], str(e))
except (GAPI.notFound, GAPI.forbidden) as e:
except (GAPI.notFound, GAPI.forbidden, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.ADMIN_ROLE, roleId], str(e))
except (GAPI.badRequest, GAPI.customerNotFound):
accessErrorExit(cd)
@@ -16192,10 +16206,10 @@ def doDeleteAdminRole():
checkForExtraneousArguments()
try:
callGAPI(cd.roles(), 'delete',
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND],
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION],
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId)
entityActionPerformed([Ent.ADMIN_ROLE, f"{role}({roleId})"])
except (GAPI.notFound, GAPI.forbidden) as e:
except (GAPI.notFound, GAPI.forbidden, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.ADMIN_ROLE, roleId], str(e))
except (GAPI.badRequest, GAPI.customerNotFound):
accessErrorExit(cd)
@@ -16236,12 +16250,12 @@ def doInfoAdminRole():
fields = ','.join(set(fieldsList))
try:
role = callGAPI(cd.roles(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND],
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION],
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId, fields=fields)
role.setdefault('isSuperAdminRole', False)
role.setdefault('isSystemRole', False)
_showAdminRole(role)
except (GAPI.notFound, GAPI.forbidden) as e:
except (GAPI.notFound, GAPI.forbidden, GAPI.failedPrecondition) as e:
entityActionFailedWarning([Ent.ADMIN_ROLE, roleId], str(e))
except (GAPI.badRequest, GAPI.customerNotFound):
accessErrorExit(cd)
@@ -16395,9 +16409,9 @@ def doPrintShowAdmins():
if roleId not in rolePrivileges:
try:
rolePrivileges[roleId] = callGAPI(cd.roles(), 'get',
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND],
throwReasons=[GAPI.BAD_REQUEST, GAPI.CUSTOMER_NOT_FOUND, GAPI.FORBIDDEN]+[GAPI.NOT_FOUND, GAPI.FAILED_PRECONDITION],
customer=GC.Values[GC.CUSTOMER_ID], roleId=roleId, fields='rolePrivileges')
except (GAPI.notFound, GAPI.forbidden) as e:
except (GAPI.notFound, GAPI.forbidden, GAPI.failedPrecondition) as e:
entityActionFailedExit([Ent.USER, userKey, Ent.ADMIN_ROLE, admin['roleId']], str(e))
rolePrivileges[roleId] = None
except (GAPI.badRequest, GAPI.customerNotFound):
@@ -25470,6 +25484,11 @@ def _getChatPageMessage(entityType, user, i, count, pfilter):
return getPageMessage()
CHAT_PAGE_SIZE = 1000
CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP = {
'createtime': 'createTime',
'lastactivetime': 'lastActiveTime',
'membershipcount': 'membershipCount.joined_direct_human_user_count'
}
# gam [<UserTypeEntity>] show chatspaces
# [types <ChatSpaceTypeList>]
@@ -25477,15 +25496,34 @@ CHAT_PAGE_SIZE = 1000
# gam [<UserTypeEntity>] print chatspaces [todrive <ToDriveAttribute>*]
# [types <ChatSpaceTypeList>]
# [formatjson [quotechar <Character>]]
# gam [<UserTypeEntity>] show chatspaces adminaccess|asadmin
# [query <String>]]
# [orderby <ChatSpaceAdminOrderByFieldName> [ascending|descending]]
# [formatjson]
# gam [<UserTypeEntity>] print chatspaces adminaccess|asadmin [todrive <ToDriveAttribute>*]
# [query <String>]]
# [orderby <ChatSpaceAdminOrderByFieldName> [ascending|descending]]
# [formatjson [quotechar <Character>]]
def printShowChatSpaces(users):
csvPF = CSVPrintFile(['User', 'name'] if not isinstance(users, list) else ['name']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
useAdminAccess = checkArgumentPresent(ADMIN_ACCESS_OPTIONS)
OBY = OrderBy(CHAT_SPACES_ADMIN_ORDERBY_CHOICE_MAP)
pfilter = ''
kwargs = {}
if not useAdminAccess:
api = API.CHAT_SPACES
function = 'list'
else:
api = API.CHAT_SPACES_ADMIN
function = 'search'
kwargs['useAdminAccess'] = True
kwargs['query'] = 'customer = "customers/my_customer" AND spaceType = "SPACE"'
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg in {'type', 'types'}:
elif not useAdminAccess and myarg in {'type', 'types'}:
for ctype in getString(Cmd.OB_GROUP_ROLE_LIST).lower().replace(',', ' ').split():
if ctype in CHAT_SPACE_TYPE_MAP:
if pfilter:
@@ -25493,23 +25531,30 @@ def printShowChatSpaces(users):
pfilter += f'spaceType = "{CHAT_SPACE_TYPE_MAP[ctype]}"'
else:
invalidChoiceExit(ctype, CHAT_SPACE_TYPE_MAP, True)
elif useAdminAccess and myarg == 'orderby':
OBY.GetChoice()
elif useAdminAccess and myarg == 'query':
kwargs['query'] += ' AND '+ getString(Cmd.OB_QUERY)
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
if not pfilter:
pfilter = None
if not useAdminAccess:
if pfilter:
kwargs['filter'] = pfilter
else:
kwargs['orderBy'] = OBY.orderBy
i, count, users = getEntityArgument(users)
for user in users:
i += 1
user, chat, kvList = buildChatServiceObject(API.CHAT_SPACES, user, i, count)
user, chat, kvList = buildChatServiceObject(api, user, i, count)
if not chat:
continue
try:
spaces = callGAPIpages(chat.spaces(), 'list', 'spaces',
spaces = callGAPIpages(chat.spaces(), function, 'spaces',
pageMessage=_getChatPageMessage(Ent.CHAT_SPACE, user, i, count, pfilter),
bailOnInternalError=True,
throwReasons=[GAPI.NOT_FOUND, GAPI.INVALID_ARGUMENT, GAPI.INTERNAL_ERROR,
GAPI.PERMISSION_DENIED, GAPI.FAILED_PRECONDITION],
pageSize=CHAT_PAGE_SIZE, filter=pfilter)
pageSize=CHAT_PAGE_SIZE, **kwargs)
except (GAPI.notFound, GAPI.invalidArgument, GAPI.internalError,
GAPI.permissionDenied, GAPI.failedPrecondition) as e:
exitIfChatNotConfigured(chat, kvList, str(e), i, count)
@@ -36146,32 +36191,36 @@ def doCalendarsPrintShowACLs(calIds):
EVENT_TYPE_DEFAULT = 'default'
EVENT_TYPE_FOCUSTIME = 'focusTime'
EVENT_TYPE_FROMGMAIL = 'fromGmail'
EVENT_TYPE_OUTOFOFFICE = 'outOfOffice'
EVENT_TYPE_WORKINGLOCATION = 'workingLocation'
EVENT_TYPES_CHOICE_MAP = {
'default': EVENT_TYPE_DEFAULT,
'focustime': EVENT_TYPE_FOCUSTIME,
'fromgmail': EVENT_TYPE_FROMGMAIL,
'outofoffice': EVENT_TYPE_OUTOFOFFICE,
'workinglocation': EVENT_TYPE_WORKINGLOCATION,
}
EVENT_TYPE_DEFAULT_PROPERTIES_MAP = {
EVENT_TYPE_DEFAULT: {'eventType': EVENT_TYPE_DEFAULT},
EVENT_TYPE_FOCUSTIME: {'eventType': EVENT_TYPE_FOCUSTIME,
'focusTimeProperties': {'autoDeclineMode': 'declineNone', 'declineMessage': 'Declined', 'chatStatus': 'doNotDisturb'},
'transparency':'opaque'},
EVENT_TYPE_OUTOFOFFICE: {'eventType': EVENT_TYPE_OUTOFOFFICE,
'outOfOfficeProperties': {'autoDeclineMode': 'declineOnlyNewConflictingInvitations', 'declineMessage': 'Declined'},
'transparency':'opaque'},
EVENT_TYPE_WORKINGLOCATION: {'eventType': EVENT_TYPE_WORKINGLOCATION,
'workingLocationProperties': {},
'visibility': 'public', 'transparency':'transparent'},
}
#EVENT_TYPE_DEFAULT_PROPERTIES_MAP = {
# EVENT_TYPE_DEFAULT: {'eventType': EVENT_TYPE_DEFAULT},
# EVENT_TYPE_FOCUSTIME: {'eventType': EVENT_TYPE_FOCUSTIME,
# 'focusTimeProperties': {'autoDeclineMode': 'declineNone', 'declineMessage': 'Declined', 'chatStatus': 'doNotDisturb'},
# 'transparency':'opaque'},
# EVENT_TYPE_FROMGMAIL: {'eventType': EVENT_TYPE_FROMGMAIL},
# EVENT_TYPE_OUTOFOFFICE: {'eventType': EVENT_TYPE_OUTOFOFFICE,
# 'outOfOfficeProperties': {'autoDeclineMode': 'declineOnlyNewConflictingInvitations', 'declineMessage': 'Declined'},
# 'transparency':'opaque'},
# EVENT_TYPE_WORKINGLOCATION: {'eventType': EVENT_TYPE_WORKINGLOCATION,
# 'workingLocationProperties': {},
# 'visibility': 'public', 'transparency':'transparent'},
# }
EVENT_TYPE_PROPERTIES_NAME_MAP = {
EVENT_TYPE_DEFAULT: None,
EVENT_TYPE_FOCUSTIME: f'{EVENT_TYPE_FOCUSTIME}Properties',
EVENT_TYPE_FROMGMAIL: None,
EVENT_TYPE_OUTOFOFFICE: f'{EVENT_TYPE_OUTOFOFFICE}Properties',
EVENT_TYPE_WORKINGLOCATION: f'{EVENT_TYPE_WORKINGLOCATION}Properties',
}
@@ -36179,6 +36228,7 @@ EVENT_TYPE_PROPERTIES_NAME_MAP = {
EVENT_TYPE_ENTITY_MAP = {
EVENT_TYPE_DEFAULT: None,
EVENT_TYPE_FOCUSTIME: Ent.EVENT_FOCUSTIME,
EVENT_TYPE_FROMGMAIL: None,
EVENT_TYPE_OUTOFOFFICE: Ent.EVENT_OUTOFOFFICE,
EVENT_TYPE_WORKINGLOCATION: Ent.EVENT_WORKINGLOCATION,
}
@@ -41875,7 +41925,7 @@ def verifyUserPrimaryEmail(cd, user, createIfNotFound, i, count):
# [updateprimaryemail <RegularExpression> <EmailReplacement>]
# [updateoufromgroup <CSVFileInput> [keyfield <FieldName>] [datafield <FieldName>]]
# [immutableous <OrgUnitEntity>]|
# [clearschema <SchemaName>] [clearschema <SchemaName>.<FieldName>]
# [clearschema <SchemaName>|<SchemaNameField>]
# [createifnotfound] [notfoundpassword (random [<Integer>])|blocklogin|<Password>]
# (groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
# [alias|aliases <EmailAddressList>]
@@ -42481,6 +42531,44 @@ def _formatLanguagesList(propertyValue, delimiter):
languages.append(lang)
return delimiter.join(languages)
def _initSchemaParms(projection):
return {'projection': projection, 'customFieldMask': None, 'selectedSchemaFields': {}}
def _getSchemaNameList(schemaParms):
customFieldMask = getString(Cmd.OB_SCHEMA_NAME_LIST).replace(' ', ',')
if customFieldMask.lower() == 'all':
schemaParms['projection'] = 'full'
schemaParms['customFieldMask'] = None
schemaParms['selectedSchemaFields'] = {}
else:
schemaParms['projection'] = 'custom'
customFieldMaskList = []
for schemaField in customFieldMask.split(','):
if schemaField.find('.') == -1:
customFieldMaskList.append(schemaField)
else:
schemaName, fieldName = schemaField.split('.', 1)
customFieldMaskList.append(schemaName)
schemaParms['selectedSchemaFields'] .setdefault(schemaName, set())
schemaParms['selectedSchemaFields'][schemaName].add(fieldName)
schemaParms['customFieldMask'] = ','.join(customFieldMaskList)
def _filterSchemaFields(userEntity, schemaParms):
schemas = userEntity.pop('customSchemas', None)
if schemas is None:
return
customSchemas = {}
for schema in sorted(schemas):
if schema in schemaParms['selectedSchemaFields']:
for field, value in sorted(iter(schemas[schema].items())):
if field not in schemaParms['selectedSchemaFields'][schema]:
continue
customSchemas.setdefault(schema, {})
customSchemas[schema][field] = value
else:
customSchemas[schema] = schemas[schema]
userEntity['customSchemas'] = customSchemas
def infoUsers(entityList):
def printUserCIGroupMap(parent, group_name_mappings, seen_group_count, edges, direction):
for a_parent, a_child in edges:
@@ -42516,8 +42604,7 @@ def infoUsers(entityList):
getAliases = getBuildingNames = getCIGroupsTree = getGroups = getLicenses = getSchemas = not GC.Values[GC.QUICK_INFO_USER]
getGroupsTree = False
FJQC = FormatJSONQuoteChar()
projection = 'full'
customFieldMask = None
schemaParms = _initSchemaParms('full')
viewType = 'admin_view'
fieldsList = []
groups = []
@@ -42540,19 +42627,13 @@ def infoUsers(entityList):
getLicenses = myarg in {'licenses', 'licences'}
elif myarg == 'noschemas':
getSchemas = False
projection = 'basic'
schemaParms = _initSchemaParms('basic')
elif myarg == 'allschemas':
getSchemas = True
projection = 'full'
schemaParms = _initSchemaParms('full')
elif myarg in {'custom', 'schemas', 'customschemas'}:
getSchemas = True
customFieldMask = getString(Cmd.OB_SCHEMA_NAME_LIST).replace(' ', ',')
if customFieldMask.lower() == 'all':
customFieldMask = None
projection = 'full'
else:
projection = 'custom'
fieldsList.append('customSchemas')
_getSchemaNameList(schemaParms)
elif myarg in {'products', 'product'}:
skus = SKU.convertProductListToSKUList(getGoogleProductList())
elif myarg in {'sku', 'skus'}:
@@ -42569,6 +42650,8 @@ def infoUsers(entityList):
FJQC.GetFormatJSON(myarg)
if fieldsList:
fieldsList.append('primaryEmail')
if getSchemas:
fieldsList.append('customSchemas')
if getAliases:
fieldsList.extend(['aliases', 'nonEditableAliases'])
fields = getFieldsFromFieldsList(fieldsList)
@@ -42585,7 +42668,8 @@ def infoUsers(entityList):
try:
user = callGAPI(cd.users(), 'get',
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND],
userKey=userEmail, projection=projection, customFieldMask=customFieldMask, viewType=viewType, fields=fields)
userKey=userEmail, projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
viewType=viewType, fields=fields)
groups = []
memberships = []
if getGroups or getGroupsTree:
@@ -42817,25 +42901,29 @@ def infoUsers(entityList):
typeKey = userProperty[UProp.TYPE_KEYWORDS][UProp.PTKW_ATTR_TYPE_KEYWORD]
typeCustomValue = userProperty[UProp.TYPE_KEYWORDS][UProp.PTKW_ATTR_TYPE_CUSTOM_VALUE]
customTypeKey = userProperty[UProp.TYPE_KEYWORDS][UProp.PTKW_ATTR_CUSTOMTYPE_KEYWORD]
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], None])
Ind.Increment()
for schema in sorted(propertyValue):
printKeyValueList(['Schema', schema])
if schemaParms['selectedSchemaFields']:
_filterSchemaFields(user, schemaParms)
propertyValue = user[up]
if propertyValue:
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], None])
Ind.Increment()
for field in propertyValue[schema]:
if isinstance(propertyValue[schema][field], list):
printKeyValueList([field])
Ind.Increment()
for an_item in propertyValue[schema][field]:
_showType(an_item, typeKey, typeCustomValue, customTypeKey, defaultType='work')
for schema in sorted(propertyValue):
printKeyValueList(['Schema', schema])
Ind.Increment()
for field in propertyValue[schema]:
if isinstance(propertyValue[schema][field], list):
printKeyValueList([field])
Ind.Increment()
printKeyValueList(['value', an_item['value']])
for an_item in propertyValue[schema][field]:
_showType(an_item, typeKey, typeCustomValue, customTypeKey, defaultType='work')
Ind.Increment()
printKeyValueList(['value', an_item['value']])
Ind.Decrement()
Ind.Decrement()
Ind.Decrement()
else:
printKeyValueList([field, propertyValue[schema][field]])
else:
printKeyValueList([field, propertyValue[schema][field]])
Ind.Decrement()
Ind.Decrement()
Ind.Decrement()
if getAliases:
for up in ['aliases', 'nonEditableAliases']:
propertyValue = user.get(up, [])
@@ -42900,8 +42988,8 @@ def infoUsers(entityList):
GAPI.badRequest, GAPI.backendError, GAPI.systemError) as e:
entityActionFailedWarning([Ent.USER, userEmail], str(e), i, count)
except (GAPI.invalidInput, GAPI.invalidMember) as e:
if customFieldMask:
entityActionFailedWarning([Ent.USER, userEmail], invalidUserSchema(customFieldMask), i, count)
if schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, userEmail], invalidUserSchema(schemaParms['customFieldMask']), i, count)
else:
entityActionFailedWarning([Ent.USER, userEmail], str(e), i, count)
@@ -43016,6 +43104,8 @@ def doPrintUsers(entityList=None):
phoneNumber = phone.get('value', '')
if phoneNumber.startswith('+'):
phone['value'] = "'"+phoneNumber
if schemaParms['selectedSchemaFields']:
_filterSchemaFields(userEntity, schemaParms)
if printOptions['getGroupFeed']:
printGettingAllEntityItemsForWhom(Ent.GROUP_MEMBERSHIP, userEmail, i, count)
try:
@@ -43086,8 +43176,8 @@ def doPrintUsers(entityList=None):
entityUnknownWarning(Ent.USER, ri[RI_ITEM], int(ri[RI_J]), int(ri[RI_JCOUNT]))
else:
_writeUserEntity({'primaryEmail': ri[RI_ITEM], showValidColumn: False})
elif (reason == GAPI.INVALID_INPUT) and customFieldMask:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], invalidUserSchema(customFieldMask), int(ri[RI_J]), int(ri[RI_JCOUNT]))
elif (reason == GAPI.INVALID_INPUT) and schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], invalidUserSchema(schemaParms['customFieldMask']), int(ri[RI_J]), int(ri[RI_JCOUNT]))
elif reason not in GAPI.DEFAULT_RETRY_REASONS:
errMsg = getHTTPError(_PRINT_USER_REASON_TO_MESSAGE_MAP, http_status, reason, message)
printKeyValueList([ERROR, errMsg])
@@ -43096,7 +43186,8 @@ def doPrintUsers(entityList=None):
try:
user = callGAPI(cd.users(), 'get',
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND, GAPI.RATE_LIMIT_EXCEEDED],
userKey=ri[RI_ITEM], projection=projection, customFieldMask=customFieldMask, viewType=viewType, fields=fields)
userKey=ri[RI_ITEM], projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
viewType=viewType, fields=fields)
_printUser(user, int(ri[RI_J]), int(ri[RI_JCOUNT]))
except (GAPI.userNotFound, GAPI.resourceNotFound):
if not showValidColumn:
@@ -43107,8 +43198,8 @@ def doPrintUsers(entityList=None):
GAPI.badRequest, GAPI.backendError, GAPI.systemError, GAPI.rateLimitExceeded) as e:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
except GAPI.invalidInput as e:
if customFieldMask:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], invalidUserSchema(customFieldMask), int(ri[RI_J]), int(ri[RI_JCOUNT]))
if schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], invalidUserSchema(schemaParms['customFieldMask']), int(ri[RI_J]), int(ri[RI_JCOUNT]))
else:
entityActionFailedWarning([Ent.USER, ri[RI_ITEM]], str(e), int(ri[RI_J]), int(ri[RI_JCOUNT]))
@@ -43132,9 +43223,8 @@ def doPrintUsers(entityList=None):
lic = None
skus = None
maxResults = GC.Values[GC.USER_MAX_RESULTS]
projection = 'basic'
schemaParms = _initSchemaParms('basic')
projectionSet = False
customFieldMask = None
oneLicensePerRow = quotePlusPhoneNumbers = showDeleted = False
aliasMatchPattern = isSuspended = orgUnitPath = orgUnitPathLower = orderBy = sortOrder = None
viewType = 'admin_view'
@@ -43161,27 +43251,22 @@ def doPrintUsers(entityList=None):
orderBy, sortOrder = getOrderBySortOrder(USERS_ORDERBY_CHOICE_MAP)
elif myarg == 'userview':
viewType = 'domain_public'
elif myarg in {'allfields', 'basic'}:
schemaParms = _initSchemaParms('basic')
projectionSet = printOptions['sortHeaders'] = True
fieldsList = []
elif myarg == 'full':
if schemaParms['projection'] != 'custom':
schemaParms = _initSchemaParms(myarg)
projectionSet = printOptions['sortHeaders'] = True
fieldsList = []
elif myarg in {'custom', 'schemas', 'customschemas'}:
if not fieldsList:
fieldsList = ['primaryEmail']
fieldsList.append('customSchemas')
customFieldMask = getString(Cmd.OB_SCHEMA_NAME_LIST).replace(' ', ',')
if customFieldMask.lower() == 'all':
customFieldMask = None
projection = 'full'
else:
projection = 'custom'
projectionSet = True
_getSchemaNameList(schemaParms)
if fieldsList:
fieldsList.append('customSchemas')
elif myarg == 'delimiter':
delimiter = getCharacter()
elif myarg in PROJECTION_CHOICE_MAP:
projection = myarg
projectionSet = printOptions['sortHeaders'] = True
fieldsList = []
elif myarg == 'allfields':
projection = 'basic'
projectionSet = printOptions['sortHeaders'] = True
fieldsList = []
elif myarg == 'sortheaders':
printOptions['sortHeaders'] = getBoolean()
elif myarg == 'scalarsfirst':
@@ -43284,7 +43369,8 @@ def doPrintUsers(entityList=None):
GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
query=query, fields=fields,
showDeleted=showDeleted, orderBy=orderBy, sortOrder=sortOrder, viewType=viewType,
projection=projection, customFieldMask=customFieldMask, maxResults=maxResults, **kwargs)
projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
maxResults=maxResults, **kwargs)
for users in feed:
if showItemCountOnly:
itemCount += len(users)
@@ -43309,12 +43395,12 @@ def doPrintUsers(entityList=None):
entityActionFailedWarning([Ent.USER, None, Ent.DOMAIN, kwargs['domain']], Msg.NOT_FOUND)
continue
except (GAPI.invalidOrgunit, GAPI.invalidInput) as e:
if query and not customFieldMask:
if query and not schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, None], invalidQuery(query))
elif customFieldMask and not query:
entityActionFailedWarning([Ent.USER, None], invalidUserSchema(customFieldMask))
elif query and customFieldMask:
entityActionFailedWarning([Ent.USER, None], f'{invalidQuery(query)} or {invalidUserSchema(customFieldMask)}')
elif schemaParms['customFieldMask'] and not query:
entityActionFailedWarning([Ent.USER, None], invalidUserSchema(schemaParms['customFieldMask']))
elif query and schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, None], f'{invalidQuery(query)} or {invalidUserSchema(schemaParms["customFieldMask"])}')
else:
entityActionFailedWarning([Ent.USER, None], str(e))
continue
@@ -43335,7 +43421,9 @@ def doPrintUsers(entityList=None):
jcount = len(entityList)
fields = getFieldsFromFieldsList(fieldsList)
if GC.Values[GC.BATCH_SIZE] > 1 and jcount > 1:
svcargs = dict([('userKey', None), ('fields', fields), ('projection', projection), ('customFieldMask', customFieldMask), ('viewType', viewType)]+GM.Globals[GM.EXTRA_ARGS_LIST])
svcargs = dict([('userKey', None), ('fields', fields),
('projection', schemaParms['projection']), ('customFieldMask', schemaParms['customFieldMask']),
('viewType', viewType)]+GM.Globals[GM.EXTRA_ARGS_LIST])
method = getattr(cd.users(), 'get')
dbatch = cd.new_batch_http_request(callback=_callbackPrintUser)
bcount = 0
@@ -43360,7 +43448,8 @@ def doPrintUsers(entityList=None):
try:
user = callGAPI(cd.users(), 'get',
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND, GAPI.RATE_LIMIT_EXCEEDED],
userKey=userEmail, projection=projection, customFieldMask=customFieldMask, viewType=viewType, fields=fields)
userKey=userEmail, projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
viewType=viewType, fields=fields)
_printUser(user, j, jcount)
except (GAPI.userNotFound, GAPI.resourceNotFound):
if not showValidColumn:
@@ -43371,8 +43460,8 @@ def doPrintUsers(entityList=None):
GAPI.badRequest, GAPI.backendError, GAPI.systemError, GAPI.rateLimitExceeded) as e:
entityActionFailedWarning([Ent.USER, userEmail], str(e), j, jcount)
except GAPI.invalidInput as e:
if customFieldMask:
entityActionFailedWarning([Ent.USER, userEmail], invalidUserSchema(customFieldMask), j, jcount)
if schemaParms['customFieldMask']:
entityActionFailedWarning([Ent.USER, userEmail], invalidUserSchema(schemaParms['customFieldMask']), j, jcount)
else:
entityActionFailedWarning([Ent.USER, userEmail], str(e), j, jcount)
# The only field specified was primaryEmail, just list the users/count the domains
@@ -43446,7 +43535,7 @@ def doPrintUserList(entityList):
def doPrintUserCountsByOrgUnit():
def _printUserCounts(title, v):
csvPF.WriteRow({'orgUnitPath': title, 'archived': v['archived'], 'active': v['active'], 'suspended': v['suspended'], 'total': v['total']})
USER_COUNTS_FIELDS = ['archived', 'active', 'suspended', 'total']
USER_COUNTS_ZERO_FIELDS = {'archived': 0, 'active': 0, 'suspended': 0, 'total': 0}
cd = buildGAPIObject(API.DIRECTORY)
@@ -51424,6 +51513,12 @@ DRIVESETTINGS_SCALAR_FIELDS = [
'usageInDriveTrash',
]
DRIVESETTINGS_USAGE_BYTES_FIELDS = {
'usage': 'usageBytes',
'usageInDrive': 'usageInDriveBytes',
'usageInDriveTrash': 'usageInDriveTrashBytes',
}
def _showSharedDriveThemeSettings(themes):
Ind.Increment()
for theme in themes:
@@ -51435,9 +51530,11 @@ def _showSharedDriveThemeSettings(themes):
Ind.Decrement()
# gam <UserTypeEntity> print drivesettings [todrive <ToDriveAttribute>*]
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)] [delimiter <Character>]
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
# [delimiter <Character>] [showusagebytes]
# gam <UserTypeEntity> show drivesettings
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)] [delimiter <Character>]
# [allfields|<DriveSettingsFieldName>*|(fields <DriveSettingsFieldNameList>)]
# [delimiter <Character>] [showusagebytes]
def printShowDriveSettings(users):
def _showFormats(title):
if title in fieldsList and title in feed:
@@ -51473,6 +51570,7 @@ def printShowDriveSettings(users):
csvPF = CSVPrintFile(['email'], ['email']+DRIVESETTINGS_SCALAR_FIELDS) if Act.csvFormat() else None
fieldsList = []
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
showUsageBytes = False
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
@@ -51483,6 +51581,8 @@ def printShowDriveSettings(users):
fieldsList.extend(DRIVESETTINGS_FIELDS_CHOICE_MAP.values())
elif getFieldsList(myarg, DRIVESETTINGS_FIELDS_CHOICE_MAP, fieldsList):
pass
elif myarg == 'showusagebytes':
showUsageBytes = True
else:
unknownArgumentExit()
if not fieldsList:
@@ -51507,7 +51607,10 @@ def printShowDriveSettings(users):
else:
feed['limit'] = 'UNLIMITED'
for setting in ['usage', 'usageInDrive', 'usageInDriveTrash']:
feed[setting] = formatFileSize(int(feed['storageQuota'].get(setting, '0')))
uval = int(feed['storageQuota'].get(setting, '0'))
feed[setting] = formatFileSize(uval)
if showUsageBytes:
feed[DRIVESETTINGS_USAGE_BYTES_FIELDS[setting]] = uval
if 'rootFolderId' in fieldsList:
feed['rootFolderId'] = callGAPI(drive.files(), 'get',
throwReasons=GAPI.DRIVE_USER_THROW_REASONS,
@@ -51521,6 +51624,10 @@ def printShowDriveSettings(users):
Ind.Increment()
for setting in DRIVESETTINGS_SCALAR_FIELDS:
_showSetting(setting)
if showUsageBytes:
for title, setting in DRIVESETTINGS_USAGE_BYTES_FIELDS.items():
if title in fieldsList and setting in feed:
printKeyValueList([setting, feed[setting]])
_showSetting('folderColorPalette')
_showFormats('exportFormats')
_showFormats('importFormats')
@@ -51538,6 +51645,10 @@ def printShowDriveSettings(users):
row = {'email': user}
for setting in DRIVESETTINGS_SCALAR_FIELDS:
_addSetting(row, setting)
if showUsageBytes:
for title, setting in DRIVESETTINGS_USAGE_BYTES_FIELDS.items():
if title in fieldsList and setting in feed:
row[setting] = feed[setting]
_addSetting(row, 'folderColorPalette')
_addFormats(row, 'exportFormats')
_addFormats(row, 'importFormats')
@@ -53887,10 +53998,9 @@ def printFileList(users):
showParent = getBoolean()
elif myarg == 'nodataheaders':
nodataFields = getString(Cmd.OB_FIELD_NAME_LIST).replace('_', '').replace(',', ' ').split()
elif myarg == 'filepath':
elif myarg in {'filepath', 'fullpath'}:
filepath = True
elif myarg == 'fullpath':
filepath = fullpath = True
fullpath = myarg == 'fullpath'
elif myarg == 'folderpathonly':
folderPathOnly = getBoolean()
elif myarg == 'pathdelimiter':
@@ -68530,6 +68640,7 @@ def printShowMessagesThreads(users, entityType):
dateHeaderFormat = ''
dateHeaderConvertTimezone = False
uploadAttachmentBody = {}
addCSVData = {}
parentParms = initDriveFileAttributes()
while Cmd.ArgumentsRemaining():
myarg = getArgument()
@@ -68588,6 +68699,9 @@ def printShowMessagesThreads(users, entityType):
dateHeaderConvertTimezone = getBoolean()
if not dateHeaderFormat:
dateHeaderFormat = RFC2822_TIME_FORMAT
elif myarg == 'addcsvdata':
k = getString(Cmd.OB_STRING)
addCSVData[k] = getString(Cmd.OB_STRING, minLen=0)
else:
unknownArgumentExit()
labelMatchPattern = parameters['labelMatchPattern']
@@ -68612,11 +68726,15 @@ def printShowMessagesThreads(users, entityType):
_callbacks = {'batch': _callbackCountLabels, 'process': _countMessages if entityType == Ent.MESSAGE else _countThreads}
if show_size:
sortTitles.append('size')
if addCSVData:
sortTitles.extend(sorted(addCSVData.keys()))
csvPF.SetTitles(sortTitles)
else:
sortTitles = ['User', 'threadId', 'id']
csvPF.SetTitles(sortTitles)
sortTitles.extend(defaultHeaders)
if addCSVData:
sortTitles.extend(sorted(addCSVData.keys()))
_callbacks = {'batch': _callbackPrint, 'process': _printMessage if entityType == Ent.MESSAGE else _printThread}
csvPF.SetSortTitles(sortTitles)
else:
@@ -68648,6 +68766,8 @@ def printShowMessagesThreads(users, entityType):
if not senderMatchPattern:
_initSenderLabelsMap(user)
messageThreadCounts = {'User': user, parameters['listType']: 0, 'size': 0}
if addCSVData:
messageThreadCounts.update(addCSVData)
senderCounts = {}
if save_attachments:
_, userName, _ = splitEmailAddressOrUID(user)
@@ -68678,14 +68798,14 @@ def printShowMessagesThreads(users, entityType):
if jcount == 0:
setSysExitRC(NO_ENTITIES_FOUND_RC)
if countsOnly and not show_labels and not senderMatchPattern and not show_size:
if not csvPF:
printEntityKVList([Ent.USER, user], [parameters['listType'], jcount], i, count)
else:
csvPF.WriteRow({'User': user, parameters['listType']: jcount})
continue
if jcount == 0:
if not csvPF:
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.NO_ENTITIES_MATCHED.format(Ent.Plural(entityType)), i, count)
if not positiveCountsOnly or jcount > 0:
if not csvPF:
printEntityKVList([Ent.USER, user], [parameters['listType'], jcount], i, count)
else:
row = {'User': user, parameters['listType']: jcount}
if addCSVData:
row.update(addCSVData)
csvPF.WriteRow(row)
continue
if not csvPF and not countsOnly:
if (parameters['messageEntity'] is not None or
@@ -68741,32 +68861,46 @@ def printShowMessagesThreads(users, entityType):
if not show_size:
for label in labelsMap.values():
label.pop('size', None)
if addCSVData:
row.update(addCSVData)
csvPF.WriteRowTitles(flattenJSON({'Labels': sorted(iter(labelsMap.values()), key=lambda k: k['name'])}, flattened=row))
elif not senderMatchPattern:
if not csvPF:
if not show_size:
printEntityKVList([Ent.USER, user], [parameters['listType'], messageThreadCounts[parameters['listType']]], i, count)
v = messageThreadCounts[parameters['listType']]
if not positiveCountsOnly or v > 0:
if not csvPF:
if not show_size:
printEntityKVList([Ent.USER, user], [parameters['listType'], v], i, count)
else:
printEntityKVList([Ent.USER, user], [parameters['listType'], v, 'size', messageThreadCounts['size']], i, count)
else:
printEntityKVList([Ent.USER, user], [parameters['listType'], messageThreadCounts[parameters['listType']], 'size', messageThreadCounts['size']], i, count)
else:
if not show_size:
messageThreadCounts.pop('size', None)
csvPF.WriteRow(messageThreadCounts)
if not show_size:
messageThreadCounts.pop('size', None)
csvPF.WriteRow(messageThreadCounts)
else:
if not show_size:
if not csvPF:
for k, v in sorted(iter(senderCounts.items())):
printEntityKVList([Ent.USER, user, Ent.SENDER, k], [parameters['listType'], v['count']], i, count)
if not positiveCountsOnly or v['count'] > 0:
printEntityKVList([Ent.USER, user, Ent.SENDER, k], [parameters['listType'], v['count']], i, count)
else:
for k, v in sorted(iter(senderCounts.items())):
csvPF.WriteRow({'User': user, 'Sender': k, parameters['listType']: v['count']})
if not positiveCountsOnly or v['count'] > 0:
row = {'User': user, 'Sender': k, parameters['listType']: v['count']}
if addCSVData:
row.update(addCSVData)
csvPF.WriteRow(row)
else:
if not csvPF:
for k, v in sorted(iter(senderCounts.items())):
printEntityKVList([Ent.USER, user, Ent.SENDER, k], [parameters['listType'], v['count'], 'size', v['size']], i, count)
if not positiveCountsOnly or v['count'] > 0:
printEntityKVList([Ent.USER, user, Ent.SENDER, k], [parameters['listType'], v['count'], 'size', v['size']], i, count)
else:
for k, v in sorted(iter(senderCounts.items())):
csvPF.WriteRow({'User': user, 'Sender': k, parameters['listType']: v['count'], 'size': v['size']})
if not positiveCountsOnly or v['count'] > 0:
row = {'User': user, 'Sender': k, parameters['listType']: v['count'], 'size': v['size']}
if addCSVData:
row.update(addCSVData)
csvPF.WriteRow(row)
if csvPF:
if not countsOnly:
csvPF.RemoveTitles(['SizeEstimate', 'LabelsCount', 'Labels', 'Snippet', 'Body'])
@@ -68783,15 +68917,16 @@ def printShowMessagesThreads(users, entityType):
else:
csvPF.writeCSVfile('Message Counts' if not show_labels else 'Message Label Counts')
# gam <UserTypeEntity> print message|messages
# 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>)
# [labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
# [showlabels] [showbody] [showdate] [showsize] [showsnippet]
# [convertcrnl] [delimiter <Character>] [todrive <ToDriveAttribute>*]
# [convertcrnl] [delimiter <Character>]
# [countsonly|positivecountsonly] [useronly]
# [[attachmentnamepattern <RegularExpression>]
# [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>)
# [labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
@@ -68805,15 +68940,16 @@ def printShowMessagesThreads(users, entityType):
def printShowMessages(users):
printShowMessagesThreads(users, Ent.MESSAGE)
# gam <UserTypeEntity> print thread|threads
# 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>)
# [labelmatchpattern <RegularExpression>]
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
# [showlabels] [showbody] [showdate] [showsize] [showsnippet]
# [convertcrnl] [delimiter <Character>] [todrive <ToDriveAttribute>*]
# [convertcrnl] [delimiter <Character>]
# [countsonly|positivecountsonly] [useronly]
# [[attachmentnamepattern <RegularExpression>]
# [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>)
# [labelmatchpattern <RegularExpression>]

View File

@@ -29,8 +29,10 @@ CBCM = 'cbcm'
CHAT = 'chat'
CHAT_EVENTS = 'chatevents'
CHAT_MEMBERSHIPS = 'chatmemberships'
CHAT_MEMBERSHIPS_ADMIN = 'chatmembershipsadmin'
CHAT_MESSAGES = 'chatmessages'
CHAT_SPACES = 'chatspaces'
CHAT_SPACES_ADMIN = 'chatspacesadmin'
CHAT_SPACES_DELETE = 'chatspacesdelete'
CHROMEMANAGEMENT = 'chromemanagement'
CHROMEMANAGEMENT_APPDETAILS = 'chromemanagementappdetails'
@@ -197,8 +199,10 @@ _INFO = {
CHAT: {'name': 'Chat API', 'version': 'v1', 'v2discovery': True},
CHAT_EVENTS: {'name': 'Chat API - Events', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_MEMBERSHIPS: {'name': 'Chat API - Memberships', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_MEMBERSHIPS_ADMIN: {'name': 'Chat API - Admin Memberships', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_MESSAGES: {'name': 'Chat API - Messages', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_SPACES: {'name': 'Chat API - Spaces', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_SPACES_ADMIN: {'name': 'Chat API - Admin Spaces', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CHAT_SPACES_DELETE: {'name': 'Chat API - Spaces Delete', 'version': 'v1', 'v2discovery': True, 'mappedAPI': CHAT},
CLASSROOM: {'name': 'Classroom API', 'version': 'v1', 'v2discovery': True},
CHROMEMANAGEMENT: {'name': 'Chrome Management API', 'version': 'v1', 'v2discovery': True},
@@ -514,6 +518,10 @@ _SVCACCT_SCOPES = [
'api': CHAT_MEMBERSHIPS,
'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/chat.memberships'},
# {'name': 'Chat API - Admin Memberships',
# 'api': CHAT_MEMBERSHIPS_ADMIN,
# 'subscopes': READONLY,
# 'scope': 'https://www.googleapis.com/auth/chat.admin.memberships'},
{'name': 'Chat API - Messages',
'api': CHAT_MESSAGES,
'subscopes': READONLY,
@@ -522,6 +530,10 @@ _SVCACCT_SCOPES = [
'api': CHAT_SPACES,
'subscopes': READONLY,
'scope': 'https://www.googleapis.com/auth/chat.spaces'},
# {'name': 'Chat API - Admin Spaces',
# 'api': CHAT_SPACES_ADMIN,
# 'subscopes': READONLY,
# 'scope': 'https://www.googleapis.com/auth/chat.admin.spaces'},
{'name': 'Chat API - Spaces Delete',
'api': CHAT_SPACES_DELETE,
'subscopes': [],

View File

@@ -171,6 +171,10 @@ RATE_CHECK_COUNT = 'rccn'
RATE_CHECK_START = 'rcst'
# Section name from outer gam, passed to inner gams
SECTION = 'sect'
# Enable/disable "Getting ... " messages
SHOW_GETTINGS = 'shog'
# Enable/disable NL at end of "Got ..." messages
SHOW_GETTINGS_GOT_NL = 'shgn'
# redirected files
SAVED_STDOUT = 'svso'
STDERR = 'stde'
@@ -287,6 +291,8 @@ Globals = {
RATE_CHECK_COUNT: 0,
RATE_CHECK_START: 0,
SECTION: None,
SHOW_GETTINGS: True,
SHOW_GETTINGS_GOT_NL: False,
SAVED_STDOUT: None,
STDERR: {},
STDOUT: {},

View File

@@ -92,6 +92,10 @@ _SKUS = {
'product': '101047', 'aliases': ['gwlabs', 'workspacelabs'], 'displayName': 'Google Workspace Labs'},
'1010470003': {
'product': '101047', 'aliases': ['geminibiz'], 'displayName': 'Gemini Business'},
'1010470006': {
'product': '101047', 'aliases': ['aisecurity'], 'displayName': 'AI Security'},
'1010470007': {
'product': '101047', 'aliases': ['aimeetingsandmessaging'], 'displayName': 'AI Meetings and Messaging'},
'1010490001': {
'product': '101049', 'aliases': ['eeu'], 'displayName': 'Endpoint Education Upgrade'},
'Google-Apps': {