I.M. Testy

Treatises on the practice of software testing

Programmatically Detecting The Operating System Version (Part II)

without comments

Time is a commodity in short supply! It has been more than 2 weeks since my last post. I have not been sitting idle, but really haven’t had a lot of free time to write. In preparation for my up-coming trip to Zurich, Switzerland to give a workshop and keynote at Swiss Testing Day I was interviewed by Marco van der Spek for TESTNIEUWS.NL. The interview provides a bit more background about me and some of my perspectives of Microsoft and software testing.

Also sucking up some of my time over the past couple of weeks were ‘reorgs’ at work. Change at Microsoft is a constant. Most people get used to it after awhile; others still freak out even with the slightest change. For me it mostly means shifting some priorities based on the new General Manager’s strategic vision, acclimating to a new manager and letting him know what I am working on, and generally making sure the day to day business on our team keeps moving forward during the transition.

Just like life at Microsoft and technology in general the Windows operating system continuously goes through changes. New versions, new service packs, Ultimate, Home, Server versions, etc. Sometimes it is hard to keep up with all the changes. And from a test automation perspective it is sometimes important to know which operating system version the test is running on. In some cases control flow in the automated test case may need to branch in order for the test to execute on different operating system versions. Branching in an automated test based on the operating system version eliminates the need to write separate test cases for variances in operating system versions.

And, certainly if our test matrix includes multiple product versions (Home, Ultimate, Professional, etc) and our automated test exposes a bug then we certainly want to collect information about the operating system version the test was running on. This is especially important if the automated test fails on one machine, but passes on another. Sometimes the cause of the failure may be a slight difference in the machine configuration or the operating system version.

Almost 2 years ago I described a way to get the operating system version information using the System.PlatformID enumeration and the System.Environment.OSVersion property and OperatingSystem class members in this blog post. But, I also mention some limitations such as detecting the specific edition or product type of a Windows Version. Another limitation is the difficulty in detecting whether the operating system version is Windows 7 or Windows 2008 Server R2.

I also mentioned that in order to identify a particular version  and/or edition of Windows we need to invoke the Win32 GetVersionEx() function. If a test is dependent on a specific edition of Windows Vista or Windows Server 2008 then we can invoke the Win32 GetProductInfo() function. But, to use these Win32 APIs we need to use platform invocation services (P/Invoke) to marshal native code into our managed test code.

The code snippet below illustrates the required Win32 function marshaled using the DLLImport attribute in C#, constant values, wrapper methods to get common operating system information, and public properties to get the operating system version, any installed service pack information, and the operating system edition for Windows Vista, Windows Server 2008, and Windows 7.

   1: // <copyright file = VersionInfo.cs" company = "Testing Mentor">

   2: // Copyright © 2010 All Rights Reserved. Test developers can simply copy and

   3: // paste the code into their code, but may not reproduce or publish the code

   4: // snippets on any web site, online service, or distribute as source on any

   5: // media without express written permission. </copyright>

   6:  

   7: namespace TestingMentor.Snippet.OperatingSystemVersionInfo

   8: {

   9:   using System;

  10:   using System.Runtime.InteropServices;

  11:  

  12:   public class WindowsVersionInfo

  13:   {

  14:     public string GetOSVersion

  15:     {

  16:       get { return this.GetOSVersionInfo(); }

  17:     }

  18:  

  19:     public string GetServicePack

  20:     {

  21:       get { return this.GetServicePackInfo(); }

  22:     }

  23:  

  24:     public string GetProductType

  25:     {

  26:       get { return this.GetProductTypeInfo(); }

  27:     }

  28:  

  29:     private string GetOSVersionInfo()

  30:     {

  31:       string version = "Unsupported Version";

  32:  

  33:       NativeMethods.OSVersionInfoEx osvi = new NativeMethods.OSVersionInfoEx();

  34:       osvi.VersionInfoSize = 

  35:         Marshal.SizeOf(typeof(NativeMethods.OSVersionInfoEx));

  36:       NativeMethods.GetVersionEx(ref osvi);

  37:  

  38:       if (OsviConstant.SupportedPlatform == osvi.PlatformId &&

  39:         osvi.MajorVersion > 4)

  40:       {

  41:         if (osvi.MajorVersion == (int)OsviConstant.MajorVersion.NT5 &&

  42:           osvi.MinorVersion == (int)OsviConstant.MinorVersion.Windows2000)

  43:         {

  44:           version = "Windows 2000";

  45:         }

  46:  

  47:         if (osvi.MajorVersion == (int)OsviConstant.MajorVersion.NT5 &&

  48:           osvi.MinorVersion == (int)OsviConstant.MinorVersion.WindowsXP)

  49:         {

  50:           version = "Windows XP";

  51:         }

  52:  

  53:         if (osvi.MajorVersion == (int)OsviConstant.MajorVersion.NT5 &&

  54:           osvi.MinorVersion == (int)OsviConstant.MinorVersion.WindowsServer2003)

  55:         {

  56:           if (osvi.ProductType == (byte)OsviConstant.WorkStation)

  57:           {

  58:             version = "Windows XP Professional x64";

  59:           }

  60:           else

  61:           {

  62:             version = "Windows Server 2003";

  63:             if (NativeMethods.GetSystemMetrics(OsviConstant.ServerR2) != 0)

  64:             {

  65:               version += " R2";

  66:             }

  67:           }

  68:         }

  69:  

  70:         if (osvi.MajorVersion == (int)OsviConstant.MajorVersion.NT6 &&

  71:           osvi.MinorVersion == (int)OsviConstant.MinorVersion.WindowsVista)

  72:         {

  73:           if (osvi.ProductType ==

  74:             (byte)OsviConstant.WorkStation)

  75:           {

  76:             version = "Windows Vista";

  77:           }

  78:           else

  79:           {

  80:             version = "Windows Server 2008";

  81:           }

  82:         }

  83:  

  84:         if (osvi.MajorVersion == (int)OsviConstant.MajorVersion.NT6 &&

  85:           osvi.MinorVersion == (int)OsviConstant.MinorVersion.Windows7)

  86:         {

  87:           if (osvi.ProductType == (byte)OsviConstant.WorkStation)

  88:           {

  89:             version = "Windows 7";

  90:           }

  91:           else

  92:           {

  93:             version = "Windows Server 2008 R2";

  94:           }

  95:         }

  96:       }

  97:  

  98:       return version;

  99:     }

 100:  

 101:     private string GetServicePackInfo()

 102:     {

 103:       NativeMethods.OSVersionInfoEx versionInfo = new NativeMethods.OSVersionInfoEx();

 104:       versionInfo.VersionInfoSize = Marshal.SizeOf(typeof(NativeMethods.OSVersionInfoEx));

 105:       NativeMethods.GetVersionEx(ref versionInfo);

 106:       return versionInfo.CSDVersion; 

 107:     }

 108:  

 109:     private string GetProductTypeInfo()

 110:     {

 111:       string product = String.Empty;

 112:  

 113:       NativeMethods.OSVersionInfoEx osvi = new NativeMethods.OSVersionInfoEx();

 114:       osvi.VersionInfoSize =

 115:         Marshal.SizeOf(typeof(NativeMethods.OSVersionInfoEx));

 116:       NativeMethods.GetVersionEx(ref osvi);

 117:  

 118:       if (osvi.MajorVersion > 5)

 119:       {

 120:         uint productType = 0;

 121:  

 122:         NativeMethods.GetProductInfo(

 123:           osvi.MajorVersion,

 124:           osvi.MinorVersion,

 125:           osvi.ServicePackMajor,

 126:           osvi.ServicePackMinor,

 127:           ref productType);

 128:  

 129:         switch (productType)

 130:         {

 131:           case (uint)OsviConstant.ProductInfo.Business:

 132:             product = "Business Edition";

 133:             break;

 134:           case (uint)OsviConstant.ProductInfo.BusinessN:

 135:             product = "Business N Edition";

 136:             break;

 137:           case (uint)OsviConstant.ProductInfo.ClusterServer:

 138:             product = "HPC Edition";

 139:             break;

 140:           case (uint)OsviConstant.ProductInfo.DatacenterServer:

 141:             product = "Server Datacenter (Full)";

 142:             break;

 143:           case (uint)OsviConstant.ProductInfo.DatacenterServerCore:

 144:             product = "Server Datacenter (Core)";

 145:             break;

 146:           case (uint)OsviConstant.ProductInfo.DataCenterServerCoreV:

 147:             product = "Server Datacenter without Hyper-V (Core)";

 148:             break;

 149:           case (uint)OsviConstant.ProductInfo.DataCenterServerV:

 150:             product = "Server Datacenter without Hyper-V (Full)";

 151:             break;

 152:           case (uint)OsviConstant.ProductInfo.Enterprise:

 153:             product = "Enterprise Edition";

 154:             break;

 155:           case (uint)OsviConstant.ProductInfo.EnterpriseE:

 156:             product = "Enterprise E Edition";

 157:             break;

 158:           case (uint)OsviConstant.ProductInfo.EnterpriseN:

 159:             product = "Enterprise N Edition";

 160:             break;

 161:           case (uint)OsviConstant.ProductInfo.EnterpriseServer:

 162:             product = "Server Enterprise (Full)";

 163:             break;

 164:           case (uint)OsviConstant.ProductInfo.EnterpriseServerCore:

 165:             product = "Server Enterprise (Core)";

 166:             break;

 167:           case (uint)OsviConstant.ProductInfo.EnterpriseServerCoreV:

 168:             product = "Server Enterprise without Hyper-V (Core)";

 169:             break;

 170:           case (uint)OsviConstant.ProductInfo.EnterpriseServerIA64:

 171:             product = "Server Enterprise for Itanium-based Systems";

 172:             break;

 173:           case (uint)OsviConstant.ProductInfo.EnterpriseServerV:

 174:             product = "Server Enterprise without Hyper-V (Full)";

 175:             break;

 176:           case (uint)OsviConstant.ProductInfo.HomeBasic:

 177:             product = "Home Basic Edition";

 178:             break;

 179:           case (uint)OsviConstant.ProductInfo.HomeBasicE:

 180:             product = "Home Basic E Edition";

 181:             break;

 182:           case (uint)OsviConstant.ProductInfo.HomeBasicN:

 183:             product = "Home Basic N Edition";

 184:             break;

 185:           case (uint)OsviConstant.ProductInfo.HomePremium:

 186:             product = "Home Premium Edition";

 187:             break;

 188:           case (uint)OsviConstant.ProductInfo.HomePremiumE:

 189:             product = "Home Premium E Edition";

 190:             break;

 191:           case (uint)OsviConstant.ProductInfo.HomePremiumN:

 192:             product = "Home Premium N Edition";

 193:             break;

 194:           case (uint)OsviConstant.ProductInfo.HomeServer:

 195:             product = "Home Server Edition";

 196:             break;

 197:           case (uint)OsviConstant.ProductInfo.HyperV:

 198:             product = "Microsoft Hyper-V Server";

 199:             break;

 200:           case (uint)OsviConstant.ProductInfo.MediumBusinessServerManagement:

 201:             product = "Windows Essential Business Server Management Server";

 202:             break;

 203:           case (uint)OsviConstant.ProductInfo.MediumBusinessServerMessaging:

 204:             product = "Windows Essential Business Server Messaging Server";

 205:             break;

 206:           case (uint)OsviConstant.ProductInfo.MediumBusinessServerSecurity:

 207:             product = "Windows Essential Business Server Security Server";

 208:             break;

 209:           case (uint)OsviConstant.ProductInfo.Professional:

 210:             product = "Professional Edition";

 211:             break;

 212:           case (uint)OsviConstant.ProductInfo.ProfessionalE:

 213:             product = "Professional E Edition";

 214:             break;

 215:           case (uint)OsviConstant.ProductInfo.ProfessionalN:

 216:             product = "Professional N Edition";

 217:             break;

 218:           case (uint)OsviConstant.ProductInfo.ServerForSmallBusiness:

 219:             product = "Windows Server 2008 for Windows Essential Server Solutions";

 220:             break;

 221:           case (uint)OsviConstant.ProductInfo.ServerForSmallBusinessV:

 222:             product = "Windows Server 2008 without Hyper-V for Windows Essential Server Solutions";

 223:             break;

 224:           case (uint)OsviConstant.ProductInfo.ServerFoundation:

 225:             product = "Server Foundation";

 226:             break;

 227:           case (uint)OsviConstant.ProductInfo.SmallBusinessServer:

 228:             product = "Windows Small Business Server";

 229:             break;

 230:           case (uint)OsviConstant.ProductInfo.SmallBusinessServerPremium:

 231:             product = "Windows Small Busines Server Premium";

 232:             break;

 233:           case (uint)OsviConstant.ProductInfo.StandardServer:

 234:             product = "Server Standard (Full)";

 235:             break;

 236:           case (uint)OsviConstant.ProductInfo.StandardServerCore:

 237:             product = "Server Standard (Core)";

 238:             break;

 239:           case (uint)OsviConstant.ProductInfo.StandardServerCoreV:

 240:             product = "Server Standard without Hyper-V (Core)";

 241:             break;

 242:           case (uint)OsviConstant.ProductInfo.StandardServerV:

 243:             product = "Server Standard without Hyper-V (Full)";

 244:             break;

 245:           case (uint)OsviConstant.ProductInfo.Starter:

 246:             product = "Starter Edition";

 247:             break;

 248:           case (uint)OsviConstant.ProductInfo.StarterE:

 249:             product = "Starter E Edition";

 250:             break;

 251:           case (uint)OsviConstant.ProductInfo.StarterN:

 252:             product = "Starter N Edition";

 253:             break;

 254:           case (uint)OsviConstant.ProductInfo.StorageEnterpriseServer:

 255:             product = "Storage Server Enterprise";

 256:             break;

 257:           case (uint)OsviConstant.ProductInfo.StorageExpressServer:

 258:             product = "Storage Server Express";

 259:             break;

 260:           case (uint)OsviConstant.ProductInfo.StorageStandardServer:

 261:             product = "Storage Server Standard";

 262:             break;

 263:           case (uint)OsviConstant.ProductInfo.StorageWorkgroupServer:

 264:             product = "Storage Server Workgroup";

 265:             break;

 266:           case (uint)OsviConstant.ProductInfo.Ultimate:

 267:             product = "Ultimate Edition";

 268:             break;

 269:           case (uint)OsviConstant.ProductInfo.UltimateE:

 270:             product = "Ultimate E Edition";

 271:             break;

 272:           case (uint)OsviConstant.ProductInfo.UltimateN:

 273:             product = "Ulitmate N Edition";

 274:             break;

 275:           case (uint)OsviConstant.ProductInfo.Undefined:

 276:             product = "Unknown Product";

 277:             break;

 278:           case (uint)OsviConstant.ProductInfo.Unlicensed:

 279:             product = "Unlicensed or Expired";

 280:             break;

 281:           case (uint)OsviConstant.ProductInfo.WebServer:

 282:             product = "Web Server (Full)";

 283:             break;

 284:           case (uint)OsviConstant.ProductInfo.WebServerCore:

 285:             product = "Web Server (Core)";

 286:             break;

 287:         }

 288:       }

 289:  

 290:       return product;

 291:     }

 292:   }

 293:  

 294: // ****************************************************************************

 295: // NEW CLASS - SHOULD BE PLACED IN SEPARATE FILE

 296: // ****************************************************************************

 297:   

 298:   internal class OsviConstant

 299:   {

 300:     internal const int SupportedPlatform = 2;

 301:     internal const int ServerR2 = 89;

 302:     internal const int WorkStation = 0x00000001;

 303:  

 304:     private OsviConstant()

 305:     {

 306:     }

 307:  

 308:     internal enum MajorVersion

 309:     {

 310:       NT5 = 5,

 311:       NT6 = 6

 312:     }

 313:  

 314:     internal enum MinorVersion

 315:     {

 316:       Windows2000 = 0,

 317:       WindowsXP = 1,

 318:       WindowsServer2003 = 2,

 319:       WindowsVista = 0,

 320:       Windows7 = 1

 321:     }

 322:  

 323:     internal enum ProductInfo : uint

 324:     {

 325:       Business = 0x00000006,

 326:       BusinessN = 0x00000010,

 327:       ClusterServer = 0x00000012,

 328:       DatacenterServer = 0x00000008,

 329:       DatacenterServerCore = 0x0000000C,

 330:       DataCenterServerCoreV = 0x00000027,

 331:       DataCenterServerV = 0x00000025,

 332:       Enterprise = 0x00000004,

 333:       EnterpriseE = 0x00000046,

 334:       EnterpriseN = 0x0000001B,

 335:       EnterpriseServer = 0x0000000A,

 336:       EnterpriseServerCore = 0x0000000E,

 337:       EnterpriseServerCoreV = 0x00000029,

 338:       EnterpriseServerIA64 = 0x0000000F,

 339:       EnterpriseServerV = 0x00000026,

 340:       HomeBasic = 0x00000002,

 341:       HomeBasicE = 0x00000043,

 342:       HomeBasicN = 0x00000005,

 343:       HomePremium = 0x00000003,

 344:       HomePremiumE = 0x00000044,

 345:       HomePremiumN = 0x0000001A,

 346:       HyperV = 0x0000002A,

 347:       MediumBusinessServerManagement = 0x0000001E,

 348:       MediumBusinessServerSecurity = 0x0000001F,

 349:       MediumBusinessServerMessaging = 0x00000020,

 350:       Professional = 0x00000030,

 351:       ProfessionalE = 0x00000045,

 352:       ProfessionalN = 0x00000031,

 353:       ServerForSmallBusiness = 0x00000018,

 354:       ServerForSmallBusinessV = 0x00000023,

 355:       ServerFoundation = 0x00000021,

 356:       SmallBusinessServer = 0x00000009,

 357:       StandardServer = 0x00000007,

 358:       StandardServerCore = 0x0000000D,

 359:       StandardServerCoreV = 0x00000028,

 360:       StandardServerV = 0x00000024,

 361:       Starter = 0x0000000B,

 362:       StarterE = 0x00000042,

 363:       StarterN = 0x0000002F,

 364:       StorageEnterpriseServer = 0x00000017,

 365:       StorageExpressServer = 0x00000014,

 366:       StorageStandardServer = 0x00000015,

 367:       StorageWorkgroupServer = 0x00000016,

 368:       Undefined = 0x00000000,

 369:       Ultimate = 0x00000001,

 370:       UltimateE = 0x00000047,

 371:       UltimateN = 0x0000001C,

 372:       WebServer = 0x00000011,

 373:       WebServerCore = 0x0000001D,

 374:       Unlicensed = 0xABCDABCD,

 375:       HomeServer = 0x00000013,

 376:       SmallBusinessServerPremium = 0x00000019,

 377:     }

 378:   }

 379:  

 380: // ****************************************************************************

 381: // NEW CLASS - SHOULD BE PLACED IN SEPARATE FILE

 382: // ****************************************************************************

 383:  

 384:   internal class NativeMethods

 385:   {

 386:     private NativeMethods()

 387:     {

 388:     }

 389:  

 390:     [DllImport("kernel32")]

 391:     [return: MarshalAs(UnmanagedType.Bool)]

 392:     internal static extern bool GetVersionEx(ref OSVersionInfoEx osvi);

 393:  

 394:     [DllImport("kernel32.dll")]

 395:     [return: MarshalAs(UnmanagedType.Bool)]

 396:     internal static extern bool GetProductInfo(

 397:       int osMajorVersion,

 398:       int osMinorVersion,

 399:       int spMajorVersion,

 400:       int spMinorVersion,

 401:       ref uint type);

 402:  

 403:     [DllImport("kernel32.dll")]

 404:     internal static extern int GetSystemMetrics(

 405:       int index);

 406:  

 407:     [StructLayout(LayoutKind.Sequential)]

 408:     internal struct OSVersionInfoEx

 409:     {

 410:       public int VersionInfoSize;

 411:       public int MajorVersion;

 412:       public int MinorVersion;

 413:       public int BuildNumber;

 414:       public int PlatformId;

 415:       [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]

 416:       public string CSDVersion;

 417:       public Int16 ServicePackMajor;

 418:       public Int16 ServicePackMinor;

 419:       public Int16 SuiteMask;

 420:       public byte ProductType;

 421:       public byte Reserved;

 422:     }

 423:   }

 424: }

Now, some of my readers have indicated that these code snippets are not very useful because they can’t copy them directly and put them to use. So, to help resolve that issue I have created a new section on my web site called the Code Snippet Library. This snippet is posted there along with fully annotated (mostly FxCopy and StyleCopy compliant) file available for download or to copy for inclusion in your automated test cases, or compiled into a dynamic link library (DLL).

This example doesn’t differentiate between 32-bit and 64-bit Windows operating systems, but that is not really difficult to add, and if I get enough requests I will certainly add that into the pot. If the operating system version is no longer supported by Microsoft the GetOsVersion property will return "Unsupported Version." If no Service Packs are installed the GetServicePack property will return an empty string. If you need to detect an unsupported operating system version use the example here.

Written by Bj Rollison

March 3rd, 2010 at 11:33 am

toxey.rsk@mailxu.com