Skip to content

Commit d649030

Browse files
committed
Update OS version detection to get version directly from Windows
1 parent 870bf84 commit d649030

File tree

3 files changed

+116
-3
lines changed

3 files changed

+116
-3
lines changed

src/Grpc.Net.Client/Internal/NtDll.cs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
#if !NET5_0_OR_GREATER
20+
21+
using System.Runtime.InteropServices;
22+
23+
namespace Grpc.Net.Client.Internal;
24+
25+
internal static class NtDll
26+
{
27+
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
28+
internal static extern NTSTATUS RtlGetVersion(ref OSVERSIONINFOEX versionInfo);
29+
30+
internal static Version DetectWindowsVersion()
31+
{
32+
var osVersionInfo = new OSVERSIONINFOEX { OSVersionInfoSize = Marshal.SizeOf(typeof(OSVERSIONINFOEX)) };
33+
34+
if (RtlGetVersion(ref osVersionInfo) != NTSTATUS.STATUS_SUCCESS)
35+
{
36+
throw new InvalidOperationException($"Failed to call internal {nameof(RtlGetVersion)}.");
37+
}
38+
39+
return new Version(osVersionInfo.MajorVersion, osVersionInfo.MinorVersion, osVersionInfo.BuildNumber, 0);
40+
}
41+
42+
internal enum NTSTATUS : uint
43+
{
44+
/// <summary>
45+
/// The operation completed successfully.
46+
/// </summary>
47+
STATUS_SUCCESS = 0x00000000
48+
}
49+
50+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
51+
internal struct OSVERSIONINFOEX
52+
{
53+
// The OSVersionInfoSize field must be set to Marshal.SizeOf(typeof(OSVERSIONINFOEX))
54+
public int OSVersionInfoSize;
55+
public int MajorVersion;
56+
public int MinorVersion;
57+
public int BuildNumber;
58+
public int PlatformId;
59+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
60+
public string CSDVersion;
61+
public ushort ServicePackMajor;
62+
public ushort ServicePackMinor;
63+
public short SuiteMask;
64+
public byte ProductType;
65+
public byte Reserved;
66+
}
67+
}
68+
69+
#endif

src/Grpc.Net.Client/Internal/OperatingSystem.cs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,23 @@ internal sealed class OperatingSystem : IOperatingSystem
3939

4040
private OperatingSystem()
4141
{
42-
IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser"));
4342
#if NET5_0_OR_GREATER
4443
IsAndroid = System.OperatingSystem.IsAndroid();
44+
IsWindows = System.OperatingSystem.IsWindows();
45+
IsBrowser = System.OperatingSystem.IsBrowser();
46+
OSVersion = Environment.OSVersion.Version;
4547
#else
4648
IsAndroid = false;
47-
#endif
4849
IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
49-
OSVersion = Environment.OSVersion.Version;
50+
IsBrowser = RuntimeInformation.IsOSPlatform(OSPlatform.Create("browser"));
51+
52+
// Older versions of .NET report an OSVersion.Version based on Windows compatibility settings.
53+
// For example, if an app running on Windows 11 is configured to be "compatible" with Windows 10
54+
// then the version returned is always Windows 10.
55+
//
56+
// Get correct Windows version directly from Windows by calling RtlGetVersion.
57+
// https://www.pinvoke.net/default.aspx/ntdll/RtlGetVersion.html
58+
OSVersion = IsWindows ? NtDll.DetectWindowsVersion() : Environment.OSVersion.Version;
59+
#endif
5060
}
5161
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#region Copyright notice and license
2+
3+
// Copyright 2019 The gRPC Authors
4+
//
5+
// Licensed under the Apache License, Version 2.0 (the "License");
6+
// you may not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing, software
12+
// distributed under the License is distributed on an "AS IS" BASIS,
13+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
// See the License for the specific language governing permissions and
15+
// limitations under the License.
16+
17+
#endregion
18+
19+
using NUnit.Framework;
20+
using OperatingSystem = Grpc.Net.Client.Internal.OperatingSystem;
21+
22+
namespace Grpc.Net.Client.Tests;
23+
24+
public class OperatingSystemTests
25+
{
26+
#if NET5_0_OR_GREATER
27+
[Test]
28+
public void OSVersion_ModernDotNet_MatchesEnvironment()
29+
{
30+
// Environment.OSVersion and OperatingSystem.OSVersion should match in .NET 5 and greater.
31+
Assert.AreEqual(Environment.OSVersion.Version, OperatingSystem.Instance.OSVersion);
32+
}
33+
#endif
34+
}

0 commit comments

Comments
 (0)