Public/Get-LocalAdminAudit.ps1
|
function Get-LocalAdminAudit { <# .SYNOPSIS Audits local administrator group membership across domain computers. .DESCRIPTION Connects to domain computers and enumerates the local Administrators group. Identifies non-standard members that may represent a security risk. Uses CIM (WinRM) with fallback to ADSI for maximum compatibility. .PARAMETER SearchBase OU to scope the computer search. Defaults to entire domain. .PARAMETER ComputerName Specific computer name(s) to audit instead of querying AD. .PARAMETER ExpectedAdmins List of accounts that are expected/approved local admins. These will be flagged as "Expected" rather than "Unexpected" in results. Defaults to: Administrator, Domain Admins. .EXAMPLE Get-LocalAdminAudit Audits all enabled domain computers. .EXAMPLE Get-LocalAdminAudit -ComputerName "SERVER01","SERVER02" .EXAMPLE Get-LocalAdminAudit -ExpectedAdmins "Administrator","Domain Admins","IT-LocalAdmins" #> [CmdletBinding()] param( [string]$SearchBase, [string[]]$ComputerName, [string[]]$ExpectedAdmins = @('Administrator', 'Domain Admins') ) # Get computer list if ($ComputerName) { $computers = $ComputerName } else { $compParams = @{ Filter = 'Enabled -eq $true' Properties = @('OperatingSystem') } if ($SearchBase) { $compParams['SearchBase'] = $SearchBase } $computers = (Get-ADComputer @compParams).Name } $total = $computers.Count $current = 0 foreach ($computer in $computers) { $current++ Write-Progress -Activity "Auditing local admins" -Status "$computer ($current/$total)" -PercentComplete (($current / $total) * 100) # Test connectivity first if (-not (Test-Connection -ComputerName $computer -Count 1 -Quiet)) { [PSCustomObject]@{ ComputerName = $computer Member = $null Type = $null Expected = $null Status = 'Offline' } continue } try { # Try CIM first (modern, uses WinRM) $admins = Get-CimInstance -ClassName Win32_GroupUser -ComputerName $computer -ErrorAction Stop | Where-Object { $_.GroupComponent.Name -eq 'Administrators' } | ForEach-Object { $memberName = $_.PartComponent.Name $memberDomain = $_.PartComponent.Domain $fullName = "$memberDomain\$memberName" $isExpected = $ExpectedAdmins -contains $memberName [PSCustomObject]@{ ComputerName = $computer Member = $fullName Type = $_.PartComponent.CimClass.CimClassName -replace 'Win32_', '' Expected = $isExpected Status = 'OK' } } if ($admins) { $admins } else { [PSCustomObject]@{ ComputerName = $computer Member = 'None found' Type = $null Expected = $null Status = 'OK' } } } catch { # Fallback: try ADSI try { $group = [ADSI]"WinNT://$computer/Administrators" $members = @($group.psbase.Invoke("Members")) foreach ($member in $members) { $memberName = $member.GetType().InvokeMember("Name", 'GetProperty', $null, $member, $null) $isExpected = $ExpectedAdmins -contains $memberName [PSCustomObject]@{ ComputerName = $computer Member = $memberName Type = 'Unknown (ADSI)' Expected = $isExpected Status = 'OK (ADSI fallback)' } } } catch { [PSCustomObject]@{ ComputerName = $computer Member = $null Type = $null Expected = $null Status = "Error: $_" } } } } Write-Progress -Activity "Auditing local admins" -Completed } |