diff --git a/docs/GAM-Return-Codes.md b/docs/GAM-Return-Codes.md index db8f21ac..cf6bf66f 100644 --- a/docs/GAM-Return-Codes.md +++ b/docs/GAM-Return-Codes.md @@ -32,6 +32,7 @@ ENTITY_IS_A_USER_ALIAS_RC = 21 ENTITY_IS_A_GROUP_RC = 22 ENTITY_IS_A_GROUP_ALIAS_RC = 23 ENTITY_IS_AN_UNMANAGED_ACCOUNT_RC = 24 +ORGUNIT_NOT_EMPTY_RC = 25 CHECK_USER_GROUPS_ERROR_RC = 29 ORPHANS_COLLECTED_RC = 30 # Warnings/Errors @@ -61,4 +62,14 @@ TARGET_DRIVE_SPACE_ERROR_RC = 74 USER_REQUIRED_TO_CHANGE_PASSWORD_ERROR_RC = 75 USER_SUSPENDED_ERROR_RC = 76 NO_CSV_DATA_TO_UPLOAD_RC = 77 +NO_SA_ACCESS_CONTEXT_MANAGER_EDITOR_ROLE_RC = 78 +ACCESS_POLICY_ERROR_RC = 79 +YUBIKEY_CONNECTION_ERROR_RC = 80 +YUBIKEY_INVALID_KEY_TYPE_RC = 81 +YUBIKEY_INVALID_SLOT_RC = 82 +YUBIKEY_INVALID_PIN_RC = 83 +YUBIKEY_APDU_ERROR_RC = 84 +YUBIKEY_VALUE_ERROR_RC = 85 +YUBIKEY_MULTIPLE_CONNECTED_RC = 86 +YUBIKEY_NOT_FOUND_RC = 87 ``` diff --git a/docs/GamUpdates.md b/docs/GamUpdates.md index 63e8daef..423b253f 100644 --- a/docs/GamUpdates.md +++ b/docs/GamUpdates.md @@ -10,9 +10,16 @@ Add the `-s` option to the end of the above commands to suppress creating the `g See [Downloads-Installs](https://github.com/taers232c/GAMADV-XTD3/wiki/Downloads-Installs) for Windows or other options, including manual installation +### 6.78.00 + +Added command to check if an OU contains items; this is useful when tryng to delete an OU +as it must not contain any items in order to be deleted. + +* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Organizational-Units#check-organizational-unit-for-contained-items + ### 6.77.18 -Added option `showitemcountonly` to `gam print domainaliasess` that causes GAM to display the +Added option `showitemcountonly` to `gam print domainaliases` that causes GAM to display the number of domain aliasess on stdout; no CSV file is written. ### 6.77.17 diff --git a/docs/How-to-Upgrade-from-Standard-GAM.md b/docs/How-to-Upgrade-from-Standard-GAM.md index 63adee8f..7b1b326c 100644 --- a/docs/How-to-Upgrade-from-Standard-GAM.md +++ b/docs/How-to-Upgrade-from-Standard-GAM.md @@ -251,7 +251,7 @@ writes the credentials into the file oauth2.txt. admin@server:/Users/admin$ rm -f /Users/admin/GAMConfig/oauth2.txt admin@server:/Users/admin$ gam version WARNING: Config File: /Users/admin/GAMConfig/gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: /Users/admin/GAMConfig/oauth2.txt, Not Found -GAMADV-XTD3 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -923,7 +923,7 @@ writes the credentials into the file oauth2.txt. C:\>del C:\GAMConfig\oauth2.txt C:\>gam version WARNING: Config File: C:\GAMConfig\gam.cfg, Section: DEFAULT, Item: oauth2_txt, Value: C:\GAMConfig\oauth2.txt, Not Found -GAMADV-XTD3 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final Windows-10-10.0.17134 AMD64 diff --git a/docs/Organizational-Units.md b/docs/Organizational-Units.md index f5a80d63..ea2a1444 100644 --- a/docs/Organizational-Units.md +++ b/docs/Organizational-Units.md @@ -15,6 +15,7 @@ - [Print organizational units](#print-organizational-units) - [Display organizational unit counts](#display-organizational-unit-counts) - [Display indented organizational unit tree](#display-indented-organizational-unit-tree) +- [Check organizational unit for contained items](#check-organizational-unit-for-contained-items) - [Special case handling for large number of organizational units](#special-case-handling-for-large-number-of-organizational-units) ## API documentation @@ -270,6 +271,44 @@ gam show orgtree [fromparent ] [batchsuborgs []] By default, Gam displays the organizational unit tree starting at /. * `fromparent ` - Display the organizational unit tree starting at ``. +## Check organizational unit for contained items +An organizational unit can be deleted only when it contains no items: + * Chrome Browsers + * ChromeOS Devices + * Shared Drives + * Sub Org Units + * Users + +This command counts those items and displays a CSV file with the item counts. + * All counts are zero - A return code of 0 is returned and the `empty` column is `True` + * Some count is greater than 0 - A return code of 25 is returned and the `empty` column is `False` + +Only items directly within the OU are counted, items in sub-OUs are not counted. +``` + ::= + browsers| + devices| + shareddrives| + subous| + users + ::= "(,)*" + +gam check org|ou [todrive *] + [*|(fields )] + [formatjson [quotechar ]] +``` +By default, GAM checks each of the five items; you can select specfic fields +with `*` or `fields `. + +By default, GAM displays the information as columns of fields; the following option causes the output to be in JSON format: +* `formatjson` - Display the fields in JSON format. + +By default, when writing CSV files, Gam uses a quote character of double quote `"`. The quote character is used to enclose columns that contain +the quote character itself, the column delimiter (comma by default) and new-line characters. Any quote characters within the column are doubled. +When using the `formatjson` option, double quotes are used extensively in the data resulting in hard to read/process output. +The `quotechar ` option allows you to choose an alternate quote character, single quote for instance, that makes for readable/processable output. +`quotechar` defaults to `gam.cfg/csv_output_quote_char`. When uploading CSV files to Google, double quote `"` should be used. + ## Special case handling for large number of organizational units By default, the `print orgs` and `show orgtree` commands issue a single API call to get the diff --git a/docs/Version-and-Help.md b/docs/Version-and-Help.md index 35c75d7e..8b34eb03 100644 --- a/docs/Version-and-Help.md +++ b/docs/Version-and-Help.md @@ -3,7 +3,7 @@ Print the current version of Gam with details ``` gam version -GAMADV-XTD3 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -15,7 +15,7 @@ Time: 2023-06-02T21:10:00-07:00 Print the current version of Gam with details and time offset information ``` gam version timeoffset -GAMADV-XTD3 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -27,7 +27,7 @@ Your system time differs from www.googleapis.com by less than 1 second Print the current version of Gam with extended details and SSL information ``` gam version extended -GAMADV-XTD3 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource +GAMADV-XTD3 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 - pythonsource Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 @@ -64,7 +64,7 @@ MacOS High Sierra 10.13.6 x86_64 Path: /Users/Admin/bin/gamadv-xtd3 Version Check: Current: 5.35.08 - Latest: 6.77.18 + Latest: 6.78.00 echo $? 1 ``` @@ -72,7 +72,7 @@ echo $? Print the current version number without details ``` gam version simple -6.77.18 +6.78.00 ``` In Linux/MacOS you can do: ``` @@ -82,7 +82,7 @@ echo $VER Print the current version of Gam and address of this Wiki ``` gam help -GAM 6.77.18 - https://github.com/taers232c/GAMADV-XTD3 +GAM 6.78.00 - https://github.com/taers232c/GAMADV-XTD3 Ross Scroggs Python 3.12.4 64-bit final MacOS Sonoma 14.5 x86_64 diff --git a/src/GamCommands.txt b/src/GamCommands.txt index 0f3c84eb..82547d9f 100644 --- a/src/GamCommands.txt +++ b/src/GamCommands.txt @@ -3275,8 +3275,7 @@ gam delete domain gam info domain [] [formatjson] -gam print domains - [todrive *] +gam print domains [todrive *] [formatjson [quotechar ]] [showitemcountonly] gam show domains @@ -3286,10 +3285,14 @@ gam show domains gam create|add domainalias|aliasdomain gam delete domainalias|aliasdomain -gam info domainalias|aliasdomain [formatjson] +gam info domainalias|aliasdomain + [formatjson] gam print domainaliases|aliasdomains [todrive *] [formatjson [quotechar ]] -gam show domainaliases|aliasdomains [formatjson] + [showitemcountonly] +gam show domainaliases|aliasdomains + [formatjson] + [showitemcountonly] # Domain - Contacts and Global Address List @@ -4214,6 +4217,18 @@ gam print orgs|ous [todrive *] [showitemcountonly] gam show orgtree [fromparent ] [batchsuborgs []] + ::= + browsers| + devices| + shareddrives| + subous| + users + ::= "(,)*" + +gam check org|ou [todrive *] + [*|(fields )] + [formatjson [quotechar ]] + # Printers ::= diff --git a/src/GamUpdate.txt b/src/GamUpdate.txt index 7da47481..abd83478 100644 --- a/src/GamUpdate.txt +++ b/src/GamUpdate.txt @@ -2,6 +2,18 @@ Merged GAM-Team version +6.78.00 + +Added command to check if an OU contains items; this is useful when tryng to delete an OU +as it must not contain any items in order to be deleted. + +* See: https://github.com/taers232c/GAMADV-XTD3/wiki/Organizational-Units#check-organizational-unit-for-contained-items + +6.77.18 + +Added option `showitemcountonly` to `gam print domainaliases` that causes GAM to display the +number of domain aliasess on stdout; no CSV file is written. + 6.77.17 Added option `showitemcountonly` to `gam print domains` that causes GAM to display the diff --git a/src/gam/__init__.py b/src/gam/__init__.py index 60085a1e..13d04d61 100755 --- a/src/gam/__init__.py +++ b/src/gam/__init__.py @@ -332,6 +332,7 @@ ENTITY_IS_A_USER_ALIAS_RC = 21 ENTITY_IS_A_GROUP_RC = 22 ENTITY_IS_A_GROUP_ALIAS_RC = 23 ENTITY_IS_AN_UNMANAGED_ACCOUNT_RC = 24 +ORGUNIT_NOT_EMPTY_RC = 25 CHECK_USER_GROUPS_ERROR_RC = 29 ORPHANS_COLLECTED_RC = 30 # Warnings/Errors @@ -8890,14 +8891,6 @@ def getTodriveOnly(csvPF): else: unknownArgumentExit() -def getTodriveFJQCOnly(csvPF, FJQC, addTitle=False, noExit=False): - while Cmd.ArgumentsRemaining(): - myarg = getArgument() - if csvPF and myarg == 'todrive': - csvPF.GetTodriveParameters() - else: - FJQC.GetFormatJSONQuoteChar(myarg, addTitle, noExit) - DEFAULT_SKIP_OBJECTS = {'kind', 'etag', 'etags', '@type'} # Clean a JSON object @@ -15763,18 +15756,33 @@ def _printDomain(domain, csvPF): DOMAIN_ALIAS_SORT_TITLES = ['domainAliasName', 'parentDomainName', 'creationTime', 'verified'] -# gam print domainaliases [todrive *] [formatjson [quotechar ]] -# gam show domainaliases [formatjson] +# gam print domainaliases [todrive *] +# [formatjson [quotechar ]] +# [showitemcountonly] +# gam show domainaliases +# [formatjson] +# [showitemcountonly] def doPrintShowDomainAliases(): cd = buildGAPIObject(API.DIRECTORY) csvPF = CSVPrintFile(['domainAliasName'], DOMAIN_ALIAS_SORT_TITLES) if Act.csvFormat() else None FJQC = FormatJSONQuoteChar(csvPF) - getTodriveFJQCOnly(csvPF, FJQC, True) + showItemCountOnly = False + while Cmd.ArgumentsRemaining(): + myarg = getArgument() + if csvPF and myarg == 'todrive': + csvPF.GetTodriveParameters() + elif myarg == 'showitemcountonly': + showItemCountOnly = True + else: + FJQC.GetFormatJSONQuoteChar(myarg, True) try: domainAliases = callGAPIitems(cd.domainAliases(), 'list', 'domainAliases', throwReasons=[GAPI.BAD_REQUEST, GAPI.NOT_FOUND, GAPI.FORBIDDEN], customer=GC.Values[GC.CUSTOMER_ID]) count = len(domainAliases) + if showItemCountOnly: + writeStdout(f'{count}\n') + return i = 0 for domainAlias in domainAliases: i += 1 @@ -17477,7 +17485,11 @@ def _getOrgUnits(cd, orgUnitPath, fieldsList, listType, showParent, batchSubOrgs else: fields = ','.join(set(fieldsList)) listfields = f'organizationUnits({fields})' - printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT) + if listType == 'all' and orgUnitPath == '/': + printGettingAllAccountEntities(Ent.ORGANIZATIONAL_UNIT) + else: + printGettingAllEntityItemsForWhom(Ent.CHILD_ORGANIZATIONAL_UNIT, orgUnitPath, + qualifier=' (Direct Children)' if listType == 'children' else '', entityType=Ent.ORGANIZATIONAL_UNIT) if listType == 'children': batchSubOrgs = False try: @@ -17515,7 +17527,10 @@ def _getOrgUnits(cd, orgUnitPath, fieldsList, listType, showParent, batchSubOrgs except (GAPI.invalidOrgunit, GAPI.orgunitNotFound, GAPI.backendError, GAPI.badRequest, GAPI.invalidCustomerId, GAPI.loginRequired): pass - printGotAccountEntities(len(orgUnits)) + if listType == 'all' and orgUnitPath == '/': + printGotAccountEntities(len(orgUnits)) + else: + printGotEntityItemsForWhom(len(orgUnits)) if childSelector is not None: for orgUnit in orgUnits: orgUnit[ORG_UNIT_SELECTOR_FIELD] = childSelector if orgUnit['orgUnitPath'] != orgUnitPath else parentSelector @@ -17748,6 +17763,157 @@ def doShowOrgTree(): for org in sorted(orgTree): printOrgUnit(org, orgTree) +ORG_ITEMS_FIELD_MAP = { + 'browsers': 'browsers', + 'devices': 'devices', + 'shareddrives': 'sharedDrives', + 'subous': 'subOus', + 'users': 'users', + } + +# gam check org|ou [todrive *] +# [*|(fields )] +# [formatjson [quotechar ]] +def doCheckOrgUnit(): + cd = buildGAPIObject(API.DIRECTORY) + csvPF = CSVPrintFile(['orgUnitPath', 'orgUnitId', 'empty']) + FJQC = FormatJSONQuoteChar(csvPF) + orgUnitPath = None + fieldsList = [] + titlesList = [] + status, orgUnitPath, orgUnitId = checkOrgUnitPathExists(cd, getOrgUnitItem()) + orgUnitPathLower = orgUnitPath.lower() + if not status: + entityDoesNotExistExit(Ent.ORGANIZATIONAL_UNIT, orgUnitPath) + while Cmd.ArgumentsRemaining(): + myarg = getArgument() + if csvPF and myarg == 'todrive': + csvPF.GetTodriveParameters() + elif myarg in ORG_ITEMS_FIELD_MAP: + fieldsList.append(myarg) + elif myarg == 'fields': + for field in _getFieldsList(): + if field in ORG_ITEMS_FIELD_MAP: + fieldsList.append(field) + else: + invalidChoiceExit(field, list(ORG_ITEMS_FIELD_MAP), True) + else: + FJQC.GetFormatJSONQuoteChar(myarg, True) + if orgUnitPath is None: + missingArgumentExit('orgunit ') + if not fieldsList: + fieldsList = ORG_ITEMS_FIELD_MAP.keys() + orgUnitItemCounts = {} + for field in sorted(fieldsList): + title = ORG_ITEMS_FIELD_MAP[field] + orgUnitItemCounts[title] = 0 + if not FJQC.formatJSON: + titlesList.append(title) + if 'browsers' in fieldsList: + cbcm = buildGAPIObject(API.CBCM) + customerId = _getCustomerIdNoC() + printGettingAllEntityItemsForWhom(Ent.CHROME_BROWSER, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT) + pageMessage = getPageMessage() + try: + feed = yieldGAPIpages(cbcm.chromebrowsers(), 'list', 'browsers', + pageMessage=pageMessage, messageAttribute='deviceId', + throwReasons=[GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.INVALID_ORGUNIT, GAPI.FORBIDDEN], + retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, + customer=customerId, orgUnitPath=orgUnitPath, projection='BASIC', + fields='nextPageToken,browsers(deviceId)') + for browsers in feed: + orgUnitItemCounts['browsers'] += len(browsers) + except (GAPI.invalidInput, GAPI.forbidden) as e: + entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e)) + except GAPI.invalidOrgunit as e: + entityActionFailedExit([Ent.CHROME_BROWSER, None], str(e)) + except (GAPI.badRequest, GAPI.resourceNotFound): + accessErrorExit(None) + if 'devices' in fieldsList: + printGettingAllEntityItemsForWhom(Ent.CROS_DEVICE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT) + pageMessage = getPageMessageForWhom() + pageToken = None + totalItems = 0 + tokenRetries = 0 + while True: + try: + feed = callGAPI(cd.chromeosdevices(), 'list', + throwReasons=[GAPI.INVALID_INPUT, GAPI.INVALID_ORGUNIT, + GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], + retryReasons=GAPI.SERVICE_NOT_AVAILABLE_RETRY_REASONS, + pageToken=pageToken, customerId=GC.Values[GC.CUSTOMER_ID], + orgUnitPath=orgUnitPath, fields='nextPageToken,chromeosdevices(deviceId)', maxResults=GC.Values[GC.DEVICE_MAX_RESULTS]) + tokenRetries = 0 + pageToken, totalItems = _processGAPIpagesResult(feed, 'chromeosdevices', None, totalItems, pageMessage, None, Ent.CROS_DEVICE) + if feed: + orgUnitItemCounts['devices'] += len(feed.get('chromeosdevices', [])) + del feed + if not pageToken: + _finalizeGAPIpagesResult(pageMessage) + printGotAccountEntities(totalItems) + break + except GAPI.invalidInput as e: + message = str(e) +# Invalid Input: xyz - Check for invalid pageToken!! +# 0123456789012345 + if message[15:] == pageToken: + tokenRetries += 1 + if tokenRetries <= 2: + writeStderr(f'{WARNING_PREFIX}{Msg.LIST_CHROMEOS_INVALID_INPUT_PAGE_TOKEN_RETRY}') + time.sleep(tokenRetries*5) + continue + entityActionFailedWarning([Ent.CROS_DEVICE, None], message) + break + entityActionFailedWarning([Ent.CROS_DEVICE, None], message) + break + except GAPI.invalidOrgunit as e: + entityActionFailedExit([Ent.CROS_DEVICE, None], str(e)) + except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden): + accessErrorExit(cd) + if 'shareddrives' in fieldsList: + ci = buildGAPIObject(API.CLOUDIDENTITY_ORGUNITS_BETA) + printGettingAllEntityItemsForWhom(Ent.SHAREDDRIVE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT) + sds = callGAPIpages(ci.orgUnits().memberships(), 'list', 'orgMemberships', + pageMessage=getPageMessageForWhom(), + parent=f'orgUnits/{orgUnitId[3:]}', + customer=_getCustomersCustomerIdWithC(), + filter="type == 'shared_drive'") + orgUnitItemCounts['sharedDrives'] = len(sds) + if 'subous' in fieldsList: + orgUnitItemCounts['subOus'] = len(_getOrgUnits(cd, orgUnitPath, ['orgUnitPath'], 'children', False, False, None, None)) + if 'users' in fieldsList: + printGettingAllEntityItemsForWhom(Ent.USER, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT) + pageMessage = getPageMessageForWhom() + try: + feed = yieldGAPIpages(cd.users(), 'list', 'users', + pageMessage=pageMessage, + throwReasons=[GAPI.INVALID_ORGUNIT, GAPI.ORGUNIT_NOT_FOUND, + GAPI.INVALID_INPUT, GAPI.BAD_REQUEST, GAPI.RESOURCE_NOT_FOUND, GAPI.FORBIDDEN], + customer=GC.Values[GC.CUSTOMER_ID], query=orgUnitPathQuery(orgUnitPath, None), + fields='nextPageToken,users(orgUnitPath)', maxResults=GC.Values[GC.USER_MAX_RESULTS]) + for users in feed: + for user in users: + if orgUnitPathLower == user.get('orgUnitPath', '').lower(): + orgUnitItemCounts['users'] += 1 + except (GAPI.invalidOrgunit, GAPI.orgunitNotFound, GAPI.invalidInput, GAPI.badRequest, GAPI.backendError, + GAPI.invalidCustomerId, GAPI.loginRequired, GAPI.resourceNotFound, GAPI.forbidden): + checkEntityDNEorAccessErrorExit(cd, Ent.ORGANIZATIONAL_UNIT, orgUnitPath) + empty = True + for count in orgUnitItemCounts.values(): + if count > 0: + empty = False + break + baseRow = {'orgUnitPath': orgUnitPath, 'orgUnitId': orgUnitId, 'empty': empty} + row = flattenJSON(orgUnitItemCounts, baseRow.copy()) + if not FJQC.formatJSON: + csvPF.WriteRowTitles(row) + elif csvPF.CheckRowTitles(row): + baseRow['JSON'] = json.dumps(cleanJSON(orgUnitItemCounts), ensure_ascii=False, sort_keys=True) + csvPF.WriteRowNoFilter(baseRow) + csvPF.writeCSVfile(f'OrgUnit {orgUnitPath} Item Counts') + if not empty and GM.Globals[GM.SYSEXITRC] == 0: + setSysExitRC(ORGUNIT_NOT_EMPTY_RC) + ALIAS_TARGET_TYPES = ['user', 'group', 'target'] # gam create aliases|nicknames user|group|target | @@ -25050,10 +25216,10 @@ def doPrintShowBrowsers(): else: entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e)) return - except GAPI.invalidOrgunit as e: + except (GAPI.invalidOrgunit, GAPI.forbidden) as e: entityActionFailedWarning([Ent.CHROME_BROWSER, None], str(e)) return - except (GAPI.badRequest, GAPI.resourceNotFound, GAPI.forbidden): + except (GAPI.badRequest, GAPI.resourceNotFound): accessErrorExit(None) else: sortRows = True @@ -43806,18 +43972,21 @@ def doPrintUsers(entityList=None): projection=schemaParms['projection'], customFieldMask=schemaParms['customFieldMask'], maxResults=maxResults, **kwargs) for users in feed: - if showItemCountOnly: - itemCount += len(users) - continue if orgUnitPath is None: - if not printOptions['countOnly']: + if showItemCountOnly: + itemCount += len(users) + elif not printOptions['countOnly']: for user in users: _printUser(user, 0, 0) else: for user in users: _updateDomainCounts(user['primaryEmail']) else: - if not printOptions['countOnly']: + if showItemCountOnly: + for user in users: + if orgUnitPathLower == user.get('orgUnitPath', '').lower(): + itemCount += 1 + elif not printOptions['countOnly']: for user in users: if orgUnitPathLower == user.get('orgUnitPath', '').lower(): _printUser(user, 0, 0) @@ -63853,7 +64022,9 @@ def doPrintShowOrgunitSharedDrives(): if csvPF and FJQC.formatJSON: csvPF.SetJSONTitles(['name', 'JSON']) _, orgUnitId = getOrgUnitId(None, orgUnitPath) + printGettingAllEntityItemsForWhom(Ent.SHAREDDRIVE, orgUnitPath, entityType=Ent.ORGANIZATIONAL_UNIT) sds = callGAPIpages(ci.orgUnits().memberships(), 'list', 'orgMemberships', + pageMessage=getPageMessageForWhom(), parent=f'orgUnits/{orgUnitId[3:]}', customer=_getCustomersCustomerIdWithC(), filter="type == 'shared_drive'") @@ -73666,6 +73837,7 @@ MAIN_COMMANDS_WITH_OBJECTS = { {Cmd.ARG_SVCACCT: doCheckUpdateSvcAcct, Cmd.ARG_USERINVITATION: doCheckCIUserInvitations, Cmd.ARG_ISINVITABLE: doCheckCIUserInvitations, + Cmd.ARG_ORG: doCheckOrgUnit, } ), 'clear': diff --git a/src/gam/gamlib/glentity.py b/src/gam/gamlib/glentity.py index 8fe2eead..01f2f240 100644 --- a/src/gam/gamlib/glentity.py +++ b/src/gam/gamlib/glentity.py @@ -94,6 +94,7 @@ class GamEntity(): CHAT_MESSAGE_ID = 'chmi' CHAT_SPACE = 'chsp' CHAT_THREAD = 'chth' + CHILD_ORGANIZATIONAL_UNIT = 'corg' CHROME_APP = 'capp' CHROME_APP_DEVICE = 'capd' CHROME_BROWSER = 'chbr' @@ -435,6 +436,7 @@ class GamEntity(): CHAT_MEMBER_USER: ['Chat User Members', 'Chat User Member'], CHAT_SPACE: ['Chat Spaces', 'Chat Space'], CHAT_THREAD: ['Chat Threads', 'Chat Thread'], + CHILD_ORGANIZATIONAL_UNIT: ['Child Organizational Units', 'Child Organizational Unit'], CHROME_APP: ['Chrome Applications', 'Chrome Application'], CHROME_APP_DEVICE: ['Chrome Application Devices', 'Chrome Application Device'], CHROME_BROWSER: ['Chrome Browsers', 'Chrome Browser'],