Enforce correct members of local administrators group

This avoids using get-localgroupmembers, which gives errors when there are ‘bad’ records in the group

# Purpose : Add and/or remove members from local administrators group
# Application: Add a domain group to the local admins group - REQ3084421 CHG0268600
# Author  : Roger C  3-Jun-2018 / 2022-12-28
# Usage: use this in a baseline
# Edits: Define the $ShouldRemediate variable:
#     For Discovery script, use $False
#     For Remediation script, use $True
# Edits: Define the members that you want to see in Local Administrators

$ScriptName = "Add_to_Local_Admins"
$Version = "1.4.3.21"
$ShouldRemediate = $True   # set this when you setup the baseline

$Compliant = $True         # Assume compliance unless we find otherwise
$Remediated = $False       # Assume not remediated unless we find otherwise
$IssuesFound = 0           # How many correctable issues were found

#           $ExtraMembers = [System.Collections.ArrayList]@()   # Members to be removed
#         $MissingMembers = [System.Collections.ArrayList]@()   # Members to added
        $RequiredMembers = [System.Collections.ArrayList]@()   # Members that we want in the group
        $DisallowedMembers = [System.Collections.ArrayList]@() # Members that we do not want in the group

#===========================================================
# Define accounts that should be in the administrators group
#===========================================================
$Null = $RequiredMembers.Add([PSCustomObject][Ordered]@{ ObjectClass = 'Group'; Name = 'CORP\DTRA-AST-LOCAL-ADMIN-W'})

#==================================================================
# Define accounts that should be not be in the administrators group
#==================================================================
$Null = $DisallowedMembers.Add([PSCustomObject][Ordered]@{ ObjectClass = 'Group'; Name = 'CORP\DTRA-AST-LOCAL-ADMIN'})

#===============================
# Setup logging function
#===============================
$LogPath = "$env:windir\ccmcache\DCM_Logs" 
#$LogPath = "c:\temp"

$Logfilename = "$LogPath\$ScriptName.log"
Function Writeln-Log( $s )  {
        $TempWhatIf = $WhatIfPreference; $WhatIfPreference = $False
        $s = (Get-Date -Format "HH:mm:ss.ff") + " $s"
        #write-Host $s
        $s | Add-Content -Path ($LogFilename) -ErrorAction SilentlyContinue | Out-Null
        $WhatIfPreference = $TempWhatIf
}
$LogFolder = split-path $Logfilename  -Parent
# Housekeeping: Remove lines if needed to reduce size and preserve the latest
if (!(Get-Item -Path $LogFolder -ErrorAction SilentlyContinue)) {New-Item -Path $LogFolder -ItemType Directory | Out-Null}
$MaxFilesize = 500 * 1KB  # 500KB
$ResizeTo = 8000          # 8000 lines (about 350K)
if (Get-ChildItem $Logfilename -ErrorAction SilentlyContinue) {
    $LogFileSize = (Get-ChildItem $Logfilename).Length
    if ($LogFileSize -gt $MaxFilesize) { # Setup a header for the start of the file
        $LogResetStamp = @("****************************************************************")                                                   
        $LogResetStamp += @("$((Get-Date).tostring()) - Log size exceeded $($MaxFilesize / 1kb)KB.")
        $LogResetStamp += @("$((Get-Date).tostring()) - Reduced to last $ResizeTo lines to reduce size")
        $LogResetStamp += @("****************************************************************")
        $LogResetStamp + (get-content $Logfilename  |  # Overwrite with headers plus the last desired lines
            select -last $ResizeTo) |  
            Set-Content $Logfilename | Out-Null
    }
} 
# INITIALIZE - write the header line
("===[ " + (get-date -uformat "%m/%d/%Y" ) `
    + " ]===[ Computer: " + (hostname) `
    + " ]===[ User: " + [System.Security.Principal.WindowsIdentity]::GetCurrent().Name `
       + " ]===[ $ScriptName V.$Version" `
   + " ]=======" ) |
    Add-Content -Path ($LogFilename)
#==============================================================================
#============================== end of logfile setup ==========================
#==============================================================================

#==============================================================================
#===[ Functions ]==============================================================
#==============================================================================
Function My-Get-LocalGroupMembers {
    param(
        [Parameter(ValuefromPipeline=$true)][array]$server = $env:computername,
        $GroupName = "Administrators"
    )
    PROCESS {
        $finalresult = @()
        $computer = [ADSI]"WinNT://$server"    
        if (!($groupName))  {
            $Groups = $computer.psbase.Children | Where {$_.psbase.schemaClassName -eq "group"} | select -expand name
        } else {
            $groups = $groupName
        }
        $CurrentDomain = ((Get-WMIObject Win32_ComputerSystem | Select-Object -ExpandProperty Domain) -split '\.')[0]
        foreach ($group in $groups) {
            $gmembers = $null
            $LocalGroup = [ADSI]("WinNT://$server/$group,group")    
            $GMembers = $LocalGroup.psbase.invoke("Members")
            $GMemberProps = @{ObjectClass="";Name="";PrincipalSource=""}
            $MemberResult = @()    
            if ($gmembers) {
                foreach ($gmember in $gmembers) {
                    $membertable = new-object psobject -Property $GMemberProps
                    $name = $gmember.GetType().InvokeMember("Name",'GetProperty', $null, $gmember, $null)
                    #$sid = $gmember.GetType().InvokeMember("objectsid",'GetProperty', $null, $gmember, $null)
                    $class = $gmember.GetType().InvokeMember("Class",'GetProperty', $null, $gmember, $null)
                    $ads = $gmember.GetType().InvokeMember("adspath",'GetProperty', $null, $gmember, $null) -replace 'WinNT://',''
                    $Name = ''
                    $Domain = ''
                    if (($ads -split '/').count -eq 2) { # (eg CORP/Domain Admins)
                        $Domain = ($ads -split '/')[0]
                        $Name = ($ads -split '/')[1]
                        $PrincipalSource = "ActiveDirectory"
                    } else  { # Domain and local hostname (eg CORP/computername/localadmin)
                        $Domain = ($ads -split '/')[1]
                        $Name = ($ads -split '/')[2]
                        $PrincipalSource = "Local"
                    }
                    if ($Name) {                        
                        $MemberTable.ObjectClass= "$class"
                        $MemberTable.name= "$Domain\$name"
                        $membertable.PrincipalSource = $PrincipalSource    
                        $MemberResult += $MemberTable
                    }
                }
            }
            $finalresult += $MemberResult 
        }
        $finalresult | select ObjectClass,Name,PrincipalSource
    } # end Process section
}

Function My-Test-LocalAdmin ($TestMember) {
    # Looks for the given name, returns $True if found in local admins
    $Found = $False
    foreach ($GroupMember in (My-Get-LocalGroupMembers)) {
        if ( ($GroupMember.Name            -eq $TestMember.Name           ) -and
             ($GroupMember.ObjectClass     -eq $TestMember.ObjectClass    ) ) {
            $Found = $true
        }
    }
    Return $Found
}

#==============================================================================
#===[ Main ]===================================================================
#==============================================================================
Writeln-Log "Should Remediate: $ShouldRemediate"
Writeln-Log "Processing Begins"

$GroupMembers = My-Get-LocalGroupMembers

if (-not $GroupMembers) {
    Writeln-Log 'Noncompliant - Powershell Error'
    Writeln-Log 'Error getting members of administrators'
    write-output 'Noncompliant - Powershell Error'
} else {
    Writeln-Log "Found $($GroupMembers.Count) Local Administrators group members"

    #-----------------------------------------------------------------
    # Check each disallowed member to make sure it is not in the group
    #-----------------------------------------------------------------
    foreach ($DisallowedMember in $DisallowedMembers) {
        Writeln-Log "Look for disallowed name: $($DisallowedMember.Name)"
        If (-not (My-Test-LocalAdmin -TestMember $DisallowedMember)) {
            Writeln-Log "$($DisallowedMember.Name) was not found in Local Administrators"
        } else { # We found a disallowed member in the group
            Writeln-Log "$($DisallowedMember.Name) does not belong in Local Administrators"
            $Compliant = $false
            $IssuesFound += 1
        
            if ($ShouldRemediate) {
                Remove-LocalGroupMember -Name 'Administrators' -Member $DisallowedMember.Name -ErrorAction SilentlyContinue
                if (My-Test-LocalAdmin -TestMember  $DisallowedMember) {
                    Writeln-Log "Failed to remove $($DisallowedMember.Name)"
                } else {
                    Writeln-Log "Successfully removed $($DisallowedMember.Name)"
                    $Remediated = $True
                }
            }
        }                    
    }
    
    #---------------------------------------------------------------
    # Check each required member to make sure it exists in the group
    #---------------------------------------------------------------
    foreach ($RequiredMember in $RequiredMembers) {
        Writeln-Log "Look for required name: $($RequiredMember.Name)"

        If (My-Test-LocalAdmin -TestMember $RequiredMember) {
            Writeln-Log "$($RequiredMember.Name) was found in Local Administrators"
        } else {
            Writeln-Log "$($RequiredMember.Name) is missing from Local Administrators"
            $Compliant = $false
            $IssuesFound += 1

            if ($ShouldRemediate) {
                writeln-log "Add: $($RequiredMember.Name)"
                Add-LocalGroupMember -Name 'Administrators' -Member $RequiredMember.Name
                if (My-Test-LocalAdmin -TestMember $RequiredMember) {
                    Writeln-Log "Successfully added $($RequiredMember.Name)"
                    $Remediated = $True
                } else {
                    Writeln-Log "Failed to add $($RequiredMember.Name)"
                }
            }
        }                    
    }

    #---------------------------------------------------------------
    # Finalize the results
    #---------------------------------------------------------------
    if ($IssuesFound -eq 1) {$es = ''} else {$es='s'}
    If (-not $ShouldRemediate) {
        If ($Compliant) {
            Writeln-Log "No issues found - Compliant"
            'Compliant'
        } else {
            Writeln-Log "$IssuesFound issue$es found - Noncompliant"
            'Noncompliant'
        }
    } else {
        if ($Remediated ) {
            Writeln-Log "$IssuesFound issue$es found - Remediated"
        } else {
            Writeln-Log "$IssuesFound issue$es found - Not Remediated"
        }
    }
}
Writeln-Log "Processing Ends"

Leave a comment