12

Microsoft released Windows Server 2019 on October 2, 2018. From Windows 2000 and up until this Windows version, you could call a WinAPI function GetVersionEx with a struct OSVERSIONINFOEX and depending on the variables of dwMajorVersion, dwMinorVersion and wProductType determine Windows version, for example, Windows 8.1, Windows 10, Windows Server 2012 R2. The code everyone used was something like this:

OSVERSIONINFOEX osvi;
SecureZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
if (GetVersionEx(&osvi)) {
    if (osvi.dwMajorVersion == 10 &&
        osvi.dwMinorVersion == 0 &&
        osvi.wProductType != VER_NT_WORKSTATION) {
            Console->Log("We are running on Windows Server 2016");
        }
}

Judging from Wikipedia the Windows Server 2019 has the same version number of NT 10.0 as Server 2016. So the above code does not work anymore.

Also, Microsoft Docs contains the following note: GetVersionEx may be altered or unavailable for releases after Windows 8.1. Instead, use the Version Helper functions.

Unfortunately, the Version Helper functions does not have a function to detect Server 2019. Also, the strange thing is that Docs page about Targeting stops at the Windows 10, and does not talk about Server editions, while these Targeting manifests is mandatory for detecting OS above Windows 8.1 or Server 2012.

Update 1. As @IInspectable and @RbMm commented about usage of RtlGetVersion function. So I ran the following code (taken from this answer):

typedef LONG NTSTATUS, *PNTSTATUS;
#define STATUS_SUCCESS (0x00000000)

typedef NTSTATUS (WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);

RTL_OSVERSIONINFOW GetRealOSVersion() {
    HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll");
    if (hMod) {
        RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion");
        if (fxPtr != nullptr) {
            RTL_OSVERSIONINFOW rovi = { 0 };
            rovi.dwOSVersionInfoSize = sizeof(rovi);
            if ( STATUS_SUCCESS == fxPtr(&rovi) ) {
                return rovi;
            }
        }
    }
    RTL_OSVERSIONINFOW rovi = { 0 };
    return rovi;
}

And here are the results for Windows 10:

  • dwMajorVersion = 10
  • dwMinorVersion = 0
  • dwBuildNumber = 17134
  • dwPlatformId = 2

Windows Server 2019:

  • dwMajorVersion = 10
  • dwMinorVersion = 0
  • dwBuildNumber = 17763
  • dwPlatformId = 2

Update2. As requested, posting full info from OSVERSIONINFOEX struct that was obtained via GetVersionEx call with a manifest file containing all the Targets till Windows 10 (see the Targeting link above):

// Windows 10
osvi.dwOSVersionInfoSize = 284
osvi.dwMajorVersion = 10
osvi.dwMinorVersion = 0
osvi.dwBuildNumber = 17134
osvi.dwPlatformId = 2
osvi.szCSDVersion =
osvi.wServicePackMinor = 0
osvi.wServicePackMinor = 0
osvi.wSuiteMask = 256  // 0x100
osvi.wProductType = 1
osvi.wReserved = 0

// Windows Server 2016
osvi.dwOSVersionInfoSize = 284
osvi.dwMajorVersion = 10
osvi.dwMinorVersion = 0
osvi.dwBuildNumber = 14393
osvi.dwPlatformId = 2
osvi.szCSDVersion =
osvi.wServicePackMinor = 0
osvi.wServicePackMinor = 0
osvi.wSuiteMask = 400
osvi.wProductType = 3
osvi.wReserved = 0

// Windows Server 2019
osvi.dwOSVersionInfoSize = 284
osvi.dwMajorVersion = 10
osvi.dwMinorVersion = 0
osvi.dwBuildNumber = 17763
osvi.dwPlatformId = 2
osvi.szCSDVersion =
osvi.wServicePackMinor = 0
osvi.wServicePackMinor = 0
osvi.wSuiteMask = 400  // 0x190
osvi.wProductType = 3
osvi.wReserved = 0

Update 3. Calling RtlGetVersion with a struct RTL_OSVERSIONINFOEXW we get exactly the same result as in Update 2.

Maris B.
  • 2,333
  • 3
  • 21
  • 35
  • [Windows Server 2019 version info](https://techcommunity.microsoft.com/t5/Windows-Server-Insiders/Windows-Server-2019-version-info/td-p/234472) – user7860670 Nov 20 '18 at 12:54
  • 1
    What values do you get back when [detecting Windows 10 version](https://stackoverflow.com/q/36543301/1889329), passing a [OSVERSIONINFOEX](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw) structure? – IInspectable Nov 20 '18 at 12:57
  • you need use ntdll api RtlGetVersion or RtlGetNtVersionNumbers for get actual os version without manifest – RbMm Nov 20 '18 at 13:15
  • @IInspectable and @RbMm, updated the question to include `RtlGetVersion` function – Maris B. Nov 20 '18 at 13:59
  • 1
    It should be noted that in general you don't want to detect a version, but check to see if the actual functionality you need is available - e.g. if you need to use an API that is available only from some OS onwards use `GetProcAddress` to see if it's provided instead of testing for that Windows version. – Matteo Italia Nov 20 '18 at 14:00
  • 1
    @MatteoItalia, where did I write that I need to detect "actual functionality"? There are numerous reasons why one would want to detect OS version, including pre-filled form bug / crash reports. And no, this is not the reason I asked the question. – Maris B. Nov 20 '18 at 14:13
  • @MarisB.: that's why I wrote "in general" and why it's just a comment. Remember that on SO there's a big focus on future visitors of questions; you surely need to check for an OS version, but probably a good chunk of the next 1000 visitors would be better off checking for a specific functionality. Incidentally, if you wrote explicitly in your question what are you trying to achieve by detecting a specific version you may get better answers. – Matteo Italia Nov 20 '18 at 14:19
  • What does [`GetProductInfo`](https://learn.microsoft.com/en-us/windows/desktop/api/sysinfoapi/nf-sysinfoapi-getproductinfo) return? – zett42 Nov 20 '18 at 14:21
  • @zett42, it returns PRODUCT_DATACENTER_SERVER 0x00000008. Probably because I have installed a Datacenter edition. – Maris B. Nov 20 '18 at 14:35
  • 3
    You only posted results from `OSVERSIONINFO`. Pass a properly initialized `OSVERSIONINFOEX` instead. That structure has additional members, that may hold the information you are looking for. Matteo raises a valid point too: If you plan to make runtime decisions of your code based on the OS version it's running on, you would be better off, testing for features instead. If, on the other hand, you need this information for e.g. diagnostic purposes, there's nothing wrong with this. – IInspectable Nov 20 '18 at 17:06
  • [`RtlGetVersion`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion) work with *Pointer to either a `RTL_OSVERSIONINFOW` structure or a `RTL_OSVERSIONINFOEXW` structure* - so you not need manifest, and separate `GetVersionEx` call. just call `RtlGetVersion` and pass pointer to [`RTL_OSVERSIONINFOEXW`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw). and you not need `GetProcAddress` for call this api. just call it - link with *ntdll.lib* – RbMm Nov 21 '18 at 10:09
  • 1
    that this is server you detect by `wProductType == VER_NT_SERVER` but which concrete - can be exactly only based on `dwBuildNumber` – RbMm Nov 21 '18 at 10:15
  • @RbMm, RTL_OSVERSIONINFOEXW contains exactly the same information as OSVERSIONINFOEX. I have checked twice. – Maris B. Nov 21 '18 at 10:38
  • @RbMm "which concrete - can be exactly only based on dwBuildNumber" - is that a documented behavior? Is there any guarantee that it will not be changed in the future? – Maris B. Nov 21 '18 at 10:39
  • This is not currently a documented contract. The best you can do for now is to open an issue, and request that the information you need be documented. You can do so at the bottom of the [documentation](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw) page. – IInspectable Nov 21 '18 at 10:52
  • Keep in mind that no matter how much you *think* you want to know the version of Windows you're running on, at some point in the future you're going to be inside a virtual compatibility environment and it won't even *know its own version*. So get used to not knowing. – Zan Lynx Apr 03 '19 at 23:37

5 Answers5

2

According to discussions in Windows Server 2019 version info:

[Windows] Server 2019 Datacenter Edition build 17744, the ReleaseId field shows 1809.

So something like this should do the trick:

const auto isWinServer2019Plus =
  IsWindowsServer() &&
  IsWindowsVersionOrGreater(10, 0, 1803);
NuSkooler
  • 5,391
  • 1
  • 34
  • 58
1

Apart from checking MajorVersion, MinorVersion and ProductType you also have to check the ReleaseId or BuildNumber.

ReleaseId: 1809 and BuildNumber: 17763 are associated with the release versions of Windows Server 2019 and Windows Server, version 1809. So by checking for those numbers you should at least be sure that you're either dealing with Windows Server 2019 or Windows Server, version 1809 (Semi-Annual Channel) (Datacenter Core, Standard Core).

enter image description here

See: Windows Server release information

Note: Insider Preview builds of Windows Server 2019 can have ReleaseId 1803 or BuildNumbers below 17763.


In this thread Mary Hoffman from Microsoft says:

ReleaseId

1809 is associated with Windows Server 2019 and Windows Server, version 1809 only. (post)

Windows Server 2016 will always be 1607. Once a product is released, that ID will not change. (post)

Following that logic Windows Server 2019 will also always be 1809.

You can read the ReleaseId from this registry key:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion - ReleaseId

See: How to read a value from the Windows registry

BuildNumber

Windows Server 2019 stops at 17763. That was the final major build number.

Anything higher than that (>=17764) will be vNext builds. (post)

Windows Server 2016 will always be 10.0.14393.### where ### increments as cumulative updates are installed.

Windows Server 2019 will always be 10.0.17763.### where ### increments as cumulative updates are installed. (post)

So BuildNumber 17763 should always correspond to Window Server 2019 or Windows Server, version 1809 (or Windows 10 1809, but checking ProductType tells you if its a server).

frido
  • 13,065
  • 5
  • 42
  • 56
0

It's the same story for each new Windows version. And again with Windows 2019

You should use VerifyVersionInfoW ... BUT also update your program manifest.

The questions is : What is the manifest for the new Windows version ...

Windows 2016 was linked with Windows 10 with id :

see here : https://learn.microsoft.com/en-us/windows/desktop/sbscs/application-manifests

Herve K.
  • 9
  • 1
  • I do not see, how it is possible to tell if we are running on Windows Server 2019 by using VerifyVersionInfoW. Could you elaborate on that? – Maris B. Dec 08 '18 at 10:56
  • Hi Maris. see these links : https://learn.microsoft.com/en-us/windows/desktop/api/versionhelpers/nf-versionhelpers-iswindowsversionorgreater / https://stackoverflow.com/questions/32115255/c-how-to-detect-windows-10 . It's solutions used to workaround GetVersionEx which is not pertinent. – Herve K. Dec 11 '18 at 09:19
  • I still do not see, how can I distinguish Windows Server 2016 and Windows Server 2019, for example... – Maris B. Dec 11 '18 at 14:23
0

The best way I found is to use the GetVersionEx method you mentioned, if that returns 6.2 (which it will for Windows 8.1 and above), fall back to the wmic api.
Code below taken from Microsoft to get the OS name using wmic api.
Reference: https://learn.microsoft.com/en-us/windows/win32/wmisdk/example--getting-wmi-data-from-the-local-computer

#define _WIN32_DCOM
#include <iostream>
using namespace std;
#include <comdef.h>
#include <Wbemidl.h>

#pragma comment(lib, "wbemuuid.lib")

int main(int argc, char **argv)
{
HRESULT hres;

// Step 1: --------------------------------------------------
// Initialize COM. ------------------------------------------

hres =  CoInitializeEx(0, COINIT_MULTITHREADED); 
if (FAILED(hres))
{
    cout << "Failed to initialize COM library. Error code = 0x" 
        << hex << hres << endl;
    return 1;                  // Program has failed.
}

// Step 2: --------------------------------------------------
// Set general COM security levels --------------------------

hres =  CoInitializeSecurity(
    NULL, 
    -1,                          // COM authentication
    NULL,                        // Authentication services
    NULL,                        // Reserved
    RPC_C_AUTHN_LEVEL_DEFAULT,   // Default authentication 
    RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
    NULL,                        // Authentication info
    EOAC_NONE,                   // Additional capabilities 
    NULL                         // Reserved
    );


if (FAILED(hres))
{
    cout << "Failed to initialize security. Error code = 0x" 
        << hex << hres << endl;
    CoUninitialize();
    return 1;                    // Program has failed.
}

// Step 3: ---------------------------------------------------
// Obtain the initial locator to WMI -------------------------

IWbemLocator *pLoc = NULL;

hres = CoCreateInstance(
    CLSID_WbemLocator,             
    0, 
    CLSCTX_INPROC_SERVER, 
    IID_IWbemLocator, (LPVOID *) &pLoc);

if (FAILED(hres))
{
    cout << "Failed to create IWbemLocator object."
        << " Err code = 0x"
        << hex << hres << endl;
    CoUninitialize();
    return 1;                 // Program has failed.
}

// Step 4: -----------------------------------------------------
// Connect to WMI through the IWbemLocator::ConnectServer method

IWbemServices *pSvc = NULL;

// Connect to the root\cimv2 namespace with
// the current user and obtain pointer pSvc
// to make IWbemServices calls.
hres = pLoc->ConnectServer(
     _bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
     NULL,                    // User name. NULL = current user
     NULL,                    // User password. NULL = current
     0,                       // Locale. NULL indicates current
     NULL,                    // Security flags.
     0,                       // Authority (for example, Kerberos)
     0,                       // Context object 
     &pSvc                    // pointer to IWbemServices proxy
     );

if (FAILED(hres))
{
    cout << "Could not connect. Error code = 0x" 
         << hex << hres << endl;
    pLoc->Release();     
    CoUninitialize();
    return 1;                // Program has failed.
}

cout << "Connected to ROOT\\CIMV2 WMI namespace" << endl;


// Step 5: --------------------------------------------------
// Set security levels on the proxy -------------------------

hres = CoSetProxyBlanket(
   pSvc,                        // Indicates the proxy to set
   RPC_C_AUTHN_WINNT,           // RPC_C_AUTHN_xxx
   RPC_C_AUTHZ_NONE,            // RPC_C_AUTHZ_xxx
   NULL,                        // Server principal name 
   RPC_C_AUTHN_LEVEL_CALL,      // RPC_C_AUTHN_LEVEL_xxx 
   RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
   NULL,                        // client identity
   EOAC_NONE                    // proxy capabilities 
);

if (FAILED(hres))
{
    cout << "Could not set proxy blanket. Error code = 0x" 
        << hex << hres << endl;
    pSvc->Release();
    pLoc->Release();     
    CoUninitialize();
    return 1;               // Program has failed.
}

// Step 6: --------------------------------------------------
// Use the IWbemServices pointer to make requests of WMI ----

// For example, get the name of the operating system
IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
    bstr_t("WQL"), 
    bstr_t("SELECT * FROM Win32_OperatingSystem"),
    WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, 
    NULL,
    &pEnumerator);

if (FAILED(hres))
{
    cout << "Query for operating system name failed."
        << " Error code = 0x" 
        << hex << hres << endl;
    pSvc->Release();
    pLoc->Release();
    CoUninitialize();
    return 1;               // Program has failed.
}

// Step 7: -------------------------------------------------
// Get the data from the query in step 6 -------------------

IWbemClassObject *pclsObj = NULL;
ULONG uReturn = 0;

while (pEnumerator)
{
    HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, 
        &pclsObj, &uReturn);

    if(0 == uReturn)
    {
        break;
    }

    VARIANT vtProp;

    // Get the value of the Name property
    hr = pclsObj->Get(L"Name", 0, &vtProp, 0, 0);
    wcout << " OS Name : " << vtProp.bstrVal << endl;
    VariantClear(&vtProp);

    pclsObj->Release();
}

// Cleanup
// ========

pSvc->Release();
pLoc->Release();
pEnumerator->Release();
CoUninitialize();

return 0;   // Program successfully completed.

}
Mayank R
  • 21
  • 2
0

The only difference is dwBuildNumber. So this code worked for me:

else if (osvi.dwMajorVersion == 10 && osvi.dwMinorVersion == 0) 
{
    if (osvi.wProductType == VER_NT_WORKSTATION)
        m_csOsType += _T("10 ");
    else
    {
        if (osvi.dwBuildNumber >= 17763)
            m_csOsType += _T("Server 2019 ");
        else
            m_csOsType += _T("Server 2016 ");
    }
        
}
Marcello B.
  • 4,177
  • 11
  • 45
  • 65