Multiple updates

This commit is contained in:
Ross Scroggs
2023-11-03 08:52:05 -07:00
parent 3facd05a94
commit 4a199c7b6f
12 changed files with 341 additions and 250 deletions

View File

@ -137,7 +137,7 @@ gam csv UpdateBrowsers.csv gam update browser ~deviceId updatenotes "~~notes~~\n
```
gam move browsers ou|org|orgunit <OrgUnitPath>
((ids <DeviceIDList>) |
(queries <QueryBrowserList> [querytime.* <Time>]) |
(queries <QueryBrowserList> [querytime<String> <Time>]) |
(browserou <OrgUnitItem>) | (browserous <OrgUnitList>) |
<FileSelector> | <CSVFileSelector>)
[batchsize <Integer>]
@ -178,7 +178,7 @@ By default, Gam displays the information as an indented list of keys and values:
```
gam show browsers
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
[formatjson]
@ -205,7 +205,7 @@ The characters following `querytime` can be any combination of lowercase letters
```
gam print browsers [todrive <ToDriveAttribute>*]
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
[sortheaders] [formatjson [quotechar <Character>]]
@ -372,7 +372,7 @@ gam revoke browsertoken <BrowserTokenPermanentID>
```
gam show browsertokens
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken)|(queries <QueryBrowserTokenList>)))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserTokenFieldName> [ascending|descending]]
[allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
[formatjson]
@ -395,7 +395,7 @@ By default, Gam displays the information as an indented list of keys and values:
```
gam print browsertokens [todrive <ToDriveAttribute>*]
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken)|(queries <QueryBrowserTokenList>)))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserTokenFieldName> [ascending|descending]]
[allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
[sortheaders] [formatjson [quotechar <Character>]]

View File

@ -538,7 +538,7 @@ gam <CrOSTypeEntity> print cros
```
gam print cros [todrive <ToDriveAttribute>*]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
[orderby <CrOSOrderByFieldName> [ascending|descending]]
@ -691,7 +691,7 @@ gam <CrOSTypeEntity> show count
```
gam print crosactivity [todrive <ToDriveAttribute>*]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
[orderby <CrOSOrderByFieldName> [ascending|descending]]

View File

@ -167,7 +167,7 @@ These two/three columns are used to match current company devices against the CS
If `preview` is specified, the operations that would be performed are previewed but are not performed; use this to test.
```
gam sync devices
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
csvfile <FileName>
(devicetype_column <String>)|(static_devicetype <DeviceType>)
(serialnumber_column <String>)
@ -190,7 +190,7 @@ By default, Gam displays the information as an indented list of keys and values.
## Print devices
```
gam print devices [todrive <ToDriveAttribute>*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
<DeviceFieldName>* [fields <DeviceFieldNameList>] [userfields <DeviceUserFieldNameList>]
[orderby <DeviceOrderByFieldName> [ascending|descending]]
[all|company|personal|nocompanydevices|nopersonaldevices]
@ -266,7 +266,7 @@ gam info deviceuser <DeviceUserEntity>
```
gam print deviceusers [todrive <ToDriveAttribute>*]
[select <DeviceID>]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
<DeviceUserFieldName>* [fields <DeviceUserFieldNameList>]
[orderby <DeviceOrderByFieldName> [ascending|descending]]
[formatjson [quotechar <Character>]]

View File

@ -10,6 +10,27 @@ 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
Updated `gam <UserTypeEntity> import|insert message` to allow `replace <Tag> <UserReplacement>` as documented.
### 6.65.05
Updated `gam info users <UserTypeEntity>` to make option `grouptree` effective when used
with option `formatjson`.
Added option `[formatjson [quotechar <Character>]]]`
to these commands so that event details are displayed in CSV format.
```
gam print|show grouptree <GroupEntity>
gam <UserTypeEntity> print|show grouptree
```
Added option `querytime<String> <Date>` to all commands that process messages.
For example, you can identify all messages within a particular time period, in this case, all messages unread
in the last 30 days.
```
gam user user@domain.com print messages querytime30d -30d query "after:#querytime30d# is:unread"
```
### 6.65.04
Fixed bug where license SKU `1010020031` (Google Workspace Frontline Standard) was improperly entered making it unusable;

View File

@ -334,7 +334,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.65.04 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.65.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.10.8 64-bit final
MacOS High Sierra 10.13.6 x86_64
@ -1002,7 +1002,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.65.04 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.65.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.0 64-bit final
Windows-10-10.0.17134 AMD64

View File

@ -100,7 +100,7 @@ By default, Gam displays the information as an indented list of keys and values.
## Print mobile devices
```
gam print mobile [todrive <ToDriveAttribute>*]
[(query <QueryMobile>)|(queries <QueryMobileList>) (querytime.* <Time>)*]
[(query <QueryMobile>)|(queries <QueryMobileList>) (querytime<String> <Time>)*]
[orderby <MobileOrderByFieldName> [ascending|descending]]
[basic|full|allfields] <MobileFieldName>* [fields <MobileFieldNameList>]
[delimiter <Character>] [appslimit <Number>] [oneappperrow] [listlimit <Number>]

View File

@ -173,6 +173,22 @@
(gcsdoc|gcshtml <StorageBucketObjectName>)|
(emlfile <FileName>)
```
## Message queries with dates
```
query <QueryGmail> [querytime<String> <Date>]*
```
* `query "xxx"` - ` xxx` is appended to the current query; you can repeat the query argument to build up a longer query.
Use the `querytime<String> <Date>` option to allow dates, usually relative, to be substituted into the `query <QueryGmail>` option.
The `querytime<String> <Date>` value replaces the string `#querytime<String>#` in any queries.
The characters following `querytime` can be any combination of lowercase letters and numbers. This is most useful in scripts
where you can specify a relative date without having to change the script.
For example, query for messages from moree than 5 years ago:
```
querytime5years -5y query "before:#querytime5years#"
```
## Subject and label queries
Using a query to select messages by subject or label requires some attention in order to achieve the desired effect.
* https://support.google.com/mail/answer/7190
@ -316,7 +332,7 @@ Your command line will have: `embedimage file1.jpg image1` embedimage file2.jpg
## Archive messages
```
gam <UserTypeEntity> archive messages <GroupItem>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
```
@ -328,10 +344,10 @@ See below for message selection.
Export messages in EML format.
```
gam <UserTypeEntity> export message|messages
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
[targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
gam <UserTypeEntity> export thread|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
[targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
```
@ -354,11 +370,11 @@ See below for message selection.
## Forward messages/threads
```
gam <UserTypeEntity> forward message|messages recipient|to <RecipientEntity>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
[subject <String>]
gam <UserTypeEntity> forward thread|threads recipient|to <RecipientEntity>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
[subject <String>]
```
@ -372,27 +388,27 @@ See below for message selection.
## Manage messages/threads
```
gam <UserTypeEntity> delete messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> modify messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
(addlabel <LabelName>)* (removelabel <LabelName>)*
gam <UserTypeEntity> spam messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> trash messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> untrash messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
```
### Manage a specific set of messages
* `ids <MessageIDEntity>` - A list of message ids
### Manage a selected set of messages
* `((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+` - Criteria to select messages
* `((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+` - Criteria to select messages
* `max_to_xxx` - Limit the number of messages that will be processed; use a value of 0 for no limit
* `doit` - No messages are processed unless you specify `doit`. By not specifying `doit`, you can preview the messages selected to verify that the results match your expectations.
@ -439,7 +455,7 @@ gam config auto_batch_min 1 groups_inde EastOffice delete message query "rfc822m
## Display messages/threads
```
gam <UserTypeEntity> show messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])*
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
[quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
[labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
[countsonly|positivecountsonly] [useronly]
@ -449,7 +465,7 @@ gam <UserTypeEntity> show messages|threads
[saveattachments [attachmentnamepattern <RegularExpression>]]
[targetfolder <FilePath>] [overwrite [<Boolean>]]
gam <UserTypeEntity> print messages|threads [todrive <ToDriveAttribute>*]
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])*
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
[quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <MessageIDEntity>)
[labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
[countsonly|positivecountsonly] [useronly]
@ -467,7 +483,7 @@ By default, Gam displays all messages.
* `ids <MessageIDEntity>` - A list of message ids
## Display a selected set of messages
* `((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+` - Criteria to select messages
* `((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+` - Criteria to select messages
* `max_to_xxx` - Limit the number of messages that will be displayed
* `includespamtrash` - Include messages in the Spam and Trash folders
* `labelmatchpattern <RegularExpression>` - Only display messages with some label that matches `<RegularExpression>`

View File

@ -4,7 +4,7 @@
Print the current version of Gam with details
```
gam version
GAMADV-XTD3 6.65.04 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.65.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.0 64-bit final
MacOS Monterey 12.7 x86_64
@ -16,7 +16,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.65.04 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.65.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.0 64-bit final
MacOS Monterey 12.7 x86_64
@ -28,7 +28,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.65.04 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
GAMADV-XTD3 6.65.05 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.0 64-bit final
MacOS Monterey 12.7 x86_64
@ -65,7 +65,7 @@ MacOS High Sierra 10.13.6 x86_64
Path: /Users/Admin/bin/gamadv-xtd3
Version Check:
Current: 5.35.08
Latest: 6.65.04
Latest: 6.65.05
echo $?
1
```
@ -73,7 +73,7 @@ echo $?
Print the current version number without details
```
gam version simple
6.65.04
6.65.05
```
In Linux/MacOS you can do:
```
@ -83,7 +83,7 @@ echo $VER
Print the current version of Gam and address of this Wiki
```
gam help
GAM 6.65.04 - https://github.com/taers232c/GAMADV-XTD3
GAM 6.65.05 - https://github.com/taers232c/GAMADV-XTD3
Ross Scroggs <ross.scroggs@gmail.com>
Python 3.12.0 64-bit final
MacOS Monterey 12.7 x86_64

View File

@ -1906,7 +1906,7 @@ gam delete browser <DeviceID>
gam update browser <BrowserEntity> <BrowserAttibute>+
gam move browsers ou|org|orgunit <OrgUnitPath>
((ids <DeviceIDList>) |
(queries <QueryBrowserList> [querytime.* <Time>]) |
(queries <QueryBrowserList> [querytime<String> <Time>]) |
(browserou <OrgUnitItem>) | (browserous <OrgUnitList>) |
<FileSelector> | <CSVFileSelector>)
[batchsize <Integer>]
@ -1916,13 +1916,13 @@ gam info browser <DeviceID>
[formatjson]
gam show browsers
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
[formatjson]
gam print browsers [todrive <ToDriveAttribute>*]
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser>)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserOrderByFieldName> [ascending|descending]]
[basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
[sortheaders]
@ -1950,13 +1950,13 @@ gam revoke browsertoken <BrowserTokenPermanentID>
gam show browsertokens
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken>)|(queries <QueryBrowserTokenList>)))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserTokenFieldName> [ascending|descending]]
[allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
[formatjson]
gam print browsertokens [todrive <ToDriveAttribute>*]
([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken>)|(queries <QueryBrowserTokenList>)))
[querytime.* <Time>]
[querytime<String> <Time>]
[orderby <BrowserTokenFieldName> [ascending|descending]]
[allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
[sortheaders]
@ -2253,13 +2253,13 @@ gam <CrOSTypeEntity> info
[formatjson]
Print fields for selected CrOS devices; use these options to select CrOS devices:
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
If none of these options are chosen, all CrOS devices are selected.
gam print cros [todrive <ToDriveAttribute>*]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
[orderby <CrOSOrderByFieldName> [ascending|descending]]
@ -2315,13 +2315,13 @@ Show count of CrOS devices
gam <CrOSTypeEntity> show count
Print activity for selected CrOS devices; use these options to select CrOS devices:
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
If none of these options are chosen, all CrOS devices are selected.
gam print crosactivity [todrive <ToDriveAttribute>*]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
[(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
[(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
(cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
[orderby <CrOSOrderByFieldName> [ascending|descending]]
@ -3593,7 +3593,9 @@ gam print groups [todrive <ToDriveAttribute>*]
gam print grouptree <GroupEntity> [todrive <ToDriveAttribute>*]
[showparentsaslist [<Boolean>]] [delimiter <Character>]
[formatjson [quotechar <Character>]]
gam show grouptree <GroupEntity>
[formatjson]
<MembersFieldName> ::=
delivery|deliverysettings|
@ -3807,7 +3809,7 @@ gam wipe device <DeviceEntity> [removeresetlock] [doit]
gam sync devices
<CSVFileSelector>
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
(devicetype_column <String>)|(static_devicetype <DeviceType>)
(serialnumber_column <String>)
[assettag_column <String>]
@ -3820,7 +3822,7 @@ gam info device <DeviceEntity>
[nodeviceusers]
[formatjson]
gam print devices [todrive <ToDriveAttribute>*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
<DeviceFieldName>* [fields <DeviceFieldNameList>] [userfields <DeviceUserFieldNameList>]
[orderby <DeviceOrderByFieldName> [ascending|descending]]
[all|company|personal|nocompanydevices|nopersonaldevices]
@ -3845,7 +3847,7 @@ gam info deviceuser <DeviceUserEntity>
[formatjson]
gam print deviceusers [todrive <ToDriveAttribute>*]
[select <DeviceID>]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
[(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
<DeviceUserFieldName>* [fields <DeviceUserFieldNameList>]
[orderby <DeviceOrderByFieldName> [ascending|descending]]
[formatjson [quotechar <Character>]]
@ -3986,7 +3988,7 @@ gam info mobile <MobileEntity>
[basic|full|allfields] <MobileFieldName>* [fields <MobileFieldNameList>]
[formatjson]
gam print mobile [todrive <ToDriveAttribute>*]
[(query <QueryMobile>)|(queries <QueryMobileList>) (querytime.* <Time>)*]
[(query <QueryMobile>)|(queries <QueryMobileList>) (querytime<String> <Time>)*]
[orderby <MobileOrderByFieldName> [ascending|descending]]
[basic|full|allfields] <MobileFieldName>* [fields <MobileFieldNameList>]
[delimiter <Character>] [appslimit <Number>] [oneappperrow] [listlimit <Number>]
@ -6865,43 +6867,43 @@ gam <UserTypeEntity> insert message
[deleted [<Boolean>]]
gam <UserTypeEntity> archive messages <GroupItem>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> delete messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> modify messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
(addlabel <LabelName>)* (removelabel <LabelName>)*
gam <UserTypeEntity> spam messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> trash messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> untrash messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
gam <UserTypeEntity> export message|messages
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
[targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
gam <UserTypeEntity> export thread|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
[targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
gam <UserTypeEntity> forward message|messages recipient|to <RecipientEntity>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
[quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
[subject <String>] [altcharset <String>]
gam <UserTypeEntity> forward thread|thtreads recipient|to <RecipientEntity>
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+
quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
[subject <String>] [altcharset <String>]
gam <UserTypeEntity> show messages|threads
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])*
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
[quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
[labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
[countsonly|positivecountsonly] [useronly]
@ -6911,7 +6913,7 @@ gam <UserTypeEntity> show messages|threads
[saveattachments [attachmentnamepattern <RegularExpression>]]
[targetfolder <FilePath>] [overwrite [<Boolean>]]
gam <UserTypeEntity> print messages|threads [todrive <ToDriveAttribute>*]
(((query <QueryGmail>) (matchlabel <LabelName>) [or|and])*
(((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])*
[quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <MessageIDEntity>)
[labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
[countsonly|positivecountsonly] [useronly]
@ -7014,9 +7016,11 @@ gam <UserTypeEntity> print grouptree [todrive <ToDriveAttribute>*]
[(domain <DomainName>)|(customerid <CustomerID>)]
[roles <GroupRoleList>]
[showparentsaslist [<Boolean>]] [delimiter <Character>]
[formatjson [quotechar <Character>]]
gam <UserTypeEntity> show grouptree
[(domain <DomainName>)|(customerid <CustomerID>)]
[roles <GroupRoleList>]
[formatjson]
gam <UserTypeEntity> print groupslist [todrive <ToDriveAttribute>*]
[(domain <DomainName>)|(customerid <CustomerID>)]
[delimiter <Character>] [quotechar <Character>]

View File

@ -1,3 +1,30 @@
7.00.00
Merged GAM-Team version
6.65.05
Updated `gam info users <UserTypeEntity>` to make option `grouptree` effective when used
with option `formatjson`.
Added option `[formatjson [quotechar <Character>]]]`
to these commands so that event details are displayed in CSV format.
```
gam print|show grouptree <GroupEntity>
gam <UserTypeEntity> print|show grouptree
```
Added option `querytime<String> <Date>` to all commands that process messages.
For example, you can identify all messages within a particular time period, in this case, all messages unread
in the last 30 days.
```
gam user user@domain.com print messages querytime30d -30d query "after:#querytime30d# is:unread"
```
Updated `gam <UserTypeEntity> import|insert message` to allow `replace <Tag> <UserReplacement>` as documented.
Updated non-owner permission handling in `gam <UserTypeEntity> copy|move drivefile`.
6.65.04
Fixed bug where license SKU `1010020031` (Google Workspace Frontline Standard) was improperly entered making it unusable;
@ -7,10 +34,6 @@ Added support for Google Workspace Additional Storage.
* ProductID - 101043
* SKUID - 1010430001 | gwas | plusstorage
7.00.00
Merged GAM-Team version
6.65.03
Fixed bug in commands that display calendar events where event start and end times were not properly displayed

View File

@ -2191,6 +2191,7 @@ def getJSON(deleteFields):
if not Cmd.ArgumentsRemaining():
missingArgumentExit(Cmd.OB_JSON_DATA)
argstr = Cmd.Current()
# argstr = Cmd.Current().replace(r'\\"', r'\"')
Cmd.Advance()
try:
if encoding == UTF8:
@ -4509,17 +4510,26 @@ def runSqliteQuery(db_file, query):
return curr.fetchone()[0]
def refreshCredentialsWithReauth(credentials):
def gcloudError():
writeStderr(f'Failed to run gcloud as {admin_email}. Please make sure it\'s setup')
e = Msg.REAUTHENTICATION_IS_NEEDED
handleOAuthTokenError(e, False)
writeStderr(Msg.CALLING_GCLOUD_FOR_REAUTH)
if 'termios' in sys.modules:
old_settings = termios.tcgetattr(sys.stdin)
admin_email = _getAdminEmail()
# First makes sure gcloud has a valid access token and thus
# should also have a valid RAPT token
try:
devnull = open(os.devnull, 'w', encoding=UTF8)
subprocess.run(['gcloud',
'auth',
'print-identity-token',
'--no-user-output-enabled'],
stderr=devnull,
check=False)
devnull.close()
# now determine gcloud's config path and token file
gcloud_path_result = subprocess.run(['gcloud',
'info',
@ -4532,14 +4542,14 @@ def refreshCredentialsWithReauth(credentials):
printBlankLine()
raise KeyboardInterrupt from e
token_path = gcloud_path_result.stdout.decode().strip()
if not token_path:
gcloudError()
token_file = f'{token_path}/access_tokens.db'
admin_email = _getAdminEmail()
try:
credentials._rapt_token = runSqliteQuery(token_file,
f'SELECT rapt_token FROM access_tokens WHERE account_id = "{admin_email}"')
except TypeError:
systemErrorExit(SYSTEM_ERROR_RC,
f'Failed to run gcloud as {admin_email}. Please make sure it\'s setup')
gcloudError()
if not credentials._rapt_token:
systemErrorExit(SYSTEM_ERROR_RC,
'Failed to retrieve reauth token from gcloud. You may need to wait until gcloud is also prompted for reauth.')
@ -4792,7 +4802,7 @@ def checkGDataError(e, service):
reason = error[0].get('reason', '')
body = error[0].get('body', '').decode(UTF8)
# First check for errors that need special handling
if reason in ['Token invalid - Invalid token: Stateless token expired', 'Token invalid - Invalid token: Token not found']:
if reason in ['Token invalid - Invalid token: Stateless token expired', 'Token invalid - Invalid token: Token not found', 'gone']:
keep_domain = service.domain
getGDataOAuthToken(service)
service.domain = keep_domain
@ -23302,7 +23312,7 @@ CROS_INDEXED_TITLES = ['activeTimeRanges', 'recentUsers', 'deviceFiles',
'cpuStatusReports', 'diskVolumeReports', 'lastKnownNetwork', 'screenshotFiles', 'systemRamFreeReports']
# gam print cros [todrive <ToDriveAttribute>*]
# [(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
# [(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
# [(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
# (cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
# gam print cros [todrive <ToDriveAttribute>*] select <CrOSTypeEntity>
@ -23628,7 +23638,7 @@ CROS_ACTIVITY_LIST_FIELDS_CHOICE_MAP = {
CROS_ACTIVITY_TIME_OBJECTS = {'createTime'}
# gam print crosactivity [todrive <ToDriveAttribute>*]
# [(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime.* <Time>]
# [(query <QueryCrOS>)|(queries <QueryCrOSList>) [querytime<String> <Time>]
# [(limittoou|cros_ou <OrgUnitItem>)|(cros_ou_and_children <OrgUnitItem>)|
# (cros_ous <OrgUnitList>)|(cros_ous_and_children <OrgUnitList>)]]
# gam print crosactivity [todrive <ToDriveAttribute>*] select <CrOSTypeEntity>
@ -24215,7 +24225,7 @@ def doInfoBrowsers():
# gam move browsers ou|org|orgunit <OrgUnitPath>
# ((ids <DeviceIDList>) |
# (queries <QueryBrowserList> [querytime.* <Time>]) |
# (queries <QueryBrowserList> [querytime<String> <Time>]) |
# (browserou <OrgUnitItem>) | (browserous <OrgUnitList>) |
# <FileSelector> | <CSVFileSelector>)
# [batchsize <Integer>]
@ -24354,13 +24364,13 @@ BROWSER_ORDERBY_CHOICE_MAP = {
# gam show browsers
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
# [querytime.* <Time>]
# [querytime<String> <Time>]
# [orderby <BrowserOrderByFieldName> [ascending|descending]]
# [basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
# [formatjson]
# gam print browsers [todrive <ToDriveAttribute>*]
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowser)|(queries <QueryBrowserList>))|(select <BrowserEntity>))
# [querytime.* <Time>]
# [querytime<String> <Time>]
# [orderby <BrowserOrderByFieldName> [ascending|descending]]
# [basic|full|allfields|annotated] <BrowserFieldName>* [fields <BrowserFieldNameList>]
# [sortheaders] [formatjson [quotechar <Character>]]
@ -24556,13 +24566,13 @@ BROWSER_TOKEN_FIELDS_CHOICE_MAP = {
# gam show browsertokens
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken)|(queries <QueryBrowserTokenList>)))
# [querytime.* <Time>]
# [querytime<String> <Time>]
# [orderby <BrowserTokenFieldName> [ascending|descending]]
# [allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
# [formatjson]
# gam print browsertokens [todrive <ToDriveAttribute>*]
# ([ou|org|orgunit|browserou <OrgUnitPath>] [(query <QueryBrowserToken)|(queries <QueryBrowserTokenList>)))
# [querytime.* <Time>]
# [querytime<String> <Time>]
# [orderby <BrowserTokenFieldName> [ascending|descending]]
# [allfields] <BrowserTokenFieldName>* [fields <BrowserTokenFieldNameList>]
# [sortheaders] [formatjson [quotechar <Character>]]
@ -26677,7 +26687,7 @@ DEVICE_MISSING_ACTION_MAP = {
# gam sync devices
# <CSVFileSelector>
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
# (devicetype_column <String>)|(static_devicetype <DeviceType>)
# (serialnumber_column <String>)
# [assettag_column <String>]
@ -26976,7 +26986,7 @@ DEVICE_ORDERBY_CHOICE_MAP = {
}
# gam print devices [todrive <ToDriveAttribute>*]
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
# <DeviceFieldName>* [fields <DeviceFieldNameList>] [userfields <DeviceUserFieldNameList>]
# [orderby <DeviceOrderByFieldName> [ascending|descending]]
# [all|company|personal|nocompanydevices|nopersonaldevices]
@ -27163,7 +27173,7 @@ def doInfoCIDeviceUser():
# gam print deviceusers [todrive <ToDriveAttribute>*]
# [select <DeviceID>]
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime.* <Time>)*]
# [(query <QueryDevice>)|(queries <QueryDeviceList>) (querytime<String> <Time>)*]
# <DeviceUserFieldName>* [fields <DevieUserFieldNameList>]
# [orderby <DeviceOrderByFieldName> [ascending|descending]]
# [formatjson [quotechar <Character>]]
@ -28948,7 +28958,7 @@ MOBILE_ORDERBY_CHOICE_MAP = {
}
# gam print mobile [todrive <ToDriveAttribute>*]
# [(query <QueryMobile>)|(queries <QueryMobileList>) [querytime.* <Time>]]
# [(query <QueryMobile>)|(queries <QueryMobileList>) [querytime<String> <Time>]]
# [orderby <MobileOrderByFieldName> [ascending|descending]]
# [basic|full|allfields] <MobileFieldName>* [fields <MobileFieldNameList>]
# [delimiter <Character>] [appslimit <Number>] [oneappperrow] [listlimit <Number>]
@ -32006,53 +32016,68 @@ def doShowGroupMembers():
if checkGroupMatchPatterns(groupEmail, group, matchPatterns):
_showGroup(groupEmail, 0)
def getGroupParents(cd, groupParents, groupEmail, groupName, kwargs):
groupParents[groupEmail] = {'name': groupName, 'parents': []}
_setUserGroupArgs(groupEmail, kwargs)
try:
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
throwReasons=GAPI.GROUP_LIST_USERKEY_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
orderBy='email', fields='nextPageToken,groups(email,name)', **kwargs)
for parentGroup in entityList:
groupParents[groupEmail]['parents'].append(parentGroup['email'])
if parentGroup['email'] not in groupParents:
getGroupParents(cd, groupParents, parentGroup['email'], parentGroup['name'], kwargs)
except (GAPI.invalidMember, GAPI.invalidInput):
badRequestWarning(Ent.GROUP, Ent.MEMBER, groupEmail)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.forbidden, GAPI.badRequest):
accessErrorExit(cd)
def showGroupParents(groupParents, groupEmail, role, i, count):
kvList = [groupEmail, f'{groupParents[groupEmail]["name"]}']
if role:
kvList.extend([Ent.Singular(Ent.ROLE), role])
printKeyValueListWithCount(kvList, i, count)
Ind.Increment()
for parentEmail in groupParents[groupEmail]['parents']:
showGroupParents(groupParents, parentEmail, None, 0, 0)
Ind.Decrement()
def addJsonGroupParents(groupParents, userGroup, groupEmail):
userGroup.setdefault('parents', [])
for parentEmail in groupParents[groupEmail]['parents']:
userGroup['parents'].append({'email': parentEmail, 'name': groupParents[parentEmail]['name'], 'parents': []})
addJsonGroupParents(groupParents, userGroup['parents'][-1], parentEmail)
def printGroupParents(groupParents, groupEmail, row, csvPF, delimiter, showParentsAsList):
if groupParents[groupEmail]['parents']:
for parentEmail in groupParents[groupEmail]['parents']:
row['parents'].append({'email': parentEmail, 'name': groupParents[parentEmail]['name']})
printGroupParents(groupParents, parentEmail, row, csvPF, delimiter, showParentsAsList)
del row['parents'][-1]
else:
if not showParentsAsList:
csvPF.WriteRowTitles(flattenJSON(row))
else:
crow = row.copy()
if 'Role' in row:
crow['Role'] = row['Role']
parents = crow.pop('parents')
crow['ParentsCount'] = len(parents)
crow['Parents'] = delimiter.join([parent['email'] for parent in parents])
crow['ParentsName'] = delimiter.join([parent['name'] for parent in parents])
csvPF.WriteRow(flattenJSON(crow))
# gam print grouptree <GroupEntity> [todrive <ToDriveAttribute>*]
# [showparentsaslist [<Boolean>]] [delimiter <Character>]
# [formatjson [quotechar <Character>]]
# gam show grouptree <GroupEntity>
# [formatjson]
def doPrintShowGroupTree():
def getGroupParents(groupEmail, groupName):
groupParents[groupEmail] = {'name': groupName, 'parents': []}
_setUserGroupArgs(groupEmail, kwargs)
try:
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
throwReasons=GAPI.GROUP_LIST_USERKEY_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
orderBy='email', fields='nextPageToken,groups(email,name)', **kwargs)
for parentGroup in entityList:
groupParents[groupEmail]['parents'].append(parentGroup['email'])
if parentGroup['email'] not in groupParents:
getGroupParents(parentGroup['email'], parentGroup['name'])
except (GAPI.invalidMember, GAPI.invalidInput):
badRequestWarning(Ent.GROUP, Ent.MEMBER, groupEmail)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.forbidden, GAPI.badRequest):
accessErrorExit(cd)
def showGroupParents(groupEmail, i, count):
printKeyValueListWithCount([f'{groupEmail}: {groupParents[groupEmail]["name"]}'], i, count)
Ind.Increment()
for parentEmail in groupParents[groupEmail]['parents']:
showGroupParents(parentEmail, 0, 0)
Ind.Decrement()
def printGroupParents(groupEmail, row):
if groupParents[groupEmail]['parents']:
for parentEmail in groupParents[groupEmail]['parents']:
row['parents'].append({'email': parentEmail, 'name': groupParents[parentEmail]['name']})
printGroupParents(parentEmail, row)
del row['parents'][-1]
else:
if not showParentsAsList:
csvPF.WriteRowTitles(flattenJSON(row))
else:
crow = {'Group': row['Group'], 'Name': row['Name']}
crow['ParentsCount'] = len(row['parents'])
crow['Parents'] = delimiter.join([parent['email'] for parent in row['parents']])
crow['ParentsName'] = delimiter.join([parent['name'] for parent in row['parents']])
csvPF.WriteRow(flattenJSON(crow))
cd = buildGAPIObject(API.DIRECTORY)
kwargs = {'customer': GC.Values[GC.CUSTOMER_ID]}
csvPF = CSVPrintFile(['Group', 'Name']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
showParentsAsList = False
entityList = getEntityList(Cmd.OB_GROUP_ENTITY)
@ -32065,8 +32090,8 @@ def doPrintShowGroupTree():
elif csvPF and myarg == 'showparentsaslist':
showParentsAsList = getBoolean()
else:
unknownArgumentExit()
if csvPF:
FJQC.GetFormatJSONQuoteChar(myarg, True)
if csvPF and not FJQC.formatJSON:
if not showParentsAsList:
csvPF.SetIndexedTitles(['parents'])
else:
@ -32074,7 +32099,7 @@ def doPrintShowGroupTree():
groupParents = {}
i = 0
count = len(entityList)
if not csvPF:
if not csvPF and not FJQC.formatJSON:
performActionNumItems(count, Ent.GROUP_TREE)
for group in entityList:
i += 1
@ -32088,12 +32113,24 @@ def doPrintShowGroupTree():
GAPI.invalid, GAPI.systemError) as e:
entityActionFailedWarning([Ent.GROUP, groupEmail], str(e), i, count)
continue
getGroupParents(groupEmail, groupName)
if not csvPF:
showGroupParents(groupEmail, i, count)
getGroupParents(cd, groupParents, groupEmail, groupName, kwargs)
if not FJQC.formatJSON:
if not csvPF:
showGroupParents(groupParents, groupEmail, None, i, count)
else:
row = {'Group': groupEmail, 'Name': groupParents[groupEmail]['name'], 'parents': []}
printGroupParents(groupParents, groupEmail, row, csvPF, delimiter, showParentsAsList)
else:
row = {'Group': groupEmail, 'Name': groupParents[groupEmail]['name'], 'parents': []}
printGroupParents(groupEmail, row)
groupInfo = {'email': groupEmail, 'name': groupParents[groupEmail]['name'], 'parents': []}
addJsonGroupParents(groupParents, groupInfo, groupEmail)
if not csvPF:
printLine(json.dumps(cleanJSON(groupInfo), ensure_ascii=False, sort_keys=True))
else:
row = flattenJSON(groupInfo)
if csvPF.CheckRowTitles(row):
csvPF.WriteRowNoFilter({'Group': groupEmail, 'Name': groupParents[groupEmail]['name'],
'JSON': json.dumps(cleanJSON(groupInfo),
ensure_ascii=False, sort_keys=True)})
if csvPF:
csvPF.writeCSVfile('Group Tree')
@ -41513,29 +41550,6 @@ def _formatLanguagesList(propertyValue, delimiter):
return delimiter.join(languages)
def infoUsers(entityList):
def getGroupParents(groupEmail, groupName):
groupParents[groupEmail] = {'name': groupName, 'parents': []}
try:
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
throwReasons=GAPI.GROUP_LIST_USERKEY_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
userKey=groupEmail, orderBy='email', fields='nextPageToken,groups(email,name)')
for parentGroup in entityList:
groupParents[groupEmail]['parents'].append(parentGroup['email'])
if parentGroup['email'] not in groupParents:
getGroupParents(parentGroup['email'], parentGroup['name'])
except (GAPI.invalidMember, GAPI.invalidInput):
badRequestWarning(Ent.GROUP, Ent.MEMBER, groupEmail)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.forbidden, GAPI.badRequest):
accessErrorExit(cd)
def showGroupParents(groupEmail):
printKeyValueList([groupEmail, f'{groupParents[groupEmail]["name"]}'])
Ind.Increment()
for parentEmail in groupParents[groupEmail]['parents']:
showGroupParents(parentEmail)
Ind.Decrement()
def printUserCIGroupMap(parent, group_name_mappings, seen_group_count, edges, direction):
for a_parent, a_child in edges:
if a_parent == parent:
@ -41662,8 +41676,14 @@ def infoUsers(entityList):
getCIGroupsTree = False
licenses = getUserLicenses(lic, user, skus) if getLicenses else []
if FJQC.formatJSON:
if getGroups:
if getGroups or getGroupsTree:
user['groups'] = groups
if getGroupsTree:
for group in user['groups']:
groupEmail = group['email']
if groupEmail not in groupParents:
getGroupParents(cd, groupParents, groupEmail, group['name'], {})
addJsonGroupParents(groupParents, group, groupEmail)
if getLicenses:
user['licenses'] = [SKU.formatSKUIdDisplayName(u_license) for u_license in licenses]
if not getAliases:
@ -41901,8 +41921,8 @@ def infoUsers(entityList):
for group in groups:
groupEmail = group['email']
if groupEmail not in groupParents:
getGroupParents(groupEmail, group['name'])
showGroupParents(groupEmail)
getGroupParents(cd, groupParents, groupEmail, group['name'], {})
showGroupParents(groupParents, groupEmail, None, 0, 0)
Ind.Decrement()
elif getCIGroupsTree:
printEntity([Ent.GROUP_MEMBERSHIP_TREE, ''])
@ -52092,7 +52112,7 @@ FILECOUNT_SUMMARY_CHOICE_MAP = {
FILECOUNT_SUMMARY_USER = 'Summary'
# gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime.* <Time>)*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime<String> <Time>)*]
# [choose <DriveFileNameEntity>|<DriveFileEntityShortcut>]
# [corpora <CorporaAttribute>]
# [select <DriveFileEntity> [selectsubquery <QueryDriveFile>]
@ -52822,7 +52842,7 @@ def printShowFilePaths(users):
csvPF.writeCSVfile('Drive File Paths')
# gam <UserTypeEntity> print filecounts [todrive <ToDriveAttribute>*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime.* <Time>)*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime<String> <Time>)*]
# [corpora <CorporaAttribute>]
# [select <SharedDriveEntity>]
# [anyowner|(showownedby any|me|others)]
@ -52832,7 +52852,7 @@ def printShowFilePaths(users):
# [excludetrashed]
# [summary none|only|plus] [summaryuser <String>] [showsize]
# gam <UserTypeEntity> show filecounts
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime.* <Time>)*]
# [((query <QueryDriveFile>) | (fullquery <QueryDriveFile>) | <DriveFileQueryShortcut>) (querytime<String> <Time>)*]
# [corpora <CorporaAttribute>]
# [select <SharedDriveEntity>]
# [anyowner|(showownedby any|me|others)]
@ -54765,7 +54785,8 @@ def _getUniqueFilename(destFilename, mimeType, targetChildren):
def _copyPermissions(drive, user, i, count, j, jcount,
entityType, fileId, fileTitle, newFileId, newFileTitle,
statistics, stat, copyMoveOptions, atTop, copyInherited, copyNonInherited):
statistics, stat, copyMoveOptions, atTop, copyInherited, copyNonInherited,
updateOwner):
def getPermissions(fid):
permissions = {}
try:
@ -54800,12 +54821,14 @@ def _copyPermissions(drive, user, i, count, j, jcount,
def isPermissionCopyable(kvList, permission):
role = permission['role']
if permission['type'] in {'group', 'user'}:
emailAddress = permission.get('emailAddress', '')
domain = ''
if copyMoveOptions['excludePermissionsFromDomains'] or copyMoveOptions['includePermissionsFromDomains']:
if permission['type'] in {'group', 'user'}:
atLoc = permission.get('emailAddress', '').find('@')
atLoc = emailAddress.find('@')
if atLoc > 0:
domain = permission['emailAddress'][atLoc+1:]
domain = emailAddress[atLoc+1:]
elif permission['type'] == 'domain':
domain = permission.get('domain', '')
if permission['inherited'] and not copyMoveOptions[copyInherited]:
@ -54813,7 +54836,13 @@ def _copyPermissions(drive, user, i, count, j, jcount,
elif not permission['inherited'] and copyMoveOptions[copyNonInherited] == COPY_NONINHERITED_PERMISSIONS_NEVER:
notCopiedMessage = 'noninherited not selected'
elif role == 'owner':
notCopiedMessage = f'role {role} copy not required/appropriate'
if emailAddress == user or copyMoveOptions['destDriveId'] or not updateOwner:
notCopiedMessage = f'role {role} copy not required/appropriate'
else:
permission['role'] = 'writer'
return True
elif updateOwner and emailAddress == user:
notCopiedMessage = 'user is now owner'
elif domain and domain in copyMoveOptions['excludePermissionsFromDomains']:
notCopiedMessage = f'domain {domain} excluded'
elif domain and copyMoveOptions['includePermissionsFromDomains'] and domain not in copyMoveOptions['includePermissionsFromDomains']:
@ -55301,7 +55330,8 @@ def copyDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, True,
'copyTopFolderInheritedPermissions',
copyFolderNonInheritedPermissions)
copyFolderNonInheritedPermissions,
True)
return (newParentId, newParentName, True)
# Merge parent folders
if copyMoveOptions['duplicateFolders'] == DUPLICATE_FOLDER_MERGE:
@ -55330,7 +55360,8 @@ def copyDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, atTop,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
copyFolderNonInheritedPermissions)
copyFolderNonInheritedPermissions,
False)
return (newFolderId, newFolderName, True)
entityActionFailedWarning(kvList+[Ent.DRIVE_FOLDER, newParentNameId], Msg.NOT_WRITABLE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_NOT_WRITABLE)
@ -55375,7 +55406,8 @@ def copyDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, False,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop])
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop],
True)
return (newFolderId, newFolderName, False)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.internalError, GAPI.storageQuotaExceeded, GAPI.teamDriveHierarchyTooDeep, GAPI.badRequest) as e:
@ -55570,7 +55602,8 @@ def copyDriveFile(users):
statistics, STAT_FILE_PERMISSIONS_FAILED,
copyMoveOptions, False,
'copySheetProtectedRangesInheritedPermissions',
'copySheetProtectedRangesNonInheritedPermissions')
'copySheetProtectedRangesNonInheritedPermissions',
True)
_updateSheetProtectedRanges(sheet, user, i, count, k, kcount, result['id'], result['name'], protectedSheetRanges,
statistics, STAT_FILE_PROTECTEDRANGES_FAILED)
elif copyMoveOptions['copyFilePermissions']:
@ -55579,7 +55612,8 @@ def copyDriveFile(users):
statistics, STAT_FILE_PERMISSIONS_FAILED,
copyMoveOptions, False,
'copyFileInheritedPermissions',
'copyFileNonInheritedPermissions')
'copyFileNonInheritedPermissions',
True)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions,
GAPI.insufficientParentPermissions, GAPI.unknownError,
GAPI.invalid, GAPI.cannotCopyFile, GAPI.badRequest, GAPI.responsePreparationFailure, GAPI.fileNeverWritable, GAPI.fieldNotWritable,
@ -55830,7 +55864,8 @@ def copyDriveFile(users):
statistics, STAT_FILE_PERMISSIONS_FAILED,
copyMoveOptions, False,
'copySheetProtectedRangesInheritedPermissions',
'copySheetProtectedRangesNonInheritedPermissions')
'copySheetProtectedRangesNonInheritedPermissions',
True)
_updateSheetProtectedRanges(sheet, user, i, count, j, jcount, result['id'], result['name'], protectedSheetRanges,
statistics, STAT_FILE_PROTECTEDRANGES_FAILED)
elif copyMoveOptions['copyFilePermissions']:
@ -55839,7 +55874,8 @@ def copyDriveFile(users):
statistics, STAT_FILE_PERMISSIONS_FAILED,
copyMoveOptions, False,
'copyFileInheritedPermissions',
'copyFileNonInheritedPermissions')
'copyFileNonInheritedPermissions',
True)
except (GAPI.fileNotFound, GAPI.forbidden, GAPI.internalError, GAPI.insufficientFilePermissions,
GAPI.insufficientParentPermissions, GAPI.unknownError,
GAPI.invalid, GAPI.badRequest, GAPI.cannotCopyFile, GAPI.responsePreparationFailure, GAPI.fileNeverWritable, GAPI.fieldNotWritable,
@ -56072,7 +56108,8 @@ def moveDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, True,
'copyTopFolderInheritedPermissions',
copyFolderNonInheritedPermissions)
copyFolderNonInheritedPermissions,
False)
source.pop('oldparents', None)
return (newParentId, newParentName, True)
# Merge parent folders
@ -56101,7 +56138,8 @@ def moveDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, atTop,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
copyFolderNonInheritedPermissions)
copyFolderNonInheritedPermissions,
False)
return (newFolderId, newFolderName, True)
entityActionFailedWarning(kvList+[Ent.DRIVE_FOLDER, newParentNameId], Msg.NOT_WRITABLE, j, jcount)
_incrStatistic(statistics, STAT_FOLDER_NOT_WRITABLE)
@ -56187,7 +56225,8 @@ def moveDriveFile(users):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, False,
['copySubFolderInheritedPermissions', 'copyTopFolderInheritedPermissions'][atTop],
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop])
['copySubFolderNonInheritedPermissions', 'copyTopFolderNonInheritedPermissions'][atTop],
True)
return (newFolderId, newFolderName, False)
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
GAPI.internalError, GAPI.storageQuotaExceeded, GAPI.teamDriveHierarchyTooDeep,
@ -59575,7 +59614,7 @@ def doInfoDriveFileACLs():
infoDriveFileACLs([_getAdminEmail()], True)
DRIVEFILE_BASIC_PERMISSION_FIELDS = [
'id', 'emailAddress', 'domain', 'role', 'type',
'displayName', 'id', 'emailAddress', 'domain', 'role', 'type',
'allowFileDiscovery', 'expirationTime', 'deleted'
]
@ -60929,7 +60968,8 @@ def copySyncSharedDriveACLs(users, useDomainAdminAccess=False):
statistics, STAT_FOLDER_PERMISSIONS_FAILED,
copyMoveOptions, True,
'copyTopFolderInheritedPermissions',
'copyTopFolderNonInheritedPermissions')
'copyTopFolderNonInheritedPermissions',
False)
def doCopySyncSharedDriveACLs():
copySyncSharedDriveACLs([_getAdminEmail()], True)
@ -62162,58 +62202,16 @@ def printShowUserGroups(users):
# [(domain <DomainName>)|(customerid <CustomerID>)]
# [roles <GroupRoleList>]
# [showparentsaslist [<Boolean>]] [delimiter <Character>]
# [formatjson [quotechar <Character>]]
# gam <UserTypeEntity> show grouptree
# [(domain <DomainName>)|(customerid <CustomerID>)]
# [roles <GroupRoleList>]
# [formatjson]
def printShowGroupTree(users):
def getGroupParents(groupEmail, groupName):
groupParents[groupEmail] = {'name': groupName, 'parents': []}
_setUserGroupArgs(groupEmail, kwargs)
try:
entityList = callGAPIpages(cd.groups(), 'list', 'groups',
throwReasons=GAPI.GROUP_LIST_USERKEY_THROW_REASONS,
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
orderBy='email', fields='nextPageToken,groups(email,name)', **kwargs)
for parentGroup in entityList:
groupParents[groupEmail]['parents'].append(parentGroup['email'])
if parentGroup['email'] not in groupParents:
getGroupParents(parentGroup['email'], parentGroup['name'])
except (GAPI.invalidMember, GAPI.invalidInput):
badRequestWarning(Ent.GROUP, Ent.MEMBER, groupEmail)
except (GAPI.resourceNotFound, GAPI.domainNotFound, GAPI.forbidden, GAPI.badRequest):
accessErrorExit(cd)
def showGroupParents(groupEmail, role, i, count):
kvList = [groupEmail, f'{groupParents[groupEmail]["name"]}']
if role:
kvList.extend([Ent.Singular(Ent.ROLE), role])
printKeyValueListWithCount(kvList, i, count)
Ind.Increment()
for parentEmail in groupParents[groupEmail]['parents']:
showGroupParents(parentEmail, None, 0, 0)
Ind.Decrement()
def printGroupParents(groupEmail, row):
if groupParents[groupEmail]['parents']:
for parentEmail in groupParents[groupEmail]['parents']:
row['parents'].append({'email': parentEmail, 'name': groupParents[parentEmail]['name']})
printGroupParents(parentEmail, row)
del row['parents'][-1]
else:
if not showParentsAsList:
csvPF.WriteRowTitles(flattenJSON(row))
else:
crow = {'User': row['User'], 'Group': row['Group'], 'Name': row['Name']}
if rolesSet:
crow['Role'] = row['Role']
crow['ParentsCount'] = len(row['parents'])
crow['Parents'] = delimiter.join([parent['email'] for parent in row['parents']])
crow['ParentsName'] = delimiter.join([parent['name'] for parent in row['parents']])
csvPF.WriteRow(flattenJSON(crow))
cd = buildGAPIObject(API.DIRECTORY)
kwargs = {'customer': GC.Values[GC.CUSTOMER_ID]}
csvPF = CSVPrintFile(['User', 'Group', 'Name']) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
showParentsAsList = False
rolesSet = set()
@ -62234,14 +62232,19 @@ def printShowGroupTree(users):
elif csvPF and myarg == 'showparentsaslist':
showParentsAsList = getBoolean()
else:
unknownArgumentExit()
FJQC.GetFormatJSONQuoteChar(myarg, False)
if csvPF:
if rolesSet:
csvPF.AddTitles('Role')
if not showParentsAsList:
csvPF.SetIndexedTitles(['parents'])
if not FJQC.formatJSON:
if not showParentsAsList:
csvPF.SetIndexedTitles(['parents'])
else:
csvPF.AddTitles(['ParentsCount', 'Parents', 'ParentsName'])
else:
csvPF.AddTitles(['ParentsCount', 'Parents', 'ParentsName'])
if rolesSet:
csvPF.AddJSONTitles('Role')
csvPF.AddJSONTitles('JSON')
allRoles = rolesSet == ALL_GROUP_ROLES
groupParents = {}
i, count, users = getEntityArgument(users)
@ -62259,7 +62262,7 @@ def printShowGroupTree(users):
continue
j = 0
jcount = len(groups)
if not csvPF:
if not csvPF and not FJQC.formatJSON:
if allRoles:
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.GROUP_TREE, i, count)
else:
@ -62269,7 +62272,7 @@ def printShowGroupTree(users):
j += 1
groupEmail = group['email']
if groupEmail not in groupParents:
getGroupParents(groupEmail, group['name'])
getGroupParents(cd, groupParents, groupEmail, group['name'], kwargs)
if rolesSet:
try:
result = callGAPI(cd.members(), 'get',
@ -62287,13 +62290,30 @@ def printShowGroupTree(users):
continue
else:
role = None
if not csvPF:
showGroupParents(groupEmail, role, j, jcount)
if not FJQC.formatJSON:
if not csvPF:
showGroupParents(groupParents, groupEmail, role, j, jcount)
else:
row = {'User': user, 'Group': groupEmail, 'Name': group['name'], 'parents': []}
if role is not None:
row['Role'] = role
printGroupParents(groupParents, groupEmail, row, csvPF, delimiter, showParentsAsList)
else:
row = {'User': user, 'Group': groupEmail, 'Name': group['name'], 'parents': []}
if rolesSet:
row['Role'] = role
printGroupParents(groupEmail, row)
groupInfo = {'email': groupEmail, 'name': group['name'], 'parents': []}
if role is not None:
groupInfo['role'] = role
addJsonGroupParents(groupParents, groupInfo, groupEmail)
if not csvPF:
printLine(json.dumps(cleanJSON(groupInfo), ensure_ascii=False, sort_keys=True))
else:
row = flattenJSON(groupInfo)
if csvPF.CheckRowTitles(row):
row = {'User': user, 'Group': groupEmail, 'Name': group['name']}
if rolesSet:
row['Role'] = role
row['JSON'] = json.dumps(cleanJSON(groupInfo),
ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(row)
Ind.Decrement()
if csvPF:
csvPF.writeCSVfile('User Group Trees')
@ -64599,7 +64619,8 @@ MESSAGES_MAX_TO_KEYWORDS = {
def _initMessageThreadParameters(entityType, doIt, maxToProcess):
listType = 'messages' if entityType == Ent.MESSAGE else 'threads'
return {'currLabelOp': 'and', 'prevLabelOp': 'and', 'labelGroupOpen': False, 'query': '',
return {'currLabelOp': 'and', 'prevLabelOp': 'and', 'labelGroupOpen': False,
'query': '', 'queryTimes': {},
'entityType': entityType, 'messageEntity': None, 'doIt': doIt, 'quick': True,
'labelMatchPattern': None, 'senderMatchPattern': None,
'maxToProcess': maxToProcess, 'maxItems': 0,
@ -64611,6 +64632,8 @@ LABEL_QUERY_REPLACEMENT_CHARACTERS = ' &()"|{}/'
def _getMessageSelectParameters(myarg, parameters):
if myarg == 'query':
parameters['query'] += f' ({getString(Cmd.OB_QUERY)})'
elif myarg.startswith('querytime'):
parameters['queryTimes'][myarg] = getDateOrDeltaFromNow().replace('-', '/')
elif myarg == 'matchlabel':
labelTemp = getString(Cmd.OB_LABEL_NAME).lower()
labelName = ''
@ -64667,6 +64690,9 @@ def _finalizeMessageSelectParameters(parameters, queryOrIdsRequired):
if parameters['query']:
if parameters['labelGroupOpen']:
parameters['query'] += ')'
if parameters['queryTimes']:
for queryTimeName, queryTimeValue in iter(parameters['queryTimes'].items()):
parameters['query'] = parameters['query'].replace(f'#{queryTimeName}#', queryTimeValue)
_mapMessageQueryDates(parameters)
elif queryOrIdsRequired and parameters['messageEntity'] is None:
missingArgumentExit('query|matchlabel|ids')
@ -64675,7 +64701,7 @@ def _finalizeMessageSelectParameters(parameters, queryOrIdsRequired):
parameters['maxItems'] = parameters['maxToProcess'] if parameters['quick'] and not parameters['labelMatchPattern'] else 0
# gam <UserTypeEntity> archive messages <GroupItem>
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_archive <Number>])|(ids <MessageIDEntity>)
def archiveMessages(users):
entityType = Ent.MESSAGE
parameters = _initMessageThreadParameters(entityType, False, 0)
@ -64905,30 +64931,30 @@ def _processMessagesThreads(users, entityType):
Ind.Decrement()
# gam <UserTypeEntity> delete message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <MessageIDEntity>)
# gam <UserTypeEntity> modify message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <MessageIDEntity>)
# (addlabel <LabelName>)* (removelabel <LabelName>)*
# gam <UserTypeEntity> spam message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <MessageIDEntity>)
# gam <UserTypeEntity> trash message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
# gam <UserTypeEntity> untrash message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <MessageIDEntity>)
def processMessages(users):
_processMessagesThreads(users, Ent.MESSAGE)
# gam <UserTypeEntity> delete thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_delete <Number>])|(ids <ThreadIDEntity>)
# gam <UserTypeEntity> modify thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_modify <Number>])|(ids <ThreadIDEntity>)
# (addlabel <LabelName>)* (removelabel <LabelName>)*
# gam <UserTypeEntity> spam thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_spam <Number>])|(ids <ThreadIDEntity>)
# gam <UserTypeEntity> trash thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_trash <Number>])|(ids <MessageIDEntity>)
# gam <UserTypeEntity> untrash thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_untrash <Number>])|(ids <ThreadIDEntity>)
def processThreads(users):
_processMessagesThreads(users, Ent.THREAD)
@ -65038,13 +65064,13 @@ def exportMessagesThreads(users, entityType):
Ind.Decrement()
# gam <UserTypeEntity> export message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <MessageIDEntity>)
# [targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
def exportMessages(users):
exportMessagesThreads(users, Ent.MESSAGE)
# gam <UserTypeEntity> export thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_export <Number>])|(ids <ThreadIDEntity>)
# [targetfolder <FilePath>] [targetname <FileName>] [overwrite [<Boolean>]]
def exportThreads(users):
exportMessagesThreads(users, Ent.THREAD)
@ -65064,10 +65090,10 @@ def _decodeHeader(header):
return header
# gam <UserTypeEntity> forward message|messages recipient|to <RecipientEntity>
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
# [subject <String>] [altcharset <String>]
# gam <UserTypeEntity> forward thread|threads recipient|to <RecipientEntity>
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
# [subject <String>] [altcharset <String>]
def forwardMessagesThreads(users, entityType):
def getRecipients():
@ -65371,7 +65397,7 @@ def _draftImportInsertMessage(users, operation):
emlFile = True
internalDateSource = 'dateHeader'
elif myarg == 'replace':
_getTagReplacement(tagReplacements, False)
_getTagReplacement(tagReplacements, True)
elif operation in IMPORT_INSERT and myarg == 'addlabel':
addLabelNames.append(getString(Cmd.OB_LABEL_NAME, minLen=1))
elif operation in IMPORT_INSERT and myarg == 'labels':
@ -66184,7 +66210,7 @@ def printShowMessagesThreads(users, entityType):
csvPF.writeCSVfile('Message Counts' if not show_labels else 'Message Label Counts')
# gam <UserTypeEntity> print message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <MessageIDEntity>)
# (((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]
@ -66192,7 +66218,7 @@ def printShowMessagesThreads(users, entityType):
# [convertcrnl] [delimiter <Character>] [todrive <ToDriveAttribute>*]
# [countsonly|positivecountsonly] [useronly]
# gam <UserTypeEntity> show message|messages
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <MessageIDEntity>)
# [labelmatchpattern <RegularExpression>] [sendermatchpattern <RegularExpression>]
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
# [showlabels] [showbody] [showdate] [showsize] [showsnippet]
@ -66203,7 +66229,7 @@ def printShowMessages(users):
printShowMessagesThreads(users, Ent.MESSAGE)
# gam <UserTypeEntity> print thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_print <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
# (((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]
@ -66211,7 +66237,7 @@ def printShowMessages(users):
# [convertcrnl] [delimiter <Character>] [todrive <ToDriveAttribute>*]
# [countsonly|positivecountsonly] [useronly]
# gam <UserTypeEntity> show thread|threads
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])* [quick|notquick] [max_to_show <Number>] [includespamtrash])|(ids <ThreadIDEntity>)
# [labelmatchpattern <RegularExpression>]
# [headers all|<SMTPHeaderList>] [dateheaderformat iso|rfc2822|<String>] [dateheaderconverttimezone [<Boolean>]]
# [showlabels] [showbody] [showdate] [showsize] [showsnippet]
@ -67169,10 +67195,10 @@ EMAILSETTINGS_FORWARD_POP_ACTION_CHOICE_MAP = {
}
# gam <UserTypeEntity> forward message|messages recipient|to <RecipientEntity>
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <MessageIDEntity>)
# [subject <String>]
# gam <UserTypeEntity> forward thread|threads recipient|to <RecipientEntity>
# (((query <QueryGmail>) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
# (((query <QueryGmail> [querytime<String> <Date>]*) (matchlabel <LabelName>) [or|and])+ [quick|notquick] [doit] [max_to_forward <Number>])|(ids <ThreadIDEntity>)
# [subject <String>]
# gam <UserTypeEntity> forward <FalseValues>
# gam <UserTypeEntity> forward <TrueValues> keep|leaveininbox|archive|delete|trash|markread <EmailAddress>

View File

@ -438,6 +438,7 @@ STRING_LENGTH = 'string length'
SUBKEY_FIELD_MISMATCH = 'subkeyfield {0} does not match saved subkeyfield {1}'
SUBSCRIPTION_NOT_FOUND = 'Could not find subscription'
SUFFIX_NOT_ALLOWED_WITH_CUSTOMLANGUAGE = 'Suffix {0} not allowed with customLanguage {1}'
TASKLIST_TITLE_NOT_FOUND = 'Task list title not found'
THREAD = 'thread'
THREADS = 'threads'
TO = 'To'