Determining The Currently Installed Exchange Cumulative Update

Quick and short post today - we’re going to be retrieving the current Cumulative Update (CU) level of Exchange servers. There are already a ton of articles covering this but they all have one problem, they extract the Exchange build number using PowerShell and then ask you to visit the Microsoft Documentation to match your build to the CU.

This is fine if you have one server, but if you’re for looking after many Exchange systems and need to make sure they’re all on supported CUs or simply want to maintain accurate documentation, well…

Ain't nobody got time for that

Here are two methods we’ll cover to get the current CU:

  1. Pulling it from the registry
  2. By parsing the documentation and doing the comparison programmatically instead of manually

The second option has the advantage of providing additional information such as the release data but does require Internet access.

Getting the Exchange CU via the Registry

A one liner can achieve this but let’s wrap it in a function.

function Get-ExchangeCU() {
    Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" | ? DisplayName -Match "Microsoft Exchange Server \d{4}" | Select -ExpandProperty DisplayName
}

Example

[PS] C:\> Get-ExchangeCU
Microsoft Exchange Server 2019 Cumulative Update 10

Tested on Exchange 2013, 2016, and 2019.

Getting the Exchange CU by scraping Microsoft Documentation

This option has the benefit of providing the CU release date but, as with all web scraping, it may be less robust in the long term if the documentation format changes (which it hasn’t for several years).

function Get-ExchangeCU() {
    Add-PSSnapin Microsoft.Exchange.Management.PowerShell.SnapIn -ErrorAction Stop

    # Extract and clean Exchange CU build data from official documentation
    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
    $Output = Invoke-WebRequest "https://docs.microsoft.com/en-us/exchange/new-features/build-numbers-and-release-dates"
    $ExchangeVersions = $Output.ParsedHtml.getElementsByTagName("tr") | % {($_.Children | Select -ExpandProperty innerText) -join "|"}
    $ExchangeVersions = $ExchangeVersions -match "Exchange Server"
    $ExchangeVersions = ($ExchangeVersions -replace "[^\x00-\x7F]+","").Trim()
    $ExchangeVersions = $ExchangeVersions | ConvertFrom-Csv -Header "DisplayName", "ReleaseDate","Build","LongBuild" -Delimiter "|"

    # Get current Exchange build from server
    $ExVer = (Get-ExchangeServer $env:computername).AdminDisplayVersion 
    $ExBuild = "$($ExVer.Major).$($ExVer.Minor).$($ExVer.Build).$($ExVer.Revision)"

    # Compare and retrieve current CU
    $ExchangeVersions | where {$_.Build -eq $ExBuild}
}

Example

PS C:\> Get-ExchangeCU

DisplayName                  ReleaseDate      Build         LongBuild
-----------                  -----------      -----         ---------
Exchange Server 2019 CU10    June 29, 2021    15.2.922.7    15.02.0922.007

This has also been tested on Exchange 2013, 2016, and 2019.

If you’re running this using a management agent you’ll get the following error when attempting to parse the HTML.

The response content cannot be parsed because the Internet Explorer engine is not available, or Internet Explorer's first-launch configuration is not complete. Specify the UseBasicParsing parameter and try again.

The UseBasicParsing parameter won’t work in this instance as we need access to the ParsedHtml method but a simple way to resolve this is by setting the following registry key, which can be added to the function.

Set-ItemProperty -Path "HKLM:\SOFTWARE\Microsoft\Internet Explorer\Main" -Name "DisableFirstRunCustomize" -Value 2

While these methods both alleviate the manual work needed to extract the current CU I’m not particularly fond of either of them as they both have a bunch of dependencies. The first relies on there being only a single match to our regex in the registry. On the systems I’ve tested it on it has worked, but this is not guaranteed to be the case in the future. The second method relies on the formatting of the documentation and Internet access from the server, and I have an aversion to doing any kind of “browsing” from servers.

I do wish Microsoft would add a property to Get-ExchangeServer that would give us this info.

That’s all for this one, I wanted to keep it nice and short but a good next step would be to use the second function where we scrape the docs to determine whether the installed CU is still supported.


If you enjoyed this post consider sharing it on , , , or , and .