Endpoint security audit
Endpoint Security Posture Audit
Collects a device security posture (local admins, inactive and passwordless accounts, RDP, firewall, Microsoft Defender for Endpoint, and BitLocker per drive) and writes it to JSON plus the console. No external database.
Replace generic placeholder values (tenant, domain, secrets) for your own environment before running. Read it first and test safely.
<#
.SYNOPSIS
Collects a Windows endpoint's security posture and outputs it as JSON (and to the console).
.DESCRIPTION
Gathers, for the local machine:
- Local administrator accounts
- Enabled local accounts inactive for 30+ days
- Enabled local accounts with no password required
- RDP (remote access) enabled/disabled
- Windows Firewall enabled on all profiles
- Microsoft Defender for Endpoint (MDE) service running
- BitLocker protection status per drive
...then writes the result to a JSON file in Downloads and prints it to the console, so
it can be captured by an RMM, a scheduled task, or piped anywhere you like.
Run as administrator. No external database or modules required.
#>
# --- Collect the security posture ---
# Enabled local accounts not logged in for 30+ days
$inactiveusers = Get-LocalUser | Where-Object { $_.Lastlogon -lt (Get-Date).AddDays(-30) -and $_.Enabled -eq $true } | Select-Object Name
$inactiveusersNameArray = @()
foreach ($inactiveuser in $inactiveusers) { $inactiveusersNameArray += $inactiveuser.Name }
# Local administrators (net localgroup output has 2 header lines to drop)
$localAdminOutput = net localgroup administrators
$localAdminUsers = $localAdminOutput | Select-String -Pattern "^\s*([^\s]+)\s*$" | ForEach-Object { $_.Matches[0].Groups[1].Value }
$arrayList = [System.Collections.ArrayList]$localAdminUsers
$arrayList.RemoveRange(0, 2)
$localAdminUsers = $arrayList.ToArray()
# Enabled local accounts that do not require a password
$insecureUsers = Get-LocalUser | Where-Object { $_.PasswordRequired -eq $false -and $_.Enabled -eq $true }
$insecureUsersNames = $insecureUsers.Name
if ($null -eq $insecureUsersNames) { $insecureUsersNames = "NONE" }
# RDP enabled?
$rdpenabled = Get-ItemProperty -Path 'HKLM:\System\CurrentControlSet\Control\Terminal Server' -Name "fDenyTSConnections" | Select-Object fDenyTSConnections
$rdp = if ($rdpenabled.fDenyTSConnections -eq 1) { "Disabled" } else { "Enabled" }
# Firewall enabled on all 3 profiles?
if (((Get-NetFirewallProfile | Select-Object name, enabled) | Where-Object { $_.Enabled -eq $True } | Measure-Object).Count -eq 3) { $firewall = "Enabled" } else { $firewall = "Disabled" }
# Microsoft Defender for Endpoint service running?
$mdecheck = (Get-Service -Name "Windows Defender Advanced Threat Protection Service" -ErrorAction SilentlyContinue).Status
$mdestatus = if ($mdecheck -eq "Running") { "Enabled" } else { "Disabled" }
# BitLocker protection per drive
$drives = Get-BitLockerVolume
$enabledDrives = @()
$disabledDrives = @()
foreach ($drive in $drives) {
if ($drive.ProtectionStatus -eq 'On') { $enabledDrives += $drive.MountPoint.TrimEnd('\') }
elseif ($drive.ProtectionStatus -eq 'Off') { $disabledDrives += $drive.MountPoint.TrimEnd('\') }
}
if ($enabledDrives.Count -eq 0) {
$bitlockerstatus = "Disabled: No drives with BitLocker enabled."
} elseif ($disabledDrives.Count -eq 0) {
$bitlockerstatus = "Enabled: $($enabledDrives -join ', ')"
} else {
$bitlockerstatus = "Enabled: $($enabledDrives -join ', ') Disabled: $($disabledDrives -join ', ')"
}
# --- Assemble and output the result ---
$results = [PSCustomObject]@{
computerName = $env:COMPUTERNAME
collectedAt = (Get-Date).ToString('s')
adminUsers = $localAdminUsers
inactiveUsers = $inactiveusersNameArray
insecureUsers = $insecureUsersNames
firewall = $firewall
remoteAccess = $rdp
mdeStatus = $mdestatus
bitlocker = $bitlockerstatus
}
# Write to a JSON file in Downloads, and print to the console
$OutputPath = "$env:USERPROFILE\Downloads\SecurityAudit_$($env:COMPUTERNAME)_$(Get-Date -Format 'yyyy-MM-dd').json"
$results | ConvertTo-Json -Depth 4 | Set-Content -Path $OutputPath -Encoding utf8
Write-Host "Security audit written to: $OutputPath" -ForegroundColor Green
$results | ConvertTo-Json -Depth 4