mirror of
https://github.com/GAM-team/GAM.git
synced 2026-06-28 09:51:36 +00:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28b909a686 | ||
|
|
ce73c62e81 | ||
|
|
8e71e18aaa | ||
|
|
d78506fa9b | ||
|
|
a2b226a124 | ||
|
|
8412ba78b2 | ||
|
|
9d01e3fa27 | ||
|
|
9a981bf02e | ||
|
|
12667bf898 | ||
|
|
7e664b437d | ||
|
|
ad3d4e9a57 | ||
|
|
4f992f4d41 | ||
|
|
a987ff4eac | ||
|
|
05fd22565d | ||
|
|
8470949e82 | ||
|
|
f56bf838ad | ||
|
|
33d808ff4b | ||
|
|
35fde0e48b | ||
|
|
7c5e79f309 | ||
|
|
71bac7c2eb | ||
|
|
539b91a27e | ||
|
|
d71bf9b6cb | ||
|
|
0c2fdeee6b | ||
|
|
33df3132b8 | ||
|
|
118e1e3ff5 | ||
|
|
2fdab61fc8 | ||
|
|
a883bf721f | ||
|
|
ca20bcbda0 | ||
|
|
a927dda9aa | ||
|
|
a2d80cac46 | ||
|
|
fcf2712f3f | ||
|
|
8dc2b4bd64 | ||
|
|
679019fc3c | ||
|
|
c4da8110a3 | ||
|
|
7bcd0611c2 | ||
|
|
485cbb65bb | ||
|
|
29a2e224bc | ||
|
|
ac25dd6557 | ||
|
|
673effa9f8 | ||
|
|
a936a5fa3d | ||
|
|
a1b073cbdd | ||
|
|
093015c617 | ||
|
|
2bee44f7e9 | ||
|
|
645155a2ea | ||
|
|
da2298ae23 | ||
|
|
02188e9c49 | ||
|
|
28b5ab34c3 | ||
|
|
b338e2da2f | ||
|
|
b1af32e487 | ||
|
|
a32cc146ef | ||
|
|
1ed1d8552b | ||
|
|
4f58f7c967 | ||
|
|
c41b94487e | ||
|
|
7f95020e6f | ||
|
|
9181a35c10 | ||
|
|
c95997e2d5 | ||
|
|
9348f57141 | ||
|
|
dab6272d55 | ||
|
|
f548d49e19 | ||
|
|
081965fa79 | ||
|
|
bcef6f9391 | ||
|
|
dfb4c88b69 | ||
|
|
f10fca04c9 | ||
|
|
096eef3f59 | ||
|
|
f9cd2f56d6 | ||
|
|
91559239ca | ||
|
|
a00256ee9f | ||
|
|
970697ec65 | ||
|
|
06d131ec55 | ||
|
|
094616e482 | ||
|
|
cbfae9226a | ||
|
|
c35cdcf4c3 | ||
|
|
d87ece177d | ||
|
|
41535666b6 | ||
|
|
20d1b18009 | ||
|
|
c27e48dd5c | ||
|
|
44fe8a22e0 | ||
|
|
21df315887 | ||
|
|
3a7c470e6e | ||
|
|
22feec5136 | ||
|
|
a30f8c325f | ||
|
|
0770c20992 | ||
|
|
cc7fb0df7b | ||
|
|
fec061c250 | ||
|
|
aef3b23061 | ||
|
|
3518fc8ad3 | ||
|
|
e5dab74336 | ||
|
|
3aef51781e | ||
|
|
23deb2526c |
133
.github/workflows/build.yml
vendored
133
.github/workflows/build.yml
vendored
@@ -126,15 +126,21 @@ jobs:
|
|||||||
name: Test Python 3.12
|
name: Test Python 3.12
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
goal: test
|
goal: test
|
||||||
python: "3.15-dev"
|
python: "3.13"
|
||||||
freethreaded: false
|
freethreaded: false
|
||||||
jid: 18
|
jid: 18
|
||||||
|
name: Test Python 3.13
|
||||||
|
- os: ubuntu-24.04
|
||||||
|
goal: test
|
||||||
|
python: "3.15-dev"
|
||||||
|
freethreaded: false
|
||||||
|
jid: 19
|
||||||
name: Test Python 3.15-dev
|
name: Test Python 3.15-dev
|
||||||
- os: ubuntu-24.04
|
- os: ubuntu-24.04
|
||||||
goal: test
|
goal: test
|
||||||
python: "3.14"
|
python: "3.14"
|
||||||
freethreaded: true
|
freethreaded: true
|
||||||
jid: 19
|
jid: 20
|
||||||
name: Test Python 3.14 freethread
|
name: Test Python 3.14 freethread
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -256,7 +262,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
p12-file-base64: ${{ secrets.CERTIFICATES_P12 }}
|
||||||
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
|
||||||
|
|
||||||
- name: Windows Configure VCode
|
- name: Windows Configure VCode
|
||||||
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
|
||||||
if: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
if: runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||||
@@ -296,8 +302,9 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
PYEXTERNALS_PATH=$(cygpath -u "${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_ARCH}")
|
PYEXTERNALS_PATH=$(cygpath -u "${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_ARCH}")
|
||||||
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PYEXTERNALS_PATH}"
|
LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${PYEXTERNALS_PATH}"
|
||||||
echo "PYTHON=${PYTHON_SOURCE_PATH}/PCbuild/${PYEXTERNALS_ARCH}/python.exe" >> $GITHUB_ENV
|
echo "PYTHON=${PYTHON_INSTALL_PATH}\python.exe" >> $GITHUB_ENV
|
||||||
echo "WIX_ARCH=${WIX_ARCH}" >> $GITHUB_ENV
|
echo "WIX_ARCH=${WIX_ARCH}" >> $GITHUB_ENV
|
||||||
|
echo "PS_PYTHON_INSTALL_PATH=$(cygpath -w $PYTHON_INSTALL_PATH)" >> $GITHUB_ENV
|
||||||
fi
|
fi
|
||||||
echo "We'll run make with: ${MAKEOPT}"
|
echo "We'll run make with: ${MAKEOPT}"
|
||||||
echo "staticx=${staticx}" >> $GITHUB_ENV
|
echo "staticx=${staticx}" >> $GITHUB_ENV
|
||||||
@@ -440,7 +447,7 @@ jobs:
|
|||||||
pip install --upgrade sphinx
|
pip install --upgrade sphinx
|
||||||
sphinx-build --version
|
sphinx-build --version
|
||||||
|
|
||||||
- name: Windows Config/Build Python
|
- name: Windows Config/Build/Install Python
|
||||||
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
if: matrix.goal == 'build' && runner.os == 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
run: |
|
run: |
|
||||||
@@ -449,13 +456,16 @@ jobs:
|
|||||||
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\ -Verbose
|
Copy-Item -Path "${env:GITHUB_WORKSPACE}\src\tools\openssl.props" -Destination PCBuild\ -Verbose
|
||||||
if (${env:RUNNER_ARCH} -eq "X64") {
|
if (${env:RUNNER_ARCH} -eq "X64") {
|
||||||
$env:arch = "x64"
|
$env:arch = "x64"
|
||||||
PCBuild\build.bat -c Release -p $env:arch --pgo
|
#PCBuild\build.bat -c Release -p $env:arch --pgo
|
||||||
} elseif (${env:RUNNER_ARCH} -eq "ARM64") {
|
} elseif (${env:RUNNER_ARCH} -eq "ARM64") {
|
||||||
$env:arch = "ARM64"
|
$env:arch = "ARM64"
|
||||||
# TODO: figure out why Windows ARM64 isn't compat with PGO optimiazation
|
# TODO: figure out why Windows ARM64 isn't compat with PGO optimiazation
|
||||||
# causes 10-20% slowdown in Python
|
# causes 10-20% slowdown in Python
|
||||||
PCBuild\build.bat -c Release -p $env:arch
|
#PCBuild\build.bat -c Release -p $env:arch
|
||||||
}
|
}
|
||||||
|
PCBuild\build.bat -c Release -p $env:arch --pgo
|
||||||
|
.\python.bat PC\layout --precompile --preset-default --copy $env:PS_PYTHON_INSTALL_PATH
|
||||||
|
Get-ChildItem -Path $env:PS_PYTHON_INSTALL_PATH -File
|
||||||
|
|
||||||
- name: Mac/Linux Build Python
|
- name: Mac/Linux Build Python
|
||||||
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
if: matrix.goal == 'build' && runner.os != 'Windows' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||||
@@ -661,28 +671,27 @@ jobs:
|
|||||||
echo "GAM Version ${GAMVERSION}"
|
echo "GAM Version ${GAMVERSION}"
|
||||||
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
|
echo "GAMVERSION=${GAMVERSION}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Install WinAppDriver
|
- name: Initialize Windows Desktop Shell
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
shell: pwsh
|
||||||
|
run: |
|
||||||
|
Write-Host "Checking for Windows Explorer shell..."
|
||||||
|
if (-not (Get-Process -Name explorer -ErrorAction SilentlyContinue)) {
|
||||||
|
Write-Host "Explorer not found. Booting the desktop shell..."
|
||||||
|
Start-Process explorer.exe
|
||||||
|
# Give the desktop a few seconds to fully render the taskbar
|
||||||
|
Start-Sleep -Seconds 10
|
||||||
|
} else {
|
||||||
|
Write-Host "Explorer is already running."
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Install NPM deps
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
run: |
|
run: |
|
||||||
choco install -y winappdriver
|
#echo "Installing appium..."
|
||||||
|
#npm install -g appium
|
||||||
- name: Enabled dev mode for WinAppDriver
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
shell: cmd
|
|
||||||
run : |
|
|
||||||
reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"
|
|
||||||
|
|
||||||
- name: Install appium and totp tools
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
run: |
|
|
||||||
echo "Installing appium..."
|
|
||||||
npm install -g appium
|
|
||||||
echo "Installing totp-generator..."
|
echo "Installing totp-generator..."
|
||||||
npm install "totp-generator"
|
npm install totp-generator
|
||||||
echo "Installing wdio..."
|
|
||||||
npm install @wdio/cli
|
|
||||||
echo "Installing appium win driver..."
|
|
||||||
appium driver install windows
|
|
||||||
|
|
||||||
- name: Install Certum MSI
|
- name: Install Certum MSI
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
@@ -704,18 +713,21 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
TOTP_SECRET: ${{ secrets.TOTP_SECRET }}
|
TOTP_SECRET: ${{ secrets.TOTP_SECRET }}
|
||||||
run: |
|
run: |
|
||||||
# disable win private firewall that interferes with appium server
|
|
||||||
Set-NetFirewallProfile -Profile Private -Enabled False
|
|
||||||
$appiumCmd = Get-Command appium
|
|
||||||
$appiumPath = $appiumCmd.Path
|
|
||||||
Start-Process -Filepath "powershell.exe" -ArgumentList "-File", $appiumPath, "--address", "127.0.0.1", "--log-level", "error"
|
|
||||||
Start-Sleep -Seconds 10
|
|
||||||
write-host "appium started"
|
|
||||||
write-host "running SimplySignDesktop login..."
|
write-host "running SimplySignDesktop login..."
|
||||||
node tools/ssd.mjs --log-level warn
|
node tools/ssd.mjs --log-level warn
|
||||||
write-host "sleeping during login..."
|
write-host "sleeping during login..."
|
||||||
Start-Sleep 10
|
Start-Sleep 10
|
||||||
|
|
||||||
|
- name: Archive png 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: |
|
||||||
|
*.png
|
||||||
|
|
||||||
- name: Sign gam.exe
|
- name: Sign gam.exe
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
@@ -758,11 +770,6 @@ jobs:
|
|||||||
echo "GAM Archive ${GAM_ARCHIVE}"
|
echo "GAM Archive ${GAM_ARCHIVE}"
|
||||||
tar -C "${gampath}/.." --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam7
|
tar -C "${gampath}/.." --create --verbose --exclude-from "${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" --file $GAM_ARCHIVE --xz gam7
|
||||||
|
|
||||||
- name: Install Wix on Win ARM64
|
|
||||||
if: runner.os == 'Windows' && runner.arch == 'ARM64'
|
|
||||||
run: |
|
|
||||||
choco install wixtoolset
|
|
||||||
|
|
||||||
- name: Windows package zip
|
- name: Windows package zip
|
||||||
if: runner.os == 'Windows' && matrix.goal != 'test'
|
if: runner.os == 'Windows' && matrix.goal != 'test'
|
||||||
run: |
|
run: |
|
||||||
@@ -772,33 +779,15 @@ jobs:
|
|||||||
GAM_ARCHIVE="${GITHUB_WORKSPACE}/gam-${GAMVERSION}-windows-${arch}.zip"
|
GAM_ARCHIVE="${GITHUB_WORKSPACE}/gam-${GAMVERSION}-windows-${arch}.zip"
|
||||||
/c/Program\ Files/7-Zip/7z.exe a -tzip "$GAM_ARCHIVE" gam7 "-xr@${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" -bb3
|
/c/Program\ Files/7-Zip/7z.exe a -tzip "$GAM_ARCHIVE" gam7 "-xr@${GITHUB_WORKSPACE}/.github/actions/package_exclusions.txt" -bb3
|
||||||
|
|
||||||
- name: Windows package MSI
|
- name: Windows package exe with Inno Setup
|
||||||
if: runner.os == 'Windows' && matrix.goal != 'test'
|
if: runner.os == 'Windows' && matrix.goal != 'test'
|
||||||
run: |
|
run: |
|
||||||
export MSI_FILENAME="${GITHUB_WORKSPACE}/gam-${GAMVERSION}-windows-${arch}.msi"
|
choco install innosetup
|
||||||
# auto-generate a lib.wxs based on the files PyInstaller created for the lib/ directory
|
export signtool="C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe"
|
||||||
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/heat.exe dir "${gampath}/lib" -ke -srd -cg Lib -gg -dr lib -directoryid lib -out lib.wxs
|
iscc \
|
||||||
$PYTHON tools/gen-wix-xml-filelist.py lib.wxs
|
/S"gamsigntool=$signtool sign /sha1 $WINDOWS_CODESIGN_CERT_HASH /tr http://time.certum.pl /td SHA256 /fd SHA256 /v \$f" \
|
||||||
echo "-- begin lib.wxs --"
|
/O"$GITHUB_WORKSPACE" \
|
||||||
cat lib.wxs
|
gam.iss
|
||||||
echo "-- end lib.wxs --"
|
|
||||||
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/candle.exe -arch "${WIX_ARCH}" gam.wxs lib.wxs
|
|
||||||
/c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/light.exe -ext /c/Program\ Files\ \(x86\)/WiX\ Toolset\ v3.14/bin/WixUIExtension.dll gam.wixobj lib.wixobj -b "${gampath}/lib" -o "$MSI_FILENAME" || true;
|
|
||||||
rm -v -f *.wixpdb
|
|
||||||
rm -v -f *.wixobj
|
|
||||||
echo "MSI_FILENAME=${MSI_FILENAME}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Sign GAM MSI
|
|
||||||
if: runner.os == 'Windows'
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
write-Host "Signing ${env:MSI_FILENAME}...."
|
|
||||||
# Always explicitely use x64 version os signtool.exe, arm64 version apparently can't
|
|
||||||
# see Certum certs since SimplySignDesktop is x64-only today.
|
|
||||||
Start-Process -Wait -NoNewWindow -ErrorAction Continue -FilePath 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' -ArgumentList "sign", "/sha1", "$env:WINDOWS_CODESIGN_CERT_HASH", "/tr", "http://time.certum.pl", "/td", "SHA256", "/fd", "SHA256", "/v", "$env:MSI_FILENAME"
|
|
||||||
write-Host "Verifying signature of ${env:MSI_FILENAME}...."
|
|
||||||
# verify signature. If we failed to sign we should fail to verify and die.
|
|
||||||
& 'C:\Program Files (x86)\Windows Kits\10\bin\10.0.26100.0\x64\signtool.exe' verify /pa /v "$env:MSI_FILENAME"
|
|
||||||
|
|
||||||
- name: Attest that gam package files were generated from this Action
|
- name: Attest that gam package files were generated from this Action
|
||||||
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
|
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
|
||||||
@@ -807,7 +796,8 @@ jobs:
|
|||||||
subject-path: |
|
subject-path: |
|
||||||
gam*.tar.xz
|
gam*.tar.xz
|
||||||
gam*.zip
|
gam*.zip
|
||||||
gam*.msi
|
gam*.exe
|
||||||
|
# gam*.msi
|
||||||
|
|
||||||
- name: Archive tar.xz artifacts
|
- name: Archive tar.xz artifacts
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
||||||
@@ -827,14 +817,14 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
gam*.zip
|
gam*.zip
|
||||||
|
|
||||||
- name: Archive msi artifacts
|
- name: Archive exe artifacts
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # 7.0.0
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
with:
|
with:
|
||||||
archive: false
|
archive: false
|
||||||
if-no-files-found: ignore
|
if-no-files-found: ignore
|
||||||
path: |
|
path: |
|
||||||
gam*.msi
|
gam*.exe
|
||||||
|
|
||||||
- name: Basic Tests build jobs only
|
- name: Basic Tests build jobs only
|
||||||
if: matrix.goal != 'test' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
if: matrix.goal != 'test' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||||
@@ -1111,11 +1101,12 @@ jobs:
|
|||||||
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
if: matrix.goal == 'build' && steps.cache-python-ssl.outputs.cache-hit != 'true'
|
||||||
working-directory: ${{ github.workspace }}
|
working-directory: ${{ github.workspace }}
|
||||||
run: |
|
run: |
|
||||||
if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
#if [[ "${RUNNER_OS}" == "Windows" ]]; then
|
||||||
tar_folders="src/cpython/ bin/ssl"
|
# tar_folders="src/cpython/ bin/ssl"
|
||||||
else
|
#else
|
||||||
tar_folders="bin/"
|
# tar_folders="bin/"
|
||||||
fi
|
#fi
|
||||||
|
tar_folders="bin/"
|
||||||
echo '.git*' > ./excludes.txt
|
echo '.git*' > ./excludes.txt
|
||||||
tar cJvvf cache.tar.xz --exclude-from=excludes.txt $tar_folders
|
tar cJvvf cache.tar.xz --exclude-from=excludes.txt $tar_folders
|
||||||
|
|
||||||
|
|||||||
59
.github/workflows/get-cacerts.yml
vendored
59
.github/workflows/get-cacerts.yml
vendored
@@ -1,14 +1,9 @@
|
|||||||
name: Check for Google Root CA Updates
|
name: Check for Google Root CA Updates
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
paths-ignore:
|
|
||||||
- 'wiki/**'
|
|
||||||
pull_request:
|
|
||||||
paths-ignore:
|
|
||||||
- 'wiki/**'
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '23 23 * * *'
|
- cron: '23 23 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
@@ -17,9 +12,9 @@ defaults:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-certs:
|
check-certs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-slim
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@ff7abcd0c3c05ccf6adc123a8cd1fd4fb30fb493 # v5.0.0
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
with:
|
with:
|
||||||
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
|
persist-credentials: false # otherwise, the token used is the GITHUB_TOKEN, instead of your personal token
|
||||||
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
|
fetch-depth: 0 # otherwise, you will failed to push refs to dest repo
|
||||||
@@ -30,9 +25,51 @@ jobs:
|
|||||||
echo "Current hash is: ${CURRENT_HASH}"
|
echo "Current hash is: ${CURRENT_HASH}"
|
||||||
echo "CURRENT_HASH=${CURRENT_HASH}" >> $GITHUB_ENV
|
echo "CURRENT_HASH=${CURRENT_HASH}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get latest cacerts.pem file from Google
|
- name: Generate GAM-specific bundle with LE + Google roots
|
||||||
run: |
|
run: |
|
||||||
curl -o ./cacerts.pem -vvvv https://pki.goog/roots.pem
|
OUTPUT_FILE="cacerts.pem"
|
||||||
|
> "$OUTPUT_FILE"
|
||||||
|
|
||||||
|
process_cert() {
|
||||||
|
local url="$1"
|
||||||
|
local op_ca="$2"
|
||||||
|
local label="$3"
|
||||||
|
local tmp_cert=$(mktemp)
|
||||||
|
curl "$url" > "$tmp_cert"
|
||||||
|
local issuer=$(openssl x509 -noout -issuer -in "$tmp_cert" | sed -e 's/^issuer= *//')
|
||||||
|
local subject=$(openssl x509 -noout -subject -in "$tmp_cert" | sed -e 's/^subject= *//')
|
||||||
|
local serial_hex=$(openssl x509 -noout -serial -in "$tmp_cert" | sed -e 's/^serial=//')
|
||||||
|
local serial_dec=$(python3 -c "print(int('$serial_hex', 16))")
|
||||||
|
local md5=$(openssl x509 -noout -fingerprint -md5 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||||
|
local sha1=$(openssl x509 -noout -fingerprint -sha1 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||||
|
local sha256=$(openssl x509 -noout -fingerprint -sha256 -in "$tmp_cert" | sed -e 's/.*=//' | tr '[:upper:]' '[:lower:]')
|
||||||
|
echo "# Operating CA: $op_ca" >> "$OUTPUT_FILE"
|
||||||
|
echo "# Issuer: $issuer" >> "$OUTPUT_FILE"
|
||||||
|
echo "# Subject: $subject" >> "$OUTPUT_FILE"
|
||||||
|
echo "# Label: \"$label\"" >> "$OUTPUT_FILE"
|
||||||
|
echo "# Serial: $serial_dec" >> "$OUTPUT_FILE"
|
||||||
|
echo "# MD5 Fingerprint: $md5" >> "$OUTPUT_FILE"
|
||||||
|
echo "# SHA1 Fingerprint: $sha1" >> "$OUTPUT_FILE"
|
||||||
|
echo "# SHA256 Fingerprint: $sha256" >> "$OUTPUT_FILE"
|
||||||
|
cat "$tmp_cert" >> "$OUTPUT_FILE"
|
||||||
|
echo "" >> "$OUTPUT_FILE"
|
||||||
|
rm "$tmp_cert"
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "#" >> "$OUTPUT_FILE"
|
||||||
|
echo "# This is a custom certificate authority bundle for GAM" >> "$OUTPUT_FILE"
|
||||||
|
echo "# It's composed of Let's Encrypt Root CAs and Google's" >> "$OUTPUT_FILE"
|
||||||
|
echo "# certificate bundle. This should be the minimal list of" >> "$OUTPUT_FILE"
|
||||||
|
echo "# CAs required to talk to Google and Github." >> "$OUTPUT_FILE"
|
||||||
|
echo"" >> "$OUTPUT_FILE"
|
||||||
|
echo "Processing Let's Encrypt ISRG Root X1..."
|
||||||
|
process_cert "https://letsencrypt.org/certs/isrgrootx1.pem" "Let's Encrypt" "ISRG Root X1"
|
||||||
|
echo "Processing Let's Encrypt ISRG Root X2..."
|
||||||
|
process_cert "https://letsencrypt.org/certs/isrg-root-x2.pem" "Let's Encrypt" "ISRG Root X2"
|
||||||
|
|
||||||
|
echo "Appending Google's roots.pem..."
|
||||||
|
curl -s https://pki.goog/roots.pem >> "$OUTPUT_FILE"
|
||||||
|
echo "Done! The new bundle has been saved to $OUTPUT_FILE."
|
||||||
|
|
||||||
- name: Compare hashes
|
- name: Compare hashes
|
||||||
run: |
|
run: |
|
||||||
@@ -51,6 +88,6 @@ jobs:
|
|||||||
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated cacerts.pem'
|
git diff --quiet && git diff --staged --quiet || git commit -am '[ci skip] Updated cacerts.pem'
|
||||||
|
|
||||||
- name: Push changes
|
- name: Push changes
|
||||||
uses: ad-m/github-push-action@master
|
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -4335,14 +4335,14 @@ gam update deviceuserstate <DeviceUserEntity> [clientid <String>]
|
|||||||
# Cloud Identity Policies
|
# Cloud Identity Policies
|
||||||
|
|
||||||
gam info policies <CIPolicyNameEntity>
|
gam info policies <CIPolicyNameEntity>
|
||||||
[nowarnings] [noappnames]
|
[nowarnings] [noappnames] [noidmappimg]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
gam print policies [todrive <ToDriveAttribute>*]
|
gam print policies [todrive <ToDriveAttribute>*]
|
||||||
[filter <String>] [nowarnings] [noappnames]
|
[filter <String>] [nowarnings] [noappnames] [noidmappimg]
|
||||||
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
[formatjson [quotechar <Character>]]
|
[formatjson [quotechar <Character>]]
|
||||||
gam show policies
|
gam show policies
|
||||||
[filter <String>] [nowarnings] [noappnames]
|
[filter <String>] [nowarnings] [noappnames] [noidmappimg]
|
||||||
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
|
|
||||||
@@ -4926,36 +4926,36 @@ gam print schema|schemas [todrive <ToDriveAttribute>*]
|
|||||||
gam sendemail [recipient|to] <RecipientEntity>
|
gam sendemail [recipient|to] <RecipientEntity>
|
||||||
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
||||||
[replyto <EmailAddress>]
|
[replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
gam <UserTypeEntity> sendemail from <EmailAddress>
|
gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||||
[replyto <EmailAddress>]
|
[replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <RESearchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
|
|
||||||
# Shared Drives - Administrator
|
# Shared Drives - Administrator
|
||||||
|
|
||||||
@@ -5803,12 +5803,12 @@ gam download storagefile <StorageBucketObjectName>
|
|||||||
|
|
||||||
<UserClearAttribute> ::=
|
<UserClearAttribute> ::=
|
||||||
(address clear)|
|
(address clear)|
|
||||||
(otheremail clear)|
|
|
||||||
(externalid clear)|
|
(externalid clear)|
|
||||||
(im clear)|
|
(im clear)|
|
||||||
(keyword clear)|
|
(keyword clear)|
|
||||||
(location clear)|
|
(location clear)|
|
||||||
(organization clear)|
|
(organization clear)|
|
||||||
|
(otheremail clear)|
|
||||||
(phone clear)|
|
(phone clear)|
|
||||||
(posix clear)|
|
(posix clear)|
|
||||||
(relation clear)|
|
(relation clear)|
|
||||||
@@ -5821,6 +5821,18 @@ gam download storagefile <StorageBucketObjectName>
|
|||||||
<UserMultiAttribute>|
|
<UserMultiAttribute>|
|
||||||
<UserClearAttribute>
|
<UserClearAttribute>
|
||||||
|
|
||||||
|
<UserMultiAttributeFilterName> ::=
|
||||||
|
address|addresses|
|
||||||
|
externalid|externalids|
|
||||||
|
im|ims|
|
||||||
|
keyword|keywords|
|
||||||
|
location|locations|
|
||||||
|
orgainzation|organizations|
|
||||||
|
otheremail|otheremails|
|
||||||
|
phone|phones|
|
||||||
|
relation|relations|
|
||||||
|
website|websites
|
||||||
|
|
||||||
gam create|add user <EmailAddress> [ignorenullpassword] <UserAttribute>*
|
gam create|add user <EmailAddress> [ignorenullpassword] <UserAttribute>*
|
||||||
[verifynotinvitable|alwaysevict]
|
[verifynotinvitable|alwaysevict]
|
||||||
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
|
(groups [<GroupRole>] [[delivery] <DeliverySetting>] <GroupEntity>)*
|
||||||
@@ -5875,6 +5887,8 @@ gam info user [<UserItem>]
|
|||||||
[nolicenses|nolicences|licenses|licences]
|
[nolicenses|nolicences|licenses|licences]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
|
|
||||||
@@ -5911,6 +5925,8 @@ gam info users <UserTypeEntity>
|
|||||||
[nolicenses|nolicences|licenses|licences]
|
[nolicenses|nolicences|licenses|licences]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
|
|
||||||
@@ -5947,6 +5963,8 @@ gam <UserTypeEntity> info users
|
|||||||
[nolicenses|nolicences|licenses|licences]
|
[nolicenses|nolicences|licenses|licences]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
|
|
||||||
@@ -5966,6 +5984,8 @@ gam print users [todrive <ToDriveAttribute>*]
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -5983,6 +6003,8 @@ gam print users [todrive <ToDriveAttribute>*] select <UserTypeEntity>
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -5998,6 +6020,8 @@ gam <UserTypeEntity> print users [todrive <ToDriveAttribute>*]
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -7498,7 +7522,7 @@ gam <UserTypeEntity> print filecounts [todrive <ToDriveAttribute>*]
|
|||||||
[filenamematchpattern <REMatchPattern>]
|
[filenamematchpattern <REMatchPattern>]
|
||||||
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[showsize] [showmimetypesize]
|
[showsize] [showsizeunits] [showmimetypesize]
|
||||||
[showlastmodification] [pathdelimiter <Character>]
|
[showlastmodification] [pathdelimiter <Character>]
|
||||||
(addcsvdata <FieldName> <String>)*
|
(addcsvdata <FieldName> <String>)*
|
||||||
[summary none|only|plus] [summaryuser <String>]
|
[summary none|only|plus] [summaryuser <String>]
|
||||||
@@ -7514,7 +7538,7 @@ gam <UserTypeEntity> show filecounts
|
|||||||
[filenamematchpattern <REMatchPattern>]
|
[filenamematchpattern <REMatchPattern>]
|
||||||
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[showsize] [showmimetypesize]
|
[showsize] [showsizeunits] [showmimetypesize]
|
||||||
[showlastmodification] [pathdelimiter <Character>]
|
[showlastmodification] [pathdelimiter <Character>]
|
||||||
[summary none|only|plus] [summaryuser <String>]
|
[summary none|only|plus] [summaryuser <String>]
|
||||||
|
|
||||||
@@ -7584,7 +7608,7 @@ gam <UserTypeEntity> print filelist [todrive <ToDriveAttribute>*]
|
|||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[maxfiles <Integer>] [nodataheaders <String>]
|
[maxfiles <Integer>] [nodataheaders <String>]
|
||||||
[countsonly [summary none|only|plus] [summaryuser <String>]
|
[countsonly [summary none|only|plus] [summaryuser <String>]
|
||||||
[showsource] [showsize] [showmimetypesize]]
|
[showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||||
[countsrowfilter]
|
[countsrowfilter]
|
||||||
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||||
|
|||||||
@@ -1,3 +1,81 @@
|
|||||||
|
7.36.02
|
||||||
|
|
||||||
|
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message
|
||||||
|
in conversation mode in for the user sending the message.
|
||||||
|
|
||||||
|
* See: https://github.com/GAM-team/GAM/wiki/Send-Email#conversation-mode
|
||||||
|
|
||||||
|
7.36.01
|
||||||
|
|
||||||
|
Fixed bug in `gam info|print|show policies` where the `policyQuery/query` field was not displayed.
|
||||||
|
|
||||||
|
Added option `noidmapping` to `gam info|print|show policies` to suppress adding the `policyQuery/groupEmail` and
|
||||||
|
`policyQuery/orgUnitPath` name fields that are mapped from the `policyQuery/group` and `policyQuery/orgInit` id fields.
|
||||||
|
|
||||||
|
7.36.00
|
||||||
|
|
||||||
|
Added options `filtermultiattrtype` and filtermultiattrcustom` to `gam info user` and
|
||||||
|
`gam print users` that support filtering `<UserMultiAttribute>` display based on `type` or `customType`.
|
||||||
|
|
||||||
|
```
|
||||||
|
<UserMultiAttributeFilterName> ::=
|
||||||
|
address|addresses|
|
||||||
|
externalid|externalids|
|
||||||
|
im|ims|
|
||||||
|
keyword|keywords|
|
||||||
|
location|locations|
|
||||||
|
orgainzation|organizations|
|
||||||
|
otheremail|otheremails|
|
||||||
|
phone|phones|
|
||||||
|
relation|relations|
|
||||||
|
website|websites
|
||||||
|
```
|
||||||
|
|
||||||
|
* `filtermultiattrtype <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `type` is `<String>`
|
||||||
|
* `filtermultiattrcustom <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `customType` is `<String>`
|
||||||
|
|
||||||
|
```
|
||||||
|
gam info user user@domain.com quick filtermultiattrtype organizations work filtermultiattrcustom phones private
|
||||||
|
```
|
||||||
|
|
||||||
|
7.35.03
|
||||||
|
|
||||||
|
Updated `gam <UserTypeEntity> print filelist|filecounts` to handle options `showsize` and `showsizeunits` as independent options.
|
||||||
|
* `showsize` - Display a column `Size` with a byte count
|
||||||
|
* `showsizeunits` - Display a column `SizeUnits` with a formatted size with units
|
||||||
|
|
||||||
|
If you select both options, you can sort multiple rows using the `Size` column.
|
||||||
|
|
||||||
|
7.35.02
|
||||||
|
|
||||||
|
Added option `showsizeunits` to `gam gam <UserTypeEntity> print filelist|filecounts` as an alternative to option `showsize`.
|
||||||
|
* `showsize` - 31549200951 - This is a byte count
|
||||||
|
* `showsizeunits` - 31.55 GB - This is as shown in the Admin console
|
||||||
|
|
||||||
|
7.35.01
|
||||||
|
|
||||||
|
The following commands have been updated to not verify the existence of `gam.cfg` credentials files
|
||||||
|
as the WARNING messages about the missing files can be confusing to new users setting up GAM.
|
||||||
|
```
|
||||||
|
gam checkconn
|
||||||
|
gam oauth|oauth2
|
||||||
|
gam version
|
||||||
|
```
|
||||||
|
|
||||||
|
7.35.00
|
||||||
|
|
||||||
|
Windows `gam-7.wx.yz-x86_64.msi` has been replaced with `gam-7.wx.yz-x86_64.exe`.
|
||||||
|
|
||||||
|
Windows `gam-7.wx.yz-arm64.msi` has been replaced with `gam-7.wx.yz-arm64.exe`.
|
||||||
|
|
||||||
|
Updated cacerts.pem to avoid to following error in `gam checkconn`.
|
||||||
|
```
|
||||||
|
Checking raw.githubusercontent.com (185.199.110.133) (2)... ERROR
|
||||||
|
Certificate verification failed. If you are behind a firewall / proxy server that does TLS / SSL inspection you may need to point GAM at your certificate authority file by setting cacerts_pem = /path/to/your/certauth.pem in gam.cfg.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have customized cacerts.pem, update your version with the `Operating CA: Let's Encrypt` values from the GAM default version.
|
||||||
|
|
||||||
7.34.13
|
7.34.13
|
||||||
|
|
||||||
Fixed bug in `gam info policies <CIPolicyNameEntity> ... formatjson` where extraneous line
|
Fixed bug in `gam info policies <CIPolicyNameEntity> ... formatjson` where extraneous line
|
||||||
@@ -6483,7 +6561,7 @@ This addresses the following issue:
|
|||||||
Updated `gam <UserTypeEntity> add|delete|update|print|show datastudiopermissions` to display an appropriate
|
Updated `gam <UserTypeEntity> add|delete|update|print|show datastudiopermissions` to display an appropriate
|
||||||
error message, `The caller does not have permission`, when the user doesn't have permission to execute the command.
|
error message, `The caller does not have permission`, when the user doesn't have permission to execute the command.
|
||||||
Previously, the following incorrect error message was displayed:
|
Previously, the following incorrect error message was displayed:
|
||||||
`ERROR: Data Studio API not enabled. Please run "gam update project" and "gam user user@domain.com check serviceaccount"`
|
`ERROR: Data Studio API not enabled. Please run "gam update project" and "gam user user@domain.com update serviceaccount"`
|
||||||
|
|
||||||
6.26.14
|
6.26.14
|
||||||
|
|
||||||
@@ -6765,7 +6843,7 @@ and display drive labels on files. Please test/experiment and report any issues.
|
|||||||
To use these commands you must add the 'Drive Labels API' to your project and update your service account authorization.
|
To use these commands you must add the 'Drive Labels API' to your project and update your service account authorization.
|
||||||
```
|
```
|
||||||
gam update project
|
gam update project
|
||||||
gam user user@domain.com check serviceaccount
|
gam user user@domain.com update serviceaccount
|
||||||
```
|
```
|
||||||
Supported editions for this feature: Business Standard and Business Plus; Enterprise; Education Standard and Education Plus; G Suite Business; Essentials.
|
Supported editions for this feature: Business Standard and Business Plus; Enterprise; Education Standard and Education Plus; G Suite Business; Essentials.
|
||||||
|
|
||||||
@@ -6963,7 +7041,7 @@ ERROR: 403: permissionDenied - Google Forms API has not been used in project XXX
|
|||||||
```
|
```
|
||||||
is replaced with
|
is replaced with
|
||||||
```
|
```
|
||||||
ERROR: Forms API not enabled. Please run "gam update project" and "gam user user@domain.com check serviceaccount"
|
ERROR: Forms API not enabled. Please run "gam update project" and "gam user user@domain.com update serviceaccount"
|
||||||
```
|
```
|
||||||
|
|
||||||
6.23.00
|
6.23.00
|
||||||
@@ -9321,7 +9399,7 @@ To use this feature you must add the `People API` to your project and authorize
|
|||||||
* `People API - Other Contacts - read only`: https://www.googleapis.com/auth/contacts.other.readonly
|
* `People API - Other Contacts - read only`: https://www.googleapis.com/auth/contacts.other.readonly
|
||||||
```
|
```
|
||||||
gam update project
|
gam update project
|
||||||
gam user user@domain.com check serviceaccount
|
gam user user@domain.com update serviceaccount
|
||||||
```
|
```
|
||||||
|
|
||||||
Added commands to display user's contact groups using the People API.
|
Added commands to display user's contact groups using the People API.
|
||||||
@@ -9362,7 +9440,7 @@ To use these features you must add the `People API` to your project and authoriz
|
|||||||
```
|
```
|
||||||
gam update project
|
gam update project
|
||||||
gam oauth create
|
gam oauth create
|
||||||
gam user user@domain.com check serviceaccount
|
gam user user@domain.com update serviceaccount
|
||||||
```
|
```
|
||||||
|
|
||||||
Following Jay's lead, added new license SKU `Cloud Search`.
|
Following Jay's lead, added new license SKU `Cloud Search`.
|
||||||
@@ -9401,7 +9479,7 @@ Added commands to display Data Studio assets and display/manage Data Studio perm
|
|||||||
To use these commands you must add the `Data Studio API` to your project and update your service account authorization.
|
To use these commands you must add the `Data Studio API` to your project and update your service account authorization.
|
||||||
```
|
```
|
||||||
gam update project
|
gam update project
|
||||||
gam user user@domain.com check serviceaccount
|
gam user user@domain.com update serviceaccount
|
||||||
```
|
```
|
||||||
This is a first release from me, experiment and use with caution.
|
This is a first release from me, experiment and use with caution.
|
||||||
|
|
||||||
@@ -10444,7 +10522,7 @@ Added commands to support the new Device Management API.
|
|||||||
|
|
||||||
To use these commands you must update your service account authorization.
|
To use these commands you must update your service account authorization.
|
||||||
```
|
```
|
||||||
gam user user@domain.com check serviceaccount
|
gam user user@domain.com update serviceaccount
|
||||||
```
|
```
|
||||||
|
|
||||||
In the following places a Google Admin email address is required; by default the admin email address in `oauth2.txt` is used.
|
In the following places a Google Admin email address is required; by default the admin email address in `oauth2.txt` is used.
|
||||||
|
|||||||
116
src/gam.iss
Normal file
116
src/gam.iss
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
; --- 1. PREPROCESSOR DEFINITIONS ---
|
||||||
|
#define AppVersion GetEnv("GAMVERSION")
|
||||||
|
#if AppVersion == ""
|
||||||
|
#define AppVersion "7.0.0"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
; Pull architecture directly from GitHub Actions environment variable
|
||||||
|
#define RunnerArch GetEnv("RUNNER_ARCH")
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
; --- 2. CORE APPLICATION INFO ---
|
||||||
|
AppId={{D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319}
|
||||||
|
AppName=GAM7
|
||||||
|
AppVersion={#AppVersion}
|
||||||
|
AppPublisher=GAM Team - google-apps-manager@googlegroups.com
|
||||||
|
DefaultDirName={sd}\GAM7
|
||||||
|
LicenseFile=dist\gam\gam7\LICENSE
|
||||||
|
PrivilegesRequired=admin
|
||||||
|
ChangesEnvironment=yes
|
||||||
|
|
||||||
|
; Tell Inno Setup to use a custom signtool defined via the command line
|
||||||
|
SignTool=gamsigntool
|
||||||
|
|
||||||
|
; --- 3. COMPRESSION & OPTIMIZATION ---
|
||||||
|
Compression=lzma2/ultra64
|
||||||
|
SolidCompression=yes
|
||||||
|
|
||||||
|
; --- 4. DYNAMIC ARCHITECTURE CONFIGURATION ---
|
||||||
|
; GitHub Actions RUNNER_ARCH is typically uppercase "ARM64" or "X64"
|
||||||
|
#if RunnerArch == "ARM64" || RunnerArch == "arm64"
|
||||||
|
ArchitecturesAllowed=arm64
|
||||||
|
ArchitecturesInstallIn64BitMode=arm64
|
||||||
|
OutputBaseFilename=gam-{#AppVersion}-windows-arm64
|
||||||
|
#else
|
||||||
|
ArchitecturesAllowed=x64compatible
|
||||||
|
ArchitecturesInstallIn64BitMode=x64compatible
|
||||||
|
OutputBaseFilename=gam-{#AppVersion}-windows-x86_64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Messages]
|
||||||
|
; Custom error if an admin tries to run the ARM64 installer on an Intel machine
|
||||||
|
#if RunnerArch == "ARM64" || RunnerArch == "arm64"
|
||||||
|
WindowsVersionNotSupported=You downloaded the ARM64 version of GAM, but this computer has an Intel or AMD processor.%n%nPlease go back to the release page and download the x86_64 installer instead.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
; --- 5. DYNAMIC FILE INCLUSION ---
|
||||||
|
Source: "dist\gam\gam7\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
[Registry]
|
||||||
|
; --- 6. PATH ENVIRONMENT VARIABLE ---
|
||||||
|
Root: HKLM; Subkey: "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; \
|
||||||
|
ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}"; \
|
||||||
|
Check: NeedsAddPath(ExpandConstant('{app}'))
|
||||||
|
|
||||||
|
[Code]
|
||||||
|
const
|
||||||
|
ERROR_SUCCESS = 0;
|
||||||
|
|
||||||
|
function MsiEnumRelatedProducts(lpUpgradeCode: string; dwReserved: Integer; iProductIndex: Integer; lpProductBuf: string): Integer;
|
||||||
|
external 'MsiEnumRelatedProductsW@msi.dll stdcall';
|
||||||
|
|
||||||
|
function UninstallWixMSI(): Boolean;
|
||||||
|
var
|
||||||
|
UpgradeCode: string;
|
||||||
|
ProductCode: string;
|
||||||
|
ResultCode: Integer;
|
||||||
|
begin
|
||||||
|
UpgradeCode := '{D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319}';
|
||||||
|
ProductCode := StringOfChar(' ', 39);
|
||||||
|
|
||||||
|
ResultCode := MsiEnumRelatedProducts(UpgradeCode, 0, 0, ProductCode);
|
||||||
|
|
||||||
|
if ResultCode = ERROR_SUCCESS then
|
||||||
|
begin
|
||||||
|
ProductCode := Trim(ProductCode);
|
||||||
|
Exec('msiexec.exe', '/x ' + ProductCode + ' /qn /norestart', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
|
||||||
|
end;
|
||||||
|
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function InitializeSetup(): Boolean;
|
||||||
|
begin
|
||||||
|
// --- Architecture Warning for Emulation ---
|
||||||
|
#if RunnerArch != "ARM64" && RunnerArch != "arm64"
|
||||||
|
if IsArm64() then
|
||||||
|
begin
|
||||||
|
if MsgBox('Notice: You are installing the Intel (x86_64) build of GAM on an ARM processor.' + #13#10#13#10 +
|
||||||
|
'While this will work via Windows emulation, it will perform worse than the native ARM64 version.' + #13#10#13#10 +
|
||||||
|
'Do you want to continue with the installation anyway?',
|
||||||
|
mbConfirmation, MB_YESNO) = idNo then
|
||||||
|
begin
|
||||||
|
Result := False;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
UninstallWixMSI();
|
||||||
|
Result := True;
|
||||||
|
end;
|
||||||
|
|
||||||
|
function NeedsAddPath(Param: string): boolean;
|
||||||
|
var
|
||||||
|
OrigPath: string;
|
||||||
|
begin
|
||||||
|
if not RegQueryStringValue(HKEY_LOCAL_MACHINE,
|
||||||
|
'SYSTEM\CurrentControlSet\Control\Session Manager\Environment',
|
||||||
|
'Path', OrigPath)
|
||||||
|
then begin
|
||||||
|
Result := True;
|
||||||
|
exit;
|
||||||
|
end;
|
||||||
|
Result := Pos(';' + Param + ';', ';' + OrigPath + ';') = 0;
|
||||||
|
end;
|
||||||
@@ -8,7 +8,7 @@
|
|||||||
Manufacturer="GAM Team - google-apps-manager@googlegroups.com"
|
Manufacturer="GAM Team - google-apps-manager@googlegroups.com"
|
||||||
UpgradeCode="D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319">
|
UpgradeCode="D86B52B2-EFE9-4F9D-8BA3-9D84B9B2D319">
|
||||||
<Package
|
<Package
|
||||||
InstallerVersion="200" Compressed="yes" InstallScope="perMachine" />
|
InstallerVersion="500" Compressed="yes" InstallScope="perMachine" />
|
||||||
|
|
||||||
<MajorUpgrade
|
<MajorUpgrade
|
||||||
DowngradeErrorMessage=
|
DowngradeErrorMessage=
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
__author__ = 'GAM Team <google-apps-manager@googlegroups.com>'
|
||||||
__version__ = '7.34.13'
|
__version__ = '7.36.02'
|
||||||
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
__license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)'
|
||||||
|
|
||||||
# pylint: disable=wrong-import-position
|
# pylint: disable=wrong-import-position
|
||||||
@@ -204,11 +204,13 @@ ERROR_PREFIX = ERROR+': '
|
|||||||
WARNING = 'WARNING'
|
WARNING = 'WARNING'
|
||||||
WARNING_PREFIX = WARNING+': '
|
WARNING_PREFIX = WARNING+': '
|
||||||
ONE_KILO_10_BYTES = 1000
|
ONE_KILO_10_BYTES = 1000
|
||||||
ONE_MEGA_10_BYTES = 1000000
|
ONE_MEGA_10_BYTES = ONE_KILO_10_BYTES*ONE_KILO_10_BYTES
|
||||||
ONE_GIGA_10_BYTES = 1000000000
|
ONE_GIGA_10_BYTES = ONE_KILO_10_BYTES*ONE_MEGA_10_BYTES
|
||||||
|
ONE_TERA_10_BYTES = ONE_KILO_10_BYTES*ONE_GIGA_10_BYTES
|
||||||
ONE_KILO_BYTES = 1024
|
ONE_KILO_BYTES = 1024
|
||||||
ONE_MEGA_BYTES = 1048576
|
ONE_MEGA_BYTES = ONE_KILO_BYTES*ONE_KILO_BYTES
|
||||||
ONE_GIGA_BYTES = 1073741824
|
ONE_GIGA_BYTES = ONE_KILO_BYTES*ONE_MEGA_BYTES
|
||||||
|
ONE_TERA_BYTES = ONE_KILO_BYTES*ONE_GIGA_BYTES
|
||||||
DAYS_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
DAYS_OF_WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
||||||
SECONDS_PER_MINUTE = 60
|
SECONDS_PER_MINUTE = 60
|
||||||
SECONDS_PER_HOUR = 3600
|
SECONDS_PER_HOUR = 3600
|
||||||
@@ -2382,16 +2384,18 @@ def splitEmailAddress(emailAddress):
|
|||||||
return (emailAddress.lower(), GC.Values[GC.DOMAIN])
|
return (emailAddress.lower(), GC.Values[GC.DOMAIN])
|
||||||
return (emailAddress[:atLoc].lower(), emailAddress[atLoc+1:].lower())
|
return (emailAddress[:atLoc].lower(), emailAddress[atLoc+1:].lower())
|
||||||
|
|
||||||
def formatFileSize(fileSize):
|
def formatFileSize(size):
|
||||||
if fileSize == 0:
|
if size == 0:
|
||||||
return '0kb'
|
return '0 KB'
|
||||||
if fileSize < ONE_KILO_10_BYTES:
|
if size < ONE_KILO_10_BYTES:
|
||||||
return '1kb'
|
return '1 KB'
|
||||||
if fileSize < ONE_MEGA_10_BYTES:
|
if size < ONE_MEGA_10_BYTES:
|
||||||
return f'{fileSize//ONE_KILO_10_BYTES}kb'
|
return f'{size/ONE_KILO_10_BYTES:.2f} KB'
|
||||||
if fileSize < ONE_GIGA_10_BYTES:
|
if size < ONE_GIGA_10_BYTES:
|
||||||
return f'{fileSize//ONE_MEGA_10_BYTES}mb'
|
return f'{size/ONE_MEGA_10_BYTES:.2f} MB'
|
||||||
return f'{fileSize//ONE_GIGA_10_BYTES}gb'
|
if size < ONE_TERA_10_BYTES:
|
||||||
|
return f'{size/ONE_GIGA_10_BYTES:.2f} GB'
|
||||||
|
return f'{size/ONE_TERA_10_BYTES:.2f} TB'
|
||||||
|
|
||||||
def formatLocalTime(dateTimeStr):
|
def formatLocalTime(dateTimeStr):
|
||||||
if dateTimeStr in {NEVER_TIME, NEVER_TIME_NOMS}:
|
if dateTimeStr in {NEVER_TIME, NEVER_TIME_NOMS}:
|
||||||
@@ -4349,7 +4353,8 @@ def SetGlobalVariables():
|
|||||||
# warn if the json files are missing and return True
|
# warn if the json files are missing and return True
|
||||||
if (Cmd.Location() == 1) or (Cmd.ArgumentsRemaining()):
|
if (Cmd.Location() == 1) or (Cmd.ArgumentsRemaining()):
|
||||||
_chkCfgDirectories(sectionName)
|
_chkCfgDirectories(sectionName)
|
||||||
_chkCfgFiles(sectionName)
|
if not Cmd.PeekArgumentPresent(['checkconn', 'checkconnection', 'comment', 'oauth', 'oauth2', 'version']):
|
||||||
|
_chkCfgFiles(sectionName)
|
||||||
if status['errors']:
|
if status['errors']:
|
||||||
sys.exit(CONFIG_ERROR_RC)
|
sys.exit(CONFIG_ERROR_RC)
|
||||||
if GC.Values[GC.NO_CACHE]:
|
if GC.Values[GC.NO_CACHE]:
|
||||||
@@ -7342,7 +7347,7 @@ def _addEmbeddedImagesToMessage(message, embeddedImages):
|
|||||||
# Send an email
|
# Send an email
|
||||||
def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None,
|
def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msgFrom=None, msgReplyTo=None,
|
||||||
html=False, charset=UTF8, attachments=None, embeddedImages=None,
|
html=False, charset=UTF8, attachments=None, embeddedImages=None,
|
||||||
msgHeaders=None, ccRecipients=None, bccRecipients=None, mailBox=None):
|
msgHeaders=None, ccRecipients=None, bccRecipients=None, mailBox=None, threadId=None):
|
||||||
def checkResult(entityType, recipients):
|
def checkResult(entityType, recipients):
|
||||||
if not recipients:
|
if not recipients:
|
||||||
return
|
return
|
||||||
@@ -7409,11 +7414,14 @@ def send_email(msgSubject, msgBody, msgTo, i=0, count=0, clientAccess=False, msg
|
|||||||
userId = mailBoxAddr
|
userId = mailBoxAddr
|
||||||
gmail = buildGAPIObject(API.GMAIL)
|
gmail = buildGAPIObject(API.GMAIL)
|
||||||
message['To'] = msgTo if msgTo else userId
|
message['To'] = msgTo if msgTo else userId
|
||||||
|
body = {'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}
|
||||||
|
if threadId is not None:
|
||||||
|
body['threadId'] = threadId
|
||||||
try:
|
try:
|
||||||
result = callGAPI(gmail.users().messages(), 'send',
|
result = callGAPI(gmail.users().messages(), 'send',
|
||||||
throwReasons=[GAPI.SERVICE_NOT_AVAILABLE, GAPI.AUTH_ERROR, GAPI.DOMAIN_POLICY,
|
throwReasons=[GAPI.SERVICE_NOT_AVAILABLE, GAPI.AUTH_ERROR, GAPI.DOMAIN_POLICY,
|
||||||
GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.FORBIDDEN, GAPI.PERMISSION_DENIED],
|
||||||
userId=userId, body={'raw': base64.urlsafe_b64encode(message.as_bytes()).decode()}, fields='id')
|
userId=userId, body=body, fields='id')
|
||||||
entityActionPerformedMessage([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], f"{result['id']}", i, count)
|
entityActionPerformedMessage([Ent.RECIPIENT, msgTo, Ent.MESSAGE, msgSubject], f"{result['id']}", i, count)
|
||||||
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy,
|
except (GAPI.serviceNotAvailable, GAPI.authError, GAPI.domainPolicy,
|
||||||
GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.permissionDenied) as e:
|
GAPI.invalid, GAPI.invalidArgument, GAPI.forbidden, GAPI.permissionDenied) as e:
|
||||||
@@ -9499,6 +9507,33 @@ def getOSPlatform():
|
|||||||
pltfrm = platform.platform()
|
pltfrm = platform.platform()
|
||||||
return f'{myos} {pltfrm}'
|
return f'{myos} {pltfrm}'
|
||||||
|
|
||||||
|
def inspect_untrusted_cert(url):
|
||||||
|
"""Bypasses validation momentarily to extract the untrusted Issuer."""
|
||||||
|
parsed = urlparse(url if '://' in url else f'https://{url}')
|
||||||
|
host = parsed.hostname
|
||||||
|
port = parsed.port or 443
|
||||||
|
# Create an unverified context purely for diagnostic extraction
|
||||||
|
ctx = ssl.create_default_context()
|
||||||
|
ctx.check_hostname = False
|
||||||
|
ctx.verify_mode = ssl.CERT_NONE
|
||||||
|
try:
|
||||||
|
with socket.create_connection((host, port), timeout=5) as sock:
|
||||||
|
with ctx.wrap_socket(sock, server_hostname=host) as ssock:
|
||||||
|
der_cert = ssock.getpeercert(binary_form=True)
|
||||||
|
cert = x509.load_der_x509_certificate(der_cert, default_backend())
|
||||||
|
issuer = cert.issuer.rfc4514_string()
|
||||||
|
subject = cert.subject.rfc4514_string()
|
||||||
|
try:
|
||||||
|
san_ext = cert.extensions.get_extension_for_oid(x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
|
||||||
|
# Loop through the list of SANs (DNS names, IP addresses, etc.)
|
||||||
|
sans = [str(name.value) for name in san_ext.value]
|
||||||
|
san_str = ", ".join(sans)
|
||||||
|
except x509.ExtensionNotFound:
|
||||||
|
san_str = "None"
|
||||||
|
return f"Untrusted Issuer: {issuer}\n Server Subject: {subject}\n SANs: {san_str}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Failed to retrieve diagnostic certificate: {e}"
|
||||||
|
|
||||||
# gam checkconnection
|
# gam checkconnection
|
||||||
def doCheckConnection():
|
def doCheckConnection():
|
||||||
|
|
||||||
@@ -9534,6 +9569,10 @@ def doCheckConnection():
|
|||||||
writeStdout(f'{not_okay}\n Connection reset by peer. {gen_firewall}\n')
|
writeStdout(f'{not_okay}\n Connection reset by peer. {gen_firewall}\n')
|
||||||
except httplib2.error.ServerNotFoundError:
|
except httplib2.error.ServerNotFoundError:
|
||||||
writeStdout(f'{not_okay}\n Failed to find server. Your DNS is probably misconfigured.\n')
|
writeStdout(f'{not_okay}\n Failed to find server. Your DNS is probably misconfigured.\n')
|
||||||
|
except ssl.SSLCertVerificationError as e:
|
||||||
|
diag_info = inspect_untrusted_cert(host)
|
||||||
|
# e.verify_message contains the specific OpenSSL error string
|
||||||
|
writeStdout(f'{not_okay}\n Certificate verification failed: {e.verify_message}\n Diagnostic Info:\n {diag_info}\nIf you are behind a firewall / proxy server that does TLS / SSL inspection you may need to point GAM at your certificate authority file by setting cacerts_pem = /path/to/your/certauth.pem in gam.cfg.\n')
|
||||||
except ssl.SSLError as e:
|
except ssl.SSLError as e:
|
||||||
if e.reason == 'SSLV3_ALERT_HANDSHAKE_FAILURE':
|
if e.reason == 'SSLV3_ALERT_HANDSHAKE_FAILURE':
|
||||||
writeStdout(f'{not_okay}\n GAM expects to connect with TLS 1.3 or newer and that failed. If your firewall / proxy server is not compatible with TLS 1.3 then you can tell GAM to allow TLS 1.2 by setting tls_min_version = TLSv1.2 in gam.cfg.\n')
|
writeStdout(f'{not_okay}\n GAM expects to connect with TLS 1.3 or newer and that failed. If your firewall / proxy server is not compatible with TLS 1.3 then you can tell GAM to allow TLS 1.2 by setting tls_min_version = TLSv1.2 in gam.cfg.\n')
|
||||||
@@ -15354,34 +15393,34 @@ def getRecipients():
|
|||||||
|
|
||||||
# gam sendemail [recipient|to] <RecipientEntity> [from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
# gam sendemail [recipient|to] <RecipientEntity> [from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
# [subject <String>]
|
# [subject <String>] [<MessageContent>]
|
||||||
# [<MessageContent>]
|
|
||||||
# (replace <Tag> <String>)*
|
# (replace <Tag> <String>)*
|
||||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||||
# (embedimage <FileName> <String>)*
|
# (embedimage <FileName> <String>)*
|
||||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
# [threadid <String>]
|
||||||
# gam <UserTypeEntity> sendemail recipient|to <RecipientEntity> [replyto <EmailAddress>]
|
# gam <UserTypeEntity> sendemail recipient|to <RecipientEntity> [replyto <EmailAddress>]
|
||||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
# [subject <String>]
|
# [subject <String>] [<MessageContent>]
|
||||||
# [<MessageContent>]
|
|
||||||
# (replace <Tag> <String>)*
|
# (replace <Tag> <String>)*
|
||||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||||
# (embedimage <FileName> <String>)*
|
# (embedimage <FileName> <String>)*
|
||||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
# [threadid <String>]
|
||||||
# gam <UserTypeEntity> sendemail from <EmailAddress> [replyto <EmailAddress>]
|
# gam <UserTypeEntity> sendemail from <EmailAddress> [replyto <EmailAddress>]
|
||||||
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
# [cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
# [subject <String>]
|
# [subject <String>] [<MessageContent>]
|
||||||
# [<MessageContent>]
|
|
||||||
# (replace <Tag> <String>)*
|
# (replace <Tag> <String>)*
|
||||||
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
# (replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
# [html [<Boolean>]] (attach <FileName> [charset <CharSet>])*
|
||||||
# (embedimage <FileName> <String>)*
|
# (embedimage <FileName> <String>)*
|
||||||
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
# [newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
# (<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
# [threadid <String>]
|
||||||
def doSendEmail(users=None):
|
def doSendEmail(users=None):
|
||||||
body = {}
|
body = {}
|
||||||
notify = {'subject': '', 'message': '', 'html': False, 'charset': UTF8, 'password': ''}
|
notify = {'subject': '', 'message': '', 'html': False, 'charset': UTF8, 'password': ''}
|
||||||
@@ -15406,6 +15445,7 @@ def doSendEmail(users=None):
|
|||||||
bccRecipients = []
|
bccRecipients = []
|
||||||
mailBox = None
|
mailBox = None
|
||||||
msgReplyTo = None
|
msgReplyTo = None
|
||||||
|
threadId = None
|
||||||
singleMessage = False
|
singleMessage = False
|
||||||
tagReplacements = _initTagReplacements()
|
tagReplacements = _initTagReplacements()
|
||||||
attachments = []
|
attachments = []
|
||||||
@@ -15456,6 +15496,8 @@ def doSendEmail(users=None):
|
|||||||
elif myarg == 'header':
|
elif myarg == 'header':
|
||||||
header = getString(Cmd.OB_STRING, minLen=1)
|
header = getString(Cmd.OB_STRING, minLen=1)
|
||||||
msgHeaders[SMTP_HEADERS_MAP.get(header.lower(), header)] = getString(Cmd.OB_STRING)
|
msgHeaders[SMTP_HEADERS_MAP.get(header.lower(), header)] = getString(Cmd.OB_STRING)
|
||||||
|
elif myarg == 'threadid':
|
||||||
|
threadId = getString(Cmd.OB_STRING)
|
||||||
else:
|
else:
|
||||||
unknownArgumentExit()
|
unknownArgumentExit()
|
||||||
notify['message'] = notify['message'].replace('\r', '').replace('\\n', '\n')
|
notify['message'] = notify['message'].replace('\r', '').replace('\\n', '\n')
|
||||||
@@ -15483,7 +15525,7 @@ def doSendEmail(users=None):
|
|||||||
msgFrom=msgFrom, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'],
|
msgFrom=msgFrom, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'],
|
||||||
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders,
|
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders,
|
||||||
ccRecipients=','.join(ccRecipients), bccRecipients=','.join(bccRecipients),
|
ccRecipients=','.join(ccRecipients), bccRecipients=','.join(bccRecipients),
|
||||||
mailBox=mailBox)
|
mailBox=mailBox, threadId=threadId)
|
||||||
else:
|
else:
|
||||||
entityPerformActionModifierNumItems([Ent.USER, msgFrom], Act.MODIFIER_TO, jcount, Ent.RECIPIENT, i, count)
|
entityPerformActionModifierNumItems([Ent.USER, msgFrom], Act.MODIFIER_TO, jcount, Ent.RECIPIENT, i, count)
|
||||||
Ind.Increment()
|
Ind.Increment()
|
||||||
@@ -15492,7 +15534,7 @@ def doSendEmail(users=None):
|
|||||||
j += 1
|
j += 1
|
||||||
send_email(notify['subject'], notify['message'], recipient, j, jcount,
|
send_email(notify['subject'], notify['message'], recipient, j, jcount,
|
||||||
msgFrom=msgFrom, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'],
|
msgFrom=msgFrom, msgReplyTo=msgReplyTo, html=notify['html'], charset=notify['charset'],
|
||||||
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders, mailBox=mailBox)
|
attachments=attachments, embeddedImages=embeddedImages, msgHeaders=msgHeaders, mailBox=mailBox, threadId=threadId)
|
||||||
Ind.Decrement()
|
Ind.Decrement()
|
||||||
|
|
||||||
ADDRESS_FIELDS_PRINT_ORDER = ['contactName', 'organizationName', 'addressLine1', 'addressLine2', 'addressLine3', 'locality', 'region', 'postalCode', 'countryCode']
|
ADDRESS_FIELDS_PRINT_ORDER = ['contactName', 'organizationName', 'addressLine1', 'addressLine2', 'addressLine3', 'locality', 'region', 'postalCode', 'countryCode']
|
||||||
@@ -37707,9 +37749,7 @@ def _filterPolicies(ci, pageMessage, ifilter):
|
|||||||
policies = callGAPIpages(ci.policies(), 'list', 'policies',
|
policies = callGAPIpages(ci.policies(), 'list', 'policies',
|
||||||
pageMessage=pageMessage,
|
pageMessage=pageMessage,
|
||||||
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED],
|
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.PERMISSION_DENIED],
|
||||||
filter=ifilter,
|
filter=ifilter, pageSize=100)
|
||||||
fields='nextPageToken,policies(name,policyQuery(group,orgUnit,sortOrder),type,setting)',
|
|
||||||
pageSize=100)
|
|
||||||
# Google returns unordered results, sort them by setting type
|
# Google returns unordered results, sort them by setting type
|
||||||
return sorted(policies, key=lambda p: p.get('setting', {}).get('type', ''))
|
return sorted(policies, key=lambda p: p.get('setting', {}).get('type', ''))
|
||||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
except (GAPI.invalid, GAPI.invalidArgument, GAPI.permissionDenied) as e:
|
||||||
@@ -37741,12 +37781,19 @@ def _getPolicyAppNameFromId(httpObj, app):
|
|||||||
if a:
|
if a:
|
||||||
app['applicationName'] = a.group(1)
|
app['applicationName'] = a.group(1)
|
||||||
|
|
||||||
def _cleanPolicy(policy, add_warnings, no_appnames,
|
def _cleanPolicy(policy, add_warnings, no_appnames, no_idmapping,
|
||||||
groupEmailPattern, orgUnitPathPattern,
|
groupEmailPattern, orgUnitPathPattern,
|
||||||
cd, groups_ci):
|
cd, groups_ci):
|
||||||
# convert any wordlists into spaced strings to reduce output complexity
|
# convert any wordlists into spaced strings to reduce output complexity
|
||||||
if policy['setting']['type'] == 'settings/detector.word_list':
|
if policy['setting']['type'] == 'settings/detector.word_list':
|
||||||
policy['setting']['value']['wordList'] = ' '.join(policy['setting']['value']['wordList']['words'])
|
wordList = ''
|
||||||
|
for word in policy['setting']['value']['wordList']['words']:
|
||||||
|
wordList += "'"
|
||||||
|
wordList += word.replace("'", r"\'")
|
||||||
|
wordList += "',"
|
||||||
|
if wordList:
|
||||||
|
wordList = wordList[:-1]
|
||||||
|
policy['setting']['value']['wordList'] = wordList
|
||||||
# get application name for application id
|
# get application name for application id
|
||||||
if policy['setting']['type'] == 'settings/workspace_marketplace.apps_allowlist' and not no_appnames:
|
if policy['setting']['type'] == 'settings/workspace_marketplace.apps_allowlist' and not no_appnames:
|
||||||
httpObj = getHttpObj(timeout=10)
|
httpObj = getHttpObj(timeout=10)
|
||||||
@@ -37756,19 +37803,19 @@ def _cleanPolicy(policy, add_warnings, no_appnames,
|
|||||||
if add_warnings and policy['setting']['type'] in CIPOLICY_ADDITIONAL_WARNINGS:
|
if add_warnings and policy['setting']['type'] in CIPOLICY_ADDITIONAL_WARNINGS:
|
||||||
policy['warning'] = CIPOLICY_ADDITIONAL_WARNINGS[policy['setting']['type']]
|
policy['warning'] = CIPOLICY_ADDITIONAL_WARNINGS[policy['setting']['type']]
|
||||||
if groupId := policy['policyQuery'].get('group'):
|
if groupId := policy['policyQuery'].get('group'):
|
||||||
_, _, policy['policyQuery']['groupEmail'] = convertGroupCloudIDToEmail(groups_ci, groupId)
|
if (not no_idmapping) or (groupEmailPattern is not None):
|
||||||
# all groups are in the root OU so the orgUnit attribute is useless
|
_, _, groupEmail = convertGroupCloudIDToEmail(groups_ci, groupId)
|
||||||
policy['policyQuery'].pop('orgUnit', None)
|
if not no_idmapping:
|
||||||
if groupEmailPattern is not None:
|
policy['policyQuery']['groupEmail'] = groupEmail
|
||||||
return groupEmailPattern.match(policy['policyQuery']['groupEmail'])
|
if groupEmailPattern is not None:
|
||||||
if orgUnitPathPattern is not None:
|
return groupEmailPattern.match(groupEmail)
|
||||||
return False
|
|
||||||
elif orgId := policy['policyQuery'].get('orgUnit'):
|
elif orgId := policy['policyQuery'].get('orgUnit'):
|
||||||
policy['policyQuery']['orgUnitPath'] = convertOrgUnitIDtoPath(cd, orgId)
|
if (not no_idmapping) or (orgUnitPathPattern is not None):
|
||||||
if orgUnitPathPattern is not None:
|
orgUnitPath = convertOrgUnitIDtoPath(cd, orgId)
|
||||||
return orgUnitPathPattern.match(policy['policyQuery']['orgUnitPath'])
|
if not no_idmapping:
|
||||||
if groupEmailPattern is not None:
|
policy['policyQuery']['orgUnitPath'] = orgUnitPath
|
||||||
return False
|
if orgUnitPathPattern is not None:
|
||||||
|
return orgUnitPathPattern.match(orgUnitPath)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _showPolicy(policy, FJQC, i=0, count=0):
|
def _showPolicy(policy, FJQC, i=0, count=0):
|
||||||
@@ -37783,9 +37830,8 @@ def _showPolicy(policy, FJQC, i=0, count=0):
|
|||||||
printBlankLine()
|
printBlankLine()
|
||||||
Ind.Decrement()
|
Ind.Decrement()
|
||||||
|
|
||||||
def _showPolicies(policies, FJQC, add_warnings, no_appnames,
|
def _showPolicies(policies, FJQC, add_warnings, no_appnames, no_idmapping,
|
||||||
groupEmailPattern, orgUnitPathPattern,
|
groupEmailPattern, orgUnitPathPattern, cd, groups_ci):
|
||||||
cd, groups_ci):
|
|
||||||
count = len(policies)
|
count = len(policies)
|
||||||
if FJQC is None or not FJQC.formatJSON:
|
if FJQC is None or not FJQC.formatJSON:
|
||||||
if groupEmailPattern is None and orgUnitPathPattern is None:
|
if groupEmailPattern is None and orgUnitPathPattern is None:
|
||||||
@@ -37796,9 +37842,8 @@ def _showPolicies(policies, FJQC, add_warnings, no_appnames,
|
|||||||
i = 0
|
i = 0
|
||||||
for policy in policies:
|
for policy in policies:
|
||||||
i += 1
|
i += 1
|
||||||
if _cleanPolicy(policy, add_warnings, no_appnames,
|
if _cleanPolicy(policy, add_warnings, no_appnames, no_idmapping,
|
||||||
groupEmailPattern, orgUnitPathPattern,
|
groupEmailPattern, orgUnitPathPattern, cd, groups_ci):
|
||||||
cd, groups_ci):
|
|
||||||
_showPolicy(policy, FJQC, i, count)
|
_showPolicy(policy, FJQC, i, count)
|
||||||
Ind.Decrement()
|
Ind.Decrement()
|
||||||
|
|
||||||
@@ -37835,49 +37880,55 @@ def doCreateUpdateCIPolicy():
|
|||||||
updateCmd = Act.Get() == Act.UPDATE
|
updateCmd = Act.Get() == Act.UPDATE
|
||||||
groupEmail = orgUnit = None
|
groupEmail = orgUnit = None
|
||||||
checkArgumentPresent('json', True)
|
checkArgumentPresent('json', True)
|
||||||
jsonData = getJSON(['customer', 'type'])
|
policy = getJSON(['customer', 'type'])
|
||||||
if updateCmd:
|
if updateCmd:
|
||||||
pname = jsonData.pop('name', None)
|
pname = policy.pop('name', None)
|
||||||
else:
|
else:
|
||||||
jsonData.pop('name', None)
|
policy.pop('name', None)
|
||||||
pname = 'New Policy'
|
pname = 'New Policy'
|
||||||
if 'policyQuery' in jsonData:
|
if 'policyQuery' in policy:
|
||||||
jsonData['policyQuery'].pop('orgUnitPath', None)
|
policy['policyQuery'].pop('orgUnitPath', None)
|
||||||
jsonData['policyQuery'].pop('groupEmail', None)
|
policy['policyQuery'].pop('groupEmail', None)
|
||||||
jsonData['policyQuery'].pop('sortOrder', None)
|
policy['policyQuery'].pop('sortOrder', None)
|
||||||
if 'setting' in jsonData:
|
if 'setting' in policy:
|
||||||
if 'value' in jsonData['setting']:
|
if 'value' in policy['setting']:
|
||||||
jsonData['setting']['value'].pop('createTime', None)
|
policy['setting']['value'].pop('createTime', None)
|
||||||
jsonData['setting']['value'].pop('updateTime', None)
|
policy['setting']['value'].pop('deleteTime', None)
|
||||||
|
policy['setting']['value'].pop('updateTime', None)
|
||||||
|
if policy['setting']['type'] == 'settings/detector.word_list':
|
||||||
|
if isinstance(policy['setting']['value']['wordList'], str):
|
||||||
|
wordList = policy['setting']['value'].pop('wordList')
|
||||||
|
policy['setting']['value']['wordList']['words'] = shlexSplitList(wordList, dataDelimiter=',')
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
if myarg in {'ou', 'org', 'orgunit'}:
|
if myarg in {'ou', 'org', 'orgunit'}:
|
||||||
orgUnit, targetResource = _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail)
|
orgUnit, targetResource = _getCIPolicyOrgUnitTarget(cd, myarg, groupEmail)
|
||||||
jsonData.setdefault('policyQuery', {})
|
policy.setdefault('policyQuery', {})
|
||||||
jsonData['policyQuery'].pop('group', None)
|
policy['policyQuery'].pop('group', None)
|
||||||
jsonData['policyQuery']['orgUnit'] = targetResource
|
policy['policyQuery']['orgUnit'] = f"orgUnits/{targetResource}"
|
||||||
|
policy['policyQuery']['query'] = f"entity.org_units.exists(org_unit, org_unit.org_unit_id == orgUnitId('{targetResource}'))"
|
||||||
elif myarg == 'group':
|
elif myarg == 'group':
|
||||||
groupEmail, targetResource = _getCIPolicyGroupTarget(cd, myarg, orgUnit)
|
groupEmail, targetResource = _getCIPolicyGroupTarget(cd, myarg, orgUnit)
|
||||||
jsonData.setdefault('policyQuery', {})
|
policy.setdefault('policyQuery', {})
|
||||||
jsonData['policyQuery'].pop('orgUnit', None)
|
policy['policyQuery'].pop('orgUnit', None)
|
||||||
jsonData['policyQuery']['group'] = targetResource
|
policy['policyQuery']['group'] = f"groups/{targetResource}"
|
||||||
|
policy['policyQuery']['query'] = f"entity.groups.exists(group, group.group_id == groupId('{targetResource}'))"
|
||||||
else:
|
else:
|
||||||
unknownArgumentExit()
|
unknownArgumentExit()
|
||||||
jsonData['customer'] = _getCustomersCustomerIdWithC()
|
policy['customer'] = _getCustomersCustomerIdWithC()
|
||||||
try:
|
try:
|
||||||
if updateCmd:
|
if updateCmd:
|
||||||
result = callGAPI(ci.policies(), 'patch',
|
result = callGAPI(ci.policies(), 'patch',
|
||||||
bailOnInternalError=True,
|
bailOnInternalError=True,
|
||||||
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.UNIMPLEMENTED_ERROR,
|
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.UNIMPLEMENTED_ERROR,
|
||||||
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||||
|
name=pname, body=policy)
|
||||||
name=pname, body=jsonData)
|
|
||||||
else:
|
else:
|
||||||
result = callGAPI(ci.policies(), 'create',
|
result = callGAPI(ci.policies(), 'create',
|
||||||
bailOnInternalError=True,
|
bailOnInternalError=True,
|
||||||
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.UNIMPLEMENTED_ERROR,
|
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT, GAPI.UNIMPLEMENTED_ERROR,
|
||||||
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||||
body=jsonData)
|
body=policy)
|
||||||
if result['done']:
|
if result['done']:
|
||||||
if 'error' not in result:
|
if 'error' not in result:
|
||||||
if not updateCmd:
|
if not updateCmd:
|
||||||
@@ -37943,7 +37994,7 @@ def doDeleteCIPolicies():
|
|||||||
Ind.Decrement()
|
Ind.Decrement()
|
||||||
|
|
||||||
# gam info policies <CIPolicyNameEntity>
|
# gam info policies <CIPolicyNameEntity>
|
||||||
# [nowarnings] [noappnames]
|
# [nowarnings] [noappnames] [noidmappiong]
|
||||||
# [formatjson]
|
# [formatjson]
|
||||||
def doInfoCIPolicies():
|
def doInfoCIPolicies():
|
||||||
_checkPoliciesWithDASA()
|
_checkPoliciesWithDASA()
|
||||||
@@ -37953,13 +38004,15 @@ def doInfoCIPolicies():
|
|||||||
entityList = getEntityList(Cmd.OB_CIPOLICY_NAME_ENTITY)
|
entityList = getEntityList(Cmd.OB_CIPOLICY_NAME_ENTITY)
|
||||||
FJQC = FormatJSONQuoteChar()
|
FJQC = FormatJSONQuoteChar()
|
||||||
add_warnings = True
|
add_warnings = True
|
||||||
no_appnames = False
|
no_appnames = no_idmapping = False
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
if myarg == 'nowarnings':
|
if myarg == 'nowarnings':
|
||||||
add_warnings = False
|
add_warnings = False
|
||||||
elif myarg == 'noappnames':
|
elif myarg == 'noappnames':
|
||||||
no_appnames=True
|
no_appnames = True
|
||||||
|
elif myarg == 'noidmapping':
|
||||||
|
no_idmapping = True
|
||||||
else:
|
else:
|
||||||
FJQC.GetFormatJSON(myarg)
|
FJQC.GetFormatJSON(myarg)
|
||||||
i = 0
|
i = 0
|
||||||
@@ -37972,7 +38025,7 @@ def doInfoCIPolicies():
|
|||||||
bailOnInternalError=True,
|
bailOnInternalError=True,
|
||||||
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT,
|
throwReasons=[GAPI.INVALID, GAPI.INVALID_ARGUMENT,
|
||||||
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
GAPI.NOT_FOUND, GAPI.PERMISSION_DENIED, GAPI.INTERNAL_ERROR],
|
||||||
name=pname, fields='name,policyQuery(group,orgUnit,sortOrder),type,setting')]
|
name=pname)]
|
||||||
except (GAPI.invalid, GAPI.invalidArgument, GAPI.notFound, GAPI.permissionDenied, GAPI.internalError) as e:
|
except (GAPI.invalid, GAPI.invalidArgument, GAPI.notFound, GAPI.permissionDenied, GAPI.internalError) as e:
|
||||||
entityActionFailedWarning([Ent.POLICY, pname], str(e), i, count)
|
entityActionFailedWarning([Ent.POLICY, pname], str(e), i, count)
|
||||||
continue
|
continue
|
||||||
@@ -37982,15 +38035,15 @@ def doInfoCIPolicies():
|
|||||||
ifilter = f"setting.type.matches('{pname}')"
|
ifilter = f"setting.type.matches('{pname}')"
|
||||||
printGettingAllAccountEntities(Ent.POLICY, ifilter)
|
printGettingAllAccountEntities(Ent.POLICY, ifilter)
|
||||||
policies = _filterPolicies(ci, getPageMessage(), ifilter)
|
policies = _filterPolicies(ci, getPageMessage(), ifilter)
|
||||||
_showPolicies(policies, FJQC, add_warnings, no_appnames,
|
_showPolicies(policies, FJQC, add_warnings, no_appnames, no_idmapping,
|
||||||
None, None, cd, groups_ci)
|
None, None, cd, groups_ci)
|
||||||
|
|
||||||
# gam print policies [todrive <ToDriveAttribute>*]
|
# gam print policies [todrive <ToDriveAttribute>*]
|
||||||
# [filter <String>] [nowarnings] [noappnames]
|
# [filter <String>] [nowarnings] [noappnames] [noidmappiong]
|
||||||
# [group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
# [group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
# [formatjson [quotechar <Character>]]
|
# [formatjson [quotechar <Character>]]
|
||||||
# gam show policies
|
# gam show policies
|
||||||
# [filter <String>] [nowarnings] [noappnames]
|
# [filter <String>] [nowarnings] [noappnames] [noidmappiong]
|
||||||
# [group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
# [group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
# [formatjson]
|
# [formatjson]
|
||||||
def doPrintShowCIPolicies():
|
def doPrintShowCIPolicies():
|
||||||
@@ -38012,7 +38065,7 @@ def doPrintShowCIPolicies():
|
|||||||
FJQC = FormatJSONQuoteChar(csvPF)
|
FJQC = FormatJSONQuoteChar(csvPF)
|
||||||
ifilter = None
|
ifilter = None
|
||||||
add_warnings = True
|
add_warnings = True
|
||||||
no_appnames = False
|
no_appnames = no_idmapping = False
|
||||||
groupEmailPattern = orgUnitPathPattern = None
|
groupEmailPattern = orgUnitPathPattern = None
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
@@ -38024,6 +38077,8 @@ def doPrintShowCIPolicies():
|
|||||||
add_warnings = False
|
add_warnings = False
|
||||||
elif myarg == 'noappnames':
|
elif myarg == 'noappnames':
|
||||||
no_appnames = True
|
no_appnames = True
|
||||||
|
elif myarg == 'noidmapping':
|
||||||
|
no_idmapping = True
|
||||||
elif myarg == 'group':
|
elif myarg == 'group':
|
||||||
groupEmailPattern = getREPattern(re.IGNORECASE)
|
groupEmailPattern = getREPattern(re.IGNORECASE)
|
||||||
elif myarg in {'ou', 'org', 'orgunit'}:
|
elif myarg in {'ou', 'org', 'orgunit'}:
|
||||||
@@ -38033,14 +38088,12 @@ def doPrintShowCIPolicies():
|
|||||||
printGettingAllAccountEntities(Ent.POLICY, ifilter)
|
printGettingAllAccountEntities(Ent.POLICY, ifilter)
|
||||||
policies = _filterPolicies(ci, getPageMessage(), ifilter)
|
policies = _filterPolicies(ci, getPageMessage(), ifilter)
|
||||||
if not csvPF:
|
if not csvPF:
|
||||||
_showPolicies(policies, FJQC, add_warnings, no_appnames,
|
_showPolicies(policies, FJQC, add_warnings, no_appnames, no_idmapping,
|
||||||
groupEmailPattern, orgUnitPathPattern,
|
groupEmailPattern, orgUnitPathPattern, cd, groups_ci)
|
||||||
cd, groups_ci)
|
|
||||||
else:
|
else:
|
||||||
for policy in policies:
|
for policy in policies:
|
||||||
if _cleanPolicy(policy, add_warnings, no_appnames,
|
if _cleanPolicy(policy, add_warnings, no_appnames, no_idmapping,
|
||||||
groupEmailPattern, orgUnitPathPattern,
|
groupEmailPattern, orgUnitPathPattern, cd, groups_ci):
|
||||||
cd, groups_ci):
|
|
||||||
_printPolicy(policy)
|
_printPolicy(policy)
|
||||||
if csvPF:
|
if csvPF:
|
||||||
csvPF.writeCSVfile('Policies')
|
csvPF.writeCSVfile('Policies')
|
||||||
@@ -39660,7 +39713,7 @@ def doCreateFeature():
|
|||||||
callGAPI(cd.resources().features(), 'insert',
|
callGAPI(cd.resources().features(), 'insert',
|
||||||
throwReasons=[GAPI.DUPLICATE, GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
throwReasons=[GAPI.DUPLICATE, GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN],
|
||||||
customer=GC.Values[GC.CUSTOMER_ID], body=body)
|
customer=GC.Values[GC.CUSTOMER_ID], body=body)
|
||||||
entityActionPerformed([Ent.BUILDING, body['name']])
|
entityActionPerformed([Ent.FEATURE, body['name']])
|
||||||
except GAPI.duplicate:
|
except GAPI.duplicate:
|
||||||
entityDuplicateWarning([Ent.FEATURE, body['name']])
|
entityDuplicateWarning([Ent.FEATURE, body['name']])
|
||||||
except GAPI.invalidInput as e:
|
except GAPI.invalidInput as e:
|
||||||
@@ -46690,10 +46743,58 @@ USER_FIELDS_CHOICE_MAP = {
|
|||||||
'websites': 'websites',
|
'websites': 'websites',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
USER_MULTI_ATTR_FILTER_CHOICE_MAP = {
|
||||||
|
'address': 'addresses',
|
||||||
|
'addresses': 'addresses',
|
||||||
|
'email': 'emails',
|
||||||
|
'emails': 'emails',
|
||||||
|
'externalid': 'externalIds',
|
||||||
|
'externalids': 'externalIds',
|
||||||
|
'im': 'ims',
|
||||||
|
'ims': 'ims',
|
||||||
|
'keyword': 'keywords',
|
||||||
|
'keywords': 'keywords',
|
||||||
|
'location': 'locations',
|
||||||
|
'locations': 'locations',
|
||||||
|
'organization': 'organizations',
|
||||||
|
'organizations': 'organizations',
|
||||||
|
'organisation': 'organizations',
|
||||||
|
'organisations': 'organizations',
|
||||||
|
'otheremail': 'emails',
|
||||||
|
'otheremails': 'emails',
|
||||||
|
'phone': 'phones',
|
||||||
|
'phones': 'phones',
|
||||||
|
'relation': 'relations',
|
||||||
|
'relations': 'relations',
|
||||||
|
'website': 'websites',
|
||||||
|
'websites': 'websites',
|
||||||
|
}
|
||||||
|
|
||||||
INFO_USER_OPTIONS = {'noaliases', 'nobuildingnames', 'nogroups', 'nolicenses', 'nolicences', 'noschemas', 'schemas', 'userview'}
|
INFO_USER_OPTIONS = {'noaliases', 'nobuildingnames', 'nogroups', 'nolicenses', 'nolicences', 'noschemas', 'schemas', 'userview'}
|
||||||
USER_SKIP_OBJECTS = {'thumbnailPhotoEtag'}
|
USER_SKIP_OBJECTS = {'thumbnailPhotoEtag'}
|
||||||
USER_TIME_OBJECTS = {'creationTime', 'deletionTime', 'lastLoginTime'}
|
USER_TIME_OBJECTS = {'creationTime', 'deletionTime', 'lastLoginTime'}
|
||||||
|
|
||||||
|
def _getUserMultiAttributeFilters(myarg, userMultiAttributeFilters):
|
||||||
|
up = getChoice(USER_MULTI_ATTR_FILTER_CHOICE_MAP, mapChoice=True)
|
||||||
|
filterValue = getString(Cmd.OB_STRING)
|
||||||
|
userMultiAttributeFilters.setdefault(up, [])
|
||||||
|
if myarg == 'filtermultiattrtype':
|
||||||
|
userMultiAttributeFilters[up].append({'type': filterValue})
|
||||||
|
else: #elif myarg == 'filtermultiattrcustom':
|
||||||
|
userMultiAttributeFilters[up].append({'customType': filterValue})
|
||||||
|
|
||||||
|
def _filterUserMultiAttributes(user, userMultiAttributeFilters):
|
||||||
|
for up, upTypes in userMultiAttributeFilters.items():
|
||||||
|
if up in user:
|
||||||
|
filterAttrList = []
|
||||||
|
for userAttr in user.pop(up):
|
||||||
|
for upType in upTypes:
|
||||||
|
if ((userAttr.get('type', None) == upType.get('type', '')) or
|
||||||
|
(userAttr.get('customType', None) == upType.get('customType', ''))):
|
||||||
|
filterAttrList.append(userAttr)
|
||||||
|
break
|
||||||
|
user[up] = filterAttrList
|
||||||
|
|
||||||
def _formatLanguagesList(propertyValue, delimiter):
|
def _formatLanguagesList(propertyValue, delimiter):
|
||||||
languages = []
|
languages = []
|
||||||
for language in propertyValue:
|
for language in propertyValue:
|
||||||
@@ -46788,6 +46889,7 @@ def infoUsers(entityList):
|
|||||||
fieldsList = []
|
fieldsList = []
|
||||||
groups = []
|
groups = []
|
||||||
memberships = []
|
memberships = []
|
||||||
|
userMultiAttributeFilters = {}
|
||||||
skus = SKU.getAllSKUs() if not GM.Globals[GM.LICENSE_SKUS] else GM.Globals[GM.LICENSE_SKUS]
|
skus = SKU.getAllSKUs() if not GM.Globals[GM.LICENSE_SKUS] else GM.Globals[GM.LICENSE_SKUS]
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
@@ -46822,6 +46924,8 @@ def infoUsers(entityList):
|
|||||||
getGroups = getLicenses = False
|
getGroups = getLicenses = False
|
||||||
elif getFieldsList(myarg, USER_FIELDS_CHOICE_MAP, fieldsList):
|
elif getFieldsList(myarg, USER_FIELDS_CHOICE_MAP, fieldsList):
|
||||||
pass
|
pass
|
||||||
|
elif myarg in {'filtermultiattrtype', 'filtermultiattrcustom'}:
|
||||||
|
_getUserMultiAttributeFilters(myarg, userMultiAttributeFilters)
|
||||||
# Ignore info group arguments that may have come from whatis
|
# Ignore info group arguments that may have come from whatis
|
||||||
elif myarg in INFO_GROUP_OPTIONS:
|
elif myarg in INFO_GROUP_OPTIONS:
|
||||||
pass
|
pass
|
||||||
@@ -46849,6 +46953,8 @@ def infoUsers(entityList):
|
|||||||
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND],
|
throwReasons=GAPI.USER_GET_THROW_REASONS+[GAPI.INVALID_INPUT, GAPI.RESOURCE_NOT_FOUND],
|
||||||
userKey=userEmail, projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
|
userKey=userEmail, projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'],
|
||||||
viewType=viewType, fields=fields)
|
viewType=viewType, fields=fields)
|
||||||
|
if userMultiAttributeFilters:
|
||||||
|
_filterUserMultiAttributes(user, userMultiAttributeFilters)
|
||||||
groups = []
|
groups = []
|
||||||
memberships = []
|
memberships = []
|
||||||
if getGroups or getGroupsTree:
|
if getGroups or getGroupsTree:
|
||||||
@@ -47286,6 +47392,8 @@ def doPrintUsers(entityList=None):
|
|||||||
return
|
return
|
||||||
if showValidColumn:
|
if showValidColumn:
|
||||||
userEntity[showValidColumn] = True
|
userEntity[showValidColumn] = True
|
||||||
|
if userMultiAttributeFilters:
|
||||||
|
_filterUserMultiAttributes(userEntity, userMultiAttributeFilters)
|
||||||
userEmail = userEntity['primaryEmail']
|
userEmail = userEntity['primaryEmail']
|
||||||
if printOptions['emailParts']:
|
if printOptions['emailParts']:
|
||||||
if userEmail.find('@') != -1:
|
if userEmail.find('@') != -1:
|
||||||
@@ -47428,6 +47536,7 @@ def doPrintUsers(entityList=None):
|
|||||||
showItemCountOnly = False
|
showItemCountOnly = False
|
||||||
addCSVData = {}
|
addCSVData = {}
|
||||||
includeCSVDataInJSON = False
|
includeCSVDataInJSON = False
|
||||||
|
userMultiAttributeFilters = {}
|
||||||
while Cmd.ArgumentsRemaining():
|
while Cmd.ArgumentsRemaining():
|
||||||
myarg = getArgument()
|
myarg = getArgument()
|
||||||
if myarg == 'todrive':
|
if myarg == 'todrive':
|
||||||
@@ -47507,6 +47616,8 @@ def doPrintUsers(entityList=None):
|
|||||||
getAddCSVData(addCSVData)
|
getAddCSVData(addCSVData)
|
||||||
elif myarg == 'includecsvdatainjson':
|
elif myarg == 'includecsvdatainjson':
|
||||||
includeCSVDataInJSON = getBoolean()
|
includeCSVDataInJSON = getBoolean()
|
||||||
|
elif myarg in {'filtermultiattrtype', 'filtermultiattrcustom'}:
|
||||||
|
_getUserMultiAttributeFilters(myarg, userMultiAttributeFilters)
|
||||||
else:
|
else:
|
||||||
FJQC.GetFormatJSONQuoteChar(myarg, False)
|
FJQC.GetFormatJSONQuoteChar(myarg, False)
|
||||||
_, _, entityList = getEntityArgument(entityList)
|
_, _, entityList = getEntityArgument(entityList)
|
||||||
@@ -58984,7 +59095,7 @@ SIZE_FIELD_CHOICE_MAP = {
|
|||||||
# [excludetrashed]
|
# [excludetrashed]
|
||||||
# [maxfiles <Integer>] [nodataheaders <String>]
|
# [maxfiles <Integer>] [nodataheaders <String>]
|
||||||
# [countsonly [summary none|only|plus] [summaryuser <String>]
|
# [countsonly [summary none|only|plus] [summaryuser <String>]
|
||||||
# [showsource] [showsize] [showmimetypesize]]
|
# [showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||||
# [countsrowfilter]
|
# [countsrowfilter]
|
||||||
# [filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
# [filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||||
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
# [allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||||
@@ -59011,7 +59122,7 @@ def printFileList(users):
|
|||||||
if 'mimeType' not in DFF.fieldsList:
|
if 'mimeType' not in DFF.fieldsList:
|
||||||
DFF.fieldsList.append('mimeType')
|
DFF.fieldsList.append('mimeType')
|
||||||
skipObjects.discard(sizeField)
|
skipObjects.discard(sizeField)
|
||||||
if showSize and sizeField not in DFF.fieldsList:
|
if (showSize or showSizeUnits) and sizeField not in DFF.fieldsList:
|
||||||
DFF.fieldsList.append(sizeField)
|
DFF.fieldsList.append(sizeField)
|
||||||
if (DLP.minimumFileSize is not None) or (DLP.maximumFileSize is not None):
|
if (DLP.minimumFileSize is not None) or (DLP.maximumFileSize is not None):
|
||||||
_setSkipObjects(skipObjects, [sizeField], DFF.fieldsList)
|
_setSkipObjects(skipObjects, [sizeField], DFF.fieldsList)
|
||||||
@@ -59209,6 +59320,8 @@ def printFileList(users):
|
|||||||
row['Name'] = sourceName
|
row['Name'] = sourceName
|
||||||
if showSize:
|
if showSize:
|
||||||
row['Size'] = sizeTotal
|
row['Size'] = sizeTotal
|
||||||
|
if showSizeUnits:
|
||||||
|
row['SizeUnits'] = formatFileSize(sizeTotal)
|
||||||
if addCSVData:
|
if addCSVData:
|
||||||
row.update(addCSVData)
|
row.update(addCSVData)
|
||||||
for mimeType, mtinfo in sorted(mimeTypeInfo.items()):
|
for mimeType, mtinfo in sorted(mimeTypeInfo.items()):
|
||||||
@@ -59225,7 +59338,7 @@ def printFileList(users):
|
|||||||
FJQC = FormatJSONQuoteChar(csvPF)
|
FJQC = FormatJSONQuoteChar(csvPF)
|
||||||
addPathsToJSON = continueOnInvalidQuery = countsRowFilter = buildTree = countsOnly = filepath = fullpath = folderPathOnly = \
|
addPathsToJSON = continueOnInvalidQuery = countsRowFilter = buildTree = countsOnly = filepath = fullpath = folderPathOnly = \
|
||||||
getPermissionsForSharedDrives = mimeTypeInQuery = noRecursion = oneItemPerRow = stripCRsFromName = \
|
getPermissionsForSharedDrives = mimeTypeInQuery = noRecursion = oneItemPerRow = stripCRsFromName = \
|
||||||
showParentsIdsAsList = showDepth = showParent = showSize = showMimeTypeSize = showSource = False
|
showParentsIdsAsList = showDepth = showParent = showSize = showSizeUnits = showMimeTypeSize = showSource = False
|
||||||
sizeField = 'quotaBytesUsed'
|
sizeField = 'quotaBytesUsed'
|
||||||
pathDelimiter = '/'
|
pathDelimiter = '/'
|
||||||
pmselect = True
|
pmselect = True
|
||||||
@@ -59298,22 +59411,12 @@ def printFileList(users):
|
|||||||
summaryUser = getString(Cmd.OB_STRING)
|
summaryUser = getString(Cmd.OB_STRING)
|
||||||
elif myarg == 'showsource':
|
elif myarg == 'showsource':
|
||||||
showSource = True
|
showSource = True
|
||||||
if countsOnly:
|
elif myarg == 'showsize':
|
||||||
if not showSize:
|
|
||||||
csvPFco.SetTitles(['Owner', 'Source', 'Name', 'Total'])
|
|
||||||
else:
|
|
||||||
csvPFco.SetTitles(['Owner', 'Source', 'Name', 'Total', 'Size'])
|
|
||||||
csvPFco.SetSortAllTitles()
|
|
||||||
elif myarg in {'showsize', 'showmimetypesize'}:
|
|
||||||
showSize = True
|
showSize = True
|
||||||
if countsOnly:
|
elif myarg == 'showsizeunits':
|
||||||
if not showSource:
|
showSizeUnits = True
|
||||||
csvPFco.SetTitles(['Owner', 'Total', 'Size'])
|
elif myarg == 'showmimetypesize':
|
||||||
else:
|
showMimeTypeSize = showSize = True
|
||||||
csvPFco.SetTitles(['Owner', 'Source', 'Name', 'Total', 'Size'])
|
|
||||||
csvPFco.SetSortAllTitles()
|
|
||||||
if myarg == 'showmimetypesize':
|
|
||||||
showMimeTypeSize = True
|
|
||||||
elif myarg == 'sizefield':
|
elif myarg == 'sizefield':
|
||||||
sizeField = getChoice(SIZE_FIELD_CHOICE_MAP, mapChoice=True)
|
sizeField = getChoice(SIZE_FIELD_CHOICE_MAP, mapChoice=True)
|
||||||
elif myarg == 'delimiter':
|
elif myarg == 'delimiter':
|
||||||
@@ -59344,6 +59447,15 @@ def printFileList(users):
|
|||||||
continueOnInvalidQuery = getBoolean()
|
continueOnInvalidQuery = getBoolean()
|
||||||
else:
|
else:
|
||||||
FJQC.GetFormatJSONQuoteChar(myarg)
|
FJQC.GetFormatJSONQuoteChar(myarg)
|
||||||
|
if countsOnly:
|
||||||
|
titles = ['Owner', 'Total'] if not showSource else ['Owner', 'Source', 'Name', 'Total']
|
||||||
|
if showSize:
|
||||||
|
titles.append('Size')
|
||||||
|
if showSizeUnits:
|
||||||
|
titles.append('SizeUnits')
|
||||||
|
csvPFco.SetTitles(titles)
|
||||||
|
csvPFco.SetSortAllTitles()
|
||||||
|
|
||||||
if not filepath and not fullpath:
|
if not filepath and not fullpath:
|
||||||
showDepth = False
|
showDepth = False
|
||||||
noSelect = noFileSelectFileIdEntity(fileIdEntity)
|
noSelect = noFileSelectFileIdEntity(fileIdEntity)
|
||||||
@@ -59409,9 +59521,11 @@ def printFileList(users):
|
|||||||
csvPF.AddJSONTitles(sorted(addCSVData.keys()))
|
csvPF.AddJSONTitles(sorted(addCSVData.keys()))
|
||||||
else:
|
else:
|
||||||
csvPFco.AddTitles(sorted(addCSVData.keys()))
|
csvPFco.AddTitles(sorted(addCSVData.keys()))
|
||||||
|
csvPFco.MoveTitlesToEnd(['Total'])
|
||||||
if showSize:
|
if showSize:
|
||||||
csvPFco.MoveTitlesToEnd(['Size'])
|
csvPFco.MoveTitlesToEnd(['Size'])
|
||||||
csvPFco.MoveTitlesToEnd(['Total'])
|
if showSizeUnits:
|
||||||
|
csvPFco.MoveTitlesToEnd(['SizeUnits'])
|
||||||
csvPFco.SetSortAllTitles()
|
csvPFco.SetSortAllTitles()
|
||||||
if filepath and not countsOnly:
|
if filepath and not countsOnly:
|
||||||
csvPF.AddTitles('paths')
|
csvPF.AddTitles('paths')
|
||||||
@@ -60119,7 +60233,7 @@ def _updateLastModificationRow(row, lastModification):
|
|||||||
# [filenamematchpattern <REMatchPattern>]
|
# [filenamematchpattern <REMatchPattern>]
|
||||||
# <PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
# <PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
# [excludetrashed] (addcsvdata <FieldName> <String>)*
|
# [excludetrashed] (addcsvdata <FieldName> <String>)*
|
||||||
# [showsize] [showmimetypesize]
|
# [showsize] [showsizeunits] [showmimetypesize]
|
||||||
# [showlastmodification] [pathdelimiter <Character>]
|
# [showlastmodification] [pathdelimiter <Character>]
|
||||||
# (addcsvdata <FieldName> <String>)*
|
# (addcsvdata <FieldName> <String>)*
|
||||||
# [summary none|only|plus] [summaryuser <String>]
|
# [summary none|only|plus] [summaryuser <String>]
|
||||||
@@ -60134,14 +60248,14 @@ def _updateLastModificationRow(row, lastModification):
|
|||||||
# [filenamematchpattern <REMatchPattern>]
|
# [filenamematchpattern <REMatchPattern>]
|
||||||
# <PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
# <PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
# [excludetrashed]
|
# [excludetrashed]
|
||||||
# [showsize] [showmimetypesize]
|
# [showsize] [showsizeunits] [showmimetypesize]
|
||||||
# [showlastmodification] [pathdelimiter <Character>]
|
# [showlastmodification] [pathdelimiter <Character>]
|
||||||
# [summary none|only|plus] [summaryuser <String>]
|
# [summary none|only|plus] [summaryuser <String>]
|
||||||
def printShowFileCounts(users):
|
def printShowFileCounts(users):
|
||||||
def _setSelectionFields():
|
def _setSelectionFields():
|
||||||
if DLP.showOwnedBy is not None:
|
if DLP.showOwnedBy is not None:
|
||||||
fieldsList.extend(OWNED_BY_ME_FIELDS_TITLES)
|
fieldsList.extend(OWNED_BY_ME_FIELDS_TITLES)
|
||||||
if showSize or (DLP.minimumFileSize is not None) or (DLP.maximumFileSize is not None):
|
if (showSize or showSizeUnits) or (DLP.minimumFileSize is not None) or (DLP.maximumFileSize is not None):
|
||||||
fieldsList.append(sizeField)
|
fieldsList.append(sizeField)
|
||||||
if showLastModification:
|
if showLastModification:
|
||||||
fieldsList.extend(['id,name,modifiedTime,lastModifyingUser(me, displayName, emailAddress),parents'])
|
fieldsList.extend(['id,name,modifiedTime,lastModifyingUser(me, displayName, emailAddress),parents'])
|
||||||
@@ -60174,7 +60288,9 @@ def printShowFileCounts(users):
|
|||||||
kvList = [Ent.USER, user]
|
kvList = [Ent.USER, user]
|
||||||
dataList = [Ent.Choose(Ent.DRIVE_FILE_OR_FOLDER, countTotal), countTotal]
|
dataList = [Ent.Choose(Ent.DRIVE_FILE_OR_FOLDER, countTotal), countTotal]
|
||||||
if showSize:
|
if showSize:
|
||||||
dataList.extend([Ent.Singular(Ent.SIZE), sizeTotal])
|
dataList.extend(['Size', sizeTotal])
|
||||||
|
if showSizeUnits:
|
||||||
|
dataList.extend(['SizeUnits', formatFileSize(sizeTotal)])
|
||||||
if sharedDriveId:
|
if sharedDriveId:
|
||||||
dataList.extend(['Item cap', f"{countTotal/SHARED_DRIVE_MAX_FILES_FOLDERS:.2%}"])
|
dataList.extend(['Item cap', f"{countTotal/SHARED_DRIVE_MAX_FILES_FOLDERS:.2%}"])
|
||||||
printEntityKVList(kvList, dataList, i, count)
|
printEntityKVList(kvList, dataList, i, count)
|
||||||
@@ -60194,6 +60310,8 @@ def printShowFileCounts(users):
|
|||||||
row = {'User': user, 'Total': countTotal}
|
row = {'User': user, 'Total': countTotal}
|
||||||
if showSize:
|
if showSize:
|
||||||
row['Size'] = sizeTotal
|
row['Size'] = sizeTotal
|
||||||
|
if showSizeUnits:
|
||||||
|
row['SizeUnits'] = formatFileSize(sizeTotal)
|
||||||
if showLastModification:
|
if showLastModification:
|
||||||
_updateLastModificationRow(row, lastModification)
|
_updateLastModificationRow(row, lastModification)
|
||||||
if addCSVData:
|
if addCSVData:
|
||||||
@@ -60211,7 +60329,7 @@ def printShowFileCounts(users):
|
|||||||
DLP = DriveListParameters({'allowChoose': False, 'allowCorpora': True, 'allowQuery': True, 'mimeTypeInQuery': True})
|
DLP = DriveListParameters({'allowChoose': False, 'allowCorpora': True, 'allowQuery': True, 'mimeTypeInQuery': True})
|
||||||
pathDelimiter = '/'
|
pathDelimiter = '/'
|
||||||
sharedDriveId = sharedDriveName = ''
|
sharedDriveId = sharedDriveName = ''
|
||||||
continueOnInvalidQuery = showSize = showLastModification = showMimeTypeSize = False
|
continueOnInvalidQuery = showSize = showSizeUnits = showLastModification = showMimeTypeSize = False
|
||||||
sizeField = 'quotaBytesUsed'
|
sizeField = 'quotaBytesUsed'
|
||||||
summary = FILECOUNT_SUMMARY_NONE
|
summary = FILECOUNT_SUMMARY_NONE
|
||||||
summaryUser = FILECOUNT_SUMMARY_USER
|
summaryUser = FILECOUNT_SUMMARY_USER
|
||||||
@@ -60231,12 +60349,14 @@ def printShowFileCounts(users):
|
|||||||
fileIdEntity = getSharedDriveEntity()
|
fileIdEntity = getSharedDriveEntity()
|
||||||
elif myarg == 'showsize':
|
elif myarg == 'showsize':
|
||||||
showSize = True
|
showSize = True
|
||||||
|
elif myarg == 'showsizeunits':
|
||||||
|
showSizeUnits = True
|
||||||
|
elif myarg == 'showmimetypesize':
|
||||||
|
showMimeTypeSize = showSize = True
|
||||||
elif myarg == 'sizefield':
|
elif myarg == 'sizefield':
|
||||||
sizeField = getChoice(SIZE_FIELD_CHOICE_MAP, mapChoice=True)
|
sizeField = getChoice(SIZE_FIELD_CHOICE_MAP, mapChoice=True)
|
||||||
elif myarg == 'showlastmodification':
|
elif myarg == 'showlastmodification':
|
||||||
showLastModification = True
|
showLastModification = True
|
||||||
elif myarg == 'showmimetypesize':
|
|
||||||
showMimeTypeSize = showSize = True
|
|
||||||
elif myarg == 'summary':
|
elif myarg == 'summary':
|
||||||
summary = getChoice(FILECOUNT_SUMMARY_CHOICE_MAP, mapChoice=True)
|
summary = getChoice(FILECOUNT_SUMMARY_CHOICE_MAP, mapChoice=True)
|
||||||
elif myarg == 'summaryuser':
|
elif myarg == 'summaryuser':
|
||||||
@@ -60265,6 +60385,8 @@ def printShowFileCounts(users):
|
|||||||
_setSelectionFields()
|
_setSelectionFields()
|
||||||
if csvPF:
|
if csvPF:
|
||||||
sortTitles = ['User', 'id', 'name', 'Total', 'Item cap'] if fileIdEntity.get('shareddrive') else ['User', 'Total']
|
sortTitles = ['User', 'id', 'name', 'Total', 'Item cap'] if fileIdEntity.get('shareddrive') else ['User', 'Total']
|
||||||
|
if showSizeUnits:
|
||||||
|
sortTitles.insert(sortTitles.index('Total')+1, 'SizeUnits')
|
||||||
if showSize:
|
if showSize:
|
||||||
sortTitles.insert(sortTitles.index('Total')+1, 'Size')
|
sortTitles.insert(sortTitles.index('Total')+1, 'Size')
|
||||||
if showLastModification:
|
if showLastModification:
|
||||||
|
|||||||
@@ -1,3 +1,72 @@
|
|||||||
|
#
|
||||||
|
# This is a custom certificate authority bundle for GAM
|
||||||
|
# It's composed of Let's Encrypt Root CAs and Google's
|
||||||
|
# certificate bundle. This should be the minimal list of
|
||||||
|
# CAs required to talk to Google and Github.
|
||||||
|
|
||||||
|
# Operating CA: Let's Encrypt
|
||||||
|
# Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||||
|
# Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X1
|
||||||
|
# Label: "ISRG Root X1"
|
||||||
|
# Serial: 172886928669790476064670243504169061120
|
||||||
|
# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e
|
||||||
|
# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8
|
||||||
|
# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
|
||||||
|
TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
|
||||||
|
cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
|
||||||
|
WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
|
||||||
|
ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
|
||||||
|
MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
|
||||||
|
h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
|
||||||
|
0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
|
||||||
|
A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
|
||||||
|
T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
|
||||||
|
B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
|
||||||
|
B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
|
||||||
|
KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
|
||||||
|
OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
|
||||||
|
jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
|
||||||
|
qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
|
||||||
|
rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
|
||||||
|
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
|
||||||
|
ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
|
||||||
|
3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
|
||||||
|
NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
|
||||||
|
ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
|
||||||
|
TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
|
||||||
|
jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
|
||||||
|
oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
|
||||||
|
4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
|
||||||
|
mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
|
||||||
|
emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
|
# Operating CA: Let's Encrypt
|
||||||
|
# Issuer: C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||||
|
# Subject: C = US, O = Internet Security Research Group, CN = ISRG Root X2
|
||||||
|
# Label: "ISRG Root X2"
|
||||||
|
# Serial: 87493402998870891108772069816698636114
|
||||||
|
# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5
|
||||||
|
# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af
|
||||||
|
# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw
|
||||||
|
CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg
|
||||||
|
R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00
|
||||||
|
MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT
|
||||||
|
ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw
|
||||||
|
EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW
|
||||||
|
+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9
|
||||||
|
ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T
|
||||||
|
AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI
|
||||||
|
zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW
|
||||||
|
tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1
|
||||||
|
/q4AaOeMSQ+2b1tbFfLn
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
|
||||||
# Operating CA: DigiCert
|
# Operating CA: DigiCert
|
||||||
# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
|
# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
|
||||||
# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
|
# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com
|
||||||
|
|||||||
@@ -1,128 +1,147 @@
|
|||||||
// Node.js script that implements an Appium client which will launch
|
// Node.js script to launch Simply Sign Desktop app and log a user in
|
||||||
// Simply Sign Desktop app and log a user in. Once logged in it should
|
// using native Windows keystrokes and screenshot-desktop for reliable CI imaging.
|
||||||
// be possible to use tools like signtool.exe to sign Windows EXE/MSI files
|
|
||||||
// with the Certum certificate.
|
|
||||||
|
|
||||||
import { Key, remote } from 'webdriverio';
|
import { execSync, spawn } from 'child_process';
|
||||||
import { exec } from 'child_process';
|
|
||||||
import { TOTP } from 'totp-generator';
|
import { TOTP } from 'totp-generator';
|
||||||
|
import path from 'path';
|
||||||
async function screenshot(driver, filename) {
|
import fs from 'fs';
|
||||||
// uncomment to save .png screenshots
|
|
||||||
await driver.saveScreenshot(filename);
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep(ms) {
|
function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeCommand(command) {
|
// Native PowerShell Keystroke Sender
|
||||||
|
function sendKeys(keys) {
|
||||||
|
const script = `$wshell = New-Object -ComObject wscript.shell; $wshell.SendKeys('${keys}')`;
|
||||||
|
execSync(`powershell -Command "${script}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Native PowerShell Desktop Clear
|
||||||
|
function minimizeAllWindows() {
|
||||||
|
console.log('Minimizing all rogue background windows...');
|
||||||
|
const script = `$shell = New-Object -ComObject "Shell.Application"; $shell.MinimizeAll()`;
|
||||||
try {
|
try {
|
||||||
let { stdout, stderr } = await exec(command);
|
execSync(`powershell -Command "${script}"`);
|
||||||
return stdout;
|
} catch (err) {
|
||||||
} catch (error) {
|
console.log('Minimize command failed silently.');
|
||||||
console.error(`Error executing command: ${command}`);
|
|
||||||
console.error(`Error details: ${error}`);
|
|
||||||
throw error;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runSSD() {
|
async function takeScreenshot(filename) {
|
||||||
const opts = {
|
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||||
port: 4723,
|
const fullPath = path.join(workspace, filename);
|
||||||
logLevel: "silent",
|
|
||||||
capabilities: {
|
|
||||||
platformName: "Windows",
|
|
||||||
"appium:app": "C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe",
|
|
||||||
"appium:automationName": "Windows",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
let driver;
|
const psScript = `
|
||||||
try {
|
Add-Type -AssemblyName System.Windows.Forms;
|
||||||
driver = await remote(opts);
|
Add-Type -AssemblyName System.Drawing;
|
||||||
|
$Screen = [System.Windows.Forms.SystemInformation]::VirtualScreen;
|
||||||
// Github Actions Win ARM64 is stuck on a OOB screen that steals focus
|
|
||||||
// These enter / escapes should dismiss it.
|
if ($Screen.Width -eq 0 -or $Screen.Height -eq 0) {
|
||||||
const runner_arch = process.env.RUNNER_ARCH;
|
Write-Error "Screen dimensions are 0x0. Desktop not fully initialized.";
|
||||||
if ( runner_arch === "ARM64" ) {
|
exit 1;
|
||||||
console.log('Running on ARM64...');
|
|
||||||
await sleep(3000); // Pause execution for 3 seconds
|
|
||||||
await screenshot(driver, 'oob1.png');
|
|
||||||
await driver.sendKeys([Key.Enter]);
|
|
||||||
await sleep(3000); // Pause execution for 3 seconds
|
|
||||||
await screenshot(driver, 'oob2.png');
|
|
||||||
await driver.sendKeys([Key.Enter]);
|
|
||||||
await sleep(3000); // Pause execution for 3 seconds
|
|
||||||
await screenshot(driver, 'oob3.png');
|
|
||||||
await driver.sendKeys([Key.Escape]);
|
|
||||||
await screenshot(driver, 'oob6.png');
|
|
||||||
} else {
|
|
||||||
console.log('NOT running on ARM64');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute SSD again to open login dialog
|
|
||||||
exec('"C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe"', (error, stdout, stderr) => {
|
|
||||||
if (error) {
|
|
||||||
console.error(`exec error: ${error}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await sleep(3000);
|
|
||||||
|
|
||||||
// Login
|
|
||||||
const windows = await driver.getWindowHandles();
|
|
||||||
const login_window = windows[0]
|
|
||||||
await driver.switchWindow(login_window);
|
|
||||||
await screenshot(driver, 'login01.png');
|
|
||||||
const id_value = 'jay0lee@gmail.com';
|
|
||||||
const id_arr = [...id_value];
|
|
||||||
await driver.sendKeys(id_arr);
|
|
||||||
await screenshot(driver, 'login02.png');
|
|
||||||
await driver.sendKeys([Key.Tab]);
|
|
||||||
console.log('Our secret is ' + process.env.TOTP_SECRET.length + ' characters.');
|
|
||||||
// We wait until the last possible second to generate
|
|
||||||
// our TOTP to ensure it's still valid.
|
|
||||||
const { otp } = await TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'});
|
|
||||||
console.log('Our token is ' + otp.length + ' characters.');
|
|
||||||
const otp_arr = [...otp];
|
|
||||||
await driver.sendKeys(otp_arr);
|
|
||||||
await screenshot(driver, 'login03.png');
|
|
||||||
await driver.sendKeys([Key.Enter]);
|
|
||||||
|
|
||||||
// TODO: it's expected that on successful login the window
|
|
||||||
// will close and these screenshots will error out. Figure
|
|
||||||
// out how to handle that gracefully.
|
|
||||||
await screenshot(driver, 'login04.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login05.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login06.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login07.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login08.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login09.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login10.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login11.png');
|
|
||||||
await sleep(500);
|
|
||||||
await screenshot(driver, 'login12.png');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
//console.error("Error during Appium run:");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$bitmap = New-Object System.Drawing.Bitmap $Screen.Width, $Screen.Height;
|
||||||
|
$graphic = [System.Drawing.Graphics]::FromImage($bitmap);
|
||||||
|
$graphic.CopyFromScreen($Screen.Left, $Screen.Top, 0, 0, $bitmap.Size);
|
||||||
|
$bitmap.Save('${fullPath}');
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
execSync(`powershell -Command "${psScript}"`);
|
||||||
|
console.log(`Saved screenshot: ${fullPath}`);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Failed to save screenshot ${fullPath}:`, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// INTENTIONAL Keep driver open so tray icon for Certum doesn't close
|
// Fire and forget application launcher
|
||||||
// finally {
|
function launchSSD() {
|
||||||
// if (driver) {
|
const child = spawn('C:\\Program Files\\Certum\\SimplySign Desktop\\SimplySignDesktop.exe', [], {
|
||||||
// await driver.deleteSession(); // Close the Appium session
|
detached: true,
|
||||||
// }
|
stdio: 'ignore'
|
||||||
//}
|
});
|
||||||
|
child.unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runSSD() {
|
||||||
|
await takeScreenshot('001.png');
|
||||||
|
minimizeAllWindows();
|
||||||
|
await sleep(2000);
|
||||||
|
await takeScreenshot('002.png');
|
||||||
|
sendKeys('{ESC}');
|
||||||
|
await sleep(2000);
|
||||||
|
await takeScreenshot('003.png');
|
||||||
|
//sendKeys('{ESC}');
|
||||||
|
//await sleep(2000);
|
||||||
|
//await takeScreenshot('004.png');
|
||||||
|
//sendKeys('{ESC}');
|
||||||
|
//await sleep(2000);
|
||||||
|
//await takeScreenshot('005.png');
|
||||||
|
//sendKeys('%{F4}');
|
||||||
|
//await sleep(2000);
|
||||||
|
//await takeScreenshot('006.png');
|
||||||
|
//sendKeys('%{F4}');
|
||||||
|
//await sleep(2000);
|
||||||
|
//await takeScreenshot('007.png');
|
||||||
|
|
||||||
|
// Re-execute SSD to open login dialog
|
||||||
|
launchSSD();
|
||||||
|
await sleep(3000);
|
||||||
|
await takeScreenshot('008.png');
|
||||||
|
launchSSD();
|
||||||
|
await sleep(3000);
|
||||||
|
await takeScreenshot('009.png');
|
||||||
|
|
||||||
|
// 2. Login Flow
|
||||||
|
console.log('Typing credentials...');
|
||||||
|
|
||||||
|
// Type Email
|
||||||
|
sendKeys('jay0lee@gmail.com');
|
||||||
|
await sleep(500);
|
||||||
|
await takeScreenshot('010.png');
|
||||||
|
|
||||||
|
// Tab to next field
|
||||||
|
sendKeys('{TAB}');
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
// Generate and type TOTP
|
||||||
|
console.log(`Our secret is ${process.env.TOTP_SECRET.length} characters.`);
|
||||||
|
const { otp } = await TOTP.generate(process.env.TOTP_SECRET, {algorithm: 'SHA-256'});
|
||||||
|
console.log(`Our token is ${otp.length} characters.`);
|
||||||
|
|
||||||
|
sendKeys(otp);
|
||||||
|
await sleep(500);
|
||||||
|
await takeScreenshot('011.png');
|
||||||
|
|
||||||
|
// Submit
|
||||||
|
sendKeys('{ENTER}');
|
||||||
|
console.log('Login sequence complete.');
|
||||||
|
|
||||||
|
// Screenshot cascade to monitor the window closing
|
||||||
|
await takeScreenshot('012.png');
|
||||||
|
await sleep(500);
|
||||||
|
await takeScreenshot('013.png');
|
||||||
|
await sleep(500);
|
||||||
|
await takeScreenshot('014.png');
|
||||||
|
await sleep(500);
|
||||||
|
|
||||||
|
|
||||||
|
console.log('Exiting script, leaving SimplySign running in background.');
|
||||||
|
|
||||||
|
// Verification block to list all PNGs in the workspace
|
||||||
|
console.log('\n--- Screenshot Verification ---');
|
||||||
|
const workspace = process.env.GITHUB_WORKSPACE || process.cwd();
|
||||||
|
try {
|
||||||
|
const files = fs.readdirSync(workspace);
|
||||||
|
const pngFiles = files.filter(f => f.endsWith('.png'));
|
||||||
|
console.log(`Target Directory: ${workspace}`);
|
||||||
|
console.log(`Found ${pngFiles.length} .png files:`);
|
||||||
|
pngFiles.forEach(f => console.log(` - ${f}`));
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error reading directory ${workspace}:`, err.message);
|
||||||
|
}
|
||||||
|
console.log('-------------------------------\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
runSSD();
|
runSSD();
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ See: https://cloud.google.com/identity/docs/concepts/supported-policy-api-settin
|
|||||||
Display selected policies.
|
Display selected policies.
|
||||||
```
|
```
|
||||||
gam info policies <CIPolicyEntity>
|
gam info policies <CIPolicyEntity>
|
||||||
[nowarnings] [noappnames]
|
[nowarnings] [noappnames] [noidmappimg]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -69,16 +69,20 @@ Select policies::
|
|||||||
|
|
||||||
By default, policy warnings are displayed, use the 'nowarnings` option to suppress their display.
|
By default, policy warnings are displayed, use the 'nowarnings` option to suppress their display.
|
||||||
|
|
||||||
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
||||||
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
||||||
|
|
||||||
|
By default, additional API calls are made to add the `policyQuery/groupEmail` and `policyQuery/orgUnitPath` fields
|
||||||
|
that are mapped from the `policyQuery/group` and `policyQuery/orgUnit` fields. Use option `noidmapping'
|
||||||
|
to suppress these calls and not add the additional fields.
|
||||||
|
|
||||||
By default, Gam displays the information as an indented list of keys and values.
|
By default, Gam displays the information as an indented list of keys and values.
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
|
|
||||||
Display all or filtered policies.
|
Display all or filtered policies.
|
||||||
```
|
```
|
||||||
gam show policies
|
gam show policies
|
||||||
[filter <String>] [nowarnings] [noappnames]
|
[filter <String>] [nowarnings] [noappnames] [noidmappimg]
|
||||||
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
[formatjson]
|
[formatjson]
|
||||||
```
|
```
|
||||||
@@ -92,12 +96,16 @@ By default, policy warnings are displayed, use the `nowarnings` option to suppre
|
|||||||
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
||||||
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
||||||
|
|
||||||
|
By default, additional API calls are made to add the `policyQuery/groupEmail` and `policyQuery/orgUnitPath` fields
|
||||||
|
that are mapped from the `policyQuery/group` and `policyQuery/orgUnit` fields. Use option `noidmapping'
|
||||||
|
to suppress these calls and not add the additional fields.
|
||||||
|
|
||||||
By default, Gam displays the information as an indented list of keys and values.
|
By default, Gam displays the information as an indented list of keys and values.
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
|
|
||||||
```
|
```
|
||||||
gam print policies [todrive <ToDriveAttribute>*]
|
gam print policies [todrive <ToDriveAttribute>*]
|
||||||
[filter <String>] [nowarnings] [noappnames]
|
[filter <String>] [nowarnings] [noappnames] [noidmappimg]
|
||||||
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
[group <REMatchPattern>] [ou|org|orgunit <REMatchPattern>]
|
||||||
[formatjson [quotechar <Character>]]
|
[formatjson [quotechar <Character>]]
|
||||||
```
|
```
|
||||||
@@ -108,6 +116,10 @@ By default, all policies are displayed:
|
|||||||
|
|
||||||
By default, policy warnings are displayed, use the `nowarnings` option to suppress their display.
|
By default, policy warnings are displayed, use the `nowarnings` option to suppress their display.
|
||||||
|
|
||||||
|
By default, additional API calls are made to add the `policyQuery/groupEmail` and `policyQuery/orgUnitPath` fields
|
||||||
|
that are mapped from the `policyQuery/group` and `policyQuery/orgUnit` fields. Use option `noidmapping'
|
||||||
|
to suppress these calls and not add the additional fields.
|
||||||
|
|
||||||
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
By default, additional API calls are made for `settings/workspace_marketplace.apps_allowlist`
|
||||||
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
to get the application name for the application ID. Use option `noappnames` to suppress these calls.
|
||||||
|
|
||||||
|
|||||||
@@ -25,41 +25,41 @@ start a new terminal session and reissue the command from above.
|
|||||||
## Executable, Manual
|
## Executable, Manual
|
||||||
|
|
||||||
* Executable Archive, Manual, Linux/Google Cloud Shell
|
* Executable Archive, Manual, Linux/Google Cloud Shell
|
||||||
- `gam-7.wx.yz-linux-x86_64-glibc2.36.tar.xz`
|
- `gam-7.wx.yz-linux-x86_64-glibc2.35.tar.xz`
|
||||||
- `gam-7.wx.yz-linux-x86_64-glibc2.39.tar.xz`
|
- `gam-7.wx.yz-linux-x86_64-glibc2.39.tar.xz`
|
||||||
- `gam-7.wx.yz-linux-x86_64-legacy.tar.xz`
|
- `gam-7.wx.yz-linux-x86_64-legacy.tar.xz`
|
||||||
- Download the archive, extract the contents into some directory.
|
- Download the archive, extract the contents into some directory.
|
||||||
- Start a terminal session.
|
- Start a terminal session.
|
||||||
|
|
||||||
* Executable Archive, Manual, Raspberry Pi/ChromeOS ARM devices
|
* Executable Archive, Manual, Raspberry Pi/ChromeOS ARM devices
|
||||||
- `gam-7.wx.yz-linux-arm64-glibc2.36.tar.xz`
|
- `gam-7.wx.yz-linux-arm64-glibc2.35.tar.xz`
|
||||||
- `gam-7.wx.yz-linux-arm64-glibc2.39.tar.xz`
|
- `gam-7.wx.yz-linux-arm64-glibc2.39.tar.xz`
|
||||||
- `gam-7.wx.yz-linux-arm64-legacy.tar.xz`
|
- `gam-7.wx.yz-linux-arm64-legacy.tar.xz`
|
||||||
- Download the archive, extract the contents into some directory.
|
- Download the archive, extract the contents into some directory.
|
||||||
- Start a terminal session.
|
- Start a terminal session.
|
||||||
|
|
||||||
* Executable Archive, Manual, Mac OS versions Sonoma, Sequoia - M1/M2
|
* Executable Archive, Manual, Mac OS versions Sonoma, Sequoia - M1/M2
|
||||||
- `gam-7.wx.yz-macos14.7-arm64.tar.xz`
|
- `gam-7.wx.yz-macos14.8-arm64.tar.xz`
|
||||||
- Download the archive, extract the contents into some directory.
|
- Download the archive, extract the contents into some directory.
|
||||||
- Start a terminal session.
|
- Start a terminal session.
|
||||||
|
|
||||||
* Executable Archive, Manual, Mac OS versions Sequoia - M2/M3
|
* Executable Archive, Manual, Mac OS versions Sequoia - M2/M3
|
||||||
- `gam-7.wx.yz-macos15.6-arm64.tar.xz`
|
- `gam-7.wx.yz-macos15.7-arm64.tar.xz`
|
||||||
- Download the archive, extract the contents into some directory.
|
|
||||||
- Start a terminal session.
|
|
||||||
|
|
||||||
* Executable Archive, Manual, Mac OS versions Tahoe - M2/M3/M4
|
|
||||||
- `gam-7.wx.yz-macos26.0-arm64.tar.xz`
|
|
||||||
- Download the archive, extract the contents into some directory.
|
|
||||||
- Start a terminal session.
|
|
||||||
|
|
||||||
* Executable Archive, Manual, Mac OS, versions Ventura, Sonoma - Intel
|
|
||||||
- `gam-7.wx.yz-macos13.7-x86_64.tar.xz`
|
|
||||||
- Download the archive, extract the contents into some directory.
|
- Download the archive, extract the contents into some directory.
|
||||||
- Start a terminal session.
|
- Start a terminal session.
|
||||||
|
|
||||||
* Executable Archive, Manual, Mac OS, versions Sequoia, Tahoe - Intel
|
* Executable Archive, Manual, Mac OS, versions Sequoia, Tahoe - Intel
|
||||||
- `gam-7.wx.yz-macos15.6-x86_64.tar.xz`
|
- `gam-7.wx.yz-macos15.7-x86_64.tar.xz`
|
||||||
|
- Download the archive, extract the contents into some directory.
|
||||||
|
- Start a terminal session.
|
||||||
|
|
||||||
|
* Executable Archive, Manual, Mac OS versions Tahoe - M2/M3/M4
|
||||||
|
- `gam-7.wx.yz-macos26.3-arm64.tar.xz`
|
||||||
|
- Download the archive, extract the contents into some directory.
|
||||||
|
- Start a terminal session.
|
||||||
|
|
||||||
|
* Executable Archive, Manual, Mac OS versions Tahoe - Intel
|
||||||
|
- `gam-7.wx.yz-macos26.3-x86_64.tar.xz`
|
||||||
- Download the archive, extract the contents into some directory.
|
- Download the archive, extract the contents into some directory.
|
||||||
- Start a terminal session.
|
- Start a terminal session.
|
||||||
|
|
||||||
@@ -69,7 +69,7 @@ start a new terminal session and reissue the command from above.
|
|||||||
- Start a Command Prompt/PowerShell session.
|
- Start a Command Prompt/PowerShell session.
|
||||||
|
|
||||||
* Executable Installer, Manual, Windows 64 bit
|
* Executable Installer, Manual, Windows 64 bit
|
||||||
- `gam-7.wx.yz-windows-x86_64.msi`
|
- `gam-7.wx.yz-windows-x86_64.exe`
|
||||||
- Download the installer and run it.
|
- Download the installer and run it.
|
||||||
- Start a Command Prompt/PowerShell session.
|
- Start a Command Prompt/PowerShell session.
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ start a new terminal session and reissue the command from above.
|
|||||||
- Start a Command Prompt/PowerShell session.
|
- Start a Command Prompt/PowerShell session.
|
||||||
|
|
||||||
* Executable Installer, Manual, Windows 11 ARM
|
* Executable Installer, Manual, Windows 11 ARM
|
||||||
- `gam-7.wx.yz-windows-arm64.msi`
|
- `gam-7.wx.yz-windows-arm64.exe`
|
||||||
- Download the installer and run it.
|
- Download the installer and run it.
|
||||||
- Start a Command Prompt/PowerShell session.
|
- Start a Command Prompt/PowerShell session.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,89 @@ 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
|
See [Downloads-Installs-GAM7](https://github.com/GAM-team/GAM/wiki/Downloads-Installs) for Windows or other options, including manual installation
|
||||||
|
|
||||||
|
### 7.36.02
|
||||||
|
|
||||||
|
Added option `threadid <String>` to `gam [<UserTypeEntity>] sendemail` that causes Gmail to recognize the message
|
||||||
|
in conversation mode in for the user sending the message.
|
||||||
|
|
||||||
|
* See: https://github.com/GAM-team/GAM/wiki/Send-Email#conversation-mode
|
||||||
|
|
||||||
|
### 7.36.01
|
||||||
|
|
||||||
|
Fixed bug in `gam info|print|show policies` where the `policyQuery/query` field was not displayed.
|
||||||
|
|
||||||
|
Added option `noidmapping` to `gam info|print|show policies` to suppress adding the `policyQuery/groupEmail` and
|
||||||
|
`policyQuery/orgUnitPath` name fields that are mapped from the `policyQuery/group` and `policyQuery/orgInit` id fields.
|
||||||
|
|
||||||
|
### 7.36.00
|
||||||
|
|
||||||
|
Added options `filtermultiattrtype` and filtermultiattrcustom` to `gam info user` and
|
||||||
|
`gam print users` that support filtering `<UserMultiAttribute>` display based on `type` or `customType`.
|
||||||
|
|
||||||
|
```
|
||||||
|
<UserMultiAttributeFilterName> ::=
|
||||||
|
address|addresses|
|
||||||
|
externalid|externalids|
|
||||||
|
im|ims|
|
||||||
|
keyword|keywords|
|
||||||
|
location|locations|
|
||||||
|
orgainzation|organizations|
|
||||||
|
otheremail|otheremails|
|
||||||
|
phone|phones|
|
||||||
|
relation|relations|
|
||||||
|
website|websites
|
||||||
|
```
|
||||||
|
|
||||||
|
* `filtermultiattrtype <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `type` is `<String>`
|
||||||
|
* `filtermultiattrcustom <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `customType` is `<String>`
|
||||||
|
|
||||||
|
```
|
||||||
|
gam info user user@domain.com quick filtermultiattrtype organizations work filtermultiattrcustom phones private
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.35.03
|
||||||
|
|
||||||
|
Updated `gam <UserTypeEntity> print filelist|filecounts` to handle options `showsize` and `showsizeunits` as independent options.
|
||||||
|
* `showsize` - Display a column `Size` with a byte count
|
||||||
|
* `showsizeunits` - Display a column `SizeUnits` with a formatted size with units
|
||||||
|
|
||||||
|
If you select both options, you can sort multiple rows using the `Size` column.
|
||||||
|
|
||||||
|
### 7.35.02
|
||||||
|
|
||||||
|
Added option `showsizeunits` to `gam gam <UserTypeEntity> print filelist|filecounts` as an alternative to option `showsize`.
|
||||||
|
* `showsize` - 31549200951 - This is a byte count
|
||||||
|
* `showsizeunits` - 31.55 GB - This is as shown in the Admin console
|
||||||
|
|
||||||
|
### 7.35.01
|
||||||
|
|
||||||
|
The following commands have been updated to not verify the existence of `gam.cfg` credentials files
|
||||||
|
as the WARNING messages about the missing files can be confusing to new users setting up GAM.
|
||||||
|
```
|
||||||
|
gam checkconn
|
||||||
|
gam oauth|oauth2
|
||||||
|
gam version
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.35.00
|
||||||
|
|
||||||
|
Windows `gam-7.wx.yz-x86_64.msi` has been replaced with `gam-7.wx.yz-x86_64.exe`.
|
||||||
|
|
||||||
|
Windows `gam-7.wx.yz-arm64.msi` has been replaced with `gam-7.wx.yz-arm64.exe`.
|
||||||
|
|
||||||
|
Updated cacerts.pem to avoid to following error in `gam checkconn`.
|
||||||
|
```
|
||||||
|
Checking raw.githubusercontent.com (185.199.110.133) (2)... ERROR
|
||||||
|
Certificate verification failed. If you are behind a firewall / proxy server that does TLS / SSL inspection you may need to point GAM at your certificate authority file by setting cacerts_pem = /path/to/your/certauth.pem in gam.cfg.
|
||||||
|
```
|
||||||
|
|
||||||
|
If you have customized cacerts.pem, update your version with the `Operating CA: Let's Encrypt` values from the GAM default version.
|
||||||
|
|
||||||
|
### 7.34.13
|
||||||
|
|
||||||
|
Fixed bug in `gam info policies <CIPolicyNameEntity> ... formatjson` where extraneous line
|
||||||
|
`Show Info 1 Policy` was displayed.
|
||||||
|
|
||||||
### 7.34.12
|
### 7.34.12
|
||||||
|
|
||||||
Fixed build errors that prevented Windows zip files from being created.
|
Fixed build errors that prevented Windows zip files from being created.
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ See: [Downloads-Installs-GAM7](Downloads-Installs-GAM7)
|
|||||||
### Update to latest version, use current path `C:\GAMADV-XTD3`.
|
### Update to latest version, use current path `C:\GAMADV-XTD3`.
|
||||||
You don't have to update path or scripts.
|
You don't have to update path or scripts.
|
||||||
* Executable Installer, Manual, Windows 64 bit
|
* Executable Installer, Manual, Windows 64 bit
|
||||||
- `gam-7.wx.yz-windows-x86_64.msi`
|
- `gam-7.wx.yz-windows-x86_64.exe`
|
||||||
- Download the installer and run it. When prompted for the Destination Foler, enter `C:\GAMADV-XTD3`.
|
- Download the installer and run it. When prompted for the Destination Foler, enter `C:\GAMADV-XTD3`.
|
||||||
* Executable Archive, Manual, Windows 64 bit
|
* Executable Archive, Manual, Windows 64 bit
|
||||||
- `gam-7.wx.yz-windows-x86_64.zip`
|
- `gam-7.wx.yz-windows-x86_64.zip`
|
||||||
@@ -139,7 +139,7 @@ Your update is complete.
|
|||||||
|
|
||||||
### Update to latest version, use new path `C:\GAM7`.
|
### Update to latest version, use new path `C:\GAM7`.
|
||||||
* Executable Installer, Manual, Windows 64 bit
|
* Executable Installer, Manual, Windows 64 bit
|
||||||
- `gam-7.wx.yz-windows-x86_64.msi`
|
- `gam-7.wx.yz-windows-x86_64.exe`
|
||||||
- Download the installer and run it.
|
- Download the installer and run it.
|
||||||
- Start a Command Prompt/PowerShell session.
|
- Start a Command Prompt/PowerShell session.
|
||||||
* Executable Archive, Manual, Windows 64 bit
|
* Executable Archive, Manual, Windows 64 bit
|
||||||
|
|||||||
@@ -251,11 +251,10 @@ writes the credentials into the file oauth2.txt.
|
|||||||
```
|
```
|
||||||
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
|
gamteam@server:/Users/gamteam$ rm -f /Users/gamteam/GAMConfig/oauth2.txt
|
||||||
gamteam@server:/Users/gamteam$ gam version
|
gamteam@server:/Users/gamteam$ gam version
|
||||||
WARNING: Config File: /Users/gamteam/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/gamteam/GAMConfig/oauth2.txt, Not Found
|
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM - pyinstaller
|
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
macOS Tahoe 26.3 arm64
|
macOS Tahoe 26.3.1 arm64
|
||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Config File: /Users/gamteam/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
Config File: /Users/gamteam/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||||
|
|
||||||
@@ -1035,8 +1034,7 @@ writes the credentials into the file oauth2.txt.
|
|||||||
```
|
```
|
||||||
C:\>del C:\GAMConfig\oauth2.txt
|
C:\>del C:\GAMConfig\oauth2.txt
|
||||||
C:\>gam version
|
C:\>gam version
|
||||||
WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found
|
GAM 7.36.02 - https://github.com/GAM-team/GAM - pythonsource
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM - pythonsource
|
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
Windows 11 10.0.26200 AMD64
|
Windows 11 10.0.26200 AMD64
|
||||||
|
|||||||
@@ -377,6 +377,9 @@ features "CameraSet"
|
|||||||
features "'Laptop Cart'"
|
features "'Laptop Cart'"
|
||||||
features "CameraSet,'Laptop Cart'"
|
features "CameraSet,'Laptop Cart'"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For quoting rules, see: [List Quoting Rules](Command-Line-Parsing)
|
||||||
|
|
||||||
## Manage buildings
|
## Manage buildings
|
||||||
When creating a building, at a minimum you must enter `address|addresslines` and `country|regioncode`.
|
When creating a building, at a minimum you must enter `address|addresslines` and `country|regioncode`.
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
- [Send an email to users](#send-an-email-to-users)
|
- [Send an email to users](#send-an-email-to-users)
|
||||||
- [Simple `replace <Tag> <String>` processing](Tag-Replace)
|
- [Simple `replace <Tag> <String>` processing](Tag-Replace)
|
||||||
- [Example](#example)
|
- [Example](#example)
|
||||||
|
- [Conversation mode](#conversation-mode)
|
||||||
|
|
||||||
## Note
|
## Note
|
||||||
Thanks to @bousquf for the following enhancement. You want to send a message from an authorized group
|
Thanks to @bousquf for the following enhancement. You want to send a message from an authorized group
|
||||||
@@ -214,14 +215,14 @@ Configure it at Admin Console > Apps > Google Workspace > Gmail > Routing > SMTP
|
|||||||
gam sendemail [recipient|to] <RecipientEntity>
|
gam sendemail [recipient|to] <RecipientEntity>
|
||||||
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
[from <EmailAddress>] [mailbox <EmailAddress>] [replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
```
|
```
|
||||||
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
||||||
|
|
||||||
@@ -272,14 +273,14 @@ You can specify additional recipients, e.g., help desk personnel.
|
|||||||
gam sendemail [recipient|to] <RecipientEntity> [from <EmailAddress>]
|
gam sendemail [recipient|to] <RecipientEntity> [from <EmailAddress>]
|
||||||
[replyto <EmailAddress>]
|
[replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
By default, emails will be sent from the admin user named in oauth2.txt, override this with the `from <EmailAddress>` option.
|
||||||
@@ -353,14 +354,14 @@ gam csv Users.csv gam sendemail "~personal" subject "Your new #domain# account`
|
|||||||
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
gam <UserTypeEntity> sendemail recipient|to <RecipientEntity>
|
||||||
[replyto <EmailAddress>]
|
[replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
```
|
```
|
||||||
Emails will be sent from the users in `<UserTypeEntity>` to the recipients in `<RecipientEntity>`.
|
Emails will be sent from the users in `<UserTypeEntity>` to the recipients in `<RecipientEntity>`.
|
||||||
|
|
||||||
@@ -395,14 +396,14 @@ Your command line will have: `embedimage file1.jpg image1 embedimage file2.jpg i
|
|||||||
gam <UserTypeEntity> sendemail from <EmailAddress>
|
gam <UserTypeEntity> sendemail from <EmailAddress>
|
||||||
[replyto <EmailAddress>]
|
[replyto <EmailAddress>]
|
||||||
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
[cc <RecipientEntity>] [bcc <RecipientEntity>] [singlemessage]
|
||||||
[subject <String>]
|
[subject <String>] [<MessageContent>]
|
||||||
[<MessageContent>]
|
|
||||||
(replace <Tag> <String>)*
|
(replace <Tag> <String>)*
|
||||||
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
(replaceregex <REMatchPattern> <RESubstitution> <Tag> <String>)*
|
||||||
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
[html [<Boolean>]] (attach <FileName> [charset <Charset>])*
|
||||||
(embedimage <FileName> <String>)*
|
(embedimage <FileName> <String>)*
|
||||||
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
[newuser <EmailAddress> firstname|givenname <String> lastname|familyname <string> password <Password>]
|
||||||
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
(<SMTPDateHeader> <Time>)* (<SMTPHeader> <String>)* (header <String> <String>)*
|
||||||
|
[threadid <String>]
|
||||||
```
|
```
|
||||||
Emails will be sent to the users in `<UserTypeEntity>`.
|
Emails will be sent to the users in `<UserTypeEntity>`.
|
||||||
|
|
||||||
@@ -451,3 +452,29 @@ $ gam csv UserEmail.csv gam user "~User" sendemail to "~To" subject "~Subject" t
|
|||||||
User: user1@domain.com, Send Email to 1 Recipient
|
User: user1@domain.com, Send Email to 1 Recipient
|
||||||
Recipient: user2@domain.com, Message: Test, Email Sent: 17677cdfbe1146f4
|
Recipient: user2@domain.com, Message: Test, Email Sent: 17677cdfbe1146f4
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Conversation mode
|
||||||
|
|
||||||
|
To reply to an email and have Gmail recognize it in conversation mode for the original sender, you have to specify the
|
||||||
|
`References` and `In-Reply-to` headers with the `RFC822 Message ID` from the original message
|
||||||
|
and the `subject` from the original message.
|
||||||
|
```
|
||||||
|
gam user recipient@domain.com sendemail to sender@domain.com references "<CAAMabc...XYZQ@mail.gmail.com>" in-reply-to "<CAAMabc...XYZQ@mail.gmail.com>" subject "Re: Original subject" textmessage "Reply text"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to have Gmail recognize the reply in conversation mode in the Sent folder of the original recipient,
|
||||||
|
you must include `threadid <String>`; you can get the 'threadId` with:
|
||||||
|
```
|
||||||
|
gam user recipient@domain.com show messages query "rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>"
|
||||||
|
Getting all Messages that match query ((rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>)) for recipient@domain.com
|
||||||
|
Got 1 Message that matched query ((rfc822MsgId:<CAAMabc...XYZQ@mail.gmail.com>)) for recipient@domain.com...
|
||||||
|
User: recipient@domain.com, Show 1 Message
|
||||||
|
Message: 19cfd414fe48430d
|
||||||
|
...
|
||||||
|
|
||||||
|
gam user recipient@domain.com sendemail to sender@domain.com references "<CAAMabc...XYZQ@mail.gmail.com>" in-reply-to "<CAAMabc...XYZQ@mail.gmail.com>" subject "Re: Original subject" textmessage "Reply text" threadid 19cfd414fe48430d
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
- [Change Shared Drive visibility](#change-shared-drive-visibility)
|
- [Change Shared Drive visibility](#change-shared-drive-visibility)
|
||||||
- [Display Shared Drives](#display-shared-drives)
|
- [Display Shared Drives](#display-shared-drives)
|
||||||
- [Display Shared Drive Counts](#display-shared-drive-counts)
|
- [Display Shared Drive Counts](#display-shared-drive-counts)
|
||||||
|
- [Display Shared Drive Storage Info](#display-shared-drive-storage-info)
|
||||||
- [Display List of Shared Drives in an Organizational Unit](#display-list-of-shared-drives-in-an-organizational-unit)
|
- [Display List of Shared Drives in an Organizational Unit](#display-list-of-shared-drives-in-an-organizational-unit)
|
||||||
- [Display Count of Shared Drives in an Organizational Unit](#display-count-of-shared-drives-in-an-organizational-unit)
|
- [Display Count of Shared Drives in an Organizational Unit](#display-count-of-shared-drives-in-an-organizational-unit)
|
||||||
- [Display Shared Drive Organizers](#display-shared-drive-organizers)
|
- [Display Shared Drive Organizers](#display-shared-drive-organizers)
|
||||||
@@ -563,6 +564,21 @@ Windows Command Prompt
|
|||||||
for /f "delims=" %a in ('gam print shareddrives showitemcountonly') do set count=%a
|
for /f "delims=" %a in ('gam print shareddrives showitemcountonly') do set count=%a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Display Shared Drive Storage Info
|
||||||
|
|
||||||
|
Get a list of Shared Drives/organizers.
|
||||||
|
```
|
||||||
|
gam redirect csv ./SharedDriveOrganizers.csv print shareddriveorganizers includefileorganizers
|
||||||
|
```
|
||||||
|
Get SharedDrive Drive file count and storage info; use one of the following for size information:
|
||||||
|
* `showsize` - 31549200951 - This is a byte count; include `Size` in `csv_output_header_filter`
|
||||||
|
* `showsizeunits` - 31.55 GB - This is as shown in the Admin console; include `SizeUnits` in csv_output_header_filter
|
||||||
|
```
|
||||||
|
gam config csv_output_header_filter "id,name,Total,Size,SizeUnits,Item cap" csv_input_row_filter "organizers:regex:^.+$"
|
||||||
|
redirect csv ./SharedDriveStorageInfo.csv multiprocess redirect stderr - multiprocess
|
||||||
|
csv ./SharedDriveOrganizers.csv gam user "~organizers" print filecounts select shareddriveid "~id" showsize showsizeunits
|
||||||
|
```
|
||||||
|
|
||||||
## Display all Shared Drives with a specific organizer
|
## Display all Shared Drives with a specific organizer
|
||||||
Substitute actual email address for `organizer@domain.com`.
|
Substitute actual email address for `organizer@domain.com`.
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -405,7 +405,7 @@ quotaBytesUsed - The number of storage quota bytes used by the file.
|
|||||||
size - Size in bytes of blobs and first party editor files.
|
size - Size in bytes of blobs and first party editor files.
|
||||||
```
|
```
|
||||||
Previously, GAM used the `size` field when totaling file sizes, it now uses the `quotaBytesUsed` field.
|
Previously, GAM used the `size` field when totaling file sizes, it now uses the `quotaBytesUsed` field.
|
||||||
The option `sizefield quotabytesused|size` allows you to select which field to use.
|
The option `sizefield quotabytesused|size` allows you to select which field to use; `quotabytesused` is the default.
|
||||||
|
|
||||||
For most MIME types, the values are the same; for the following MIME types, `quotabytesused` is larger.
|
For most MIME types, the values are the same; for the following MIME types, `quotabytesused` is larger.
|
||||||
```
|
```
|
||||||
@@ -719,7 +719,7 @@ gam <UserTypeEntity> print filecounts [todrive <ToDriveAttribute>*]
|
|||||||
[filenamematchpattern <REMatchPattern>]
|
[filenamematchpattern <REMatchPattern>]
|
||||||
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[showsize] [showmimetypesize]
|
[showsize] [showsizeunits] [showmimetypesize]
|
||||||
[showlastmodification] [pathdelimiter <Character>]
|
[showlastmodification] [pathdelimiter <Character>]
|
||||||
(addcsvdata <FieldName> <String>)*
|
(addcsvdata <FieldName> <String>)*
|
||||||
[summary none|only|plus] [summaryuser <String>]
|
[summary none|only|plus] [summaryuser <String>]
|
||||||
@@ -735,7 +735,7 @@ gam <UserTypeEntity> show filecounts
|
|||||||
[filenamematchpattern <REMatchPattern>]
|
[filenamematchpattern <REMatchPattern>]
|
||||||
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
<PermissionMatch>* [<PermissionMatchMode>] [<PermissionMatchAction>]
|
||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[showsize] [showmimetypesize]
|
[showsize] [showsizeunits] [showmimetypesize]
|
||||||
[showlastmodification] [pathdelimiter <Character>]
|
[showlastmodification] [pathdelimiter <Character>]
|
||||||
[summary none|only|plus] [summaryuser <String>]
|
[summary none|only|plus] [summaryuser <String>]
|
||||||
```
|
```
|
||||||
@@ -748,7 +748,11 @@ saying that the query is invalid when, in fact, it is but the user does not have
|
|||||||
When `continueoninvalidquery` is true, GAM prints an error message and proceeds to the next user rather that terminating
|
When `continueoninvalidquery` is true, GAM prints an error message and proceeds to the next user rather that terminating
|
||||||
as it does now. Of course, if the query really is invalid, you will get the message for every user.
|
as it does now. Of course, if the query really is invalid, you will get the message for every user.
|
||||||
|
|
||||||
The `showsize` option displays the total size (in bytes) of the files counted.
|
The `showsize` option displays the total size (in bytes) of the files counted; e.g., `31549200951`.
|
||||||
|
With `print filecounts`, this will be in a column labelled `Size`.
|
||||||
|
|
||||||
|
The `showsizeunits` option displays the total size of the files counted with two decimal places and units; e.g., `31.55 GB`.
|
||||||
|
With `print filecounts`, this will be in a column labelled `SizeUnits`.
|
||||||
|
|
||||||
The `showmimetypesize` option displays the total size (in bytes) of each MIME type counted.
|
The `showmimetypesize` option displays the total size (in bytes) of each MIME type counted.
|
||||||
|
|
||||||
@@ -1100,7 +1104,7 @@ gam <UserTypeEntity> print|show filelist [todrive <ToDriveAttribute>*]
|
|||||||
[excludetrashed]
|
[excludetrashed]
|
||||||
[maxfiles <Integer>] [nodataheaders <String>]
|
[maxfiles <Integer>] [nodataheaders <String>]
|
||||||
[countsonly [summary none|only|plus] [summaryuser <String>]
|
[countsonly [summary none|only|plus] [summaryuser <String>]
|
||||||
[showsource] [showsize] [showmimetypesize]]
|
[showsource] [showsize] [showsizeunits] [showmimetypesize]]
|
||||||
[countsrowfilter]
|
[countsrowfilter]
|
||||||
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
[filepath|fullpath [folderpathonly [<Boolean>]] [pathdelimiter <Character>] [addpathstojson] [showdepth]] [buildtree]
|
||||||
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
[allfields|<DriveFieldName>*|(fields <DriveFieldNameList>)]
|
||||||
@@ -1304,7 +1308,9 @@ The `summaryuser <String>` option replaces the default summary user `Summary` w
|
|||||||
|
|
||||||
The `countsonly` suboption `showsource` adds additional columns `Source` and `Name` that identify the top level folder ID and Name from which the counts are derived.
|
The `countsonly` suboption `showsource` adds additional columns `Source` and `Name` that identify the top level folder ID and Name from which the counts are derived.
|
||||||
|
|
||||||
The `countsonly` suboption `showsize` adds an additional column `Size` that indicates the total size (in bytes) of the files represented on the row.
|
The `countsonly` suboption `showsize` adds an additional column `Size` that indicates the total size (in bytes) of the files represented on the row; e.g., `31549200951`.
|
||||||
|
|
||||||
|
The `countsonly` suboption `showsizeunits` adds an additional column `SizeUnits` that indicates the total size of the files represented on the row with two decimal places and units; e.g., `31.55 GB`.
|
||||||
|
|
||||||
The `countsonly` suboption `showmimetypesize` adds additional columns `<MimeType>:Size` that indicate the total size (in bytes) of each MIME type.
|
The `countsonly` suboption `showmimetypesize` adds additional columns `<MimeType>:Size` that indicate the total size (in bytes) of each MIME type.
|
||||||
|
|
||||||
|
|||||||
@@ -329,6 +329,20 @@ You can remove all instances of a `<UserMultiAttribute>` with `<UserClearAttribu
|
|||||||
<UserMultiAttribute>|
|
<UserMultiAttribute>|
|
||||||
<UserClearAttribute>
|
<UserClearAttribute>
|
||||||
```
|
```
|
||||||
|
```
|
||||||
|
<UserMultiAttributeFilterName> ::=
|
||||||
|
address|addresses|
|
||||||
|
externalid|externalids|
|
||||||
|
im|ims|
|
||||||
|
keyword|keywords|
|
||||||
|
location|locations|
|
||||||
|
orgainzation|organizations|
|
||||||
|
otheremail|otheremails|
|
||||||
|
phone|phones|
|
||||||
|
relation|relations|
|
||||||
|
website|websites
|
||||||
|
```
|
||||||
|
|
||||||
## Admin Console User Info
|
## Admin Console User Info
|
||||||
When defining a user in the admin console, there is a section labelled `Employee information` with the following items:
|
When defining a user in the admin console, there is a section labelled `Employee information` with the following items:
|
||||||
* `Employee ID`
|
* `Employee ID`
|
||||||
@@ -971,6 +985,8 @@ gam info user [<UserItem>]
|
|||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[formatjson]
|
[formatjson]
|
||||||
```
|
```
|
||||||
### Display information about multiple users
|
### Display information about multiple users
|
||||||
@@ -984,6 +1000,8 @@ gam info users <UserTypeEntity>
|
|||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[formatjson]
|
[formatjson]
|
||||||
gam <UserTypeEntity> info users
|
gam <UserTypeEntity> info users
|
||||||
[quick]
|
[quick]
|
||||||
@@ -994,6 +1012,8 @@ gam <UserTypeEntity> info users
|
|||||||
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
[(products|product <ProductIDList>)|(skus|sku <SKUIDList>)]
|
||||||
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
[noschemas|allschemas|(schemas|custom|customschemas <SchemaNameList>)]
|
||||||
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
[userview] <UserFieldName>* [fields <UserFieldNameList>]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[formatjson]
|
[formatjson]
|
||||||
```
|
```
|
||||||
For `info users`, unlike all other GAM commands, a `<UserTypeEntity>` value of `all users` is actually `all users_ns_susp` not `all users_ns`.
|
For `info users`, unlike all other GAM commands, a `<UserTypeEntity>` value of `all users` is actually `all users_ns_susp` not `all users_ns`.
|
||||||
@@ -1031,6 +1051,11 @@ By default, Gam displays fields that only an adminstrator can view.
|
|||||||
By default, Gam displays all fields for a user.
|
By default, Gam displays all fields for a user.
|
||||||
* `<UserFieldName>* [fields <UserFieldNameList>]` - Only display selected fields.
|
* `<UserFieldName>* [fields <UserFieldNameList>]` - Only display selected fields.
|
||||||
|
|
||||||
|
By default, all instances of `<UserMultiAttribute>` are displayed, use these options to only display instances
|
||||||
|
of a specified `type` or `customType`.
|
||||||
|
* `filtermultiattrtype <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `type` is `<String>`
|
||||||
|
* `filtermultiattrcustom <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `customType` is `<String>`
|
||||||
|
|
||||||
By default, Gam displays the information as an indented list of keys and values.
|
By default, Gam displays the information as an indented list of keys and values.
|
||||||
* `formatjson` - Display the fields in JSON format.
|
* `formatjson` - Display the fields in JSON format.
|
||||||
|
|
||||||
@@ -1062,6 +1087,8 @@ gam print users [todrive <ToDriveAttribute>*]
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [allfields|basic|full|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [allfields|basic|full|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -1088,6 +1115,8 @@ gam print users [todrive <ToDriveAttribute>*] select <UserTypeEntity>
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -1102,6 +1131,8 @@ gam <UserTypeEntity> print users [todrive <ToDriveAttribute>*]
|
|||||||
[schemas|custom|customschemas all|<SchemaNameList>]
|
[schemas|custom|customschemas all|<SchemaNameList>]
|
||||||
[emailpart|emailparts|username]
|
[emailpart|emailparts|username]
|
||||||
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
[userview] [basic|full|allfields|(<UserFieldName>*|fields <UserFieldNameList>)]
|
||||||
|
(filtermultiattrtype <UserMultiAttributeFilterName> <String>)*
|
||||||
|
(filtermultiattrcustom <UserMultiAttributeFilterName> <String>)*
|
||||||
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
[delimiter <Character>] [sortheaders [<Boolean>]] [scalarsfirst [<Boolean>]]
|
||||||
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
[formatjson [quotechar <Character>]] [quoteplusphonenumbers]
|
||||||
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
[issuspended <Boolean>] [isarchived <Boolean>] [aliasmatchpattern <REMatchPattern>]
|
||||||
@@ -1137,6 +1168,11 @@ By default, Gam displays only the primary email address for each user.
|
|||||||
* `schemas|custom all` - Display custom schema information for all schemas.
|
* `schemas|custom all` - Display custom schema information for all schemas.
|
||||||
* `schemas|custom <SchemaNameList>` - Display all fields or selected fields of the specified custom schemas
|
* `schemas|custom <SchemaNameList>` - Display all fields or selected fields of the specified custom schemas
|
||||||
|
|
||||||
|
By default, all instances of `<UserMultiAttribute>` are displayed, use these options to only display instances
|
||||||
|
of a specified `type` or `customType`.
|
||||||
|
* `filtermultiattrtype <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `type` is `<String>`
|
||||||
|
* `filtermultiattrcustom <UserMultiAttributeFilterName> <String>` - Display `<UserMultiAttributeFilterName>` if its `customType` is `<String>`
|
||||||
|
|
||||||
By default, when aliases are displayed, all aliases are displayed. Use `aliasmatchpattern <REMatchPattern>`
|
By default, when aliases are displayed, all aliases are displayed. Use `aliasmatchpattern <REMatchPattern>`
|
||||||
to limit the display of aliases to those that match `<REMatchPattern>`.
|
to limit the display of aliases to those that match `<REMatchPattern>`.
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ It's important to confirm you are always running an official GAM7 release. The f
|
|||||||
# GitHub Attestation (Linux/MacOS/Windows)
|
# GitHub Attestation (Linux/MacOS/Windows)
|
||||||
GitHub offers [artifict attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) which prove if a given GAM binary or archive was built by the [GAM-team/GAM](https://gitHub.com/GAM-team/GAM) project and links to the build job. This offers you certainty that the GAM executable you are running or the GAM package you downloaded were officially generated by the [GAM-team/GAM](https://gitHub.com/GAM-team/GAM) project.
|
GitHub offers [artifict attestations](https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/using-artifact-attestations-to-establish-provenance-for-builds) which prove if a given GAM binary or archive was built by the [GAM-team/GAM](https://gitHub.com/GAM-team/GAM) project and links to the build job. This offers you certainty that the GAM executable you are running or the GAM package you downloaded were officially generated by the [GAM-team/GAM](https://gitHub.com/GAM-team/GAM) project.
|
||||||
|
|
||||||
To verify a given GAM executable file or package (.zip, .msi or .tar.xz) is legitimate, use the following steps:
|
To verify a given GAM executable file or package (.zip, .exe or .tar.xz) is legitimate, use the following steps:
|
||||||
1. Install the [GitHub CLI command line tool](https://github.com/cli/cli#installation).
|
1. Install the [GitHub CLI command line tool](https://github.com/cli/cli#installation).
|
||||||
2. Login to the tool with the command. You need a [free GitHub account](https://gitHub.com/join) for this.
|
2. Login to the tool with the command. You need a [free GitHub account](https://gitHub.com/join) for this.
|
||||||
```
|
```
|
||||||
@@ -27,7 +27,7 @@ gh attestation verify --repo GAM-team/GAM --format=json \
|
|||||||
|
|
||||||
4. If the GAM file or package is legit you'll see output like:
|
4. If the GAM file or package is legit you'll see output like:
|
||||||
```
|
```
|
||||||
Loaded digest sha256:a63dc5e71c0b3335865877fc7dc9248bbf7481d22995c18253a2ae71fcb9793a for file://gam-7.00.00-windows-x86_64.msi
|
Loaded digest sha256:a63dc5e71c0b3335865877fc7dc9248bbf7481d22995c18253a2ae71fcb9793a for file://gam-7.00.00-windows-x86_64.exe
|
||||||
Loaded 1 attestation from GitHub API
|
Loaded 1 attestation from GitHub API
|
||||||
✓ Verification succeeded!
|
✓ Verification succeeded!
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ origin=Developer ID Application: Jay Lee (GZ85H2DRLM)
|
|||||||
If you do not see "accepted" and "Jay Lee" as the developer ID, there may be a problem. Please report any suspicious files or concerns to the [GAM Group](https://groups.google.com/g/google-apps-manager) or the [GAM Chat Space](https://git.io/gam-chat).
|
If you do not see "accepted" and "Jay Lee" as the developer ID, there may be a problem. Please report any suspicious files or concerns to the [GAM Group](https://groups.google.com/g/google-apps-manager) or the [GAM Chat Space](https://git.io/gam-chat).
|
||||||
|
|
||||||
# Windows Code Sign
|
# Windows Code Sign
|
||||||
On Windows, Official gam.exe files and MSI installer packages are signed by a [Certum Open Source code signing certificate](https://shop.certum.eu/open-source-code-signing.html). You can validate the signature and thus be sure you are running official GAM7 from the command line and GUI:
|
On Windows, Official gam.exe files and EXE installer packages are signed by a [Certum Open Source code signing certificate](https://shop.certum.eu/open-source-code-signing.html). You can validate the signature and thus be sure you are running official GAM7 from the command line and GUI:
|
||||||
|
|
||||||
# Command Line
|
# Command Line
|
||||||
From PowerShell, run the following command:
|
From PowerShell, run the following command:
|
||||||
@@ -113,6 +113,6 @@ SignerCertificate : [Subject]
|
|||||||
confirm that status is "Valid" and the SignerCertificate says "Open Source Developer, James Lee" (yes, James is Jay's legal name, now you know).
|
confirm that status is "Valid" and the SignerCertificate says "Open Source Developer, James Lee" (yes, James is Jay's legal name, now you know).
|
||||||
|
|
||||||
## GUI
|
## GUI
|
||||||
From File Manager, you can right click on gam.exe or the MSI package and go to the Digital Signatures tab. From there you'll see the signing certificate which should show "Open Source Developer, James Lee".
|
From File Manager, you can right click on gam.exe or the EXE installer package and go to the Digital Signatures tab. From there you'll see the signing certificate which should show "Open Source Developer, James Lee".
|
||||||
|
|
||||||

|

|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
\# Version and Help
|
# Version and Help
|
||||||
|
|
||||||
Print the current version of Gam with details
|
Print the current version of Gam with details
|
||||||
```
|
```
|
||||||
gam version
|
gam version
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
macOS Tahoe 26.3 arm64
|
macOS Tahoe 26.3.1 arm64
|
||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||||
Time: 2026-02-15T07:51:00-08:00
|
Time: 2026-02-15T07:51:00-08:00
|
||||||
@@ -15,10 +15,10 @@ Time: 2026-02-15T07:51:00-08:00
|
|||||||
Print the current version of Gam with details and time offset information
|
Print the current version of Gam with details and time offset information
|
||||||
```
|
```
|
||||||
gam version timeoffset
|
gam version timeoffset
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
macOS Tahoe 26.3 arm64
|
macOS Tahoe 26.3.1 arm64
|
||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||||
Your system time differs from www.googleapis.com by less than 1 second
|
Your system time differs from www.googleapis.com by less than 1 second
|
||||||
@@ -27,10 +27,10 @@ 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
|
Print the current version of Gam with extended details and SSL information
|
||||||
```
|
```
|
||||||
gam version extended
|
gam version extended
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM - pyinstaller
|
GAM 7.36.02 - https://github.com/GAM-team/GAM - pyinstaller
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
macOS Tahoe 26.3 arm64
|
macOS Tahoe 26.3.1 arm64
|
||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||||
Time: 2026-02-15T07:51:00-08:00
|
Time: 2026-02-15T07:51:00-08:00
|
||||||
@@ -68,7 +68,7 @@ MacOS High Sierra 10.13.6 x86_64
|
|||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Version Check:
|
Version Check:
|
||||||
Current: 5.35.08
|
Current: 5.35.08
|
||||||
Latest: 7.34.12
|
Latest: 7.36.02
|
||||||
echo $?
|
echo $?
|
||||||
1
|
1
|
||||||
```
|
```
|
||||||
@@ -76,7 +76,7 @@ echo $?
|
|||||||
Print the current version number without details
|
Print the current version number without details
|
||||||
```
|
```
|
||||||
gam version simple
|
gam version simple
|
||||||
7.34.12
|
7.36.02
|
||||||
```
|
```
|
||||||
In Linux/MacOS you can do:
|
In Linux/MacOS you can do:
|
||||||
```
|
```
|
||||||
@@ -86,10 +86,10 @@ echo $VER
|
|||||||
Print the current version of Gam and address of this Wiki
|
Print the current version of Gam and address of this Wiki
|
||||||
```
|
```
|
||||||
gam help
|
gam help
|
||||||
GAM 7.34.12 - https://github.com/GAM-team/GAM
|
GAM 7.36.02 - https://github.com/GAM-team/GAM
|
||||||
GAM Team <google-apps-manager@googlegroups.com>
|
GAM Team <google-apps-manager@googlegroups.com>
|
||||||
Python 3.14.3 64-bit final
|
Python 3.14.3 64-bit final
|
||||||
macOS Tahoe 26.3 arm64
|
macOS Tahoe 26.3.1 arm64
|
||||||
Path: /Users/gamteam/bin/gam7
|
Path: /Users/gamteam/bin/gam7
|
||||||
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
Config File: /Users/gamteam/GamConfig/gam.cfg, Section: DEFAULT, customer_id: my_customer, domain: domain.com
|
||||||
Time: 2026-02-15T07:51:00-08:00
|
Time: 2026-02-15T07:51:00-08:00
|
||||||
|
|||||||
Reference in New Issue
Block a user