mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-17 12:41:37 +00:00
Compare commits
18 Commits
20241010.1
...
20241020.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef14359d9b | ||
|
|
b1444d7c04 | ||
|
|
c3c7d629f7 | ||
|
|
eb33b6521b | ||
|
|
932fe5db02 | ||
|
|
6885bcae92 | ||
|
|
d35e9fcae4 | ||
|
|
861279e614 | ||
|
|
b80dd15f4b | ||
|
|
ae95c8fdea | ||
|
|
090b5937ab | ||
|
|
2323e130b1 | ||
|
|
6ef127f283 | ||
|
|
266f00d3a8 | ||
|
|
5c61867e1f | ||
|
|
0bbe1cc958 | ||
|
|
d1e02e4695 | ||
|
|
f707c83e1a |
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -17,7 +17,7 @@ defaults:
|
||||
working-directory: src
|
||||
|
||||
env:
|
||||
SCRATCH_COUNTER: 2
|
||||
SCRATCH_COUNTER: 3
|
||||
OPENSSL_CONFIG_OPTS: no-fips --api=3.0.0
|
||||
OPENSSL_INSTALL_PATH: ${{ github.workspace }}/bin/ssl
|
||||
OPENSSL_SOURCE_PATH: ${{ github.workspace }}/src/openssl
|
||||
@@ -36,58 +36,63 @@ jobs:
|
||||
goal: build
|
||||
arch: x86_64
|
||||
openssl_archs: linux-x86_64
|
||||
- os: [self-hosted, linux, arm64]
|
||||
- os: ubuntu-24.04
|
||||
jid: 2
|
||||
goal: build
|
||||
arch: x86_64
|
||||
openssl_archs: linux-x86_64
|
||||
- os: [self-hosted, linux, arm64]
|
||||
jid: 3
|
||||
goal: build
|
||||
arch: aarch64
|
||||
openssl_archs: linux-aarch64
|
||||
- os: ubuntu-22.04
|
||||
jid: 3
|
||||
jid: 4
|
||||
goal: build
|
||||
arch: x86_64
|
||||
openssl_archs: linux-x86_64
|
||||
staticx: yes
|
||||
- os: [self-hosted, linux, arm64]
|
||||
jid: 4
|
||||
jid: 5
|
||||
goal: build
|
||||
arch: aarch64
|
||||
openssl_archs: linux-aarch64
|
||||
staticx: yes
|
||||
- os: macos-13
|
||||
jid: 5
|
||||
jid: 6
|
||||
goal: build
|
||||
arch: x86_64
|
||||
openssl_archs: darwin64-x86_64
|
||||
- os: macos-14
|
||||
jid: 6
|
||||
jid: 7
|
||||
goal: build
|
||||
arch: aarch64
|
||||
openssl_archs: darwin64-arm64
|
||||
- os: windows-2022
|
||||
jid: 7
|
||||
jid: 8
|
||||
goal: build
|
||||
arch: Win64
|
||||
openssl_archs: VC-WIN64A
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.9"
|
||||
jid: 8
|
||||
arch: x86_64
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.10"
|
||||
jid: 9
|
||||
arch: x86_64
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.11"
|
||||
python: "3.10"
|
||||
jid: 10
|
||||
arch: x86_64
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.12"
|
||||
python: "3.11"
|
||||
jid: 11
|
||||
arch: x86_64
|
||||
- os: ubuntu-24.04
|
||||
goal: test
|
||||
python: "3.12"
|
||||
jid: 12
|
||||
arch: x86_64
|
||||
|
||||
steps:
|
||||
|
||||
@@ -110,7 +115,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20241008
|
||||
key: gam-${{ matrix.jid }}-20241014
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -181,7 +186,7 @@ jobs:
|
||||
run: |
|
||||
echo "RUNNING: apt update..."
|
||||
sudo apt-get -qq --yes update
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev libxslt1-dev
|
||||
sudo apt-get -qq --yes install swig libpcsclite-dev libxslt1-dev libsqlite3-dev
|
||||
|
||||
- name: MacOS install tools
|
||||
if: runner.os == 'macOS'
|
||||
@@ -697,6 +702,7 @@ jobs:
|
||||
export MSI_FILENAME="${GITHUB_WORKSPACE}/gam-${GAMVERSION}-windows-${GAM_ARCHIVE_ARCH}.msi"
|
||||
# auto-generate a lib.wxs based on the files PyInstaller created for the lib/ directory
|
||||
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/heat.exe dir "${gampath}/lib" -ke -srd -cg Lib -gg -dr lib -directoryid lib -out lib.wxs
|
||||
$PYTHON tools/gen-wix-xml-filelist.py lib.wxs
|
||||
echo "-- begin lib.wxs --"
|
||||
cat lib.wxs
|
||||
echo "-- end lib.wxs --"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Authorization
|
||||
- [Introduction](#introduction)
|
||||
- [Headless computers and Cloud Shells](#headless-computers-and-cloud-shells)
|
||||
- [Version 5 Update](#version-5-update)
|
||||
- [API documentation](#api-documentation)
|
||||
- [Python Regular Expressions](Python-Regular-Expressions)
|
||||
- [Definitions](#definitions)
|
||||
@@ -127,25 +126,6 @@ as required by Google for headless computers/cloud shells; this is required as o
|
||||
* See: https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html
|
||||
* OAuth out-of-band (oob) flow will be deprecated
|
||||
|
||||
## Version 5 Update
|
||||
GAM version `5.00.00` replaced the deprecated `oauth2client` library with the `google-auth` library.
|
||||
This change requires a one-time update of the client access file `oauth2.txt`; GAM will continue
|
||||
to use the old version of `oauth2.txt` until you perform the update. There is a small performance
|
||||
impact until the update is performed. However, you can't use the updated version of `oauth2.txt`
|
||||
in prior versions of GAM; if you want to run GAM `5.00.00` and prior versions of GAM,
|
||||
do not perform the update until you no longer need to run the prior versions of GAM.
|
||||
|
||||
If you are running any GAM version `4.85.00` or later, perform the following command
|
||||
after installing `5.00.00` to perform the update.
|
||||
```
|
||||
gam oauth refresh
|
||||
```
|
||||
If you are running any GAM version before `4.85.00`, perform the following command
|
||||
after installing `5.00.00` to perform the update.
|
||||
```
|
||||
gam oauth update
|
||||
```
|
||||
|
||||
## API documentation
|
||||
* https://cloud.google.com/resource-manager/docs/creating-managing-organization#adding_an_organization_administrator
|
||||
* https://cloud.google.com/service-usage/docs/reference/rest
|
||||
@@ -213,7 +193,7 @@ perform these steps and then retry the create project command.
|
||||
|
||||
## Authorize Service Account Key Uploads
|
||||
|
||||
If you try to create a project and get an error saying that Constraint `constraints/iam.disableServiceAccountKeyUpload violated for service account projects/gam-project-xxx`,
|
||||
If you try to create a project and get an error saying that Constraint `constraints/iam.disableServiceAccountKeyUpload violated for service account projects/gam-project-xxxxx`,
|
||||
perform these steps and then you should be able to authorize and use your project.
|
||||
|
||||
* Login as an existing super admin at console.cloud.google.com
|
||||
@@ -293,7 +273,7 @@ You can skip these steps if you know that untrusted third-party apps are allowed
|
||||
|
||||
### Default values
|
||||
* `<AppName>` - "GAM"
|
||||
* `<ProjectID>` - "gam-project-abc-def-jki" where "abc-def-ghi" are randomly generated
|
||||
* `<ProjectID>` - "gam-project-a1b2c" where "a1b2c" are randomly generated
|
||||
* `<ProjectName>` - "GAM Project"
|
||||
* `<ServiceAccountName>` - `<ProjectID>`
|
||||
* `<ServiceAccountDisplayName>` - `<ProjectName>`
|
||||
@@ -382,7 +362,7 @@ gam update project [[admin] <EmailAddress>] [<ProjectIDEntity>]
|
||||
* `<EmailAddress>` - A Google Workspace admin/GCP project manager; if omitted, you will be prompted for the address
|
||||
|
||||
Use these options to select projects.
|
||||
* `current` - The project referenced in `client_secret.json`; this is the default
|
||||
* `current` - The project referenced in `client_secrets.json`; this is the default
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -394,7 +374,7 @@ gam delete project [[admin] <EmailAddress>] [<ProjectIDEntity>]
|
||||
* `<EmailAddress>` - A Google Workspace admin/GCP project manager; if omitted, you will be prompted for the address
|
||||
|
||||
Use these options to select projects.
|
||||
* `current` - The project referenced in `client_secret.json`; this is the default
|
||||
* `current` - The project referenced in `client_secrets.json`; this is the default
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -414,7 +394,7 @@ gam show projects [[admin] <EmailAddress>] [all|<ProjectIDEntity>]
|
||||
|
||||
Use these options to select projects.
|
||||
* `all` - All projects accessible by the administrator; this is the default
|
||||
* `current` - The project referenced in `client_secret.json`
|
||||
* `current` - The project referenced in `client_secrets.json`
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -432,7 +412,7 @@ gam print projects [[admin] <EmailAddress>] [all|<ProjectIDEntity>] [todrive <To
|
||||
|
||||
Use these options to select projects.
|
||||
* `all` - All projects accessible by the administrator; this is the default
|
||||
* `current` - The project referenced in `client_secret.json`
|
||||
* `current` - The project referenced in `client_secrets.json`
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -718,7 +698,7 @@ gam create|add svcacct [[admin] <EmailAddress>] [<ProjectIDEntity>]
|
||||
* `<EmailAddress>` - Google Workspace admin/GCP project manager; if omitted, you will be prompted for the address
|
||||
|
||||
Use these options to select projects.
|
||||
* `current` - The project referenced in `client_secret.json`; this is the default
|
||||
* `current` - The project referenced in `client_secrets.json`; this is the default
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -741,7 +721,7 @@ gam delete svcacct [[admin] <EmailAddress>] [<ProjectIDEntity>]
|
||||
* `<EmailAddress>` - Google Workspace admin/GCP project manager; if omitted, you will be prompted for the address
|
||||
|
||||
Use these options to select projects.
|
||||
* `current` - The project referenced in `client_secret.json`; this is the default
|
||||
* `current` - The project referenced in `client_secrets.json`; this is the default
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
@@ -762,7 +742,7 @@ gam print svcaccts [[admin] <EmailAddress>] [all|<ProjectIDEntity>]
|
||||
|
||||
Use these options to select projects.
|
||||
* `all` - All projects accessible by the administrator; this is the default
|
||||
* `current` - The project referenced in `client_secret.json`
|
||||
* `current` - The project referenced in `client_secrets.json`
|
||||
* `gam` - Projects accessible by the administrator that were created by Gam, i.e, their project ID begins with `gam-project-`
|
||||
* `<ProjectID>` - A Google API project ID
|
||||
* `filter <String>` - A filter to select projects accessible by the administrator; see the API documentation
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
!# CSV Output Filtering
|
||||
# CSV Output Filtering
|
||||
- [Python Regular Expressions](Python-Regular-Expressions) Search function
|
||||
- [Definitions](#definitions)
|
||||
- [Quoting rules](#quoting-rules)
|
||||
@@ -11,9 +11,11 @@
|
||||
- [Column row limiting](#column-row-limiting)
|
||||
- [Saving filters in gam.cfg](#saving-filters-in-gamcfg)
|
||||
|
||||
There are five values in `gam.cfg` that can be used to filter the output from `gam print` commands.
|
||||
There are seven values in `gam.cfg` that can be used to filter the output from `gam print` commands.
|
||||
* `csv_output_header_filter` - A list of `<RegularExpressions>` used to select specific column headers to include
|
||||
* `csv_output_header_drop_filter` - A list of `<RegularExpressions>` used to select specific column headers to exclude
|
||||
* `csv_output_header_force` - A list of <Strings> used to specify the exact column headers to include
|
||||
* `csv_output_header_order` - A list of <Strings> used to specify the column header order; any headers in the file but not in the list will appear after the headers in the list.
|
||||
* `csv_output_row_filter` - A list or JSON dictionary used to include specific rows based on column values
|
||||
* `csv_output_row_drop_filter` - A list or JSON dictionary used to exclude specific rows based on column values
|
||||
* `csv_output_row_limit` - A limit on the number of rows written
|
||||
@@ -334,7 +336,7 @@ gam config csv_output_row_limit 10 auto_batch_min 1 redirect csv ./BigQuotaFiles
|
||||
```
|
||||
|
||||
## Saving filters in gam.cfg
|
||||
If you define a value for `csv_output_header_filter`, `csv_output_header_drop_filter`, `csv_output_row_filter`, `csv_output_row_drop_filter` or `csv_output_row_limit` in the `[DEFAULT]` section of `gam.cfg`,
|
||||
If you define a value for `csv_output_header_filter`, `csv_output_header_drop_filter`, `csv_output_header_force`, `csv_output_header_order`, `csv_output_row_filter`, `csv_output_row_drop_filter` or `csv_output_row_limit` in the `[DEFAULT]` section of `gam.cfg`,
|
||||
it will apply to every `gam print` command which is probably not desirable. You can store them in `gam.cfg` in named sections.
|
||||
```
|
||||
[Filter510]
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
- [Parameters for Basic Levels](#parameters-for-basic-levels)
|
||||
- [Create an Access Level](#create-an-access-level)
|
||||
- [Update an Access Level](#update-an-access-level)
|
||||
- [Update Access Levels with JSON](#update-access-levels-with-json)
|
||||
- [Delete an Access Level](#delete-an-access-level)
|
||||
- [Display all Access Levels](#display-all-access-levels)
|
||||
- [CAA Region Codes](#caa-region-codes)
|
||||
@@ -177,6 +178,27 @@ This example adds UK to the allowed regions for CORP_COUNTRIES
|
||||
gam update caalevel CORP_COUNTRIES basic condition regions US,CA,UK endcondition
|
||||
```
|
||||
|
||||
## Update Access Levels with JSON
|
||||
Update existing CAA levels via their JSON data; create a CSV file of CAA levels.
|
||||
```
|
||||
gam redirect csv ./CAAlevels.csv print caalevels formatjson quotechar "'"
|
||||
```
|
||||
Edit the JSON column for the desired CAA level(s) in CAAlevels.csv.
|
||||
Update the desired CAA level by selecting the row by it's title; repeat for each title to update.
|
||||
```
|
||||
gam config csv_input_row_filter "title:text='Example Title'" csv CAAlevels.csv quotechar "'" gam update caalevel "~name" json "~JSON"
|
||||
```
|
||||
|
||||
## Example
|
||||
Edit CAAlevels.csv and add UK to the allowed regions for CORP_COUNTRIES
|
||||
```
|
||||
{"regions": ["US", "CA", "UK"]}
|
||||
```
|
||||
Do the update.
|
||||
```
|
||||
gam config csv_input_row_filter "title:text='CORP_COUNTRIES'" csv CAAlevels.csv quotechar "'" gam update caalevel "~name" json "~JSON"
|
||||
```
|
||||
|
||||
## Delete an Access Level
|
||||
Deletes the specified access level.
|
||||
```
|
||||
|
||||
@@ -10,6 +10,57 @@ Add the `-s` option to the end of the above commands to suppress creating the `g
|
||||
|
||||
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||
|
||||
### 7.00.24
|
||||
|
||||
Updated `gam print|show projects ... showiampolicies 0|1|3` to use non-service account authentication.
|
||||
|
||||
### 7.00.23
|
||||
|
||||
Updated `gam <UserTypeEntity> create|delete chatmember` to accept external (non-domain) email addresses.
|
||||
|
||||
### 7.00.22
|
||||
|
||||
Fixed bug in `gam create vaultmatter ... showdetails` that caused a trap.
|
||||
|
||||
### 7.00.21
|
||||
|
||||
Added `csv_output_header_order` variable to `gam.cfg` that is a list of `<Strings>`
|
||||
that are used to specify the order of column headers in the CSV file written by a gam print command.
|
||||
Any headers in the file but not in the list will appear after the headers in the list.
|
||||
|
||||
This might be used when the CSV file data is to be processed by another program
|
||||
that requires that the headers be in a particular order.
|
||||
|
||||
### 7.00.20
|
||||
|
||||
Fix Windows MSI installer issues on version upgrade. If you are having issues upgrading from a version older than 7.00.20 to this version or newer you may need to do a one time uninstall of GAM7 and then reinstall the new version. No configuration files will be lost during the uninstall / reinstall.
|
||||
|
||||
### 7.00.19
|
||||
|
||||
Updated `gam update shareddrive <SharedDriveEntity> ou <OrgUnitItem>` to handle the following error
|
||||
that occurs when an invalid `<SharedDriveEntity>` is specified.
|
||||
```
|
||||
ERROR: 400: invalidArgument - Invalid org membership name 0AJ3b2FTPakToUk9PVAxx.~
|
||||
```
|
||||
|
||||
Updated `gam print browsers` to properly format the time field `deviceIdentifiersHistory.records.0.firstRecordTime`.
|
||||
|
||||
### 7.00.18
|
||||
|
||||
Updated `gam create project` to use a default project name of `gam-project-a1b2c` (`a1b2c` is a random string of 5 characters)
|
||||
instead of `gam-project-abc-123-xyz` to avoid the following warning:
|
||||
```
|
||||
Project: gam-project-abc-123-xyz, Service Account: gam-project-abc-123-xyz@gam-project-abc-123-xyz.iam.gserviceaccount.com, Extracting public certificate
|
||||
init.py:12382: UserWarning: Attribute's length must be >= 1 and <= 64, but it was 70
|
||||
init.py:12383: UserWarning: Attribute's length must be >= 1 and <= 64, but it was 70
|
||||
Project: gam-project-abc-123-xyz, Service Account: gam-project-abc-123-xyz@gam-project-abc-123-xyz.iam.gserviceaccount.com, Done generating private key and public certificate
|
||||
```
|
||||
|
||||
### 7.00.17
|
||||
|
||||
Update all user calendar commands to disable falling back to client access if service account
|
||||
authorization has never been performed.
|
||||
|
||||
### 7.00.16
|
||||
|
||||
Updated `gam <UserTypeEntity> claim|transfer ownership` to show `Got N Drive Files/Folders that matched query` messages
|
||||
|
||||
@@ -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
|
||||
GAM 7.00.16 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.00.24 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 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
|
||||
GAM7 7.00.16 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM7 7.00.24 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 64-bit final
|
||||
Windows-10-10.0.17134 AMD64
|
||||
|
||||
@@ -51,6 +51,7 @@ The only `<VariableNames>` recognized in this `<Section>` are:
|
||||
* `csv_output_header_filter`
|
||||
* `csv_output_header_drop_filter`
|
||||
* `csv_output_header_force`
|
||||
* `csv_output_header_order`
|
||||
* `csv_output_row_filter`
|
||||
* `csv_output_row_filter_mode`
|
||||
* `csv_output_row_drop_filter`
|
||||
|
||||
@@ -427,6 +427,15 @@ gam info ou <OrgUnitPath> nousers
|
||||
gam show teamdrives query "orgUnitId='03ph8a2z21rexy'" fields id,name,orgunit,createdtime
|
||||
gam print teamdrives query "orgUnitId='03ph8a2z21rexy'" fields id,name,orgunit,createdtime
|
||||
```
|
||||
Alternative method; `<OrgUnitPath>` defaults to `/`.
|
||||
```
|
||||
gam show oushareddrives
|
||||
[ou|org|orgunit <OrgUnitPath>]
|
||||
[formatjson]
|
||||
gam print oushareddrives [todrive <ToDriveAttribute>*]
|
||||
[ou|org|orgunit <OrgUnitPath>]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
|
||||
## Manage Shared Drive access
|
||||
These commands are used to manage the ACLs on Shared Drives themselves, not the files/folders on the Shared Drives.
|
||||
|
||||
@@ -434,6 +434,7 @@ gam <UserTypeEntity> remove chatmember members <ChatMemberList>
|
||||
```
|
||||
|
||||
### Add members to a chat space, asadmin
|
||||
Creating memberships for users outside the administrator's Google Workspace organization isn't supported using asadmin.
|
||||
```
|
||||
gam <UserItem> create chatmember asadmin <ChatSpace>
|
||||
[type human|bot] [role member|manager]
|
||||
|
||||
@@ -811,7 +811,7 @@ User: testuser@domain.com, Drive Files/Folders: 261, Size: 13822521
|
||||
```
|
||||
Print file counts for a user.
|
||||
```
|
||||
$ gam user testuser@domain,com print filecounts showsize
|
||||
$ gam user testuser@domain.com print filecounts showsize
|
||||
Getting all Drive Files/Folders that match query ('me' in owners) for testuser@domain.com
|
||||
Got 261 Drive Files/Folders that matched query ('me' in owners) for testuser@domain.com...
|
||||
User,Total,Size,application/octet-stream,application/pdf,application/vnd.google-apps.document,application/vnd.google-apps.drawing,application/vnd.google-apps.drive-sdk.423565144751,application/vnd.google-apps.folder,application/vnd.google-apps.form,application/vnd.google-apps.jam,application/vnd.google-apps.presentation,application/vnd.google-apps.shortcut,application/vnd.google-apps.site,application/vnd.google-apps.spreadsheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.wordprocessingml.template,application/x-gzip,application/zip,image/jpeg,image/vnd.adobe.photoshop,text/csv,text/plain,text/rtf,text/x-sh
|
||||
@@ -825,6 +825,14 @@ Got 261 Drive Files/Folders that matched query ('me' in owners) for testuser@dom
|
||||
User,Total,Size,application/octet-stream,application/octet-stream-size,application/pdf,application/pdf-size,application/vnd.google-apps.document,application/vnd.google-apps.document-size,application/vnd.google-apps.drawing,application/vnd.google-apps.drawing-size,application/vnd.google-apps.drive-sdk.423565144751,application/vnd.google-apps.drive-sdk.423565144751-size,application/vnd.google-apps.folder,application/vnd.google-apps.folder-size,application/vnd.google-apps.form,application/vnd.google-apps.form-size,application/vnd.google-apps.jam,application/vnd.google-apps.jam-size,application/vnd.google-apps.presentation,application/vnd.google-apps.presentation-size,application/vnd.google-apps.shortcut,application/vnd.google-apps.shortcut-size,application/vnd.google-apps.site,application/vnd.google-apps.site-size,application/vnd.google-apps.spreadsheet,application/vnd.google-apps.spreadsheet-size,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet-size,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.wordprocessingml.document-size,application/vnd.openxmlformats-officedocument.wordprocessingml.template,application/vnd.openxmlformats-officedocument.wordprocessingml.template-size,application/x-gzip,application/x-gzip-size,application/zip,application/zip-size,image/jpeg,image/jpeg-size,image/vnd.adobe.photoshop,image/vnd.adobe.photoshop-size,text/csv,text/csv-size,text/plain,text/plain-size,text/rtf,text/rtf-size,text/x-sh,text/x-sh-size
|
||||
testuser@domain.com,261,13822521,8,17,1,9879,98,52858,2,2048,1,0,68,0,3,0,1,1024,1,0,14,0,1,0,24,11264,1,8157,3,34407,1,25906,4,2768,2,765,8,16498,1,13613198,2,397,13,41461,3,1738,1,136
|
||||
```
|
||||
Print file counts for a Shared Drive
|
||||
```
|
||||
$ gam user testuser@domain.com print filecounts select <SharedDriveID> showsize
|
||||
Getting all Drive Files/Folders for testuser@domain.com
|
||||
Got 261 Drive Files/Folders for testuser@domain.com...
|
||||
User,id,name,Total,Size,Item cap,application/octet-stream,application/pdf,application/vnd.google-apps.document,application/vnd.google-apps.drawing,application/vnd.google-apps.drive-sdk.423565144751,application/vnd.google-apps.folder,application/vnd.google-apps.form,application/vnd.google-apps.jam,application/vnd.google-apps.presentation,application/vnd.google-apps.shortcut,application/vnd.google-apps.site,application/vnd.google-apps.spreadsheet,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.wordprocessingml.template,application/x-gzip,application/zip,image/jpeg,image/vnd.adobe.photoshop,text/csv,text/plain,text/rtf,text/x-sh
|
||||
testuser@domain.com,0AMzwfhFBpwLHUkWXYZ,Shared Drive Name,261,13822521,3.45%,8,1,98,2,1,68,3,1,1,14,1,24,1,3,1,4,2,8,1,2,13,3,1
|
||||
```
|
||||
Get file count summaries by OU; top level selector is ou, sub level selectors are ou_and_children
|
||||
```
|
||||
gam redirect csv ./TopLevelOUs.csv print ous showparent toplevelonly parentselector ou childselector ou_and_children fields orgunitpath
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
- [API documentation](#api-documentation)
|
||||
- [Query documentation](Users-Drive-Query)
|
||||
- [Python Regular Expressions](Python-Regular-Expressions) Sub function
|
||||
- [Folders with Limited Access Beta](#folders-with-limited-access-beta)
|
||||
- [Permission Matches](Permission-Matches)
|
||||
- [Definitions](#definitions)
|
||||
- [Create files](#create-files)
|
||||
@@ -31,6 +32,15 @@
|
||||
* https://support.google.com/a/users/answer/7338880
|
||||
* https://developers.google.com/docs/api/reference/rest
|
||||
|
||||
## Folders with Limited Access Beta
|
||||
|
||||
If you are enrolled in the Beta and want to access the `inheritedpermissionsdisabled` field,
|
||||
you must turn on Drive API v3 beta.
|
||||
|
||||
```
|
||||
gam config drive_v3_beta true user user@domain.com update drivefile <FolderID> inheritedpermissionsdisabled true
|
||||
```
|
||||
|
||||
## Definitions
|
||||
* [`<DriveFileEntity>`](Drive-File-Selection)
|
||||
* [`<UserTypeEntity>`](Collections-of-Users)
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
- [Print user domain counts](#print-user-domain-counts)
|
||||
- [Print domain counts for users in a specific domain and/or selected by a query](#print-domain-counts-for-users-in-a-specific-domain-and-or-selected-by-a-query)
|
||||
- [Print domain counts for users specified by `<UserTypeEntity>`](#print-domain-counts-for-users-specified-by-usertypeentity)
|
||||
- [Print user counts by OrgUnit](print-user-counts-by-orgunit)
|
||||
- [Print user counts by OrgUnit](#print-user-counts-by-orgunit)
|
||||
- [Print user list](#print-user-list)
|
||||
- [Display user counts](#display-user-counts)
|
||||
- [Verify domain membership]($verify-domain-membership)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.00.16 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.00.24 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 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
|
||||
GAM 7.00.16 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.00.24 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 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
|
||||
GAM 7.00.16 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.00.24 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 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/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.00.16
|
||||
Latest: 7.00.24
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -72,7 +72,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.00.16
|
||||
7.00.24
|
||||
```
|
||||
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 7.00.16 - https://github.com/GAM-team/GAM
|
||||
GAM 7.00.24 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.13.0 64-bit final
|
||||
MacOS Sonoma 14.5 x86_64
|
||||
|
||||
@@ -214,6 +214,12 @@ csv_output_header_force
|
||||
A list of <Strings> used to specify the exact column headers
|
||||
for inclusion in the CSV file written by a gam print command
|
||||
Default: ''
|
||||
csv_output_header_order
|
||||
A list of <Strings> used to specify the order of column headers
|
||||
for inclusion in the CSV file written by a gam print command
|
||||
Any headers in the file but not in the list will appear after
|
||||
the headers in the list
|
||||
Default: ''
|
||||
csv_output_line_terminator
|
||||
Allowed values: cr, lf, crlf
|
||||
Designates character(s) used to terminate the lines of a CSV file.
|
||||
@@ -305,6 +311,9 @@ drive_max_results
|
||||
how many should be retrieved in each API call
|
||||
Default: 1000
|
||||
Range: 1 - 1000
|
||||
drive_v3_beta
|
||||
Enable/disable use of Drive API v3 beta for Limited Folder Access testing
|
||||
Default: False
|
||||
drive_v3_native_names
|
||||
Enable/disable use of Drive API v3 native column names
|
||||
in all gam print/show commands related to Google Drive
|
||||
|
||||
@@ -1,3 +1,56 @@
|
||||
7.00.24
|
||||
|
||||
Updated `gam print|show projects ... showiampolicies 0|1|3` to use non-service account authentication.
|
||||
|
||||
7.00.23
|
||||
|
||||
Updated `gam <UserTypeEntity> create|delete chatmember` to accept external (non-domain) email addresses.
|
||||
|
||||
7.00.22
|
||||
|
||||
Fixed bug in `gam create vaultmatter ... showdetails` that caused a trap.
|
||||
|
||||
7.00.21
|
||||
|
||||
Added `csv_output_header_order` variable to `gam.cfg` that is a list of `<Strings>`
|
||||
that are used to specify the order of column headers in the CSV file written by a gam print command.
|
||||
Any headers in the file but not in the list will appear after the headers in the list.
|
||||
|
||||
This might be used when the CSV file data is to be processed by another program
|
||||
that requires that the headers be in a particular order.
|
||||
|
||||
7.00.20
|
||||
|
||||
Fix Windows MSI installer issues on version upgrade. If you are having issues upgrading from a version older than 7.00.20 to this version or newer you may need to do a one time uninstall of GAM7 and then reinstall the new version.
|
||||
No configuration files will be lost during the uninstall / reinstall.
|
||||
|
||||
7.00.19
|
||||
|
||||
Updated `gam update shareddrive <SharedDriveEntity> ou <OrgUnitItem>` to handle the following error
|
||||
that occurs when an invalid `<SharedDriveEntity>` is specified.
|
||||
```
|
||||
ERROR: 400: invalidArgument - Invalid org membership name 0AJ3b2FTPakToUk9PVAxx.~
|
||||
```
|
||||
|
||||
Updated `gam print browsers` to properly format the time field `deviceIdentifiersHistory.records.0.firstRecordTime`.
|
||||
|
||||
7.00.18
|
||||
|
||||
Updated `gam create project` to use a default project name of `gam-project-a1b2c` (`a1b2c` is a random string of 5 characters)
|
||||
instead of `gam-project-abc-123-xyz` to avoid the following warning:
|
||||
```
|
||||
Project: gam-project-abc-123-xyz, Service Account: gam-project-abc-123-xyz@gam-project-abc-123-xyz.iam.gserviceaccount.com, Extracting public certificate
|
||||
init.py:12382: UserWarning: Attribute's length must be >= 1 and <= 64, but it was 70
|
||||
init.py:12383: UserWarning: Attribute's length must be >= 1 and <= 64, but it was 70
|
||||
Project: gam-project-abc-123-xyz, Service Account: gam-project-abc-123-xyz@gam-project-abc-123-xyz.iam.gserviceaccount.com, Done generating private key and public certificate
|
||||
```
|
||||
|
||||
7.00.17
|
||||
|
||||
Update all user calendar commands to disable falling back to client access if service account
|
||||
authorization has never been performed. Previously, in this circumstance, the admin's calendars
|
||||
rather than the user's calendars were processed.
|
||||
|
||||
7.00.16
|
||||
|
||||
Updated `gam <UserTypeEntity> claim|transfer ownership` to show `Got N Drive Files/Folders that matched query` messages
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.00.16'
|
||||
__version__ = '7.00.24'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
#pylint: disable=wrong-import-position
|
||||
@@ -4008,7 +4008,7 @@ def SetGlobalVariables():
|
||||
GC.Values[itemName] = _getCfgPassword(sectionName, itemName)
|
||||
elif varType == GC.TYPE_STRING:
|
||||
GC.Values[itemName] = _getCfgString(sectionName, itemName)
|
||||
elif varType in {GC.TYPE_STRINGLIST, GC.TYPE_HEADERFORCE}:
|
||||
elif varType in {GC.TYPE_STRINGLIST, GC.TYPE_HEADERFORCE, GC.TYPE_HEADERORDER}:
|
||||
GC.Values[itemName] = _getCfgStringList(sectionName, itemName)
|
||||
elif varType == GC.TYPE_FILE:
|
||||
GC.Values[itemName] = _getCfgFile(sectionName, itemName)
|
||||
@@ -4031,6 +4031,7 @@ def SetGlobalVariables():
|
||||
else:
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_FILTER] = _getCfgHeaderFilter(outputFilterSectionName, GC.CSV_OUTPUT_HEADER_FILTER)
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_DROP_FILTER] = _getCfgHeaderFilter(outputFilterSectionName, GC.CSV_OUTPUT_HEADER_DROP_FILTER)
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_ORDER] = _getCfgStringList(outputFilterSectionName, GC.CSV_OUTPUT_HEADER_ORDER)
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER] = _getCfgRowFilter(outputFilterSectionName, GC.CSV_OUTPUT_ROW_FILTER)
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER_MODE] = _getCfgChoice(outputFilterSectionName, GC.CSV_OUTPUT_ROW_FILTER_MODE)
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_DROP_FILTER] = _getCfgRowFilter(outputFilterSectionName, GC.CSV_OUTPUT_ROW_DROP_FILTER)
|
||||
@@ -4151,7 +4152,7 @@ def SetGlobalVariables():
|
||||
if GM.Globals[GM.PID] == 0:
|
||||
for itemName, itemEntry in sorted(iter(GC.VAR_INFO.items())):
|
||||
varType = itemEntry[GC.VAR_TYPE]
|
||||
if varType in {GC.TYPE_HEADERFILTER, GC.TYPE_HEADERFORCE, GC.TYPE_ROWFILTER}:
|
||||
if varType in {GC.TYPE_HEADERFILTER, GC.TYPE_HEADERFORCE, GC.TYPE_HEADERORDER, GC.TYPE_ROWFILTER}:
|
||||
GM.Globals[GM.PARSER].set(sectionName, itemName, '')
|
||||
elif (varType == GC.TYPE_INTEGER) and itemName in {GC.CSV_INPUT_ROW_LIMIT, GC.CSV_OUTPUT_ROW_LIMIT}:
|
||||
GM.Globals[GM.PARSER].set(sectionName, itemName, '0')
|
||||
@@ -4164,6 +4165,8 @@ def SetGlobalVariables():
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_DROP_FILTER] = GM.Globals[GM.CSV_OUTPUT_HEADER_DROP_FILTER][:]
|
||||
if not GC.Values[GC.CSV_OUTPUT_HEADER_FORCE]:
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_FORCE] = GM.Globals[GM.CSV_OUTPUT_HEADER_FORCE][:]
|
||||
if not GC.Values[GC.CSV_OUTPUT_HEADER_ORDER]:
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_ORDER] = GM.Globals[GM.CSV_OUTPUT_HEADER_ORDER][:]
|
||||
if not GC.Values[GC.CSV_OUTPUT_ROW_FILTER]:
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER] = GM.Globals[GM.CSV_OUTPUT_ROW_FILTER][:]
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER_MODE] = GM.Globals[GM.CSV_OUTPUT_ROW_FILTER_MODE]
|
||||
@@ -6056,7 +6059,7 @@ def checkGroupExists(cd, ci, ciGroupsAPI, group, i=0, count=0):
|
||||
|
||||
# Turn the entity into a list of Users/CrOS devices
|
||||
def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isArchived=None,
|
||||
groupMemberType=Ent.TYPE_USER, noListConversion=False):
|
||||
groupMemberType=Ent.TYPE_USER, noListConversion=False, recursive=False, noCLArgs=False):
|
||||
def _incrEntityDoesNotExist(entityType):
|
||||
entityError['entityType'] = entityType
|
||||
entityError[ENTITY_ERROR_DNE] += 1
|
||||
@@ -6227,32 +6230,33 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
|
||||
isSuspended = True
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
groups = convertEntityToList(entity)
|
||||
includeDerivedMembership = recursive = False
|
||||
includeDerivedMembership = False
|
||||
domains = []
|
||||
rolesSet = set()
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in GROUP_ROLES_MAP:
|
||||
rolesSet.add(GROUP_ROLES_MAP[myarg])
|
||||
elif myarg == 'primarydomain':
|
||||
domains.append(GC.Values[GC.DOMAIN])
|
||||
elif myarg == 'domains':
|
||||
domains.extend(getEntityList(Cmd.OB_DOMAIN_NAME_ENTITY))
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
includeDerivedMembership = False
|
||||
elif myarg == 'includederivedmembership':
|
||||
includeDerivedMembership = True
|
||||
recursive = False
|
||||
elif entityType == Cmd.ENTITY_GROUP_USERS_SELECT and myarg in SUSPENDED_ARGUMENTS:
|
||||
isSuspended = _getIsSuspended(myarg)
|
||||
elif entityType == Cmd.ENTITY_GROUP_USERS_SELECT and myarg in ARCHIVED_ARGUMENTS:
|
||||
isArchived = _getIsArchived(myarg)
|
||||
elif myarg == 'end':
|
||||
break
|
||||
else:
|
||||
Cmd.Backup()
|
||||
missingArgumentExit('end')
|
||||
if not noCLArgs:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in GROUP_ROLES_MAP:
|
||||
rolesSet.add(GROUP_ROLES_MAP[myarg])
|
||||
elif myarg == 'primarydomain':
|
||||
domains.append(GC.Values[GC.DOMAIN])
|
||||
elif myarg == 'domains':
|
||||
domains.extend(getEntityList(Cmd.OB_DOMAIN_NAME_ENTITY))
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
includeDerivedMembership = False
|
||||
elif myarg == 'includederivedmembership':
|
||||
includeDerivedMembership = True
|
||||
recursive = False
|
||||
elif entityType == Cmd.ENTITY_GROUP_USERS_SELECT and myarg in SUSPENDED_ARGUMENTS:
|
||||
isSuspended = _getIsSuspended(myarg)
|
||||
elif entityType == Cmd.ENTITY_GROUP_USERS_SELECT and myarg in ARCHIVED_ARGUMENTS:
|
||||
isArchived = _getIsArchived(myarg)
|
||||
elif myarg == 'end':
|
||||
break
|
||||
else:
|
||||
Cmd.Backup()
|
||||
missingArgumentExit('end')
|
||||
if rolesSet:
|
||||
memberRoles = ','.join(sorted(rolesSet))
|
||||
for group in groups:
|
||||
@@ -6293,19 +6297,19 @@ def getItemsToModify(entityType, entity, memberRoles=None, isSuspended=None, isA
|
||||
elif entityType in {Cmd.ENTITY_CIGROUP_USERS}:
|
||||
ci = buildGAPIObject(API.CLOUDIDENTITY_GROUPS)
|
||||
groups = convertEntityToList(entity)
|
||||
recursive = False
|
||||
rolesSet = set()
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in GROUP_ROLES_MAP:
|
||||
rolesSet.add(GROUP_ROLES_MAP[myarg])
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
elif myarg == 'end':
|
||||
break
|
||||
else:
|
||||
Cmd.Backup()
|
||||
missingArgumentExit('end')
|
||||
if not noCLArgs:
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in GROUP_ROLES_MAP:
|
||||
rolesSet.add(GROUP_ROLES_MAP[myarg])
|
||||
elif myarg == 'recursive':
|
||||
recursive = True
|
||||
elif myarg == 'end':
|
||||
break
|
||||
else:
|
||||
Cmd.Backup()
|
||||
missingArgumentExit('end')
|
||||
if rolesSet:
|
||||
memberRoles = ','.join(sorted(rolesSet))
|
||||
for group in groups:
|
||||
@@ -7730,6 +7734,7 @@ class CSVPrintFile():
|
||||
if not self.headerForce and titles is not None:
|
||||
self.SetTitles(titles)
|
||||
self.SetJSONTitles(titles)
|
||||
self.SetHeaderOrder(GC.Values[GC.CSV_OUTPUT_HEADER_ORDER])
|
||||
if GM.Globals.get(GM.CSV_OUTPUT_COLUMN_DELIMITER) is None:
|
||||
GM.Globals[GM.CSV_OUTPUT_COLUMN_DELIMITER] = GC.Values.get(GC.CSV_OUTPUT_COLUMN_DELIMITER, ',')
|
||||
self.SetColumnDelimiter(GM.Globals[GM.CSV_OUTPUT_COLUMN_DELIMITER])
|
||||
@@ -7739,10 +7744,12 @@ class CSVPrintFile():
|
||||
GM.Globals[GM.CSV_OUTPUT_NO_ESCAPE_CHAR] = GC.Values.get(GC.CSV_OUTPUT_NO_ESCAPE_CHAR, False)
|
||||
self.SetNoEscapeChar(GM.Globals[GM.CSV_OUTPUT_NO_ESCAPE_CHAR])
|
||||
self.SetQuoteChar(GM.Globals[GM.CSV_OUTPUT_QUOTE_CHAR])
|
||||
if GM.Globals.get(GM.CSV_OUTPUT_SORT_HEADERS) is None:
|
||||
# if GM.Globals.get(GM.CSV_OUTPUT_SORT_HEADERS) is None:
|
||||
if not GM.Globals.get(GM.CSV_OUTPUT_SORT_HEADERS):
|
||||
GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS] = GC.Values.get(GC.CSV_OUTPUT_SORT_HEADERS, [])
|
||||
self.SetSortHeaders(GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS])
|
||||
if GM.Globals.get(GM.CSV_OUTPUT_TIMESTAMP_COLUMN) is None:
|
||||
# if GM.Globals.get(GM.CSV_OUTPUT_TIMESTAMP_COLUMN) is None:
|
||||
if not GM.Globals.get(GM.CSV_OUTPUT_TIMESTAMP_COLUMN):
|
||||
GM.Globals[GM.CSV_OUTPUT_TIMESTAMP_COLUMN] = GC.Values.get(GC.CSV_OUTPUT_TIMESTAMP_COLUMN, '')
|
||||
self.SetTimestampColumn(GM.Globals[GM.CSV_OUTPUT_TIMESTAMP_COLUMN])
|
||||
self.SetFormatJSON(False)
|
||||
@@ -8430,6 +8437,15 @@ class CSVPrintFile():
|
||||
self.SetTitles(headerForce)
|
||||
self.SetJSONTitles(headerForce)
|
||||
|
||||
def SetHeaderOrder(self, headerOrder):
|
||||
self.headerOrder = headerOrder
|
||||
|
||||
def orderHeaders(self, titlesList):
|
||||
for title in self.headerOrder:
|
||||
if title in titlesList:
|
||||
titlesList.remove(title)
|
||||
return self.headerOrder+titlesList
|
||||
|
||||
@staticmethod
|
||||
def HeaderFilterMatch(filters, title):
|
||||
for filterStr in filters:
|
||||
@@ -8869,14 +8885,21 @@ class CSVPrintFile():
|
||||
self.FixNodataTitles()
|
||||
if self.mapDrive3Titles:
|
||||
self. MapDrive3TitlesToDrive2()
|
||||
else:
|
||||
self.titlesList = self.headerForce
|
||||
if self.timestampColumn:
|
||||
self.AddTitle(self.timestampColumn)
|
||||
if self.headerOrder:
|
||||
self.titlesList = self.orderHeaders(self.titlesList)
|
||||
titlesList = self.titlesList
|
||||
else:
|
||||
if self.fixPaths:
|
||||
self.FixPathsTitles(self.JSONtitlesList)
|
||||
if not self.rows and self.nodataFields is not None:
|
||||
self.FixNodataTitles()
|
||||
if not self.headerForce:
|
||||
if self.fixPaths:
|
||||
self.FixPathsTitles(self.JSONtitlesList)
|
||||
if not self.rows and self.nodataFields is not None:
|
||||
self.FixNodataTitles()
|
||||
else:
|
||||
self.JSONtitlesList = self.headerForce
|
||||
if self.timestampColumn:
|
||||
for i, v in enumerate(self.JSONtitlesList):
|
||||
if v.startswith('JSON'):
|
||||
@@ -8885,6 +8908,8 @@ class CSVPrintFile():
|
||||
break
|
||||
else:
|
||||
self.AddJSONTitle(self.timestampColumn)
|
||||
if self.headerOrder:
|
||||
self.JSONtitlesList = self.orderHeaders(self.JSONtitlesList)
|
||||
titlesList = self.JSONtitlesList
|
||||
normalizeSortHeaders()
|
||||
if (not self.todrive) or self.todrive['localcopy']:
|
||||
@@ -9622,7 +9647,7 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
|
||||
csvColumnDelimiter, csvNoEscapeChar, csvQuoteChar,
|
||||
csvSortHeaders, csvTimestampColumn,
|
||||
csvHeaderFilter, csvHeaderDropFilter,
|
||||
csvHeaderForce,
|
||||
csvHeaderForce, csvHeaderOrder,
|
||||
csvRowFilter, csvRowFilterMode, csvRowDropFilter, csvRowDropFilterMode,
|
||||
csvRowLimit,
|
||||
showGettings, showGettingsGotNL,
|
||||
@@ -9646,13 +9671,14 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
|
||||
GM.Globals[GM.CSV_OUTPUT_HEADER_DROP_FILTER] = csvHeaderDropFilter[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_HEADER_FILTER] = csvHeaderFilter[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_HEADER_FORCE] = csvHeaderForce[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_HEADER_ORDER] = csvHeaderOrder[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_QUOTE_CHAR] = csvQuoteChar
|
||||
GM.Globals[GM.CSV_OUTPUT_ROW_DROP_FILTER] = csvRowDropFilter[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_ROW_DROP_FILTER_MODE] = csvRowDropFilterMode
|
||||
GM.Globals[GM.CSV_OUTPUT_ROW_FILTER] = csvRowFilter[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_ROW_FILTER_MODE] = csvRowFilterMode
|
||||
GM.Globals[GM.CSV_OUTPUT_ROW_LIMIT] = csvRowLimit
|
||||
GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS] = csvSortHeaders
|
||||
GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS] = csvSortHeaders[:]
|
||||
GM.Globals[GM.CSV_OUTPUT_TIMESTAMP_COLUMN] = csvTimestampColumn
|
||||
GM.Globals[GM.CSV_TODRIVE] = todrive.copy()
|
||||
GM.Globals[GM.DEBUG_LEVEL] = debugLevel
|
||||
@@ -9660,9 +9686,9 @@ def ProcessGAMCommandMulti(pid, numItems, logCmd, mpQueueCSVFile, mpQueueStdout,
|
||||
GM.Globals[GM.OUTPUT_TIMEFORMAT] = output_timeformat
|
||||
GM.Globals[GM.NUM_BATCH_ITEMS] = numItems
|
||||
GM.Globals[GM.PID] = pid
|
||||
GM.Globals[GM.PRINT_AGU_DOMAINS] = printAguDomains
|
||||
GM.Globals[GM.PRINT_CROS_OUS] = printCrosOUs
|
||||
GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN] = printCrosOUsAndChildren
|
||||
GM.Globals[GM.PRINT_AGU_DOMAINS] = printAguDomains[:]
|
||||
GM.Globals[GM.PRINT_CROS_OUS] = printCrosOUs[:]
|
||||
GM.Globals[GM.PRINT_CROS_OUS_AND_CHILDREN] = printCrosOUsAndChildren[:]
|
||||
GM.Globals[GM.SAVED_STDOUT] = None
|
||||
GM.Globals[GM.SHOW_GETTINGS] = showGettings
|
||||
GM.Globals[GM.SHOW_GETTINGS_GOT_NL] = showGettingsGotNL
|
||||
@@ -9869,6 +9895,7 @@ def MultiprocessGAMCommands(items, showCmds):
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_FILTER],
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_DROP_FILTER],
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_FORCE],
|
||||
GC.Values[GC.CSV_OUTPUT_HEADER_ORDER],
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER],
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_FILTER_MODE],
|
||||
GC.Values[GC.CSV_OUTPUT_ROW_DROP_FILTER],
|
||||
@@ -11428,10 +11455,7 @@ def _getAppInfo(myarg, appInfo):
|
||||
return True
|
||||
|
||||
def _generateProjectSvcAcctId(prefix):
|
||||
psaId = prefix
|
||||
for _ in range(3):
|
||||
psaId += f'-{"".join(random.choice(LOWERNUMERIC_CHARS) for _ in range(3))}'
|
||||
return psaId
|
||||
return f'{prefix}-{"".join(random.choice(LOWERNUMERIC_CHARS) for _ in range(5))}'
|
||||
|
||||
def _getLoginHintProjectInfo(createCmd):
|
||||
login_hint = None
|
||||
@@ -11827,10 +11851,11 @@ def doPrintShowProjects():
|
||||
resource=project['name'], body=policyBody)
|
||||
return policy
|
||||
except (GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||
entityActionFailedWarning([Ent.PROJECT, project['projectId'], Ent.IAM_POLICY], str(e), i, count)
|
||||
entityActionFailedWarning([Ent.PROJECT, project['projectId'], Ent.IAM_POLICY, None], str(e), i, count)
|
||||
return {}
|
||||
|
||||
crm, _, login_hint, projects = _getLoginHintProjects(printShowCmd=True, readOnly=True)
|
||||
readOnly = not Cmd.ArgumentIsAhead('showiampolicies')
|
||||
crm, _, login_hint, projects = _getLoginHintProjects(printShowCmd=True, readOnly=readOnly)
|
||||
csvPF = CSVPrintFile(['User', 'projectId']) if Act.csvFormat() else None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
oneMemberPerRow = False
|
||||
@@ -24995,7 +25020,7 @@ def doDeleteBrowsers():
|
||||
except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden):
|
||||
checkEntityAFDNEorAccessErrorExit(None, Ent.CHROME_BROWSER, deviceId)
|
||||
|
||||
BROWSER_TIME_OBJECTS = {'lastActivityTime', 'lastPolicyFetchTime', 'lastRegistrationTime', 'lastStatusReportTime', 'safeBrowsingWarningsResetTime'}
|
||||
BROWSER_TIME_OBJECTS = {'firstRecordTime', 'lastActivityTime', 'lastPolicyFetchTime', 'lastRegistrationTime', 'lastStatusReportTime', 'safeBrowsingWarningsResetTime'}
|
||||
|
||||
def _showBrowser(browser, FJQC, i=0, count=0):
|
||||
if FJQC.formatJSON:
|
||||
@@ -25782,7 +25807,7 @@ CHAT_UPDATE_SPACE_PERMISSIONS_MAP = {
|
||||
'managewebhooks': 'manageWebhooks',
|
||||
'replymessages': 'replyMessages',
|
||||
}
|
||||
|
||||
|
||||
# gam <UserTypeEntity> update chatspace <ChatSpace>
|
||||
# [restricted|(audience <String>)]|
|
||||
# ([displayname <String>]
|
||||
@@ -26091,10 +26116,13 @@ def _getChatMemberEmail(cd, member):
|
||||
_, memberUid = member['groupMember']['name'].split('/')
|
||||
member['groupMember']['email'], _ = convertUIDtoEmailAddressWithType(f'uid:{memberUid}', cd, None, emailTypes=['group'])
|
||||
|
||||
def normalizeUserMember(cd, user, userList):
|
||||
def normalizeUserMember(user, userList):
|
||||
userList.append(normalizeEmailAddressOrUID(user))
|
||||
|
||||
def getUserMemberID(cd, user, userList):
|
||||
userList.append(convertEmailAddressToUID(user, cd, emailType='user'))
|
||||
|
||||
def normalizeGroupMember(cd, group, groupList):
|
||||
def getGroupMemberID(cd, group, groupList):
|
||||
groupList.append(convertEmailAddressToUID(group, cd, emailType='group'))
|
||||
|
||||
# gam <UserTypeEntity> create chatmember <ChatSpace>
|
||||
@@ -26157,16 +26185,16 @@ def createChatMember(users):
|
||||
if myarg == 'space' or myarg.startswith('spaces/') or myarg.startswith('space/'):
|
||||
parent = getSpaceName(myarg)
|
||||
elif myarg == 'user':
|
||||
normalizeUserMember(cd, getEmailAddress(returnUIDprefix='uid:'), userList)
|
||||
normalizeUserMember(getEmailAddress(returnUIDprefix='uid:'), userList)
|
||||
elif myarg in {'member', 'members'}:
|
||||
_, members = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
for user in members:
|
||||
normalizeUserMember(cd, user, userList)
|
||||
normalizeUserMember(user, userList)
|
||||
elif myarg == 'group':
|
||||
normalizeGroupMember(cd, getEmailAddress(returnUIDprefix='uid:'), groupList)
|
||||
getGroupMemberID(cd, getEmailAddress(returnUIDprefix='uid:'), groupList)
|
||||
elif myarg == 'groups':
|
||||
for group in getEntityList(Cmd.OB_GROUP_ENTITY):
|
||||
normalizeGroupMember(cd, group, groupList)
|
||||
getGroupMemberID(cd, group, groupList)
|
||||
elif myarg == 'role':
|
||||
role = getChoice(CHAT_MEMBER_ROLE_MAP, mapChoice=True)
|
||||
elif myarg == 'type':
|
||||
@@ -26264,16 +26292,16 @@ def deleteUpdateChatMember(users):
|
||||
if myarg == 'space' or myarg.startswith('spaces/') or myarg.startswith('space/'):
|
||||
parent = getSpaceName(myarg)
|
||||
elif myarg == 'user':
|
||||
normalizeUserMember(cd, getEmailAddress(returnUIDprefix='uid:'), userList)
|
||||
normalizeUserMember(getEmailAddress(returnUIDprefix='uid:'), userList)
|
||||
elif myarg in {'member', 'members'}:
|
||||
_, members = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
for user in members:
|
||||
normalizeUserMember(cd, user, userList)
|
||||
normalizeUserMember(user, userList)
|
||||
elif deleteMode and myarg == 'group':
|
||||
normalizeGroupMember(cd, getEmailAddress(returnUIDprefix='uid:'), groupList)
|
||||
getGroupMemberID(cd, getEmailAddress(returnUIDprefix='uid:'), groupList)
|
||||
elif deleteMode and myarg == 'groups':
|
||||
for group in getEntityList(Cmd.OB_GROUP_ENTITY):
|
||||
normalizeGroupMember(cd, group, groupList)
|
||||
getGroupMemberID(cd, group, groupList)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if not deleteMode and 'role' not in body:
|
||||
@@ -26417,16 +26445,16 @@ def syncChatMembers(users):
|
||||
csvPF = CSVPrintFile(CHAT_SYNC_PREVIEW_TITLES)
|
||||
elif myarg == 'users':
|
||||
for user in getEntityList(Cmd.OB_USER_ENTITY):
|
||||
normalizeUserMember(cd, user, userList)
|
||||
getUserMemberID(cd, user, userList)
|
||||
usersSpecified = True
|
||||
elif myarg in {'member', 'members'}:
|
||||
_, members = getEntityToModify(defaultEntityType=Cmd.ENTITY_USERS)
|
||||
for user in members:
|
||||
normalizeUserMember(cd, user, userList)
|
||||
getUserMemberID(cd, user, userList)
|
||||
usersSpecified = True
|
||||
elif myarg == 'groups':
|
||||
for group in getEntityList(Cmd.OB_GROUP_ENTITY):
|
||||
normalizeGroupMember(cd, group, groupList)
|
||||
getGroupMemberID(cd, group, groupList)
|
||||
groupsSpecified = True
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
@@ -37028,11 +37056,13 @@ def checkCalendarExists(cal, calId, showMessage=False):
|
||||
entityActionFailedWarning([Ent.CALENDAR, calId], str(e))
|
||||
return None
|
||||
|
||||
def validateCalendar(calId, i=0, count=0):
|
||||
def validateCalendar(calId, i=0, count=0, noClientAccess=False):
|
||||
cal = None
|
||||
if not calId.endswith('.calendar.google.com'):
|
||||
calId, cal = buildGAPIServiceObject(API.CALENDAR, calId, i, count, displayError=False)
|
||||
calId, cal = buildGAPIServiceObject(API.CALENDAR, calId, i, count, displayError=noClientAccess)
|
||||
if not cal:
|
||||
if noClientAccess:
|
||||
return (calId, None)
|
||||
cal = buildGAPIObject(API.CALENDAR)
|
||||
try:
|
||||
callGAPI(cal.calendars(), 'get',
|
||||
@@ -41128,7 +41158,7 @@ def doCreateVaultMatter():
|
||||
break
|
||||
Ind.Decrement()
|
||||
if showDetails:
|
||||
_showVaultMatter(None, matter, cd, None)
|
||||
_showVaultMatter(matter, cd, None)
|
||||
|
||||
VAULT_MATTER_ACTIONS = {
|
||||
'close': Act.CLOSE,
|
||||
@@ -49759,7 +49789,7 @@ def _validateUserGetCalendarIds(user, i, count, calendarEntity,
|
||||
calIds = calendarEntity['dict'][user][:]
|
||||
else:
|
||||
calIds = calendarEntity['list'][:]
|
||||
user, cal = validateCalendar(user, i, count)
|
||||
user, cal = validateCalendar(user, i, count, noClientAccess=True)
|
||||
if not cal:
|
||||
return (user, None, None, 0)
|
||||
if calendarEntity['resourceIds']:
|
||||
@@ -50178,7 +50208,7 @@ def printShowCalendars(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, cal = validateCalendar(user, i, count)
|
||||
user, cal = validateCalendar(user, i, count, noClientAccess=True)
|
||||
if not cal:
|
||||
continue
|
||||
if csvPF:
|
||||
@@ -50278,7 +50308,7 @@ def printShowCalSettings(users):
|
||||
i, count, users = getEntityArgument(users)
|
||||
for user in users:
|
||||
i += 1
|
||||
user, cal = validateCalendar(user, i, count)
|
||||
user, cal = validateCalendar(user, i, count, noClientAccess=True)
|
||||
if not cal:
|
||||
continue
|
||||
try:
|
||||
@@ -50438,7 +50468,7 @@ def transferCalendars(users):
|
||||
_getCalendarAttributes(targetListBody, returnOnUnknownArgument=True)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
targetUser, targetCal = validateCalendar(targetUser)
|
||||
targetUser, targetCal = validateCalendar(targetUser, noClientAccess=True)
|
||||
if not targetCal:
|
||||
return
|
||||
colorRgbFormat = 'backgroundColor' in targetListBody or 'foregroundColor' in targetListBody
|
||||
@@ -57236,7 +57266,7 @@ def createDriveFile(users):
|
||||
continue
|
||||
result = callGAPI(drive.files(), 'create',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.FORBIDDEN, GAPI.INSUFFICIENT_PERMISSIONS, GAPI.INSUFFICIENT_PARENT_PERMISSIONS,
|
||||
GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.CANNOT_ADD_PARENT,
|
||||
GAPI.PERMISSION_DENIED, GAPI.INVALID, GAPI.BAD_REQUEST, GAPI.CANNOT_ADD_PARENT,
|
||||
GAPI.FILE_NOT_FOUND, GAPI.UNKNOWN_ERROR, GAPI.INTERNAL_ERROR,
|
||||
GAPI.STORAGE_QUOTA_EXCEEDED, GAPI.TEAMDRIVES_SHARING_RESTRICTION_NOT_ALLOWED,
|
||||
GAPI.TEAMDRIVE_FILE_LIMIT_EXCEEDED, GAPI.TEAMDRIVE_HIERARCHY_TOO_DEEP,
|
||||
@@ -57275,7 +57305,7 @@ def createDriveFile(users):
|
||||
row.update(addCSVData)
|
||||
csvPF.WriteRow(row)
|
||||
except (GAPI.forbidden, GAPI.insufficientFilePermissions, GAPI.insufficientParentPermissions,
|
||||
GAPI.invalidQuery, GAPI.invalid, GAPI.badRequest, GAPI.cannotAddParent,
|
||||
GAPI.invalidQuery, GAPI.permissionDenied, GAPI.invalid, GAPI.badRequest, GAPI.cannotAddParent,
|
||||
GAPI.fileNotFound, GAPI.unknownError, GAPI.storageQuotaExceeded, GAPI.teamDrivesSharingRestrictionNotAllowed,
|
||||
GAPI.teamDriveFileLimitExceeded, GAPI.teamDriveHierarchyTooDeep,
|
||||
GAPI.uploadTooLarge, GAPI.teamDrivesShortcutFileNotSupported) as e:
|
||||
@@ -64285,13 +64315,15 @@ def _moveSharedDriveToOU(orgUnit, orgUnitId, driveId, user, i, count, ci, return
|
||||
'destinationOrgUnit': f'orgUnits/{orgUnitId[3:]}'}
|
||||
try:
|
||||
callGAPI(ci.orgUnits().memberships(), 'move',
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FORBIDDEN, GAPI.ABORTED],
|
||||
throwReasons=GAPI.DRIVE_USER_THROW_REASONS+[GAPI.NOT_FOUND, GAPI.FORBIDDEN,
|
||||
GAPI.INVALID_ARGUMENT, GAPI.ABORTED],
|
||||
name=name, body=cibody)
|
||||
if not returnIdOnly:
|
||||
Act.Set(Act.MOVE)
|
||||
entityModifierNewValueActionPerformed([Ent.SHAREDDRIVE, driveId], Act.MODIFIER_TO, f'{Ent.Singular(Ent.ORGANIZATIONAL_UNIT)}: {orgUnit}', i, count)
|
||||
entityModifierNewValueActionPerformed([Ent.USER, user, Ent.SHAREDDRIVE, driveId], Act.MODIFIER_TO,
|
||||
f'{Ent.Singular(Ent.ORGANIZATIONAL_UNIT)}: {orgUnit}', i, count)
|
||||
except (GAPI.notFound, GAPI.forbidden, GAPI.aborted, GAPI.badRequest, GAPI.internalError,
|
||||
GAPI.noManageTeamDriveAdministratorPrivilege) as e:
|
||||
GAPI.noManageTeamDriveAdministratorPrivilege, GAPI.invalidArgument) as e:
|
||||
entityActionFailedWarning([Ent.USER, user, Ent.SHAREDDRIVE_ID, driveId], str(e), i, count)
|
||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy) as e:
|
||||
userSvcNotApplicableOrDriveDisabled(user, str(e), i, count)
|
||||
|
||||
@@ -117,6 +117,8 @@ CSV_OUTPUT_HEADER_FILTER = 'csv_output_header_filter'
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER = 'csv_output_header_drop_filter'
|
||||
# Force output column headers
|
||||
CSV_OUTPUT_HEADER_FORCE = 'csv_output_header_force'
|
||||
# Orde output column headers
|
||||
CSV_OUTPUT_HEADER_ORDER = 'csv_output_header_order'
|
||||
# Line terminator in CSV output file
|
||||
CSV_OUTPUT_LINE_TERMINATOR = 'csv_output_line_terminator'
|
||||
# Quote character in CSV output file
|
||||
@@ -309,7 +311,8 @@ CSV_INPUT_ROW_FILTER_ITEMS = {CSV_INPUT_ROW_FILTER, CSV_INPUT_ROW_FILTER_MODE,
|
||||
CSV_INPUT_ROW_DROP_FILTER, CSV_INPUT_ROW_DROP_FILTER_MODE,
|
||||
CSV_INPUT_ROW_LIMIT}
|
||||
|
||||
CSV_OUTPUT_ROW_FILTER_ITEMS = {CSV_OUTPUT_HEADER_FILTER, CSV_OUTPUT_HEADER_DROP_FILTER, CSV_OUTPUT_HEADER_FORCE,
|
||||
CSV_OUTPUT_ROW_FILTER_ITEMS = {CSV_OUTPUT_HEADER_FILTER, CSV_OUTPUT_HEADER_DROP_FILTER,
|
||||
CSV_OUTPUT_HEADER_FORCE, CSV_OUTPUT_HEADER_ORDER,
|
||||
CSV_OUTPUT_ROW_FILTER, CSV_OUTPUT_ROW_FILTER_MODE,
|
||||
CSV_OUTPUT_ROW_DROP_FILTER, CSV_OUTPUT_ROW_DROP_FILTER_MODE,
|
||||
CSV_OUTPUT_ROW_LIMIT}
|
||||
@@ -351,6 +354,7 @@ Defaults = {
|
||||
CSV_OUTPUT_HEADER_FILTER: '',
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: '',
|
||||
CSV_OUTPUT_HEADER_FORCE: '',
|
||||
CSV_OUTPUT_HEADER_ORDER: '',
|
||||
CSV_OUTPUT_LINE_TERMINATOR: 'lf',
|
||||
CSV_OUTPUT_QUOTE_CHAR: '\'"\'',
|
||||
CSV_OUTPUT_ROW_FILTER: '',
|
||||
@@ -460,6 +464,7 @@ TYPE_FILE = 'file'
|
||||
TYPE_FLOAT = 'floa'
|
||||
TYPE_HEADERFILTER = 'heaf'
|
||||
TYPE_HEADERFORCE = 'hefo'
|
||||
TYPE_HEADERORDER = 'heor'
|
||||
TYPE_INTEGER = 'inte'
|
||||
TYPE_LANGUAGE = 'lang'
|
||||
TYPE_LOCALE = 'locl'
|
||||
@@ -514,6 +519,7 @@ VAR_INFO = {
|
||||
CSV_OUTPUT_HEADER_FILTER: {VAR_TYPE: TYPE_HEADERFILTER},
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: {VAR_TYPE: TYPE_HEADERFILTER},
|
||||
CSV_OUTPUT_HEADER_FORCE: {VAR_TYPE: TYPE_HEADERFORCE},
|
||||
CSV_OUTPUT_HEADER_ORDER: {VAR_TYPE: TYPE_HEADERORDER},
|
||||
CSV_OUTPUT_LINE_TERMINATOR: {VAR_TYPE: TYPE_CHOICE, VAR_CHOICES: {'cr': '\r', 'lf': '\n', 'crlf': '\r\n'}},
|
||||
CSV_OUTPUT_QUOTE_CHAR: {VAR_TYPE: TYPE_CHARACTER},
|
||||
CSV_OUTPUT_ROW_FILTER: {VAR_TYPE: TYPE_ROWFILTER},
|
||||
|
||||
@@ -65,6 +65,8 @@ CSV_OUTPUT_HEADER_DROP_FILTER = 'cohd'
|
||||
CSV_OUTPUT_HEADER_FILTER = 'cohf'
|
||||
# Force output column headers
|
||||
CSV_OUTPUT_HEADER_FORCE = 'cofh'
|
||||
# Order output column headers
|
||||
CSV_OUTPUT_HEADER_ORDER = 'coho'
|
||||
# No escape character in CSV output file
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR = 'cone'
|
||||
# Quote character in CSV output file
|
||||
@@ -80,7 +82,7 @@ CSV_OUTPUT_ROW_FILTER_MODE = 'corm'
|
||||
# Limit number of output rows
|
||||
CSV_OUTPUT_ROW_LIMIT = 'corl'
|
||||
# Add timestamp column to CSV output file
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN = 'csv_output_timestamp_column'
|
||||
CSV_OUTPUT_TIMESTAMP_COLUMN = 'cotc'
|
||||
# Output sort headers
|
||||
CSV_OUTPUT_SORT_HEADERS = 'cosh'
|
||||
# CSV todrive options
|
||||
@@ -235,6 +237,7 @@ Globals = {
|
||||
CSV_OUTPUT_HEADER_DROP_FILTER: [],
|
||||
CSV_OUTPUT_HEADER_FILTER: [],
|
||||
CSV_OUTPUT_HEADER_FORCE: [],
|
||||
CSV_OUTPUT_HEADER_ORDER: [],
|
||||
CSV_OUTPUT_NO_ESCAPE_CHAR: None,
|
||||
CSV_OUTPUT_QUOTE_CHAR: None,
|
||||
CSV_OUTPUT_ROW_DROP_FILTER: [],
|
||||
|
||||
@@ -1,441 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
usage()
|
||||
{
|
||||
cat << EOF
|
||||
GAM installation script.
|
||||
|
||||
OPTIONS:
|
||||
-h show help.
|
||||
-d Directory where gam folder will be installed. Default is \$HOME/bin/
|
||||
-a Architecture to install (i386, x86_64, x86_64_legacy, arm, arm64). Default is to detect your arch with "uname -m".
|
||||
-o OS we are running (linux, macos). Default is to detect your OS with "uname -s".
|
||||
-b OS version. Default is to detect on MacOS and Linux.
|
||||
-l Just upgrade GAM to latest version. Skips project creation and auth.
|
||||
-p Profile update (true, false). Should script add gam command to environment. Default is true.
|
||||
-u Admin user email address to use with GAM. Default is to prompt.
|
||||
-r Regular user email address. Used to test service account access to user data. Default is to prompt.
|
||||
-v Version to install (latest, prerelease, draft, 3.8, etc). Default is latest.
|
||||
-s Strip gam component from extracted files, files will be downloaded directly to $target_dir
|
||||
EOF
|
||||
}
|
||||
|
||||
target_dir="$HOME/bin"
|
||||
target_gam="gam7/gam"
|
||||
gamarch=$(uname -m)
|
||||
gamos=$(uname -s)
|
||||
osversion=""
|
||||
update_profile=true
|
||||
upgrade_only=false
|
||||
gamversion="latest"
|
||||
adminuser=""
|
||||
regularuser=""
|
||||
strip_gam="--strip-components 0"
|
||||
|
||||
while getopts "hd:a:o:b:lp:u:r:v:s" OPTION
|
||||
do
|
||||
case $OPTION in
|
||||
h) usage; exit;;
|
||||
d) target_dir="$OPTARG";;
|
||||
a) gamarch="$OPTARG";;
|
||||
o) gamos="$OPTARG";;
|
||||
b) osversion="$OPTARG";;
|
||||
l) upgrade_only=true;;
|
||||
p) update_profile="$OPTARG";;
|
||||
u) adminuser="$OPTARG";;
|
||||
r) regularuser="$OPTARG";;
|
||||
v) gamversion="$OPTARG";;
|
||||
s) strip_gam="--strip-components 1"; target_gam="gam";;
|
||||
?) usage; exit;;
|
||||
esac
|
||||
done
|
||||
|
||||
# remove possible / from end of target_dir
|
||||
target_dir=${target_dir%/}
|
||||
|
||||
update_profile() {
|
||||
[ "$2" -eq 1 ] || [ -f "$1" ] || return 1
|
||||
|
||||
grep -F "$alias_line" "$1" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo_yellow "Adding gam alias to profile file $1."
|
||||
echo -e "\n$alias_line" >> "$1"
|
||||
else
|
||||
echo_yellow "gam alias already exists in profile file $1. Skipping add."
|
||||
fi
|
||||
}
|
||||
|
||||
echo_red()
|
||||
{
|
||||
echo -e "\x1B[1;31m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_green()
|
||||
{
|
||||
echo -e "\x1B[1;32m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
echo_yellow()
|
||||
{
|
||||
echo -e "\x1B[1;33m$1"
|
||||
echo -e '\x1B[0m'
|
||||
}
|
||||
|
||||
version_gt()
|
||||
{
|
||||
# MacOS < 10.13 doesn't support sort -V
|
||||
echo "" | sort -V > /dev/null 2>&1
|
||||
vsort_failed=$?
|
||||
if [ "${1}" = "${2}" ]; then
|
||||
true
|
||||
elif (( $vsort_failed != 0 )); then
|
||||
false
|
||||
else
|
||||
test "$(printf '%s\n' "$@" | sort -V | head -n 1)" != "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [ "$gamversion" == "latest" ]; then
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases/latest"
|
||||
elif [ "$gamversion" == "prerelease" -o "$gamversion" == "draft" ]; then
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases"
|
||||
else
|
||||
release_url="https://api.github.com/repos/GAM-team/GAM/releases/tags/v$gamversion"
|
||||
fi
|
||||
|
||||
if [ -z ${GHCLIENT+x} ]; then
|
||||
check_type="unauthenticated"
|
||||
curl_opts=( )
|
||||
else
|
||||
check_type="authenticated"
|
||||
curl_opts=( "$GHCLIENT" )
|
||||
fi
|
||||
echo_yellow "Checking GitHub URL $release_url for $gamversion GAM release ($check_type)..."
|
||||
release_json=$(curl \
|
||||
--silent \
|
||||
"${curl_opts[@]}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
"$release_url" \
|
||||
2>&1 /dev/null)
|
||||
|
||||
echo_yellow "Getting file and download URL..."
|
||||
# Python is sadly the nearest to universal way to safely handle JSON with Bash
|
||||
# At least this code should be compatible with just about any Python version ever
|
||||
# unlike GAM itself. If some users don't have Python we can try grep / sed / etc
|
||||
# but that gets really ugly
|
||||
pycode="import json
|
||||
import sys
|
||||
|
||||
attrib = sys.argv[1]
|
||||
gamversion = sys.argv[2]
|
||||
|
||||
release = json.load(sys.stdin)
|
||||
if type(release) is list:
|
||||
for a_release in release:
|
||||
if a_release['prerelease'] and gamversion != 'prerelease':
|
||||
continue
|
||||
elif a_release['draft'] and gamversion != 'draft':
|
||||
continue
|
||||
release = a_release
|
||||
break
|
||||
try:
|
||||
for asset in release['assets']:
|
||||
print(asset[attrib])
|
||||
#else:
|
||||
# print('ERROR: Attribute: {0} for version {1} not found'.format(attrib, gamversion))
|
||||
except KeyError:
|
||||
print('ERROR: assets value not found in JSON value of:\n\n%s' % release)"
|
||||
|
||||
pycmd="python3"
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="python"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="/usr/bin/python3"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
pycmd="python2"
|
||||
fi
|
||||
$pycmd -V >/dev/null 2>&1
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: No version of python installed."
|
||||
exit
|
||||
fi
|
||||
download_urls=$(echo "$release_json" | $pycmd -c "$pycode" browser_download_url "$gamversion")
|
||||
if [[ ${download_urls:0:5} = "ERROR" ]]; then
|
||||
echo_red "${download_urls}"
|
||||
exit
|
||||
fi
|
||||
|
||||
case $gamos in
|
||||
[lL]inux)
|
||||
gamos="linux"
|
||||
download_urls=$(echo -e "$download_urls" | grep "\-linux-")
|
||||
if [ "$osversion" == "" ]; then
|
||||
this_glibc_ver=$(ldd --version | awk '/ldd/{print $NF}')
|
||||
else
|
||||
this_glibc_ver=$osversion
|
||||
fi
|
||||
echo "This Linux distribution uses glibc $this_glibc_ver"
|
||||
case $gamarch in
|
||||
x86_64)
|
||||
download_urls=$(echo -e "$download_urls" | grep "\-x86_64-")
|
||||
gam_x86_64_glibc_vers=$(echo -e "$download_urls" | \
|
||||
grep --only-matching 'glibc[0-9\.]*\.tar\.xz$' \
|
||||
| cut -c 6-9 )
|
||||
useglibc="legacy"
|
||||
for gam_glibc_ver in $gam_x86_64_glibc_vers; do
|
||||
if version_gt $this_glibc_ver $gam_glibc_ver; then
|
||||
useglibc="glibc$gam_glibc_ver"
|
||||
echo_green "Using GAM compiled against $useglibc"
|
||||
break
|
||||
fi
|
||||
done
|
||||
download_url=$(echo -e "$download_urls" | grep "$useglibc")
|
||||
;;
|
||||
arm|arm64|aarch64)
|
||||
download_urls=$(echo -e "$download_urls" | grep "\-aarch64-")
|
||||
gam_arm64_glibc_vers=$(echo -e "$download_urls" | \
|
||||
grep --only-matching 'glibc[0-9\.]*\.tar\.xz$' \
|
||||
| cut -c 6-9 )
|
||||
useglibc="legacy"
|
||||
for gam_glibc_ver in $gam_arm64_glibc_vers; do
|
||||
if version_gt $this_glibc_ver $gam_glibc_ver; then
|
||||
useglibc="glibc$gam_glibc_ver"
|
||||
echo_green "Using GAM compiled against $useglibc"
|
||||
break
|
||||
fi
|
||||
done
|
||||
download_url=$(echo -e "$download_urls" | grep "$useglibc")
|
||||
;;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports x86_64 and arm64 Linux. Looks like you're running on $gamarch. Exiting."
|
||||
exit
|
||||
esac
|
||||
;;
|
||||
[Mm]ac[Oo][sS]|[Dd]arwin)
|
||||
gamos="macos"
|
||||
fullversion=$(sw_vers -productVersion)
|
||||
# override osversion only if it wasn't set by cli arguments
|
||||
osversion=${osversion:-${fullversion:0:2}}
|
||||
download_urls=$(echo -e "$download_urls" | grep "\-macos-")
|
||||
case $gamarch in
|
||||
x86_64)
|
||||
download_url=$(echo -e "$download_urls" | grep "\-x86_64")
|
||||
minimum_version=13
|
||||
;;
|
||||
arm|arm64|aarch64)
|
||||
download_url=$(echo -e "$download_urls" | grep "\-aarch64")
|
||||
minimum_version=14
|
||||
;;
|
||||
*)
|
||||
echo_red "ERROR: this installer currently only supports x86_64 and arm64 MacOS. Looks like you're running on ${gamarch}. Exiting."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
if [[ "$osversion" -ge "$minimum_version" ]]; then
|
||||
echo_green "You are running MacOS ${fullversion}, good. Using GAM with ${download_url}."
|
||||
else
|
||||
echo_red "Sorry, you are running MacOS ${fullversion} but GAM on ${gamarch} requires MacOS ${minimum_version}. Exiting."
|
||||
exit
|
||||
fi
|
||||
;;
|
||||
MINGW64_NT*)
|
||||
gamos="windows"
|
||||
echo "You are running Windows"
|
||||
download_url=$(echo -e "$download_urls" | grep "\-windows-" | grep ".zip")
|
||||
;;
|
||||
*)
|
||||
echo_red "Sorry, this installer currently only supports Linux and MacOS. Looks like you're running on ${gamos}. Exiting."
|
||||
exit
|
||||
;;
|
||||
esac
|
||||
|
||||
# Temp dir for archive
|
||||
temp_archive_dir=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir')
|
||||
|
||||
# Clean up after ourselves even if we are killed with CTRL-C
|
||||
trap "rm -rf $temp_archive_dir" EXIT
|
||||
|
||||
# hack to grab the end of the URL which should be the filename.
|
||||
name=$(echo -e "$download_url" | rev | cut -f1 -d "/" | rev)
|
||||
|
||||
echo_yellow "Downloading ${download_url} to $temp_archive_dir ($check_type)..."
|
||||
# Save archive to temp w/o losing our path
|
||||
(cd "$temp_archive_dir" && curl -O -L -s "${curl_opts[@]}" "$download_url")
|
||||
|
||||
mkdir -p "$target_dir"
|
||||
|
||||
echo_yellow "Extracting archive to $target_dir"
|
||||
if [[ "$name" =~ tar.xz|tar.gz|tar ]]; then
|
||||
tar $strip_gam -xf "$temp_archive_dir"/"$name" -C "$target_dir"
|
||||
elif [[ "$name" == *.zip ]]; then
|
||||
unzip -o "${temp_archive_dir}/${name}" -d "${target_dir}"
|
||||
else
|
||||
echo "I don't know what to do with files like ${name}. Giving up."
|
||||
exit 1
|
||||
fi
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: extracting the GAM archive with tar failed with error $rc. Exiting."
|
||||
exit
|
||||
else
|
||||
echo_green "Finished extracting GAM archive."
|
||||
fi
|
||||
|
||||
# Update profile to add gam command
|
||||
if [ "$update_profile" = true ]; then
|
||||
alias_line="alias gam=\"${target_dir// /\\ }/$target_gam\""
|
||||
if [ "$gamos" == "linux" ]; then
|
||||
update_profile "$HOME/.bash_aliases" 0 || update_profile "$HOME/.bash_profile" 0 || update_profile "$HOME/.bashrc" 0
|
||||
update_profile "$HOME/.zshrc" 0
|
||||
elif [ "$gamos" == "macos" ]; then
|
||||
update_profile "$HOME/.bash_aliases" 0 || update_profile "$HOME/.bash_profile" 0 || update_profile "$HOME/.bashrc" 0 || update_profile "$HOME/.profile" 1
|
||||
update_profile "$HOME/.zshrc" 1
|
||||
fi
|
||||
else
|
||||
echo_yellow "skipping profile update."
|
||||
fi
|
||||
|
||||
if [ "$upgrade_only" = true ]; then
|
||||
echo_green "Here's information about your GAM upgrade:"
|
||||
"$target_dir/$target_gam" version extended
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: Failed running GAM for the first time with return code $rc. Please report this error to GAM mailing list. Exiting."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo_green "GAM upgrade complete!"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Set config command
|
||||
#config_cmd="config no_browser false"
|
||||
|
||||
while true; do
|
||||
read -p "Can you run a full browser on this machine? (usually Y for MacOS, N for Linux if you SSH into this machine) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
break
|
||||
;;
|
||||
[Nn]*)
|
||||
# config_cmd="config no_browser true"
|
||||
touch "$target_dir/gam/nobrowser.txt" > /dev/null 2>&1
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
echo
|
||||
|
||||
project_created=false
|
||||
while true; do
|
||||
read -p "GAM is now installed. Are you ready to set up a Google API project for GAM? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$adminuser" == "" ]; then
|
||||
read -p "Please enter your Google Workspace admin email address: " adminuser
|
||||
fi
|
||||
# "$target_dir/$target_gam" $config_cmd create project $adminuser
|
||||
"$target_dir/$target_gam" create project $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Project creation complete."
|
||||
project_created=true
|
||||
break
|
||||
else
|
||||
echo_red "Project creation failed. Trying again. Say N to skip project creation."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can create an API project later by running:\n\ngam create project\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
admin_authorized=false
|
||||
while $project_created; do
|
||||
read -p "Are you ready to authorize GAM to perform Google Workspace management operations as your admin account? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
# "$target_dir/$target_gam" $config_cmd oauth create $adminuser
|
||||
"$target_dir/$target_gam" oauth create $adminuser
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Admin authorization complete."
|
||||
admin_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Admin authorization failed. Trying again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize an admin later by running:\n\ngam oauth create\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
service_account_authorized=false
|
||||
while $admin_authorized; do
|
||||
read -p "Are you ready to authorize GAM to manage Google Workspace user data and settings? (yes or no) " yn
|
||||
case $yn in
|
||||
[Yy]*)
|
||||
if [ "$regularuser" == "" ]; then
|
||||
read -p "Please enter the email address of a regular Google Workspace user: " regularuser
|
||||
fi
|
||||
echo_yellow "Great! Checking service account scopes.This will fail the first time. Follow the steps to authorize and retry. It can take a few minutes for scopes to PASS after they've been authorized in the admin console."
|
||||
# "$target_dir/$target_gam" $config_cmd user $regularuser check serviceaccount
|
||||
"$target_dir/$target_gam" user $regularuser check serviceaccount
|
||||
rc=$?
|
||||
if (( $rc == 0 )); then
|
||||
echo_green "Service account authorization complete."
|
||||
service_account_authorized=true
|
||||
break
|
||||
else
|
||||
echo_red "Service account authorization failed. Confirm you entered the scopes correctly in the admin console. It can take a few minutes for scopes to PASS after they are entered in the admin console so if you're sure you entered them correctly, go grab a coffee and then hit Y to try again. Say N to skip admin authorization."
|
||||
fi
|
||||
;;
|
||||
[Nn]*)
|
||||
echo -e "\nYou can authorize a service account later by running:\n\ngam user $adminuser check serviceaccount\n"
|
||||
break
|
||||
;;
|
||||
*)
|
||||
echo_red "Please answer yes or no."
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo_green "Here's information about your new GAM installation:"
|
||||
#"$target_dir/$target_gam" $config_cmd save version extended
|
||||
"$target_dir/$target_gam" version extended
|
||||
rc=$?
|
||||
if (( $rc != 0 )); then
|
||||
echo_red "ERROR: Failed running GAM for the first time with $rc. Please report this error to GAM mailing list. Exiting."
|
||||
exit
|
||||
fi
|
||||
|
||||
echo_green "GAM installation and setup complete!"
|
||||
if [ "$update_profile" = true ]; then
|
||||
echo_green "Please restart your terminal shell or to get started right away run:\n\n$alias_line"
|
||||
fi
|
||||
@@ -1,58 +1,23 @@
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
from lxml import etree
|
||||
import sys
|
||||
|
||||
source_dir = sys.argv[1]
|
||||
template_file = sys.argv[2]
|
||||
target_file = sys.argv[3]
|
||||
# Hacky solution to create a Guid for all files
|
||||
# so Wix is happy and Guid is stable every time.
|
||||
# uuid5 is used for the Guid and the input is the
|
||||
# source filename so the Guid will be the same
|
||||
# every time as long as the source file name is
|
||||
# the same.
|
||||
|
||||
existing_components = {
|
||||
'gam.exe': ''' <Component Id="gam_exe" Guid="d046ea24-c9f8-40ca-84db-70b0119933ff">
|
||||
<File Name="gam.exe" KeyPath="yes" />
|
||||
<Environment Id="PATH" Name="PATH" Value="[INSTALLFOLDER]" Permanent="yes" Part="last" Action="set" System="yes" />
|
||||
</Component>
|
||||
''',
|
||||
'LICENSE': ''' <Component Id="license" Guid="c76864c5-d005-44d5-bb7c-a27e5923792d">
|
||||
<File Name="LICENSE" KeyPath="yes" />
|
||||
</Component>
|
||||
''',
|
||||
'gam-setup.bat': ''' <Component Id="gam_setup_bat" Guid="5e6bbacb-d86f-4d80-a10b-89b81ee63fcb">
|
||||
<File Name="gam-setup.bat" KeyPath="yes" />
|
||||
</Component>
|
||||
''',
|
||||
'GamCommands.txt': ''' <Component Id="GamCommands_txt" Guid="a2dca862-b222-469e-a637-95ea2a1c53e7">
|
||||
<File Name="GamCommands.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
''',
|
||||
'GamUpdate.txt': ''' <Component Id="GamUpdate_txt" Guid="1b7cdd48-0fff-4943-a219-102fcd14c755">
|
||||
<File Name="GamUpdate.txt" KeyPath="yes" />
|
||||
</Component>
|
||||
''',
|
||||
'cacerts.pem': ''' <Component Id="cacerts_pem" Guid="61fe2b2d-1646-4bed-b844-193965e97727">
|
||||
<File Name="cacerts.pem" KeyPath="yes" />
|
||||
</Component>
|
||||
''',
|
||||
}
|
||||
|
||||
component_xml = ''
|
||||
all_files = []
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
for filename in files:
|
||||
relpath = os.path.relpath(root, source_dir)
|
||||
if relpath == '.':
|
||||
all_files.append(filename)
|
||||
else:
|
||||
all_files.append(os.path.join(relpath, filename))
|
||||
all_files.sort()
|
||||
for filename in all_files:
|
||||
component_xml += existing_components.get(filename,
|
||||
f' <Component>\n <File Name="{filename}" KeyPath="yes"/>\n </Component>\n')
|
||||
|
||||
with open(template_file, 'r') as f:
|
||||
template = f.read()
|
||||
|
||||
full_xml = template.replace('REPLACE_ME_WITH_FILE_COMPONENTS', component_xml)
|
||||
|
||||
with open(target_file, 'w') as f:
|
||||
f.write(full_xml)
|
||||
rewrite_file = sys.argv[1]
|
||||
|
||||
with open(rewrite_file, 'rb') as f:
|
||||
input_xml = f.read()
|
||||
root = etree.fromstring(input_xml)
|
||||
for elem in root.getiterator():
|
||||
if 'Guid' in elem.attrib:
|
||||
source = elem.getchildren()[0].attrib['Source']
|
||||
stable_uuid = str(uuid.uuid5(uuid.NAMESPACE_URL, source))
|
||||
elem.attrib['Guid'] = stable_uuid
|
||||
with open(rewrite_file, 'w') as f:
|
||||
f.write(etree.tostring(root).decode())
|
||||
|
||||
Reference in New Issue
Block a user