Local Administrator Audit Script


In a large environment, correctly managing Privileged Access can be complicated and complex. Especially in a Windows Server environment, administrators can easily add other users and groups to the server’s Local Administrator group. This can lead to privilege escalation, privilege abuse, and data loss. So it’s important to monitor local administrators on servers. I put together this script to audit local administrator members across all the servers.


The goals I had for this script were as follows:

  • The script can be run from one central location and audit all servers in the domain.
  • One output that could be parsed at any time for rogue accounts.
  • As dynamic as possible.

Powershell Script

This audit script is fairly simple. There’s an array of users and group names that are whitelisted because they are valid administrator accounts and groups.

We pull the server machine names from Active Directory and then pull each server’s Local Administrator members using Get-NetLocalGroup. We take the output from that and parse it in Check-Members. Any unexpected accounts that are members of the group are logged with a warning.

### Configuration ###
$Logdir = "C:\scripts\logs\"
$Logfile = "LocalAdmin-Audit-$(get-date -Format FileDateTimeUniversal).log"
$Logfile = Join-Path $Logdir $Logfile
$Tab = [char]9

# Whitelist Users and Groups
$validadmins = "LOCALUS\Domain Admins", "Domain Admins", "Enterprise Admins", "LOCALUS\Server-Local-Admins", "LOCALUS\SVC_DEPLOY", "LOCALUS\SVC_CS_Admin", "LOCALUS\SVC_SQL", "LOCALUS\SVC_Manager", "LOCALUS\Web_Admin", "LOCALUS\SVC_SQL_AGENT"

### Functions ###
Function LogWrite
   Param ([string]$logstring)

   if (!(Test-Path $Logdir)) { New-Item -ItemType Directory -Force -Path $Logdir}

   $d = Get-Date -Format “dd/MM/yyyy HH:mm:ss”
   $logline = "$d $Tab $logstring"

   Add-content $Logfile -value $logline

Function Get-NetLocalGroup {

[string]$Group = "Administrators",

Write-Verbose "Getting members of local group $Group"

#define the scriptblock
$sb = {
 Param([string]$Name = "Administrators")
$members = net localgroup $Name | 
 where {$_ -AND $_ -notmatch "command completed successfully"} | 
 select -skip 4
New-Object PSObject -Property @{
 Computername = $env:COMPUTERNAME
 Group = $Name
} #end scriptblock

#define a parameter hash table for splatting
$paramhash = @{
 Scriptblock = $sb

if ($Computername[0] -is [management.automation.runspaces.pssession]) {
else {

if ($asjob) {
    Write-Verbose "Running as job"

#run the command
Invoke-Command @paramhash | Select * -ExcludeProperty RunspaceID

} #end Get-NetLocalGroup

function Check-Members($admingroup){

$h = $admingroup.Computername
$m = $admingroup.Members

Write-Host "Admins for $h"
foreach ($m1 in $m)
     if($validadmins -contains $m1){
        Write-Host $m1 -ForegroundColor Green
        if ($m1 -match "-Admin") { Write-Host $m1 -ForegroundColor Green } else {
        Write-Host $m1 -ForegroundColor Yellow
        LogWrite "$h $Tab WARNING $Tab Unexpected Admin Account found - $m1"

### Main ###
$servers = Get-ADComputer -Filter *

foreach ($s in $servers)
    $server = $s.DNSHostName

    $ping = Test-Connection $server -Count 1 -Quiet

    LogWrite "$server $Tab Checking $server"

    if ($ping){
        Write-Host "         $server       " -BackgroundColor White -ForegroundColor DarkBlue

        $admins = Get-NetLocalGroup $server
        Check-Members $admins
        Write-Warning "Could not connect to $server"
        LogWrite "$server $Tab Could not connect to $server"
    Write-Host "                                    " -BackgroundColor White -ForegroundColor DarkBlue

### End Script ###

The script will generate a log output for every run. Servers that can not be contacted will be logged. Unexpected Admin accounts are logged as a warning.

09/11/2019 02:58:20      SERVERSSI01     Checking  SERVERSSI01
09/11/2019 02:58:20      SERVERAVS02     Checking SERVERAVS02
09/11/2019 02:58:21      SERVERAVS02     WARNING     Unexpected Admin Account found - LOCALUS\joel.robinson
09/11/2019 02:58:21      SERVERANL01     Checking SERVERANL01
09/11/2019 02:58:25      SERVERAPP02     Checking SERVERAPP02
09/11/2019 02:58:25      SERVERAPP02     Could not connect to SERVERAPP02
09/11/2019 02:58:29      SERVERSQL01     Checking SERVERSQL01

You could load this output into your SIEM, or configure the script to log directly to a SIEM or other data lake.


The Get-NetLocalGroup function was originally published on powershell.org in 2013.

My New Stories

March 2016 Web Hosting Deals
Powershell AD Group Management
Troubleshooting 403