OK, so you are using System.Environment.OSVersion.Version…

The .NET Framework provides a class to find out the version of Windows. Take a look at the following example:

using System;

namespace OsVersionCheck
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"" +
                $"{System.Environment.OSVersion.Version.Major}." +
                $"{System.Environment.OSVersion.Version.Minor}." +
                $"{System.Environment.OSVersion.Version.Build}" +
                $"");

            Console.ReadKey();
        }
    }
}

The output of the above code looks something like this:

6.2.9200

This number can be used to identify the operating system. The following table summarizes the most recent operating system version numbers:

Operating systemVersion number (Major.Minor)
Windows 1110.0*
Windows 1010.0*
Windows Server 202210.0*
Windows Server 201910.0*
Windows Server 201610.0*
Windows 8.16.3*
Windows Server 2012 R26.3*
Windows 86.2
Windows Server 20126.2
Windows 76.1
Windows Server 2008 R26.1
Windows Server 20086.0
Windows Vista6.0
Windows Server 2003 R25.2
Windows Server 20035.2
Windows XP 64-Bit Edition5.2
Windows XP5.1
Windows 20005.0

However, this does not work as desired if the function is executed on the operating systems marked with an asterisk in the table above. Specifically, these are: Windows 11, Windows 10, Windows Server 2022, Windows Server 2019, Windows Server 2016, Windows 8.1, Windows Server 2012 R2

This means for .NET Framework and .NET Core until version 4.8 respectively 3.1: for Windows 10 it will return 6.2, which is wrong, as this refers to Windows 8 / Windows Server 2012. Note: .NET 5.0+ is not affected and returns the correct version information.

How does this behavior come about? (Note: you can skip the following paragraph if you are not interested in technical background details)

Why OSVersion.Version will return wrong results on newer OSes

In Windows 8.1 and Windows 10, the GetVersion and GetVersionEx functions have been deprecated. In Windows 10, the VerifyVersionInfo function has also been deprecated. While you can still call the deprecated functions, if your application does not specifically target Windows 8.1 or Windows 10, you will get Windows 8 version (6.2.0.0).

https://docs.microsoft.com/de-de/windows/win32/sysinfo/targeting-your-application-at-windows-8-1

In order to target Windows 8.1 or Windows 10, you need to include the app manifest in the source file, which is our first possible solution.

Targeting your application for Windows in the app manifest

In Visual Studio you can add an app manifest by doing the following:

Go to your project, right click and choose Add / New Item and choose Application Manifest File. A new file will be added to your project having default name app.manifest.

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->

            <!-- Windows 10 and Windows 11 -->
            <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
            <!-- Windows 8.1 -->
            <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
            <!-- Windows 8 -->
            <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
            <!-- Windows 7 -->
            <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
            <!-- Windows Vista -->
            <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>

        </application>

    </application>
  </compatibility>

If you run the program again, the output on a Windows 10 will be correct now:

10.0.17763

Please note:

  • Microsoft says: Manifesting the .exe for Windows 8.1 or Windows 10 will not have any impact when run on previous operating systems.
  • The GUIDs in manifest file are commented with Windows desktop versions, but they are also valid for the Windows Server edition. E.g., the GUID for Windows 8.1 also represents Windows Server 2012 R2, as they have the same OS version number 6.3
  • The approach with the manifest file has a major drawback: Every time Microsoft launches a new Windows version, you need to update your application with a new manifest file. Without such an update, your app will not be able to detect the new Windows and will report the number wrong (again).

Calling RtlGetVersion in ntdll.dll

The Windows Kernel offers an interesting function. The RtlGetVersion routine returns version information about the currently running operating system. It is available starting with Windows 2000 and also works on Windows 10/Server 2019/Server 2016 right away.

using System;
using System.Runtime.InteropServices;
using System.Security;

namespace ConsoleApp1
{
    class Program
    {
        /// <summary>
        /// taken from https://stackoverflow.com/a/49641055
        /// </summary>
        /// <param name="versionInfo"></param>
        /// <returns></returns>
        [SecurityCritical]
        [DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern int RtlGetVersion(ref OSVERSIONINFOEX versionInfo);
        [StructLayout(LayoutKind.Sequential)]
        internal struct OSVERSIONINFOEX
        {
            // The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX))
            internal int OSVersionInfoSize;
            internal int MajorVersion;
            internal int MinorVersion;
            internal int BuildNumber;
            internal int PlatformId;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            internal string CSDVersion;
            internal ushort ServicePackMajor;
            internal ushort ServicePackMinor;
            internal short SuiteMask;
            internal byte ProductType;
            internal byte Reserved;
        }

        static void Main(string[] args)
        {
            var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) };
            if (RtlGetVersion(ref osVersionInfo) != 0)
            {
                // TODO: Error handling
            }
            Console.WriteLine($"Windows Version {osVersionInfo.MajorVersion}.{osVersionInfo.MinorVersion}.{osVersionInfo.BuildNumber}");

            Console.ReadKey();
        }
    }
}

How to get the Windows release ID (like “21H2”) and the update build release (UBR)

Feature updates for Windows 10 are released twice a year, around March and September, via the Semi-Annual Channel. They will be serviced with monthly quality updates for 18 or 30 months from the date of the release, depending on the lifecycle policy.

As a result, Windows 10 has the feature update versions, which are 1909, 1903, 1809, etc. This is referred as Release ID. Within this feature release, Windows gets monthly quality updates.

The current state of this quality updates can be determined using the UBR, which is the last part of the build number (the 778 in “Version 10.0.18363.778”). According to current knowledge, both information can only be read from the registry and there is no Windows API command for this. This is also confirmed by the fact that WINVER also uses the registry.

until Windows 10 version 2009/20H2 (build 19042)later Windows versions
Windows releaseHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ReleaseIDHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DisplayVersion
update build release (UBR)HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBRHKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\UBR
Windows registry keys for Windows Release ID / Display Version and UBR

Further reading

Delve inside Windows architecture and internals*, and see how core components work behind the scenes. Led by a team of internals experts, this classic guide has been fully updated for Windows 10 and Windows Server 2016.

* sponsored link

Get source code on GitHub

You can view the source code here: https://github.com/pruggitorg/detect-windows-version

7 Comments

  1. added the information, that .NET Framework and .NET Core until version 4.8 respectively 3.1 are affected, but not .NET 5.0+

    1. Currently there is no official way provided by Microsoft, which allows to determine Windows 11. According to the OSVERSIONINFOEXW documentation, Windows 11 and Windows 10 are having the same version number 10.0. It might be because Windows 11 is just a marketing name and uses the basic system architecture of Windows 10.
      As far as I know, the distinction can currently only be made based on the build number, where Windows 11 has a number with 22000 or higher.

      The information for the Windows release ID is outdated here. Yet, the source code in the GitHub project uses the most recent method.

    2. Updated information on how to get the Windows Release ID / Display Version (e.g “21H2”) across all Windows 10 versions

  2. updated information for OS version table (Windows 11 and Windows Server 2022) and app manifest (Windows 11)

  3. Hello Stefan Prugg, I developed this kind of a tool as well and I am in the process of modernizing it. I also ran into how to distinct between Win10 and Win11. In my search I’ve found that if the buildnumber is > 22000 it is windows 11. I did not save the url of that document but I did find this one: https://stackoverflow.com/questions/71250924/how-to-get-osversioninfo-for-windows-11. Now for the distimction between server 2016, 2019 and server 2022…

    Eric-Jan

Leave a Reply

Your email address will not be published.

*