mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-16 20:21:37 +00:00
Compare commits
10 Commits
20240721.2
...
20240805.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71bf658e17 | ||
|
|
8211d5df8c | ||
|
|
10e54e49a5 | ||
|
|
6b9ac2700e | ||
|
|
012616a285 | ||
|
|
2669b1bff6 | ||
|
|
2aeebd17a4 | ||
|
|
e43802e197 | ||
|
|
16b3d2b006 | ||
|
|
f777ec177c |
@@ -225,7 +225,7 @@ perform these steps and then you should be able to authorize and use your projec
|
||||
* Click on Grant Access
|
||||
* Enter the new admin address in Principals
|
||||
* Click in the Select a role box
|
||||
* Type orgpolicy.policies.update in the Filter box
|
||||
* Type organization policy administrator in the Filter box
|
||||
* Click Organization Policy Administrator
|
||||
* Click Save
|
||||
* In the upper left click the three lines to the left of Google Cloud and select IAM & Admin
|
||||
|
||||
@@ -193,7 +193,8 @@ Select the fields to be displayed:
|
||||
* `annotated` - Display these fields: deviceId,annotatedAssetId,annotatedLocation,annotatedNotes,annotatedUser
|
||||
* `basic` - Display all fields except: browsers, lastDeviceUsers, lastStatusReportTime, machinePloicies; this is the default
|
||||
* `allfields/full` - Display all fields
|
||||
* `<BrowserFieldName>* [fields <BrowserFieldNameList>]` - Displaya selected list of fields
|
||||
* `<BrowserFieldName>* [fields <BrowserFieldNameList>]` - Display a selected list of fields
|
||||
* Note that `ou, org and orgunit` are both command line options and field names; use `fields` to include them in the selected list of fields
|
||||
|
||||
By default, Gam displays the information as an indented list of keys and values:
|
||||
- `formatjson` - Display the fields in JSON format.
|
||||
@@ -232,7 +233,8 @@ Select the fields to be displayed:
|
||||
* `annotated` - Display these fields: deviceId,annotatedAssetId,annotatedLocation,annotatedNotes,annotatedUser
|
||||
* `basic` - Display all fields except: browsers, lastDeviceUsers, lastStatusReportTime, machinePloicies; this is the default
|
||||
* `allfields/full` - Display all fields
|
||||
* `<BrowserFieldName>* [fields <BrowserFieldNameList>]` - Displaya selected list of fields
|
||||
* `<BrowserFieldName>* [fields <BrowserFieldNameList>]` - Display a selected list of fields
|
||||
* Note that `ou, org and orgunit` are both command line options and field names; use `fields` to include them in the selected list of fields
|
||||
|
||||
By default, Gam displays the information as columns of fields; the following option causes the output to be in JSON format:
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
|
||||
@@ -199,6 +199,16 @@ gam update chromepolicy convertcrnl chrome.devices.DisabledDeviceReturnInstructi
|
||||
```
|
||||
|
||||
### Examples
|
||||
Restrict use of Chromebooks in an OU to a specific list of users.
|
||||
```
|
||||
gam update chromepolicy chrome.devices.SignInRestriction deviceAllowNewUsers RESTRICTED_LIST userAllowlist "user1@domain.com,user2@domain.com" ou "<Path/To/Ou>"
|
||||
```
|
||||
|
||||
Restrict use of Chromebooks in an OU to users in a specific domain.
|
||||
```
|
||||
gam update chromepolicy chrome.devices.SignInRestriction deviceAllowNewUsers RESTRICTED_LIST userAllowlist "*@domain.com" ou "<Path/To/Ou>"
|
||||
```
|
||||
|
||||
Restrict student users from adding additional printers and set default printing to black and white.
|
||||
```
|
||||
gam update chromepolicy chrome.users.UserPrintersAllowed userPrintersAllowed false chrome.users.DefaultPrintColor printingColorDefault MONOCHROME orgunit "/Students"
|
||||
|
||||
@@ -10,6 +10,60 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 6.79.08
|
||||
|
||||
Clarified action to perform messages when creating/deleting/updating licenses.
|
||||
|
||||
### 6.79.07
|
||||
|
||||
Added option `totalonly` to `gam <UserTypeEntity> print|show groups` that displays
|
||||
the user email address and the total number of groups to which it belongs. This is in
|
||||
contrast to `countsonly` that has to make an additional API call per group per user to get the user's role.
|
||||
When `countsonly` is specified, an additional column `Total` is displayed that is the sum
|
||||
of the role counts.
|
||||
|
||||
### 6.79.06
|
||||
|
||||
Fixed bug in `gam calendars <CalendarEntity> update event ... removeattendee <EmailAddress>` that caused a trap
|
||||
if the event had no attendees.
|
||||
|
||||
### 6.79.05
|
||||
|
||||
Updated `gam <UserTypeEntity> empty drivetrash <SharedDriveEntity>` to handle this error that
|
||||
occurs when the user is not a Manager of the Shared Drive.
|
||||
```
|
||||
ERROR: 403: insufficientFilePermissions - The user does not have sufficient permissions for this file.
|
||||
```
|
||||
|
||||
### 6.79.04
|
||||
|
||||
Added options `filename <FileName>` and `movetoou <OrgUnitItem>` to `gam check ou <OrgUnitItem>`
|
||||
that causes GAM to create a batch file of GAM commands that will move any remaining items
|
||||
in `ou <OrgUnitItem>` to `movetoou <OrgUnitItem>`; executing the batch file will then allow
|
||||
`ou <OrgUnitItem>` to be deleted if desired.
|
||||
|
||||
### 6.79.03
|
||||
|
||||
Added column|field `assignedToUnknown` to `gam print|show admins` that will be True when
|
||||
the API `assignedTo` value can not be converted to an email address; it will be False when
|
||||
the email address is determinable.
|
||||
|
||||
### 6.79.02
|
||||
|
||||
Updated `gam print admins` to handle the following error that occurs when a service account admin no longer exists.
|
||||
```
|
||||
ERROR: 404: notFound - Requested entity was not found.
|
||||
```
|
||||
|
||||
### 6.79.01
|
||||
|
||||
Updated commands that take `<RoleItem>` as an argument to take the value in any case,
|
||||
e.g., _SEED_ADMIN_ROLE or _seed_admin_role.
|
||||
|
||||
### 6.79.00
|
||||
|
||||
Updated code to work around a Cryptography library change that caused service account private key creation to fail.
|
||||
|
||||
### 6.78.00
|
||||
|
||||
Added command to check if an OU contains items; this is useful when tryng to delete an OU
|
||||
|
||||
@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
|
||||
admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt
|
||||
admin@server:/Users/admin$ gam version
|
||||
WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found
|
||||
GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.79.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
MacOS Sonoma 14.5 x86_64
|
||||
@@ -923,7 +923,7 @@ writes the credentials into the file oauth2.txt.
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
||||
GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.79.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
|
||||
@@ -295,6 +295,7 @@ Only items directly within the OU are counted, items in sub-OUs are not counted.
|
||||
|
||||
gam check org|ou <OrgUnitItem> [todrive <ToDriveAttribute>*]
|
||||
[<OrgUnitCheckName>*|(fields <OrgUnitCheckNameList>)]
|
||||
[filename <FileName>] [movetoou <OrgUnitItem>]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, GAM checks each of the five items; you can select specfic fields
|
||||
@@ -309,6 +310,17 @@ When using the `formatjson` option, double quotes are used extensively in the da
|
||||
The `quotechar <Character>` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output.
|
||||
`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used.
|
||||
|
||||
If `movetoou <OrgUnitItem>` is specified, GAM will create a batch file of GAM commands that will move any remaining items
|
||||
in `ou <OrgUnitItem>` to `movetoou <OrgUnitItem>`.
|
||||
|
||||
By default, the batch file will be named `CleanOuBatch.txt` and will be created in `gam.cfg/drive_dir`.
|
||||
This can be overridden with `filename <FileName>`.
|
||||
|
||||
You can inspect the file and execute it if desired; substitute actual filenames as desired.
|
||||
```
|
||||
gam redirect stdout CleanOuLog.txt multiproces redirect stderr stdout batch CleanOuBatch.txt
|
||||
```
|
||||
|
||||
## Special case handling for large number of organizational units
|
||||
|
||||
By default, the `print orgs` and `show orgtree` commands issue a single API call to get the
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
- [Change Shared Drive visibility](#change-shared-drive-visibility)
|
||||
- [Display Shared Drives](#display-shared-drives)
|
||||
- [Display List of Shared Drives in an Organizational Unit](#display-list-of-shared-drives-in-an-organizational-unit)
|
||||
- [Display all Shared Drives with a specific organizer](#display-all-shared-drives-with-a-specific-organizer)
|
||||
- [Display all Shared Drives without a specific organizer](#display-all-shared-drives-without-a-specific-organizer)
|
||||
- [Manage Shared Drive access](#manage-shared-drive-access)
|
||||
- [Transfer Shared Drive access](#transfer-shared-drive-access)
|
||||
- [Display Shared Drive access](#display-shared-drive-access)
|
||||
@@ -72,6 +74,22 @@
|
||||
<OrgUnitPath> ::= /|(/<String>)+
|
||||
<OrgUnitItem> ::= <OrgUnitID>|<OrgUnitPath>
|
||||
|
||||
<DriveFileOrderByFieldName> ::=
|
||||
createddate|createdtime|
|
||||
folder|
|
||||
lastviewedbyme|lastviewedbymedate|lastviewedbymetime|lastviewedbyuser|
|
||||
modifiedbyme|modifiedbymedate|modifiedbymetime|modifiedbyuser|
|
||||
modifieddate|modifiedtime|
|
||||
name|
|
||||
name_natural|
|
||||
quotabytesused|quotaused|
|
||||
recency|
|
||||
sharedwithmedate|sharedwithmetime|
|
||||
starred|
|
||||
title|
|
||||
title_natural|
|
||||
viewedbymedate|viewedbymetime
|
||||
|
||||
<DriveFileACLRole> ::=
|
||||
manager|organizer|owner|
|
||||
contentmanager|fileorganizer|
|
||||
@@ -380,6 +398,18 @@ Print information about Shared Drives that have admin@domain.com as a member.
|
||||
```
|
||||
gam user admin@domain.com print teamdrives
|
||||
```
|
||||
## Display all Shared Drives with a specific organizer
|
||||
Substitute actual email address for `organizer@domain.com`.
|
||||
```
|
||||
gam config csv_output_header_filter "id,name" print teamdriveacls pm emailaddress organizer@domain.com role organizer em pma process pmselect
|
||||
```
|
||||
|
||||
## Display all Shared Drives without a specific organizer
|
||||
Substitute actual email address for `organizer@domain.com`.
|
||||
```
|
||||
gam config csv_output_header_filter "id,name" print teamdriveacls pm emailaddress organizer@domain.com role organizer em pma skip pmselect
|
||||
```
|
||||
|
||||
## Display List of Shared Drives in an Organizational Unit
|
||||
To use this command you must add the `Cloud Identity API` to your project and authorize
|
||||
the appropriate scope: `Cloud Identity OrgUnits API`.
|
||||
@@ -580,10 +610,12 @@ Print ACLs for all Shared Drives in the organization created after November 1, 2
|
||||
```
|
||||
gam print teamdriveacls teamdriveadminquery "createdTime > '2017-11-01T00:00:00'"
|
||||
```
|
||||
|
||||
Print ACLs for all Shared Drives in the organization with foo@bar.com as an organizer.
|
||||
```
|
||||
gam print teamdriveacls user foo@bar.com role organizer
|
||||
```
|
||||
|
||||
Print ACLs for all Shared Drives in the organization with foo@bar.com or groups that contain foo@bar.com as a reader.
|
||||
```
|
||||
gam print teamdriveacls user foo@bar.com role reader checkgroups
|
||||
|
||||
@@ -63,6 +63,10 @@ gam <UserTypeEntity> transfer ownership <DriveFileEntity> <UserItem>
|
||||
(orderby <DriveOrderByFieldName> [ascending|descending])*
|
||||
[preview] [filepath] [pathdelimiter <Character>] [buildtree] [todrive <ToDriveAttribute>*]
|
||||
```
|
||||
`<DriveFileEntity>` specifies a file/folder owned by the source user `<UserTypeEntity>`.
|
||||
|
||||
The target user is specified by `<UserItem>`.
|
||||
|
||||
By default, there is no change of parents for the transferred files/folders, they remain in their current location.
|
||||
* `<DriveFileParentAttribute>` - Specify a parent folder in the My Drive of the target user `<UserItem>`.
|
||||
|
||||
|
||||
@@ -25,6 +25,22 @@
|
||||
<UniqueID> ::= id:<String>
|
||||
<UserItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
|
||||
<DriveFileOrderByFieldName> ::=
|
||||
createddate|createdtime|
|
||||
folder|
|
||||
lastviewedbyme|lastviewedbymedate|lastviewedbymetime|lastviewedbyuser|
|
||||
modifiedbyme|modifiedbymedate|modifiedbymetime|modifiedbyuser|
|
||||
modifieddate|modifiedtime|
|
||||
name|
|
||||
name_natural|
|
||||
quotabytesused|quotaused|
|
||||
recency|
|
||||
sharedwithmedate|sharedwithmetime|
|
||||
starred|
|
||||
title|
|
||||
title_natural|
|
||||
viewedbymedate|viewedbymetime
|
||||
|
||||
<DrivePermissionsFieldName> ::=
|
||||
additionalroles|
|
||||
allowfilediscovery|
|
||||
|
||||
@@ -19,6 +19,22 @@
|
||||
<EmailAddress> ::= <String>@<DomainName>
|
||||
<UniqueID> ::= id:<String>
|
||||
<UserItem> ::= <EmailAddress>|<UniqueID>|<String>
|
||||
|
||||
<DriveFileOrderByFieldName> ::=
|
||||
createddate|createdtime|
|
||||
folder|
|
||||
lastviewedbyme|lastviewedbymedate|lastviewedbymetime|lastviewedbyuser|
|
||||
modifiedbyme|modifiedbymedate|modifiedbymetime|modifiedbyuser|
|
||||
modifieddate|modifiedtime|
|
||||
name|
|
||||
name_natural|
|
||||
quotabytesused|quotaused|
|
||||
recency|
|
||||
sharedwithmedate|sharedwithmetime|
|
||||
starred|
|
||||
title|
|
||||
title_natural|
|
||||
viewedbymedate|viewedbymetime
|
||||
```
|
||||
## GAM Data Transfers
|
||||
```
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
- [Display group details in CSV format](#display-group-details-in-csv-format)
|
||||
- [Display group counts as an indented list](#display-group-counts-as-an-indented-list)
|
||||
- [Display group counts in CSV format](#display-group-counts-in-csv-format)
|
||||
- [Display total group counts as an indented list](#display-total-group-counts-as-an-indented-list)
|
||||
- [Display total group counts in CSV format](#display-total-group-counts-in-csv-format)
|
||||
- [Display group addresses in CSV format](#display-group-addresses-in-csv-format)
|
||||
- [Display groups and their parents](#display-groups-and-their-parents)
|
||||
- [Add a target user to the same groups as a source user](#add-a-target-user-to-the-same-groups-as-a-source-user)
|
||||
@@ -461,6 +463,10 @@ gam <UserTypeEntity> show groups
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
[roles <GroupRoleList>] countsonly
|
||||
```
|
||||
By default, all groups to which a member belongs are displayed, these options allow selection of subsets of groups:
|
||||
* `domain <DomainName>` - Limit display to groups in the domain `<DomainName>` of which they are a member
|
||||
* `customerid <CustomerID>` - For resellers, display all groups in a resold workspace of which they are a member
|
||||
* `roles <GroupRoleList>` - Limit display to those groups for which the user has a specific role
|
||||
|
||||
### Display group counts in CSV format
|
||||
There is one row per user displaying the number of groups, by role, to which a user belongs.
|
||||
@@ -476,6 +482,33 @@ By default, all groups to which a member belongs are displayed, these options al
|
||||
* `customerid <CustomerID>` - For resellers, display all groups in a resold workspace of which they are a member
|
||||
* `roles <GroupRoleList>` - Limit display to those groups for which the user has a specific role
|
||||
|
||||
### Display total group counts as an indented list
|
||||
There is one row per user displaying the number of groups to which a user belongs.
|
||||
|
||||
There is one API call per user to get the total group count.
|
||||
```
|
||||
gam <UserTypeEntity> show groups
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
totalonly
|
||||
```
|
||||
By default, all groups to which a member belongs are displayed, these options allow selection of subsets of groups:
|
||||
* `domain <DomainName>` - Limit display to groups in the domain `<DomainName>` of which they are a member
|
||||
* `customerid <CustomerID>` - For resellers, display all groups in a resold workspace of which they are a member
|
||||
|
||||
|
||||
### Display total group counts in CSV format
|
||||
There is one row per user displaying the total number of groups to which a user belongs.
|
||||
|
||||
There is one API call per user to get the total group count.
|
||||
```
|
||||
gam <UserTypeEntity> print groups [todrive <ToDriveAttribute>*]
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
totalonly
|
||||
```
|
||||
By default, all groups to which a member belongs are displayed, these options allow selection of subsets of groups:
|
||||
* `domain <DomainName>` - Limit display to groups in the domain `<DomainName>` of which they are a member
|
||||
* `customerid <CustomerID>` - For resellers, display all groups in a resold workspace of which they are a member
|
||||
|
||||
### Display group addresses in CSV format
|
||||
There is one row per user showing the number and list of groups to which a user directly belongs.
|
||||
```
|
||||
|
||||
@@ -73,6 +73,22 @@
|
||||
<OrgUnitPath> ::= /|(/<String>)+
|
||||
<OrgUnitItem> ::= <OrgUnitID>|<OrgUnitPath>
|
||||
|
||||
<DriveFileOrderByFieldName> ::=
|
||||
createddate|createdtime|
|
||||
folder|
|
||||
lastviewedbyme|lastviewedbymedate|lastviewedbymetime|lastviewedbyuser|
|
||||
modifiedbyme|modifiedbymedate|modifiedbymetime|modifiedbyuser|
|
||||
modifieddate|modifiedtime|
|
||||
name|
|
||||
name_natural|
|
||||
quotabytesused|quotaused|
|
||||
recency|
|
||||
sharedwithmedate|sharedwithmetime|
|
||||
starred|
|
||||
title|
|
||||
title_natural|
|
||||
viewedbymedate|viewedbymetime
|
||||
|
||||
<DriveFileACLRole> ::=
|
||||
manager|organizer|owner|
|
||||
contentmanager|fileorganizer|
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
\# Version and Help
|
||||
\\# Version and Help
|
||||
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.79.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
MacOS Sonoma 14.5 x86_64
|
||||
@@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.79.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
MacOS Sonoma 14.5 x86_64
|
||||
@@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second
|
||||
Print the current version of Gam with extended details and SSL information
|
||||
```
|
||||
gam version extended
|
||||
GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
GAMADV-XTD3 6.79.08 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
MacOS Sonoma 14.5 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.78.00
|
||||
Latest: 6.79.08
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -72,7 +72,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
6.78.00
|
||||
6.79.08
|
||||
```
|
||||
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.78.00 - https://github.com/taers232c/GAMADV-XTD3
|
||||
GAM 6.79.08 - https://github.com/taers232c/GAMADV-XTD3
|
||||
Ross Scroggs <ross.scroggs@gmail.com>
|
||||
Python 3.12.4 64-bit final
|
||||
MacOS Sonoma 14.5 x86_64
|
||||
|
||||
@@ -1994,6 +1994,7 @@ gam revoke browsertoken <BrowserTokenPermanentID>
|
||||
org|
|
||||
orgunit|
|
||||
orgunitpath|
|
||||
ou|
|
||||
revoketime|
|
||||
revokerid|
|
||||
state|
|
||||
@@ -4225,8 +4226,9 @@ gam show orgtree [fromparent <OrgUnitItem>] [batchsuborgs [<Boolean>]]
|
||||
users
|
||||
<OrgUnitCheckNameList> ::= "<OrgUnitCheckName>(,<OrgUnitCheckName>)*"
|
||||
|
||||
gam check org|ou <OrgUnitItem> [todrive <ToDriveAttribute>*]
|
||||
gam check ou|org <OrgUnitItem> [todrive <ToDriveAttribute>*]
|
||||
[<OrgUnitCheckName>*|(fields <OrgUnitCheckNameList>)]
|
||||
[filename <FileName>] [movetoou <OrgUnitItem>]
|
||||
[formatjson [quotechar <Character>]]
|
||||
|
||||
# Printers
|
||||
@@ -5355,7 +5357,7 @@ gam download storagefile <StorageBucketObjectName>
|
||||
(note clear|([text_html|text_plain] <String>|
|
||||
(file|htmlfile <FileName> [charset <Charset>])|
|
||||
(gdoc|ghtml <UserGoogleDoc>)))|
|
||||
(org|ou|orgunitpath <OrgUnitPath>|<OrgUnitID>)
|
||||
(ou|org|orgunitpath <OrgUnitPath>|<OrgUnitID>)
|
||||
(password (random [<Integer>])|(uniquerandom [<Integer>])|blocklogin|<Password>)|
|
||||
(recoveryemail <EmailAddress>)|
|
||||
(recoveryphone <string>)|
|
||||
@@ -7602,10 +7604,10 @@ gam <UserTypeEntity> check group|groups
|
||||
[roles <GroupRoleList>] [includederivedmembership] [csv] <GroupEntity>
|
||||
gam <UserTypeEntity> print groups [todrive <ToDriveAttribute>*]
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
[roles <GroupRoleList>] [countsonly|nodetails]
|
||||
[roles <GroupRoleList>] [countsonly|totalonly|nodetails]
|
||||
gam <UserTypeEntity> show groups
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
[roles <GroupRoleList>] [countsonly|nodetails]
|
||||
[roles <GroupRoleList>] [countsonly|totalonly|nodetails]
|
||||
gam <UserTypeEntity> print grouptree [todrive <ToDriveAttribute>*]
|
||||
[(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
[roles <GroupRoleList>]
|
||||
|
||||
@@ -2,6 +2,60 @@
|
||||
|
||||
Merged GAM-Team version
|
||||
|
||||
6.79.08
|
||||
|
||||
Clarified action to perform messages when creating/deleting/updating licenses.
|
||||
|
||||
6.79.07
|
||||
|
||||
Added option `totalonly` to `gam <UserTypeEntity> print|show groups` that displays
|
||||
the user email address and the total number of groups to which it belongs. This is in
|
||||
contrast to `countsonly` that has to make an additional API call per group per user to get the user's role.
|
||||
When `countsonly` is specified, an additional column `Total` is displayed that is the sum
|
||||
of the role counts.
|
||||
|
||||
6.79.06
|
||||
|
||||
Fixed bug in `gam calendars <CalendarEntity> update event ... removeattendee <EmailAddress>` that caused a trap
|
||||
if the event had no attendees.
|
||||
|
||||
6.79.05
|
||||
|
||||
Updated `gam <UserTypeEntity> empty drivetrash <SharedDriveEntity>` to handle this error that
|
||||
occurs when the user is not a Manager of the Shared Drive.
|
||||
```
|
||||
ERROR: 403: insufficientFilePermissions - The user does not have sufficient permissions for this file.
|
||||
```
|
||||
|
||||
6.79.04
|
||||
|
||||
Added options `filename <FileName>` and `movetoou <OrgUnitItem>` to `gam check ou <OrgUnitItem>`
|
||||
that causes GAM to create a batch file of GAM commands that will move any remaining items
|
||||
in `ou <OrgUnitItem>` to `movetoou <OrgUnitItem>`; executing the batch file will then allow
|
||||
`ou <OrgUnitItem>` to be deleted if desired.
|
||||
|
||||
6.79.03
|
||||
|
||||
Added column|field `assignedToUnknown` to `gam print|show admins` that will be True when
|
||||
the API `assignedTo` value can not be converted to an email address; it will be False when
|
||||
the email address is determinable.
|
||||
|
||||
6.79.02
|
||||
|
||||
Updated `gam print admins` to handle the following error that occurs when a service account admin no longer exists.
|
||||
```
|
||||
ERROR: 404: notFound - Requested entity was not found.
|
||||
```
|
||||
|
||||
6.79.01
|
||||
|
||||
Updated commands that take `<RoleItem>` as an argument to take the value in any case,
|
||||
e.g., _SEED_ADMIN_ROLE or _seed_admin_role.
|
||||
|
||||
6.79.00
|
||||
|
||||
Updated code to work around a Cryptography library change that caused service account private key creation to fail.
|
||||
|
||||
6.78.00
|
||||
|
||||
Added command to check if an OU contains items; this is useful when tryng to delete an OU
|
||||
|
||||
@@ -2810,6 +2810,15 @@ def entityModifierNewValueKeyValueActionPerformed(entityValueList, modifier, new
|
||||
def cleanFilename(filename):
|
||||
return sanitize_filename(filename, '_')
|
||||
|
||||
def setFilePath(fileName):
|
||||
if fileName.startswith('./') or fileName.startswith('.\\'):
|
||||
fileName = os.path.join(os.getcwd(), fileName[2:])
|
||||
else:
|
||||
fileName = os.path.expanduser(fileName)
|
||||
if not os.path.isabs(fileName):
|
||||
fileName = os.path.join(GC.Values[GC.DRIVE_DIR], fileName)
|
||||
return fileName
|
||||
|
||||
def uniqueFilename(targetFolder, filetitle, overwrite, extension=None):
|
||||
filename = filetitle
|
||||
y = 0
|
||||
@@ -3610,6 +3619,8 @@ def SetGlobalVariables():
|
||||
|
||||
def _getCfgString(sectionName, itemName):
|
||||
value = _stripStringQuotes(GM.Globals[GM.PARSER].get(sectionName, itemName))
|
||||
if itemName == GC.DOMAIN:
|
||||
value = value.strip()
|
||||
minLen, maxLen = GC.VAR_INFO[itemName].get(GC.VAR_LIMITS, (None, None))
|
||||
if ((minLen is None) or (len(value) >= minLen)) and ((maxLen is None) or (len(value) <= maxLen)):
|
||||
if itemName == GC.LICENSE_SKUS and value:
|
||||
@@ -3773,12 +3784,7 @@ def SetGlobalVariables():
|
||||
|
||||
def _setCSVFile(fileName, mode, encoding, writeHeader, multi):
|
||||
if fileName != '-':
|
||||
if fileName.startswith('./') or fileName.startswith('.\\'):
|
||||
fileName = os.path.join(os.getcwd(), fileName[2:])
|
||||
else:
|
||||
fileName = os.path.expanduser(fileName)
|
||||
if not os.path.isabs(fileName):
|
||||
fileName = os.path.join(GC.Values[GC.DRIVE_DIR], fileName)
|
||||
fileName = setFilePath(fileName)
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_NAME] = fileName
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_MODE] = mode
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_ENCODING] = encoding
|
||||
@@ -3799,12 +3805,7 @@ def SetGlobalVariables():
|
||||
else:
|
||||
GM.Globals[stdtype][GM.REDIRECT_FD] = os.fdopen(os.dup(sys.stderr.fileno()), mode, encoding=GM.Globals[GM.SYS_ENCODING])
|
||||
else:
|
||||
if fileName.startswith('./') or fileName.startswith('.\\'):
|
||||
fileName = os.path.join(os.getcwd(), fileName[2:])
|
||||
else:
|
||||
fileName = os.path.expanduser(fileName)
|
||||
if not os.path.isabs(fileName):
|
||||
fileName = os.path.join(GC.Values[GC.DRIVE_DIR], fileName)
|
||||
fileName = setFilePath(fileName)
|
||||
if multi and mode == DEFAULT_FILE_WRITE_MODE:
|
||||
deleteFile(fileName)
|
||||
mode = DEFAULT_FILE_APPEND_MODE
|
||||
@@ -5184,7 +5185,7 @@ def checkGAPIError(e, softErrors=False, retryOnHttpError=False, mapNotFound=True
|
||||
elif http_status == 409:
|
||||
if status == 'ALREADY_EXISTS' or 'requested entity already exists' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.ALREADY_EXISTS, message)
|
||||
elif status == 'ABORTED':
|
||||
elif status == 'ABORTED' or 'the operation was aborted' in lmessage:
|
||||
error = makeErrorDict(http_status, GAPI.ABORTED, message)
|
||||
elif http_status == 412:
|
||||
if 'insufficient archived user licenses' in lmessage:
|
||||
@@ -5628,9 +5629,9 @@ def getServiceAccountEmailFromID(account_id, sal=None):
|
||||
sal = buildGAPIObject(API.SERVICEACCOUNTLOOKUP)
|
||||
try:
|
||||
certs = callGAPI(sal.serviceaccounts(), 'lookup',
|
||||
throwReasons = [GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.INVALID_ARGUMENT],
|
||||
throwReasons = [GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.RESOURCE_NOT_FOUND, GAPI.INVALID_ARGUMENT],
|
||||
account=account_id)
|
||||
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.invalidArgument):
|
||||
except (GAPI.badRequest, GAPI.notFound, GAPI.resourceNotFound, GAPI.invalidArgument):
|
||||
return None
|
||||
sa_cn_rx = r'CN=(.+)\.(.+)\.iam\.gservice.*'
|
||||
sa_emails = []
|
||||
@@ -12348,8 +12349,8 @@ def _generatePrivateKeyAndPublicCert(projectId, clientEmail, name, key_size, b64
|
||||
writeStdout(Msg.EXTRACTING_PUBLIC_CERTIFICATE+'\n')
|
||||
public_key = private_key.public_key()
|
||||
builder = x509.CertificateBuilder()
|
||||
builder = builder.subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, name)]))
|
||||
builder = builder.issuer_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, name)]))
|
||||
builder = builder.subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, name, _validate=False)]))
|
||||
builder = builder.issuer_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, name, _validate=False)]))
|
||||
# Gooogle seems to enforce the not before date strictly. Set the not before
|
||||
# date to be UTC two minutes ago which should cover any clock skew.
|
||||
now = datetime.datetime.utcnow()
|
||||
@@ -13660,7 +13661,7 @@ def doReport():
|
||||
elif myarg in {'range', 'thismonth', 'previousmonths'}:
|
||||
startEndTime.Get(myarg)
|
||||
userCustomerRange = True
|
||||
elif myarg in {'orgunit', 'org', 'ou'}:
|
||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||
if cd is None:
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
orgUnit, orgUnitId = getOrgUnitId(cd)
|
||||
@@ -16210,7 +16211,7 @@ def makeRoleIdNameMap():
|
||||
accessErrorExit(cd)
|
||||
for role in result:
|
||||
GM.Globals[GM.MAP_ROLE_ID_TO_NAME][role['roleId']] = role['roleName']
|
||||
GM.Globals[GM.MAP_ROLE_NAME_TO_ID][role['roleName']] = role['roleId']
|
||||
GM.Globals[GM.MAP_ROLE_NAME_TO_ID][role['roleName'].lower()] = role['roleId']
|
||||
|
||||
def role_from_roleid(roleid):
|
||||
if GM.Globals[GM.MAKE_ROLE_ID_NAME_MAP]:
|
||||
@@ -16220,7 +16221,7 @@ def role_from_roleid(roleid):
|
||||
def roleid_from_role(role):
|
||||
if GM.Globals[GM.MAKE_ROLE_ID_NAME_MAP]:
|
||||
makeRoleIdNameMap()
|
||||
return GM.Globals[GM.MAP_ROLE_NAME_TO_ID].get(role, None)
|
||||
return GM.Globals[GM.MAP_ROLE_NAME_TO_ID].get(role.lower(), None)
|
||||
|
||||
def getRoleId():
|
||||
role = getString(Cmd.OB_ROLE_ITEM)
|
||||
@@ -16344,7 +16345,7 @@ def _showAdminRole(role, i=0, count=0):
|
||||
def doInfoAdminRole():
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
fieldsList = PRINT_ADMIN_ROLES_FIELDS[:]
|
||||
role, roleId = getRoleId()
|
||||
_, roleId = getRoleId()
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'privileges':
|
||||
@@ -16506,7 +16507,7 @@ ASSIGNEE_EMAILTYPE_TOFIELD_MAP = {
|
||||
}
|
||||
PRINT_ADMIN_FIELDS = ['roleAssignmentId', 'roleId', 'assignedTo', 'scopeType', 'orgUnitId']
|
||||
PRINT_ADMIN_TITLES = ['roleAssignmentId', 'roleId', 'role',
|
||||
'assignedTo', 'assignedToUser', 'assignedToGroup', 'assignedToServiceAccount',
|
||||
'assignedTo', 'assignedToUser', 'assignedToGroup', 'assignedToServiceAccount', 'assignedToUnknown',
|
||||
'scopeType', 'orgUnitId', 'orgUnit']
|
||||
|
||||
# gam print admins [todrive <ToDriveAttribute>*]
|
||||
@@ -16536,6 +16537,7 @@ def doPrintShowAdmins():
|
||||
def _setNamesFromIds(admin, privileges):
|
||||
admin['role'] = role_from_roleid(admin['roleId'])
|
||||
assignedTo = admin['assignedTo']
|
||||
admin['assignedToUnknown'] = False
|
||||
if assignedTo not in assignedToIdEmailMap:
|
||||
assigneeType = admin.get('assigneeType')
|
||||
assignedToField = ASSIGNEE_EMAILTYPE_TOFIELD_MAP.get(assigneeType, None)
|
||||
@@ -16545,10 +16547,11 @@ def doPrintShowAdmins():
|
||||
emailTypes=list(ASSIGNEE_EMAILTYPE_TOFIELD_MAP.keys()))
|
||||
if not assignedToField and assigneeType in ASSIGNEE_EMAILTYPE_TOFIELD_MAP:
|
||||
assignedToField = ASSIGNEE_EMAILTYPE_TOFIELD_MAP[assigneeType]
|
||||
if assigneeType == 'unknown':
|
||||
assignedToField = 'assignedToUnknown'
|
||||
assigneeEmail = True
|
||||
assignedToIdEmailMap[assignedTo] = {'assignedToField': assignedToField, 'assigneeEmail': assigneeEmail}
|
||||
assignedToField = assignedToIdEmailMap[assignedTo]['assignedToField']
|
||||
if assignedToField:
|
||||
admin[assignedToField] = assignedToIdEmailMap[assignedTo]['assigneeEmail']
|
||||
admin[assignedToIdEmailMap[assignedTo]['assignedToField']] = assignedToIdEmailMap[assignedTo]['assigneeEmail']
|
||||
if privileges is not None:
|
||||
admin.update(privileges)
|
||||
if 'orgUnitId' in admin:
|
||||
@@ -17771,20 +17774,32 @@ ORG_ITEMS_FIELD_MAP = {
|
||||
'users': 'users',
|
||||
}
|
||||
|
||||
# gam check org|ou <OrgUnitItem> [todrive <ToDriveAttribute>*]
|
||||
# gam check ou|org <OrgUnitItem> [todrive <ToDriveAttribute>*]
|
||||
# [<OrgUnitCheckName>*|(fields <OrgUnitCheckNameList>)]
|
||||
# [filename <FileName>] [movetoou <OrgUnitItem>]
|
||||
# [formatjson [quotechar <Character>]]
|
||||
def doCheckOrgUnit():
|
||||
def writeCommandInfo(field):
|
||||
nonlocal commitBatch
|
||||
if commitBatch:
|
||||
f.write(f'{Cmd.COMMIT_BATCH_CMD}\n')
|
||||
else:
|
||||
commitBatch = True
|
||||
f.write(f'{Cmd.PRINT_CMD} Move {field} from {orgUnitPath} to {moveToOrgUnitPath}\n')
|
||||
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
csvPF = CSVPrintFile(['orgUnitPath', 'orgUnitId', 'empty'])
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
orgUnitPath = None
|
||||
f = orgUnitPath = None
|
||||
fieldsList = []
|
||||
titlesList = []
|
||||
status, orgUnitPath, orgUnitId = checkOrgUnitPathExists(cd, getOrgUnitItem())
|
||||
orgUnitPathLower = orgUnitPath.lower()
|
||||
if not status:
|
||||
entityDoesNotExistExit(Ent.ORGANIZATIONAL_UNIT, orgUnitPath)
|
||||
orgUnitPathLower = orgUnitPath.lower()
|
||||
fileName = 'CleanOuBatch.txt'
|
||||
moveToOrgUnitPath = moveToOrgUnitPathLower = None
|
||||
commitBatch = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -17797,12 +17812,26 @@ def doCheckOrgUnit():
|
||||
fieldsList.append(field)
|
||||
else:
|
||||
invalidChoiceExit(field, list(ORG_ITEMS_FIELD_MAP), True)
|
||||
elif myarg == 'filename':
|
||||
fileName = setFilePath(getString(Cmd.OB_FILE_NAME))
|
||||
elif myarg == 'movetoou':
|
||||
movetoouLocation = Cmd.Location()
|
||||
status, moveToOrgUnitPath, _ = checkOrgUnitPathExists(cd, getOrgUnitItem())
|
||||
moveToOrgUnitPathLower = moveToOrgUnitPath.lower()
|
||||
if not status:
|
||||
entityDoesNotExistExit(Ent.ORGANIZATIONAL_UNIT, moveToOrgUnitPath)
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if orgUnitPath is None:
|
||||
missingArgumentExit('orgunit <OrgUnitItem>')
|
||||
if not fieldsList:
|
||||
fieldsList = ORG_ITEMS_FIELD_MAP.keys()
|
||||
if moveToOrgUnitPath is not None:
|
||||
Cmd.SetLocation(movetoouLocation)
|
||||
if orgUnitPathLower == moveToOrgUnitPathLower:
|
||||
usageErrorExit(Msg.OU_AND_MOVETOOU_CANNOT_BE_IDENTICAL.format(orgUnitPath, moveToOrgUnitPath))
|
||||
if 'subous' in fieldsList and moveToOrgUnitPathLower.startswith(orgUnitPathLower):
|
||||
usageErrorExit(Msg.OU_SUBOUS_CANNOT_BE_MOVED_TO_MOVETOOU.format(orgUnitPath, moveToOrgUnitPath))
|
||||
fileName = setFilePath(fileName)
|
||||
f = openFile(fileName, DEFAULT_FILE_WRITE_MODE)
|
||||
orgUnitItemCounts = {}
|
||||
for field in sorted(fieldsList):
|
||||
title = ORG_ITEMS_FIELD_MAP[field]
|
||||
@@ -17823,6 +17852,9 @@ def doCheckOrgUnit():
|
||||
fields='nextPageToken,browsers(deviceId)')
|
||||
for browsers in feed:
|
||||
orgUnitItemCounts['browsers'] += len(browsers)
|
||||
if f is not None and orgUnitItemCounts['browsers'] > 0:
|
||||
writeCommandInfo('browsers')
|
||||
f.write(f'gam move browsers ou {moveToOrgUnitPath} browserou {orgUnitPath}\n')
|
||||
except (GAPI.invalidInput, GAPI.forbidden) as e:
|
||||
entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e))
|
||||
except GAPI.invalidOrgunit as e:
|
||||
@@ -17851,6 +17883,9 @@ def doCheckOrgUnit():
|
||||
if not pageToken:
|
||||
_finalizeGAPIpagesResult(pageMessage)
|
||||
printGotAccountEntities(totalItems)
|
||||
if f is not None and orgUnitItemCounts['devices'] > 0:
|
||||
writeCommandInfo('devices')
|
||||
f.write(f'gam update ou {moveToOrgUnitPath} add cros_ou {orgUnitPath}\n')
|
||||
break
|
||||
except GAPI.invalidInput as e:
|
||||
message = str(e)
|
||||
@@ -17879,8 +17914,18 @@ def doCheckOrgUnit():
|
||||
customer=_getCustomersCustomerIdWithC(),
|
||||
filter="type == 'shared_drive'")
|
||||
orgUnitItemCounts['sharedDrives'] = len(sds)
|
||||
if f is not None and orgUnitItemCounts['sharedDrives'] > 0:
|
||||
writeCommandInfo('Shared Drives')
|
||||
for sd in sds:
|
||||
name = sd['name'].split(';')[1]
|
||||
f.write(f'gam update shareddrive {name} ou {moveToOrgUnitPath}\n')
|
||||
if 'subous' in fieldsList:
|
||||
orgUnitItemCounts['subOus'] = len(_getOrgUnits(cd, orgUnitPath, ['orgUnitPath'], 'children', False, False, None, None))
|
||||
subOus = _getOrgUnits(cd, orgUnitPath, ['orgUnitPath'], 'children', False, False, None, None)
|
||||
orgUnitItemCounts['subOus'] = len(subOus)
|
||||
if f is not None and orgUnitItemCounts['subOus'] > 0:
|
||||
writeCommandInfo('Sub OrgUnit')
|
||||
for ou in subOus:
|
||||
f.write(f'gam update ou {ou["orgUnitPath"]} parent {moveToOrgUnitPath}\n')
|
||||
if 'users' in fieldsList:
|
||||
printGettingAllEntityItemsForWhom(Ent.USER, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT)
|
||||
pageMessage = getPageMessageForWhom()
|
||||
@@ -17895,9 +17940,15 @@ def doCheckOrgUnit():
|
||||
for user in users:
|
||||
if orgUnitPathLower == user.get('orgUnitPath', '').lower():
|
||||
orgUnitItemCounts['users'] += 1
|
||||
if f is not None and orgUnitItemCounts['users'] > 0:
|
||||
writeCommandInfo('users')
|
||||
f.write(f'gam update ou {moveToOrgUnitPath} add ou {orgUnitPath}\n')
|
||||
except (GAPI.invalidOrgunit, GAPI.orgunitNotFound, GAPI.invalidInput, GAPI.badRequest, GAPI.backendError,
|
||||
GAPI.invalidCustomerId, GAPI.loginRequired, GAPI.resourceNotFound, GAPI.forbidden):
|
||||
checkEntityDNEorAccessErrorExit(cd, Ent.ORGANIZATIONAL_UNIT, orgUnitPath)
|
||||
if f is not None:
|
||||
closeFile(f)
|
||||
writeStderr(Msg.GAM_BATCH_FILE_WRITTEN.format(fileName))
|
||||
empty = True
|
||||
for count in orgUnitItemCounts.values():
|
||||
if count > 0:
|
||||
@@ -17913,7 +17964,7 @@ def doCheckOrgUnit():
|
||||
csvPF.writeCSVfile(f'OrgUnit {orgUnitPath} Item Counts')
|
||||
if not empty and GM.Globals[GM.SYSEXITRC] == 0:
|
||||
setSysExitRC(ORGUNIT_NOT_EMPTY_RC)
|
||||
|
||||
|
||||
ALIAS_TARGET_TYPES = ['user', 'group', 'target']
|
||||
|
||||
# gam create aliases|nicknames <EmailAddressEntity> user|group|target <UniqueID>|<EmailAddress>
|
||||
@@ -24783,7 +24834,7 @@ def doInfoPrintShowCrOSTelemetry():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
csvPF.GetTodriveParameters()
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'limittoou', 'ouandchildren', 'crossn', 'filter']:
|
||||
elif myarg in {'ou', 'org', 'orgunit', 'limittoou', 'ouandchildren', 'crossn', 'filter'}:
|
||||
if pfilters:
|
||||
Cmd.Backup()
|
||||
usageErrorExit(Msg.ONLY_ONE_DEVICE_SELECTION_ALLOWED.format(pfilters[0][1]))
|
||||
@@ -24989,7 +25040,7 @@ def doMoveBrowsers():
|
||||
queryTimes = {}
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in ['ou', 'org', 'orgunit']:
|
||||
if myarg in {'ou', 'org', 'orgunit'}:
|
||||
orgUnitPath = getOrgUnitItem()
|
||||
elif myarg == 'ids':
|
||||
deviceIds.extend(convertEntityToList(getString(Cmd.OB_DEVICE_ID_LIST, minLen=0)))
|
||||
@@ -25154,7 +25205,7 @@ def doPrintShowBrowsers():
|
||||
queries = getQueries(myarg)
|
||||
elif myarg.startswith('querytime'):
|
||||
queryTimes[myarg] = getTimeOrDeltaFromNow()[0:19]
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'browserou']:
|
||||
elif myarg in {'ou', 'org', 'orgunit', 'browserou'}:
|
||||
orgUnitPath = getOrgUnitItem(pathOnly=True, absolutePath=True)
|
||||
elif myarg == 'select':
|
||||
_, entityList = getEntityToModify(defaultEntityType=Cmd.ENTITY_BROWSER, browserAllowed=True, crosAllowed=False, userAllowed=False)
|
||||
@@ -25263,7 +25314,7 @@ def doCreateBrowserToken():
|
||||
body = {'token_type': 'CHROME_BROWSER'}
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in ['ou', 'org', 'orgunit', 'browserou']:
|
||||
if myarg in {'ou', 'org', 'orgunit', 'browserou'}:
|
||||
body['org_unit_path'] = getOrgUnitItem(pathOnly=True, absolutePath=True)
|
||||
elif myarg in ['expire', 'expires']:
|
||||
body['expire_time'] = getTimeOrDeltaFromNow()
|
||||
@@ -25307,6 +25358,7 @@ BROWSER_TOKEN_FIELDS_CHOICE_MAP = {
|
||||
'org': 'orgUnitPath',
|
||||
'orgunit': 'orgUnitPath',
|
||||
'orgunitpath': 'orgUnitPath',
|
||||
'ou': 'orgUnitPath',
|
||||
'revoketime': 'revokeTime',
|
||||
'revokerid': 'revokerId',
|
||||
'state': 'state',
|
||||
@@ -25355,7 +25407,7 @@ def doPrintShowBrowserTokens():
|
||||
queries = getQueries(myarg)
|
||||
elif myarg.startswith('querytime'):
|
||||
queryTimes[myarg] = getTimeOrDeltaFromNow()[0:19]
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'browserou']:
|
||||
elif myarg in {'ou', 'org', 'orgunit', 'browserou'}:
|
||||
orgUnitPath = getOrgUnitItem(pathOnly=True, absolutePath=True)
|
||||
elif myarg == 'orderby':
|
||||
orderBy, sortOrder = getOrderBySortOrder(BROWSER_TOKEN_FIELDS_CHOICE_MAP, 'DESCENDING', True)
|
||||
@@ -28799,7 +28851,7 @@ def _getPrinterAttributes(cd, jsonDeleteFields):
|
||||
body['displayName'] = getString(Cmd.OB_STRING)
|
||||
elif myarg == 'makeandmodel':
|
||||
body['makeAndModel'] = getString(Cmd.OB_STRING)
|
||||
elif myarg in ['ou', 'org', 'orgunit', 'orgunitid']:
|
||||
elif myarg in {'ou', 'org', 'orgunit', 'orgunitid'}:
|
||||
_, body['orgUnitId'] = getOrgUnitId(cd)
|
||||
body['orgUnitId'] = body['orgUnitId'][3:]
|
||||
elif myarg == 'uri':
|
||||
@@ -37557,6 +37609,8 @@ def _updateCalendarEvents(origUser, user, origCal, calIds, count, calendarEventE
|
||||
body['attendees'].append(addAttendee)
|
||||
elif parameters['attendees']:
|
||||
body['attendees'] = parameters['attendees']
|
||||
else:
|
||||
body['attendees'] = []
|
||||
elif parameters['attendees']:
|
||||
body['attendees'] = parameters['attendees']
|
||||
else:
|
||||
@@ -39813,7 +39867,7 @@ def doCreateVaultHold():
|
||||
elif myarg in {'accounts', 'users', 'groups'}:
|
||||
accountsLocation = Cmd.Location()
|
||||
accounts = getEntityList(Cmd.OB_EMAIL_ADDRESS_ENTITY)
|
||||
elif myarg in {'orgunit', 'org', 'ou'}:
|
||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||
body['orgUnit'] = {'orgUnitId': getOrgUnitId()[1]}
|
||||
elif _getHoldQueryParameters(myarg, queryParameters):
|
||||
pass
|
||||
@@ -39881,7 +39935,7 @@ def doUpdateVaultHold():
|
||||
elif myarg in {'removeusers', 'removeaccounts', 'removegroups'}:
|
||||
removeAccountsLocation = Cmd.Location()
|
||||
removeAccounts = getEntityList(Cmd.OB_EMAIL_ADDRESS_ENTITY)
|
||||
elif myarg in {'orgunit', 'org', 'ou'}:
|
||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||
body['orgUnit'] = {'orgUnitId': getOrgUnitId()[1]}
|
||||
elif _getHoldQueryParameters(myarg, queryParameters):
|
||||
pass
|
||||
@@ -43087,6 +43141,7 @@ USER_FIELDS_CHOICE_MAP = {
|
||||
'organizations': 'organizations',
|
||||
'organisation': 'organizations',
|
||||
'organisations': 'organizations',
|
||||
'orgunit': 'orgUnitPath',
|
||||
'orgunitpath': 'orgUnitPath',
|
||||
'otheremail': 'emails',
|
||||
'otheremails': 'emails',
|
||||
@@ -44892,7 +44947,7 @@ def _getInboundSSOAssignmentArguments(ci, cd, body):
|
||||
body['signInBehavior'] = {'redirectCondition': 'NEVER'}
|
||||
elif myarg == 'group':
|
||||
_, body['targetGroup'], _ = convertGroupEmailToCloudID(ci, getString(Cmd.OB_STRING))
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||
body['targetOrgUnit'] = getCIOrgunitID(cd, getString(Cmd.OB_ORGUNIT_ITEM))
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
@@ -61621,10 +61676,10 @@ def emptyDriveTrash(users):
|
||||
kwargs['driveId'] = fileIdEntity['shareddrive']['driveId']
|
||||
try:
|
||||
callGAPI(drive.files(), 'emptyTrash',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND],
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.INSUFFICIENT_FILE_PERMISSIONS],
|
||||
**kwargs)
|
||||
entityActionPerformed([Ent.USER, user, Ent.DRIVE_TRASH, kwargs['driveId']], i, count)
|
||||
except GAPI.notFound as e:
|
||||
except (GAPI.notFound, GAPI.insufficientFilePermissions) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.SHAREDDRIVE_ID, kwargs['driveId']], str(e), i, count)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
|
||||
@@ -63493,7 +63548,7 @@ def createSharedDrive(users, useDomainAdminAccess=False):
|
||||
waitingForCreationToComplete(updateInitialDelay)
|
||||
created = False
|
||||
retry = 0
|
||||
while True:
|
||||
while not created:
|
||||
try:
|
||||
callGAPI(drive.drives(), 'get',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND],
|
||||
@@ -64015,7 +64070,7 @@ def doPrintShowOrgunitSharedDrives():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
csvPF.GetTodriveParameters()
|
||||
elif myarg in ['ou', 'org', 'orgunit']:
|
||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||
orgUnitPath = getString(Cmd.OB_ORGUNIT_ITEM)
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
@@ -65222,16 +65277,16 @@ def checkUserInGroups(users):
|
||||
|
||||
# gam <UserTypeEntity> print groups [todrive <ToDriveAttribute>*]
|
||||
# [(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
# [roles <GroupRoleList>] [countsonly|nodetails]
|
||||
# [roles <GroupRoleList>] [countsonly|totalonly|nodetails]
|
||||
# gam <UserTypeEntity> show groups
|
||||
# [(domain <DomainName>)|(customerid <CustomerID>)]
|
||||
# [roles <GroupRoleList>] [countsonly|nodetails]
|
||||
# [roles <GroupRoleList>] [countsonly|totalonly|nodetails]
|
||||
def printShowUserGroups(users):
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
kwargs = {'customer': GC.Values[GC.CUSTOMER_ID]}
|
||||
csvPF = CSVPrintFile(['User', 'Group', 'Role', 'Status', 'Delivery'], 'sortall') if Act.csvFormat() else None
|
||||
rolesSet = set()
|
||||
countsOnly = noDetails = False
|
||||
countsOnly = noDetails = totalOnly = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if csvPF and myarg == 'todrive':
|
||||
@@ -65246,6 +65301,8 @@ def printShowUserGroups(users):
|
||||
invalidChoiceExit(role, GROUP_ROLES_MAP, True)
|
||||
elif myarg == 'countsonly':
|
||||
countsOnly = True
|
||||
elif myarg == 'totalonly':
|
||||
countsOnly = totalOnly = True
|
||||
elif myarg == 'nodetails':
|
||||
noDetails = True
|
||||
else:
|
||||
@@ -65259,15 +65316,17 @@ def printShowUserGroups(users):
|
||||
csvPF.SetTitles(titles)
|
||||
csvPF.SetSortTitles([])
|
||||
elif countsOnly:
|
||||
zeroCounts = {'User': None}
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
if role in rolesSet:
|
||||
zeroCounts[role] = 0
|
||||
if csvPF:
|
||||
titles = ['User']
|
||||
zeroCounts = {'User': None, 'Total': 0}
|
||||
if not totalOnly:
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
if role in rolesSet:
|
||||
titles.append(role)
|
||||
zeroCounts[role] = 0
|
||||
if csvPF:
|
||||
titles = ['User', 'Total']
|
||||
if not totalOnly:
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
if role in rolesSet:
|
||||
titles.append(role)
|
||||
csvPF.SetTitles(titles)
|
||||
csvPF.SetSortTitles([])
|
||||
i, count, users = getEntityArgument(users)
|
||||
@@ -65295,6 +65354,12 @@ def printShowUserGroups(users):
|
||||
return
|
||||
accessErrorExit(cd)
|
||||
jcount = len(entityList)
|
||||
if totalOnly:
|
||||
if not csvPF:
|
||||
printEntityKVList([Ent.USER, user], ['Total', jcount])
|
||||
else:
|
||||
csvPF.WriteRow({'User': user, 'Total': jcount})
|
||||
continue
|
||||
if not csvPF:
|
||||
if allRoles:
|
||||
entityPerformActionNumItems([Ent.USER, user], jcount, Ent.GROUP, i, count)
|
||||
@@ -65340,8 +65405,11 @@ def printShowUserGroups(users):
|
||||
except (GAPI.memberNotFound, GAPI.invalidMember, GAPI.conditionNotMet, GAPI.serviceNotAvailable) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.GROUP, groupEmail], str(e), j, jcount)
|
||||
if countsOnly:
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
if role in rolesSet:
|
||||
userCounts['Total'] += userCounts[role]
|
||||
if not csvPF:
|
||||
kvList = []
|
||||
kvList = ['Total', userCounts['Total']]
|
||||
for role in [Ent.ROLE_MEMBER, Ent.ROLE_MANAGER, Ent.ROLE_OWNER]:
|
||||
if role in rolesSet:
|
||||
kvList.extend([role, userCounts[role]])
|
||||
@@ -65597,9 +65665,9 @@ def _createLicenses(lic, productId, skuId, parameters, jcount, users, i, count,
|
||||
noAvailableLicenses = False
|
||||
doneSet = set()
|
||||
if not returnDoneSet:
|
||||
entityPerformActionNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], jcount, Ent.USER, i, count)
|
||||
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.TO_LC, jcount, Ent.USER, i, count)
|
||||
else:
|
||||
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.MAXIMUM_OF, jcount, Ent.USER, i, count)
|
||||
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.TO_MAXIMUM_OF, jcount, Ent.USER, i, count)
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for user in users:
|
||||
@@ -65664,7 +65732,7 @@ def updateLicense(users):
|
||||
message = Act.PREVIEW
|
||||
productId, skuId, oldSkuId = parameters[LICENSE_PRODUCT_SKUIDS][0]
|
||||
body = {'skuId': skuId}
|
||||
entityPerformActionNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], jcount, Ent.USER)
|
||||
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.FOR, jcount, Ent.USER)
|
||||
Ind.Increment()
|
||||
for user in users:
|
||||
j += 1
|
||||
@@ -65697,7 +65765,7 @@ def _deleteLicenses(lic, productId, skuId, parameters, jcount, users, i, count):
|
||||
Act.Set([Act.DELETE, Act.DELETE_PREVIEW][parameters['preview']])
|
||||
if parameters['preview']:
|
||||
message = Act.PREVIEW
|
||||
entityPerformActionNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], jcount, Ent.USER, i, count)
|
||||
entityPerformActionModifierNumItems([Ent.LICENSE, SKU.skuIdToDisplayName(skuId)], Msg.FROM_LC, jcount, Ent.USER, i, count)
|
||||
Ind.Increment()
|
||||
j = 0
|
||||
for user in users:
|
||||
@@ -68382,8 +68450,8 @@ def forwardMessagesThreads(users, entityType):
|
||||
if not gmail:
|
||||
continue
|
||||
service = gmail.users().messages() if entityType == Ent.MESSAGE else gmail.users().threads()
|
||||
try:
|
||||
if parameters['messageEntity'] is None:
|
||||
if parameters['messageEntity'] is None:
|
||||
try:
|
||||
printGettingAllEntityItemsForWhom(entityType, user, i, count)
|
||||
listResult = callGAPIpages(service, 'list', parameters['listType'],
|
||||
pageMessage=getPageMessageForWhom(), maxItems=parameters['maxItems'],
|
||||
@@ -68391,12 +68459,12 @@ def forwardMessagesThreads(users, entityType):
|
||||
userId='me', q=parameters['query'], fields=parameters['fields'], includeSpamTrash=includeSpamTrash,
|
||||
maxResults=GC.Values[GC.MESSAGE_MAX_RESULTS])
|
||||
entityIds = [entity['id'] for entity in listResult]
|
||||
except (GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.invalid, GAPI.invalidArgument) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
continue
|
||||
except (GAPI.serviceNotAvailable, GAPI.badRequest):
|
||||
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
|
||||
continue
|
||||
except (GAPI.failedPrecondition, GAPI.permissionDenied, GAPI.invalid, GAPI.invalidArgument) as e:
|
||||
entityActionFailedWarning([Ent.USER, user], str(e), i, count)
|
||||
continue
|
||||
except (GAPI.serviceNotAvailable, GAPI.badRequest):
|
||||
entityServiceNotApplicableWarning(Ent.USER, user, i, count)
|
||||
continue
|
||||
jcount = len(entityIds)
|
||||
if jcount == 0:
|
||||
entityNumEntitiesActionNotPerformedWarning([Ent.USER, user], entityType, jcount, Msg.NO_ENTITIES_MATCHED.format(Ent.Plural(entityType)), i, count)
|
||||
@@ -74403,6 +74471,8 @@ MAIN_COMMANDS_OBJ_ALIASES = {
|
||||
Cmd.ARG_MOBILES: Cmd.ARG_MOBILE,
|
||||
Cmd.ARG_NICKNAME: Cmd.ARG_ALIAS,
|
||||
Cmd.ARG_NICKNAMES: Cmd.ARG_ALIAS,
|
||||
Cmd.ARG_ORGUNIT: Cmd.ARG_ORG,
|
||||
Cmd.ARG_ORGUNITS: Cmd.ARG_ORGS,
|
||||
Cmd.ARG_ORGUNITSHAREDDRIVES: Cmd.ARG_ORGUNITSHAREDDRIVE,
|
||||
Cmd.ARG_OU: Cmd.ARG_ORG,
|
||||
Cmd.ARG_OUS: Cmd.ARG_ORGS,
|
||||
|
||||
@@ -672,6 +672,8 @@ class GamCLArgs():
|
||||
ARG_ORG = 'org'
|
||||
ARG_ORGS = 'orgs'
|
||||
ARG_ORGTREE = 'orgtree'
|
||||
ARG_ORGUNIT = 'orgunit'
|
||||
ARG_ORGUNITS = 'orgunits'
|
||||
ARG_ORGUNITSHAREDDRIVE = 'orgunitshareddrive'
|
||||
ARG_ORGUNITSHAREDDRIVES = 'orgunitshareddrives'
|
||||
ARG_ORPHANS = 'orphans'
|
||||
|
||||
@@ -257,7 +257,9 @@ FORBIDDEN = 'Forbidden'
|
||||
FORMAT_NOT_AVAILABLE = 'Format ({0}) not available'
|
||||
FORMAT_NOT_DOWNLOADABLE = 'Format not downloadable'
|
||||
FROM = 'From'
|
||||
FROM_LC = 'from'
|
||||
FULL_PATH_MUST_START_WITH_DRIVE = 'fullpath must start with {0} or {1}'
|
||||
GAM_BATCH_FILE_WRITTEN = 'GAM batch file {0} written\n'
|
||||
GAM_LATEST_VERSION_NOT_AVAILABLE = 'GAM Latest Version information not available'
|
||||
GAM_OUT_OF_MEMORY = 'GAM has run out of memory. If this is a large Google Workspace instance, you should use a 64-bit version of GAM on Windows or a 64-bit version of Python on other systems.'
|
||||
GENERATING_NEW_PRIVATE_KEY = 'Generating new private key'
|
||||
@@ -420,6 +422,8 @@ ONLY_ONE_DEVICE_SELECTION_ALLOWED = 'Only one device selection allowed, filter =
|
||||
ONLY_ONE_JSON_RANGE_ALLOWED = 'Only one range/json allowed'
|
||||
ONLY_ONE_OWNER_ALLOWED = 'Only one owner allowed'
|
||||
OR = 'or'
|
||||
OU_AND_MOVETOOU_CANNOT_BE_IDENTICAL = 'ou {0} can not be be identical to movetoou {1}'
|
||||
OU_SUBOUS_CANNOT_BE_MOVED_TO_MOVETOOU = 'ou {0} sub OUs can not be be moved to movetoou {1}'
|
||||
PERMISSION_DENIED = 'The caller does not have permission'
|
||||
PLEASE_CORRECT_YOUR_SYSTEM_TIME = 'Please correct your system time.'
|
||||
PLEASE_ENTER_A_OR_M = 'Please enter a or m ...\n'
|
||||
@@ -465,6 +469,8 @@ TASKLIST_TITLE_NOT_FOUND = 'Task list title not found'
|
||||
THREAD = 'thread'
|
||||
THREADS = 'threads'
|
||||
TO = 'To'
|
||||
TO_LC = 'to'
|
||||
TO_MAXIMUM_OF = 'to maximum of'
|
||||
TO_SET_UP_GOOGLE_CHAT = """
|
||||
To set up Google Chat for your API project, please go to:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user