Part of the DBA-Tools Project.
Standing up a trace session is the easy part. The actual decision, is this database safe to decommission, is that service account still connecting from the right host, comes from reading the results back and turning raw events into an answer. Extended Events writes to rolling .xel files, and reading those directly means shredding XML by hand every single time, exactly the kind of task worth writing once and never doing manually again.
This script reads a named session’s file target, shreds every captured event, and rolls it up into unique caller combinations, login, hostname, application, database, with an occurrence count and a first-seen/last-seen time range for each. Point it at Create Decommission Audit Session or Create Login Activity Session output and you get a straight answer instead of a pile of raw events.
Why XE Session Activity Matters
The value of any trace session is entirely in how it gets read back:
- A
.xelfile full of raw events isn’t an answer by itself, it needs grouping and summarising before “who’s connecting to this” turns into “these three applications, from these two hosts.” - The
first_seen/last_seen/span_hourscolumns turn a pile of events into a timeline, whether a caller showed up once by accident or has been connecting steadily across the whole capture window. - This is the script named directly in the two Create scripts’ own headers as the intended way to review their output, they’re a pair by design, not a coincidence.
When to Run This Script
- After letting Create Decommission Audit Session run for its full observation window, to see exactly who’s still connecting before you decommission anything
- After letting Create Login Activity Session run, to build the list of accounts, hosts, and applications actually using a server
- Periodically during a long-running capture, to check progress rather than waiting until the very end to look
- Before removing a session with Remove XE Session, review its captured data one last time
The Script
Run the following script against your SQL Server instance.
/*
Script Name : Get-XeSessionActivity
Category : traces
Purpose : Reads and summarises Extended Events file target data for a named session.
Returns unique caller combinations (login, hostname, app, database) with occurrence counts and time range.
Primary use: reviewing DecommissionAudit or LoginActivity session output to determine if a database or server is still in active use.
Author : Peter Whyte (https://sqldba.blog/dba-scripts-get-xe-session-activity/)
Requires : VIEW SERVER STATE, read access to the XE output folder
*/
-- SAFE:ReadOnly
-- IMPACT:Low
SET NOCOUNT ON;
/* ── Configuration ───────────────────────────────────────────────────────── */
DECLARE @SessionName NVARCHAR(128) = N'DecommissionAudit';
/* ─────────────────────────────────────────────────────────────────────────── */
/* Locate the file target path from the running session metadata */
DECLARE @FilePath NVARCHAR(500);
SELECT @FilePath =
CAST(
CAST(t.target_data AS XML).value(
'(EventFileTarget/File/@name)[1]', 'nvarchar(500)')
AS NVARCHAR(500))
FROM sys.dm_xe_sessions s
JOIN sys.dm_xe_session_targets t ON t.event_session_address = s.address
WHERE s.name = @SessionName
AND t.target_name = 'event_file';
IF @FilePath IS NULL
BEGIN
/* Session stopped but definition still exists — get path from session fields */
SELECT @FilePath = CONVERT(NVARCHAR(500), f.value)
FROM sys.server_event_sessions ses
JOIN sys.server_event_session_targets t ON t.event_session_id = ses.event_session_id
AND t.name = 'event_file'
JOIN sys.server_event_session_fields f ON f.event_session_id = ses.event_session_id
AND f.object_id = t.target_id
AND f.name = 'filename'
WHERE ses.name = @SessionName;
END;
IF @FilePath IS NULL
BEGIN
RAISERROR('Session "%s" not found. Check the session name and ensure it has been created.', 16, 1, @SessionName);
RETURN;
END;
/* Build wildcard pattern: extract folder + session name + *.xel */
DECLARE @Folder NVARCHAR(500) = LEFT(@FilePath, LEN(@FilePath) - CHARINDEX(N'\', REVERSE(@FilePath)));
DECLARE @Pattern NVARCHAR(500) = @Folder + N'\' + @SessionName + N'*.xel';
/* Read, shred, and summarise */
;WITH raw AS (
SELECT
e.value('(event/@name)[1]', 'nvarchar(128)') AS event_name,
e.value('(event/action[@name="database_name"]/value)[1]', 'nvarchar(128)') AS database_name,
e.value('(event/action[@name="username"]/value)[1]', 'nvarchar(128)') AS username,
e.value('(event/action[@name="nt_username"]/value)[1]', 'nvarchar(128)') AS nt_username,
e.value('(event/action[@name="client_hostname"]/value)[1]','nvarchar(128)') AS client_hostname,
e.value('(event/action[@name="client_app_name"]/value)[1]','nvarchar(256)') AS client_app_name,
CAST(e.value('(event/@timestamp)[1]', 'nvarchar(30)') AS DATETIME2) AS event_time
FROM (
SELECT CAST(event_data AS XML) AS e
FROM sys.fn_xe_file_target_read_file(@Pattern, NULL, NULL, NULL)
) AS src
)
SELECT
event_name,
COALESCE(database_name, '(server-level)') AS database_name,
COALESCE(nt_username, username) AS login_name,
client_hostname,
client_app_name,
COUNT(*) AS occurrences,
MIN(event_time) AS first_seen,
MAX(event_time) AS last_seen,
DATEDIFF(HOUR, MIN(event_time), MAX(event_time)) AS span_hours
FROM raw
GROUP BY event_name, database_name, nt_username, username, client_hostname, client_app_name
ORDER BY occurrences DESC, database_name, login_name;
This locates the named session’s output file (falling back to the session definition if it’s been stopped), builds a wildcard pattern to catch every rollover file, shreds each event’s XML into login, hostname, application, and database fields, then groups and counts by that unique combination, returning one row per distinct caller with how often they showed up and across what time span.
Change @SessionName before running. It defaults to DecommissionAudit, edit the DECLARE at the top of the script to point at LoginActivity, SpExecution, or any other session you’ve created.
How To Run From The Repo
Clone DBA Tools, initialize and run the script:
# Clone dba-tools repo:
git clone https://github.com/peterwhyte-lgtm/dba-tools
# Initialize environment:
cd dba-tools
.\Initialize-Environment.ps1
# Summarise a named session's captured file target data:
.\run.ps1 Get-XeSessionActivity
# To run against a remote sql server:
.\run.ps1 Get-XeSessionActivity -ServerInstance SQLSERVER01
This script lives in the repo at:
Example Output
| event_name | database_name | login_name | client_app_name | occurrences | span_hours |
|---|---|---|---|---|---|
| login | master | NT SERVICE\SQLTELEMETRY | SQLServerCEIP | 20953 | 827 |
| login | master | HPAI01\Peter | Core Microsoft SqlClient Data Provider | 610 | 827 |
| login | msdb | NT SERVICE\SQLSERVERAGENT | SQLAgent – Job Manager | 604 | 587 |
| login | WatchtowerMetrics | NT SERVICE\SQLSERVERAGENT | SQLAgent – TSQL JobStep (Job 0x1B78…) | 265 | 587 |
| login | msdb | NT SERVICE\SQLSERVERAGENT | SQLAgent – Schedule Saver | 181 | 587 |
| login | master | HPAI01\Peter | SSMS IntelliSense | 106 | 705 |
| login | RandomLab | HPAI01\Peter | Core Microsoft SqlClient Data Provider | 86 | 104 |
| error_reported | master | (blank) | Core Microsoft SqlClient Data Provider | 2 | 0 |
Real output, read back from a LoginActivity session pointed at a local SQL Server 2025 instance that’s genuinely been running this trace, off and on, for over a month, 36 distinct rows in total, trimmed here to the top by occurrence count plus one row worth calling out specifically. That last row is a deliberately triggered failed login (sqlcmd/Invoke-Sqlcmd with a bad SQL login and a wrong password), captured as a real error_reported event with login_name blank because the login attempt never resolved to a valid principal.
Understanding the Results
occurrencestells you what’s routine before you even look at anything else. TheSQLServerCEIPtelemetry service alone accounts for nearly 21,000 of the 36 rows’ worth of events above, SQL Server’s own telemetry, logging in roughly every two and a half minutes, was the single loudest caller on this instance by a wide margin.span_hoursis 827 for the top rows here, meaning this trace has genuinely captured over a month of real activity.sys.fn_xe_file_target_read_file‘s wildcard pattern reads every rollover file matching the session name, not just the ones from the session’s most recent start, so recreating a session withCreate-LoginActivitySessiondoesn’t reset this history unless the old.xelfiles are actually deleted from disk.- The
error_reportedrow with a blanklogin_nameis exactly the kind of finding this session exists to catch, a failed login that never resolved to a real account. In production, a pattern like this repeating against a real login name is worth investigating immediately, not just noting. - SQL Agent’s own service account shows up constantly here (
SQLAgent - Job Manager,SQLAgent - Schedule Saver, and multiple job-step logins), a reminder that “who’s connecting” always includes the instance’s own automation, not just applications and people.
Best Practices
- Run this against a session that’s been capturing for a meaningful window, several business days at minimum, a single hour of data will understate genuine but infrequent callers.
- Zero rows back doesn’t necessarily mean zero activity, confirm the session is actually running (Get Active XE Sessions) and pointed at the folder you expect before trusting a quiet result.
- Old
.xelfiles from a previous incarnation of a same-named session stay on disk and get picked up by the wildcard read, if you want a genuinely clean window, delete the old files, don’t just recreate the session. - On a session with high event volume, particularly a
DecommissionAuditcapture left with no database filter on a busy instance, this query has real XML shredding work to do and can take noticeably longer to return than a filtered, lower-volume session. - Read the results before deciding anything is safe to remove,
span_hoursandoccurrencestogether tell you whether a caller is a one-off or a steady, ongoing pattern.
Related Scripts
You may also find these scripts useful:
- Create Decommission Audit Session
- Create Login Activity Session
- Create SP Execution Session
- Get Active XE Sessions
- Remove XE Session
Frequently Asked Questions
How do I read Extended Events .xel file data with T-SQL?
Use sys.fn_xe_file_target_read_file, pointed at the file path (a wildcard pattern catches rollover files), then shred the returned event_data XML column with .value() calls for the fields you need. This script does exactly that and rolls the result up into a summary, rather than leaving you with one row per raw event.
Why does this script return no rows for a session I know is running?
The most common cause is a session that hasn’t hit MAX_DISPATCH_LATENCY yet, events sit buffered before they’re flushed to the file target, so a session that’s only been running for a few seconds may genuinely have nothing written to disk. Give it a minute, then check MAX_DISPATCH_LATENCY in the session definition if it’s still empty after that.
Summary
A trace session’s output is only as useful as your ability to read it back and turn it into a decision. This script does the XML shredding once, so you get a clean summary of who’s actually connecting, calling, or running, complete with how often and over what window, instead of a raw event file nobody wants to page through by hand.
Point it at any session in this series once it’s had time to capture a real pattern, and let the occurrence counts and time spans do the talking.
Leave a Reply