The conversation with the security team always starts the same way: “We need to know who logged into this server over the past week.” And that’s when it turns out there are two different questions that are easy to mix up. The first is “when did this user last actually work here” (for cleaning up old profiles). The second is “who logged in exactly when, including failed attempts” (for a real investigation). One quick trick answers the first question in a second. The second requires looking somewhere less pleasant — the Security log.
The quick way: the secret in NTUSER.DAT
If you need to know in five seconds which profiles on a server are actually “alive” and which haven’t been touched in six months, don’t open the event log. Every profile in C:Users has a hidden file called NTUSER.DAT — the user’s personal registry hive, where desktop settings, file associations, and dozens of other small things get written. Windows updates it when the user logs off, which means the file’s modification date is the moment their last session ended.
PowerShell
Get-ChildItem -Path C:Users -Force -Directory |
Where-Object { $_.Name -notin "Public", "Default", "Default User" } |
Select-Object @{Name="User"; Expression={$_.Name}},
@{Name="Last session ended"; Expression={
(Get-Item "$($_.FullName)NTUSER.DAT" -Force -ErrorAction SilentlyContinue).LastWriteTime
}} |
Sort-Object "Last session ended" -Descending |
Format-Table -AutoSize
User Last session ended
---- -------------------
admin 10/7/2025 8:30:11 AM
d.ivanov 10/6/2025 6:15:45 PM
a.petrov 9/1/2025 12:00:22 PM
The most recently active users sit at the top. A great way to spot at a glance that a.petrov hasn’t logged in for a month, and decide whether that profile should be archived.
But the trick has a weak spot worth knowing before you take these numbers into an incident review: NTUSER.DAT sometimes gets updated by background profile processes even without a full interactive logon, and the file itself says nothing about where the login came from, or whether there were failed attempts. Great for cleaning up old profiles. Not enough for an investigation.
The reliable way: the Security log and event IDs 4624/4625
When the question is “who exactly, and when,” the only authoritative source is the Security log. Two event codes cover almost everything you need for a login audit:
- 4624 — a successful logon. The event details include the logon type (2 = local, 3 = network, 10 = RDP) and, for network logons, the source IP address.
- 4625 — a failed logon attempt. This is exactly what no file in a profile will ever show you — and it’s usually exactly what the security team cares about most.
PowerShell
# Every logon and failed attempt over the last 24 hours
Get-WinEvent -FilterHashtable @{
LogName = 'Security'
Id = 4624, 4625
StartTime = (Get-Date).AddHours(-24)
} | ForEach-Object {
[PSCustomObject]@{
Time = $_.TimeCreated
Event = if ($_.Id -eq 4624) { "Successful logon" } else { "FAILED logon" }
Account = $_.Properties[5].Value
SourceIP = $_.Properties[18].Value
}
} | Sort-Object Time -Descending | Format-Table -AutoSize
Time Event Account SourceIP
---- ----- ------- --------
9/26/2026 9:15:03 AM Successful logon d.ivanov 10.20.4.51
9/26/2026 9:14:07 AM FAILED logon admin 185.220.101.42
9/26/2026 9:14:05 AM FAILED logon admin 185.220.101.42
9/26/2026 9:14:02 AM FAILED logon admin 185.220.101.42
Three failed attempts against the admin account from an external IP in three seconds isn’t “the user forgot their password” — it’s a brute-force attempt. That’s exactly the picture the Security log shows you, and exactly what a profile file’s timestamp will never reveal.
The $_.Properties[N] index on a Windows event has no readable names out of the box — this is one of those things you can’t memorize, only look up once and leave in the script with a comment about what lives where. Here, [5] is the account name, [18] is the source IP; for 4625 the indexes match, because both events share the same field schema.
Which method to use when
- NTUSER.DAT — when you need to quickly find unused profiles for cleanup, inventory, or freeing up space. Not for security reports.
- The Security log (4624/4625) — when the source, second-precision timing, and failed attempts matter. The only real option for an actual investigation.
- The limit of both methods: if the event log has already rolled over (the Security log isn’t infinite by default) or the profile was deleted long ago, neither method will have any data. For servers that need history longer than a week or two, ship these events to an external SIEM instead of relying on the local log.
Practical uses for both scripts:
- Security audit: failed logon attempts after hours or from unfamiliar IPs.
- System cleanup: profiles untouched for months are candidates for archiving.
- Inventory: which accounts on a shared server are actually in use, and which can be closed.
Tags: #powershell #windows #security #audit #sysadmin
Want to become tomorrow’s architect instead of just an admin? This article is only the tip of the iceberg. On our Telegram channel “Admin Future” we publish systems-thinking guides, ready-to-use scripts, AI tips, and checklists every day that you won’t find on Google. Subscribe and become the expert in the room: Admin Future



