A PowerShell module to query Windows Security event logs for NTLM authentication events (Event ID 4624 for successful logons, and optionally Event ID 4625 for failed logons). It can also correlate NTLM logons with Event ID 4672 to identify privileged sessions, query the NTLM Operational log for process-level detail (events 8001-8006/4001-4006), and check your NTLM audit/restriction GPO configuration. Designed for security auditing and identifying legacy NTLMv1 and NTLMv2 usage across your environment.
# Install from PowerShell Gallery
Install-Module -Name Get-NtlmLogonEvents
# Or using PSResourceGet
Install-PSResource -Name Get-NtlmLogonEventsNTLM (including NTLMv1, NTLMv2, and LM) is a legacy authentication protocol that is vulnerable to relay, brute-force, and pass-the-hash attacks. Microsoft strongly recommends Kerberos authentication instead. This module helps you find which users, workstations, and applications are still using NTLM so you can remediate them before enforcing stronger authentication policies.
Privileged accounts using NTLM are especially dangerous — they are prime targets for relay and pass-the-hash attacks. The -CorrelatePrivileged switch cross-references Event ID 4672 (special privileges assigned to new logon) to flag these high-risk sessions, so you can prioritize them for Kerberos migration.
Not all NTLM usage is the same. Understanding why NTLM was used is critical for choosing the right fix:
| Scenario | AuthenticationPackageName |
LogonProcessName |
Root Cause | Remediation |
|---|---|---|---|---|
| Direct NTLM | NTLM |
NtLmSsp |
App is hardcoded to NTLM | Change app config or code to use Negotiate/Kerberos |
| Negotiate→NTLM fallback | Negotiate |
Negotiate |
Kerberos was tried but failed | Fix SPNs, DNS, clock skew, or trust issues |
This module exposes both AuthenticationPackageName and LogonProcessName so you can tell these apart at a glance.
- Query NTLMv1-only or all NTLM (v1, v2, LM) logon events
- Detect Negotiate→NTLM fallbacks —
AuthenticationPackageNameandLogonProcessNamefields reveal when Kerberos was attempted but fell back to NTLM - Include failed NTLM logon attempts (Event ID 4625) for brute-force and relay attack detection
- Correlate with privileged logons —
-CorrelatePrivilegedcross-references Event ID 4672 to flag NTLM logons that received elevated privileges (high-value relay/pass-the-hash targets) - Target localhost, a specific remote server, all domain controllers, or all DCs across the entire AD forest
- Filter by date range (
-StartTime/-EndTime) - Exclude null sessions (ANONYMOUS LOGON)
- Alternate credential support for remote connections
- Query NTLM Operational log —
-IncludeNtlmOperationalLogqueries theMicrosoft-Windows-NTLM/Operationallog for audit events (8001-8006) and block events (4001-4006) that capture process names, target server SPNs, and secure channel names - Check NTLM audit configuration —
-CheckAuditConfigreads the relevant registry values and reports whether recommended NTLM auditing GPO settings are enabled (reference) - Include full event message text —
-IncludeMessageadds the human-readableMessageproperty to output objects for both Security log and NTLM Operational log events, useful for forensic review or export - Translates impersonation level codes (
%%1831–%%1834) to human-readable names (see Impersonation Levels Reference) - Enriches
LogonTypewith human-readable descriptions (e.g.3 (Network),10 (RemoteInteractive)) — see Logon Types Reference - Outputs structured
PSCustomObject— pipeable toExport-Csv,ConvertTo-Json,Format-Table, etc.
| Requirement | Details |
|---|---|
| PowerShell | 5.1 or later |
| Privileges | Must run elevated (Administrator) to read the Security event log |
| Remote targets | WinRM enabled on remote hosts (winrm quickconfig). See WinRM Firewall Prerequisites. |
| Domain Controllers | ActiveDirectory PowerShell module (RSAT) |
| NTLM Operational log | Requires NTLM auditing GPO policies to be configured first (see Prerequisites for NTLM Operational Log). The log channel itself is enabled by default — no manual enablement needed. |
When querying remote hosts (via -ComputerName, -Target DCs, or -Target Forest), the module uses WinRM (Get-WinEvent -ComputerName / Invoke-Command). The following ports and firewall rules must be in place:
| Port | Protocol | Transport | Purpose |
|---|---|---|---|
| TCP 5985 | HTTP | WinRM default listener | Used by PowerShell remoting and Get-WinEvent -ComputerName |
| TCP 5986 | HTTPS | WinRM over TLS | Used when an SSL certificate is configured on the remote host |
- Windows Firewall — The built-in rule "Windows Remote Management (HTTP-In)" must be enabled. Running
winrm quickconfigorEnable-PSRemotingon the remote host configures this automatically. - Network / perimeter firewalls — Any firewalls between the querying machine and the remote targets must allow inbound TCP 5985 (or TCP 5986 for HTTPS).
# Option 1 — Enable-PSRemoting (recommended, creates firewall rule + starts WinRM service)
Enable-PSRemoting -Force
# Option 2 — winrm quickconfig (classic approach)
winrm quickconfig
# Verify the listener is active
winrm enumerate winrm/config/listenerNote: On domain-joined machines the WinRM service is often already enabled via Group Policy. If you can
Enter-PSSessionto the target host, no further configuration is needed.
No installation needed. Clone or download the module and run it directly:
git clone https://github.com/BetaHydri/Get-NtlmLogonEvents.git
cd Get-NtlmLogonEvents| Parameter | Type | Default | Description |
|---|---|---|---|
-Target |
String | Localhost |
Scope: Localhost, DCs, or Forest |
-ComputerName |
String[] | — | Remote host(s) to query (mandatory in its sets) |
-NumEvents |
Int | 30 |
Max events to return per host |
-OnlyNTLMv1 |
Switch | Off | Return only NTLMv1 events |
-ExcludeNullSessions |
Switch | Off | Filter out ANONYMOUS LOGON / null sessions |
-IncludeFailedLogons |
Switch | Off | Include failed logons (Event ID 4625) |
-CorrelatePrivileged |
Switch | Off | Cross-reference Event ID 4672 for privileged sessions |
-IncludeNtlmOperationalLog |
Switch | Off | Query NTLM Operational log (8001–8006 / 4001–4006) |
-CheckAuditConfig |
Switch | — | Check NTLM audit GPO settings (standalone mode) |
-IncludeMessage |
Switch | Off | Include full event Message text in output |
-Domain |
String | — | AD domain for -Target DCs (see note below) |
-StartTime |
DateTime | — | Only return events after this date/time |
-EndTime |
DateTime | — | Only return events before this date/time |
-Authentication |
String | — | WinRM auth: Default, Negotiate, Kerberos, NegotiateWithImplicitCredential |
-Credential |
PSCredential | — | Alternate credentials for remote connections |
-Target—Localhostqueries the local machine.DCsenumerates all domain controllers viaGet-ADDomainController(requires the ActiveDirectory module).Forestqueries all DCs across every domain in the AD forest.-Domain— Passed as-ServertoGet-ADDomainControllerwhen using-Target DCs. Without-Domain, DCs are discovered from the current user's domain ($env:USERDNSDOMAIN), not the machine's domain. In cross-domain scenarios (e.g., user from domain A logged onto a machine joined to domain B), you must specify-Domainexplicitly to target the correct domain's DCs. Not used with-Target Forest.-ExcludeNullSessions— Filters out ANONYMOUS LOGON events from the Security log and null-credential events ((NULL)UserName) from the NTLM Operational log.-IncludeNtlmOperationalLog— Queries theMicrosoft-Windows-NTLM/Operationallog for audit events (8001–8006) and block events (4001–4006). Requires NTLM auditing GPO policies to be configured first (see Prerequisites for NTLM Operational Log).-CheckAuditConfig— Standalone mode that reads NTLM-related registry values and reports each policy's state against Microsoft's recommended settings. No event log queries are performed.-IncludeMessage— Adds the human-readableMessageproperty to output objects for both Security log and NTLM Operational log events. Useful for forensic review or export.-Authentication— UseNegotiatewhen Kerberos is unavailable (workgroup machines, clock skew, missing SPNs). Works with or without-Credential.
| Parameter Set | Purpose | Key Parameters |
|---|---|---|
| Default | Event log queries using -Target scope |
-Target (Localhost/DCs/Forest), event filters |
| ComputerName | Event log queries on specific host(s) | -ComputerName (mandatory), event filters |
| AuditConfig | Audit config check using -Target scope |
-CheckAuditConfig (mandatory), -Target |
| AuditConfigComputerName | Audit config check on specific host(s) | -CheckAuditConfig (mandatory), -ComputerName (mandatory) |
# All NTLM events on localhost (last 30)
Get-NtlmLogonEvents
# Limit to 10 events
Get-NtlmLogonEvents -NumEvents 10
# Query a specific remote server
Get-NtlmLogonEvents -ComputerName server.contoso.com
# Query multiple remote servers
Get-NtlmLogonEvents -ComputerName server1.contoso.com, server2.contoso.com
# Query all domain controllers in the current user's domain
# NOTE: Without -Domain, DCs are discovered from the logged-in user's domain,
# NOT the machine's domain. If your user account is from domain A but you
# are logged onto a machine joined to domain B, this queries domain A's DCs.
# Use -Domain to target a specific domain explicitly.
Get-NtlmLogonEvents -Target DCs
# Query all DCs across the entire AD forest
Get-NtlmLogonEvents -Target Forest
# Query DCs in a specific domain (required in cross-domain scenarios)
# Use this when your user domain differs from the machine's domain,
# or to target a child domain / trusted domain explicitly
Get-NtlmLogonEvents -Target DCs -Domain child.contoso.com
# Query DCs in a trusted domain with alternate credentials
Get-NtlmLogonEvents -Target DCs -Domain partner.fabrikam.com -Credential (Get-Credential)
# Use alternate credentials for remote connections
Get-NtlmLogonEvents -ComputerName server.contoso.com -Credential (Get-Credential)
# Force Negotiate authentication (Kerberos with NTLM fallback) — useful for workgroup machines or SPN issues
Get-NtlmLogonEvents -ComputerName server.contoso.com -Authentication Negotiate
# Combine alternate credentials with Negotiate authentication
Get-NtlmLogonEvents -ComputerName server.contoso.com -Credential (Get-Credential) -Authentication Negotiate
# Verbose output for troubleshooting
Get-NtlmLogonEvents -Target DCs -Verbose
# Include the full event Message text (human-readable event description)
Get-NtlmLogonEvents -IncludeMessage
# Include Message text from both Security log and NTLM Operational log events
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -IncludeMessage | Format-List# Only NTLMv1 events (most insecure — prioritize these)
Get-NtlmLogonEvents -OnlyNTLMv1
# NTLMv1-only from a specific server
Get-NtlmLogonEvents -ComputerName server.contoso.com -OnlyNTLMv1
# Exclude null sessions (ANONYMOUS LOGON) to focus on real users
Get-NtlmLogonEvents -ExcludeNullSessions
# Combine: NTLMv1 only, no null sessions
Get-NtlmLogonEvents -OnlyNTLMv1 -ExcludeNullSessions
# Events from the last 7 days
Get-NtlmLogonEvents -StartTime (Get-Date).AddDays(-7)
# Events within a specific date range
Get-NtlmLogonEvents -StartTime '2026-02-01' -EndTime '2026-02-28'
# Include failed logon attempts (Event ID 4625)
Get-NtlmLogonEvents -IncludeFailedLogons
# Failed NTLMv1 attempts only
Get-NtlmLogonEvents -IncludeFailedLogons -OnlyNTLMv1 |
Where-Object EventId -eq 4625NTLM logons with elevated privileges are high-value targets for relay and pass-the-hash attacks. The -CorrelatePrivileged switch cross-references Event ID 4672 (special privileges assigned to new logon) to flag these sessions.
# Find all NTLM logons and show which ones received elevated privileges
Get-NtlmLogonEvents -CorrelatePrivileged
# Show only privileged NTLM logons (excluding null sessions)
Get-NtlmLogonEvents -CorrelatePrivileged -ExcludeNullSessions |
Where-Object IsPrivileged
# Privileged NTLMv1 logons — the highest risk combination
Get-NtlmLogonEvents -CorrelatePrivileged -OnlyNTLMv1 |
Where-Object IsPrivileged |
Select-Object Time, UserName, TargetDomainName, WorkstationName, IPAddress, PrivilegeList
# Audit privileged NTLM usage across all DCs in the last 7 days
Get-NtlmLogonEvents -Target DCs -CorrelatePrivileged -ExcludeNullSessions `
-NumEvents 1000 -StartTime (Get-Date).AddDays(-7) |
Where-Object IsPrivileged |
Sort-Object UserName |
Format-Table Time, UserName, WorkstationName, IPAddress, LmPackageName, ComputerName
# Count privileged vs. non-privileged NTLM logons
Get-NtlmLogonEvents -CorrelatePrivileged -NumEvents 500 |
Group-Object IsPrivileged |
Select-Object @{N='Category';E={if($_.Name -eq 'True'){'Privileged'}else{'Standard'}}}, CountWhen Kerberos negotiation fails (e.g., missing SPNs, clock skew, DNS issues), Windows silently falls back to NTLM via the Negotiate package. These events look like normal logons but indicate a Kerberos configuration problem.
# Find all Negotiate→NTLM fallbacks (Kerberos was tried but failed)
Get-NtlmLogonEvents -NumEvents 500 |
Where-Object AuthenticationPackageName -eq 'Negotiate'
# Compare direct NTLM vs. Negotiate fallback — group by auth package
Get-NtlmLogonEvents -NumEvents 1000 |
Group-Object -Property AuthenticationPackageName |
Select-Object Count, Name
# Show only fallbacks with workstation and user details
Get-NtlmLogonEvents -NumEvents 500 |
Where-Object AuthenticationPackageName -eq 'Negotiate' |
Select-Object UserName, TargetDomainName, WorkstationName, LmPackageName, IPAddress
# Find fallbacks on domain controllers (securityLog only: likely SPN or trust issues)
Get-NtlmLogonEvents -Target DCs -NumEvents 200 |
Where-Object AuthenticationPackageName -eq 'Negotiate' |
Sort-Object WorkstationName |
Format-Table Time, UserName, WorkstationName, LmPackageName, IPAddress
# Show only failed logon attempts
Get-NtlmLogonEvents -IncludeFailedLogons |
Where-Object EventId -eq 4625
# Failed logons grouped by source IP (spot brute-force attacks)
Get-NtlmLogonEvents -IncludeFailedLogons -NumEvents 1000 |
Where-Object EventId -eq 4625 |
Group-Object -Property IPAddress |
Sort-Object -Property Count -Descending |
Select-Object Count, Name
# Failed logons from the last 24 hours with status codes
Get-NtlmLogonEvents -IncludeFailedLogons -StartTime (Get-Date).AddHours(-24) |
Where-Object EventId -eq 4625 |
Select-Object Time, UserName, IPAddress, WorkstationName, Status, SubStatus
# Compare successful vs. failed logons side by side
Get-NtlmLogonEvents -IncludeFailedLogons -NumEvents 500 |
Group-Object EventId |
Select-Object @{N='EventType';E={if($_.Name -eq '4624'){'Success'}else{'Failed'}}}, Count# Top 10 users still using NTLM
Get-NtlmLogonEvents -NumEvents 1000 -ExcludeNullSessions |
Group-Object -Property UserName |
Sort-Object -Property Count -Descending |
Select-Object -First 10 Count, Name
# Find workstations still sending NTLMv1 (highest risk)
Get-NtlmLogonEvents -OnlyNTLMv1 -NumEvents 500 |
Select-Object -ExpandProperty WorkstationName -Unique
# Find unique source IPs using NTLM
Get-NtlmLogonEvents -NumEvents 500 |
Select-Object -ExpandProperty IPAddress -Unique
# Full audit: all NTLM events across DCs, last 7 days, no null sessions
Get-NtlmLogonEvents -Target DCs -NumEvents 5000 `
-ExcludeNullSessions `
-StartTime (Get-Date).AddDays(-7) |
Sort-Object Time
# Full audit of a child domain
Get-NtlmLogonEvents -Target DCs -Domain child.contoso.com -NumEvents 5000 `
-ExcludeNullSessions -IncludeFailedLogons `
-StartTime (Get-Date).AddDays(-7) |
Export-Csv -Path .\child_domain_ntlm_audit.csv -NoTypeInformation
# Full audit with failed logons included, exported to CSV
Get-NtlmLogonEvents -Target DCs -NumEvents 5000 `
-IncludeFailedLogons -ExcludeNullSessions `
-StartTime (Get-Date).AddDays(-7) |
Export-Csv -Path .\ntlm_audit.csv -NoTypeInformation
# Categorize each event: Direct NTLM vs. Negotiate Fallback vs. Failed
Get-NtlmLogonEvents -IncludeFailedLogons -NumEvents 500 |
Select-Object Time, UserName, WorkstationName, IPAddress, LmPackageName,
@{N='Category';E={
if ($_.EventId -eq 4625) { 'Failed' }
elseif ($_.AuthenticationPackageName -eq 'Negotiate') { 'Negotiate Fallback' }
else { 'Direct NTLM' }
}} |
Format-Table -AutoSize# Export to CSV
Get-NtlmLogonEvents -NumEvents 1000 |
Export-Csv -Path .\ntlm_audit.csv -NoTypeInformation
# Export to JSON
Get-NtlmLogonEvents |
ConvertTo-Json -Depth 3 |
Set-Content -Path .\ntlm_audit.json
# HTML report
Get-NtlmLogonEvents -NumEvents 200 |
ConvertTo-Html -Title 'NTLM Audit Report' |
Set-Content -Path .\ntlm_report.htmlBefore you can collect NTLM operational events (8001–8006), the corresponding GPO auditing policies must be enabled. The -CheckAuditConfig switch reads the relevant registry values and reports each policy—s current state against Microsoft—s recommended settings from the AD Hardening Series – Part 8.
# Check NTLM audit configuration on the local machine
Get-NtlmLogonEvents -CheckAuditConfig
# Check on all domain controllers
Get-NtlmLogonEvents -CheckAuditConfig -Target DCs
# Check on all DCs across the entire forest
Get-NtlmLogonEvents -CheckAuditConfig -Target Forest
# Check on a specific remote server
Get-NtlmLogonEvents -CheckAuditConfig -ComputerName server.contoso.com
# Check on multiple remote servers
Get-NtlmLogonEvents -CheckAuditConfig -ComputerName server1.contoso.com, server2.contoso.com
# Check on DCs in a trusted domain
Get-NtlmLogonEvents -CheckAuditConfig -Target DCs -Domain partner.fabrikam.com -Credential (Get-Credential)
# Show only policies that are NOT at the recommended setting
Get-NtlmLogonEvents -CheckAuditConfig | Where-Object { -not $_.IsRecommended }The Microsoft-Windows-NTLM/Operational log provides process-level detail that Security log events (4624/4625) lack. Use -IncludeNtlmOperationalLog to query these events alongside the Security log results.
The log channel (Microsoft-Windows-NTLM/Operational) is enabled by default on all Windows machines, including Domain Controllers — you do not need to manually enable it. However, no events are written unless the corresponding NTLM auditing GPO policies are configured:
| Events | Prerequisite GPO Settings |
|---|---|
| 8001–8006 (audit events) | Restrict NTLM: Outgoing NTLM traffic set to Audit all, Audit Incoming NTLM Traffic enabled, and/or Audit NTLM authentication in this domain enabled (DCs) |
| 4001–4006 (block events) | Restrict NTLM: Incoming/Outgoing NTLM traffic or NTLM authentication in this domain set to a Deny value |
Each event is generated by a different GPO policy that must be linked to the correct OU or scope:
| Events | GPO Policy | Link GPO To |
|---|---|---|
| 8001 (outgoing NTLM, client-side) | Restrict NTLM: Outgoing NTLM traffic to remote servers = Audit all | Domain root (or OUs containing workstations/member servers) — applies to all devices |
| 8002–8003 (incoming NTLM, server-side) | Restrict NTLM: Audit Incoming NTLM Traffic = Enable auditing for domain accounts | Domain root (or OUs containing member servers/workstations) — applies to all devices |
| 8004–8006 (DC-side credential validation) | Restrict NTLM: Audit NTLM authentication in this domain = Enable all | Domain Controllers OU only (OU=Domain Controllers,DC=contoso,DC=com) |
Key takeaway: Events 8001–8003 require GPOs on all devices (workstations and member servers), while events 8004–8006 require a GPO linked exclusively to the Domain Controllers OU.
Use -CheckAuditConfig to verify whether these policies are configured on your target machines:
# Verify audit policies are enabled before querying operational events
Get-NtlmLogonEvents -CheckAuditConfig
Get-NtlmLogonEvents -CheckAuditConfig -Target DCsIf -IncludeNtlmOperationalLog returns no events, run -CheckAuditConfig first to identify which policies need to be enabled. See the NTLM Audit GPO Settings Reference and Recommended Blocking Strategy sections for details.
# Get both Security log and NTLM operational events
Get-NtlmLogonEvents -IncludeNtlmOperationalLog
# Get only the operational events (filter by PSTypeName)
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -NumEvents 500 |
Where-Object { $_.PSObject.TypeNames -contains 'NtlmOperationalEvent' }
# Show which processes are using NTLM (from operational events)
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -NumEvents 1000 |
Where-Object ProcessName |
Group-Object ProcessName |
Sort-Object Count -Descending |
Select-Object Count, Name
# Check for NTLM block events (4001-4006) — indicates blocking is active
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -NumEvents 500 |
Where-Object EventType -eq 'Block'
# Combined: operational events on all DCs, last 7 days
Get-NtlmLogonEvents -Target DCs -IncludeNtlmOperationalLog -NumEvents 1000 `
-StartTime (Get-Date).AddDays(-7) |
Where-Object EventId -ge 8001 |
Sort-Object Time |
Format-Table Time, EventDescription, UserName, WorkstationName, ProcessName, ComputerNameSince Security log events (4624/4625) and NTLM Operational events (8001–8006) are separate event streams with no shared session ID, they cannot be deterministically joined. The following recipes help you visually correlate or analyse the combined output.
# Side-by-side timeline — interleave both streams sorted by time
# Security (4624) and Operational (8002/8003) events that happened at the same time
# are likely related to the same authentication
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -ExcludeNullSessions |
Sort-Object Time |
Format-Table EventId, Time, UserName, ProcessName, EventType, IPAddress, LmPackageName -AutoSize
# Group events by second — events within the same second are likely related
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -ExcludeNullSessions |
Sort-Object Time |
Select-Object *, @{N='TimeSlot'; E={$_.Time.ToString('yyyy-MM-dd HH:mm:ss')}} |
Format-Table EventId, TimeSlot, UserName, ProcessName, EventType, IPAddress -GroupBy TimeSlot
# Export both streams to separate CSV files for analysis in Excel / Power Query
Get-NtlmLogonEvents -ExcludeNullSessions -NumEvents 1000 |
Export-Csv -Path .\security_events.csv -NoTypeInformation
Get-NtlmLogonEvents -IncludeNtlmOperationalLog -ExcludeNullSessions -NumEvents 1000 |
Where-Object EventId -notin 4624,4625 |
Export-Csv -Path .\operational_events.csv -NoTypeInformation
# Quick pipeline join by time proximity (┬▒3 seconds)
$all = Get-NtlmLogonEvents -IncludeNtlmOperationalLog -ExcludeNullSessions
$sec = $all | Where-Object { $_.EventId -in 4624, 4625 }
$op = $all | Where-Object { $_.EventId -notin 4624, 4625 }
$sec | ForEach-Object {
$t = $_.Time
$match = $op |
Where-Object { [Math]::Abs(($_.Time - $t).TotalSeconds) -le 3 } |
Select-Object -First 1
$_ | Select-Object EventId, Time, UserName, IPAddress,
@{N='OpEventId';E={$match.EventId}},
@{N='OpProcess';E={$match.ProcessName}},
@{N='OpType';E={$match.EventType}}
} | Format-Table -AutoSizeNote: Time-based grouping works well in low-traffic environments. On busy servers with many concurrent NTLM logons from the same account, multiple operational events may fall within the same time window, making it difficult to determine which operational event belongs to which security event.
EventId : 4624
Time : 2/25/2026 10:23:45 AM
UserName : jsmith
TargetDomainName : CONTOSO
LogonType : 3 (Network)
LogonProcessName : NtLmSsp
AuthenticationPackageName : NTLM
WorkstationName : WKS-PC042
LmPackageName : NTLM V1
IPAddress : 192.168.1.50
TCPPort : 49832
ImpersonationLevel : Impersonation
ProcessName : -
Status :
FailureReason :
SubStatus :
ComputerName : DC01
EventId : 4624
Time : 2/25/2026 10:25:03 AM
UserName : jsmith
TargetDomainName : CONTOSO
LogonType : 3 (Network)
LogonProcessName : Negotiate
AuthenticationPackageName : Negotiate
WorkstationName : WKS-PC042
LmPackageName : NTLM V2
IPAddress : 192.168.1.50
TCPPort : 50112
ImpersonationLevel : Impersonation
ProcessName : -
Status :
FailureReason :
SubStatus :
ComputerName : DC01
EventId : 4625
Time : 2/25/2026 10:24:12 AM
UserName : admin
TargetDomainName : CONTOSO
LogonType : 3 (Network)
LogonProcessName : NtLmSsp
AuthenticationPackageName : NTLM
WorkstationName : ATTACKER-PC
LmPackageName : NTLM V1
IPAddress : 10.0.0.99
TCPPort : 55555
ImpersonationLevel :
ProcessName : -
Status : 0xC000006D
FailureReason : %%2313
SubStatus : 0xC0000064
ComputerName : DC01
EventId : 4624
Time : 2/25/2026 10:23:45 AM
UserName : admin.jsmith
TargetDomainName : CONTOSO
LogonType : 3 (Network)
LogonProcessName : NtLmSsp
AuthenticationPackageName : NTLM
WorkstationName : WKS-PC042
LmPackageName : NTLM V2
IPAddress : 192.168.1.50
TCPPort : 49832
ImpersonationLevel : Impersonation
ProcessName : -
Status :
FailureReason :
SubStatus :
TargetLogonId : 0x12cff454c
IsPrivileged : True
PrivilegeList : SeSecurityPrivilege
SeBackupPrivilege
SeRestorePrivilege
SeDebugPrivilege
ComputerName : DC01
EventId : 8001
EventType : Audit
EventDescription : Outgoing NTLM authentication (client-side)
Time : 2/25/2026 10:25:10 AM
UserName : jsmith
DomainName : CONTOSO
TargetName : HTTP/intranet.contoso.local
WorkstationName :
SecureChannelName:
ProcessName : C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe
ProcessId : 12456
ComputerName : WKS-PC042
PolicyName : Network security: Restrict NTLM: Audit Incoming NTLM Traffic
RegistryPath : HKLM:\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0\AuditReceivingNTLMTraffic
RawValue : 1
Setting : Enable auditing for domain accounts
Recommended : Enable auditing for domain accounts
IsRecommended : True
Scope : All devices
ComputerName : DC01
| Field | Description |
|---|---|
EventId |
Event ID (4624 = success, 4625 = failure) |
Time |
Timestamp of the logon event |
UserName |
Account name that logged on (or attempted to) |
TargetDomainName |
Domain of the target account |
LogonType |
Logon type enriched with description (e.g., 3 (Network), 10 (RemoteInteractive)) — see Logon Types Reference |
LogonProcessName |
Logon process (NtLmSsp = direct NTLM, Negotiate = SPNEGO negotiation) |
AuthenticationPackageName |
Auth package used (NTLM = direct, Negotiate = Kerberos attempted first → fell back to NTLM) |
WorkstationName |
Name of the source workstation |
LmPackageName |
NTLM version used (NTLM V1, NTLM V2, etc.) |
IPAddress |
Source IP address |
TCPPort |
Source TCP port |
ImpersonationLevel |
Impersonation level (4624 only: Anonymous, Identify, Impersonation, Delegation) |
ProcessName |
Process that initiated the logon |
Status |
Top-level NTSTATUS failure code (4625 only, e.g., 0xC000006D) |
FailureReason |
Failure reason replacement string (4625 only, e.g., %%2313) |
SubStatus |
Detailed NTSTATUS failure code (4625 only, e.g., 0xC0000064) |
TargetLogonId |
Logon session ID (4624 only) — used for correlation with Event ID 4672 |
IsPrivileged |
Whether the logon session received special privileges (only with -CorrelatePrivileged) |
PrivilegeList |
Privileges assigned to the logon session (only with -CorrelatePrivileged) |
Message |
Full human-readable event description rendered by Windows (only with -IncludeMessage) |
ComputerName |
Computer where the event was logged |
| Field | Description |
|---|---|
EventId |
Event ID (8001-8006 = audit, 4001-4006 = block) |
EventType |
Audit or Block |
EventDescription |
Human-readable description of the event type |
Time |
Timestamp of the event |
UserName |
Authenticating user |
DomainName |
User—s domain |
TargetName |
Target server SPN (8001/4001 only, e.g. HTTP/server.contoso.local) |
WorkstationName |
Client device name (8002-8006/4002-4006) |
SecureChannelName |
Server being authenticated to via secure channel (8004-8006/4004-4006, DC events) |
ProcessName |
Process name initiating or receiving NTLM (8001-8003/4001-4003) |
ProcessId |
Process ID (8001-8003/4001-4003) |
Message |
Full human-readable event description rendered by Windows (only with -IncludeMessage) |
ComputerName |
Computer where the event was logged |
| Field | Description |
|---|---|
PolicyName |
GPO policy name |
RegistryPath |
Full registry path of the setting |
RawValue |
Raw DWORD value from the registry ($null if not configured) |
Setting |
Human-readable interpretation of the current value |
Recommended |
Recommended setting per Microsoft—s AD hardening guidance |
IsRecommended |
$true if the current setting meets or exceeds the recommendation |
Scope |
Whether the policy applies to all devices or domain controllers only |
ComputerName |
Computer where the configuration was read |
| Value | Name | Description |
|---|---|---|
| 2 | Interactive | Local console logon |
| 3 | Network | Network logon (file shares, etc.) |
| 4 | Batch | Scheduled task |
| 5 | Service | Service startup |
| 7 | Unlock | Workstation unlock |
| 8 | NetworkCleartext | IIS basic auth, PowerShell with CredSSP |
| 9 | NewCredentials | RunAs with /netonly |
| 10 | RemoteInteractive | RDP / Terminal Services |
| 11 | CachedInteractive | Cached domain credentials |
Windows stores impersonation levels in the Security event log as replacement strings (%%18xx). the module translates these to human-readable names automatically.
| Code | Name | Description |
|---|---|---|
%%1831 |
Anonymous | The server cannot impersonate or identify the client |
%%1832 |
Identify | The server can identify the client but cannot impersonate |
%%1833 |
Impersonation | The server can impersonate the client's security context on the local system |
%%1834 |
Delegation | The server can impersonate the client's security context on remote systems |
Note: Failed logon events (4625) do not include an impersonation level — the field will be empty.
Common failure status codes seen in Event ID 4625:
| Status / SubStatus | Meaning |
|---|---|
0xC000006D |
Logon failure — bad username or password |
0xC000006A |
Incorrect password |
0xC0000064 |
User does not exist |
0xC0000072 |
Account disabled |
0xC0000234 |
Account locked out |
0xC0000193 |
Account expired |
0xC0000071 |
Password expired |
0xC0000133 |
Clock skew too great between client and server |
0xC0000224 |
User must change password at next logon |
This table summarizes all NTLM-related event IDs across different Windows logs. Based on guidance from Microsoft—s Active Directory Hardening Series – Part 8 – Disabling NTLM.
| Event ID | Log | Description | Pros | Cons |
|---|---|---|---|---|
| 4776 | Security | Credential Validation (DC) | Only requires DC log collection; baselines NTLM volume | Only captures client + user; not target server |
| 4624 | Security | Successful logon | Captures user, client, NTLM version, target server | Requires collection from all devices; no process name |
| 4625 | Security | Failed logon | Captures failed NTLM attempts with status codes | Requires collection from all devices |
| 4672 | Security | Special privileges assigned | Identifies privileged NTLM logon sessions | Must be correlated with 4624 by TargetLogonId |
| 8001 | NTLM Operational | Outgoing NTLM audit (client) | Captures target server SPN, process name, user | Requires GPO: Outgoing NTLM traffic = Audit all |
| 8002 | NTLM Operational | Incoming NTLM audit (local/loopback) | Captures process name being accessed | Requires GPO: Audit Incoming NTLM Traffic |
| 8003 | NTLM Operational | Incoming NTLM audit (domain account, server) | Captures client name + process on server | Requires GPO: Audit NTLM auth in this domain (DC) |
| 8004 | NTLM Operational | NTLM credential validation (DC) | Client + server (secure channel) from DC only | Requires GPO: Audit NTLM auth in this domain (DC) |
| 8005 | NTLM Operational | Direct NTLM auth to DC | Detects direct NTLM to DC | DC-only |
| 8006 | NTLM Operational | Cross-domain NTLM auth (DC) | Detects NTLM across trust boundaries | DC-only |
| 4001–4006 | NTLM Operational | NTLM blocked (mirrors 8001–8006) | Confirms blocking is working | Only logged when blocking policies are active |
| 4020,4022,4032 | NTLM Operational | Enhanced NTLM audit (Win11 24H2 / Server 2025+) | Includes fallback reason, SPN, negotiation flags, NTLM version | Only on newest OS; not yet widely available |
These Group Policy settings control NTLM auditing and restriction. Use -CheckAuditConfig to verify their state.
GPO location: Computer Configuration\Policies\Windows Settings\Security Settings\Local Policies\Security Options
Registry base paths:
| Key | Full Path |
|---|---|
| MSV1_0 | HKLM\SYSTEM\CurrentControlSet\Control\Lsa\MSV1_0 |
| Netlogon | HKLM\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters |
| GPO Setting | Registry Value | Values | Recommended | Scope |
|---|---|---|---|---|
| Audit Incoming NTLM Traffic | MSV1_0 \ AuditReceivingNTLMTraffic |
0=Disable, 1=Domain, 2=All | 1 — safe to enable | All devices |
| Outgoing NTLM traffic to remote servers | MSV1_0 \ RestrictSendingNTLMTraffic |
0=Allow, 1=Audit, 2=Deny | 1 — audit only | All devices |
| Audit NTLM auth in this domain | Netlogon \ AuditNTLMInDomain |
0=Off, 1=Acct→srv, 3=Accts, 5=Srvs, 7=All | 7 — safe to enable | DCs only |
| GPO Setting | Registry Value | Values | Recommended | Scope |
|---|---|---|---|---|
| Incoming NTLM traffic | MSV1_0 \ RestrictReceivingNTLMTraffic |
0=Allow, 1=Deny domain, 2=Deny all | 1 — final goal | All devices |
| NTLM auth in this domain | Netlogon \ RestrictNTLMInDomain |
0=Off, 1=Acct→srv, 3=Accts, 5=Srvs, 7=All | 7 — final goal | DCs only |
Note: Blocking policies should only be configured after auditing is complete and all NTLM dependencies have been remediated.
| GPO Setting | Registry Value | Scope |
|---|---|---|
| Add remote server exceptions | MSV1_0 \ ClientAllowedNTLMServers |
All devices |
| Add server exceptions in this domain | Netlogon \ DCAllowedNTLMServers |
DCs only |
Supports wildcards and SPN format (e.g.,
cifs/server.contoso.com). Minimize exceptions.
| Auditing Policy | Events | Description |
|---|---|---|
| Audit Incoming NTLM Traffic | 8002, 8003 | Incoming NTLM on servers |
| Outgoing NTLM traffic = Audit | 8001 | Outgoing NTLM from clients |
| Audit NTLM auth in domain = All | 8004, 8005, 8006 | NTLM at DC level |
| Incoming NTLM = Deny | 4002, 4003 | Blocked incoming |
| Outgoing NTLM = Deny | 4001 | Blocked outgoing |
| NTLM auth in domain = Deny | 4004, 4005, 4006 | Blocked at DC level |
Once you—ve identified NTLM usage with This module, investigate why NTLM was selected over Kerberos. Common root causes:
| Category | Cause | How to Detect | Fix |
|---|---|---|---|
| SPN Issues | Missing or duplicate SPNs | setspn -x (duplicates); Event 4769 failures; 4020 events on 24H2+ |
Register correct SPNs; remove duplicates |
| IP-based Access | Client connected by IP (Kerberos needs DNS hostname) | 8001 events with IP in TargetName; 4020 events on 24H2+ | Use DNS names; or set TryIPSPN registry + register IP SPNs |
| App Hardcoded NTLM | Application explicitly requests NTLM instead of Negotiate | 8001 events showing the process; AuthenticationPackageName=NTLM in 4624 |
Reconfigure app to use Negotiate; contact vendor |
| Negotiate Fallback | Kerberos tried but failed; NTLM used via SPNEGO | AuthenticationPackageName=Negotiate + LogonProcessName=Negotiate in 4624 |
Fix SPNs, DNS, clock skew, or trust issues |
| DC Connectivity | Client can't reach DC in resource domain for Kerberos | Multi-domain environments with network segmentation | KDC Proxy to tunnel Kerberos over HTTPS (MS-KKDCP spec); IAKerb (future) |
| Local Accounts | Local account auth always uses NTLM | 8002 events; local account in 4624 | Use domain accounts; LocalKDC (Server 2025) |
| RPC Endpoint Mapper | GPO forces NTLM for RPC EPM authentication | 8001 events from System account for RPC | Disable "Enable RPC Endpoint Mapper Client Authentication" GPO |
| Loopback Auth | System account connecting to itself | 8001 events from SYSTEM on same machine | Expected behavior; exempt if needed |
| Print Spooler | Named Pipe auth with bad SPN (krbtgt/NT Authority) |
Kerberos errors in System log | Configure Print Spooler to use RPC over TCP |
| External Trusts | External trusts default to NTLM | Cross-domain 8006 events; 4624 from trusted domain | Convert to forest trusts |
- Baseline — Enable auditing GPOs and collect 8001-8006 events for 2-4 weeks
- Protect privileged accounts — Add admin accounts to Protected Users Group
- Start with Tier 0 — Block NTLM on PAWs and management servers first
- Remediate applications — Fix SPN issues, reconfigure apps from NTLM to Negotiate
- Block outbound — Set Outgoing NTLM traffic = Deny all (with exceptions as needed)
- Block inbound — Set Incoming NTLM traffic = Deny all domain accounts
- Block domain-wide — Set NTLM auth in this domain = Deny all (final goal)
- Monitor — Watch for 4001-4006 block events and 4625 failures with SubStatus
0xC0000418
For the complete walkthrough see Active Directory Hardening Series – Part 8 – Disabling NTLM.
"No events were found"
- Ensure the Security log has Event ID 4624 (and/or 4625 with
-IncludeFailedLogons) events with NTLM authentication - Verify audit policy:
auditpol /get /subcategory:"Logon"should show Success (and Failure) auditing enabled
"Access denied" or permission errors
- Run PowerShell as Administrator
- For remote targets, ensure your account has permissions on the remote Security log
"WinRM cannot process the request"
- Run
winrm quickconfigon the remote host - Ensure the remote host is in your TrustedHosts or domain-joined
"ActiveDirectory module not found"
- Install RSAT:
Add-WindowsCapability -Online -Name Rsat.ActiveDirectory.DS-LDS.Tools~~~~0.0.1.0
The project includes a comprehensive Pester test suite with 100+ tests covering:
- XPath filter generation — default behavior, NTLMv1 filtering, null session exclusion, time range filters, failed logon inclusion, structural validation
- Event-to-object conversion — field mapping for 4624 and 4625 events, impersonation level translation, Negotiate→NTLM fallback detection, TargetLogonId extraction, pipeline input, output object shape
- NTLM Operational events —
Build-NtlmOperationalXPathFiltertime range filters,Convert-NtlmOperationalEventToObjectfield mapping for 8001-8006/4001-4006, event type classification, pipeline input - NTLM Audit Configuration —
Test-NtlmAuditConfigurationregistry reading, policy name mapping, IsRecommended evaluation, output object shape - Privileged logon correlation —
Get-PrivilegedLogonLookuptime-range queries,Merge-PrivilegedLogonDataproperty injection, IsPrivileged/PrivilegeList field mapping, handling of no matching 4672 events - Failed logon (4625) mapping — shifted property indices, Status/FailureReason/SubStatus extraction, mixed event type pipeline
- Script parameters — type checks, default values, validation rules, CmdletBinding support
- Script execution (mocked) — warning on no events, object output with mock events, ActiveDirectory module error handling
- Script file quality — help block, coding standards, absence of deprecated patterns
Run the tests:
Invoke-Pester -Path .\Tests\Get-NtlmLogonEvents.Tests.ps1 -Output Detailed| Version | Date | Changes |
|---|---|---|
| 4.6 | 2026-03-04 | Enriched LogonType output with human-readable descriptions — now shows 3 (Network) instead of just 3. Covers all standard logon types (0, 2–5, 7–13). Unknown types pass through unchanged. Applied in both local and remote (WinRM) code paths. Added comprehensive Pester tests for all logon type mappings. |
| 4.5 | 2026-03-04 | Added -IncludeMessage switch — includes the full human-readable event Message text in output for both Security log events (4624/4625) and NTLM Operational log events (8001-8006/4001-4006). Useful for detailed forensic review or exporting human-readable event descriptions. |
| 4.4 | 2026-03-03 | -ExcludeNullSessions now also filters NTLM Operational log events (8001-8006/4001-4006) where UserName is empty or (NULL) — i.e. anonymous/null-credential NTLM probes (e.g. SMB null sessions, DFS referrals, GPO processing by IP). Previously the switch only applied to Security log events (4624/4625). Updated help text accordingly. |
| 4.3 | 2026-03-03 | Fixed misleading Write-Error when no NTLM events exist on domain controllers — now emits a clear Write-Warning instead (matching localhost/remote-host behavior). Fixed Cannot index into a null array crash in NTLM Operational log parsing when event Properties collection is null or empty — added null guards in both Convert-NtlmOperationalEventToObject and the remote script block, with safe per-index bounds checks. |
| 4.2 | 2026-02-26 | -Target Forest now queries each domain's DCs separately instead of batching all forest DCs into a single Invoke-Command call; if one domain's DCs are unreachable (e.g. WinRM/DNS failure), the module emits a warning and continues with the remaining domains instead of failing entirely. Applies to event queries, NTLM operational log queries, and -CheckAuditConfig. |
| 4.1 | 2026-02-26 | Improved error handling for Azure AD-joined clients using -Target DCs or -Target Forest without line-of-sight to a domain controller; added KDC Proxy documentation references to remediation guide and acknowledgments |
| 4.0 | 2026-02-26 | Breaking change: Refactored to proper PowerShell parameter sets (Default, ComputerName, AuditConfig, AuditConfigComputerName); -Target now uses [ValidateSet('Localhost', 'DCs', 'Forest')] (default Localhost); new -ComputerName (String[]) parameter replaces -Target <hostname> for querying specific remote hosts; -CheckAuditConfig is mandatory in its own parameter sets; event-only parameters restricted to event query sets; -Domain restricted to Target-based sets |
| 3.3 | 2026-02-26 | Added -Target Forest to query all domain controllers across every domain in the AD forest; enumerates domains via Get-ADForest and collects DCs from each |
| 3.2 | 2026-02-26 | Added -CheckAuditConfig switch to verify NTLM audit/restriction GPO settings; -IncludeNtlmOperationalLog switch to query NTLM Operational log (events 8001-8006 audit + 4001-4006 block); Build-NtlmOperationalXPathFilter, Convert-NtlmOperationalEventToObject, and Test-NtlmAuditConfiguration helper functions; NTLM Event ID Reference, Audit GPO Settings Reference, and Remediation Guide in README; based on Microsoft's AD Hardening Series – Part 8 |
| 3.1 | 2026-02-25 | Added -CorrelatePrivileged switch for Event ID 4672 correlation; TargetLogonId, IsPrivileged, and PrivilegeList output fields; Get-PrivilegedLogonLookup and Merge-PrivilegedLogonData helper functions |
| 3.0 | 2026-02-25 | Added -IncludeFailedLogons switch for Event ID 4625; -Domain parameter for multi-domain/forest DC queries; AuthenticationPackageName and LogonProcessName output fields to identify Negotiate→NTLM fallbacks; EventId/Status/FailureReason/SubStatus fields; separate property mapping for 4624 vs 4625 layouts |
| 2.1 | 2023-05-25 | Fixed parameter splatting for optional DateTime parameters; relaxed pipeline type constraint for testability; added comprehensive Pester test suite (60 tests) |
| 2.0 | 2023-05-04 | Major rewrite: structured output objects, XPath filtering, date range support, credential support, impersonation level translation |
This project is licensed under the MIT License. See the LICENSE file for details.
Jan Tiedemann
- Microsoft Security Auditing Reference
- TechNet: The Most Misunderstood Windows Security Setting of All Time
- Active Directory Hardening Series – Part 8 – Disabling NTLM by Jerry Devore
- Overview of NTLM auditing enhancements in Windows 11 24H2 and Windows Server 2025
- The Evolution of Windows Authentication
- Auditing and restricting NTLM usage guide
- KDC Proxy Server (MS-KKDCP) – Protocol specification
- KDC Proxy for Remote Access – Deployment guide by Steve Syfuhs (Microsoft)
- Configure SSO for Microsoft Entra joined devices