Public/Set-AllGroupAdminCount.ps1

function Set-AllGroupAdminCount {
    <#
        .SYNOPSIS
            Clears the 'adminCount' attribute and enables inherited security on all group objects with 'adminCount = 1' in a specified scope.

        .DESCRIPTION
            This function finds all group objects with 'adminCount = 1' in a specified Organizational Unit (OU) or in the entire domain
            and clears the 'adminCount' attribute while also enabling inherited security on those group objects.

        .PARAMETER SubTree
            Processes group objects with 'adminCount = 1' in a specified OU and its child OUs. If not specified, the entire domain is processed.

        .PARAMETER SearchRootDN
            The Distinguished Name of the OU where the search should begin. This parameter is required if using the SubTree parameter.

        .PARAMETER ExcludedGroups
            A list of group SamAccountNames to exclude from processing.

        .EXAMPLE
            Set-AllGroupAdminCount

            Description
            -----------
            Clears the 'adminCount' attribute and resets inheritance on all group objects with 'adminCount = 1' in the domain.

        .EXAMPLE
            Set-AllGroupAdminCount -SubTree -SearchRootDN "OU=Admin Groups,OU=Admin,DC=EguibarIT,DC=local" -ExcludedGroups "Domain Admins", "Enterprise Admins"

            Description
            -----------
            Clears the 'adminCount' attribute and resets inheritance on all group objects with 'adminCount = 1' in the specified OU, excluding the specified groups.

        .OUTPUTS
            Integer
            Outputs the number of group objects modified.

        .NOTES
            Version: 1.0
            Author: Vicente Rodriguez Eguibar
            Date: 08/Feb/2024
            Contact: vicente@eguibar.com
            Company: Eguibar Information Technology S.L.
            http://www.eguibarit.com
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium')]
    [OutputType([int])]

    Param (
        [Parameter(
            Position = 0,
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'SubTree',
            HelpMessage = 'Process group objects within a specific OU and its child OUs.'
        )]
        [switch]$SubTree,

        [Parameter(
            Position = 1,
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            ParameterSetName = 'SubTree',
            HelpMessage = 'The Distinguished Name of the OU where the search starts.'
        )]
        [string]
        $SearchRootDN,

        [Parameter(
            Position = 2,
            Mandatory = $false,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = 'List of group SamAccountNames to exclude from processing.'
        )]
        [System.Collections.Generic.List[string]]
        $ExcludedGroups
    )

    Begin {
        $txt = ($Variables.HeaderHousekeeping -f
            (Get-Date).ToShortDateString(),
            $MyInvocation.Mycommand,
            (Get-FunctionDisplay -Hashtable $PsBoundParameters -Verbose:$False)
        )
        Write-Verbose -Message $txt

        ##############################
        # Variables Definition

        $ObjectsChanged = 0

        # Initialize ExcludedGroups if not provided
        if (-not $PSBoundParameters.ContainsKey('ExcludedGroups')) {
            $ExcludedGroups = [System.Collections.Generic.List[string]]::new()
        } #end If

        # Initialize splatting hashtables
        [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)

        $wellKnownSids = @{
            'S-1-5-32-544'   = 'Administrators'
            'S-1-5-32-548'   = 'Account Operators'
            'S-1-5-32-549'   = 'Server Operators'
            'S-1-5-32-550'   = 'Print Operators'
            'S-1-5-32-552'   = 'Replicators'
            'S-1-5-21-*-512' = 'Domain Admins'
            'S-1-5-21-*-516' = 'Domain Controllers'
            'S-1-5-21-*-518' = 'Schema Admins'
            'S-1-5-21-*-519' = 'Enterprise Admins'
            'S-1-5-21-*-521' = 'Read-only Domain Controllers'
            'S-1-5-21-*-526' = 'Key Admins'
            'S-1-5-21-*-527' = 'Enterprise Key Admins'
        }

        # iterate all default groups (Default AdminCount = 1)
        foreach ($sid in $wellKnownSids.Keys) {
            if ($sid -like '*-*-*') {
                # For SIDs with wildcards, we need to use a different approach
                $groups = Get-ADGroup -Filter * | Where-Object -FilterScript { $_.SID -like $sid }
            } else {
                $Splat['Filter'] = 'SID -eq ''{0}''' -f $sid
                $groups = Get-ADGroup @Splat
            } #end If-Else

            # Ensure default exclusions are added to variable
            foreach ($group in $groups) {
                if ($group -and ($group.SamAccountName -notin $ExcludedGroups)) {
                    $ExcludedGroups.Add($group.SamAccountName)
                } #end If
            } #end ForEach
        } #end ForEach

    } #end Begin

    Process {
        try {
            $Splat.Clear()
            $Splat['Filter'] = 'adminCount -eq 1'
            $Splat['Properties'] = @('SamAccountName', 'DistinguishedName')

            if ($SubTree) {
                $Splat['SearchBase'] = $SearchRootDN
                $Splat['SearchScope'] = 'Subtree'
            }

            $groups = Get-ADGroup @Splat
            $totalGroups = $groups.Count
            Write-Verbose -Message ('Total groups found: {0}' -f $totalGroups)

            foreach ($Group in $Groups) {

                $Splat.Clear()
                $Splat['Activity'] = 'Processing Groups'
                $Splat['Status'] = 'Processing {0} ({1} of {2})' -f $group.SamAccountName, ($groups.IndexOf($group) + 1), $totalGroups
                $Splat['PercentComplete'] = (($groups.IndexOf($group) + 1) / $totalGroups) * 100
                Write-Progress @Splat

                # Skip excluded groups
                if ($Group.SamAccountName -notin $ExcludedGroups) {

                    if ($PSCmdlet.ShouldProcess($Group.DistinguishedName, 'Clear adminCount and reset inheritance')) {

                        $Result = Clear-AdminCount -SamAccountName $Group.SamAccountName -Verbose:$VerbosePreference

                        if ($Result -like '*Updated*') {
                            $ObjectsChanged++
                            Write-Verbose -Message ('Updated group: {0}' -f $group.DistinguishedName)
                        } #end If
                    } #end If
                } else {
                    Write-Verbose -Message ('
                        [Skipped]
                            Excluded group: {0}
                            '
 -f $group.SamAccountName
                    )
                }#end If
            } #end Foreach
        } catch {
            Write-Error -Message ('An error occurred: {0}' -f $_)
        } #end Try-Catch
    } #end Process

    End {
        $txt = ($Variables.FooterHousekeeping -f $MyInvocation.InvocationName,
            'processing AdminCount on group objects.'
        )
        Write-Verbose -Message $txt

        return $ObjectsChanged
    } #end End
}