Get-LocalUser: auditing local Windows accounts with PowerShell

Get-LocalUser: auditing local Windows accounts with PowerShell

Do you know exactly how many user accounts exist on your Windows machine right now? Most people don't — and that's exactly the gap an attacker with initial access will exploit: create a hidden account with a generic name, enable it with a password, and keep quiet persistence. Get-LocalUser is the cmdlet that exposes all of it in a single line.

What Get-LocalUser is

Get-LocalUser is part of the Microsoft.PowerShell.LocalAccounts module, available natively since PowerShell 5.1 (Windows 10 / Server 2016 onward). It returns every local user on the machine — manually created accounts, built-in system accounts (Administrator, Guest, DefaultAccount, WDAGUtilityAccount) and accounts linked to a Microsoft account.

Unlike net user, Get-LocalUser returns structured objects you can filter, compare and export easily.

Listing all local accounts

Open PowerShell as administrator and run:

Get-LocalUser

The output includes the columns that matter most for an immediate audit:

Name               Enabled Description
----               ------- -----------
Administrator      False   Built-in account for administering the computer
DefaultAccount     False   A user account managed by the system
Guest              False   Built-in account for guest access
john               True
WDAGUtilityAccount False   A user account managed by Windows Defender...

Any account with Enabled: True and an unrecognized name — or Guest itself enabled — deserves investigation.

Filtering by status and properties

The cmdlet accepts a name filter, or you can use Where-Object for more specific conditions:

# Enabled accounts only
Get-LocalUser | Where-Object { $_.Enabled -eq $true }

# Accounts with no expiration date (risk: no defined lifecycle)
Get-LocalUser | Where-Object { $_.AccountExpires -eq $null -and $_.Enabled -eq $true }

# Accounts that have never logged in
Get-LocalUser | Where-Object { $_.LastLogon -eq $null }

Enabled accounts with no expiration and no login history are a clear sign of an account created for persistence, or a forgotten account — either way, they should be disabled or removed.

Checking local group membership

Who is in the Administrators group?

Any unauthorized account in the Administrators group is a compromise in progress — not a suspicion, a certainty.

Get-LocalGroupMember -Group "Administrators"

On a localized Windows install the group name may differ — check with Get-LocalGroup if the command above fails.

The output shows name, type (User/Group) and SID (Security Identifier). Compare the SID against the expected one whenever you find unfamiliar names — attackers sometimes create accounts with names similar to existing ones (admn instead of admin).

# List all local groups and their members
Get-LocalGroup | ForEach-Object {
    $group = $_.Name
    Get-LocalGroupMember -Group $group | Select-Object @{n="Group";e={$group}}, Name, ObjectClass
}

Full details of a specific account

Get-LocalUser -Name "john" | Select-Object *

This exposes properties that don't show up in the default listing:

  • PasswordLastSet — when the password was last changed
  • PasswordExpires — whether the password expires (service accounts often have this disabled)
  • UserMayChangePassword — whether the user can change their own password
  • AccountExpires — the account's expiration date
  • LastLogon — date of the last successful login

Exporting to CSV: a complete audit report

For documentation or later analysis, export everything at once:

Get-LocalUser | Select-Object Name, Enabled, LastLogon, PasswordLastSet, AccountExpires, Description |
    Export-Csv -Path "$env:USERPROFILE\Desktop\account-audit-$(Get-Date -Format 'yyyyMMdd').csv" -NoTypeInformation -Encoding UTF8

The file generated on the Desktop includes the date in its name — useful for comparing weekly snapshots and identifying accounts that appeared between one period and the next.

How do you identify recently created accounts?

Get-LocalUser doesn't expose the account creation date directly, but you can cross-reference the security event log. Event 4720 records the creation of local accounts:

Get-WinEvent -FilterHashtable @{LogName="Security"; Id=4720} |
    Select-Object TimeCreated, Message | Format-List

Combined with Get-WinEvent for investigating Windows events, you can trace exactly when each account was created and by which process — essential data in incident response.

Disabling or removing a suspicious account

Found something wrong? Disable it before investigating (less destructive than removing):

# Disable
Disable-LocalUser -Name "suspicious-account"

# Remove (irreversible)
Remove-LocalUser -Name "suspicious-account"

Disabling preserves the SID and history for forensics; removing wipes the trail. In an incident response scenario, always disable first.

Quick check on problematic built-in accounts

Three checks every sysadmin should run on any new machine:

# Is Guest enabled? (it never should be)
Get-LocalUser -Name "Guest" | Select-Object Name, Enabled

# Is the built-in Administrator account active?
Get-LocalUser -Name "Administrator" | Select-Object Name, Enabled, LastLogon

# Accounts whose password is set never to expire
Get-LocalUser | Where-Object { $_.PasswordExpires -eq $null -and $_.Enabled -eq $true } |
    Select-Object Name, PasswordLastSet

Guest enabled, the built-in Administrator active for no apparent reason, and accounts with permanent passwords are classic findings in hardening audits. For the full list of settings that strengthen Windows, see the post on a safer Windows 11: 10 settings to enable today.

Get-LocalUser across a network: auditing multiple machines

The cmdlet has no -ComputerName parameter (unlike older cmdlets), but it works via PowerShell Remoting:

$computers = @("PC-01", "PC-02", "PC-03")

$result = Invoke-Command -ComputerName $computers -ScriptBlock {
    Get-LocalUser | Where-Object { $_.Enabled -eq $true } |
        Select-Object Name, Enabled, LastLogon, @{n="Machine";e={$env:COMPUTERNAME}}
}

$result | Export-Csv -Path "network-audit.csv" -NoTypeInformation -Encoding UTF8

That produces a consolidated inventory of every enabled account across multiple workstations — the starting point for any identity audit in a corporate environment.

To monitor suspicious network connections on those same machines, pair it with Get-NetTCPConnection for monitoring open ports on Windows. And if you need to review authentication logs against security events, the guide to auditing security logs with PowerShell covers that full workflow.

Auditing local accounts isn't a one-off task — it should be a monthly routine in any environment. An orphaned account, or one created by malware, goes unnoticed for months if nobody checks. Get-LocalUser takes less than a minute to run and can show you exactly what's been waiting to be found.

Comments