mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-06 23:31:38 +00:00
Compare commits
51 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
063e0876b9 | ||
|
|
4743d12ea8 | ||
|
|
ad548cbe33 | ||
|
|
bb9d5b3cdc | ||
|
|
ee3bb42d19 | ||
|
|
7e8042a2f8 | ||
|
|
5ed0b9ffd2 | ||
|
|
db2a37f358 | ||
|
|
77ebeddaac | ||
|
|
f00e7ecdbb | ||
|
|
f75d7d78f6 | ||
|
|
75929117aa | ||
|
|
a06348c1c5 | ||
|
|
425286482f | ||
|
|
a162cf870b | ||
|
|
28a1048f57 | ||
|
|
def54f1513 | ||
|
|
4d86d44ec1 | ||
|
|
2c0136834b | ||
|
|
9cca97b12e | ||
|
|
f69d0b1427 | ||
|
|
abd39176d8 | ||
|
|
782eee45cc | ||
|
|
6450fb0c3d | ||
|
|
523bd45fa0 | ||
|
|
9e6122769a | ||
|
|
9dc427c47b | ||
|
|
023c81de93 | ||
|
|
a570e1f300 | ||
|
|
86881b58f4 | ||
|
|
1b8793df9b | ||
|
|
dfdc03ba28 | ||
|
|
0cafde359e | ||
|
|
9b2ca0dedd | ||
|
|
741a2e3b79 | ||
|
|
760963889d | ||
|
|
b0b572a5b2 | ||
|
|
ae9e329169 | ||
|
|
d997900955 | ||
|
|
699d95d7d7 | ||
|
|
0274c27dd6 | ||
|
|
cb41cd4abf | ||
|
|
a971ea37ae | ||
|
|
c2192674ff | ||
|
|
1b7e736caf | ||
|
|
2a49e1ebe2 | ||
|
|
4075bef468 | ||
|
|
00cccbe920 | ||
|
|
0f50ce18b3 | ||
|
|
1475e7a1d2 | ||
|
|
573a0dc6f1 |
92
.github/workflows/build.yml
vendored
92
.github/workflows/build.yml
vendored
@@ -165,7 +165,7 @@ jobs:
|
||||
with:
|
||||
path: |
|
||||
cache.tar.xz
|
||||
key: gam-${{ matrix.jid }}-20260416
|
||||
key: gam-${{ matrix.jid }}-20260508
|
||||
|
||||
- name: Untar Cache archive
|
||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit == 'true'
|
||||
@@ -199,7 +199,10 @@ jobs:
|
||||
*)
|
||||
echo "arch=${RUNNER_ARCH}" >> $GITHUB_ENV
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
# calculate with python because date on MacOS is just stupid
|
||||
DEPS_CUTOFF=$(python3 -c "from datetime import datetime, timedelta, timezone; print((datetime.now(timezone.utc) - timedelta(days=14)).strftime('%Y-%m-%dT00:00:00Z'))")
|
||||
echo "DEPS_CUTOFF=${DEPS_CUTOFF}" >> $GITHUB_ENV
|
||||
echo "JID=${JID}" >> $GITHUB_ENV
|
||||
echo "freethreaded=${freethreaded}" >> $GITHUB_ENV
|
||||
if "$freethreaded"; then
|
||||
@@ -506,19 +509,19 @@ jobs:
|
||||
"${PYTHON}" -VV
|
||||
"${PYTHON}" -c "import ssl; print(f'Using {ssl.OPENSSL_VERSION}')"
|
||||
|
||||
- name: Create and use Python venv
|
||||
- name: Create and use Python .venv
|
||||
run: |
|
||||
cd "$GITHUB_WORKSPACE"
|
||||
curl -o get-pip.py https://bootstrap.pypa.io/get-pip.py
|
||||
"$PYTHON" get-pip.py
|
||||
"$PYTHON" -m venv venv
|
||||
"$PYTHON" -m venv .venv
|
||||
if [[ "$RUNNER_OS" == "Windows" ]]; then
|
||||
# pyscard seems to build outside venv but not in it.
|
||||
# build it so it's cached.
|
||||
"$PYTHON" -m pip install --upgrade --force-reinstall pyscard
|
||||
export PYTHON="${GITHUB_WORKSPACE}/venv/scripts/python.exe"
|
||||
"$PYTHON" -m pip install --upgrade pyscard
|
||||
export PYTHON="${GITHUB_WORKSPACE}/.venv/scripts/python.exe"
|
||||
else
|
||||
export PYTHON="${GITHUB_WORKSPACE}/venv/bin/python3"
|
||||
export PYTHON="${GITHUB_WORKSPACE}/.venv/bin/python3"
|
||||
fi
|
||||
echo "PYTHON=${PYTHON}" >> $GITHUB_ENV
|
||||
if [[ "$ACTIONS_GOAL" == "test" ]]; then
|
||||
@@ -526,31 +529,22 @@ jobs:
|
||||
echo "gam=${gam}" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Upgrade pip, wheel, etc
|
||||
run: |
|
||||
curl $curl_retry -O https://bootstrap.pypa.io/get-pip.py
|
||||
"$PYTHON" get-pip.py
|
||||
"$PYTHON" -m pip install --upgrade pip
|
||||
"$PYTHON" -m pip install --upgrade wheel
|
||||
"$PYTHON" -m pip install --upgrade setuptools
|
||||
"$PYTHON" -m pip install --upgrade importlib-metadata
|
||||
"$PYTHON" -m pip install --upgrade setuptools-scm
|
||||
"$PYTHON" -m pip install --upgrade packaging
|
||||
"$PYTHON" -m pip list
|
||||
#- name: Upgrade pip, wheel, etc
|
||||
# run: |
|
||||
# curl $curl_retry -O https://bootstrap.pypa.io/get-pip.py
|
||||
# "$PYTHON" get-pip.py
|
||||
# "$PYTHON" -m pip install --upgrade pip
|
||||
# "$PYTHON" -m pip install --upgrade wheel
|
||||
# "$PYTHON" -m pip install --upgrade setuptools
|
||||
# "$PYTHON" -m pip install --upgrade importlib-metadata
|
||||
# "$PYTHON" -m pip install --upgrade setuptools-scm
|
||||
# "$PYTHON" -m pip install --upgrade packaging
|
||||
# "$PYTHON" -m pip list
|
||||
|
||||
- name: Install pip requirements
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
echo "before anything..."
|
||||
"$PYTHON" -m pip list
|
||||
echo "--info--"
|
||||
"$PYTHON" -m pip cache info
|
||||
echo "--list--"
|
||||
"$PYTHON" -m pip cache list
|
||||
echo "--pip debug verbose--"
|
||||
"$PYTHON" -m pip debug --verbose
|
||||
echo "--------"
|
||||
if ([ "$RUNNER_OS" == "Windows" ] && [ "$RUNNER_ARCH" == "ARM64" ]); then
|
||||
# custom cryptography wheel for win arm64 since the project doesn't provide one:
|
||||
# https://github.com/pyca/cryptography/issues/14293
|
||||
@@ -558,24 +552,12 @@ jobs:
|
||||
"$PYTHON" -m pip install cryptography-*.whl
|
||||
fi
|
||||
"$PYTHON" -m pip install -vvv --upgrade ..[yubikey]
|
||||
echo "after everything..."
|
||||
"$PYTHON" -m pip list
|
||||
|
||||
- name: Install PyInstaller
|
||||
if: matrix.goal == 'build'
|
||||
run: |
|
||||
#git clone https://github.com/pyinstaller/pyinstaller.git
|
||||
#cd pyinstaller
|
||||
#export latest_release=$(git tag --list | grep -v dev | grep -v rc | sort -Vr | head -n1)
|
||||
#git checkout "${latest_release}"
|
||||
# git checkout "v6.9.0"
|
||||
# remove pre-compiled bootloaders so we fail if bootloader compile fails
|
||||
#rm -rvf PyInstaller/bootloader/*-*/*
|
||||
#cd bootloader
|
||||
#"${PYTHON}" ./waf all
|
||||
#cd ..
|
||||
#echo "---- Installing PyInstaller ----"
|
||||
#"${PYTHON}" -m pip install .
|
||||
# Install latest version of PyInstaller *that's 2 weeks old*
|
||||
# Calculate the exact timestamp for 14 days ago
|
||||
"$PYTHON" -m pip install --upgrade pyinstaller
|
||||
|
||||
- name: Build GAM with PyInstaller
|
||||
@@ -642,11 +624,9 @@ jobs:
|
||||
sudo apt-get -qq --yes update
|
||||
# arm64 needs to build a wheel and needs scons to build
|
||||
sudo apt-get -qq --yes install scons
|
||||
"${PYTHON}" -m pip install --upgrade patchelf-wrapper
|
||||
"${PYTHON}" -m pip install --upgrade typing_extensions
|
||||
# "${PYTHON}" -m pip install --upgrade staticx
|
||||
# install latest github src for staticx
|
||||
"${PYTHON}" -m pip install --upgrade "git+https://github.com/JonathonReinhart/staticx"
|
||||
"$PYTHON" -m pip install --upgrade patchelf-wrapper
|
||||
"$PYTHON" -m pip install --upgrade typing_extensions
|
||||
"$PYTHON" -m pip install --upgrade staticx
|
||||
|
||||
- name: Make StaticX GAM build
|
||||
if: matrix.staticx == 'yes'
|
||||
@@ -733,16 +713,16 @@ jobs:
|
||||
write-host "sleeping during login..."
|
||||
Start-Sleep 10
|
||||
|
||||
- name: Archive artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
||||
if: runner.os == 'Windows'
|
||||
with:
|
||||
archive: true
|
||||
name: images-${{ matrix.os }}
|
||||
if-no-files-found: ignore
|
||||
path: |
|
||||
${{ github.workspace }}/*.png
|
||||
${{ github.workspace }}/*.log
|
||||
# - name: Archive artifacts
|
||||
# uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
||||
# if: runner.os == 'Windows'
|
||||
# with:
|
||||
# archive: true
|
||||
# name: images-${{ matrix.os }}
|
||||
# if-no-files-found: ignore
|
||||
# path: |
|
||||
# ${{ github.workspace }}/*.png
|
||||
# ${{ github.workspace }}/*.log
|
||||
|
||||
- name: Sign gam.exe
|
||||
if: runner.os == 'Windows'
|
||||
|
||||
144
.github/workflows/upgrade_deps.yml
vendored
Normal file
144
.github/workflows/upgrade_deps.yml
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
name: Daily Dependency Pinning (2-Week Buffer)
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs every day at midnight UTC
|
||||
- cron: '0 0 * * *'
|
||||
workflow_dispatch: # Allows you to trigger it manually from the UI
|
||||
|
||||
jobs:
|
||||
pin-deps:
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
|
||||
with:
|
||||
python-version: '3.14'
|
||||
|
||||
- name: Calculate and pin two-week old stable versions
|
||||
shell: python
|
||||
run: |
|
||||
import json
|
||||
import urllib.request
|
||||
import re
|
||||
import tomllib
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
|
||||
toml_path = Path("pyproject.toml")
|
||||
if not toml_path.exists():
|
||||
print("pyproject.toml not found!")
|
||||
exit(1)
|
||||
|
||||
content = toml_path.read_text(encoding="utf-8")
|
||||
parsed_toml = tomllib.loads(content)
|
||||
|
||||
target_deps = set()
|
||||
|
||||
# Standard [project.dependencies]
|
||||
target_deps.update(parsed_toml.get("project", {}).get("dependencies", []))
|
||||
|
||||
# Optional [project.optional-dependencies]
|
||||
for deps in parsed_toml.get("project", {}).get("optional-dependencies", {}).values():
|
||||
target_deps.update(deps)
|
||||
|
||||
# Dev [dependency-groups] (uv / PEP 735 standard)
|
||||
for deps in parsed_toml.get("dependency-groups", {}).values():
|
||||
if isinstance(deps, list):
|
||||
for d in deps:
|
||||
if isinstance(d, str):
|
||||
target_deps.add(d)
|
||||
|
||||
if not target_deps:
|
||||
print("No dependencies found to process.")
|
||||
exit(0)
|
||||
|
||||
updates = {}
|
||||
two_weeks_ago = datetime.now(timezone.utc) - timedelta(days=14)
|
||||
print(f"Evaluating dependencies against cutoff date: {two_weeks_ago.isoformat()}")
|
||||
|
||||
for dep in target_deps:
|
||||
# Isolate base package name (e.g., "yubikey-manager>=5.6.1" -> "yubikey-manager")
|
||||
pkg_name = re.split(r'[<>=!~;\s]', dep)[0].strip()
|
||||
|
||||
# Preserve environment markers if they exist
|
||||
marker = ""
|
||||
if ";" in dep:
|
||||
marker = " ; " + dep.split(";", 1)[1].strip()
|
||||
|
||||
print(f"Fetching PyPI data for: {pkg_name}")
|
||||
try:
|
||||
url = f"https://pypi.org/pypi/{pkg_name}/json"
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'GAM-CI-Script'})
|
||||
with urllib.request.urlopen(req) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
|
||||
valid_versions = []
|
||||
for ver, files in data.get("releases", {}).items():
|
||||
if not files:
|
||||
continue
|
||||
|
||||
upload_time_str = files[0].get("upload_time_iso_8601")
|
||||
if not upload_time_str:
|
||||
continue
|
||||
|
||||
if upload_time_str.endswith('Z'):
|
||||
upload_time_str = upload_time_str[:-1] + '+00:00'
|
||||
|
||||
upload_time = datetime.fromisoformat(upload_time_str)
|
||||
|
||||
# Filter: Must be older than 2 weeks and not a pre-release
|
||||
if upload_time <= two_weeks_ago and not any(x in ver.lower() for x in ['a', 'b', 'rc', 'dev']):
|
||||
valid_versions.append((upload_time, ver))
|
||||
|
||||
if valid_versions:
|
||||
# Sort by upload time descending to get the newest valid option
|
||||
valid_versions.sort(key=lambda x: x[0], reverse=True)
|
||||
target_version = valid_versions[0][1]
|
||||
|
||||
pinned_dep = f"{pkg_name}=={target_version}{marker}"
|
||||
if pinned_dep != dep:
|
||||
updates[dep] = pinned_dep
|
||||
print(f" -> Pinning: '{dep}' => '{pinned_dep}'")
|
||||
else:
|
||||
print(f" -> Already pinned correctly to {target_version}")
|
||||
else:
|
||||
print(f" -> No valid historical versions found.")
|
||||
|
||||
except urllib.error.HTTPError as e:
|
||||
print(f" -> Package not found on PyPI or HTTP error: {e}")
|
||||
except Exception as e:
|
||||
print(f" -> Error processing {pkg_name}: {e}")
|
||||
|
||||
# 3. Replace the strings safely in the original file content
|
||||
new_content = content
|
||||
for old_dep, new_dep in updates.items():
|
||||
# Regex targets the exact string inside either single or double quotes
|
||||
# Using a lambda replacement ensures we don't trip over escape sequences in the new string
|
||||
escaped_old = re.escape(old_dep)
|
||||
pattern = r'([\'"])' + escaped_old + r'\1'
|
||||
new_content = re.sub(pattern, lambda m: m.group(1) + new_dep + m.group(1), new_content)
|
||||
|
||||
# Write changes back to pyproject.toml
|
||||
if content != new_content:
|
||||
toml_path.write_text(new_content, encoding="utf-8")
|
||||
print("\npyproject.toml updated successfully.")
|
||||
else:
|
||||
print("\nNo updates required.")
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "chore: upgrade PyPi deps"
|
||||
title: "Upgrade PyPi deps"
|
||||
body: "Automated scan checking PyPI for package versions at least 2 weeks old."
|
||||
branch: sys-deps-upgrade
|
||||
force: false # Standard push, plays nice with rulesets
|
||||
6
dep-overrides.txt
Normal file
6
dep-overrides.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
# overrides uv.lock to force newer dependencies
|
||||
# when old deps are vulnerable. These should be set
|
||||
# to expire after 2 weeks when the fixed version will
|
||||
# be automatically picked up anyway.
|
||||
# Format: package_requirement | MM/DD/YYYY
|
||||
urllib3>=2.7.0 | 05/22/2026
|
||||
@@ -4,26 +4,24 @@ dynamic = [
|
||||
"version",
|
||||
]
|
||||
authors = [
|
||||
{ name="Jay Lee", email="jay0lee@gmail.com" },
|
||||
{ name="Ross Scroggs", email="Ross.Scroggs@gmail.com" },
|
||||
{ name = "Jay Lee", email = "jay0lee@gmail.com" },
|
||||
{ name = "Ross Scroggs", email = "Ross.Scroggs@gmail.com" },
|
||||
]
|
||||
# notice that yubikey-manager remains optional further down since it is less command and adds
|
||||
#significant compile dependencies.
|
||||
dependencies = [
|
||||
"arrow>=1.3.0",
|
||||
"chardet==5.2.0",
|
||||
"cryptography>=46.0.5",
|
||||
"distro; sys_platform=='linux'",
|
||||
"filelock>=3.18.0",
|
||||
"google-api-python-client>=2.167.0",
|
||||
"google-auth-httplib2>=0.2.0",
|
||||
"google-auth-oauthlib>=1.2.2",
|
||||
"google-auth>=2.39.0",
|
||||
"httplib2>=0.31.0",
|
||||
"lxml>=5.4.0",
|
||||
"passlib>=1.7.4",
|
||||
"pathvalidate>=3.2.3",
|
||||
"pysocks>=1.7.1",
|
||||
"arrow==1.4.0",
|
||||
"chardet==7.4.3",
|
||||
"cryptography==47.0.0",
|
||||
"distro==1.9.0 ; sys_platform=='linux'",
|
||||
"filelock==3.29.0",
|
||||
"google-api-python-client==2.195.0",
|
||||
"google-auth-httplib2==0.3.1",
|
||||
"google-auth-oauthlib==1.3.1",
|
||||
"google-auth==2.50.0",
|
||||
"httplib2==0.31.2",
|
||||
"lxml==6.1.0",
|
||||
"passlib==1.7.4",
|
||||
"pathvalidate==3.3.1",
|
||||
"pysocks==1.7.1",
|
||||
]
|
||||
description = "CLI tool to manage Google Workspace"
|
||||
readme = "README.md"
|
||||
@@ -38,11 +36,17 @@ classifiers = [
|
||||
"Programming Language :: Python :: 3.14",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
license = {text = "Apache License (2.0)"}
|
||||
license-files = ["LICEN[CS]E*"]
|
||||
license-files = [
|
||||
"LICEN[CS]E*",
|
||||
]
|
||||
|
||||
[project.license]
|
||||
text = "Apache License (2.0)"
|
||||
|
||||
[project.optional-dependencies]
|
||||
yubikey = ["yubikey-manager>=5.6.1"]
|
||||
yubikey = [
|
||||
"yubikey-manager==5.9.1",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
gam = "gam.__main__:main"
|
||||
@@ -57,7 +61,9 @@ Chat = "https://git.io/gam-chat"
|
||||
path = "src/gam/__init__.py"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/gam"]
|
||||
packages = [
|
||||
"src/gam",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = [
|
||||
|
||||
@@ -916,6 +916,8 @@ Specify a collection of Users by directly specifying them or by specifying items
|
||||
|
||||
<UserTypeEntity> ::=
|
||||
(all users|users_na|users_arch|users_ns|users_susp|users_arch_or_susp|users_na_ns|users_ns_susp)|
|
||||
(all guests|guests_ns|guests_susp|guests_ns_susp)|
|
||||
(all users_and_guests|users_and_guests_ns|users_and_guests_susp|users_and_guests_ns_susp)|
|
||||
(user <UserItem>)|
|
||||
(users <UserList>)|
|
||||
(oauthuser)
|
||||
@@ -1351,9 +1353,12 @@ verify
|
||||
## File Redirection
|
||||
|
||||
If the pattern {{Section}} appears in <FileName>, it will be replaced with the name of the current section.
|
||||
For redirect csv, the optional arguments must appear in the order shown.
|
||||
|
||||
For redirect csv, the optional arguments can be specfified in any order but `todrive <ToDriveAttribute>*`
|
||||
must be last.
|
||||
|
||||
<Redirect> ::=
|
||||
redirect csv <FileName> [multiprocess] [append] [noheader] [charset <Charset>]
|
||||
redirect csv <FileName> [delayopen] multiprocess] [append] [noheader] [charset <Charset>]
|
||||
[columndelimiter <Character>] [quotechar <Character>] [noescapechar [<Boolean>]]
|
||||
[sortheaders <StringList>] [timestampcolumn <String>] [transpose [<Bopolean>]]
|
||||
[todrive <ToDriveAttribute>*] |
|
||||
@@ -1715,6 +1720,7 @@ gam calendar <CalendarEntity> printacl [todrive <ToDriveAttribute>*]
|
||||
|
||||
<EventMatchProperty> ::=
|
||||
(matchfield attendees <EmailAddressEntity>)|
|
||||
(matchfield attendeesorganizer <Boolean> <EmailAddressEntity>)|
|
||||
(matchfield attendeesonlydomainlist <DomainNameList>)|
|
||||
(matchfield attendeesdomainlist <DomainNameList>)|
|
||||
(matchfield attendeesnotdomainlist <DomainNameList>)|
|
||||
@@ -2463,9 +2469,9 @@ gam print chromehistory releases [todrive <ToDriveAttribute>*]
|
||||
pre_provisioned_reenable
|
||||
|
||||
gam update cros <CrOSEntity> action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
[actionbatchsize <Integer>] [maxtodeprov <Integer>]
|
||||
gam <CrOSTypeEntity> update action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
[actionbatchsize <Integer>]
|
||||
[actionbatchsize <Integer>] [maxtodeprov <Integer>]
|
||||
|
||||
<CrOSCommand>
|
||||
reboot|
|
||||
@@ -5494,6 +5500,7 @@ gam print vaultcounts [todrive <ToDriveAttributes>*]
|
||||
[excludedrafts <Boolean>]
|
||||
[<JSONData>]
|
||||
[wait <Integer>]
|
||||
[include_suspended_zeros [<Boolean>]]
|
||||
gam print vaultcounts [todrive <ToDriveAttributes>*]
|
||||
matter <MatterItem> operation <String> [wait <Integer>]
|
||||
|
||||
@@ -7027,7 +7034,7 @@ gam <UserTypeEntity> untrash drivefile <DriveFileEntity> [shortcutandtarget [<Bo
|
||||
|
||||
gam <UserTypeEntity> info drivefile <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[includepermissionsforview published]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
@@ -7508,7 +7515,7 @@ gam <UserTypeEntity> collect orphans
|
||||
|
||||
gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[includepermissionsforview published]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
@@ -7522,12 +7529,12 @@ gam <UserTypeEntity> show filepath <DriveFileEntity>
|
||||
[returnpathonly]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[stripcrsfromname]
|
||||
[fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[followshortcuts [<Boolean>]]
|
||||
gam <UserTypeEntity> print filepath <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[stripcrsfromname] [oneitemperrow]
|
||||
[fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[followshortcuts [<Boolean>]]
|
||||
|
||||
gam <UserTypeEntity> print filecounts [todrive <ToDriveAttribute>*]
|
||||
@@ -7630,7 +7637,7 @@ gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
|
||||
[countsonly [summary none|only|plus] [summaryuser <String>]
|
||||
[showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||
[countsrowfilter]
|
||||
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
[filepath|fullpath [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
[(showlabels details|ids)|(includelabels <ClassificationLabelIDList>)]
|
||||
@@ -7668,7 +7675,7 @@ gam <UserTypeEntity> print driveactivity [todrive <ToDriveAttribute>*]
|
||||
(drivefilename <DriveFileName>) | (drivefoldername <DriveFolderName>) | (query <QueryDriveFile>)]
|
||||
[([start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>])|(range <Date>|<Time> <Date>)|<Time>|
|
||||
yesterday|today|thismonth|(previousmonths <Integer>)]
|
||||
[action|actions [not] <DriveActivityActionList>]
|
||||
[action|actions [not] <DriveActivityActionList>] [maxactivities <Number>]
|
||||
[consolidationstrategy legacy|none]
|
||||
[idmapfile <CSVFileInput> endcsv]
|
||||
[stripcrsfromname] [formatjson [quotechar <Character>]]
|
||||
|
||||
@@ -1,3 +1,60 @@
|
||||
7.43.05
|
||||
|
||||
Added option `matchfield attendeesorganizer <Boolean> <EmailAddressEntity>` to `<EventMatchProperty>`
|
||||
that is used in commands that process events. The match is true if all of the addresses in `<EmailAddressEntity>`
|
||||
are present as attendees in the event and are an organizer or not based on `<Boolean>`.
|
||||
|
||||
Added option `max_to_deprov <Integer>` to `gam update cros <CrOSEntity> action <CrOSAction>`
|
||||
that is used when `<CrOSAction>` is any of the following:
|
||||
```
|
||||
deprovision_different_model_replace|
|
||||
deprovision_different_model_replacement|
|
||||
deprovision_retiring_device|
|
||||
deprovision_same_model_replace|
|
||||
deprovision_same_model_replacement|
|
||||
deprovision_upgrade_transfer
|
||||
```
|
||||
`max_to_deprov <Integer>` - No deprovisions are processed if the number of devices in `<CrOSEntity>` exceeds `<Integer>`;
|
||||
the default value is one; set `<Integer>` to 0 for no limit.
|
||||
|
||||
7.43.04
|
||||
|
||||
Added option `include_suspended_zeros [<Boolean>]` to `gam print vaultcounts` that causes
|
||||
GAM to generate zero count lines for suspended users with zero items as well as non-suspended users.
|
||||
|
||||
7.43.03
|
||||
|
||||
Added option `parentpathonly [<Boolean>]` to the following commands that causes GAM
|
||||
to display only the parent folder names when displaying the path to a file.
|
||||
```
|
||||
gam <UserTypeEntity> info drivefile ... filepath|fullpath
|
||||
gam <UserTypeEntity> show fileinfo ... filepath|fullpath
|
||||
gam <UserTypeEntity> print|show filepath
|
||||
gam <UserTypeEntity> print filelist ... filepath|fullpath
|
||||
```
|
||||
|
||||
7.43.02
|
||||
|
||||
Added option `maxactivities <Integer>` to `gam <UserTypeEntity> print driveactivity` to limit
|
||||
the number of activities displayed; the default is 0, no limit.
|
||||
|
||||
7.43.01
|
||||
|
||||
Updated `gam info user` and `gam print users` to display guest user attributes: `isGuestUser, guestAccountInfo`
|
||||
|
||||
Expanded `<UserTypeEntity>` to allow specification of guest users.
|
||||
* See [Collections of Users](Collections-of-Users)
|
||||
|
||||
7.42.00
|
||||
|
||||
In versions prior to 7.42.00, when `redirect csv <FileName>` was used, GAM did not open and write `<FileName>`
|
||||
until all processing was complete; if `<FileName>` was not accessible, an error was generated
|
||||
and no results were saved. Now, `<FileName>` is opened initially to verify accessiblity
|
||||
and then written when processing is complete.
|
||||
|
||||
In the unlikely event that this causes issues, you can do `redirect csv <FileName> delayopen`
|
||||
to get the previous behavior.
|
||||
|
||||
7.41.03
|
||||
|
||||
Fixed bug in the following:
|
||||
|
||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
||||
"""
|
||||
|
||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||
__version__ = '7.41.03'
|
||||
__version__ = '7.43.05'
|
||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
@@ -3953,7 +3953,7 @@ def SetGlobalVariables():
|
||||
'\n'))
|
||||
status['errors'] = True
|
||||
|
||||
def _setCSVFile(fileName, mode, encoding, writeHeader, multi):
|
||||
def _setCSVFile(fileName, mode, encoding, writeHeader, multi, delayOpen):
|
||||
if fileName != '-':
|
||||
fileName = setFilePath(fileName, GC.DRIVE_DIR)
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_NAME] = fileName
|
||||
@@ -3962,6 +3962,9 @@ def SetGlobalVariables():
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_WRITE_HEADER] = writeHeader
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_MULTIPROCESS] = multi
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE] = None
|
||||
if not delayOpen and fileName != '-':
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_FD] = openFile(fileName, mode, newline='',
|
||||
encoding=encoding, errors='backslashreplace')
|
||||
|
||||
def _setSTDFile(stdtype, fileName, mode, multi):
|
||||
if stdtype == GM.STDOUT:
|
||||
@@ -4243,7 +4246,7 @@ def SetGlobalVariables():
|
||||
# multiprocessexit (rc<Operator><Number>)|(rcrange=<Number>/<Number>)|(rcrange!=<Number>/<Number>)
|
||||
if checkArgumentPresent(Cmd.MULTIPROCESSEXIT_CMD):
|
||||
_setMultiprocessExit()
|
||||
# redirect csv <FileName> [multiprocess] [append] [noheader] [charset <CharSet>]
|
||||
# redirect csv <FileName> [delayopen] [multiprocess] [append] [noheader] [charset <CharSet>]
|
||||
# [columndelimiter <Character>] [quotechar <Character>]] [noescapechar [<Boolean>]]
|
||||
# [sortheaders <StringList>] [timestampcolumn <String>] [transpose [<Boolean>]]
|
||||
# [todrive <ToDriveAttribute>*]
|
||||
@@ -4256,23 +4259,39 @@ def SetGlobalVariables():
|
||||
myarg = getChoice(['csv', 'stdout', 'stderr'])
|
||||
filename = re.sub(r'{{Section}}', sectionName, getString(Cmd.OB_FILE_NAME, checkBlank=True))
|
||||
if myarg == 'csv':
|
||||
multi = checkArgumentPresent('multiprocess')
|
||||
mode = DEFAULT_FILE_APPEND_MODE if checkArgumentPresent('append') else DEFAULT_FILE_WRITE_MODE
|
||||
writeHeader = not checkArgumentPresent('noheader')
|
||||
encoding = getCharSet()
|
||||
if checkArgumentPresent('columndelimiter'):
|
||||
GM.Globals[GM.CSV_OUTPUT_COLUMN_DELIMITER] = GC.Values[GC.CSV_OUTPUT_COLUMN_DELIMITER] = getCharacter()
|
||||
if checkArgumentPresent('quotechar'):
|
||||
GM.Globals[GM.CSV_OUTPUT_QUOTE_CHAR] = GC.Values[GC.CSV_OUTPUT_QUOTE_CHAR] = getCharacter()
|
||||
if checkArgumentPresent('noescapechar'):
|
||||
GM.Globals[GM.CSV_OUTPUT_NO_ESCAPE_CHAR] = GC.Values[GC.CSV_OUTPUT_NO_ESCAPE_CHAR] = getBoolean()
|
||||
if checkArgumentPresent('sortheaders'):
|
||||
GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS] = GC.Values[GC.CSV_OUTPUT_SORT_HEADERS] = getString(Cmd.OB_STRING_LIST, minLen=0).replace(',', ' ').split()
|
||||
if checkArgumentPresent('timestampcolumn'):
|
||||
GM.Globals[GM.CSV_OUTPUT_TIMESTAMP_COLUMN] = GC.Values[GC.CSV_OUTPUT_TIMESTAMP_COLUMN] = getString(Cmd.OB_STRING, minLen=0)
|
||||
if checkArgumentPresent('transpose'):
|
||||
GM.Globals[GM.CSV_OUTPUT_TRANSPOSE] = getBoolean()
|
||||
_setCSVFile(filename, mode, encoding, writeHeader, multi)
|
||||
multi = False
|
||||
mode = DEFAULT_FILE_WRITE_MODE
|
||||
writeHeader = True
|
||||
encoding = GC.Values[GC.CHARSET]
|
||||
delayOpen = False
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg == 'multiprocess':
|
||||
multi = True
|
||||
elif myarg == 'append':
|
||||
mode = DEFAULT_FILE_APPEND_MODE
|
||||
elif myarg == 'noheader':
|
||||
writeHeader = False
|
||||
elif myarg == 'charset':
|
||||
encoding = getString(Cmd.OB_CHAR_SET)
|
||||
elif myarg == 'delayopen':
|
||||
delayOpen = True
|
||||
elif myarg == 'columndelimiter':
|
||||
GM.Globals[GM.CSV_OUTPUT_COLUMN_DELIMITER] = GC.Values[GC.CSV_OUTPUT_COLUMN_DELIMITER] = getCharacter()
|
||||
elif myarg == 'quotechar':
|
||||
GM.Globals[GM.CSV_OUTPUT_QUOTE_CHAR] = GC.Values[GC.CSV_OUTPUT_QUOTE_CHAR] = getCharacter()
|
||||
elif myarg == 'noescapechar':
|
||||
GM.Globals[GM.CSV_OUTPUT_NO_ESCAPE_CHAR] = GC.Values[GC.CSV_OUTPUT_NO_ESCAPE_CHAR] = getBoolean()
|
||||
elif myarg == 'sortheaders':
|
||||
GM.Globals[GM.CSV_OUTPUT_SORT_HEADERS] = GC.Values[GC.CSV_OUTPUT_SORT_HEADERS] = getString(Cmd.OB_STRING_LIST, minLen=0).replace(',', ' ').split()
|
||||
elif myarg == 'timestampcolumn':
|
||||
GM.Globals[GM.CSV_OUTPUT_TIMESTAMP_COLUMN] = GC.Values[GC.CSV_OUTPUT_TIMESTAMP_COLUMN] = getString(Cmd.OB_STRING, minLen=0)
|
||||
elif myarg == 'transpose':
|
||||
GM.Globals[GM.CSV_OUTPUT_TRANSPOSE] = getBoolean()
|
||||
else:
|
||||
Cmd.Backup()
|
||||
break
|
||||
_setCSVFile(filename, mode, encoding, writeHeader, multi, delayOpen)
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE_CSVPF] = CSVPrintFile()
|
||||
if checkArgumentPresent('todrive'):
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE_CSVPF].GetTodriveParameters()
|
||||
@@ -4309,9 +4328,9 @@ def SetGlobalVariables():
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_MULTIPROCESS] == GM.Globals[GM.STDOUT][GM.REDIRECT_MULTIPROCESS] and
|
||||
GM.Globals[GM.CSVFILE].get(GM.REDIRECT_QUEUE_CSVPF) and not GM.Globals[GM.CSVFILE][GM.REDIRECT_QUEUE_CSVPF].todrive):
|
||||
_setCSVFile('-', GM.Globals[GM.STDOUT].get(GM.REDIRECT_MODE, DEFAULT_FILE_WRITE_MODE), GC.Values[GC.CHARSET],
|
||||
GM.Globals[GM.CSVFILE].get(GM.REDIRECT_WRITE_HEADER, True), GM.Globals[GM.STDOUT][GM.REDIRECT_MULTIPROCESS])
|
||||
GM.Globals[GM.CSVFILE].get(GM.REDIRECT_WRITE_HEADER, True), GM.Globals[GM.STDOUT][GM.REDIRECT_MULTIPROCESS], False)
|
||||
elif not GM.Globals[GM.CSVFILE]:
|
||||
_setCSVFile('-', GM.Globals[GM.STDOUT].get(GM.REDIRECT_MODE, DEFAULT_FILE_WRITE_MODE), GC.Values[GC.CHARSET], True, False)
|
||||
_setCSVFile('-', GM.Globals[GM.STDOUT].get(GM.REDIRECT_MODE, DEFAULT_FILE_WRITE_MODE), GC.Values[GC.CHARSET], True, False, False)
|
||||
initAPICallsRateCheck()
|
||||
# Main process
|
||||
# Clear input row filters/limit from parser, children can define but shouldn't inherit global value
|
||||
@@ -5576,6 +5595,30 @@ def _finalizeGAPIpagesResult(pageMessage):
|
||||
writeStderr('\r\n')
|
||||
flushStderr()
|
||||
|
||||
def _setMaxArgResults(maxItems, pageArgsInBody, kwargs):
|
||||
if pageArgsInBody:
|
||||
kwargs.setdefault('body', {})
|
||||
maxArg = ''
|
||||
maxResults = 0
|
||||
if maxItems:
|
||||
if not pageArgsInBody:
|
||||
maxResults = kwargs.get('maxResults', 0)
|
||||
if maxResults:
|
||||
maxArg = 'maxResults'
|
||||
else:
|
||||
maxResults = kwargs.get('pageSize', 0)
|
||||
if maxResults:
|
||||
maxArg = 'pageSize'
|
||||
else:
|
||||
maxResults = kwargs['body'].get('maxResults', 0)
|
||||
if maxResults:
|
||||
maxArg = 'maxResults'
|
||||
else:
|
||||
maxResults = kwargs['body'].get('pageSize', 0)
|
||||
if maxResults:
|
||||
maxArg = 'pageSize'
|
||||
return (maxArg, maxResults)
|
||||
|
||||
def callGAPIpages(service, function, items,
|
||||
pageMessage=None, messageAttribute=None, maxItems=0, noFinalize=False,
|
||||
throwReasons=None, retryReasons=None,
|
||||
@@ -5587,21 +5630,14 @@ def callGAPIpages(service, function, items,
|
||||
retryReasons = []
|
||||
allResults = []
|
||||
totalItems = 0
|
||||
maxArg = ''
|
||||
if maxItems:
|
||||
maxResults = kwargs.get('maxResults', 0)
|
||||
if maxResults:
|
||||
maxArg = 'maxResults'
|
||||
else:
|
||||
maxResults = kwargs.get('pageSize', 0)
|
||||
if maxResults:
|
||||
maxArg = 'pageSize'
|
||||
if pageArgsInBody:
|
||||
kwargs.setdefault('body', {})
|
||||
maxArg, maxResults = _setMaxArgResults(maxItems, pageArgsInBody, kwargs)
|
||||
entityType = Ent.Getting() if pageMessage else None
|
||||
while True:
|
||||
if maxArg and maxItems-totalItems < maxResults:
|
||||
kwargs[maxArg] = maxItems-totalItems
|
||||
if not pageArgsInBody:
|
||||
kwargs[maxArg] = maxItems-totalItems
|
||||
else:
|
||||
kwargs['body'][maxArg] = maxItems-totalItems
|
||||
results = callGAPI(service, function,
|
||||
throwReasons=throwReasons, retryReasons=retryReasons,
|
||||
**kwargs)
|
||||
@@ -5610,10 +5646,10 @@ def callGAPIpages(service, function, items,
|
||||
if not noFinalize:
|
||||
_finalizeGAPIpagesResult(pageMessage)
|
||||
return allResults
|
||||
if pageArgsInBody:
|
||||
kwargs['body']['pageToken'] = pageToken
|
||||
else:
|
||||
if not pageArgsInBody:
|
||||
kwargs['pageToken'] = pageToken
|
||||
else:
|
||||
kwargs['body']['pageToken'] = pageToken
|
||||
|
||||
def yieldGAPIpages(service, function, items,
|
||||
pageMessage=None, messageAttribute=None, maxItems=0, noFinalize=False,
|
||||
@@ -5625,21 +5661,14 @@ def yieldGAPIpages(service, function, items,
|
||||
if retryReasons is None:
|
||||
retryReasons = []
|
||||
totalItems = 0
|
||||
maxArg = ''
|
||||
if maxItems:
|
||||
maxResults = kwargs.get('maxResults', 0)
|
||||
if maxResults:
|
||||
maxArg = 'maxResults'
|
||||
else:
|
||||
maxResults = kwargs.get('pageSize', 0)
|
||||
if maxResults:
|
||||
maxArg = 'pageSize'
|
||||
if pageArgsInBody:
|
||||
kwargs.setdefault('body', {})
|
||||
maxArg, maxResults = _setMaxArgResults(maxItems, pageArgsInBody, kwargs)
|
||||
entityType = Ent.Getting() if pageMessage else None
|
||||
while True:
|
||||
if maxArg and maxItems-totalItems < maxResults:
|
||||
kwargs[maxArg] = maxItems-totalItems
|
||||
if not pageArgsInBody:
|
||||
kwargs[maxArg] = maxItems-totalItems
|
||||
else:
|
||||
kwargs['body'][maxArg] = maxItems-totalItems
|
||||
results = callGAPI(service, function,
|
||||
throwReasons=throwReasons, retryReasons=retryReasons,
|
||||
**kwargs)
|
||||
@@ -5662,10 +5691,10 @@ def yieldGAPIpages(service, function, items,
|
||||
if not noFinalize:
|
||||
_finalizeGAPIpagesResult(pageMessage)
|
||||
return
|
||||
if pageArgsInBody:
|
||||
kwargs['body']['pageToken'] = pageToken
|
||||
else:
|
||||
if not pageArgsInBody:
|
||||
kwargs['pageToken'] = pageToken
|
||||
else:
|
||||
kwargs['body']['pageToken'] = pageToken
|
||||
|
||||
def callGAPIitems(service, function, items,
|
||||
throwReasons=None, retryReasons=None,
|
||||
@@ -8782,9 +8811,11 @@ class CSVPrintFile():
|
||||
closeFile(csvFile)
|
||||
|
||||
def writeCSVToFile():
|
||||
csvFile = openFile(GM.Globals[GM.CSVFILE][GM.REDIRECT_NAME], GM.Globals[GM.CSVFILE][GM.REDIRECT_MODE], newline='',
|
||||
encoding=GM.Globals[GM.CSVFILE][GM.REDIRECT_ENCODING], errors='backslashreplace',
|
||||
continueOnError=True)
|
||||
csvFile = GM.Globals[GM.CSVFILE].get(GM.REDIRECT_FD, None)
|
||||
if not csvFile:
|
||||
csvFile = openFile(GM.Globals[GM.CSVFILE][GM.REDIRECT_NAME], GM.Globals[GM.CSVFILE][GM.REDIRECT_MODE], newline='',
|
||||
encoding=GM.Globals[GM.CSVFILE][GM.REDIRECT_ENCODING], errors='backslashreplace',
|
||||
continueOnError=True)
|
||||
if csvFile:
|
||||
writerDialect = setDialect(str(GC.Values[GC.CSV_OUTPUT_LINE_TERMINATOR]), self.noEscapeChar)
|
||||
writer = csv.DictWriter(csvFile, titlesList, extrasaction=extrasaction, **writerDialect)
|
||||
@@ -9741,18 +9772,15 @@ def initializeLogging():
|
||||
logging.getLogger().addHandler(nh)
|
||||
|
||||
def saveNonPickleableValues():
|
||||
savedValues = {GM.STDOUT: {}, GM.STDERR: {}, GM.SAVED_STDOUT: None,
|
||||
GM.CMDLOG_HANDLER: None, GM.CMDLOG_LOGGER: None}
|
||||
savedValues = {GM.CSVFILE: {}, GM.STDOUT: {}, GM.STDERR: {},
|
||||
GM.SAVED_STDOUT: None, GM.CMDLOG_HANDLER: None, GM.CMDLOG_LOGGER: None}
|
||||
savedValues[GM.CSVFILE][GM.REDIRECT_FD] = GM.Globals[GM.CSVFILE].pop(GM.REDIRECT_FD, None)
|
||||
savedValues[GM.STDOUT][GM.REDIRECT_FD] = GM.Globals[GM.STDOUT].pop(GM.REDIRECT_FD, None)
|
||||
savedValues[GM.STDOUT][GM.REDIRECT_MULTI_FD] = GM.Globals[GM.STDOUT].pop(GM.REDIRECT_MULTI_FD, None)
|
||||
savedValues[GM.STDERR][GM.REDIRECT_FD] = GM.Globals[GM.STDERR].pop(GM.REDIRECT_FD, None)
|
||||
savedValues[GM.STDERR][GM.REDIRECT_MULTI_FD] = GM.Globals[GM.STDERR].pop(GM.REDIRECT_MULTI_FD, None)
|
||||
savedValues[GM.SAVED_STDOUT] = GM.Globals[GM.SAVED_STDOUT]
|
||||
GM.Globals[GM.SAVED_STDOUT] = None
|
||||
savedValues[GM.STDOUT][GM.REDIRECT_FD] = GM.Globals[GM.STDOUT].get(GM.REDIRECT_FD, None)
|
||||
GM.Globals[GM.STDOUT].pop(GM.REDIRECT_FD, None)
|
||||
savedValues[GM.STDERR][GM.REDIRECT_FD] = GM.Globals[GM.STDERR].get(GM.REDIRECT_FD, None)
|
||||
GM.Globals[GM.STDERR].pop(GM.REDIRECT_FD, None)
|
||||
savedValues[GM.STDOUT][GM.REDIRECT_MULTI_FD] = GM.Globals[GM.STDOUT].get(GM.REDIRECT_MULTI_FD, None)
|
||||
GM.Globals[GM.STDOUT].pop(GM.REDIRECT_MULTI_FD, None)
|
||||
savedValues[GM.STDERR][GM.REDIRECT_MULTI_FD] = GM.Globals[GM.STDERR].get(GM.REDIRECT_MULTI_FD, None)
|
||||
GM.Globals[GM.STDERR].pop(GM.REDIRECT_MULTI_FD, None)
|
||||
savedValues[GM.CMDLOG_HANDLER] = GM.Globals[GM.CMDLOG_HANDLER]
|
||||
GM.Globals[GM.CMDLOG_HANDLER] = None
|
||||
savedValues[GM.CMDLOG_LOGGER] = GM.Globals[GM.CMDLOG_LOGGER]
|
||||
@@ -9760,11 +9788,12 @@ def saveNonPickleableValues():
|
||||
return savedValues
|
||||
|
||||
def restoreNonPickleableValues(savedValues):
|
||||
GM.Globals[GM.SAVED_STDOUT] = savedValues[GM.SAVED_STDOUT]
|
||||
GM.Globals[GM.CSVFILE][GM.REDIRECT_FD] = savedValues[GM.CSVFILE][GM.REDIRECT_FD]
|
||||
GM.Globals[GM.STDOUT][GM.REDIRECT_FD] = savedValues[GM.STDOUT][GM.REDIRECT_FD]
|
||||
GM.Globals[GM.STDERR][GM.REDIRECT_FD] = savedValues[GM.STDERR][GM.REDIRECT_FD]
|
||||
GM.Globals[GM.STDOUT][GM.REDIRECT_MULTI_FD] = savedValues[GM.STDOUT][GM.REDIRECT_MULTI_FD]
|
||||
GM.Globals[GM.STDERR][GM.REDIRECT_FD] = savedValues[GM.STDERR][GM.REDIRECT_FD]
|
||||
GM.Globals[GM.STDERR][GM.REDIRECT_MULTI_FD] = savedValues[GM.STDERR][GM.REDIRECT_MULTI_FD]
|
||||
GM.Globals[GM.SAVED_STDOUT] = savedValues[GM.SAVED_STDOUT]
|
||||
GM.Globals[GM.CMDLOG_HANDLER] = savedValues[GM.CMDLOG_HANDLER]
|
||||
GM.Globals[GM.CMDLOG_LOGGER] = savedValues[GM.CMDLOG_LOGGER]
|
||||
|
||||
@@ -24181,7 +24210,7 @@ CROS_ACTION_NAME_MAP = {
|
||||
|
||||
# gam <CrOSTypeEntity> update <CrOSAttribute>+ [quickcrosmove [<Boolean>]] [nobatchupdate]
|
||||
# gam <CrOSTypeEntity> update action <CrOSAction> [acknowledge_device_touch_requirement]
|
||||
# [actionbatchsize <Integer>]
|
||||
# [actionbatchsize <Integer>] [maxtodeprov <Integer>]
|
||||
def updateCrOSDevices(entityList):
|
||||
cd = buildGAPIObject(API.DIRECTORY)
|
||||
noBatchUpdate = False
|
||||
@@ -24191,6 +24220,7 @@ def updateCrOSDevices(entityList):
|
||||
ackWipe = False
|
||||
quickCrOSMove = GC.Values[GC.QUICK_CROS_MOVE]
|
||||
actionBatchSize = 10
|
||||
maxToDeprovision = None
|
||||
while Cmd.ArgumentsRemaining():
|
||||
myarg = getArgument()
|
||||
if myarg in UPDATE_CROS_ARGUMENT_TO_PROPERTY_MAP:
|
||||
@@ -24216,6 +24246,8 @@ def updateCrOSDevices(entityList):
|
||||
noBatchUpdate = getBoolean()
|
||||
elif myarg == 'actionbatchsize':
|
||||
actionBatchSize = getInteger(minVal=10, maxVal=250)
|
||||
elif myarg == 'maxtodeprov':
|
||||
maxToDeprovision = getInteger(minVal=0)
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if action_body and update_body:
|
||||
@@ -24229,9 +24261,15 @@ def updateCrOSDevices(entityList):
|
||||
i, count, entityList = getEntityArgument(entityList)
|
||||
# Action
|
||||
if action_body:
|
||||
if action_body['changeChromeOsDeviceStatusAction'] == 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION' and not ackWipe:
|
||||
stderrWarningMsg(Msg.REFUSING_TO_DEPROVISION_DEVICES.format(count))
|
||||
systemErrorExit(ACTION_NOT_PERFORMED_RC, None)
|
||||
if action_body['changeChromeOsDeviceStatusAction'] == 'CHANGE_CHROME_OS_DEVICE_STATUS_ACTION_DEPROVISION':
|
||||
if not ackWipe:
|
||||
stderrWarningMsg(Msg.REFUSING_TO_DEPROVISION_DEVICES.format(count))
|
||||
systemErrorExit(ACTION_NOT_PERFORMED_RC, None)
|
||||
if maxToDeprovision is None:
|
||||
maxToDeprovision = 1
|
||||
if count > maxToDeprovision > 0:
|
||||
stderrWarningMsg(Msg.REFUSING_TO_DEPROVISION_N_DEVICES.format(count, maxToDeprovision))
|
||||
systemErrorExit(ACTION_NOT_PERFORMED_RC, None)
|
||||
while i < count:
|
||||
bcount = min(count-i, actionBatchSize)
|
||||
action_body['deviceIds'] = entityList[i:i+bcount]
|
||||
@@ -41092,6 +41130,8 @@ LIST_EVENTS_SELECT_PROPERTIES = {
|
||||
|
||||
LIST_EVENTS_MATCH_FIELDS = {
|
||||
'attendees': ['attendees', 'email'],
|
||||
'attendeesorganizer': ['attendees', 'organizer'],
|
||||
'attendeesorganiser': ['attendees', 'organizer'],
|
||||
'attendeesonlydomainlist': ['attendees', 'onlydomainlist'],
|
||||
'attendeesdomainlist': ['attendees', 'domainlist'],
|
||||
'attendeesnotdomainlist': ['attendees', 'notdomainlist'],
|
||||
@@ -41171,6 +41211,8 @@ def getCalendarEventEntity():
|
||||
calendarEventEntity['matches'].append((matchField, set(getString(Cmd.OB_DOMAIN_NAME_LIST).replace(',', ' ').split())))
|
||||
elif matchField[1] == 'email':
|
||||
calendarEventEntity['matches'].append((matchField, getNormalizedEmailAddressEntity()))
|
||||
elif matchField[1] == 'organizer':
|
||||
calendarEventEntity['matches'].append((matchField, getBoolean(defaultValue=None), getNormalizedEmailAddressEntity()))
|
||||
else: #status
|
||||
calendarEventEntity['matches'].append((matchField,
|
||||
getChoice(CALENDAR_ATTENDEE_OPTIONAL_CHOICE_MAP, defaultChoice=False, mapChoice=True),
|
||||
@@ -41502,7 +41544,17 @@ def _eventMatches(event, match):
|
||||
if domain not in match[1]:
|
||||
return True
|
||||
return False
|
||||
# status
|
||||
if match[0][1] == 'organizer':
|
||||
for matchEmail in match[2]:
|
||||
for attendee in event['attendees']:
|
||||
if 'email' in attendee and matchEmail == attendee['email']:
|
||||
if attendee.get('organizer', False) != match[1]:
|
||||
return False
|
||||
break
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
# if match[0][1] == 'status':
|
||||
for matchEmail in match[3]:
|
||||
for attendee in event['attendees']:
|
||||
if 'email' in attendee and matchEmail == attendee['email']:
|
||||
@@ -45362,11 +45414,13 @@ PRINT_VAULT_COUNTS_TITLES = ['account', 'count', 'error']
|
||||
# [excludedrafts <Boolean>]
|
||||
# [<JSONData>]
|
||||
# [wait <Integer>]
|
||||
# [include_suspended_zeros [<Boolean>]]
|
||||
# gam print vaultcounts [todrive <ToDriveAttributes>*]
|
||||
# matter <MatterItem> operation <String> [wait <Integer>]
|
||||
def doPrintVaultCounts():
|
||||
v = buildGAPIObject(API.VAULT)
|
||||
csvPF = CSVPrintFile(PRINT_VAULT_COUNTS_TITLES, 'sortall')
|
||||
includeSuspendedZeros = False
|
||||
matterId = name = None
|
||||
operationWait = 15
|
||||
body = {'view': 'ALL', 'query': {}}
|
||||
@@ -45384,6 +45438,8 @@ def doPrintVaultCounts():
|
||||
_buildVaultQuery(myarg, body['query'], VAULT_COUNTS_CORPUS_ARGUMENT_MAP)
|
||||
elif myarg == 'wait':
|
||||
operationWait = getInteger(minVal=1)
|
||||
elif myarg == "includesuspendedzeros":
|
||||
includeSuspendedZeros = getBoolean()
|
||||
else:
|
||||
unknownArgumentExit()
|
||||
if not matterId:
|
||||
@@ -45422,9 +45478,13 @@ def doPrintVaultCounts():
|
||||
if search_method == 'ACCOUNT':
|
||||
query_accounts = query.get('accountInfo', {}).get('emails', [])
|
||||
elif search_method == 'ENTIRE_ORG':
|
||||
query_accounts = getItemsToModify(Cmd.ENTITY_ALL_USERS, '')
|
||||
query_accounts = getItemsToModify(Cmd.ENTITY_ALL_USERS if not includeSuspendedZeros else Cmd.ENTITY_ALL_USERS_NS_SUSP,
|
||||
'')
|
||||
elif search_method == 'ORG_UNIT':
|
||||
query_accounts = getItemsToModify(Cmd.ENTITY_OU, query['orgUnitInfo']['orgUnitId'])
|
||||
query_accounts = getItemsToModify(Cmd.ENTITY_OU if not includeSuspendedZeros else Cmd.ENTITY_OU_NS_SUSP,
|
||||
query['orgUnitInfo']['orgUnitId'])
|
||||
else:
|
||||
query_accounts = []
|
||||
mailcounts = response.get('mailCountResult', {})
|
||||
groupcounts = response.get('groupsCountResult', {})
|
||||
for a_count in [mailcounts, groupcounts]:
|
||||
@@ -45943,6 +46003,8 @@ def getUserAttributes(cd, updateCmd, noUid=False):
|
||||
elif updateCmd and myarg == 'updateprimaryemail':
|
||||
updatePrimaryEmail = list(getREPatternSubstitution(re.IGNORECASE))
|
||||
updatePrimaryEmail.append(checkArgumentPresent(['preview']))
|
||||
# elif updateCmd and myarg == 'primaryguestemail':
|
||||
# body['guestAccountInfo'] = {'primaryGuestEmail': getEmailAddress(noUid=True)}
|
||||
elif myarg == 'json':
|
||||
body.update(getJSON(USER_JSON_SKIP_FIELDS))
|
||||
if 'name' in body and 'fullName' in body['name']:
|
||||
@@ -46910,10 +46972,15 @@ USER_NAME_PROPERTY_PRINT_ORDER = [
|
||||
'fullName',
|
||||
'displayName',
|
||||
]
|
||||
USER_GUEST_PROPERTY_PRINT_ORDER = [
|
||||
'primaryGuestEmail',
|
||||
]
|
||||
USER_LANGUAGE_PROPERTY_PRINT_ORDER = [
|
||||
'languages',
|
||||
]
|
||||
USER_SCALAR_PROPERTY_PRINT_ORDER = [
|
||||
'isGuestUser',
|
||||
'guestAccountInfo',
|
||||
'isAdmin',
|
||||
'isDelegatedAdmin',
|
||||
'isEnrolledIn2Sv',
|
||||
@@ -47031,7 +47098,7 @@ USER_FIELDS_CHOICE_MAP = {
|
||||
'gal': 'includeInGlobalAddressList',
|
||||
'gender': ['gender.type', 'gender.customGender', 'gender.addressMeAs'],
|
||||
'givenname': 'name.givenName',
|
||||
'guestaccountinfo': 'guestAccountInfo',
|
||||
'guestaccountinfo': ['guestAccountInfo.primaryGuestEmail'],
|
||||
'id': 'id',
|
||||
'im': 'ims',
|
||||
'ims': 'ims',
|
||||
@@ -47229,7 +47296,7 @@ def infoUsers(entityList):
|
||||
ci = None
|
||||
setTrueCustomerId(cd)
|
||||
getAliases = getBuildingNames = getCIGroupsTree = getGroups = getLicenses = getSchemas = not GC.Values[GC.QUICK_INFO_USER]
|
||||
getGroupsTree = False
|
||||
getGroupsTree = getIsGuestUser = False
|
||||
FJQC = FormatJSONQuoteChar()
|
||||
schemaParms = _initSchemaParms('full')
|
||||
viewType = 'admin_view'
|
||||
@@ -47284,6 +47351,7 @@ def infoUsers(entityList):
|
||||
fieldsList.append('customSchemas')
|
||||
if getAliases:
|
||||
fieldsList.extend(['aliases', 'nonEditableAliases'])
|
||||
getIsGuestUser = not fieldsList or 'isGuestUser' in fieldsList
|
||||
fields = getFieldsFromFieldsList(fieldsList)
|
||||
if getLicenses:
|
||||
lic = buildGAPIObject(API.LICENSING)
|
||||
@@ -47300,6 +47368,8 @@ def infoUsers(entityList):
|
||||
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND],
|
||||
userKey=userEmail, projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
|
||||
viewType=viewType, fields=fields)
|
||||
if getIsGuestUser and 'isGuestUser' not in user:
|
||||
user['isGuestUser'] = False
|
||||
if userMultiAttributeFilters:
|
||||
_filterUserMultiAttributes(user, userMultiAttributeFilters)
|
||||
groups = []
|
||||
@@ -47349,17 +47419,23 @@ def infoUsers(entityList):
|
||||
Ind.Increment()
|
||||
printKeyValueList(['Settings', None])
|
||||
Ind.Increment()
|
||||
if 'name' in user:
|
||||
for up in USER_NAME_PROPERTY_PRINT_ORDER:
|
||||
if up in user['name']:
|
||||
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], user['name'][up]])
|
||||
up = 'name'
|
||||
if up in user:
|
||||
for nup in USER_NAME_PROPERTY_PRINT_ORDER:
|
||||
if nup in user[up]:
|
||||
printKeyValueList([UProp.PROPERTIES[nup][UProp.TITLE], user[up][nup]])
|
||||
up = 'languages'
|
||||
if up in user:
|
||||
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], _formatLanguagesList(user[up], ',')])
|
||||
for up in USER_SCALAR_PROPERTY_PRINT_ORDER:
|
||||
if up in user:
|
||||
if up not in USER_TIME_OBJECTS:
|
||||
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], user[up]])
|
||||
if up != 'guestAccountInfo':
|
||||
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], user[up]])
|
||||
else:
|
||||
for gup in USER_GUEST_PROPERTY_PRINT_ORDER:
|
||||
if gup in user[up]:
|
||||
printKeyValueList([UProp.PROPERTIES[gup][UProp.TITLE], user[up][gup]])
|
||||
else:
|
||||
printKeyValueList([UProp.PROPERTIES[up][UProp.TITLE], formatLocalTime(user[up])])
|
||||
Ind.Decrement()
|
||||
@@ -47737,6 +47813,8 @@ def doPrintUsers(entityList=None):
|
||||
(isArchived == userEntity.get('archived', isArchived)))
|
||||
if not showUser:
|
||||
return
|
||||
if getIsGuestUser and 'isGuestUser' not in userEntity:
|
||||
userEntity['isGuestUser'] = False
|
||||
if showValidColumn:
|
||||
userEntity[showValidColumn] = True
|
||||
if userMultiAttributeFilters:
|
||||
@@ -47875,7 +47953,7 @@ def doPrintUsers(entityList=None):
|
||||
maxResults = GC.Values[GC.USER_MAX_RESULTS]
|
||||
schemaParms = _initSchemaParms('basic')
|
||||
projectionSet = False
|
||||
oneLicensePerRow = quotePlusPhoneNumbers = showDeleted = False
|
||||
getIsGuestUser = oneLicensePerRow = quotePlusPhoneNumbers = showDeleted = False
|
||||
aliasMatchPattern = isArchived = isSuspended = orgUnitPath = orgUnitPathLower = orderBy = sortOrder = None
|
||||
viewType = 'admin_view'
|
||||
delimiter = GC.Values[GC.CSV_OUTPUT_FIELD_DELIMITER]
|
||||
@@ -48013,6 +48091,7 @@ def doPrintUsers(entityList=None):
|
||||
sortRows = False
|
||||
if orgUnitPath is not None and fieldsList:
|
||||
fieldsList.append('orgUnitPath')
|
||||
getIsGuestUser = not fieldsList or 'isGuestUser' in fieldsList
|
||||
fields = getItemFieldsFromFieldsList('users', fieldsList)
|
||||
itemCount = 0
|
||||
for kwargsQuery in makeUserGroupDomainQueryFilters(kwargsDict):
|
||||
@@ -48081,6 +48160,7 @@ def doPrintUsers(entityList=None):
|
||||
return
|
||||
sortRows = True
|
||||
# If no individual fields were specified (allfields, basic, full) or individual fields other than primaryEmail were specified, look up each user
|
||||
getIsGuestUser = not fieldsList or 'isGuestUser' in fieldsList
|
||||
if isSuspended is not None and fieldsList:
|
||||
fieldsList.append('suspended')
|
||||
if isArchived is not None and fieldsList:
|
||||
@@ -57052,7 +57132,7 @@ CONSOLIDATION_GROUPING_STRATEGY_CHOICE_MAP = {'driveui': 'legacy', 'legacy': 'le
|
||||
# (drivefilename <DriveFileName>) | (drivefoldername <DriveFolderName>) | (query <QueryDriveFile>)]
|
||||
# [([start <Date>|<Time>] [end <Date>|<Time>])|(range <Date>|<Time> <Date>|<Time>)|
|
||||
# yesterday|today|thismonth|(previousmonths <Integer>)]
|
||||
# [action|actions [not] <DriveActivityActionList>]
|
||||
# [action|actions [not] <DriveActivityActionList>] [maxactivities <Number>]
|
||||
# [consolidationstrategy legacy|none]
|
||||
# [idmapfile <CSVFileInput> endcsv]
|
||||
# [stripcrsfromname] [formatjson [quotechar <Character>]]
|
||||
@@ -57097,6 +57177,7 @@ def printDriveActivity(users):
|
||||
actions = set()
|
||||
strategy = 'none'
|
||||
negativeAction = stripCRsFromName = False
|
||||
maxActivities = 0
|
||||
checkArgumentPresent(['v2'])
|
||||
csvPF = CSVPrintFile([f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}name',
|
||||
f'user{GC.Values[GC.CSV_OUTPUT_SUBFIELD_DELIMITER]}emailAddress',
|
||||
@@ -57143,6 +57224,8 @@ def printDriveActivity(users):
|
||||
closeFile(f)
|
||||
elif myarg == 'stripcrsfromname':
|
||||
stripCRsFromName = True
|
||||
elif myarg == 'maxactivities':
|
||||
maxActivities = getInteger(minVal=0)
|
||||
else:
|
||||
FJQC.GetFormatJSONQuoteChar(myarg, True)
|
||||
if not baseFileList and not query:
|
||||
@@ -57201,18 +57284,18 @@ def printDriveActivity(users):
|
||||
qualifier = f' for {Ent.Singular(entityType)}: {fileId}'
|
||||
printGettingAllEntityItemsForWhom(Ent.ACTIVITY, user, i, count, qualifier=qualifier)
|
||||
pageMessage = getPageMessageForWhom()
|
||||
kwargs = {
|
||||
body = {
|
||||
'consolidationStrategy': {strategy: {}},
|
||||
'pageSize': GC.Values[GC.ACTIVITY_MAX_RESULTS],
|
||||
'pageToken': None,
|
||||
drive_key: f'items/{fileId}',
|
||||
'filter': activityFilter}
|
||||
numActivities = 0
|
||||
try:
|
||||
feed = yieldGAPIpages(activity.activity(), 'query', 'activities',
|
||||
pageMessage=pageMessage,
|
||||
pageMessage=pageMessage, maxItems=maxActivities,
|
||||
throwReasons=GAPI.ACTIVITY_THROW_REASONS,
|
||||
pageArgsInBody=True,
|
||||
fields='nextPageToken,activities', body=kwargs)
|
||||
fields='nextPageToken,activities', body=body, pageArgsInBody=True)
|
||||
for activities in feed:
|
||||
for activityEvent in activities:
|
||||
eventRow = {}
|
||||
@@ -57258,6 +57341,9 @@ def printDriveActivity(users):
|
||||
if csvPF.CheckRowTitles(checkRow):
|
||||
eventRow['JSON'] = json.dumps(cleanJSON(activityEvent), ensure_ascii=False, sort_keys=True)
|
||||
csvPF.WriteRowNoFilter(eventRow)
|
||||
numActivities += 1
|
||||
if maxActivities and numActivities >= maxActivities:
|
||||
break
|
||||
except GAPI.badRequest as e:
|
||||
entityActionFailedWarning([Ent.USER, user, entityType, fileId], str(e), i, count)
|
||||
continue
|
||||
@@ -57486,7 +57572,7 @@ def initFilePathInfo(delimiter):
|
||||
return {'ids': {}, 'allPaths': {}, 'localPaths': None, 'delimiter': delimiter}
|
||||
|
||||
def getFilePaths(drive, fileTree, initialResult, filePathInfo, addParentsToTree=False,
|
||||
fullpath=False, showDepth=False, folderPathOnly=False):
|
||||
fullpath=False, showDepth=False, folderPathOnly=False, parentPathOnly=False):
|
||||
def _getParentName(result):
|
||||
if (result['mimeType'] == MIMETYPE_GA_FOLDER) and result.get('driveId') and (result['name'] == TEAM_DRIVE):
|
||||
parentName = _getSharedDriveNameFromId(drive, result['driveId'])
|
||||
@@ -57540,8 +57626,9 @@ def getFilePaths(drive, fileTree, initialResult, filePathInfo, addParentsToTree=
|
||||
if depth > maxDepth:
|
||||
maxDepth = depth-1
|
||||
fp.reverse()
|
||||
if initialMimeType == MIMETYPE_GA_FOLDER or not folderPathOnly:
|
||||
fp.append(name)
|
||||
if not parentPathOnly:
|
||||
if initialMimeType == MIMETYPE_GA_FOLDER or not folderPathOnly:
|
||||
fp.append(name)
|
||||
filePaths.append(filePathInfo['delimiter'].join(fp))
|
||||
else:
|
||||
maxDepth = _makeFilePaths(v, fplist, filePaths, name, maxDepth)
|
||||
@@ -58057,7 +58144,7 @@ def _formatFileDriveLabels(showLabels, labels, result, printMode, delimiter):
|
||||
|
||||
# gam <UserTypeEntity> info drivefile <DriveFileEntity>
|
||||
# [returnidonly]
|
||||
# [filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)] [formatjson]
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
@@ -58067,7 +58154,7 @@ def _formatFileDriveLabels(showLabels, labels, result, printMode, delimiter):
|
||||
# [stripcrsfromname] [formatjson]
|
||||
# gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
# [returnidonly]
|
||||
# [filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)] [formatjson]
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
@@ -58085,7 +58172,7 @@ def showFileInfo(users):
|
||||
if followShortcuts:
|
||||
_setSkipObjects(skipObjects, ['mimeType', 'shortcutDetails'], DFF.fieldsList)
|
||||
|
||||
getPermissionsForSharedDrives = filepath = fullpath = folderPathOnly = followShortcuts = \
|
||||
getPermissionsForSharedDrives = filepath = fullpath = folderPathOnly = parentPathOnly = followShortcuts = \
|
||||
returnIdOnly = showParentsIdsAsList = showNoParents = stripCRsFromName = False
|
||||
pathDelimiter = '/'
|
||||
showLabels = None
|
||||
@@ -58103,6 +58190,8 @@ def showFileInfo(users):
|
||||
filepath = fullpath = True
|
||||
elif myarg == 'folderpathonly':
|
||||
folderPathOnly = getBoolean()
|
||||
elif myarg == 'parentpathonly':
|
||||
parentPathOnly = getBoolean()
|
||||
elif myarg == 'pathdelimiter':
|
||||
pathDelimiter = getCharacter()
|
||||
elif myarg == 'showparentsidsaslist':
|
||||
@@ -58219,7 +58308,7 @@ def showFileInfo(users):
|
||||
extendFileTreeParents(drive, fileTree, pathFields)
|
||||
if not FJQC.formatJSON:
|
||||
_, paths, _ = getFilePaths(drive, fileTree, result, filePathInfo, addParentsToTree=True,
|
||||
fullpath=fullpath, folderPathOnly=folderPathOnly)
|
||||
fullpath=fullpath, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
kcount = len(paths)
|
||||
printKeyValueList(['paths', kcount])
|
||||
Ind.Increment()
|
||||
@@ -58228,7 +58317,7 @@ def showFileInfo(users):
|
||||
Ind.Decrement()
|
||||
else:
|
||||
addFilePathsToInfo(drive, fileTree, result, filePathInfo,
|
||||
addParentsToTree=True, folderPathOnly=folderPathOnly)
|
||||
addParentsToTree=True, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
if fullpath:
|
||||
# Save simple parents list as mappings turn it into a list of dicts
|
||||
fpparents = result['parents'][:]
|
||||
@@ -58905,9 +58994,9 @@ def buildFileTree(feed, drive):
|
||||
return fileTree
|
||||
|
||||
def addFilePathsToRow(drive, fileTree, fileEntryInfo, filePathInfo, csvPF, row,
|
||||
fullpath=False, showDepth=False, folderPathOnly=False):
|
||||
fullpath=False, showDepth=False, folderPathOnly=False, parentPathOnly=False):
|
||||
_, paths, maxDepth = getFilePaths(drive, fileTree, fileEntryInfo, filePathInfo,
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly)
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
kcount = len(paths)
|
||||
if showDepth:
|
||||
row['depth'] = maxDepth
|
||||
@@ -58922,9 +59011,9 @@ def addFilePathsToRow(drive, fileTree, fileEntryInfo, filePathInfo, csvPF, row,
|
||||
row[key] = path
|
||||
k += 1
|
||||
|
||||
def addFilePathsToInfo(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=False, folderPathOnly=False):
|
||||
def addFilePathsToInfo(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=False, folderPathOnly=False, parentPathOnly=False):
|
||||
_, paths, _ = getFilePaths(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=addParentsToTree,
|
||||
showDepth=False, folderPathOnly=folderPathOnly)
|
||||
showDepth=False, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
fileEntryInfo['paths'] = []
|
||||
for path in sorted(paths):
|
||||
if GC.Values[GC.CSV_OUTPUT_CONVERT_CR_NL] and (path.find('\n') >= 0 or path.find('\r') >= 0):
|
||||
@@ -59517,7 +59606,7 @@ SIZE_FIELD_CHOICE_MAP = {
|
||||
# [countsonly [summary none|only|plus] [summaryuser <String>]
|
||||
# [showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||
# [countsrowfilter]
|
||||
# [filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
# [filepath|fullpath [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
# [showdrivename] [showshareddrivepermissions]
|
||||
# (showlabels details|ids)|(includelabels <DriveLabelIDList>)]
|
||||
@@ -59621,9 +59710,9 @@ def printFileList(users):
|
||||
if filepath:
|
||||
if not FJQC.formatJSON or not addPathsToJSON:
|
||||
addFilePathsToRow(drive, fileTree, fileInfo, filePathInfo, csvPF, row,
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly)
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
else:
|
||||
addFilePathsToInfo(drive, fileTree, fileInfo, filePathInfo, folderPathOnly=folderPathOnly)
|
||||
addFilePathsToInfo(drive, fileTree, fileInfo, filePathInfo, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
_mapDriveInfo(fileInfo, DFF.parentsSubFields, showParentsIdsAsList)
|
||||
if showParentsIdsAsList and 'parentsIds' in fileInfo:
|
||||
fileInfo['parents'] = len(fileInfo['parentsIds'])
|
||||
@@ -59757,7 +59846,7 @@ def printFileList(users):
|
||||
csvPF = CSVPrintFile('Owner', indexedTitles=DRIVE_INDEXED_TITLES)
|
||||
csvPFco = None
|
||||
FJQC = FormatJSONQuoteChar(csvPF)
|
||||
addPathsToJSON = continueOnInvalidQuery = countsRowFilter = buildTree = countsOnly = filepath = fullpath = folderPathOnly = \
|
||||
addPathsToJSON = continueOnInvalidQuery = countsRowFilter = buildTree = countsOnly = filepath = fullpath = folderPathOnly = parentPathOnly = \
|
||||
getPermissionDetailsForMyDrive = getPermissionsForSharedDrives = mimeTypeInQuery = noRecursion = oneItemPerRow = stripCRsFromName = \
|
||||
showParentsIdsAsList = showDepth = showParent = showSize = showSizeUnits = showMimeTypeSize = showSource = False
|
||||
sizeField = 'quotaBytesUsed'
|
||||
@@ -59815,6 +59904,8 @@ def printFileList(users):
|
||||
fullpath = myarg == 'fullpath'
|
||||
elif myarg == 'folderpathonly':
|
||||
folderPathOnly = getBoolean()
|
||||
elif myarg == 'parentpathonly':
|
||||
parentPathOnly = getBoolean()
|
||||
elif myarg == 'pathdelimiter':
|
||||
pathDelimiter = getCharacter()
|
||||
elif myarg == 'addpathstojson':
|
||||
@@ -60113,7 +60204,7 @@ def printFileList(users):
|
||||
break
|
||||
if fullpath:
|
||||
getFilePaths(drive, fileTree, fileEntryInfo, filePathInfo, addParentsToTree=True,
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly)
|
||||
fullpath=fullpath, showDepth=showDepth, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
if ((showParent and (fileEntryInfo['id'] not in {ORPHANS, SHARED_WITHME, SHARED_DRIVES})) or
|
||||
fileEntryInfo['mimeType'] != MIMETYPE_GA_FOLDER or noRecursion):
|
||||
if fileId not in filesPrinted:
|
||||
@@ -60408,18 +60499,18 @@ def printShowFileComments(users):
|
||||
# gam <UserTypeEntity> print filepaths <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [stripcrsfromname] [oneitemperrow]
|
||||
# [fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [followshortcuts [<Boolean>]]
|
||||
# gam <UserTypeEntity> show filepaths <DriveFileEntity>
|
||||
# [returnpathonly]
|
||||
# (orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
# [stripcrsfromname]
|
||||
# [fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
# [followshortcuts [<Boolean>]]
|
||||
def printShowFilePaths(users):
|
||||
csvPF = CSVPrintFile(['Owner', 'id', 'name', 'paths'], 'sortall', ['paths']) if Act.csvFormat() else None
|
||||
fileIdEntity = getDriveFileEntity()
|
||||
fullpath = folderPathOnly = followShortcuts = oneItemPerRow = returnPathOnly = stripCRsFromName = False
|
||||
fullpath = folderPathOnly = parentPathOnly = followShortcuts = oneItemPerRow = returnPathOnly = stripCRsFromName = False
|
||||
pathDelimiter = '/'
|
||||
OBY = OrderBy(DRIVEFILE_ORDERBY_CHOICE_MAP)
|
||||
while Cmd.ArgumentsRemaining():
|
||||
@@ -60430,6 +60521,8 @@ def printShowFilePaths(users):
|
||||
fullpath = True
|
||||
elif myarg == 'folderpathonly':
|
||||
folderPathOnly = getBoolean()
|
||||
elif myarg == 'parentpathonly':
|
||||
parentPathOnly = getBoolean()
|
||||
elif myarg == 'pathdelimiter':
|
||||
pathDelimiter = getCharacter()
|
||||
elif myarg == 'stripcrsfromname':
|
||||
@@ -60501,7 +60594,7 @@ def printShowFilePaths(users):
|
||||
extendFileTree(fileTree, [result], None, False)
|
||||
extendFileTreeParents(drive, fileTree, pathFields)
|
||||
entityType, paths, _ = getFilePaths(drive, fileTree, result, filePathInfo, addParentsToTree=True,
|
||||
fullpath=fullpath, folderPathOnly=folderPathOnly)
|
||||
fullpath=fullpath, folderPathOnly=folderPathOnly, parentPathOnly=parentPathOnly)
|
||||
if returnPathOnly:
|
||||
for path in paths:
|
||||
writeStdout(f'{path}\n')
|
||||
|
||||
@@ -53,6 +53,7 @@ class GamCLArgs():
|
||||
ENTITY_DOMAINS_ARCH = 'domains_arch'
|
||||
ENTITY_DOMAINS_NS = 'domains_ns'
|
||||
ENTITY_DOMAINS_SUSP = 'domains_susp'
|
||||
ENTITY_DOMAINS_NS_SUSP = 'domains_ns_susp'
|
||||
ENTITY_DOMAINS_NA_NS = 'domains_na_ns'
|
||||
ENTITY_GROUP = 'group'
|
||||
ENTITY_GROUP_INDE = 'group_inde'
|
||||
@@ -60,6 +61,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUP_ARCH = 'group_arch'
|
||||
ENTITY_GROUP_NS = 'group_ns'
|
||||
ENTITY_GROUP_SUSP = 'group_susp'
|
||||
ENTITY_GROUP_NS_SUSP = 'group_ns_susp'
|
||||
ENTITY_GROUP_NA_NS = 'group_na_ns'
|
||||
ENTITY_GROUPS = 'groups'
|
||||
ENTITY_GROUPS_INDE = 'groups_inde'
|
||||
@@ -67,12 +69,14 @@ class GamCLArgs():
|
||||
ENTITY_GROUPS_ARCH = 'groups_arch'
|
||||
ENTITY_GROUPS_NS = 'groups_ns'
|
||||
ENTITY_GROUPS_SUSP = 'groups_susp'
|
||||
ENTITY_GROUPS_NS_SUSP = 'groups_ns_susp'
|
||||
ENTITY_GROUPS_NA_NS = 'groups_na_ns'
|
||||
ENTITY_GROUP_USERS = 'group_users'
|
||||
ENTITY_GROUP_USERS_NA = 'group_users_na'
|
||||
ENTITY_GROUP_USERS_ARCH = 'group_users_arch'
|
||||
ENTITY_GROUP_USERS_NS = 'group_users_ns'
|
||||
ENTITY_GROUP_USERS_SUSP = 'group_users_susp'
|
||||
ENTITY_GROUP_USERS_NS_SUSP = 'group_users_ns_susp'
|
||||
ENTITY_GROUP_USERS_NA_NS = 'group_users_na_ns'
|
||||
ENTITY_GROUP_USERS_SELECT = 'group_users_select'
|
||||
ENTITY_LICENSES = 'licenses'
|
||||
@@ -82,24 +86,28 @@ class GamCLArgs():
|
||||
ENTITY_OU_ARCH = 'ou_arch'
|
||||
ENTITY_OU_NS = 'ou_ns'
|
||||
ENTITY_OU_SUSP = 'ou_susp'
|
||||
ENTITY_OU_NS_SUSP = 'ou_ns_susp'
|
||||
ENTITY_OU_NA_NS = 'ou_na_ns'
|
||||
ENTITY_OU_AND_CHILDREN = 'ou_and_children'
|
||||
ENTITY_OU_AND_CHILDREN_NA = 'ou_and_children_na'
|
||||
ENTITY_OU_AND_CHILDREN_ARCH = 'ou_and_children_arch'
|
||||
ENTITY_OU_AND_CHILDREN_NS = 'ou_and_children_ns'
|
||||
ENTITY_OU_AND_CHILDREN_SUSP = 'ou_and_children_susp'
|
||||
ENTITY_OU_AND_CHILDREN_NS_SUSP = 'ou_and_children_ns_susp'
|
||||
ENTITY_OU_AND_CHILDREN_NA_NS = 'ou_and_children_na_ns'
|
||||
ENTITY_OUS = 'ous'
|
||||
ENTITY_OUS_NA = 'ous_na'
|
||||
ENTITY_OUS_ARCH = 'ous_arch'
|
||||
ENTITY_OUS_NS = 'ous_ns'
|
||||
ENTITY_OUS_SUSP = 'ous_susp'
|
||||
ENTITY_OUS_NS_SUSP = 'ous_ns_susp'
|
||||
ENTITY_OUS_NA_NS = 'ous_na_ns'
|
||||
ENTITY_OUS_AND_CHILDREN = 'ous_and_children'
|
||||
ENTITY_OUS_AND_CHILDREN_NA = 'ous_and_children_na'
|
||||
ENTITY_OUS_AND_CHILDREN_ARCH = 'ous_and_children_arch'
|
||||
ENTITY_OUS_AND_CHILDREN_NS = 'ous_and_children_ns'
|
||||
ENTITY_OUS_AND_CHILDREN_SUSP = 'ous_and_children_susp'
|
||||
ENTITY_OUS_AND_CHILDREN_NS_SUSP = 'ous_and_children_ns_susp'
|
||||
ENTITY_OUS_AND_CHILDREN_NA_NS = 'ous_and_children_na_ns'
|
||||
ENTITY_QUERIES = 'queries'
|
||||
ENTITY_QUERY = 'query'
|
||||
@@ -111,9 +119,17 @@ class GamCLArgs():
|
||||
ENTITY_USERS_ARCH = 'users_arch'
|
||||
ENTITY_USERS_NS = 'users_ns'
|
||||
ENTITY_USERS_SUSP = 'users_susp'
|
||||
ENTITY_USERS_NS_SUSP = 'users_ns_susp'
|
||||
ENTITY_USERS_NA_NS = 'users_na_ns'
|
||||
ENTITY_USERS_ARCH_OR_SUSP = 'users_arch_or_susp'
|
||||
ENTITY_USERS_NS_SUSP = 'users_ns_susp'
|
||||
ENTITY_USERS_AND_GUESTS = 'users_and_guests'
|
||||
ENTITY_USERS_AND_GUESTS_NS = 'users_and_guests_ns'
|
||||
ENTITY_USERS_AND_GUESTS_SUSP = 'users_and_guests_susp'
|
||||
ENTITY_USERS_AND_GUESTS_NS_SUSP = 'users_and_guests_ns_susp'
|
||||
ENTITY_GUESTS = 'guests'
|
||||
ENTITY_GUESTS_NS = 'guests_ns'
|
||||
ENTITY_GUESTS_SUSP = 'guests_susp'
|
||||
ENTITY_GUESTS_NS_SUSP = 'guests_ns_susp'
|
||||
#
|
||||
BROWSER_ENTITIES = [
|
||||
ENTITY_BROWSER,
|
||||
@@ -150,6 +166,7 @@ class GamCLArgs():
|
||||
ENTITY_DOMAINS_ARCH,
|
||||
ENTITY_DOMAINS_NS,
|
||||
ENTITY_DOMAINS_SUSP,
|
||||
ENTITY_DOMAINS_NS_SUSP,
|
||||
ENTITY_DOMAINS_NA_NS,
|
||||
ENTITY_GROUP,
|
||||
ENTITY_GROUP_INDE,
|
||||
@@ -157,6 +174,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUP_ARCH,
|
||||
ENTITY_GROUP_NS,
|
||||
ENTITY_GROUP_SUSP,
|
||||
ENTITY_GROUP_NS_SUSP,
|
||||
ENTITY_GROUP_NA_NS,
|
||||
ENTITY_GROUPS,
|
||||
ENTITY_GROUPS_INDE,
|
||||
@@ -164,12 +182,14 @@ class GamCLArgs():
|
||||
ENTITY_GROUPS_ARCH,
|
||||
ENTITY_GROUPS_NS,
|
||||
ENTITY_GROUPS_SUSP,
|
||||
ENTITY_GROUPS_NS_SUSP,
|
||||
ENTITY_GROUPS_NA_NS,
|
||||
ENTITY_GROUP_USERS,
|
||||
ENTITY_GROUP_USERS_NA,
|
||||
ENTITY_GROUP_USERS_ARCH,
|
||||
ENTITY_GROUP_USERS_NS,
|
||||
ENTITY_GROUP_USERS_SUSP,
|
||||
ENTITY_GROUP_USERS_NS_SUSP,
|
||||
ENTITY_GROUP_USERS_NA_NS,
|
||||
ENTITY_GROUP_USERS_SELECT,
|
||||
ENTITY_LICENSES,
|
||||
@@ -179,24 +199,28 @@ class GamCLArgs():
|
||||
ENTITY_OU_ARCH,
|
||||
ENTITY_OU_NS,
|
||||
ENTITY_OU_SUSP,
|
||||
ENTITY_OU_NS_SUSP,
|
||||
ENTITY_OU_NA_NS,
|
||||
ENTITY_OU_AND_CHILDREN,
|
||||
ENTITY_OU_AND_CHILDREN_NA,
|
||||
ENTITY_OU_AND_CHILDREN_ARCH,
|
||||
ENTITY_OU_AND_CHILDREN_NS,
|
||||
ENTITY_OU_AND_CHILDREN_SUSP,
|
||||
ENTITY_OU_AND_CHILDREN_NS_SUSP,
|
||||
ENTITY_OU_AND_CHILDREN_NA_NS,
|
||||
ENTITY_OUS,
|
||||
ENTITY_OUS_NA,
|
||||
ENTITY_OUS_ARCH,
|
||||
ENTITY_OUS_NS,
|
||||
ENTITY_OUS_SUSP,
|
||||
ENTITY_OUS_NS_SUSP,
|
||||
ENTITY_OUS_NA_NS,
|
||||
ENTITY_OUS_AND_CHILDREN,
|
||||
ENTITY_OUS_AND_CHILDREN_NA,
|
||||
ENTITY_OUS_AND_CHILDREN_ARCH,
|
||||
ENTITY_OUS_AND_CHILDREN_NS,
|
||||
ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
ENTITY_QUERIES,
|
||||
ENTITY_QUERY,
|
||||
@@ -278,48 +302,56 @@ class GamCLArgs():
|
||||
'org_arch': ENTITY_OU_ARCH,
|
||||
'org_ns': ENTITY_OU_NS,
|
||||
'org_susp': ENTITY_OU_SUSP,
|
||||
'org_ns_susp': ENTITY_OU_NS_SUSP,
|
||||
'org_na_ns': ENTITY_OU_NA_NS,
|
||||
'org_and_child': ENTITY_OU_AND_CHILDREN,
|
||||
'org_and_child_na': ENTITY_OU_AND_CHILDREN_NA,
|
||||
'org_and_child_arch': ENTITY_OU_AND_CHILDREN_ARCH,
|
||||
'org_and_child_ns': ENTITY_OU_AND_CHILDREN_NS,
|
||||
'org_and_child_susp': ENTITY_OU_AND_CHILDREN_SUSP,
|
||||
'org_and_child_ns_susp': ENTITY_OU_AND_CHILDREN_NS_SUSP,
|
||||
'org_and_child_na_ns': ENTITY_OU_AND_CHILDREN_NA_NS,
|
||||
'org_and_children': ENTITY_OU_AND_CHILDREN,
|
||||
'org_and_children_na': ENTITY_OU_AND_CHILDREN_NA,
|
||||
'org_and_children_arch': ENTITY_OU_AND_CHILDREN_ARCH,
|
||||
'org_and_children_ns': ENTITY_OU_AND_CHILDREN_NS,
|
||||
'org_and_children_susp': ENTITY_OU_AND_CHILDREN_SUSP,
|
||||
'org_and_children_ns_susp': ENTITY_OU_AND_CHILDREN_NS_SUSP,
|
||||
'org_and_children_na_ns': ENTITY_OU_AND_CHILDREN_NA_NS,
|
||||
'orgs': ENTITY_OUS,
|
||||
'orgs_na': ENTITY_OUS_NA,
|
||||
'orgs_arch': ENTITY_OUS_ARCH,
|
||||
'orgs_ns': ENTITY_OUS_NS,
|
||||
'orgs_susp': ENTITY_OUS_SUSP,
|
||||
'orgs_ns_susp': ENTITY_OUS_NS_SUSP,
|
||||
'orgs_na_ns': ENTITY_OUS_NA_NS,
|
||||
'orgs_and_child': ENTITY_OUS_AND_CHILDREN,
|
||||
'orgs_and_child_na': ENTITY_OUS_AND_CHILDREN_NA,
|
||||
'orgs_and_child_arch': ENTITY_OUS_AND_CHILDREN_ARCH,
|
||||
'orgs_and_child_ns': ENTITY_OUS_AND_CHILDREN_NS,
|
||||
'orgs_and_child_susp': ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
'orgs_and_child_ns_susp': ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
'orgs_and_child_na_ns': ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
'orgs_and_children': ENTITY_OUS_AND_CHILDREN,
|
||||
'orgs_and_children_na': ENTITY_OUS_AND_CHILDREN_NA,
|
||||
'orgs_and_children_arch': ENTITY_OUS_AND_CHILDREN_ARCH,
|
||||
'orgs_and_children_ns': ENTITY_OUS_AND_CHILDREN_NS,
|
||||
'orgs_and_children_susp': ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
'orgs_and_children_ns_susp': ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
'orgs_and_children_na_ns': ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
'ou_and_child': ENTITY_OU_AND_CHILDREN,
|
||||
'ou_and_child_na': ENTITY_OU_AND_CHILDREN_NA,
|
||||
'ou_and_child_arch': ENTITY_OU_AND_CHILDREN_ARCH,
|
||||
'ou_and_child_ns': ENTITY_OU_AND_CHILDREN_NS,
|
||||
'ou_and_child_susp': ENTITY_OU_AND_CHILDREN_SUSP,
|
||||
'ou_and_child_ns_susp': ENTITY_OU_AND_CHILDREN_NS_SUSP,
|
||||
'ou_and_child_na_ns': ENTITY_OU_AND_CHILDREN_NA_NS,
|
||||
'ous_and_child': ENTITY_OUS_AND_CHILDREN,
|
||||
'ous_and_child_na': ENTITY_OUS_AND_CHILDREN_NA,
|
||||
'ous_and_child_arch': ENTITY_OUS_AND_CHILDREN_ARCH,
|
||||
'ous_and_child_ns': ENTITY_OUS_AND_CHILDREN_NS,
|
||||
'ous_and_child_susp': ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
'ous_and_child_ns_susp': ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
'ous_and_child_na_ns': ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
}
|
||||
# CL entity source selectors
|
||||
@@ -395,9 +427,17 @@ class GamCLArgs():
|
||||
ENTITY_USERS_ARCH,
|
||||
ENTITY_USERS_NS,
|
||||
ENTITY_USERS_SUSP,
|
||||
ENTITY_USERS_NS_SUSP,
|
||||
ENTITY_USERS_ARCH_OR_SUSP,
|
||||
ENTITY_USERS_NA_NS,
|
||||
ENTITY_USERS_NS_SUSP,
|
||||
ENTITY_USERS_AND_GUESTS,
|
||||
ENTITY_USERS_AND_GUESTS_NS,
|
||||
ENTITY_USERS_AND_GUESTS_SUSP,
|
||||
ENTITY_USERS_AND_GUESTS_NS_SUSP,
|
||||
ENTITY_GUESTS,
|
||||
ENTITY_GUESTS_NS,
|
||||
ENTITY_GUESTS_SUSP,
|
||||
ENTITY_GUESTS_NS_SUSP,
|
||||
]
|
||||
#
|
||||
ENTITY_ALL_CROS = ENTITY_SELECTOR_ALL+' '+ENTITY_CROS
|
||||
@@ -406,9 +446,17 @@ class GamCLArgs():
|
||||
ENTITY_ALL_USERS_ARCH = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_ARCH
|
||||
ENTITY_ALL_USERS_NS = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_NS
|
||||
ENTITY_ALL_USERS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_SUSP
|
||||
ENTITY_ALL_USERS_NS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_NS_SUSP
|
||||
ENTITY_ALL_USERS_NA_NS = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_NA_NS
|
||||
ENTITY_ALL_USERS_ARCH_OR_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_ARCH_OR_SUSP
|
||||
ENTITY_ALL_USERS_NS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_NS_SUSP
|
||||
ENTITY_ALL_USERS_AND_GUESTS = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_AND_GUESTS
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_AND_GUESTS_NS
|
||||
ENTITY_ALL_USERS_AND_GUESTS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_AND_GUESTS_SUSP
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_USERS_AND_GUESTS_NS_SUSP
|
||||
ENTITY_ALL_GUESTS = ENTITY_SELECTOR_ALL+' '+ENTITY_GUESTS
|
||||
ENTITY_ALL_GUESTS_NS = ENTITY_SELECTOR_ALL+' '+ENTITY_GUESTS_NS
|
||||
ENTITY_ALL_GUESTS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_GUESTS_SUSP
|
||||
ENTITY_ALL_GUESTS_NS_SUSP = ENTITY_SELECTOR_ALL+' '+ENTITY_GUESTS_NS_SUSP
|
||||
#
|
||||
ALL_USER_ENTITY_TYPES = {
|
||||
ENTITY_ALL_USERS,
|
||||
@@ -416,8 +464,16 @@ class GamCLArgs():
|
||||
ENTITY_ALL_USERS_ARCH,
|
||||
ENTITY_ALL_USERS_NS,
|
||||
ENTITY_ALL_USERS_SUSP,
|
||||
ENTITY_ALL_USERS_NA_NS,
|
||||
ENTITY_ALL_USERS_NS_SUSP,
|
||||
ENTITY_ALL_USERS_NA_NS,
|
||||
ENTITY_ALL_USERS_AND_GUESTS,
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS,
|
||||
ENTITY_ALL_USERS_AND_GUESTS_SUSP,
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS_SUSP,
|
||||
ENTITY_ALL_GUESTS,
|
||||
ENTITY_ALL_GUESTS_NS,
|
||||
ENTITY_ALL_GUESTS_SUSP,
|
||||
ENTITY_ALL_GUESTS_NS_SUSP,
|
||||
}
|
||||
DOMAIN_ENTITY_TYPES = {
|
||||
ENTITY_DOMAINS,
|
||||
@@ -425,6 +481,7 @@ class GamCLArgs():
|
||||
ENTITY_DOMAINS_ARCH,
|
||||
ENTITY_DOMAINS_NS,
|
||||
ENTITY_DOMAINS_SUSP,
|
||||
ENTITY_DOMAINS_NS_SUSP,
|
||||
ENTITY_DOMAINS_NA_NS,
|
||||
}
|
||||
GROUP_ENTITY_TYPES = {
|
||||
@@ -433,6 +490,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUP_ARCH,
|
||||
ENTITY_GROUP_NS,
|
||||
ENTITY_GROUP_SUSP,
|
||||
ENTITY_GROUP_NS_SUSP,
|
||||
ENTITY_GROUP_NA_NS,
|
||||
ENTITY_GROUP_INDE,
|
||||
}
|
||||
@@ -442,6 +500,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUPS_ARCH,
|
||||
ENTITY_GROUPS_NS,
|
||||
ENTITY_GROUPS_SUSP,
|
||||
ENTITY_GROUPS_NS_SUSP,
|
||||
ENTITY_GROUPS_NA_NS,
|
||||
ENTITY_GROUPS_INDE,
|
||||
}
|
||||
@@ -451,6 +510,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUP_USERS_ARCH,
|
||||
ENTITY_GROUP_USERS_NS,
|
||||
ENTITY_GROUP_USERS_SUSP,
|
||||
ENTITY_GROUP_USERS_NS_SUSP,
|
||||
ENTITY_GROUP_USERS_NA_NS,
|
||||
ENTITY_GROUP_USERS_SELECT,
|
||||
}
|
||||
@@ -465,6 +525,8 @@ class GamCLArgs():
|
||||
ENTITY_OU_AND_CHILDREN_NS,
|
||||
ENTITY_OU_SUSP,
|
||||
ENTITY_OU_AND_CHILDREN_SUSP,
|
||||
ENTITY_OU_NS_SUSP,
|
||||
ENTITY_OU_AND_CHILDREN_NS_SUSP,
|
||||
ENTITY_OU_NA_NS,
|
||||
ENTITY_OU_AND_CHILDREN_NA_NS,
|
||||
}
|
||||
@@ -479,6 +541,8 @@ class GamCLArgs():
|
||||
ENTITY_OUS_AND_CHILDREN_NS,
|
||||
ENTITY_OUS_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
ENTITY_OUS_NS_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
ENTITY_OUS_NA_NS,
|
||||
ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
}
|
||||
@@ -493,6 +557,8 @@ class GamCLArgs():
|
||||
ENTITY_OUS_NS,
|
||||
ENTITY_OU_SUSP,
|
||||
ENTITY_OUS_SUSP,
|
||||
ENTITY_OU_NS_SUSP,
|
||||
ENTITY_OUS_NS_SUSP,
|
||||
ENTITY_OU_NA_NS,
|
||||
ENTITY_OUS_NA_NS,
|
||||
}
|
||||
@@ -534,13 +600,21 @@ class GamCLArgs():
|
||||
}
|
||||
#
|
||||
ALL_USERS_QUERY_MAP = {
|
||||
ENTITY_ALL_USERS: 'isSuspended=False',
|
||||
ENTITY_ALL_USERS_NA: 'isArchived=False',
|
||||
ENTITY_ALL_USERS_ARCH: 'isArchived=True',
|
||||
ENTITY_ALL_USERS_NS: 'isSuspended=False',
|
||||
ENTITY_ALL_USERS_SUSP: 'isSuspended=True',
|
||||
ENTITY_ALL_USERS_NA_NS: 'isArchived=False isSuspended=False',
|
||||
ENTITY_ALL_USERS_NS_SUSP: None,
|
||||
ENTITY_ALL_USERS: 'isSuspended=False isGuest=False',
|
||||
ENTITY_ALL_USERS_NA: 'isArchived=False isGuest=False',
|
||||
ENTITY_ALL_USERS_ARCH: 'isArchived=True isGuest=False',
|
||||
ENTITY_ALL_USERS_NS: 'isSuspended=False isGuest=False',
|
||||
ENTITY_ALL_USERS_SUSP: 'isSuspended=True isGuest=False',
|
||||
ENTITY_ALL_USERS_NA_NS: 'isArchived=False isSuspended=False isGuest=False',
|
||||
ENTITY_ALL_USERS_NS_SUSP: 'isGuest=False',
|
||||
ENTITY_ALL_USERS_AND_GUESTS: 'isSuspended=False',
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS: 'isSuspended=False',
|
||||
ENTITY_ALL_USERS_AND_GUESTS_SUSP: 'isSuspended=True',
|
||||
ENTITY_ALL_USERS_AND_GUESTS_NS_SUSP: None,
|
||||
ENTITY_ALL_GUESTS: 'isSuspended=False isGuest=True',
|
||||
ENTITY_ALL_GUESTS_NS: 'isSuspended=False isGuest=True',
|
||||
ENTITY_ALL_GUESTS_SUSP: 'isSuspended=True isGuest=True',
|
||||
ENTITY_ALL_GUESTS_NS_SUSP: 'isGuest=True',
|
||||
}
|
||||
DOMAINS_QUERY_MAP = {
|
||||
ENTITY_DOMAINS: None,
|
||||
@@ -548,6 +622,7 @@ class GamCLArgs():
|
||||
ENTITY_DOMAINS_ARCH: 'isArchived=True',
|
||||
ENTITY_DOMAINS_NS: 'isSuspended=False',
|
||||
ENTITY_DOMAINS_SUSP: 'isSuspended=True',
|
||||
ENTITY_DOMAINS_NS_SUSP: None,
|
||||
ENTITY_DOMAINS_NA_NS: 'isArchived=False isSuspended=False',
|
||||
}
|
||||
GROUPS_QUERY_MAP = { #(isArchived, isSuspended)
|
||||
@@ -559,6 +634,8 @@ class GamCLArgs():
|
||||
ENTITY_GROUPS_NS: (None, False),
|
||||
ENTITY_GROUP_SUSP: (None, True),
|
||||
ENTITY_GROUPS_SUSP: (None, True),
|
||||
ENTITY_GROUP_NS_SUSP: (None, None),
|
||||
ENTITY_GROUPS_NS_SUSP: (None, None),
|
||||
ENTITY_GROUP_NA_NS: (False, False),
|
||||
ENTITY_GROUPS_NA_NS: (False, False),
|
||||
}
|
||||
@@ -567,6 +644,7 @@ class GamCLArgs():
|
||||
ENTITY_GROUP_USERS_ARCH: (True, None),
|
||||
ENTITY_GROUP_USERS_NS: (None, False),
|
||||
ENTITY_GROUP_USERS_SUSP: (None, True),
|
||||
ENTITY_GROUP_USERS_NS_SUSP: (None, None),
|
||||
ENTITY_GROUP_USERS_NA_NS: (False, False),
|
||||
}
|
||||
OU_QUERY_MAP = { #(isArchived, isSuspended)
|
||||
@@ -586,6 +664,10 @@ class GamCLArgs():
|
||||
ENTITY_OUS_SUSP: (None, True),
|
||||
ENTITY_OU_AND_CHILDREN_SUSP: (None, True),
|
||||
ENTITY_OUS_AND_CHILDREN_SUSP: (None, True),
|
||||
ENTITY_OU_NS_SUSP: (None, None),
|
||||
ENTITY_OUS_NS_SUSP: (None, None),
|
||||
ENTITY_OU_AND_CHILDREN_NS_SUSP: (None, None),
|
||||
ENTITY_OUS_AND_CHILDREN_NS_SUSP: (None, None),
|
||||
ENTITY_OU_NA_NS: (False, False),
|
||||
ENTITY_OUS_NA_NS: (False, False),
|
||||
ENTITY_OU_AND_CHILDREN_NA_NS: (False, False),
|
||||
@@ -602,6 +684,14 @@ class GamCLArgs():
|
||||
ENTITY_USERS_NA_NS: ENTITY_ALL_USERS_NA_NS,
|
||||
ENTITY_USERS_ARCH_OR_SUSP: ENTITY_ALL_USERS_ARCH_OR_SUSP,
|
||||
ENTITY_USERS_NS_SUSP: ENTITY_ALL_USERS_NS_SUSP,
|
||||
ENTITY_USERS_AND_GUESTS: ENTITY_ALL_USERS_AND_GUESTS,
|
||||
ENTITY_USERS_AND_GUESTS_NS: ENTITY_ALL_USERS_AND_GUESTS_NS,
|
||||
ENTITY_USERS_AND_GUESTS_SUSP: ENTITY_ALL_USERS_AND_GUESTS_SUSP,
|
||||
ENTITY_USERS_AND_GUESTS_NS_SUSP: ENTITY_ALL_USERS_AND_GUESTS_NS_SUSP,
|
||||
ENTITY_GUESTS: ENTITY_ALL_GUESTS,
|
||||
ENTITY_GUESTS_NS: ENTITY_ALL_GUESTS_NS,
|
||||
ENTITY_GUESTS_SUSP: ENTITY_ALL_GUESTS_SUSP,
|
||||
ENTITY_GUESTS_NS_SUSP: ENTITY_ALL_GUESTS_NS_SUSP,
|
||||
}
|
||||
# Allowed values for CL source selector datafile, csvkmd
|
||||
CROS_ENTITY_SELECTOR_DATAFILE_CSVKMD_SUBTYPES = [
|
||||
@@ -619,6 +709,7 @@ class GamCLArgs():
|
||||
ENTITY_DOMAINS_ARCH,
|
||||
ENTITY_DOMAINS_NS,
|
||||
ENTITY_DOMAINS_SUSP,
|
||||
ENTITY_DOMAINS_NS_SUSP,
|
||||
ENTITY_DOMAINS_NA_NS,
|
||||
ENTITY_GROUPS,
|
||||
ENTITY_GROUPS_INDE,
|
||||
@@ -626,12 +717,14 @@ class GamCLArgs():
|
||||
ENTITY_GROUPS_ARCH,
|
||||
ENTITY_GROUPS_NS,
|
||||
ENTITY_GROUPS_SUSP,
|
||||
ENTITY_GROUPS_NS_SUSP,
|
||||
ENTITY_GROUPS_NA_NS,
|
||||
ENTITY_GROUP_USERS,
|
||||
ENTITY_GROUP_USERS_NA,
|
||||
ENTITY_GROUP_USERS_ARCH,
|
||||
ENTITY_GROUP_USERS_NS,
|
||||
ENTITY_GROUP_USERS_SUSP,
|
||||
ENTITY_GROUP_USERS_NS_SUSP,
|
||||
ENTITY_GROUP_USERS_NA_NS,
|
||||
ENTITY_GROUP_USERS_SELECT,
|
||||
ENTITY_OUS,
|
||||
@@ -639,12 +732,14 @@ class GamCLArgs():
|
||||
ENTITY_OUS_ARCH,
|
||||
ENTITY_OUS_NS,
|
||||
ENTITY_OUS_SUSP,
|
||||
ENTITY_OUS_NS_SUSP,
|
||||
ENTITY_OUS_NA_NS,
|
||||
ENTITY_OUS_AND_CHILDREN,
|
||||
ENTITY_OUS_AND_CHILDREN_NA,
|
||||
ENTITY_OUS_AND_CHILDREN_ARCH,
|
||||
ENTITY_OUS_AND_CHILDREN_NS,
|
||||
ENTITY_OUS_AND_CHILDREN_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_NS_SUSP,
|
||||
ENTITY_OUS_AND_CHILDREN_NA_NS,
|
||||
ENTITY_COURSEPARTICIPANTS,
|
||||
ENTITY_STUDENTS,
|
||||
|
||||
@@ -474,6 +474,7 @@ REASON_ONLY_VALID_WITH_CONTENTRESTRICTIONS_READONLY_TRUE = 'reason only valid wi
|
||||
REAUTHENTICATION_IS_NEEDED = 'Reauthentication is needed, please run\n\ngam oauth create'
|
||||
RECOMMEND_RUNNING_GAM_ROTATE_SAKEY = 'Recommend running "gam rotate sakey" to get a new key\n'
|
||||
REFUSING_TO_DEPROVISION_DEVICES = 'Refusing to deprovision {0} devices because acknowledge_device_touch_requirement not specified.\nDeprovisioning a device means the device will have to be physically wiped and re-enrolled to be managed by your domain again.\nThis requires physical access to the device and is very time consuming to perform for each device.\nPlease add "acknowledge_device_touch_requirement" to the GAM command if you understand this and wish to proceed with the deprovision.\nPlease also be aware that deprovisioning can have an effect on your device license count.\nSee https://support.google.com/chrome/a/answer/3523633 for full details.'
|
||||
REFUSING_TO_DEPROVISION_N_DEVICES = 'Refusing to deprovision {0} devices due to maxtodepov {1}.'
|
||||
REPLY_TO_CUSTOM_REQUIRES_EMAIL_ADDRESS = 'replyto REPLY_TO_CUSTOM requires customReplyTo <EmailAddress>'
|
||||
REQUEST_COMPLETED_NO_FILES = 'Request completed but no results/files were returned, try requesting again'
|
||||
REQUEST_NOT_COMPLETE = 'Request needs to be completed before downloading, current status is: {0}'
|
||||
|
||||
@@ -117,6 +117,8 @@ PROPERTIES = {
|
||||
{CLASS: PC_STRING, TITLE: 'Full Name',},
|
||||
'displayName':
|
||||
{CLASS: PC_STRING, TITLE: 'Display Name',},
|
||||
'primaryGuestEmail':
|
||||
{CLASS: PC_STRING, TITLE: 'Primary Guest Email',},
|
||||
'languages':
|
||||
{CLASS: PC_LANGUAGES, TITLE: 'Languages',},
|
||||
'languageCode':
|
||||
@@ -131,6 +133,8 @@ PROPERTIES = {
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is a Super Admin',},
|
||||
'isDelegatedAdmin':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is Delegated Admin',},
|
||||
'isGuestUser':
|
||||
{CLASS: PC_BOOLEAN, TITLE: 'Is a Guest User',},
|
||||
'isEnrolledIn2Sv':
|
||||
{CLASS: PC_BOOLEAN, TITLE: '2-step enrolled',},
|
||||
'isEnforcedIn2Sv':
|
||||
|
||||
@@ -315,6 +315,9 @@ You can optionally specify the type of service account key with `algorithm|local
|
||||
Use `nokey` if you do not want a service account key created for the project.
|
||||
|
||||
## Use an existing project for GAM authorization
|
||||
|
||||
To use the same GAM project on multiple computers, see: [GAM Configuration](Multiple-Computers)
|
||||
|
||||
Use an existing project to create and download two files: `client_secrets.json` for the Client and `oauth2service.json` for the Service Account.
|
||||
|
||||
### Default values
|
||||
|
||||
@@ -3,9 +3,19 @@
|
||||
- [Notes](#notes)
|
||||
- [Definitions](#definitions)
|
||||
- [User Type Entity](#user-type-entity)
|
||||
- [All non-archived Users](#all-non-archived-users)
|
||||
- [All archived Users](#all-archived-Users)
|
||||
- [All non-suspended Users](#all-non-suspended-users)
|
||||
- [All suspended Users](#all-suspended-Users)
|
||||
- [All archived or suspended Users](#all-archived-or-suspended-users)
|
||||
- [All non-archived and non-suspended Users](#all-non-archived-and-non-suspended-users)
|
||||
- [All non-suspended and suspended Users](#all-non-suspended-and-suspended-users)
|
||||
- [All non-suspended Guests](#all-non-suspended-guests)
|
||||
- [All suspended Guests](#all-suspended-Guests)
|
||||
- [All non-suspended and suspended Guests](#all-non-suspended-and-suspended-guests)
|
||||
- [All non-suspended Users and Guests](#all-non-suspended-users-and-guests)
|
||||
- [All suspended Users and Guests](#all-suspended-users-and-guests)
|
||||
- [All non-suspended and suspended Users and Guests](#all-non-suspended-and-suspended-users-and-guests)
|
||||
- [A single User](#a-single-user)
|
||||
- [A list of Users](#a-list-of-users)
|
||||
- [The admin user referenced in oauth2.txt](#the-admin-user-referenced-in-oauth2txt)
|
||||
@@ -40,6 +50,18 @@
|
||||
|
||||
## Notes
|
||||
|
||||
The following items referencing guest users were added to `<UserTypeEntity>` in version 7.43.00.
|
||||
```
|
||||
all_guests
|
||||
all_guests_ns
|
||||
all_guests_susp
|
||||
all_guests_ns_susp
|
||||
all_users_and_guests
|
||||
all_users_and_guests_ns
|
||||
all_users_and_guests_susp
|
||||
all_users_and_guests_ns_susp
|
||||
```
|
||||
|
||||
The followig items referencing non-archived/archived users were added to `<UserTypeEntity>` in version 7.22.00.
|
||||
```
|
||||
all users_na
|
||||
@@ -123,6 +145,8 @@ ous_and_children_na_ns
|
||||
|
||||
<UserTypeEntity> ::=
|
||||
(all users|users_na|users_arch|users_ns|users_susp|users_ns_susp|users_arch_or_susp|users_na_ns)|
|
||||
(all guests|guests_ns|guests_susp|guests_ns_susp)|
|
||||
(all users_and_guests|users_and_guests_ns|users_and_guests_susp|users_and_guests_ns_susp)|
|
||||
(user <UserItem>)|
|
||||
(users <UserList>)|
|
||||
(oauthuser)
|
||||
@@ -232,6 +256,26 @@ Use these options to select users for GAM commands.
|
||||
## All non-suspended and suspended Users
|
||||
* `all users_ns_susp`
|
||||
|
||||
## All non-suspended Guests
|
||||
* `all guests`
|
||||
* `all guests_ns`
|
||||
|
||||
## All suspended Guests
|
||||
* `all guests_susp`
|
||||
|
||||
## All non-suspended and suspended Guests
|
||||
* `all guests_ns_susp`
|
||||
|
||||
## All non-suspended Users and Guests
|
||||
* `all users_and_guests`
|
||||
* `all users_and_guests_ns`
|
||||
|
||||
## All suspended Users and Guests
|
||||
* `all users_and_guests_susp`
|
||||
|
||||
## All non-suspended and suspended Users and Guests
|
||||
* `all users_and_guests_ns_susp`
|
||||
|
||||
## A single User
|
||||
* `user <UserItem>`
|
||||
|
||||
|
||||
@@ -10,6 +10,55 @@ 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.43.04
|
||||
|
||||
Added option `include_suspended_zeros [<Boolean>]` to `gam print vaultcounts` that causes
|
||||
GAM to generate zero count lines for suspended users with zero items as well as non-suspended users.
|
||||
|
||||
### 7.43.03
|
||||
|
||||
Added option `parentpathonly [<Boolean>]` to the following commands that causes GAM
|
||||
to display only the parent folder names when displaying the path to a file.
|
||||
```
|
||||
gam <UserTypeEntity> info drivefile ... filepath|fullpath
|
||||
gam <UserTypeEntity> show fileinfo ... filepath|fullpath
|
||||
gam <UserTypeEntity> print|show filepath
|
||||
gam <UserTypeEntity> print filelist ... filepath|fullpath
|
||||
```
|
||||
|
||||
### 7.43.02
|
||||
|
||||
Added option `maxactivities <Number>` to `gam <UserTypeEntity> print driveactivity` to limit
|
||||
the number of activities displayed; the default is 0, no limit.
|
||||
|
||||
### 7.43.01
|
||||
|
||||
Updated `gam info user` and `gam print users` to display guest user attributes: `isGuestUser, guestAccountInfo`
|
||||
|
||||
Expanded `<UserTypeEntity>` to allow specification of guest users.
|
||||
* See [Collections of Users](Collections-of-Users)
|
||||
|
||||
### 7.42.00
|
||||
|
||||
In versions prior to 7.42.00, when `redirect csv <FileName>` was used, GAM did not open and write `<FileName>`
|
||||
until all processing was complete; if `<FileName>` was not accessible, an error was generated
|
||||
and no results were saved. Now, `<FileName>` is opened initially to verify accessiblity
|
||||
and then written when processing is complete.
|
||||
|
||||
In the unlikely event that this causes issues, you can do `redirect csv <FileName> delayopen`
|
||||
to get the previous behavior.
|
||||
|
||||
### 7.41.03
|
||||
|
||||
Fixed bug in the following:
|
||||
Added the following to `<RowValueFilter>` used in CSV input/output row filtering; these are
|
||||
synonyms for `count` and `countrange`.
|
||||
```
|
||||
[(any|all):]number<Operator><Number>|
|
||||
[(any|all):]numberrange!=<Number>/<Number>|
|
||||
[(any|all):]numberrange=<Number>/<Number>|
|
||||
```
|
||||
|
||||
### 7.41.02
|
||||
|
||||
Added option `ownername` to `gam info|print courses` to have GAM display the course owners full name;
|
||||
|
||||
@@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
|
||||
gamteam@server:/Users/gamteam$ gam version
|
||||
GAM 7.41.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
@@ -1034,7 +1034,7 @@ writes the credentials into the file oauth2.txt.
|
||||
```
|
||||
C:\>del C:\GAMConfig\oauth2.txt
|
||||
C:\>gam version
|
||||
GAM 7.41.02 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM - pythonsource
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
Windows 11 10.0.26200 AMD64
|
||||
|
||||
@@ -119,7 +119,7 @@ You can redirect CSV file output and stdout/stderr output to files. By using red
|
||||
You can redirect stdout and stderr to null and stderr can be redirected to stdout.
|
||||
```
|
||||
<Redirect> ::=
|
||||
redirect csv <FileName> [multiprocess] [append] [noheader] [charset <Charset>]
|
||||
redirect csv <FileName> [delayopen] [multiprocess] [append] [noheader] [charset <Charset>]
|
||||
[columndelimiter <Character>] [quotechar <Character>] [noescapechar [<Boolean>]]
|
||||
[sortheaders <StringList>] [timestampcolumn <String>] [transpose [<Boolean>]]
|
||||
[todrive <ToDriveAttribute>*] |
|
||||
@@ -129,12 +129,20 @@ You can redirect stdout and stderr to null and stderr can be redirected to stdou
|
||||
redirect stderr null [multiprocess] |
|
||||
redirect stderr stdout [multiprocess]
|
||||
```
|
||||
For `redirect`, the optional subarguments must appear in the order shown.
|
||||
In versions prior to 7.42.00`, the `redirect csv` optional subarguments had to be specified in the order shown.
|
||||
Now the arguments can be specified in any order except that `todrive <ToDriveAttribute>*` must still be last.
|
||||
|
||||
If `<FileName>` specifies a relative path, the file will be put in the directory specified by `drive_dir` in gam.cfg.
|
||||
If `<FileName>` specifies an absolute path, the file will be put in the directory specified.
|
||||
Specify `./<FileName>` to put the file in your current working directory.
|
||||
|
||||
In versions prior to 7.42.00, when `redirect csv <FileName>` was used, GAM did not open and write `<FileName>`
|
||||
until all processing was complete; if `<FileName>` was not accessible, an error was generated and no results were saved.
|
||||
Now, `<FileName>` is opened initially to verify accessiblity and then written when processing is complete.
|
||||
|
||||
In the unlikely event that this causes issues, you can do `redirect csv <FileName> delayopen`
|
||||
to get the previous behavior.
|
||||
|
||||
The `multiprocess` subargument allows the multiple subprocesses started by `gam csv` to write intelligently
|
||||
to a single redirected CSV/stdout/stderr file. If you don't specify `multiprocess`, each subprocess
|
||||
writes `<FileName>` independently; you end up with a single file written by the last subprocess.
|
||||
|
||||
@@ -72,7 +72,7 @@ gam <UserTypeEntity> print driveactivity [todrive <ToDriveAttributes>*]
|
||||
(query <QueryDriveFile>)]
|
||||
[([start|starttime <Date>|<Time>] [end|endtime <Date>|<Time>])|(range <Date>|<Time> <Date)|<Time>|
|
||||
yesterday|today|thismonth|(previousmonths <Integer>)]
|
||||
[action|actions [not] <DriveActivityActionList>]
|
||||
[action|actions [not] <DriveActivityActionList>] [maxactivities <Number>]
|
||||
[consolidationstrategy legacy|none]
|
||||
[idmapfile <FileName>|(gsheet <UserGoogleSheet>) [charset <String>] [columndelimiter <Character>] [noescapechar <Boolean>] [quotechar <Character>]]
|
||||
[stripcrsfromname] [formatjson [quotechar <Character>]]
|
||||
@@ -99,6 +99,8 @@ Google does the filtering.
|
||||
* `action|actions <DriveActivityActionList>` - Only display activities with the specified actions; by default, all actions are displayed
|
||||
* `action|actions not <DriveActivityActionList>` - Only display activities without the specified actions; by default, all actions are displayed
|
||||
|
||||
You can limit the number of activities displayed with the `maxactivities <Number>; the default is 0, no limit.
|
||||
|
||||
The API only returns a permissionId and user name for each event but no user email address. To get an email address perform the
|
||||
following command to generate a file that contains the email address and permissionId for all users. You can substitute for `all users` if desired.
|
||||
```
|
||||
|
||||
@@ -528,7 +528,7 @@ There are two methods for moving a folder from a My Drive to a Shared Drive:
|
||||
* GAM
|
||||
* The Drive API doesn't allow moving a folder from a My Drive to a Shared Drive; GAM has to recreate the folders on the Shared Drive, thus changing their IDs
|
||||
* Files are simply moved from their existing My Drive folder to the recreated Shared Drive folder; their IDs do not change
|
||||
* Files owmed by users outside of your domain can't be moved
|
||||
* Files owned by users outside of your domain can't be moved
|
||||
|
||||
## Simple moves by changing parents
|
||||
Use this command in the following cases:
|
||||
|
||||
@@ -425,7 +425,7 @@ Display file details in indented keyword: value format. The two forms are equiva
|
||||
```
|
||||
gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
@@ -436,7 +436,7 @@ gam <UserTypeEntity> show fileinfo <DriveFileEntity>
|
||||
[formatjson]
|
||||
gam <UserTypeEntity> info drivefile <DriveFileEntity>
|
||||
[returnidonly]
|
||||
[filepath|fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[filepath|fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
@@ -455,6 +455,7 @@ Use `fullpath` to add additional path information indicating that a file is an O
|
||||
By default, the path to a file includes the file name as the last element of the path.
|
||||
Use `folderpathonly` to display only the folder names when displaying the path to a file. This folder only path
|
||||
an be used in `gam <UserTypeEntity> create drivefolderpath` to recreate the folder hierarchy.
|
||||
Use `parentpathonly` to display only the parent folder names when displaying the path to a file.
|
||||
|
||||
By default, file path components are separated by `/`; use `pathdelimiter <Character>` to use `<Character>` as the separator.
|
||||
|
||||
@@ -517,12 +518,12 @@ gam <UserTypeEntity> show filepath <DriveFileEntity>
|
||||
[returnpathonly]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[stripcrsfromname]
|
||||
[folderpathonly [<Boolean>]] [fullpath] [pathdelimiter <Character>]
|
||||
[folderpathonly|parentpathonly [<Boolean>]] [fullpath] [pathdelimiter <Character>]
|
||||
[followshortcuts [<Boolean>]]
|
||||
gam <UserTypeEntity> print filepath <DriveFileEntity> [todrive <ToDriveAttribute>*]
|
||||
(orderby <DriveFileOrderByFieldName> [ascending|descending])*
|
||||
[stripcrsfromname] [oneitemperrow]
|
||||
[fullpath] [folderpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[fullpath] [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>]
|
||||
[followshortcuts [<Boolean>]]
|
||||
```
|
||||
Use `returnpathonly` to display just the file path of the files in `<DriveFileEntity>`.
|
||||
@@ -532,6 +533,7 @@ Use `fullpath` to add additional path information indicating that a file is an O
|
||||
By default, the path to a file includes the file name as the last element of the path.
|
||||
Use `folderpathonly` to display only the folder names when displaying the path to a file. This folder only path
|
||||
an be used in `gam <UserTypeEntity> create drivefolderpath` to recreate the folder hierarchy.
|
||||
Use `parentpathonly` to display only the parent folder names when displaying the path to a file.
|
||||
|
||||
By default, file path components are separated by `/`; use `pathdelimiter <Character>` to use `<Character>` as the separator.
|
||||
|
||||
@@ -1106,7 +1108,7 @@ gam <UserTypeEntity> print|show filelist [todrive <ToDriveAttribute>*]
|
||||
[countsonly [summary none|only|plus] [summaryuser <String>]
|
||||
[showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||
[countsrowfilter]
|
||||
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
[filepath|fullpath [folderpathonly|parentpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||
[showdrivename] [showshareddrivepermissions]
|
||||
[(showlabels details|ids)|(includelabels <ClassificationLabelIDList>)]
|
||||
@@ -1239,6 +1241,7 @@ JSON data rather than as additional columns
|
||||
By default, the path to a file includes the file name as the last element of the path.
|
||||
Use `folderpathonly` to display only the folder names when displaying the path to a file. This folder only path
|
||||
an be used in `gam <UserTypeEntity> create drivefolderpath` to recreate the folder hierarchy.
|
||||
Use `parentpathonly` to display only the parent folder names when displaying the path to a file.
|
||||
|
||||
By default, file path components are separated by `/`; use `pathdelimiter <Character>` to use `<Character>` as the separator.
|
||||
|
||||
|
||||
@@ -91,8 +91,8 @@ gam <UserItem> show meetconferences
|
||||
[formatjson]
|
||||
```
|
||||
By default, conferences are shown for all of a user's meet spaces. To limit the display use:
|
||||
* `space <MeetSpaceName>` - Display conferences for a specifc space by giving its name
|
||||
* `code <String>` - Display conferences for a specifc space by giving its code
|
||||
* `space <MeetSpaceName>` - Display conferences for a specific space by giving its name
|
||||
* `code <String>` - Display conferences for a specific space by giving its code
|
||||
|
||||
By default, Gam displays the information about the meet conferences as an indented list of keys and values.
|
||||
* `formatjson` - Display the fields in JSON format.
|
||||
@@ -103,8 +103,8 @@ gam <UserItem> print meetconferences [todrive <ToDriveAttribute>*]
|
||||
[formatjson [quotechar <Character>]]
|
||||
```
|
||||
By default, conferences are shown for all of a user's meet spaces. To limit the display use:
|
||||
* `space <MeetSpaceName>` - Display conferences for a specifc space by giving its name
|
||||
* `code <String>` - Display conferences for a specifc space by giving its code
|
||||
* `space <MeetSpaceName>` - Display conferences for a specific space by giving its name
|
||||
* `code <String>` - Display conferences for a specific space by giving its code
|
||||
|
||||
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.
|
||||
|
||||
@@ -257,11 +257,17 @@ gam print vaultcounts [todrive <ToDriveAttributes>*]
|
||||
[excludedrafts <Boolean>]
|
||||
[<JSONData>]
|
||||
[wait <Integer>]
|
||||
[include_suspended_zeros [<Boolean>]]
|
||||
```
|
||||
Specify the search method, this is optional:
|
||||
* `accounts <EmailAddressEntity>` - Search all accounts specified in `<EmailAddressEntity>`
|
||||
* `orgunit|org|ou <OrgUnitPath>` - Search all accounts in the OU `<OrgUnitPath>`
|
||||
* `everyone|entireorg` - Search for all accounts in the organization
|
||||
* `everyone|entireorg` - Search all accounts in the organization
|
||||
|
||||
By default, the Vault API doesn't return accounts with zero items; GAM
|
||||
generates a zero count line for non-suspended accounts with zero items.
|
||||
The `include_suspended_zeros` option causes GAM to generate a zero count line
|
||||
for suspended accounts with zero items.
|
||||
|
||||
For `corpus mail|group`, you can specify search terms to limit the search.
|
||||
* `terms <String>` - [Vault search](https://support.google.com/vault/answer/2474474)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Print the current version of Gam with details
|
||||
```
|
||||
gam version
|
||||
GAM 7.41.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
@@ -15,7 +15,7 @@ Time: 2026-02-15T07:51:00-08:00
|
||||
Print the current version of Gam with details and time offset information
|
||||
```
|
||||
gam version timeoffset
|
||||
GAM 7.41.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
@@ -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.41.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM - pyinstaller
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
||||
Path: /Users/gamteam/bin/gam7
|
||||
Version Check:
|
||||
Current: 5.35.08
|
||||
Latest: 7.41.02
|
||||
Latest: 7.43.04
|
||||
echo $?
|
||||
1
|
||||
```
|
||||
@@ -76,7 +76,7 @@ echo $?
|
||||
Print the current version number without details
|
||||
```
|
||||
gam version simple
|
||||
7.41.02
|
||||
7.43.04
|
||||
```
|
||||
In Linux/MacOS you can do:
|
||||
```
|
||||
@@ -86,7 +86,7 @@ echo $VER
|
||||
Print the current version of Gam and address of this Wiki
|
||||
```
|
||||
gam help
|
||||
GAM 7.41.02 - https://github.com/GAM-team/GAM
|
||||
GAM 7.43.04 - https://github.com/GAM-team/GAM
|
||||
GAM Team <google-apps-manager@googlegroups.com>
|
||||
Python 3.14.4 64-bit final
|
||||
macOS Tahoe 26.4.1 arm64
|
||||
|
||||
Reference in New Issue
Block a user