Compare commits

..

16 Commits

Author SHA1 Message Date
Ross Scroggs
71bf658e17 Clarified action to perform messages when creating/deleting/updating licenses.
Some checks failed
Build and test GAM / build (Win64, build, 8, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 2, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (universal2, build, 7, darwin64-arm64 darwin64-x86_64, macos-14) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-20.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 3, linux-x86_64, ubuntu-20.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, darwin64-x86_64, macos-12) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-22.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-22.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-22.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 9, ubuntu-22.04, 3.8) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-08-05 10:14:29 -07:00
Ross Scroggs
8211d5df8c Updated gam <UserTypeEntity> print|show groups` to show totals
Some checks failed
Build and test GAM / build (Win64, build, 8, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 2, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (universal2, build, 7, darwin64-arm64 darwin64-x86_64, macos-14) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-20.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 3, linux-x86_64, ubuntu-20.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, darwin64-x86_64, macos-12) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-22.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-22.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-22.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 9, ubuntu-22.04, 3.8) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
2024-07-31 16:37:28 -07:00
Ross Scroggs
10e54e49a5 Fixed bug in gam calendars <CalendarEntity> update event ... removeattendee <EmailAddress>
Some checks are pending
Build and test GAM / build (Win64, build, 8, VC-WIN64A, windows-2022) (push) Waiting to run
Build and test GAM / build (aarch64, build, 2, linux-aarch64, [self-hosted linux arm64]) (push) Waiting to run
Build and test GAM / build (aarch64, build, 4, linux-aarch64, [self-hosted linux arm64], yes) (push) Waiting to run
Build and test GAM / build (aarch64, build, 6, darwin64-arm64, macos-14) (push) Waiting to run
Build and test GAM / build (universal2, build, 7, darwin64-arm64 darwin64-x86_64, macos-14) (push) Waiting to run
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-20.04) (push) Waiting to run
Build and test GAM / build (x86_64, build, 3, linux-x86_64, ubuntu-20.04, yes) (push) Waiting to run
Build and test GAM / build (x86_64, build, 5, darwin64-x86_64, macos-12) (push) Waiting to run
Build and test GAM / build (x86_64, test, 10, ubuntu-22.04, 3.9) (push) Waiting to run
Build and test GAM / build (x86_64, test, 11, ubuntu-22.04, 3.10) (push) Waiting to run
Build and test GAM / build (x86_64, test, 12, ubuntu-22.04, 3.11) (push) Waiting to run
Build and test GAM / build (x86_64, test, 9, ubuntu-22.04, 3.8) (push) Waiting to run
Build and test GAM / merge (push) Blocked by required conditions
Build and test GAM / publish (push) Blocked by required conditions
CodeQL / Analyze (python) (push) Waiting to run
Check for Google Root CA Updates / check-apis (push) Waiting to run
2024-07-30 11:51:12 -07:00
Ross Scroggs
6b9ac2700e Updated gam <UserTypeEntity> empty drivetrash <SharedDriveEntity>
Some checks failed
Build and test GAM / build (Win64, build, 8, VC-WIN64A, windows-2022) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 2, linux-aarch64, [self-hosted linux arm64]) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 4, linux-aarch64, [self-hosted linux arm64], yes) (push) Has been cancelled
Build and test GAM / build (aarch64, build, 6, darwin64-arm64, macos-14) (push) Has been cancelled
Build and test GAM / build (universal2, build, 7, darwin64-arm64 darwin64-x86_64, macos-14) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 1, linux-x86_64, ubuntu-20.04) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 3, linux-x86_64, ubuntu-20.04, yes) (push) Has been cancelled
Build and test GAM / build (x86_64, build, 5, darwin64-x86_64, macos-12) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 10, ubuntu-22.04, 3.9) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 11, ubuntu-22.04, 3.10) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 12, ubuntu-22.04, 3.11) (push) Has been cancelled
Build and test GAM / build (x86_64, test, 9, ubuntu-22.04, 3.8) (push) Has been cancelled
Build and test GAM / merge (push) Has been cancelled
Build and test GAM / publish (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Check for Google Root CA Updates / check-apis (push) Has been cancelled
2024-07-26 17:09:27 -07:00
Ross Scroggs
012616a285 Fix bug in check ou 2024-07-25 19:16:01 -07:00
Ross Scroggs
2669b1bff6 Added options filename <FileName> and movetoou <OrgUnitItem> to gam check ou <OrgUnitItem> 2024-07-25 18:28:28 -07:00
Ross Scroggs
2aeebd17a4 Added column|field assignedToUnknown to gam print|show admins 2024-07-24 08:49:09 -07:00
Ross Scroggs
e43802e197 Updated gam print admins to handle the following error
ERROR: 404: notFound - Requested entity was not found.
2024-07-23 16:00:36 -07:00
Ross Scroggs
16b3d2b006 Allow <RoleItem> in any case 2024-07-22 20:47:05 -07:00
Ross Scroggs
f777ec177c Updated code to work around a Cryptography library change 2024-07-22 08:37:33 -07:00
Ross Scroggs
19304f95e8 Added command to check if an OU contains items 2024-07-21 13:14:15 -07:00
Ross Scroggs
5b49b8c957 Added option showitemcountonly to gam print domainaliasess 2024-07-19 16:11:57 -07:00
Ross Scroggs
f1e599d535 Added option showitemcountonly to gam print domains 2024-07-18 13:19:41 -07:00
Ross Scroggs
752b502399 Fixed bug in gam <UserTypeEntity> print filelist that caused a trap. 2024-07-18 07:07:54 -07:00
Ross Scroggs
8e3d562830 Revert to setuptools 70.3.0 2024-07-17 19:05:54 -07:00
Ross Scroggs
5b6c7a30d7 Updated gam calendars <CalendarEntity> import event icaluid <iCalUID> json <JSONdata> 2024-07-17 16:50:04 -07:00
25 changed files with 839 additions and 261 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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"

View File

@@ -5,8 +5,10 @@
- [Promote a domain to be primary](#promote-a-domain-to-be-primary)
- [Delete a domain](#delete-a-domain)
- [Display domains](#display-domains)
- [Display domains count](#display-domains-count)
- [Create and delete domain aliases](#create-and-delete-domain-aliases)
- [Display domain aliases](#display-domain-aliases)
- [Display domain aliases count](#display-domain-aliases-count)
## API documentation
* https://developers.google.com/admin-sdk/directory/reference/rest/v1/domains
@@ -30,15 +32,18 @@ gam delete domain <DomainName>
```
## Display domains
```
gam info domain [<DomainName>] [formatjson]
gam show domains [formatjson]
gam info domain [<DomainName>]
[formatjson]
gam show domains
[formatjson]
```
For `info`, if `<DomainName>` is omitted, information about the primary domain will be displayed.
By default, Gam displays the information as an indented list of keys and values.
* `formatjson` - Display the fields in JSON format.
```
gam print domains [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]
gam print domains [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
```
By default, Gam displays the information as columns of fields.
* `formatjson` - Display the fields in JSON format.
@@ -49,6 +54,13 @@ 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.
## Display domains count
Display the number of domains.
```
gam print|show domains
showitemcountonly
```
## Create and delete domain aliases
```
gam create domainalias|aliasdomain <DomainAlias> <DomainName>
@@ -56,13 +68,16 @@ gam delete domainalias|aliasdomain <DomainAlias>
```
## Display domain aliases
```
gam info domainalias|aliasdomain <DomainAlias> [formatjson]
gam show domainaliases|aliasdomains [formatjson] [formatjson [quotechar <Character>]]
gam info domainalias|aliasdomain <DomainAlias>
[formatjson]
gam show domainaliases|aliasdomains
[formatjson]
```
By default, Gam displays the information as an indented list of keys and values.
* `formatjson` - Display the fields in JSON format.
```
gam print domainaliases|aliasdomains [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]
gam print domainaliases|aliasdomains [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
```
By default, Gam displays the information as columns of fields.
* `formatjson` - Display the fields in JSON format.
@@ -73,3 +88,9 @@ 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.
## Display domain aliases count
Display the number of domain aliases.
```
gam print|show domainaliases|aliasdomains
showitemcountonly
```

View File

@@ -23,42 +23,42 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
- `gamadv-xtd3-6.wx.yz-linux-x86_64-glibc2.19.tar.xz`
- `gamadv-xtd3-6.wx.yz-linux-x86_64-legacy.tar.xz`
- Download the archive, extract the contents into some directory.
- Start a terminal session and cd to the install directory.
- Start a terminal session.
* Executable Archive, Manual, Raspberry Pi/ChromeOS ARM devices
- `gamadv-xtd3-6.wx.yz-linux-arm64-glibc2.31.tar.xz`
- `gamadv-xtd3-6.wx.yz-linux-arm64-glibc2.27.tar.xz`
- `gamadv-xtd3-6.wx.yz-linux-arm64-glibc2.23.tar.xz`
- Download the archive, extract the contents into some directory.
- Start a terminal session and cd to the install directory.
- Start a terminal session.
* Executable Archive, Manual, Mac OS versions Big Sur, Monterey, Ventura - M1/M2
- `gamadv-xtd3-6.wx.yz-macos-arm64.tar.xz`
- Download the archive, extract the contents into some directory.
- Start a terminal session and cd to the install directory.
- Start a terminal session.
* Executable Archive, Manual, Mac OS, versions Big Sur, Monterey, Ventura - Intel
- `gamadv-xtd3-6.wx.yz-macos-x86_64.tar.xz`
- Download the archive, extract the contents into some directory.
- Start a terminal session and cd to the install directory.
- Start a terminal session.
* Executable Archive, Manual, Windows 64 bit
- `gamadv-xtd3-6.wx.yz-windows-x86_64.zip`
- Download the archive, extract the contents into some directory.
- Start a Command Prompt/PowerShell session and cd to the install directory.
- Start a Command Prompt/PowerShell session.
* Executable Installer, Manual, Windows 64 bit
- `gamadv-xtd3-6.wx.yz-windows-x86_64.msi`
- Download the installer and run it.
- Start a Command Prompt/PowerShell session and cd to the install directory.
- Start a Command Prompt/PowerShell session.
* Winget
- `winget install taers232c.GAMADV-XTD3 --location C:\GAMADV-XTD3`
- Specify an alternate location if desired
- Start a Command Prompt/PowerShell session and cd to the install directory.
- Start a Command Prompt/PowerShell session.
* Source, all platforms
- `Source code(zip)`
- `Source code(tar.gz)`
- Download the archive, extract the contents into some directory.
- Start a terminal/Command Prompt/PowerShell session and cd to the install directory.
- Start a terminal/Command Prompt/PowerShell session.

View File

@@ -32,6 +32,7 @@ ENTITY_IS_A_USER_ALIAS_RC = 21
ENTITY_IS_A_GROUP_RC = 22
ENTITY_IS_A_GROUP_ALIAS_RC = 23
ENTITY_IS_AN_UNMANAGED_ACCOUNT_RC = 24
ORGUNIT_NOT_EMPTY_RC = 25
CHECK_USER_GROUPS_ERROR_RC = 29
ORPHANS_COLLECTED_RC = 30
# Warnings/Errors
@@ -61,4 +62,14 @@ TARGET_DRIVE_SPACE_ERROR_RC = 74
USER_REQUIRED_TO_CHANGE_PASSWORD_ERROR_RC = 75
USER_SUSPENDED_ERROR_RC = 76
NO_CSV_DATA_TO_UPLOAD_RC = 77
NO_SA_ACCESS_CONTEXT_MANAGER_EDITOR_ROLE_RC = 78
ACCESS_POLICY_ERROR_RC = 79
YUBIKEY_CONNECTION_ERROR_RC = 80
YUBIKEY_INVALID_KEY_TYPE_RC = 81
YUBIKEY_INVALID_SLOT_RC = 82
YUBIKEY_INVALID_PIN_RC = 83
YUBIKEY_APDU_ERROR_RC = 84
YUBIKEY_VALUE_ERROR_RC = 85
YUBIKEY_MULTIPLE_CONNECTED_RC = 86
YUBIKEY_NOT_FOUND_RC = 87
```

View File

@@ -8,7 +8,87 @@ Automatic update to the latest version on Linux/Mac OS/Google Cloud Shell/Raspbe
By default, a folder, `gamadv-xtd3`, is created in the default or specified path and the files are downloaded into that folder.
Add the `-s` option to the end of the above commands to suppress creating the `gamadv-xtd3` folder; the files are downloaded directly into the default or specified path.
See [Downloads](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads) for Windows or other options, including manual installation
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
as it must not contain any items in order to be deleted.
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Organizational-Units#check-organizational-unit-for-contained-items
### 6.77.18
Added option `showitemcountonly` to `gam print domainaliases` that causes GAM to display the
number of domain aliasess on stdout; no CSV file is written.
### 6.77.17
Added option `showitemcountonly` to `gam print domains` that causes GAM to display the
number of domains on stdout; no CSV file is written.
### 6.77.16
Fixed bug in `gam <UserTypeEntity> print filelist` that caused a trap.
### 6.77.15
Updated `gam calendars <CalendarEntity> import event icaluid <iCalUID> json <JSONdata>` to handle API
constraints on recurring events.
### 6.77.14

View File

@@ -39,7 +39,7 @@ To run all commands properly, GAMADV-XTD3 requires three things:
Use these steps if you have never used any version of GAM in your domain. They will create a GAM project
and all necessary authentications.
* Download: [Downloads](Downloads)
* Download: [Downloads-Installs](Downloads-Installs)
* Configuration: [GAM Configuration](gam.cfg)
* Install: [How to Install Advanced GAM](How-to-Install-Advanced-GAM)
@@ -47,7 +47,7 @@ and all necessary authentications.
Use these steps if you have used any version of GAM in your domain. They will update your GAM project
and all necessary authentications.
* Download: [Downloads](Downloads)
* Download: [Downloads-Installs](Downloads-Installs)
* Configuration: [GAM Configuration](gam.cfg)
* Upgrade: [How to Upgrade from Standard GAM](How-to-Upgrade-from-Standard-GAM)
@@ -56,7 +56,7 @@ Use these steps if you already use GAMADV-X or GAMADV-XTD or GAMADV-XTD3. The up
or authentications because new features have been included.
* Updates: [GAM Updates]
* Download: [Downloads](Downloads)
* Download: [Downloads-Installs](Downloads-Installs)
You can install multiple versions of GAM and GAMADV-XTD3 in different parallel directories.

View File

@@ -27,7 +27,7 @@ substitute that value in the directions.
Make the directory:
```
mkdir -p /Users/admin/GAMconfig
mkdir -p /Users/admin/GAMConfig
```
Add the following line:
@@ -42,14 +42,7 @@ to one of these files based on your shell:
~/.profile
```
You need to enable this setting in the environment. The easiest way is probably to close your terminal and open a new session. This will load the environment variables, including the one you just added. Test this by issuing this command:
```
echo $GAMCFGDIR
```
This should print the name of the directory you used above.
Alternatively, without starting a new session, load the new variable in this session directly: issue the following command replacing `<Filename>` with the name of the file you edited:
Issue the following command replacing `<Filename>` with the name of the file you edited:
```
source <Filename>
```
@@ -66,7 +59,11 @@ data in this folder and execute GAM commands from this folder. You should not us
/Users/admin/bin/gamadv-xtd3 or /Users/admin/GAMConfig for this purpose.
This example assumes that the GAM working directory will be /Users/admin/GAMWork; If you've chosen
another directory, substitute that value in the directions.
* Make the /Users/admin/GAMWork directory before proceeding.
Make the directory:
```
mkdir -p /Users/admin/GAMWork
```
### Set an alias
You should set an alias to point to /Users/admin/bin/gamadv-xtd3/gam so you can operate from the /Users/admin/GAMWork directory.
@@ -103,9 +100,12 @@ Created: /Users/admin/GAMConfig
Created: /Users/admin/GAMConfig/gamcache
Config File: /Users/admin/GAMConfig/gam.cfg, Initialized
Section: DEFAULT
activity_max_results = 100
...
[long list of all config settings that should match the directories you specified]
cache_dir = /Users/admin/GAMConfig/gamcache
...
config_dir = /Users/admin/GAMConfig
...
drive_dir = /Users/admin/GAMWork
...
admin@server:/Users/admin$
@@ -476,9 +476,12 @@ Default Language: en
admin@server:/Users/admin$ gam config customer_id C01234567 domain domain.com timezone local save verify
Config File: /Users/admin/GAMConfig/gam.cfg, Saved
Section: DEFAULT
activity_max_results = 100
...
[long list of all config settings that should match the data you specified]
customer_id = C01234567
...
domain = domain.com
...
timezone = local
...
admin@server:/Users/admin$
@@ -542,9 +545,12 @@ Created: C:\GAMConfig
Created: C:\GAMConfig\gamcache
Config File: C:\GAMConfig\gam.cfg, Initialized
Section: DEFAULT
activity_max_results = 100
...
[long list of all config settings that should match the directories you specified]
cache_dir = C:\GAMConfig\gamcache
...
config_dir = C:\GAMConfig
...
drive_dir = C:\GAMWork
...
C:\>
@@ -920,9 +926,12 @@ Default Language: en
C:\>gam config customer_id C01234567 domain domain.com timezone local save verify
Config File: C:\GAMConfig\gam.cfg, Saved
Section: DEFAULT
activity_max_results = 100
...
[long list of all config settings that should match the directories you specified]
customer_id = C01234567
...
domain = domain.com
...
timezone = local
...
C:\>

View File

@@ -1,7 +1,7 @@
# Updating GAMADV-XTD3
Use these steps to update your version of GAMADV-XTD3.
- [Downloads](Downloads)
- [Downloads-Installs](Downloads-Installs)
- [Linux and MacOS and Google Cloud Shell](#linux-and-mac-os-and-google-cloud-shell)
- [Windows](#windows)
- [GAM Configuration](gam.cfg)
@@ -13,7 +13,7 @@ Use these steps to update your version of GAMADV-XTD3.
This example assumes that GAMADV-XTD3 has been installed in /Users/admin/bin/gamadv-xtd3.
If you've installed GAMADV-XTD3 in another directory, substitute that value in the directions when downloading.
See: [Downloads](Downloads)
See: [Downloads-Installs](Downloads-Installs)
In these examples, your Google Super admin is shown as admin@domain.com; replace with the
actual email adddress.
@@ -301,7 +301,7 @@ admin@server:/Users/admin/bin/gamadv-xtd3$
This example assumes that GAMADV-XTD3 has been installed in C:\GAMADV-XTD3.
If you've installed GAMADV-XTD3 in another directory, substitute that value in the directions when downloading.
See: [Downloads](Downloads)
See: [Downloads-Installs](Downloads-Installs)
In these examples, your Google Super admin is shown as admin@domain.com; replace with the
actual email adddress.

View File

@@ -27,7 +27,7 @@ substitute that value in the directions.
Make the directory:
```
mkdir -p /Users/admin/GAMconfig
mkdir -p /Users/admin/GAMConfig
```
Add the following line:
@@ -47,7 +47,10 @@ Issue the following command replacing `<Filename>` with the name of the file you
source <Filename>
```
* Make the /Users/admin/GAMConfig directory before proceeding.
You need to make sure the GAM configuration directory actually exists. Test that like this:
```
ls -l $GAMCFGDIR
```
### Set a working directory
@@ -56,10 +59,15 @@ data in this folder and execute GAM commands from this folder. You should not us
/Users/admin/bin/gamadv-xtd3 or /Users/admin/GAMConfig for this purpose.
This example assumes that the GAM working directory will be /Users/admin/GAMWork; If you've chosen
another directory, substitute that value in the directions.
* Make the /Users/admin/GAMWork directory before proceeding.
Make the directory:
```
mkdir -p /Users/admin/GAMWork
```
### Set an alias
You should set an alias to point to /Users/admin/bin/gamadv-xtd3/gam so you can operate from the /Users/admin/GAMWork directory.
Aliases aren't available in scripts, so you may want to set a symlink instead, see below.
Add the following line:
```
@@ -91,6 +99,12 @@ Issue the following command replacing `<Filename>` with the name of the file you
source <Filename>
```
### Set a symlink
Set a symlink in `/usr/local/bin` (or some other location on $PATH) to point to GAM.
```
ln -s "/Users/admin/bin/gamadv-xtd3/gam" /usr/local/bin/gam
```
Set environment variable OLDGAMPATH to point to the existing Gam directory; /Users/admin/bin/gam will be used in this example.
If your existing Gam is in another directory, substitute that value in the directions.
```
@@ -113,122 +127,13 @@ Copied: /Users/admin/bin/gam/oauth2.txt, To: /Users/admin/GAMConfig/oauth2.txt
Copied: /Users/admin/bin/gam/client_secrets.json, To: /Users/admin/GAMConfig/client_secrets.json
Config File: /Users/admin/GAMConfig/gam.cfg, Initialized
Section: DEFAULT
activity_max_results = 100
admin_email = ''
api_calls_rate_check = false
api_calls_rate_limit = 100
api_calls_tries_limit = 10
auto_batch_min = 0
bail_on_internal_error_tries = 2
batch_size = 50
cacerts_pem = ''
...
cache_dir = /Users/admin/GAMConfig/gamcache
cache_discovery_only = true
channel_customer_id = ''
charset = utf-8
classroom_max_results = 0
client_secrets_json = client_secrets.json ; /Users/admin/GAMConfig/client_secrets.json
clock_skew_in_seconds = 10
cmdlog = ''
cmdlog_max_backups = 5
cmdlog_max_kilo_bytes = 1000
...
config_dir = /Users/admin/GAMConfig
contact_max_results = 100
csv_input_column_delimiter = ,
csv_input_quote_char = '"'
csv_input_row_drop_filter = ''
csv_input_row_drop_filter_mode = anymatch
csv_input_row_filter = ''
csv_input_row_filter_mode = allmatch
csv_input_row_limit = 0
csv_output_column_delimiter = ,
csv_output_convert_cr_nl = false
csv_output_field_delimiter = ' '
csv_output_header_drop_filter = ''
csv_output_header_filter = ''
csv_output_header_force = ''
csv_output_line_terminator = lf
csv_output_quote_char = '"'
csv_output_row_drop_filter = ''
csv_output_row_drop_filter_mode = anymatch
csv_output_row_filter = ''
csv_output_row_filter_mode = allmatch
csv_output_row_limit = 0
csv_output_subfield_delimiter = '.'
csv_output_timestamp_column = ''
csv_output_users_audit = false
customer_id = my_customer
debug_level = 0
device_max_results = 200
domain = ''
...
drive_dir = /Users/admin/GAMWork
drive_max_results = 1000
drive_v3_native_names = true
email_batch_size = 50
enable_dasa = false
event_max_results = 250
extra_args = ''
inter_batch_wait = 0
license_max_results = 100
license_skus = ''
member_max_results = 200
message_batch_size = 50
message_max_results = 500
mobile_max_results = 100
multiprocess_pool_limit = 0
never_time = Never
no_browser = false
no_cache = false
no_update_check = true
no_verify_ssl = false
num_tbatch_threads = 2
num_threads = 5
oauth2_txt = oauth2.txt ; /Users/admin/GAMConfig/oauth2.txt
oauth2service_json = oauth2service.json ; /Users/admin/GAMConfig/oauth2service.json
people_max_results = 100
print_agu_domains = ''
print_cros_ous = ''
print_cros_ous_and_children = ''
process_wait_limit = 0
quick_cros_move = false
quick_info_user = false
reseller_id = ''
retry_api_service_not_available = false
section = ''
show_api_calls_retry_data = false
show_commands = false
show_convert_cr_nl = false
show_counts_min = 1
show_gettings = true
show_gettings_got_nl = false
show_multiprocess_info = false
smtp_fqdn = ''
smtp_host = ''
smtp_password = ''
smtp_username = ''
timezone = utc
tls_max_version = ''
tls_min_version = 'TLSv1_2'
todrive_clearfilter = false
todrive_clientaccess = false
todrive_conversion = true
todrive_localcopy = false
todrive_locale = ''
todrive_nobrowser = false
todrive_noemail = true
todrive_parent = root
todrive_sheet_timeformat = ''
todrive_sheet_timestamp = false
todrive_timeformat = ''
todrive_timestamp = false
todrive_timezone = ''
todrive_upload_nodata = true
todrive_user = ''
truncate_client_id = false
update_cros_ou_with_id = false
use_projectid_as_name = false
user_max_results = 500
user_service_account_access_only = false
...
admin@server:/Users/admin$
```
@@ -346,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.77.14 - 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
@@ -1018,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.77.14 - 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

View File

@@ -15,6 +15,7 @@
- [Print organizational units](#print-organizational-units)
- [Display organizational unit counts](#display-organizational-unit-counts)
- [Display indented organizational unit tree](#display-indented-organizational-unit-tree)
- [Check organizational unit for contained items](#check-organizational-unit-for-contained-items)
- [Special case handling for large number of organizational units](#special-case-handling-for-large-number-of-organizational-units)
## API documentation
@@ -270,6 +271,56 @@ gam show orgtree [fromparent <OrgUnitItem>] [batchsuborgs [<Boolean>]]
By default, Gam displays the organizational unit tree starting at /.
* `fromparent <OrgUnitItem>` - Display the organizational unit tree starting at `<OrgUnitItem>`.
## Check organizational unit for contained items
An organizational unit can be deleted only when it contains no items:
* Chrome Browsers
* ChromeOS Devices
* Shared Drives
* Sub Org Units
* Users
This command counts those items and displays a CSV file with the item counts.
* All counts are zero - A return code of 0 is returned and the `empty` column is `True`
* Some count is greater than 0 - A return code of 25 is returned and the `empty` column is `False`
Only items directly within the OU are counted, items in sub-OUs are not counted.
```
<OrgUnitCheckName> ::=
browsers|
devices|
shareddrives|
subous|
users
<OrgUnitCheckNameList> ::= "<OrgUnitCheckName>(,<OrgUnitCheckName>)*"
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
with `<OrgUnitCheckName>*` or `fields <OrgUnitCheckNameList>`.
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.
By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain
the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled.
When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output.
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

View File

@@ -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`.
@@ -509,7 +539,7 @@ Find all the organizers and file organizers on the Golgafrincham shared drive in
```
By default, all Shared Drives specified are displayed; use the following option to select a subset of those Shared Drives.
* `<PermissionMatch>* [<PermissionMatchAction>] pmselect` - Use permission matching to select Shared Drives
* `<PermissionMatch>* [<PermissionMatchAction>] pmselect` - Use permission matching to select Shared Drives; all ACLs are displayed for the selected Shared Drives
By default, all ACLS are displayed; use the following option to select a subset of the ACLS to display.
* `<PermissionMatch>* [<PermissionMatchAction>]` - Use permission matching to display a subset of the ACLs for each Shared Drive; this only applies when `pmselect` is not specified
@@ -548,7 +578,7 @@ By default, all Shared Drives are displayed; use the following options to select
* `teamdriveadminquery|query <QueryTeamDrive>` - Use a query to select Shared Drives
* `matchname <RegularExpression>` - Retrieve Shared Drives with names that match a pattern.
* `orgunit|org|ou <OrgUnitPath>` - Only Shared Drives in the specified Org Unit are selected
* `<PermissionMatch>* [<PermissionMatchAction>] pmselect` - Use permission matching to select Shared Drives
* `<PermissionMatch>* [<PermissionMatchAction>] pmselect` - Use permission matching to select Shared Drives; all ACLs are displayed for the selected Shared Drives
By default, Shared Drives with no permissions are not displayed; use the `shownopermissionsdrives` to control whether
Shared Drives with no permissions are displayed.
@@ -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

View File

@@ -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>`.

View File

@@ -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|

View File

@@ -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
```

View File

@@ -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.
```

View File

@@ -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|

View File

@@ -1,9 +1,9 @@
\# Version and Help
\\# Version and Help
Print the current version of Gam with details
```
gam version
GAMADV-XTD3 6.77.14 - 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.77.14 - 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.77.14 - 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.77.14
Latest: 6.79.08
echo $?
1
```
@@ -72,7 +72,7 @@ echo $?
Print the current version number without details
```
gam version simple
6.77.14
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.77.14 - 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

View File

@@ -1994,6 +1994,7 @@ gam revoke browsertoken <BrowserTokenPermanentID>
org|
orgunit|
orgunitpath|
ou|
revoketime|
revokerid|
state|
@@ -3277,15 +3278,22 @@ gam info domain [<DomainName>]
[formatjson]
gam print domains [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
gam show domains [formatjson]
[showitemcountonly]
gam show domains
[formatjson]
[showitemcountonly]
gam create|add domainalias|aliasdomain <DomainAlias> <DomainName>
gam delete domainalias|aliasdomain <DomainAlias>
gam info domainalias|aliasdomain <DomainAlias> [formatjson]
gam info domainalias|aliasdomain <DomainAlias>
[formatjson]
gam print domainaliases|aliasdomains [todrive <ToDriveAttribute>*]
[formatjson [quotechar <Character>]]
gam show domainaliases|aliasdomains [formatjson]
[showitemcountonly]
gam show domainaliases|aliasdomains
[formatjson]
[showitemcountonly]
# Domain - Contacts and Global Address List
@@ -4210,6 +4218,19 @@ gam print orgs|ous [todrive <ToDriveAttribute>*]
[showitemcountonly]
gam show orgtree [fromparent <OrgUnitItem>] [batchsuborgs [<Boolean>]]
<OrgUnitCheckName> ::=
browsers|
devices|
shareddrives|
subous|
users
<OrgUnitCheckNameList> ::= "<OrgUnitCheckName>(,<OrgUnitCheckName>)*"
gam check ou|org <OrgUnitItem> [todrive <ToDriveAttribute>*]
[<OrgUnitCheckName>*|(fields <OrgUnitCheckNameList>)]
[filename <FileName>] [movetoou <OrgUnitItem>]
[formatjson [quotechar <Character>]]
# Printers
<PrinterAttribute> ::=
@@ -5336,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>)|
@@ -7583,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>]

View File

@@ -2,6 +2,86 @@
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
as it must not contain any items in order to be deleted.
* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Organizational-Units#check-organizational-unit-for-contained-items
6.77.18
Added option `showitemcountonly` to `gam print domainaliases` that causes GAM to display the
number of domain aliasess on stdout; no CSV file is written.
6.77.17
Added option `showitemcountonly` to `gam print domains` that causes GAM to display the
number of domains on stdout; no CSV file is written.
6.77.16
Fixed bug in `gam <UserTypeEntity> print filelist` that caused a trap.
6.77.15
Updated `gam calendars <CalendarEntity> import event icaluid <iCalUID> json <JSONdata>` to handle API
constraints on recurring events.
6.77.14
Fixed bug in `gam calendars <CalendarEntity> import event icaluid <iCalUID> json <JSONdata>` that caused an error.

View File

@@ -332,6 +332,7 @@ ENTITY_IS_A_USER_ALIAS_RC = 21
ENTITY_IS_A_GROUP_RC = 22
ENTITY_IS_A_GROUP_ALIAS_RC = 23
ENTITY_IS_AN_UNMANAGED_ACCOUNT_RC = 24
ORGUNIT_NOT_EMPTY_RC = 25
CHECK_USER_GROUPS_ERROR_RC = 29
ORPHANS_COLLECTED_RC = 30
# Warnings/Errors
@@ -2809,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
@@ -3609,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:
@@ -3772,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
@@ -3798,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
@@ -5183,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:
@@ -5627,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 = []
@@ -8890,14 +8892,6 @@ def getTodriveOnly(csvPF):
else:
unknownArgumentExit()
def getTodriveFJQCOnly(csvPF, FJQC, addTitle=False, noExit=False):
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
else:
FJQC.GetFormatJSONQuoteChar(myarg, addTitle, noExit)
DEFAULT_SKIP_OBJECTS = {'kind', 'etag', 'etags', '@type'}
# Clean a JSON object
@@ -12355,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()
@@ -13667,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)
@@ -15763,18 +15757,33 @@ def _printDomain(domain, csvPF):
DOMAIN_ALIAS_SORT_TITLES = ['domainAliasName', 'parentDomainName', 'creationTime', 'verified']
# gam print domainaliases [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]
# gam show domainaliases [formatjson]
# gam print domainaliases [todrive <ToDriveAttribute>*]
# [formatjson [quotechar <Character>]]
# [showitemcountonly]
# gam show domainaliases
# [formatjson]
# [showitemcountonly]
def doPrintShowDomainAliases():
cd = buildGAPIObject(API.DIRECTORY)
csvPF = CSVPrintFile(['domainAliasName'], DOMAIN_ALIAS_SORT_TITLES) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
getTodriveFJQCOnly(csvPF, FJQC, True)
showItemCountOnly = False
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg == 'showitemcountonly':
showItemCountOnly = True
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
try:
domainAliases = callGAPIitems(cd.domainAliases(), 'list', 'domainAliases',
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
customer=GC.Values[GC.CUSTOMER_ID])
count = len(domainAliases)
if showItemCountOnly:
writeStdout(f'{count}\n')
return
i = 0
for domainAlias in domainAliases:
i += 1
@@ -16090,18 +16099,33 @@ def doInfoDomain():
DOMAIN_SORT_TITLES = ['domainName', 'parentDomainName', 'creationTime', 'type', 'verified']
# gam print domains [todrive <ToDriveAttribute>*] [formatjson [quotechar <Character>]]
# gam show domains [formatjson]
# gam print domains [todrive <ToDriveAttribute>*]
# [formatjson [quotechar <Character>]]
# [showitemcountonly]
# gam show domains
# [formatjson]
# [showitemcountonly]
def doPrintShowDomains():
cd = buildGAPIObject(API.DIRECTORY)
csvPF = CSVPrintFile(['domainName'], DOMAIN_SORT_TITLES) if Act.csvFormat() else None
FJQC = FormatJSONQuoteChar(csvPF)
getTodriveFJQCOnly(csvPF, FJQC, True)
showItemCountOnly = False
while Cmd.ArgumentsRemaining():
myarg = getArgument()
if csvPF and myarg == 'todrive':
csvPF.GetTodriveParameters()
elif myarg == 'showitemcountonly':
showItemCountOnly = True
else:
FJQC.GetFormatJSONQuoteChar(myarg, True)
try:
domains = callGAPIitems(cd.domains(), 'list', 'domains',
throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
customer=GC.Values[GC.CUSTOMER_ID])
count = len(domains)
if showItemCountOnly:
writeStdout(f'{count}\n')
return
i = 0
for domain in domains:
i += 1
@@ -16187,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]:
@@ -16197,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)
@@ -16321,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':
@@ -16483,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>*]
@@ -16513,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)
@@ -16522,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:
@@ -17462,7 +17488,11 @@ def _getOrgUnits(cd, orgUnitPath, fieldsList, listType, showParent, batchSubOrgs
else:
fields = ','.join(set(fieldsList))
listfields = f'organizationUnits({fields})'
printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT)
if listType == 'all' and orgUnitPath == '/':
printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT)
else:
printGettingAllEntityItemsForWhom(Ent.CHILD_ORGANIZATIONAL_UNIT, orgUnitPath,
qualifier=' (Direct Children)' if listType == 'children' else '', entityType=Ent.ORGANIZATIONAL_UNIT)
if listType == 'children':
batchSubOrgs = False
try:
@@ -17500,7 +17530,10 @@ def _getOrgUnits(cd, orgUnitPath, fieldsList, listType, showParent, batchSubOrgs
except (GAPI.invalidOrgunit, GAPI.orgunitNotFound, GAPI.backendError,
GAPI.badRequest, GAPI.invalidCustomerId, GAPI.loginRequired):
pass
printGotAccountEntities(len(orgUnits))
if listType == 'all' and orgUnitPath == '/':
printGotAccountEntities(len(orgUnits))
else:
printGotEntityItemsForWhom(len(orgUnits))
if childSelector is not None:
for orgUnit in orgUnits:
orgUnit[ORG_UNIT_SELECTOR_FIELD] = childSelector if orgUnit['orgUnitPath'] != orgUnitPath else parentSelector
@@ -17733,6 +17766,205 @@ def doShowOrgTree():
for org in sorted(orgTree):
printOrgUnit(org, orgTree)
ORG_ITEMS_FIELD_MAP = {
'browsers': 'browsers',
'devices': 'devices',
'shareddrives': 'sharedDrives',
'subous': 'subOus',
'users': 'users',
}
# 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)
f = orgUnitPath = None
fieldsList = []
titlesList = []
status, orgUnitPath, orgUnitId = checkOrgUnitPathExists(cd, getOrgUnitItem())
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':
csvPF.GetTodriveParameters()
elif myarg in ORG_ITEMS_FIELD_MAP:
fieldsList.append(myarg)
elif myarg == 'fields':
for field in _getFieldsList():
if field in ORG_ITEMS_FIELD_MAP:
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 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]
orgUnitItemCounts[title] = 0
if not FJQC.formatJSON:
titlesList.append(title)
if 'browsers' in fieldsList:
cbcm = buildGAPIObject(API.CBCM)
customerId = _getCustomerIdNoC()
printGettingAllEntityItemsForWhom(Ent.CHROME_BROWSER, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT)
pageMessage = getPageMessage()
try:
feed = yieldGAPIpages(cbcm.chromebrowsers(), 'list', 'browsers',
pageMessage=pageMessage, messageAttribute='deviceId',
throwReasons=[GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.INVALID_ORGUNIT, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
customer=customerId, orgUnitPath=orgUnitPath, projection='BASIC',
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:
entityActionFailedExit([Ent.CHROME_BROWSER, None], str(e))
except (GAPI.badRequest, GAPI.resourceNotFound):
accessErrorExit(None)
if 'devices' in fieldsList:
printGettingAllEntityItemsForWhom(Ent.CROS_DEVICE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT)
pageMessage = getPageMessageForWhom()
pageToken = None
totalItems = 0
tokenRetries = 0
while True:
try:
feed = callGAPI(cd.chromeosdevices(), 'list',
throwReasons=[GAPI.INVALID_INPUT, GAPI.INVALID_ORGUNIT,
GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS,
pageToken=pageToken, customerId=GC.Values[GC.CUSTOMER_ID],
orgUnitPath=orgUnitPath, fields='nextPageToken,chromeosdevices(deviceId)', maxResults=GC.Values[GC.DEVICE_MAX_RESULTS])
tokenRetries = 0
pageToken, totalItems = _processGAPIpagesResult(feed, 'chromeosdevices', None, totalItems, pageMessage, None, Ent.CROS_DEVICE)
if feed:
orgUnitItemCounts['devices'] += len(feed.get('chromeosdevices', []))
del feed
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)
# Invalid Input: xyz - Check for invalid pageToken!!
# 0123456789012345
if message[15:] == pageToken:
tokenRetries += 1
if tokenRetries <= 2:
writeStderr(f'{WARNING_PREFIX}{Msg.LIST_CHROMEOS_INVALID_INPUT_PAGE_TOKEN_RETRY}')
time.sleep(tokenRetries*5)
continue
entityActionFailedWarning([Ent.CROS_DEVICE, None], message)
break
entityActionFailedWarning([Ent.CROS_DEVICE, None], message)
break
except GAPI.invalidOrgunit as e:
entityActionFailedExit([Ent.CROS_DEVICE, None], str(e))
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
accessErrorExit(cd)
if 'shareddrives' in fieldsList:
ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA)
printGettingAllEntityItemsForWhom(Ent.SHAREDDRIVE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT)
sds = callGAPIpages(ci.orgUnits().memberships(), 'list', 'orgMemberships',
pageMessage=getPageMessageForWhom(),
parent=f'orgUnits/{orgUnitId[3:]}',
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:
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()
try:
feed = yieldGAPIpages(cd.users(), 'list', 'users',
pageMessage=pageMessage,
throwReasons=[GAPI.INVALID_ORGUNIT, GAPI.ORGUNIT_NOT_FOUND,
GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN],
customer=GC.Values[GC.CUSTOMER_ID], query=orgUnitPathQuery(orgUnitPath, None),
fields='nextPageToken,users(orgUnitPath)', maxResults=GC.Values[GC.USER_MAX_RESULTS])
for users in feed:
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:
empty = False
break
baseRow = {'orgUnitPath': orgUnitPath, 'orgUnitId': orgUnitId, 'empty': empty}
row = flattenJSON(orgUnitItemCounts, baseRow.copy())
if not FJQC.formatJSON:
csvPF.WriteRowTitles(row)
elif csvPF.CheckRowTitles(row):
baseRow['JSON'] = json.dumps(cleanJSON(orgUnitItemCounts), ensure_ascii=False, sort_keys=True)
csvPF.WriteRowNoFilter(baseRow)
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>
@@ -24602,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]))
@@ -24808,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)))
@@ -24973,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)
@@ -25035,10 +25267,10 @@ def doPrintShowBrowsers():
else:
entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e))
return
except GAPI.invalidOrgunit as e:
except (GAPI.invalidOrgunit, GAPI.forbidden) as e:
entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e))
return
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
except (GAPI.badRequest, GAPI.resourceNotFound):
accessErrorExit(None)
else:
sortRows = True
@@ -25082,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()
@@ -25126,6 +25358,7 @@ BROWSER_TOKEN_FIELDS_CHOICE_MAP = {
'org': 'orgUnitPath',
'orgunit': 'orgUnitPath',
'orgunitpath': 'orgUnitPath',
'ou': 'orgUnitPath',
'revoketime': 'revokeTime',
'revokerid': 'revokerId',
'state': 'state',
@@ -25174,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)
@@ -28618,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':
@@ -36918,9 +37151,13 @@ def _getCalendarEventAttribute(myarg, body, parameters, function):
parameters['attendees'].append(addAttendee)
elif myarg == 'json':
jsonData = getJSON(EVENT_JSON_CLEAR_FIELDS)
if function in {'insert', 'import'}:
if function == 'insert':
body.update(jsonData)
clearJSONfields(body, EVENT_JSON_INSERT_CLEAR_FIELDS)
elif function == 'import':
if 'id' in jsonData:
jsonData['iCalUID'] = jsonData.pop('id')
body.update(jsonData)
elif function == 'update':
if 'event' in jsonData and 'attendees' in jsonData['event']:
parameters['attendees'].extend(jsonData['event'].pop('attendees'))
@@ -37372,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:
@@ -39628,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
@@ -39696,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
@@ -42902,6 +43141,7 @@ USER_FIELDS_CHOICE_MAP = {
'organizations': 'organizations',
'organisation': 'organizations',
'organisations': 'organizations',
'orgunit': 'orgUnitPath',
'orgunitpath': 'orgUnitPath',
'otheremail': 'emails',
'otheremails': 'emails',
@@ -43787,18 +44027,21 @@ def doPrintUsers(entityList=None):
projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
maxResults=maxResults, **kwargs)
for users in feed:
if showItemCountOnly:
itemCount += len(users)
continue
if orgUnitPath is None:
if not printOptions['countOnly']:
if showItemCountOnly:
itemCount += len(users)
elif not printOptions['countOnly']:
for user in users:
_printUser(user, 0, 0)
else:
for user in users:
_updateDomainCounts(user['primaryEmail'])
else:
if not printOptions['countOnly']:
if showItemCountOnly:
for user in users:
if orgUnitPathLower == user.get('orgUnitPath', '').lower():
itemCount += 1
elif not printOptions['countOnly']:
for user in users:
if orgUnitPathLower == user.get('orgUnitPath', '').lower():
_printUser(user, 0, 0)
@@ -44704,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()
@@ -54409,7 +54652,7 @@ def printFileList(users):
simpleLists = ['permissionIds', 'spaces']
skipObjects = set()
fileIdEntity = {}
selectSubQuery = ''
getSharedDriveACLsCountMsg = selectSubQuery = ''
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
DLP = DriveListParameters({'allowChoose': True, 'allowCorpora': True, 'allowQuery': True, 'mimeTypeInQuery': False})
DFF = DriveFileFields()
@@ -61433,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)
@@ -63305,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],
@@ -63827,14 +64070,16 @@ 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)
if csvPF and FJQC.formatJSON:
csvPF.SetJSONTitles(['name', 'JSON'])
_, orgUnitId = getOrgUnitId(None, orgUnitPath)
printGettingAllEntityItemsForWhom(Ent.SHAREDDRIVE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT)
sds = callGAPIpages(ci.orgUnits().memberships(), 'list', 'orgMemberships',
pageMessage=getPageMessageForWhom(),
parent=f'orgUnits/{orgUnitId[3:]}',
customer=_getCustomersCustomerIdWithC(),
filter="type == 'shared_drive'")
@@ -65032,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':
@@ -65056,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:
@@ -65069,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)
@@ -65105,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)
@@ -65150,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]])
@@ -65407,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:
@@ -65474,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
@@ -65507,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:
@@ -68192,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'],
@@ -68201,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)
@@ -73647,6 +73905,7 @@ MAIN_COMMANDS_WITH_OBJECTS = {
{Cmd.ARG_SVCACCT: doCheckUpdateSvcAcct,
Cmd.ARG_USERINVITATION: doCheckCIUserInvitations,
Cmd.ARG_ISINVITABLE: doCheckCIUserInvitations,
Cmd.ARG_ORG: doCheckOrgUnit,
}
),
'clear':
@@ -74212,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,

View File

@@ -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'

View File

@@ -94,6 +94,7 @@ class GamEntity():
CHAT_MESSAGE_ID = 'chmi'
CHAT_SPACE = 'chsp'
CHAT_THREAD = 'chth'
CHILD_ORGANIZATIONAL_UNIT = 'corg'
CHROME_APP = 'capp'
CHROME_APP_DEVICE = 'capd'
CHROME_BROWSER = 'chbr'
@@ -435,6 +436,7 @@ class GamEntity():
CHAT_MEMBER_USER: ['Chat User Members', 'Chat User Member'],
CHAT_SPACE: ['Chat Spaces', 'Chat Space'],
CHAT_THREAD: ['Chat Threads', 'Chat Thread'],
CHILD_ORGANIZATIONAL_UNIT: ['Child Organizational Units', 'Child Organizational Unit'],
CHROME_APP: ['Chrome Applications', 'Chrome Application'],
CHROME_APP_DEVICE: ['Chrome Application Devices', 'Chrome Application Device'],
CHROME_BROWSER: ['Chrome Browsers', 'Chrome Browser'],

View File

@@ -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: