Click to return to the Web Content Management home page    
Web Workshop  |  Web Content Management

Sniffing For Browsers, Virtual Machines, and Operating Systems


Michael Edwards
Developer Technology Engineer
Microsoft Corporation

Updated June 25, 1999

Editor's note: This article is but the latest in a series of articles Michael has written on sniffing for various system components. Other parts include "More Sniffing for Browsers, Virtual Machines, and Operating Systems" and "Sniffing for the Microsoft Virtual Machine."

Contents
Introduction
Sniffing the Browser
   Using JavaScript
   Using C++
   Using Java
Sniffing for a Scripting Engine
Sniffing for the VM Version
   Using Java
   Using JavaScript or C++
Sniffing for Operating System
   Using JavaScript
   Using C++
   Using Java
Server-side Sniffing
Summary
Part II: More Sniffing ...

Introduction

Anybody who creates Web pages these days is well aware of how much choice customers really have (lots and lots)! Besides choosing from a range of browsers, they use different virtual machines, operating systems, and processors. Of course, platform diversity means that Web authors have to decide what features they put in their Web pages, since feature support varies across all these environments. Your choices include:

Whatever the case, you'll probably want to sniff for the configurations you choose to support. Which is where this article comes in. If you're looking for scripts, C++, or Java code to sniff the stuff your users are running, you've come to the right place.

Before getting started, I want to thank the Microsoft® MSDN Online, DirectX, and MSN.com Non-MSDN Online link development teams for helping me put together most of this code. But if anything is screwed up (no way, I tested this stuff!), it's my fault because I made changes here and there. (How could I not make changes? I'm a hacker at heart!)

Back to topBack to top

Sniffing the Browser

All the browser-sniffing code for script, C++, or Java uses the document object model (DOM) exposed by the browser. Specifically, the Navigator object offers several properties that give details on the browser, including navigator.userAgent, navigator.appVersion, and navigator.appName.

Using JavaScript

The algorithm below parses the navigator.userAgent browser property, a character string that indicates product name, version, and other information specific to a given browser. Once a given version of a browser ships, you can examine its userAgent property in order to write script to identify it. (This is the same userAgent string that is included in the HTTP request headers, and, as discussed in the server-side sniffing section below, the same algorithm can be used to sniff from the server.) This userAgent technique is similar to an Internet Explorer version-checking script from the Internet Client Software Development Kit (INetSDK) that its authors claim is the easiest way to sniff for Internet Explorer and its version number (I've modified it to sniff for Netscape versions as well). Warning to those who modify this code: the userAgent string for a given browser and version does not remain constant even within versions. You've heard of "silent upgrades", where a software vendor starts shipping a newer version, usually with some important bug fix(es), without changing the version number? Well, this happens with browsers, too. Vendors ship code that has been modified from previous versions without giving it the distinction of a new release. They will often change the userAgent string, however, which could lead to lots of problems. Thankfully, they usually just tack extra stuff on the end of the string. In response, the string comparisons in our sample code match only the beginning, rather than all, of a string.

So here's the JavaScript you can use in your client-side scripting that will sniff the browser version using the navigator.userAgent property. It works for Internet Explorer 3.x, Internet Explorer 4.x, as well as Netscape 2.x, 3.x and 4.x (and here's a page that uses the code below to do client-side JavaScript browser-sniffing).

<SCRIPT LANGUAGE="JavaScript"><!--

// These variables are global to the included page
var bMSIE = false;
var bMSIE3 = false, bMSIE4 = false, bMSIE4_beta = false, bMSIE4_01 = false, bMSIE5 = false;
var bNetscape = false;
var bNetscape_2 = false, bNetscape_3 = false, bNetscape_4 = false;

function browser()
{
    var ua = navigator.userAgent;
    var an = navigator.appName;

    // Is it IE?
    bMSIE = (ua.indexOf("MSIE")>=1);
    if (bMSIE)
    {
        // IE3
        bMSIE3 = (ua.indexOf("MSIE 3.0")>=1);

        // IE4
        var iMSIE4 = ua.indexOf("MSIE 4.0");
        bMSIE4 = (iMSIE4>=1);
        if (bMSIE4)
        {
            var sMinorVer = ua.charAt(iMSIE4+8);
            // Some folks are still running an IE4 beta!
            // (the Mac IE team used a 'p' to mark their beta)
            bMSIE4_beta = bMSIE4 && ((sMinorVer == "b") || (sMinorVer == "p"));

            // IE4.01
            bMSIE4_01 = bMSIE4 && (sMinorVer == "1");
        }
        // IE5
        bMSIE5 = (ua.indexOf("MSIE 5.0")>=1);
    }
    else if (an == "Netscape")
    {
        bNetscape = true;
        appVer = parseInt(navigator.appVersion);
        if (appVer >= 4)
            bNetscape_4 = true;
        else if (appVer >= 3)
            bNetscape_3 = true;
        else
            bNetscape_2 = true;
    }
}
//-->
</SCRIPT>

Note that the method shown here is not the only algorithm people use to determine the browser. In fact, there are other properties for the Navigator object that are actually better-suited for sniffing. For example, the code would be simpler using the navigator.appVersion and navigator.appName properties (both are character strings) to determine the various flavors of each browser. However, I don't know exactly what strings those properties return for the twenty million different versions of Netscape and Microsoft browsers shipped to date, whereas I do know the userAgent strings (we've kept a record of them over the years). Finally, the userAgent method is what we used here at the Site Builder Network, so it has been thoroughly tested (you guys have lots of different browsers!).

Back to topBack to top

Using C++

For ActiveX controls, Microsoft Support Online's answer to the question "How do I access Internet Explorer's object model from a contained control?" Non-MSDN Online link tells how to get a pointer to the dispatch interface for the window object, IOmWindow, from your IOleClientSite interface. Here's some code that will work for both Internet Explorer 3.x and 4.x (I've modified their example Non-MSDN Online link slightly):

#include <mshtml.h>
...
IOleClientSite* pClientSite;
... 
// pClientSite should already point to the control's client site.

IServiceProvider* pISP;
IDispatch* pIHTMLDocument;

pClientSite->QueryInterface(IID_IServiceProvider, (void **)&pISP);
pISP->QueryService(SID_SContainerDispatch, IID_IDispatch, (void**)& pIHTMLDocument);

// From pIHTMLDocument we can get the window object 
// (IOmWindow was superceded by IHTMLWindow2 in Internet Explorer4.x)
IDispatch* pIOmWindow;
pIHTMLDocument->get_Script(&pIOmWindow);

// Now we can get the navigator object.
IDispatch* pIOmNavigator;
pIOmWindow->get_navigator(&pIOmNavigator);

BSTR appName, appVersion;
pIOmNavigator->get_appName(&appName);
pIOmNavigator->get_appVersion(&appVersion);

On the other hand, if all you want to know is whether you are being hosted by Internet Explorer 3.x or Internet Explorer 4.x, here is an even easier way:

IOleClientSite* pClientSite; 
// pClientSite should already point to the control's client site

IServiceProvider* pISP;
IWebBrowser2* pIWebBrowser2;
HRESULT hr;

pClientSite->QueryInterface(IID_IServiceProvider, (void **)&pISP);
hr = pISP->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2,
                  (LPVOID*)& pIWebBrowser2);
if (hr == S_OK)
{
   // We're running on Internet Explorer4.x or greater 
   //(IWebBrowser2 is only on Internet Explorer 4).
}
else
{
   // Are we at least being hosted by Internet Explorer3.x?
   IDispatch* pIDispatch;
   hr = pISP->QueryService(IID_IWebBrowserApp, IID_IDispatch, 
                       (void **)& pIDispatch);
   if (hr == S_OK)
{
   // Yup, it's Internet Explorer3.x alright.
}
else
{
   // Dang, must be a non-Internet Explorer container (VB, Netscape, who knows...)
}
}

If you like to do your Component Object Model (COM) coding with Active Template Library (ATL), check out the GetWebBrowser() method in the PageAcc.cpp source file included with the Data Persistence control (built with ATL) from "Discardable Properties for Your Web Pages in Internet Explorer 4.0". There's also the 'ObjVw' sample that illustrates using Internet Explorer's Automation and scripting models from an ATL control.

Unfortunately I don't know anything about accessing the DOM from C++ for Netscape's browser. I did spend an unsuccessful half-hour looking through the Netscape site's developer documentation pages Non-MS link. Maybe they export a proprietary interface for checking the browser and version. Anyway, I'd be happy to update this article should anybody offer up better information (e-mail me).

Back to topBack to top

Using Java

If you extend browser functionality using Java, your code is much more dependent upon the virtual machine vendor and build version than the browser. If you need to detect the browser from your Java code, you could detect using JavaScript (as above) and pass the results via a public method in your applet. Another option is to catch exceptions of the type ClassNotFound when you reference classes implemented only for certain operating systems or browsers.

Back to topBack to top

Sniffing for a Scripting Engine

Unfortunately, with the exception of Netscape and JavaScript, developers can't simply assume that certain script engines come with a given version of the browser. With Internet Explorer, for example, users can upgrade their script engines. When upgraded scripting engines were released four months or so after Internet Explorer 3.0 came out, version-sniffing was essential for developers that wanted to offer improved functionality to users who obviously cared (else they wouldn't have upgraded), without screwing up those who didn't. Further, Internet Explorer hosts a third-party scripting interface that allows script engines for Perl and other languages to be installed.

With Netscape browsers, JavaScript is the only script language offered, and each browser version can only use the version included when it shipped. So for Netscape, you simply need to sniff the browser (as shown above) to determine the script engine and build version. The following table shows the JavaScript version shipped with each Netscape browser:



Netscape Browser Version JavaScript Version
Navigator version 1.x JavaScript version 1.0
Navigator version 3.x JavaScript version 1.1
Communicator version 4.x JavaScript version 1.2

For Internet Explorer 2.0 and later, both the built-in VBScript and JavaScript (a.k.a. JScript® and ECMAScript) engines support functions for querying the script engine name and build version. The APIs are identical, so you can query using either the JavaScript or VBScript <SCRIPT> tag. See the documentation for VBScript and JScript on the Windows Script Technologies Web site. If you use a third-party script engine in Internet Explorer, you'll have to use version detection methods that are supported by that engine (and should be discussed in their docs).

No matter what scripting engine you use, your primary consideration is probably whether it is supported at all. At the very least, you probably want to craft a decent error message that lets users down gracefully and meaningfully. The first place to do this is by supporting the <NOSCRIPT> tag for browsers that don't support scripting. What you do beyond that depends upon why you are sniffing for the script engine in the first place. But whether you offer advanced functionality that makes use of recently-introduced scripting features, or go with some sort of lowest-common-denominator solution supported on more browsers, most people want to offer a meaningful message to users who navigate their site with inadequate scripting support.

Back to topBack to top

Sniffing for the Virtual Machine Version

Much to the consternation of many, not all virtual machines are created equal. It's a fact that has to be dealt with. One way you do this is by testing your Java code on certain virtual machines that have been determined to represent your primary platform targets, and then making sure that you catch when you are running on a virtual machine you haven't tested.

Using Java

You can use the BuildIncrement property of the SystemVersionManager.getVMVersion() static method (in the com.ms.util package) to find out which version of the Microsoft virtual machine is installed:

String strBuild;
try {
   strBuild = SystemVersionManager.getVMVersion().getProperty("BuildIncrement");
   System.out.println("Using build "+ strBuild);
// If you want a number:
int iBuild = parseInt(strBuild);
}
catch (InterruptedException e) {
   // Dang, must be somebody else's virtual machine, bail out quick.
}
// There is an article in the January '98 MIND about using J/Direct that
// includes some examples for using SystemVersionManager:
// http://www.microsoft.com/mind/0198/jdirect.htm

(I discuss this method a little more in the third part of my ever-growing sniffing series, "Sniffing the Virtual Machine".)

What if you actually care about running on a virtual machine other than Microsoft's? Your best bet is probably the static method getProperties of the java.lang.System class supported by any JDK 1.0-compatible virtual machine (including Microsoft and Netscape):

Properties props = java.lang.System.getProperties();
String strVendor = props.getProperty("java.vendor");  // tells who made it
String strVersion = props.getProperty("java.version");// tells the JDK version
// Doesn't get the virtual machine build number, how can you get the build number???

Using JavaScript or C++

When I first wrote this article, I couldn't think of any reasons why people would want to use script or C++ alone to sniff for the virtual machine. I've since been corrected, and even wrote a follow-up article devoted exclusively to virtual machine sniffing with script (and why it's hard). For sniffing with C++, the definitive solution was published in an April 1998 Microsoft Systems Journal magazine article by Paul DiLascia (a portion of the article which includes the sample code, "C++ Q&A with Paul DiLascia" Non-MSDN Online link, is available online, but it's still only a portion, so I apologize if you don't have a copy).

Back to topBack to top

Sniffing for Operating System

If you get all your information from the news media, you might believe there is only one operating system in use on the Internet. The truth is that Microsoft is working very hard to provide excellent Internet Explorer support for the Macintosh and UNIX operating systems. And even on the Windows® platform, there's Windows 95, Windows NT®, Windows CE, Windows 3.x, and last, but certainly not least, the newest version of Windows, Windows 98 (which all the code we give you will sniff for). So, yes, you do have choices, as do your readers. But of course, not all operating systems are created equal. For example, running Java code on Windows 3.1 platforms creates a huge performance problem.

Using JavaScript

Following is some JavaScript that will sniff the OS version. It works for Internet Explorer 3.x and 4.x, as well as Netscape 2.x, 3.x and 4.x. It is based on the code that we used here at the Site Builder Network (here's a page that sniffs for OS using JavaScript). If you want to go further and check the type of processor running the operating system, the latest DOM spec includes the new navigator.cpuClass property (which is supported by Internet Explorer 4.0 and documented in the Internet Client SDK).

<SCRIPT LANGUAGE="JavaScript"><!--

// Call the browser() function before you call this one!

// These variables can be used globally by the included page.
var bWin16 = false; // includes Win31 and WinNT 3.51
var bWin95 = false;
var bWin98 = false;
var bWinNT = false;
var bMac = false, bMac68K = false, bMacPPC = false;
var bMSSun = false;

function os()
{
    var ua = navigator.userAgent;
   
   if (bMSIE)
   {
      //IE supported OS's
      bWin95 = (ua.indexOf("Windows 95")>=1);
      bWin98 = (ua.indexOf("Windows 98")>=1);
      bWinNT = (ua.indexOf("Windows NT")>=1);
      bWin16 = (ua.indexOf("Windows 3.1")>=1);
      bMac = (ua.indexOf("Mac")>=1);
      bMac68K = (ua.indexOf("Mac_68000")>=1);
      bMacPPC = (ua.indexOf("Mac_PowerPC")>=1);
      bMSSun = (ua.indexOf("SunOS")>=1)
   } 
   else if (bNetscape)
   {
      // NSCP supporte OS's
      bWin95 = (ua.indexOf("Win95")>=1);
      bWin98 = (ua.indexOf("Win98")>=1);
      bWinNT = (ua.indexOf("WinNT")>=1);
      bWin16 = (ua.indexOf("Win16")>=1);
      bMac = (ua.indexOf("Mac")>=1);
      if (bMac)
      {
         bMac68K = (ua.indexOf("68K")>=1);
         bMacPPC = (ua.indexOf("PPC")>=1);
      }
      // BUGBUG: this doesn't find any of Netscape's Unix versions
   }
}
</SCRIPT>

Using C++

Here's the code that works for any browser running on a Win32 platform (including Windows NT (version 4.00 and later) for the Alpha and PowerPC® platforms, and Windows CE):

OSVERSIONINFO os;
os.dwOSVersionInfoSize = sizeof(os);
GetVersionEx(&os);
// OSVERSIONINFO will tell you major, minor, and build version numbers, as
// well as an id that can be used to get other platform information.
// For details see the MSDN Library Online docs on OSVERSIONINFO:
// http://msdn.microsoft.com/library/sdkdoc/pdsms/sysinfo_49iw.htm

Back to topBack to top

Using Java

You can sniff for OS in a cross-platform and cross-browser way by extending the virtual machine-sniffing example.

String strOSName = props.getProperty("os.name");
String strOSVer  = props.getProperty("os.version");
String strOSArch = props.getProperty("os.arch");

Back to topBack to top

Server-side Sniffing

The difference between client-side and server-side script refers to where the script is executed. Script that is inside an HTML <SCRIPT> tag is downloaded with the rest of the page's HTML, and executes on the client. With server-side scripting, it doesn't matter what browser the client is using, because the code runs only on the server. In fact, people often use server-side sniffing to determine what to send to the client (as you'll see below).

If your Web site is hosted on IIS, and you are already using Internet Server API, it might be easier to use the Microsoft Foundation Classes (MFC) to retrieve and parse the HTTP User Agent Header on the server directly. What's more, you could write out a global server variable to store the browser info, and only have to sniff once per application session. Voila, ease and performance-improvement simultaneously! If you would like more information on this option, e-mail me.

Never satisfied with showing you only one way to do something, I'm going to give you the ASP browser-sniffing script that we used here at the Site Builder Network. Why don't we use the method I just described above? I'm not sure, but I think it has something to do with the huge traffic volumes we run on our Microsoft Web sites (the Browser Caps stuff is server-side, and might bog down the server on high-traffic sites). Since our sites are hosted on Internet Information Server 3.0 and 4.0, this example code (like the above sample) uses ASP server-side scripting. This page sniffs for OS and browser from the server using this code.

How about a variation that passes pre-initialized JavaScript variables to the client (so client-side script can do browser-specific stuff too, without having to sniff all over again)? All you have to do is add something like this:

<SCRIPT LANGUAGE=JavaScript>
var AnyIE = <%= LCase(CStr(AnyIE)) %>
var IE5 = <%= LCase(CStr(IE5)) %>
var IE4 = <%= LCase(CStr(IE4)) %>
var IE3 = <%= LCase(CStr(IE3)) %>
var IE302 = <%= LCase(CStr(IE302)) %>
var AnyNetscape = <%= LCase(CStr(AnyNetscape)) %>
var Netscape4 = <%= LCase(CStr(Netscape4)) %>
var Netscape3 = <%= LCase(CStr(Netscape3)) %>
var Win95 = <%= LCase(CStr(Win95)) %>
var Win98 = <%= LCase(CStr(Win98)) %>
var WinNT = <%= LCase(CStr(WinNT)) %>
var AnyMac = <%= LCase(CStr(AnyMac)) %>
</SCRIPT>

For example, if you insert the above ASP code in front of the fill_table() function, you could rewrite fill_table() in JavaScript and have access to the browser version information elsewhere.

function fill_table()
{
if (AnyIE)
   document.browserTable.Name.value = "Microsoft Internet Explorer";
else if (AnyNetscape)
    document.browserTable.Name.value = "Netscape Navigator";
else
    document.browserTable.Name.value = "Not supported";

if (IE4 || Netscape4)
   document.browserTable.Version.value = "4.x";
else if (IE3 || Netscape3)
    document.browserTable.Version.value = "3.x";
else
    document.browserTable.Version.value = "Not supported";   

if (Win95)
   document.browserTable.os.value = "Windows 95";
else if (Win98)
    document.browserTable.os.value = "Windows 98";
else if (WinNT)
    document.browserTable.os.value = "Windows NT";
else if (AnyMac)
    document.browserTable.os.value = "Macintosh";
else if (Unix)
    document.browserTable.os.value = "Unix";
else
    document.browserTable.os.value = "Not supported";   
}

Back to topBack to top

Summary

Unfortunately, sniffing for browser, virtual machine, and/or operating system is often necessary for anybody implementing new Web functions or features. The proliferation of version releases for each (even the nefarious "silent upgrades") complicates things still further. Perhaps someday we'll live in a world where all the browsers are bug-free and one-hundred-percent standards-compliant. New software versions will be simultaneously shipped by every vendor, and all users will immediately upgrade to them. Then you can throw all your sniffing code away. Until then, however, you're stuck with sniffing (and the multiple Web pages necessary to support those different configurations). At least you have lots of options: you can sniff from server- or client-side, and you can sniff using C++, Java, or different scripting engines. Hopefully, you're not implementing so many features that you need more than two versions of each page, but if you are, at least you have some flexibility.

Although I've tested this stuff extensively, I am not foolish enough to think nobody will run into any problems. But I am foolish enough to believe that those of you who do run into trouble will let me know so I can fix it right away. (E-mail me!)

Good luck and happy sniffing!

October 2, 1998 update: Many people wrote in with questions on sniffing. We've answered them in More Sniffing For Browsers, Virtual Machines, and Operating Systems.



Back to topBack to top

Did you find this material useful? Gripes? Compliments? Suggestions for other articles? Write us!

© 1999 Microsoft Corporation. All rights reserved. Terms of use.