PSBlitz by Vlad Drumea is a well-made PowerShell wrapper around Brent Ozar's First Responder Kit: one call, one report with the plan cache, index diagnostics, waits, deadlocks and more. I compared its feature list with the 196 functions of sqmSQLTool and found three things we simply did not have, plus one function that did less than its own help text claimed. sqmSQLTool 1.9.169.0 closes all four.
One design rule stayed the same as for everything else in the module: nothing is installed on the target instance. No stored procedures in master, no helper database. Every report reads the DMVs directly and needs VIEW SERVER STATE. In banking environments where every object in master needs a change ticket, that is the difference between "run it now" and "run it next month".
1. What is expensive right now: Get-sqmPlanCacheReport
The plan cache answers the question every performance call starts with: which statements cost the most? Get-sqmPlanCacheReport ranks every cached statement from sys.dm_exec_query_stats once per metric and returns the top N of each list: CPU, duration, reads, writes, executions, memory grant and tempdb spills.
Get-sqmPlanCacheReport -SqlInstance SQL01 -SortBy All -Top 5 -ExportPlans
A statement that is expensive in several ways comes back once, with all its ranks, for example CPU#1, Duration#3, Reads#4, and with its share of the instance-wide total. The plan XML is checked for the usual suspects: implicit conversions, missing index hints, spills, key lookups, missing join predicates and parallel plans.
The part I like most is -ExportPlans. Each plan is saved as .sqlplan and goes straight into Get-sqmPlanInsights, the module's plan analyzer:
$top = Get-sqmPlanCacheReport -SqlInstance SQL01 -SortBy CPU -Top 1 -ExportPlans -NoReport
Get-sqmPlanInsights -Path $top[0].PlanFile
Two details that matter in practice. Only values above zero get a rank, so -SortBy Spills on a healthy server returns nothing instead of ten random rows with zero spills. And memory grant and spill columns only exist from SQL Server 2016 SP2 / 2017 on; on older versions those metrics are skipped with a warning instead of failing the whole report.
2. Which indexes cost more than they bring: Get-sqmIndexUsageReport
sqmSQLTool already told you which indexes are missing and how fragmented the existing ones are. It did not tell you which existing indexes you should not have. Get-sqmIndexUsageReport looks for:
- Unused: nonclustered indexes without a single read since the last restart, maintained on every write anyway. Unique indexes and primary keys are never reported, they enforce rules.
- Duplicate: same key columns, same order and direction, same filter. The clustered, primary key or unique index is kept; differing INCLUDE columns are named so you can merge first.
- Overlapping: the keys are the leading part of a wider index. The report checks whether the wider index already covers the INCLUDE columns.
- Heaps, disabled indexes and hypothetical leftovers from the Tuning Advisor.
The function never drops anything. The DROP statements land in a _DropCandidates.sql file with every line commented out. Usage counters reset on every restart and failover, so an index used only by the month-end run looks unused for weeks. If the instance runs for less than seven days the report says so and marks every "unused" finding as unreliable. Talk to the application owner before you delete anything.
3. Is it the storage: Get-sqmFileLatencyReport
"The database is slow" very often ends with "the SAN was busy". Get-sqmFileLatencyReport reads sys.dm_io_virtual_file_stats and gives the average read and write latency per database file, rated and summarized per drive.
# Since the last start
Get-sqmFileLatencyReport -SqlInstance SQL01
# What the storage delivers right now, during the batch
Get-sqmFileLatencyReport -SqlInstance SQL01 -SampleSeconds 60
The cumulative view hides a short slow phase inside a long uptime, which is why the sample mode exists: two snapshots, the difference is what counts. Data files are rated OK up to 20 ms and critical above 50 ms; log writes are judged much more strictly, OK up to 5 ms and critical above 15 ms, because every commit waits for them. Files with only a handful of I/Os are listed but not rated.
4. The bug: deadlocks that were there but not in the report
While comparing, I reread the help of Get-sqmDeadlockReport. It said the system_health .xel files were read "when available". They were not. The function only queried the ring buffer, which holds a few megabytes in memory and often only hours of history.
In the test on SQL Server 2025 it was worse than "less history": I produced a deadlock with two sessions and read it back a few seconds later. The ring buffer returned nothing. The .xel file had it.
The second problem was in the time filter. Extended Events store UTC; the function compared that with -StartTime in local time. On a server in Germany, the newest one to two hours of deadlocks were silently cut off, exactly the ones you are asking about when the phone rings.
Since 1.9.169.0 the function reads both targets by default (-Source Both), removes duplicates, takes the .xel path from the session itself (so a moved LOG folder and Linux paths work) and converts the time window to UTC. Timestamp is now local time, the original value is in TimestampUtc.
How it was tested
Thirteen new unit tests cover the finding logic, the ratings and the version check. The SQL side ran against a SQL Server 2025 container under Windows PowerShell 5.1: a test database with a deliberate implicit conversion, hand-made duplicate, overlapping, disabled and hypothetical indexes, a heap, a forced deadlock and a five-second latency sample under load. A large production plan cache is the one thing a lab cannot simulate; the queries rank plain numbers first and only resolve text and plans for the top rows, so the cost should stay small, but measure it on your own big instance first.
The Bottom Line
PSBlitz remains a great tool, and the credit for the ideas goes to it and to the First Responder Kit. For teams that already run sqmSQLTool, the four answers are now part of the same module, with the same logging, the same HTML reports and nothing to install on the server. Next on the list: one consolidated health report that runs all of them in a single call.
Command reference: Get-sqmPlanCacheReport, Get-sqmIndexUsageReport, Get-sqmFileLatencyReport, Get-sqmDeadlockReport. Source on GitHub.