From 1df2f14c97f09d66f887c362bddbf17774a139e2 Mon Sep 17 00:00:00 2001 From: Ross Scroggs Date: Sat, 4 Jul 2026 08:59:53 -0700 Subject: [PATCH] Added command `gam show configlicenseskus` #1938 --- src/GamCommands.txt | 1 + src/GamUpdate.txt | 25 +++++++++++++++++++++++++ src/callgam.py | 2 +- src/gam/__init__.py | 35 ++++++++++++++++++++++++++++++++++- src/gam/gamlib/glclargs.py | 2 ++ src/gam/gamlib/glmsgs.py | 1 + src/gam/gamlib/glverlibs.py | 4 +++- 7 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/GamCommands.txt b/src/GamCommands.txt index bee6b282..197ae118 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -4458,6 +4458,7 @@ gam print licenses [todrive *] gam show licenses [(products|product )|(skus|sku )|allskus|gsuite] [maxresults ] +gam show configlicenseskus [quiet] # Mobile Devices diff --git a/src/GamUpdate.txt b/src/GamUpdate.txt index f83b2b87..a1841269 100644 --- a/src/GamUpdate.txt +++ b/src/GamUpdate.txt @@ -1,3 +1,28 @@ +7.46.07 + +Added command `gam show configlicenseskus` that can be used to generate a command +that sets the list of used license SKUs in gam.cfg; this improves performance of `gam info user`. +``` +gam show configlicenseskus +Got 0 Licenses for 1010010001 (Cloud Identity Free)... +Got 0 Licenses for 1010050001 (Cloud Identity Premium)... +... +Got 2 Licenses for 1010340007 (Google Workspace for Education Fundamentals - Archived User)... +... +Got 100 Licenses for 1010070001 (Google Workspace for Education Fundamentals)... +Got 200 Licenses for 1010070001 (Google Workspace for Education Fundamentals)... +Got 300 Licenses for 1010070001 (Google Workspace for Education Fundamentals)... +Got 358 Licenses for 1010070001 (Google Workspace for Education Fundamentals)... +Got 2 Licenses for 1010070004 (Google Workspace for Education Gmail Only)... +... +Got 0 Licenses for Google-Vault (Google Vault)... +Got 0 Licenses for Google-Vault-Former-Employee (Google Vault - Former Employee)... +To set license_skus in gam.cfg, execute the following command: +gam config license_skus "1010340007,1010070001,1010070004" save verify variables license_skus +``` + +* See: https://github.com/GAM-team/GAM/wiki/Licenses#info-user-performance + 7.46.06 Updated GAM to suppress the following message: diff --git a/src/callgam.py b/src/callgam.py index a155fec8..54ed5b58 100755 --- a/src/callgam.py +++ b/src/callgam.py @@ -14,7 +14,7 @@ if __name__ == '__main__': multiprocessing.set_start_method('spawn', force=True) initializeLogging() # - CallGAMCommand(['gam', 'version']) + CallGAMCommand(['gam', 'version', 'extended']) # Issue command, output goes to stdout/stderr rc = CallGAMCommand(['gam', 'info', 'domain']) # Issue command, redirect stdout/stderr diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 5687131f..fa44bbda 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -25,7 +25,7 @@ https://github.com/GAM-team/GAM/wiki """ __author__ = 'GAM Team ' -__version__ = '7.46.06' +__version__ = '7.46.07' __license__ = 'Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0)' # pylint: disable=wrong-import-position @@ -39761,6 +39761,37 @@ def doShowLicenses(): for u_license in licenseCounts: printEntityKVList(u_license[:-2], [Ent.Plural(u_license[-2]), u_license[-1]]) +# gam show configlicenseskus [quiet] +def doShowConfigLicenseSKUs(): + lic = buildGAPIObject(API.LICENSING) + setTrueCustomerId() + customerId = _getCustomerId() + licenseSKUcounts = [] + maxResults = GC.Values[GC.LICENSE_MAX_RESULTS] + quiet = checkArgumentPresent('quiet') + checkForExtraneousArguments() + fields = getItemFieldsFromFieldsList('items', ['userId']) + for sku in SKU.getAllSKUs(): + Ent.SetGetting(Ent.LICENSE) + productId = sku[0] + skuId = sku[1] + productDisplay = SKU.formatProductIdDisplayName(productId) + skuIdDisplay = SKU.formatSKUIdDisplayName(skuId) + pageMessage = getPageMessageForWhom(forWhom=skuIdDisplay) if not quiet else None + try: + feed = callGAPIpages(lic.licenseAssignments(), 'listForProductAndSku', 'items', + pageMessage=pageMessage, + throwReasons=[GAPI.INVALID, GAPI.FORBIDDEN, GAPI.INVALID_ARGUMENT], + customerId=customerId, productId=productId, skuId=skuId, + maxResults=maxResults, fields=fields) + if len(feed) > 0: + licenseSKUcounts.append(skuId) + except (GAPI.invalid, GAPI.forbidden, GAPI.invalidArgument) as e: + entityActionNotPerformedWarning([Ent.PRODUCT, productDisplay, Ent.SKU, skuIdDisplay], str(e)) + writeStderr(Msg.CONFIG_LICENSE_SKUS) + flushStderr() + writeStdout(f"gam config license_skus \"{','.join(licenseSKUcounts)}\" save verify variables license_skus\n") + # gam delete alert # gam undelete alert def doDeleteOrUndeleteAlert(): @@ -81176,6 +81207,7 @@ MAIN_COMMANDS_WITH_OBJECTS = { Cmd.ARG_CIGROUPMEMBERS: doShowCIGroupMembers, Cmd.ARG_CIPOLICY: doPrintShowCIPolicies, Cmd.ARG_CLASSROOMINVITATION: doPrintShowClassroomInvitations, + Cmd.ARG_CONFIGLICENSESKUS: doShowConfigLicenseSKUs, Cmd.ARG_CONTACT: doPrintShowDomainContacts, Cmd.ARG_CROSTELEMETRY: doInfoPrintShowCrOSTelemetry, Cmd.ARG_DATATRANSFER: doPrintShowDataTransfers, @@ -82454,6 +82486,7 @@ USER_COMMANDS_OBJ_ALIASES = { Cmd.ARG_CHATSPACES: Cmd.ARG_CHATSPACE, Cmd.ARG_CIMEMBER: Cmd.ARG_CIGROUPMEMBERS, Cmd.ARG_CIMEMBERS: Cmd.ARG_CIGROUPMEMBERS, + Cmd.ARG_CONFIGLICENCESKUS: Cmd.ARG_CONFIGLICENSESKUS, Cmd.ARG_CONTACT: Cmd.ARG_PEOPLECONTACT, Cmd.ARG_CONTACTS: Cmd.ARG_PEOPLECONTACT, Cmd.ARG_CONTACTDELEGATES: Cmd.ARG_CONTACTDELEGATE, diff --git a/src/gam/gamlib/glclargs.py b/src/gam/gamlib/glclargs.py index 7fb4f02d..7584f397 100644 --- a/src/gam/gamlib/glclargs.py +++ b/src/gam/gamlib/glclargs.py @@ -895,6 +895,8 @@ class GamCLArgs(): ARG_CLASSROOMINVITATIONS = 'classroominvitations' ARG_CLASSROOMOAUTH2 = 'classroomoauth2' ARG_CLASSROOMPROFILE = 'classroomprofile' + ARG_CONFIGLICENCESKUS = 'configlicenceskus' + ARG_CONFIGLICENSESKUS = 'configlicenseskus' ARG_CONTACT = 'contact' ARG_CONTACTS = 'contacts' ARG_CONTACTDELEGATE = 'contactdelegate' diff --git a/src/gam/gamlib/glmsgs.py b/src/gam/gamlib/glmsgs.py index 40b1e07e..ea11ef0a 100644 --- a/src/gam/gamlib/glmsgs.py +++ b/src/gam/gamlib/glmsgs.py @@ -216,6 +216,7 @@ COLUMN_DOES_NOT_MATCH_ANY_OUTPUT_COLUMNS = '{0} column "{1}" does not match any COMMAND_NOT_COMPATIBLE_WITH_ENABLE_DASA = 'gam {0} {1} is not compatible with enable_dasa = true in gam.cfg' COMMIT_BATCH_COMPLETE = '{0},0/{1},commit-batch - running {2} finished, proceeding\n' COMMIT_BATCH_WAIT_N_PROCESSES = '{0},0/{1},commit-batch - waiting for {2} running {3} to finish before proceeding\n' +CONFIG_LICENSE_SKUS = 'To set license_skus in gam.cfg, execute the following command:\n' CONFIRM_WIPE_YUBIKEY_PIV = 'This will wipe all YubiKey PIV keys and configuration from your YubiKey. Are you sure? (y/N) ' CONTACT_ADMINISTRATOR_FOR_PASSWORD = 'Contact administrator for password' CONTACT_PHOTO_NOT_FOUND = 'Contact photo not found' diff --git a/src/gam/gamlib/glverlibs.py b/src/gam/gamlib/glverlibs.py index 67f499f3..385f75c7 100644 --- a/src/gam/gamlib/glverlibs.py +++ b/src/gam/gamlib/glverlibs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (C) 2025 Ross Scroggs All Rights Reserved. +# Copyright (C) 2026 Ross Scroggs All Rights Reserved. # # All Rights Reserved. # @@ -23,6 +23,7 @@ GAM_VER_LIBS = [ 'arrow', 'chardet', + 'charset_normalizer', 'cryptography', 'filelock', 'google-api-python-client', @@ -34,5 +35,6 @@ GAM_VER_LIBS = [ 'passlib', 'pathvalidate', 'pyscard', + 'urllib3', 'yubikey-manager', ]