TopNet247Independent notes for Windows admins

How-to · RDS & Session Management

Pull RDP logon history from event logs (IDs 4624, 21, 24, 25)

Which Windows event logs record RDP logons, disconnects and reconnects, and PowerShell to pull a clean history from the hosts you administer.

Someone asks, “Who was on the finance terminal server Tuesday night?” You don’t need to install anything to answer. Windows already writes every piece of an RDP visit to two or three logs. The work is knowing which event IDs matter, how they relate to each other, and how to pull them from several hosts without scrolling through Event Viewer by hand. This guide covers that, with PowerShell you can adapt, and ends with when a dedicated reporting tool makes sense.

Use only on systems you administer and with your organisation’s authorization. Logon history is personal data in many jurisdictions. Keep the output inside the access-review or incident process that asked for it.

The events that matter

Where Event ID What it tells you
Microsoft-Windows-TerminalServices-RemoteConnectionManager/Operational 1149 The RDP connection passed network authentication. The desktop logon may not have happened yet
Microsoft-Windows-TerminalServices-LocalSessionManager/Operational 21 Session logon succeeded (user, session ID, source address)
same 23 Session logoff succeeded
same 24 Session has been disconnected
same 25 Session reconnection succeeded
Security 4624, LogonType 10 RemoteInteractive logon, with source IP and authentication package
Security 4778 / 4779 Session reconnected / disconnected (needs Audit Other Logon/Logoff Events)

The Local Session Manager log is the easiest to read and needs no audit policy. The Security log is what auditors and incident responders trust, because it records logon type, authentication package and elevated-token details. Use both.

Step 1 — Confirm the logs are there and big enough

On each session host, check that logon auditing is on. It usually is by default, but confirm it:

auditpol /get /subcategory:"Logon"
auditpol /get /subcategory:"Other Logon/Logoff Events"

Then check the log sizes. Operational logs are small by default and roll over quickly on a busy host:

wevtutil gl "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" | Select-String maxSize
wevtutil gl Security | Select-String maxSize

If you need to look back a month on a busy farm, raise the limits (as an administrator):

wevtutil sl "Microsoft-Windows-TerminalServices-LocalSessionManager/Operational" /ms:104857600   # 100 MB

A bigger log on the host is not archiving. For history you’ll need later, forward events to a collector or a SIEM.

Step 2 — Get the rights without handing out admin

You don’t need Domain Admins to read these logs. Add the reviewing account (or better, a group) to the built-in Event Log Readers group on each session host, through a Restricted Groups or Local Users and Groups GPO preference. For remote queries, also enable the Remote Event Log Management firewall rule group on the hosts.

Step 3 — Pull session events from one or more hosts

The Local Session Manager events keep user, session ID and source address in the event’s UserData XML. The script reads them from there, rather than parsing the localized message text:

$hosts = 'RDSH01','RDSH02'
$since = (Get-Date).AddDays(-7)
$names = @{ 21 = 'Logon'; 23 = 'Logoff'; 24 = 'Disconnect'; 25 = 'Reconnect' }

$history = foreach ($h in $hosts) {
    Get-WinEvent -ComputerName $h -ErrorAction SilentlyContinue -FilterHashtable @{
        LogName   = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
        Id        = 21, 23, 24, 25
        StartTime = $since
    } | ForEach-Object {
        $x = ([xml]$_.ToXml()).Event.UserData.EventXML
        [pscustomobject]@{
            Time    = $_.TimeCreated
            Host    = $h
            Action  = $names[$_.Id]
            User    = $x.User
            Session = $x.SessionID
            Source  = $x.Address
        }
    }
}

$history | Sort-Object Time | Export-Csv C:\Admin\rdp-history.csv -NoTypeInformation

Event 23 (logoff) usually has no Address, which is normal. In an RDS farm that uses a Connection Broker or RD Gateway, the source address may be the gateway rather than the user’s machine. Record that limitation in your report.

Step 4 — Cross-check against the Security log

For anything that could end up in an incident report, confirm with 4624 logon type 10:

Get-WinEvent -ComputerName RDSH01 -FilterHashtable @{
    LogName = 'Security'; Id = 4624; StartTime = (Get-Date).AddDays(-7)
} | ForEach-Object {
    $d = @{}
    ([xml]$_.ToXml()).Event.EventData.Data | ForEach-Object { $d[$_.Name] = $_.'#text' }
    if ($d.LogonType -eq '10') {
        [pscustomobject]@{
            Time   = $_.TimeCreated
            User   = "$($d.TargetDomainName)\$($d.TargetUserName)"
            Source = $d.IpAddress
            Auth   = $d.AuthenticationPackageName
        }
    }
}

On a busy host the Security log is huge. -FilterHashtable filters on the server side, but still narrow the time window as far as you can. When the log is large, a query with a -FilterXPath on LogonType can be faster still.

Step 5 — Build sessions, not rows

Auditors want sessions, not raw events: “jdoe, 10.2.0.44, connected 21:04, disconnected 21:37, reconnected 22:10, logged off 23:02”. Group the CSV by Host + Session, then order each group by time. A logon (21) opens a group and a logoff (23) closes it. Any disconnects (24) and reconnects (25) fall in between.

Common mistakes

When a reporting tool is worth paying for

For a one-off question, the scripts above are enough. If someone asks for a monthly access review across a dozen hosts, the grouping and cleanup become a chore. LizardSystems Remote Desktop Audit reads these same Local Session Manager and Security logs without an agent. It aggregates by server, user, IP or time, and shows the result as tables and charts. It’s commercial, and its official OS list stops at Server 2019, so check it against your hosts first. For live sessions rather than history, see who is logged on to your RDS hosts. If you need change auditing across AD rather than RDP history, look at Netwrix Auditor. Related tools are grouped under RDS & Session Management.

Tool used in this how-to