Finding your Operating System version programmatically

Usually if you ask "how do I find out what software I'm running on?", the answer is "don't."

If you're a web application, you shouldn't care if you're in Chrome, Safari, Firefox, on Mac, Linux, Windows, a 32-bit machine... you really really don't need to know.

The same usually goes for native applications. If you need an API that was only introduced in a particular version, you can usually check for if that API exists. This is commonly referred to as "Feature Availability" - instead of your code's flow going along the lines of "if I am Windows 10 then do X", it should say "if X is available then do X."

However, sometimes you do really need to know. Two cases in particular that I've recently encountered:

For automatic crash reports which will be ready by a human (or automatically sorted by a machine), you need to be able to see exactly what environment your program was running in when it crashed.

Some APIs accept different parameters on different framework/OS versions. For example, CreateSymbolicLink in Windows only accepts the SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE[1] flag on Windows 10 v1703, or some slightly earlier Insiders builds. (This flag is so new that it doesn't even appear on MSDN yet.) If you try use it on older Windows versions, you just get an error.

So if you really absolutely pinky-swear need to know your OS version, how can you go about finding it?

Linux

In order to find Linux distribution information, you should shell out to lsb_release -a, or ingest /etc/lsb-release which is a plain text file. This is defined by the Linux Standard Base.

Note that under the Windows Subsystem for Linux, this appears to be a pure Ubuntu installation. See uname below for differentiating Microsoft Ubuntu from Ubuntu.

macOS

On macOS you can either use Gestalt(), an old Carbon API which has since been deprecated, or if you can invoke Objective-C or Swift code you can use [[NSProcessInfo processInfo] operatingSystemVersion].

This allows you to obtain the major, minor and patch versions of macOS (e.g. 10.12.4).

I'm not sure how to get the build number (e.g. 16E195).

Linux and macOS (POSIX)

On any POSIX-compliant operating system, you can call uname(2). This shows quite a bit of differentiation between OSs. Using the following sample code:

Windows

Windows used to have GetVersion and GetVersionEx, but the behaviour of this was altered in Windows 8.1. It still has those functions, but they no longer do what you want them to do.

If you're running Windows 8 or below, these functions will returns the current version, e.g. 6.0 for Vista, 6.1 for 7, and so on.

If you're running Windows 8.1 or above, then this function will return 6.2 unless your Application Manifest explicitly declares that your application is compatible with the current Windows version - 8.1 or 10.0.

If you're running a Windows driver - or if you like to live dangerously - you can instead use the kernel-mode RtlGetVersion function, which will return the exact version that Windows is running. This is inside the Windows kernel, so it's only accessible from 32-bit applications on 32-bit Windows, or 64-bit applications on 64-bit Windows.

While writing this blog post (but not when I originally did this research for work 🙁) I also came across another version of RtlGetVersion. This one is in ntdll.dll so, unlike the kernel call above, this one works in 32-bit applications on 64-bit Windows.

The official way, however, to do version checking, is to use VerifyVersionInfo. If this look daunting, there are a whole bunch of helper functions such as IsWindows10OrGreater(), IsWindowsServer() and so on. This only lets you find out if you're running Windows greater than X, or less than X, or equal to X. It doesn't tell you outright what you're running on.

Note: these helper functions are defined in the Windows SDK as inline functions, they are not standalone functions in Windows DLLs. This means that you can't invoke them as native calls from .NET or any other dynamic runtime, you have to re-implement them as the same thin wrappers over VerifyVersionInfo.

The solution I ended up using was to turn to the Windows Registry. There are 3 keys you can use that only exist in Windows 10, which are:

Do not use HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentVersion as a primary source, because although it says 6.3 on Windows 8.1, it still says 6.3 on Windows 10. Only use it if you know you're on a version of Windows older than Windows 10.