Although this is technically a blog, it's primary content is a series of articles on how to get Firefox working in a corporate Windows environment. Later ones build on earlier ones, so you might want to use the Table of Contents on the right to read through it chronologically instead of reading straight down from here.

Configuring user.js From a Login Script

Now we need to be able to push out settings to Firefox from the server.  There are two ways to do this.

The first is to use FrontMotion Firefox Community Edition, as that bakes in support for group policy.  Frankly, that's a superior way to handle the built in settings because my method has some bad side effects.  One is that it only changes on login as opposed to whenever the policies refresh, and two is that it removes the ability to import IE favorites the first time Firefox starts.

Why do I use my way anyway?  Because I need to be able to configure the extensions centrally as well, not just Firefox.  PlainOldFavorites adequately takes care of the IE import issue.

The script below is used to configure XMarks via user.js, which is what this is all leading up to.  You can easily strip out the XMarks settings and substitute your own.  Save it as a JS file (i.e. SetupFirefox.js) and set it as a login script via group policy.

Here's a walkthrough of what it does:
  • Finds Firefox since it can be in a few possible locations.
  • Gets the path of every existing Firefox profile for the logged in user, assuming all are in the default location.
  • If there aren't any profiles, creates one.  This is important because Firefox uses randomized profile folder names.  If the user has never opened Firefox yet, we can't generate user.js for it because we don't know where it should go.  We don't want their first use of Firefox to be unconfigured until their next login, so we use "firefox.exe -CreateProfile" to force it.  This is invisible to the user, a Firefox window won't open.  However, it does prevent the IE favorites import dialog from appearing on the first open which is why we needed PlainOldFavorites.
  • Generates user.js for all profiles, new or preexisting.  Any existing user.js will be overwritten.
  • Optionally copies extensions from their install location to the user profile if that's required for the extension to work.
var fs = new ActiveXObject("Scripting.FileSystemObject")
var network = new ActiveXObject("WScript.Network");

var shell = new ActiveXObject("WScript.Shell");
var env = shell.Environment("PROCESS")

var userName = network.UserName.toLowerCase();
var computerName = network.ComputerName.toLowerCase();



// Section: Configuration
// ___________________________________________________________________________
//
// Remember that any backslashes in strings have to be doubled, such as
// "C:\\Folder1\\Folder2" or "\\\\ServerName\\Share".


/* var: firefoxLocations
   An array of possible places to find Firefox.exe.  Add more if you need them.
*/
var firefoxLocations = new Array(
   "C:\\Program Files\\Mozilla Firefox\\firefox.exe",
   "C:\\Program Files (x86)\\Mozilla Firefox\\firefox.exe"
   );


/* var: webDAVURL
   The URL of your WebDAV server that XMarks is going to sync with.  Don't
   include a slash on the end.
*/
var webDAVURL = "http://firefoxbookmarks:8082";


/* var: firefoxSettings
   An array of key-value pairs specifying the settings you want included in
   user.js.  For example:

   : var firefoxSettings = new Array(
   :    "sample.boolean.setting", false,
   :    "sample.integer.setting", 0,
   :    "sample.string.setting", "value"
   :    );
*/
var firefoxSettings = new Array(
   "extensions.xmarksbyos.syncOnShutdownAsk", false,
   "extensions.xmarksbyos.url-bookmarks", webDAVURL + "/" + userName + "-bookmarks.json",
   "extensions.xmarksbyos.url-passwords", webDAVURL + "/" + userName + "-passwords.json",
   "extensions.xmarksbyos.username", userName,

   "extensions.xmarks.syncOnShutdown", 1,
   "extensions.xmarks.syncOnShutdownAsk", false,
   "extensions.xmarks.useOwnServer", true,
   "extensions.xmarks.url-bookmarks", webDAVURL + "/" + userName + "-bookmarks.json",
   "extensions.xmarks.url-passwords", webDAVURL + "/" + userName + "-passwords.json",
   "extensions.xmarks.username", userName
);


/* var: ieTabRules
   An array of *regular expressions* to apply as filters with the IE Tab Plus
   extension.  If defined, it will create the coral.ietab.rulelist entry in
   firefoxSettings for you automatically.  Remember to double any backslashes.
*/
var ieTabRules = new Array(
   "http://*update.microsoft.com/*",
   "http://www.windowsupdate.com/*"
   );


/* var: ieViewURLs
   An array of URL prefixes to force to open in IE with the IE View extension.
   If defined, it will create the ieview.forceielist entry in firefoxSettings
   for you automatically.  These are simple strings instead of regular 
   expressions which may be easier to use.  You'll need separate entries for
   URLs with and without "www.", and for URLs under http and https.
*/
var ieViewURLs = new Array(
   );


/* var: copyExtensions
   Some extensions need to be in the profile directory to work.  Sigh.  This
   is an array of value pairs where the first is the directory where the
   extension resides as installed by the MSI, and the second is the name of
   the subfolder in Firefox\Profiles\xxx\extensions they should be copied to.
   For example:

   : var copyExtensions = new Array(
   :    "C:\\Program Files\\PlainOldFavorites", "{7E7165E2-0767-448c-852F-5FA8714F2C37}"
   :    );

   The contents will be copied over on each run.
*/
var copyExtensions = new Array(
   );



// Section: Code
// ____________________________________________________________________________
//
// You don't have to worry about anything below this point.


// Find Firefox

var firefoxLocation = undefined;

for (var i = 0; i < firefoxLocations.length; i++)
   {
   if (fs.FileExists(firefoxLocations[i]))
      {  
      firefoxLocation = firefoxLocations[i];
      break;
      }
   }

if (firefoxLocation === undefined)
   {  WScript.Quit();  }



// Create profile parent folder if it doesn't exist

var profileParentFolder = env.Item("appdata") + "\\Mozilla\\Firefox\\Profiles";

if (!fs.FolderExists(profileParentFolder))
   {  
   var pathSoFar = env.Item("appdata");
   var pathPieces = new Array("Mozilla", "Firefox", "Profiles");

   for (var i = 0; i < pathPieces.length; i++)
      {
      pathSoFar += "\\" + pathPieces[i];

      if (!fs.FolderExists(pathSoFar))
         {  fs.CreateFolder(pathSoFar);  }
      }
   }


// Get all the existing profile folder paths

var profileParentFolderObject = fs.GetFolder(profileParentFolder);

var profileFolders = new Array();
var profileEnum = new Enumerator(profileParentFolderObject.SubFolders);

for (; !profileEnum.atEnd(); profileEnum.moveNext())
   {
   profileFolders.push(profileParentFolder + "\\" + profileEnum.item().Name);
   }


// Create a profile if there isn't one already.

if (profileFolders.length == 0)
   {
   // 5 - Activate the window, leave its size alone
   // true - Wait for the command to finish to return.
   shell.Run("\"" + firefoxLocation + "\" -CreateProfile \"Default " + profileParentFolder + "\\Default\"", 5, true);
   profileFolders.push(profileParentFolder + "\\Default");
   }


// Generate rule lists if ieTabRules are defined.

if (ieTabRules != undefined && ieTabRules.length > 0)
   {
   // coral.ietab.rulelist

   var ruleList = "[";

   for (var i = 0; i < ieTabRules.length; i++)
      {
      if (i > 0)
         {  ruleList += ",";  }

      ruleList += "\\\"0," + ieTabRules[i] + "\\\"";
      }

   ruleList += "]";

   firefoxSettings.push("coral.ietab.rulelist", ruleList);


   // extensions.ietab2.filterlist

   var ruleList = "";

   for (var i = 0; i < ieTabRules.length; i++)
      {
      if (i > 0)
         {  ruleList += " ";  }

      ruleList += ieTabRules[i];
      }

   firefoxSettings.push("extensions.ietab2.filterlist", ruleList);
   }


// Generate ieview.forceielist if ieViewURLs are defined.

if (ieViewURLs != undefined && ieViewURLs.length > 0)
   {
   var forceList = "";

   for (var i = 0; i < ieViewURLs.length; i++)
      {
      if (i > 0)
         {  forceList += " ";  }

      forceList += ieViewURLs[i];
      }

   firefoxSettings.push("ieview.forceielist", forceList);
   }


// Generate user.js for all profiles

for (var i = 0; i < profileFolders.length; i++)
   {
   // true - overwrite if exists
   var userFile = fs.CreateTextFile(profileFolders[i] + "\\user.js", true);

   for (var s = 0; s < firefoxSettings.length; s += 2)
      {
      var settingLine = "user_pref(\"" + firefoxSettings[s] + "\", ";
      
      if (typeof(firefoxSettings[s+1]) == "string")
         {  settingLine += "\"" + firefoxSettings[s+1] + "\");";  }
      else if (typeof(firefoxSettings[s+1]) == "number" || typeof(firefoxSettings[s+1]) == "boolean")
         {  settingLine += firefoxSettings[s+1] + ");";  }
      else
         {  settingLine = "// Can't create setting \"" + firefoxSettings[s] + "\" for type " + typeof(firefoxSettings[s+1]) + ".";  }
      
      userFile.WriteLine(settingLine);
      }

   userFile.Close();

   if (copyExtensions != undefined && copyExtensions.length > 0)
      {
      var profileExtensionFolder = profileFolders[i] + "\\extensions";

      if (!fs.FolderExists(profileExtensionFolder))
         {  fs.CreateFolder(profileExtensionFolder);  }

      for (var e = 0; e < copyExtensions.length; e += 2)
         {
         if (fs.FolderExists(profileExtensionFolder + "\\" + copyExtensions[e+1]))
            {  fs.DeleteFolder(profileExtensionFolder + "\\" + copyExtensions[e+1]);  }

         if (fs.FolderExists(copyExtensions[e]))
            {  fs.CopyFolder(copyExtensions[e], profileExtensionFolder + "\\" + copyExtensions[e+1], true);  }
         }
      }
   }
Updated January 10, 2012:  Added the copyExtensions array for extensions that need to be in the user profile.  Also made the ieTabRules array generate both coral.ietab.rulelist and extensions.ietab2.filterlist properties.

Updated June 3, 2011:  Added settings for XMarks in addition to XMarks BYOS, which is needed to work with Firefox 4.

Updated June 9, 2010:  The settings are now specified at the beginning of the script in firefoxSettings. You don't have to deal with the code at the end of the script anymore. Also added arrays for the IE Tab Plus and IE View rule lists so they can be specified more easily.

Updated July 27, 2012:  Added computerName variable in case you wanted to push out settings per-computer.

3 comments:

  1. Is there a way to deploy add-ons (xpi's) via this login script?

    ReplyDelete
  2. No. To do that you would need to write a script to replicate what the MSI is doing: copying the unpacked files somewhere and then adding a registry key to point Firefox at it.

    ReplyDelete
  3. AutoConfig is also interesting:

    https://developer.mozilla.org/en-US/docs/MCD,_Mission_Control_Desktop_AKA_AutoConfig

    ReplyDelete