Public/Set-PrivilegedGroupsHousekeeping.ps1

function Set-PrivilegedGroupsHousekeeping {
    <#
        .Synopsis
            Removes unauthorized users from privileged Active Directory groups in a specified OU.

        .DESCRIPTION
            This function audits groups in a specified Admin OU (Tier 0) and ensures that they only contain authorized users.
            Authorized users are those with a SamAccountName ending in _T0, _T1, or _T2 or those who have the EmployeeType as 'T0' or 'T1' or 'T2'. Any users not matching this criteria
            or not explicitly excluded are removed from these groups.

        .PARAMETER AdminGroupsDN
            The Distinguished Name of the OU where the privileged groups are located.

        .PARAMETER ExcludeList
            An array of usernames that should be excluded from removal regardless of their naming convention.

        .EXAMPLE
            Set-PrivilegedGroupsHousekeeping "OU=Groups,OU=Admin,DC=EguibarIT,DC=local"

        .EXAMPLE
            Set-PrivilegedGroupsHousekeeping -AdminGroupsDN "OU=Groups,OU=Admin,DC=EguibarIT,DC=local"

        .EXAMPLE
            Set-PrivilegedGroupsHousekeeping -AdminGroupsDN "OU=Groups,OU=Admin,DC=EguibarIT,DC=local" -ExcludeList "dvader", "hsolo"

        .NOTES
            Used Functions:
                Name | Module
                ---------------------------------------|--------------------------
                Get-ADGroup         | ActiveDirectory
                Get-ADGroupMember         | ActiveDirectory
                Remove-ADGroupMember         | ActiveDirectory
                Import-Module | Microsoft.PowerShell.Core
                Write-Verbose | Microsoft.PowerShell.Utility
                Write-Progress | Microsoft.PowerShell.Utility
                Get-FunctionToDisplay | EguibarIT.DelegationPS & EguibarIT.HousekeepingPS
                Test-IsValidDN | EguibarIT.DelegationPS & EguibarIT.HousekeepingPS
                Get-AdObjectType | EguibarIT.DelegationPS & EguibarIT.HousekeepingPS

        .NOTES
            Version: 1.0
            DateModified: 20/Jul/2017
            LasModifiedBy: Vicente Rodriguez Eguibar
                vicente@eguibar.com
                Eguibar Information Technology S.L.
                http://www.eguibarit.com
    #>

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

    Param (

        #Param1
        [Parameter(Mandatory = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $true,
            HelpMessage = 'Admin Groups OU Distinguished Name.',
            Position = 0)]
        [ValidateScript({ Test-IsValidDN -ObjectDN $_ }, ErrorMessage = 'DistinguishedName provided is not valid! Please Check.')]
        [Alias('DN', 'DistinguishedName', 'LDAPPath')]
        [String]
        $AdminGroupsDN,

        #Param2
        [Parameter(Mandatory = $false,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            ValueFromRemainingArguments = $true,
            HelpMessage = 'User list to be excluded from this process.',
            Position = 1)]
        [System.Collections.Generic.List[String]]
        $ExcludeList

    )

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

        Import-MyModule ActiveDirectory


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

        # parameters variable for splatting CMDlets
        [hashtable]$Splat = [hashtable]::New([StringComparer]::OrdinalIgnoreCase)


        # If parameter is parsed, initialize variable to be used by default objects
        If (-Not $PSBoundParameters.ContainsKey('ExcludeList')) {
            $ExcludeList = [System.Collections.Generic.List[String]]::New()
        } #end If

        $wellKnownUserSids = @{
            'S-1-5-21-*-500' = 'Administrator'
            'S-1-5-21-*-502' = 'krbtgt'
        }

        foreach ($sid in $wellKnownUserSids.Keys) {
            # For these SIDs, we always need to use the wildcard approach
            $users = Get-ADUser -Filter * | Where-Object -FilterScript { $_.SID -like $sid }

            foreach ($item in $users) {
                If ($item.SamAccountName -notin $ExcludeList) {
                    $ExcludeList.Add($item.SamAccountName) | Out-Null
                }
            } # end foreach

        } #end Foreach

        # Item Counter
        [int]$i = 0

        # Total Objects Found
        [int]$TotalObjectsFound = $AllPrivGroups.Count

        # Removed Users counter
        [int]$userRemovedCount = 0

    } #end Begin

    Process {
        # All objects from Source domain
        Write-Verbose -Message 'Getting the list of ALL semi-privileged groups.'
        $Splat = @{
            Filter      = '*'
            Properties  = 'SamAccountName'
            SearchBase  = $PsBoundParameters['AdminGroupsDN']
            ErrorAction = 'Stop'
        }
        $AllPrivGroups = Get-ADGroup @Splat

        $TotalObjectsFound = $AllPrivGroups.Count
        [int]$userRemovedCount = 0

        Write-Verbose -Message ('Iterate through each item returned. Total found: {0}' -f $TotalObjectsFound)

        # Iterate all found groups
        Foreach ($group in $AllPrivGroups) {
            $i ++

            # Display the progress bar
            $parameters = @{
                Activity        = 'Checking group membership'
                Status          = "Working on item No. $i from $TotalObjectsFound"
                PercentComplete = ($i / $TotalObjectsFound * 100)
            }
            Write-Progress @parameters

            # Exclude "Domain Users" group
            If (-Not ($group.SID.value -like '*-513')) {

                # Get members of current group
                $Splat = @{
                    Identity    = $group
                    ErrorAction = 'Continue'
                }
                $groupMembers = Get-ADGroupMember @Splat | Where-Object { $_.objectClass -eq 'user' }

                # iterate group members
                foreach ($member in $groupMembers) {

                    if ($member.SamAccountName -notmatch '_T[0-2]$' -and $ExcludeList -notcontains $member.SamAccountName) {

                        if ($PSCmdlet.ShouldProcess("$($member.SamAccountName) in $($group.SamAccountName)", 'Remove unauthorized member')) {

                            Remove-ADGroupMember -Identity $group -Members $member -Confirm:$false -ErrorAction Stop

                            Write-Verbose -Message ('
                                Removed unauthorized user {0}
                                from group {1}'
 -f
                                $member.SamAccountName, $group.SamAccountName
                            )

                            $userRemovedCount++
                        } #end If ShouldProcess
                    } #end If

                } #end If "Domain Users"
            } #end ForEach
        } #end ForEach

    } #end Process

    End {
        $Constants.NL
        Write-Verbose ('
            A semi-privileged and/or Privileged group can ONLY contain semi-privileged and/or Privileged accounts.
            Any userID which does not complies with this statement, will automatically be removed from the group.

            {0} users were removed from Privileged/Semi-Privileged groups.'
 -f
            $userRemovedCount
        )

        $txt = ($Variables.FooterHousekeeping -f $MyInvocation.InvocationName,
            'setting semi-privileged and/or Privileged group housekeeping.'
        )
        Write-Verbose -Message $txt
    } #end End
}