DBA Scripts: Get Patch Level

🔧Part of the DBA-Tools Project, copy/paste SQL Server scripts and health checks.In: Server & ConfigurationServer Inventory

Building a Patch Compliance Inventory Across an Estate

Version and Edition answers “what is this one instance running” as a quick single-server snapshot. This script answers a related but different question: run it across every server you manage and you get a patch compliance inventory, ready to compare against what’s actually current, one row per server, with the friendly version name and the exact KB reference already resolved.

If you’re checking one server, either script tells you what you need. If you’re tracking patch compliance across an estate, this is the one built for that.


Why Patch Level Matters

  • A friendly version name (SQL Server 2019) plus the exact product_version build number is what you need to cross-reference against a patch tracking site like sqlserverupdates.com, and the SQL Server builds reference here tracks the latest CU and build per version
  • product_update_level (the CU number) is the single fastest way to see whether a server is behind on cumulative updates without decoding a raw build number
  • Run across every instance in an estate, this becomes a compliance report: which servers are current, which are stuck on an old CU, which need scheduling for the next patch window
  • kb_reference gives you the exact article to check for what that specific update actually fixed, useful when deciding whether an unpatched CVE applies

When to Run This Script

  • Building or refreshing a patch compliance inventory across a server estate
  • Before deciding whether a specific CVE or bug fix applies to an instance, cross-referencing the exact build against Microsoft’s release notes
  • Ahead of a migration or upgrade project, to know the real starting point for every server involved
  • Routine SQL Server health checks, alongside OS and Hardware Info

The Script

Run the following script against your SQL Server instance.

✓ Verified
  • Tested on: SQL Server 2025 (RTM CU8), Windows lab instance
  • Last verified: 2026-09-02 (saved output from a real run, Get-PatchLevel-20260902-151649.csv)
  • Permissions: Public (no elevated permissions required)
  • Safety: read-only, impact low
Any thresholds in this script are operational heuristics; claim types are labelled where they appear in the text.
/*
Script Name : Get-PatchLevel
Category    : monitoring
Purpose     : Reports SQL Server version, Cumulative Update level, edition, and build
              number for patch-level tracking across an estate.
              Run on each server to build a patch compliance inventory.
Author      : Peter Whyte (https://sqldba.blog/dba-scripts-get-patch-level/)
Requires    : Public (no elevated permissions required)
Notes       : product_update_level returns the CU number (e.g. CU12) on SQL 2012+.
              For SQL 2012 RTM this column may be NULL.
              Compare product_version against https://sqlserverupdates.com to determine
              whether the instance is on the latest CU for its major version.
              build_clr_version is included because CLR version changes affect assembly
              compatibility during upgrades.
*/
-- SAFE:ReadOnly
-- IMPACT:Low
SET NOCOUNT ON;

DECLARE @productVersion   varchar(20) = CAST(SERVERPROPERTY('ProductVersion')         AS varchar(20));
DECLARE @majorVer         int         = CAST(PARSENAME(@productVersion, 4)             AS int);

SELECT
    @@SERVERNAME                                                                AS server_name,
    @productVersion                                                             AS product_version,

    CASE @majorVer
        WHEN 17 THEN 'SQL Server 2025'
        WHEN 16 THEN 'SQL Server 2022'
        WHEN 15 THEN 'SQL Server 2019'
        WHEN 14 THEN 'SQL Server 2017'
        WHEN 13 THEN 'SQL Server 2016'
        WHEN 12 THEN 'SQL Server 2014'
        WHEN 11 THEN 'SQL Server 2012'
        ELSE          'SQL Server (ver ' + CAST(@majorVer AS varchar(5)) + ')'
    END                                                                         AS version_friendly,

    CAST(SERVERPROPERTY('ProductLevel')         AS varchar(20))                 AS product_level,
    CAST(SERVERPROPERTY('ProductUpdateLevel')   AS varchar(20))                 AS product_update_level,
    CAST(SERVERPROPERTY('ProductUpdateReference') AS varchar(30))               AS kb_reference,
    CAST(SERVERPROPERTY('Edition')              AS varchar(128))                AS edition,

    CASE CAST(SERVERPROPERTY('EngineEdition') AS int)
        WHEN 1  THEN 'Personal/Desktop'
        WHEN 2  THEN 'Standard'
        WHEN 3  THEN 'Enterprise' -- includes Developer and Evaluation
        WHEN 4  THEN 'Express'
        WHEN 5  THEN 'SQL Database (Azure)'
        WHEN 6  THEN 'Azure Synapse Analytics'
        WHEN 8  THEN 'Azure SQL Managed Instance'
        WHEN 9  THEN 'Azure SQL Edge'
        WHEN 11 THEN 'Azure Synapse serverless SQL pool'
        ELSE        'Unknown'
    END                                                                         AS engine_edition,

    CAST(SERVERPROPERTY('ResourceLastUpdateDateTime') AS datetime)              AS resource_db_updated,
    CAST(SERVERPROPERTY('BuildClrVersion')      AS varchar(20))                 AS clr_version,

    -- Friendly patch summary: e.g. "SQL Server 2019 CU12 (15.0.4153.1)"
    CASE @majorVer
        WHEN 17 THEN 'SQL Server 2025'
        WHEN 16 THEN 'SQL Server 2022'
        WHEN 15 THEN 'SQL Server 2019'
        WHEN 14 THEN 'SQL Server 2017'
        WHEN 13 THEN 'SQL Server 2016'
        WHEN 12 THEN 'SQL Server 2014'
        WHEN 11 THEN 'SQL Server 2012'
        ELSE          'SQL Server'
    END
    + ' '
    + ISNULL(CAST(SERVERPROPERTY('ProductUpdateLevel') AS varchar(20)),
             CAST(SERVERPROPERTY('ProductLevel') AS varchar(20)))
    + ' (' + @productVersion + ')'                                             AS patch_summary;

The patch_summary column is deliberately built as one readable string, “SQL Server 2019 CU12 (15.0.4153.1)”, the format you’d actually paste into a compliance spreadsheet or a ticket.


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

# Get patch level and version detail for one instance:
.\run.ps1 Get-PatchLevel

# To run against a remote sql server:
.\run.ps1 Get-PatchLevel -ServerInstance SQLSERVER01

This script lives in the repo at:


Example Output

One row per instance. Run it on each server and stack the rows to get a patch inventory for the estate, which is the job this script exists for.

SSMS results grid showing Get-PatchLevel returning build 17.0.4075.5, SQL Server 2025 RTM CU8, KB5104822 and the edition for one instance

Understanding the Results

server_name
patch_summary
The two you paste into a change record. patch_summary is assembled for you as version, update level and build in one string, falling back to product_level when an instance reports no CU.
product_version
version_friendly
The raw four-part build, and the release name resolved from its major number so you do not have to remember that 16 is 2022 and 17 is 2025.Act when you are checking currency. product_version is the lookup key, not the CU label: two instances can both say CU8 and be different builds on different major versions. The build and lifecycle table maps it to a release date and a support window.
product_level
product_update_level
Two different things that are easy to conflate. product_level is the servicing baseline, RTM or a Service Pack on older versions. product_update_level is the CU sitting on top of it.Act when the update level comes back NULL. On SQL Server 2012 RTM it is not populated, so fall back to product_level rather than reading the NULL as unpatched.
kb_reference
The KB article for the update actually installed, which is the fastest way to find out what a given patch level fixed without guessing from the build number.
edition
engine_edition
The free-text edition string, and the numeric family behind it. engine_edition is the one to filter an estate inventory on, because the text string varies by build and architecture.Act when you are counting licences. EngineEdition 3 is not only Enterprise, it also covers Developer and Evaluation, so a filter on that alone will overstate the Enterprise footprint. The edition string is what tells the three apart.
resource_db_updated
clr_version
When the Resource database was last written, and the CLR version the instance was built against. Both are upgrade-planning detail: CLR version changes affect assembly compatibility, which is why the script returns it at all.

Best Practices

  • Run this across every server in an estate on a schedule, not just when a patch is being considered, a compliance inventory is only useful if it’s current
  • Cross-reference product_version against a patch tracking site to determine whether each instance is on the latest CU for its major version, this script tells you what’s installed, not what’s current
  • Pair with OS and Hardware Info when building a full server inventory, patch level plus hardware context covers most of what a migration or capacity project needs upfront

Microsoft’s reference covers SERVERPROPERTY in full, including every property this script reads and the versions each one is available on.


Related Scripts

You may also find these scripts useful:


Frequently Asked Questions

How is this different from Version and Edition?

Version and Edition is a quick single-server snapshot, core version, edition, cluster status. This script is built for scale, run it across an entire estate and the friendly version name plus KB reference turn the results directly into a patch compliance report.

Does this tell me if I’m missing a security patch?

It tells you exactly what’s installed. Whether that’s current requires comparing product_version against Microsoft’s own release history, this script gives you the precise, unambiguous number to check.


Summary

Patch compliance across an estate starts with knowing, precisely and consistently, what every server is actually running, not an approximate “it’s on 2019 I think.” This script resolves the raw version number into a friendly name, a CU level, and a KB reference in one readable row, built to run across many servers, not just one.

Run it as part of routine health checks across every instance you manage, and keep the results current, a patch inventory that’s months out of date is only slightly better than none at all.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *