Scott Hanselman

How to Remove the .NET ClickOnce Firefox Extension

June 05, 2009 Comment on this post [10] Posted in Microsoft | Open Source
Sponsored By

.NET 3.5 SP1 enables a Firefox Extension that allows .NET applications on Firefox, but its Uninstall button is grayed out.  Since .NET 3.5 SP1 is on Windows Update and is rolling out to more users than ever before, a number of people have noticed this and are frustrated.

Why is the Uninstall button grayed out? What does this add-in do?

Short Version: Remove the .NET Firefox Extension

First: To uninstall completely, if you're running XP or Vista, here's an update that will make the .NET ClickOnce Extension/Addin easily uninstallable by adding on a per-user, rather than per-machine basis. This update will also go out on Windows Update if you prefer to wait. Alternatively, you can click Disable and it won't be loaded.

Longer Version: Some Background and Details

This addin allows users to launch .NET applications from Firefox. The primary issue is that the plugin is enabled at the machine level, rather than the user level. This has the benefit of enabling the Addin for all users, but it as the unfortunate side effect of making the "Uninstall" button grayed out which makes people feel that something sinister has happened or they've lost control.

There are other plugins that use this technique to enable themselves machine-wide, including Google Gears and Java, but I totally understand that some folks don't want certain Firefox Addins enabled.

image

image

This is a tricky situation, because you'll notice that many applications enable browser plugins in similar ways, including, but not limited to:

  • Java
  • VLC Player
  • Adobe Acrobat
  • QuickTime
  • Google Talk
  • iTunes
  • Hulu
  • Picasa

There are lots of other applications that include their own browser addins, many install this same way. Most of these addins are in the "Plugins" tab, rather than the Extensions tab, which might explain why fewer people freak out. 

My point here is that the "Extensions" tab is something that Firefox users, myself included, feel strongly about, while they think less about plugins. Extensions often affect UI and make Firefox personalizable, while Plugins usually enable system-level functionality. It's a subtle difference, but it's significant and important to remember. Extensions are personalization points for Firefox at the User level, and are thought of as such. Some addins (not ours) mark themselves as Hidden, which could be perceived as even more sinister.

Ultimately since many people requested .NET support for Firefox, and we wanted to give it to them, we made this feature. When .NET was updated, this feature was included.

The Future

Here's some more detail and some insight into what we're doing about it.

  • First, this is already fixed in Windows 7 RC. You can uninstall this addin, no problem.
  • Second, for XP and Vista, the update above makes this add-in per-user so it can be easily uninstalled like any other Firefox addin.
    • This update will go out on Windows Update automatically soon. That means. if you already have this add-in, the only visual difference for you will be that the uninstall button will be enabled and you can remove it if you like.
  • Going forward, for .NET 4, the team wants to host this plugin at https://addons.mozilla.org. If you want it you can go get it. If not, no worries. We're still checking on this.

All the Technical Details

In case you want to know the technical details, and in the interest of transparency, it's called "MicrosoftDotNetFrameworkAssistant.xpi." You can rename it to .zip and unzip it. It's got XUL and JavaScript inside - it's just a zip file. If you have made a Firefox add-on, this will look familiar. There's an options dialog, standard stuff, keeping track of two booleans as preferences:

var dotNetAssistantOptions = 
{
onAccept: function()
{
var prefs = this.getPrefs();

var autoLaunchChk = document.getElementById("autolaunch");
prefs.setBoolPref("clickonce.autolaunch", !autoLaunchChk.checked);

var allVersionsChk = document.getElementById("allversions");
prefs.setBoolPref("all_clr_versions_in_useragent", allVersionsChk.checked);

// Update the user-agent if preferences changed
checkCLRVersionString();
return true;
},

onLoad: function()
{
var prefs = this.getPrefs();

var autoLaunchChk = document.getElementById("autolaunch");
autoLaunchChk.checked = !prefs.getBoolPref("clickonce.autolaunch");

var allVersionsChk = document.getElementById("allversions");
allVersionsChk.checked = prefs.getBoolPref("all_clr_versions_in_useragent");
},

getPrefs: function()
{
return Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("microsoft.CLR.");
}
}

There's a contentType handler. This is the meat of it. We see if the mime/type being requested is "application/x-ms-application," indicating the user is trying to launch a .NET ClickOnce application. We then do some path stuff and launch our "sandbox" application that hosts these apps.

var clickOnce = {

dialog_onload: function()
{
// This is the function that would have been called had our overlay not stepped in.
dialog.initDialog();

if (this.isClickOnce())
{
this.prefs = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("microsoft.CLR.clickonce.");

if (this.prefs.getBoolPref("autolaunch"))
{
this.launch_application();
}
else
{
var button = dialog.dialogElement("launchApplicationButton");
button.collapsed = false;
// If the user has both this addon and FFClickOnce installed show only our button
var ffClickOnceButton = dialog.dialogElement("FFClickOnce-runbutton");
if (ffClickOnceButton)
{
ffClickOnceButton.collapsed = true;
}

// Let the dialog know it must resize to accommodate the new button
dialog.mDialog.sizeToContent();
}
}
},


isClickOnce: function()
{
var mimeInfo = dialog.mLauncher.MIMEInfo;

// Checking the file extension may be second guessing the webserver
// but when 'prompt' is set the user will still have a save button.
if (mimeInfo.MIMEType == "application/x-ms-application" ||
mimeInfo.primaryExtension == "application")
{
return true;
}

return false;
},


launch_application: function()
{
this.execute(this.getPresentationHostLocation(), "-LaunchApplication " + dialog.mLauncher.source.spec);

dialog.mDialog.close();
},

getPresentationHostLocation: function()
{
var nsIProperties = Components.classes["@mozilla.org/file/directory_service;1"]
.getService(Components.interfaces.nsIProperties)

// Attempting to forward-proof this extension by testing for where PresentationHost
// will probably be for CLR 4
var phNewLoc64 = nsIProperties.get("WinD", Components.interfaces.nsILocalFile);
phNewLoc64.appendRelativePath("Microsoft.NET\\Framework64\\wpf\PresentationHost.exe");
if (phNewLoc64.exists())
{
return phNewLoc64;
}

var phNewLoc32 = nsIProperties.get("WinD", Components.interfaces.nsILocalFile);
phNewLoc32.appendRelativePath("Microsoft.NET\\Framework\\wpf\PresentationHost.exe");
if (phNewLoc32.exists())
{
return phNewLoc32;
}

// Fallback to the default location
var phOldLoc = nsIProperties.get("SysD", Components.interfaces.nsILocalFile);
phOldLoc.appendRelativePath("PresentationHost.exe");
return phOldLoc;
},

execute: function(exe, strArgs)
{
// exe should already be an nsILocalFile

// create an nsIProcess
var process = Components.classes["@mozilla.org/process/util;1"]
.createInstance(Components.interfaces.nsIProcess);
process.init(exe);

// Run the process.
// If first param is true, calling thread will be blocked until
// called process terminates.
// Second and third params are used to pass command-line arguments
// to the process.
var args = strArgs.split(" ");
process.run(false, args, args.length);

}

}

There's also a UserAgent module that will optionally append the .NET CLR version to the UserAgent so that websites can detect and show content based on

// Check for updated .NET Framework install on browser startup
checkCLRVersionString();

function checkCLRVersionString()
{
var useragentExtrasPref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("general.useragent.extra.");

useragentExtrasPref.QueryInterface(Components.interfaces.nsIPrefBranch2);

// startup check the registry for the latest version of the CLR added to the IE user agent
var versions = getInstalledCLRVersions();

var addonPref = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService)
.getBranch("microsoft.CLR.");
addonPref.QueryInterface(Components.interfaces.nsIPrefBranch2);
var allAddonsInAgent = addonPref.getBoolPref("all_clr_versions_in_useragent");
// Allow the user to configure whether they want all version of the CLR in their user agent
// or just the highest.
if (allAddonsInAgent)
{
var appendString = "(";
for (var i=0; i < versions.length; i++)
{
if (i != 0)
{
appendString += "; ";
}
var localVersion = versions[i];
appendString += ".NET " + localVersion[0] + " " + localVersion[1];
}
appendString += ")";

useragentExtrasPref.setCharPref("microsoftdotnet", appendString);
}
else
{
var latestVersion = versions[versions.length-1];
var appendString = "(.NET " + latestVersion[0] + " " + latestVersion[1] + ")";
useragentExtrasPref.setCharPref("microsoftdotnet", appendString);
}
}

function getInstalledCLRVersions()
{
var wrk = Components.classes["@mozilla.org/windows-registry-key;1"]
.createInstance(Components.interfaces.nsIWindowsRegKey);

// Enumerate though the CLR version strings that will be appended to the Internet Explorer user agent
wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE,
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\5.0\\User Agent\\Post Platform",
wrk.ACCESS_READ);

var clrVersions = new Array();
for (var i=0; i < wrk.valueCount; i++)
{
var name = wrk.getValueName(i);
var matches = name.match("\.NET (CLR|Client) \(.*\)");
if (matches && matches.length > 1)
{
clrVersions.push([matches[1], matches[2]]);
}
}
wrk.close();

clrVersions.sort(function(left, right)
{
if (left[1] < right[1]) return -1;
if (left[1] > right[1]) return 1;
// if version numbers are equal, prefer full CLR over Client sku
if (left[0] == "Client" && right[0] == "CLR") return -1;
if (left[0] == "CLR" && right[0] == "Client") return 1;
return 0;
});
return clrVersions;
}

That's it. Hope this helps. I'll keep my ear to the ground if anything else comes up, so stay tuned.

Related Links

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
June 05, 2009 12:20
I'm the one using FF, I use FF only because it is immunize to most of the virus.
June 05, 2009 13:55
> There's also a UserAgent module that will optionally append the .NET CLR version to the UserAgent

So how do I "optionally" turn the UserAgent CLR string off? The only option I see is to show the latest or all CLR versions.
June 05, 2009 14:01
Secondly, there's a bug in the Add-In options dialog. Here's how to repro:

1) Go to about:config, type "microsoft" into the Filter box to see the relevant config options.
2) Change general.useragent.extra.microsoftdotnet to another value by double clicking on it
3) Open the Add-ons window, find the Microsoft .NET Framework Assistant and click Options to show the Options dialog

The general.useragent.extra.microsoftdotnet setting will have been changed without you confirming anything in Options dialog. This value should only change when you click OK. (Also notice it doesn't revert to it's previous value when you click "Cancel")
June 05, 2009 18:24
You say that the Microsoft .NET extension is similar to extensions from Java, Adobe Acrobat, QuickTime, Google Talk, Picasa, etc. But the machine versus user install isn't really the point, now is it?

What feels like a sneaky practice is that Windows Update installs this extension to Firefox without me requesting it, while I have to specifically request the other apps you're comparing.

This weekend I installed the Windows 7 release candidate and Firefox, then ran Windows Update. In addition to operating system patches, I was surprised to see that the .NET Firefox extension was quietly installed.

I don't think Microsoft should touch Firefox unless I request it.
mb
June 05, 2009 22:58
RichB - looks like if the addin is enabled, then it'll always report at least ONE framework version. If it doesn't report versions, then it can't do its job, so then you'd want to disable it. Odd bugs, I'll report those. Take a look at the code above...that's not how it's written. Notice the "onaccept" and "onload" events. Perhaps what you're seeing is an effect of the about:config page throwing events?

mb - I agree. The extension was installed with the 3.5 Framework. It should be an opt-in thing and that's why I'm working to get this fixed. The add-in should be hosted where all other add-ins are hosted. The reason I point out the other plugins is that, for example, Picassa never pops up a dialog that says "I'm about to install a Firefox add-in, continue?" To be clear, that's no excuse. It's just a comment that it's more common that one would think; it's not just Microsoft.
June 06, 2009 11:14
Great writeup, and good to see it's not really as bad as its rumor, still the fear that followed this addin should have been foreseen, and I guess for many people (me included) having an addin for FireFox that can run Silverlight (ActiveX2) sneak install itself doesn't really reduce fear ...

-3 points to Microsoft...

-1 for sneak install
-1 for Silverlight association
-1 for machine wide install (and hence un-installable)
June 06, 2009 12:13
Thomas - Just an FYI, Silverlight isn't related to this add-in in any way. This is only .NET related, not Silverlight-related. So, -2 points. And, as I said, soon it'll be uninstallable, and soon it won't be "sneaky." I'm trying to get folks to see the light. ;)
June 09, 2009 6:42
* "This update will go out on Windows Update automatically soon. That means. if you already have this add-in, the only visual difference for you will be that the uninstall button will be enabled and you can remove it if you like."

Communication ahead of time would be nice as a FYI.
June 26, 2009 1:54
Extension still displays after using the 'uninstall' button in firefox.
Check extension.rdf and extension.cache. As well it will still exist in prefs.js.
Using http://support.microsoft.com/kb/963707 and deleting the folder causes firefox to hang on start.
Deleting registry key isn't performed with 'uninstall' button. Manually removing the key and rebuilding the extension.rdf and extension.cache files is necessary.
July 22, 2009 1:31
The NET extension is installed even if Firefox is not present (I think). I have used IE until recently, when I decided to switch to FF 3.5 (thank you Mozilla's guys for that little jewel). After the installation of FF, the NET extension was already here, greyed (as well as Norton stuff).

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.