Persistence is how everything comes back after a reboot — for malware (MITRE ATT&CK T1547), for vendor agents, and for the helpful PowerShell script someone wrote five years ago and forgot to document. The two locations that catch 90% of it on Windows are registry Run keys and Scheduled Tasks. Service autorun is a third leg, covered separately in our service security audit.
This guide enumerates every Run key, every enabled scheduled task, and classifies the launched binary by Authenticode signature so you can spot the unsigned outlier. The Dargslan.WinPersistenceAudit module bundles it all and a free PDF cheat sheet ships the workflow.
Table of Contents
Step 1: Read every Run / RunOnce key
The five canonical Run keys you must read are:
HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunHKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnceHKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run(32-bit on x64)HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunHKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\RunOnce
$keys = @(
'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run',
'HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce',
'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run',
'HKCU:\Software\Microsoft\Windows\CurrentVersion\Run',
'HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce'
)
foreach ($k in $keys) {
Get-ItemProperty $k -ErrorAction SilentlyContinue
}
Step 2: Enumerate scheduled tasks
Get-ScheduledTask returns every task; pair it with Get-ScheduledTaskInfo for last / next run:
Get-ScheduledTask | Where State -ne 'Disabled' |
ForEach-Object {
$i = Get-ScheduledTaskInfo -TaskName $_.TaskName -TaskPath $_.TaskPath
[pscustomobject]@{
Task = $_.TaskName
RunAs = $_.Principal.UserId
Action = ($_.Actions.Execute -join ',')
Last = $i.LastRunTime
}
}
Filter on Author -notmatch 'Microsoft' first to remove the OS noise.
Step 3: Authenticode classification
For every binary referenced by a Run key or task, run Get-AuthenticodeSignature. Status = Valid with a Microsoft signer is OS-baseline. A non-Microsoft Valid signer is usually a known vendor (Adobe, NVIDIA, OneDrive). NotSigned, HashMismatch, or UnknownError on a binary in %TEMP% or %APPDATA% is a hunt lead.
Triage rules
- Unsigned binary in
C:\\Users\\<user>\\AppData\\Roaming→ high priority - Run key pointing at
powershell -enc,mshta,regsvr32 /s /u /i:→ investigate immediately - Task in
\\(root path) authored by something other than Microsoft → suspicious - Task running as
SYSTEMfrom a writable folder → service-style hijack risk
A pragmatic PASS / WARN / FAIL score
- ≤ 2 unsigned autorun binaries (1 pt)
- ≤ 10 non-Microsoft signed autoruns (1 pt) (vendor agents are normal but a long list is a clean-up trigger)
- Run keys readable (sanity check) (1 pt)
3/3 PASS, 1-2 WARN, 0 FAIL.
Dargslan.WinPersistenceAudit module
Install-Module Dargslan.WinPersistenceAudit -Scope CurrentUser
Import-Module Dargslan.WinPersistenceAudit
Export-DargslanPersistenceAuditReport -OutDir C:\reports
FAQ
Why not Sysinternals Autoruns?
Autoruns is the gold standard but it is interactive. The module is the headless equivalent for fleet-wide collection.
Does this find WMI subscriptions?
This module focuses on Run keys + scheduled tasks (the 90% case). WMI persistence is a separate audit; Get-WmiObject -Namespace root\\subscription -Class __EventConsumer is the starting point.
Cheat sheet?
Free PDF at /cheat-sheets/windows-persistence-audit-2026.