Detecting dot net CLR version on client machine in Firefox
2007-9-4
By Sumit
Update [November 24, 2009]: Folks it's been a while since Microsoft launched Silverlight. Then came what is referred to as Microsoft .NET Framework Assistant add-on for Firefox (http://msdn.microsoft.com/en-us/library/cc716877.aspx). This does what my original article attempted. Someday I'll post a new article on how to use it. As of now, this article is deprecated.
Recently I came across the need for my ASP.NET application running on Firefox browser to tell me the version of .NET CLR version on the client side. On IE it's a breeze because the browser identifier string has the CLR version and is neatly encapsulated in the Browser object on the server side. However the object doesn't work for non-IE browsers.
Initial googling did not return anything available ready-made, so the requirement was shelved due to lack of time. Couple of weeks back I got enough time to look-up what it takes to create a Firefox plugin.
Firefox architecture is extremely extensible, however I was disappointed to find different kinds of 'plugin' mechanisms for different purposes. Maybe I don't understand it all that well but anyway I got really confused between an XPCOM component (extension) or Plugin architecture.
After reading thru a lot of documentation I felt XPCOM component (extension) is what I needed to create. I am still not 100% sure it is THE right approach. If someone can do the same using the 'plugin' architecture please let me know.
A very nice article outlining creation of XPCOM components using VC++ Express helped me create and build the interface required. The article also has links to a sample solution that built without a hitch after I had setup the external environment correctly (for the first time in life I got a C++ solution in Visual Studio working). I used the sample as a prototype for my solution. Took me a lot of further reading to actually realize a very simple thing. After building an XPCOM interface (in C++) it is not necessary you have to code the implementation in C++. It can be done in javascript as well. Once I realized that, this article showed the path forward. XPCOM has established API to access windows registry. Though I would really have loved to do all this in C++ (curiosity killed the cat) I succumbed to the temptation of using Javascript.
This link outlines Microsoft's recommendations on detecting .NET CLR versions thru registry entries. I was a little irritated to find almost each version of CLR being written to a new location in the registry making the component totally at mercy of Microsoft in future. I did not have enough patience to write code for version 1.x. The code I wrote works for .NET version 2 and 3. Not sure if it will work for 3.5 or future versions.
The final outcome is just about functional. It can be termed as an alpha release. Here are the steps to DIY. I am not publishing a package/bundle/xpi because somehow I couldn't create one that installs in Firefox. Kept giving errors while installation. But if you place the pieces in the right place the functionality works.
So whatever is presented below is completely on an 'AS IS' basis. I've tested it on Vista Business/XP Pro and Win2003 Server and Firefox 2.x. It did not hose my Firefox installations in either of those machines. Please don't sue me if it messes up your Firefox.
Now to the 'real' stuff.
Step 1: The XPCOM Interface: a. Create and IDL file as follows:
[sourcecode language="cpp"] /** Dot net detection IDL by Sumit Kumar Maitra. August 2007 Pune, India
References/Acknowledgement: http://developer.mozilla.org/en/docs/How\_to\_Build\_an\_XPCOM\_Component\_in\_Javascript http://www.codeproject.com/dotnet/DetectDotNet.asp http://msdn2.microsoft.com/en-us/library/aa480198.aspx#netfx30\_topic14 http://developer.mozilla.org/en/docs/Accessing\_the\_Windows\_Registry\_Using\_XPCOM#Reading\_Registry\_Values http://developer.mozilla.org/en/docs/install.rdf */
#include "nsISupports.idl"
[scriptable, uuid(429E4518-4D9B-11DC-BB8A-307E55D89593)] interface IDotnetDetect : nsISupports { AString GetDotNetVersion(); };
[/sourcecode]
b. The idl is compiled into an xpt file and the required headers are also generated as follows:
[sourcecode language="cpp"]
/* * DO NOT EDIT THIS FILE IS GENERATED FROM dotnetdetectComp.idl */
#ifndef __gen_dotnetdetectComp_h__ #define __gen_dotnetdetectComp_h__
#ifndef __gen_nsISupports_h__ #include "nsISupports.h" #endif
/* For IDL files that don't want to include root IDL files. */ #ifndef NS_NO_VTABLE #define NS_NO_VTABLE #endif
/* starting interface:����¯�¿�½���¯���¿���½ ����¯�¿�½���¯���¿���½ ����¯�¿�½���¯���¿���½ IDotnetDetect */ #define IDOTNETDETECT_IID_STR "429e4518-4d9b-11dc-bb8a-307e55d89593"
#define IDOTNETDETECT_IID \ {0x429e4518, 0x4d9b, 0x11dc, \ { 0xbb, 0x8a, 0x30, 0x7e, 0x55, 0xd8, 0x95, 0x93 }}
class NS_NO_VTABLE IDotnetDetect : public nsISupports
{ public: NS_DEFINE_STATIC_IID_ACCESSOR(IDOTNETDETECT_IID) /* AString GetDotNetVersion (); */ NS_IMETHOD GetDotNetVersion(nsAString & _retval) = 0; };
/* Use this macro when declaring classes that implement this interface. */ #define NS_DECL_IDOTNETDETECT \ NS_IMETHOD GetDotNetVersion(nsAString & _retval);
/* Use this macro to declare functions that forward the behavior of this interface to another object. */ #define NS_FORWARD_IDOTNETDETECT(_to) \ NS_IMETHOD GetDotNetVersion(nsAString & _retval) { return _to GetDotNetVersion(_retval); }
/* Use this macro to declare functions that forward the behavior of this interface to another object in a safe way. */ #define NS_FORWARD_SAFE_IDOTNETDETECT(_to) \ NS_IMETHOD GetDotNetVersion(nsAString & _retval) { return !_to ? NS_ERROR_NULL_POINTER : _to->GetDotNetVersion(_retval); }
#if 0 /* generated class template below */
/* End of implementation class template. */ #endif
#endif /* __gen_dotnetdetectComp_h__ */ [/sourcecode]
c. You will notice that the header also contains a template for the class to be used for implementing the interfaces. It looks as follows:
[sourcecode language="cpp"]
/* Use the code below as a template for the implementation class for this interface. */
/* Header file */ class _MYCLASS_ : public IDotnetDetect { public: NS_DECL_ISUPPORTS NS_DECL_IDOTNETDETECT
_MYCLASS_();
private: ~_MYCLASS_();
protected: /* additional members */ };
/* Implementation file */ NS_IMPL_ISUPPORTS1(_MYCLASS_, IDotnetDetect)
_MYCLASS_::_MYCLASS_() { /* member initializers and constructor code */ }
_MYCLASS_::~_MYCLASS_() { /* destructor code */ }
/* AString GetDotNetVersion (); */ NS_IMETHODIMP _MYCLASS_::GetDotNetVersion(nsAString & _retval) { return NS_ERROR_NOT_IMPLEMENTED; }
[/sourcecode]
d. If you intend to implement the interfaces in C++ use the given template to save yourself that much typing.
Step 2: XPCOM implementation using Javascript
The javascript to detect dot net CLR versions is as follows
[sourcecode language="jscript"]
/*********************************************************** constants ***********************************************************/
// reference to the interface defined in dotnetDetect.idl const IDotnetDetect = Components.interfaces.IDotnetDetect;
// reference to the required base interface that all components must support const nsISupports = Components.interfaces.nsISupports;
// UUID uniquely identifying our component const CLASS_ID = Components.ID("{429E4518-4D9B-11DC-BB8A-307E55D89593}");
// description const CLASS_NAME = "DotnetDetect";
// textual unique identifier const CONTRACT_ID = "@sumitmaitra.com/dotnetdetect;1";
/*********************************************************** class definition ***********************************************************/
//class constructor function DotnetDetect() { };
// class definition DotnetDetect.prototype = {
// define the function we want to expose in our interface GetDotNetVersion: function() { var wrk = Components.classes["@mozilla.org/windows-registry-key;1"] .createInstance(Components.interfaces.nsIWindowsRegKey);
var detectedCLRversions = "Unknown";
// Detect Version 2 and 3 of CLR Runtime wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\NET Framework Setup\\NDP", wrk.ACCESS_READ);
var detectedCLRversionsCount = 0;
for (var i = wrk.childCount - 1; i >= 0; i--) { var name = wrk.getChildName(i); var subkey = wrk.openChild(name, wrk.ACCESS_READ); var isInstalled = "0" var valueName = "Install"; if (subkey.hasValue(valueName)) // .NET version 2.x and 1.1.x { isInstalled = subkey.readIntValue(valueName); } else //.NET version 3.x { var setupKey = subkey.openChild("Setup", wrk.ACCESS_READ); valueName = "InstallSuccess"; isInstalled = setupKey.readIntValue(valueName); } //Alert(valueName + ' ' + isInstalled); if(isInstalled == 1) { if(detectedCLRversionsCount == 0) { detectedCLRversions = name; } else { detectedCLRversions = detectedCLRversions + ' ' + name; } detectedCLRversionsCount = detectedCLRversionsCount + 1; } subkey.close(); }
wrk.close(); return detectedCLRversions ; },
QueryInterface: function(aIID) { if (!aIID.equals(IDotnetDetect) && !aIID.equals(nsISupports)) throw Components.results.NS_ERROR_NO_INTERFACE; return this; } };
/*********************************************************** //class factory
//This object is a member of the global-scope Components.classes. //It is keyed off of the contract ID. Eg:
myDotnetDetect = Components.classes["@sumitmaitra.com/dotnetdetect;1"]. createInstance(Components.interfaces.IDotnetDetect);
***********************************************************/ var DotnetDetectFactory = { createInstance: function (aOuter, aIID) { if (aOuter != null) throw Components.results.NS_ERROR_NO_AGGREGATION; return (new DotnetDetect()).QueryInterface(aIID); } };
/*********************************************************** module definition (xpcom registration) ***********************************************************/ var DotnetDetectModule = { registerSelf: function(aCompMgr, aFileSpec, aLocation, aType) { aCompMgr = aCompMgr. QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.registerFactoryLocation(CLASS_ID, CLASS_NAME, CONTRACT_ID, aFileSpec, aLocation, aType); },
unregisterSelf: function(aCompMgr, aLocation, aType) { aCompMgr = aCompMgr. QueryInterface(Components.interfaces.nsIComponentRegistrar); aCompMgr.unregisterFactoryLocation(CLASS_ID, aLocation); },
getClassObject: function(aCompMgr, aCID, aIID) { if (!aIID.equals(Components.interfaces.nsIFactory)) throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
if (aCID.equals(CLASS_ID)) return DotnetDetectFactory;
throw Components.results.NS_ERROR_NO_INTERFACE; },
canUnload: function(aCompMgr) { return true; } };
/*********************************************************** module initialization
When the application registers the component, this function is called. ***********************************************************/ function NSGetModule(aCompMgr, aFileSpec) { return DotnetDetectModule; }
[/sourcecode]
Step 3: Putting the pieces in the right locations. Now due to the lack of a working installable package you have to place all the pieces in the right locations manually. So here are the steps:
1. Copy the xpt and javascript file to "[Mozilla Firefox install directory]\components" folder. 2. Restart Firefox (if simple restart doesn't work restart the machine).
Step 4: Testing it out
1. Save the following as an HTML file
[sourcecode language="css"]
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); var oMyComponent = Components.classes['@sumitmaitra.com/dotnetdetect;1'].createInstance(Components.interfaces.IDotnetDetect); function btnReverse_onclick() { netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); try { var s = oMyComponent.GetDotNetVersion(); document.thisForm.txtResult.value = s; } catch(e) { alert(e); } }
[/sourcecode]
Part of the above is copy/paste from another sample so don't mind the naming convention.
2. I am guessing here but I believe because we didn't 'install' the extension Firefox gives a security warning every-time the page is loaded. If you ask it not to bother you it won't in the future.
3. Click on the button and checkout the versions of CLR installed.
Well, sometime in the near future I hope to iron out the installer issues and submit this as a 'legal' extension to 'Mozilla'. If anyone takes the reference of this article and does it first, I will greatly appreciate a note of acknowledgment. Everything here is free for use (even commercially), just drop me a mail or put it in comments saying you used it. Any tips and trick on how to package this into a well behaving Firefox extension will also be highly appreciated.
Finally I repeat, everything here is AS IS. All trademarks acknowledged.