Friday, February 18, 2011

Essential OperatingSystem Extensions

There are times that you may need to determine which version of Windows your program is running upon.  In the .NET Framework there is the System.OperatingSystem class that provides many of these details.  You can acquire an instance of this class via the System.Environment.OSVersion property.  The OperatingSystem class will tell you essential details like the major and minor version numbers, the revision and build numbers, and even the service pack version (if any) installed on the computer.  There is also a PlatformID property that will tell you the platform type.  This is an enumeration of the various supported platforms.  If you look this up in the MSDN Documentation, you may be surprised to find values such as: Win32S, Win32Windows (were talking Windows 95, 98, ME), WinCE, Xbox, MacOSX and even Unix!  Most commonly this value will be Win32NT as this is used for Windows 2000, XP, server 2003, server 2008, and 7.  These are the NT line of the Windows operating system.  Generally this is the information that your program needs for most purposes like a diagnostic report.
Missing from the OperatingSystem class is any method or property that would tell me if I am running on a server operating system.  I recently had a need to know this information, thus I had to resort to using platform invoke to call into some native Windows functions.  I used the extension method mechanism to integrate my methods with the OperatingSystem class.  Here is my IsServer extension method.
 1 public static class OSExtensions
 2 {
 3     public static bool IsServer(this OperatingSystem Os)
 4     {
 5         var osinfo = new OSVERSIONINFOEX();
 6         osinfo.dwOSVersionInfoSize = (UInt32)Marshal.SizeOf(osinfo);
 7 
 8         if (GetVersionEx(ref osinfo))
 9         {
10             return (VER_NT_WORKSTATION != osinfo.wProductType);
11         }
12 
13         throw new Win32Exception();
14    }
15 }

You can see that this is a simple method that calls the Win32 API GetVersionEx to get even more useful version information.  This function provides a wealth of information about the underlying Windows installation.  In this function, I look at the wProductType field of the OSVERSIONINFORMATIONEX structure to see if its value is not VER_NT_WORKSTATION.  The wProductType field can be one of the following values: VER_NT_WORKSTATION, VER_NT_SERVER, or VER_NT_DOMAIN_CONTROLLER.  Thus if the value is not VER_NT_WORKSTATION then I must be on a server (a domain controller is a server).  Below is some of the interop code that makes this work.

 1 [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
 2 private struct OSVERSIONINFOEX
 3 {
 4    public UInt32 dwOSVersionInfoSize;
 5    public UInt32 dwMajorVersion;
 6    public UInt32 dwMinorVersion;
 7    public UInt32 dwBuildNumber;
 8    public UInt32 dwPlatformId;
 9    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
10    public string szCSDVersion;
11    public Int16 wServicePackMajor;
12    public Int16 wServicePackMinor;
13    public Int16 wSuiteMask;
14    public SByte wProductType;
15    public SByte wReserved;
16 }
17 
18 private const SByte VER_NT_WORKSTATION = 0x01;
19 private const SByte VER_NT_DOMAIN_CONTROLLER = 0x02;
20 private const SByte VER_NT_SERVER = 0x03;
21 
22 [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
23 private static extern bool GetVersionEx(
24    ref OSVERSIONINFOEX lpVersionInfo
25    );

I placed these items in the OSExtensions class as well.  The MSDN documentation for OSVERSIONINFOEX will show you all of the interesting information you can retrieve if needed.

Another piece of information you may want is the friendly name of the operating system.  The OperatingSystem class includes a VersionString property that will give you the information, but not in the format that you may desire.  Yes, telling your users that the OS is “Microsoft Windows NT 6.1.7600.0” is precise, but the end user of your software will tell you, “No, I’m running Windows 7.”  Below is the Name extension method.  It is not exhaustive, but it covers all of the cases in which my software was interested.  If the friendly name is not determined, it returns the same value as the VersionString property.

 1 public static string Name(this OperatingSystem Os)
 2 {
 3     string name = String.Empty;
 4     if (PlatformID.Win32Windows == Os.Platform)
 5     {
 6         name = GetLegacyName(Os);
 7     }
 8     else if (PlatformID.Win32NT == Os.Platform)
 9     {
10         if (Os.IsServer())
11             name = GetServerName(Os);
12         else
13             name = GetWorkstationName(Os);
14     }
15 
16     if (String.IsNullOrEmpty(name))
17         return Os.VersionString;
18     else
19         return String.Format("{0}, {1}", name, Os.Version.ToString());
20 }
21 
22 private static string GetLegacyName(OperatingSystem Os)
23 {
24     string name = String.Empty;
25     switch (Os.Version.Minor)
26     {
27         case 0:
28             name = "Windows 95";
29             break;
30         case 10:
31             name = "Windows 98";
32             break;
33         case 90:
34             name = "Windows ME";
35             break;
36     }
37 
38     return name;
39 }
40 
41 private static string GetServerName(OperatingSystem Os)
42 {
43     string name = String.Empty;
44     switch (Os.Version.Major)
45     {
46         case 3:
47         case 4:
48             name = "Windows NT Server";
49             break;
50         case 5:
51             if (Os.Version.Minor == 0)
52                 name = "Windows 2000 Server";
53             else if (Os.Version.Minor == 2)
54             {
55                 if (0 == GetSystemMetrics(SM_SERVERR2))
56                     name = "Windows Server 2003";
57                 else
58                     name = "Windows Server 2003 R2";
59             }
60             break;
61         case 6:
62             if (Os.Version.Minor == 0)
63                 name = "Windows Server 2008";
64             else if (Os.Version.Minor == 1)
65                 name = "Windows Server 2008 R2";
66             break;
67     }
68 
69     return name;
70 }
71 
72 private static string GetWorkstationName(OperatingSystem Os)
73 {
74     string name = String.Empty;
75     switch (Os.Version.Major)
76     {
77         case 3:
78         case 4:
79             name = "Windows NT";
80             break;
81         case 5:
82             if (Os.Version.Minor == 0)
83                 name = "Windows 2000";
84             else if (Os.Version.Minor == 1)
85                 name = "Windows XP";
86             else if (Os.Version.Minor == 2)
87                 name = "Windows XP x64";
88             break;
89         case 6:
90             if (Os.Version.Minor == 0)
91                 name = "Windows Vista";
92             else if (Os.Version.Minor == 1)
93                 name = "Windows 7";
94             break;
95     }
96 
97     return name;
98 }

There was a little more interop code required to make this work.  This code allows you to retrieve the value of the SM_SERVERR2 flag, which if non-zero means you are running the R2 variant of Windows Server 2003.  Here is the code.

1  private const int SM_SERVERR2 = 89;
2 
3 [DllImport("User32.dll", SetLastError = false)]
4 private static extern int GetSystemMetrics(int nIndex);

Through the use of extension methods and a little p/invoke we are able to determine if a .NET program is running on a server OS and provide a more friendly name that could be displayed to the user.  Until next time.

No comments:

Post a Comment