Running a structured PowerShell health check on your Active Directory environment takes under two hours and will surface the security gaps that attackers consistently exploit in Australian organisations — stale privileged accounts, Kerberoastable service accounts, and domain controllers with replication failures.
This is not a theoretical audit. Every command in this guide reflects findings from real assessments of Australian AD environments. Use them before commissioning a penetration test, before claiming Essential Eight compliance, and as part of your quarterly security operations cadence.
If you want professional eyes on your results, an Active Directory assessment provides structured analysis and a remediation roadmap beyond what a self-service script can offer.
Prerequisites
You will need:
- PowerShell 5.1 or later (Windows Server 2016 and later have this by default)
- The ActiveDirectory module:
Import-Module ActiveDirectory - An account with Domain Users rights at minimum (read-only; no elevated rights required for most checks)
- On a workstation, the RSAT Active Directory tools. On Windows 10/11, install them from Settings > Optional features (or, in an elevated PowerShell session,
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0). On Windows Server, add the feature instead withInstall-WindowsFeature RSAT-AD-PowerShell.
Run all commands from a domain-joined machine. Save output to a dedicated folder so you can compare results across quarters.
# Set up output directory
$OutputPath = 'C:\ADHealthCheck'
New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null
$Date = Get-Date -Format 'yyyy-MM-dd'
Write-Host "AD Health Check started: $Date" -ForegroundColor Green
Section 1 — Privileged Account Audit
Privileged account hygiene is the foundation of ACSC Essential Eight ML2 compliance. These queries identify over-privileged accounts, dormant admin accounts, and service accounts with inappropriate group membership.
1.1 — Members of Highly Privileged Groups
# Export all members of sensitive privileged groups
$PrivGroups = @(
'Domain Admins',
'Enterprise Admins',
'Schema Admins',
'Administrators',
'Group Policy Creator Owners',
'Account Operators',
'Backup Operators',
'Server Operators',
'Print Operators',
'DnsAdmins'
)
$Results = foreach ($Group in $PrivGroups) {
try {
$Members = Get-ADGroupMember -Identity $Group -Recursive -ErrorAction Stop
foreach ($Member in $Members) {
if ($Member.objectClass -eq 'user') {
# Enabled and PasswordNeverExpires are Get-ADUser properties, so query the
# user directly and request them explicitly for the fields to populate.
$Account = Get-ADUser -Identity $Member.DistinguishedName `
-Properties Enabled, PasswordNeverExpires, LastLogonDate
[PSCustomObject]@{
Group = $Group
Name = $Member.Name
SamAccountName = $Member.SamAccountName
ObjectClass = $Member.objectClass
Enabled = $Account.Enabled
LastLogonDate = $Account.LastLogonDate
PasswordExpires = -not [bool]$Account.PasswordNeverExpires
}
} else {
# Recursive expansion can surface computer or managed service accounts.
[PSCustomObject]@{
Group = $Group
Name = $Member.Name
SamAccountName = $Member.SamAccountName
ObjectClass = $Member.objectClass
Enabled = 'N/A'
LastLogonDate = $null
PasswordExpires = 'N/A'
}
}
}
} catch {
Write-Warning "Could not query group: $Group"
}
}
$Results | Export-Csv "$OutputPath\$Date-PrivilegedGroupMembers.csv" -NoTypeInformation
Write-Host "Privileged group members exported: $($Results.Count) entries"
What to look for: Service accounts in Domain Admins (extremely common in Australian environments), user accounts with passwords that never expire, and accounts that have not logged on in over 90 days.
1.2 — Dormant Admin Accounts (No Logon in 90 Days)
$90DaysAgo = (Get-Date).AddDays(-90)
# Domain Admins is a group, not a container, so its members do not live under a DN.
# Expand the group membership, then query each user account.
$DomainAdminMembers = Get-ADGroupMember -Identity "Domain Admins" -Recursive |
Where-Object { $_.objectClass -eq 'user' }
$DormantAdmins = foreach ($Member in $DomainAdminMembers) {
Get-ADUser -Identity $Member.DistinguishedName `
-Properties LastLogonDate, PasswordNeverExpires, MemberOf |
Where-Object { $_.Enabled -eq $true -and $_.LastLogonDate -lt $90DaysAgo } |
Select-Object Name, SamAccountName, LastLogonDate, PasswordNeverExpires
}
$DormantAdmins | Export-Csv "$OutputPath\$Date-DormantAdminAccounts.csv" -NoTypeInformation
Write-Host "Dormant admin accounts found: $($DormantAdmins.Count)"
Any result here is a critical finding. Disable these accounts immediately and investigate why they exist.
1.3 — Accounts with Passwords That Never Expire
$NeverExpire = Get-ADUser -Filter { PasswordNeverExpires -eq $true -and Enabled -eq $true } `
-Properties PasswordNeverExpires, LastLogonDate, MemberOf |
Select-Object Name, SamAccountName, LastLogonDate, @{
Name='IsPrivileged'
Expression={ ($_.MemberOf -match 'Domain Admins|Enterprise Admins') -ne $null }
}
$NeverExpire | Export-Csv "$OutputPath\$Date-PasswordNeverExpires.csv" -NoTypeInformation
Write-Host "Non-expiring password accounts: $($NeverExpire.Count)"
Focus remediation on any account where IsPrivileged is True. Standard user accounts with non-expiring passwords are a lower-priority finding but should still be reviewed.
Section 2 — Kerberos and Authentication Weaknesses
Kerberoasting and AS-REP roasting are the two most commonly used attack techniques against Australian AD environments, appearing in the majority of penetration test reports Educ4te reviews. These queries identify vulnerable accounts.
2.1 — Kerberoastable Service Accounts (SPNs Set on User Accounts)
$Kerberoastable = Get-ADUser -Filter { ServicePrincipalName -like '*' -and Enabled -eq $true } `
-Properties ServicePrincipalName, PasswordLastSet, PasswordNeverExpires, LastLogonDate |
Select-Object Name, SamAccountName, ServicePrincipalName,
PasswordLastSet, PasswordNeverExpires, LastLogonDate
$Kerberoastable | Export-Csv "$OutputPath\$Date-KerberoastableAccounts.csv" -NoTypeInformation
Write-Host "Kerberoastable accounts: $($Kerberoastable.Count)"
For each result, verify whether the SPN is still in use. Orphaned SPNs (service no longer running) should be removed. Active SPNs should use managed service accounts (gMSA) where possible, which rotate passwords automatically and resist offline cracking.
2.2 — AS-REP Roastable Accounts (Pre-Authentication Disabled)
$ASREPRoastable = Get-ADUser -Filter {
DoesNotRequirePreAuth -eq $true -and Enabled -eq $true
} -Properties DoesNotRequirePreAuth, LastLogonDate |
Select-Object Name, SamAccountName, LastLogonDate
$ASREPRoastable | Export-Csv "$OutputPath\$Date-ASREPRoastableAccounts.csv" -NoTypeInformation
Write-Host "AS-REP Roastable accounts: $($ASREPRoastable.Count)"
This attribute should be set on zero accounts unless you have a specific legacy application requirement. Even then, accounts should be monitored closely and the setting documented with a risk acceptance.
2.3 — Accounts Allowing RC4 or Default (Unset) Kerberos Encryption
# Accounts that still allow RC4, or leave msDS-SupportedEncryptionTypes unset.
# RC4-HMAC is bit 0x4 (decimal 4); AES128 is 0x8, AES256 is 0x10.
# A null or 0 value is the real default-RC4 risk: the KDC may fall back to RC4.
$RC4Accounts = Get-ADUser -Filter { Enabled -eq $true } `
-Properties msDS-SupportedEncryptionTypes, ServicePrincipalName |
Where-Object {
$encTypes = $_.'msDS-SupportedEncryptionTypes'
($null -eq $encTypes) -or ($encTypes -eq 0) -or ($encTypes -band 4)
} |
Select-Object Name, SamAccountName, 'msDS-SupportedEncryptionTypes'
Write-Host "Accounts allowing RC4 or using default encryption: $($RC4Accounts.Count)"
RC4-HMAC derives its key from the account's NT hash, which makes it far weaker than AES — but it is a Kerberos encryption type, not NTLM authentication. Accounts that leave msDS-SupportedEncryptionTypes unset (null or 0) are the quiet risk, because the KDC can still fall back to RC4 for them; an AES256-only account (value 16) is not flagged. ACSC guidance recommends disabling RC4 and enforcing AES-128 or AES-256 for Kerberos. This is a phased remediation because legacy applications may depend on RC4 — identify them before enforcing.
Section 3 — Stale and Orphaned Objects
Stale objects increase your attack surface without providing operational value. They represent accounts that attackers can use as cover while remaining below monitoring thresholds.
3.1 — Enabled User Accounts Inactive for Over 180 Days
$180DaysAgo = (Get-Date).AddDays(-180)
$StaleUsers = Get-ADUser -Filter {
Enabled -eq $true -and LastLogonDate -lt $180DaysAgo
} -Properties LastLogonDate, PasswordLastSet, Department, Manager |
Select-Object Name, SamAccountName, Department, Manager,
LastLogonDate, PasswordLastSet |
Sort-Object LastLogonDate
$StaleUsers | Export-Csv "$OutputPath\$Date-StaleUserAccounts.csv" -NoTypeInformation
Write-Host "Stale user accounts (180+ days): $($StaleUsers.Count)"
3.2 — Stale Computer Accounts
$StaleComputers = Get-ADComputer -Filter {
Enabled -eq $true -and LastLogonDate -lt $180DaysAgo
} -Properties LastLogonDate, OperatingSystem, OperatingSystemVersion |
Select-Object Name, DNSHostName, OperatingSystem, OperatingSystemVersion, LastLogonDate |
Sort-Object LastLogonDate
$StaleComputers | Export-Csv "$OutputPath\$Date-StaleComputerAccounts.csv" -NoTypeInformation
Write-Host "Stale computer accounts (180+ days): $($StaleComputers.Count)"
Pay particular attention to computer accounts running Windows 7, Windows Server 2008/2012, or any end-of-life operating system. These are both stale and a direct vulnerability.
Section 4 — Domain Controller Health
Domain controller replication failures cause authentication outages and can mask security events. These should be zero-finding categories.
4.1 — Replication Status
# Check replication summary
repadmin /replsummary
# Check for replication errors (requires domain admin or replication monitoring rights)
$ReplErrors = Get-ADReplicationFailure -Target (Get-ADDomain).DNSRoot -Scope Domain
if ($ReplErrors) {
$ReplErrors | Select-Object Server, FirstFailureTime, FailureCount, LastError |
Export-Csv "$OutputPath\$Date-ReplicationErrors.csv" -NoTypeInformation
Write-Warning "Replication errors found: $($ReplErrors.Count)"
} else {
Write-Host "No replication errors detected." -ForegroundColor Green
}
4.2 — Domain Controller Sysvol and Netlogon Availability
$DomainControllers = Get-ADDomainController -Filter * | Select-Object Name, IPv4Address, Site
foreach ($DC in $DomainControllers) {
$SysvolPath = "\\$($DC.Name)\SYSVOL"
$NetlogonPath = "\\$($DC.Name)\NETLOGON"
[PSCustomObject]@{
DCName = $DC.Name
Site = $DC.Site
SysvolAccess = (Test-Path $SysvolPath)
NetlogonAccess = (Test-Path $NetlogonPath)
}
} | Format-Table -AutoSize
4.3 — DNS Health
# Check DNS server records for domain controllers
$DCList = (Get-ADDomainController -Filter *).HostName
foreach ($DC in $DCList) {
$DNSResult = Resolve-DnsName -Name $DC -ErrorAction SilentlyContinue
if (-not $DNSResult) {
Write-Warning "DNS resolution failed for: $DC"
} else {
Write-Host "DNS OK: $DC -> $($DNSResult.IPAddress)" -ForegroundColor Green
}
}
# Check for stale DNS records (computers that no longer exist but still have DNS entries)
dcdiag /test:DNS /v /c /DnsBasic /DnsForwarders /DnsDelegation /DnsDynamicUpdate
Section 5 — Group Policy and Security Configuration Checks
5.1 — GPOs with No Links (Orphaned Policies)
$AllGPOs = Get-GPO -All
$OrphanedGPOs = $AllGPOs | Where-Object { $_.GpoStatus -ne 'AllSettingsDisabled' } |
Where-Object { (Get-GPOReport -Guid $_.Id -ReportType XML) -notmatch '<LinksTo>' }
$OrphanedGPOs | Select-Object DisplayName, CreationTime, ModificationTime |
Export-Csv "$OutputPath\$Date-OrphanedGPOs.csv" -NoTypeInformation
Write-Host "Orphaned GPOs: $($OrphanedGPOs.Count)"
5.2 — Default Domain Policy Last Modified Date
$DefaultPolicy = Get-GPO -Name 'Default Domain Policy'
Write-Host "Default Domain Policy last modified: $($DefaultPolicy.ModificationTime)"
Write-Host "Default Domain Controllers Policy last modified:"
(Get-GPO -Name 'Default Domain Controllers Policy').ModificationTime
Organisations should avoid modifying the Default Domain Policy directly. Custom GPOs linked to the appropriate OUs provide better manageability and auditability.
Section 6 — Protected Users Group
The Protected Users security group is one of the most underutilised defences in Australian AD environments. Accounts in this group cannot use NTLM authentication, cannot use RC4 Kerberos encryption, and are not cached on devices. All Tier 0 and Tier 1 admin accounts should be members.
$ProtectedUsers = Get-ADGroupMember -Identity 'Protected Users' -Recursive
Write-Host "Accounts in Protected Users group: $($ProtectedUsers.Count)"
# Compare against privileged group membership
$DomainAdmins = Get-ADGroupMember -Identity 'Domain Admins' -Recursive
$NotProtected = $DomainAdmins | Where-Object {
$_.SamAccountName -notin $ProtectedUsers.SamAccountName
}
Write-Host "Domain Admins NOT in Protected Users: $($NotProtected.Count)" -ForegroundColor Yellow
$NotProtected | Select-Object Name, SamAccountName
Any Domain Admin not in the Protected Users group is an immediate remediation priority. The Tier Model implementation guide covers how to integrate Protected Users group membership into your tiered administration design.
Interpreting and Prioritising Your Results
After running these checks, you will have a set of CSV exports and console output. Prioritise remediation in this order:
- Critical — Immediate action: Dormant privileged accounts (disable within 24 hours), Kerberoastable service accounts with administrative rights, AS-REP roastable accounts, replication failures
- High — Remediate within 30 days: Service accounts in Domain Admins, accounts with non-expiring passwords in privileged groups, Domain Admins not in Protected Users
- Medium — Remediate within 90 days: Stale user accounts over 180 days, stale computer accounts, orphaned GPOs, accounts using RC4 encryption
- Informational — Ongoing hygiene: Overall stale object volumes, DNS housekeeping
Taking It Further
These PowerShell commands cover the fundamentals, but a complete Active Directory security assessment goes deeper — analysing ACL inheritance, attack path mapping with BloodHound, and reviewing fine-grained password policies against your actual account population.
A structured Active Directory assessment provides this depth, including a finding report mapped directly to ACSC Essential Eight controls and an actionable remediation plan your team can implement immediately.
Run these checks quarterly at minimum. Save each run's output to a dated folder and compare results over time — the trend is often as informative as any single point-in-time result. An environment where stale account counts are falling and privileged group membership is shrinking is one moving in the right direction, regardless of where it starts.


