49 Comments

There have been some interesting developments recently in the field of CSS pre-processors. CSS pre-processors work around the very limited syntax of CSS by allowing you to create your stylesheets using a more powerful, expressive syntax which then gets turned into plain CSS for consumption by all the main browsers. A couple of ones that I've come across are SASS and LESS. LESS is the one I've felt most comfortable with because it's simply an augmentation of the CSS which allows you to incrementally introduce its syntax into your stylesheets.

For example LESS supports variables:

@brand_color: #4D926F;
#header {
   color: @brand_color;
}
h2 {
   color: @brand_color;
}

nested rules:

#header {
  color: red;
  a {
    font-weight: bold;
  }
}

...mixins, operations on colors and size, etc. Check out the  lesscss.org homepage for more.

LESS was originally a Ruby gem, but now also has implementations on ASP.NET (dotlesscss.org) and PHP. LESS has now (inevitably) been re-implemented in JavaScript by Alexis Sellier (github.com/cloudhead/less.js). As well as supporting pre-processing *.less files into *.css via the command-line, it can also be actually run live, directly in your browser!

<link rel="stylesheet/less" href="stylesheets/main.less" type="text/css" />
<script src="script/less-1.0.38.min.js"></script>

I'm not sure I'm quite ready to take the plunge running it live, in-browser on sites just yet, although maybe I shouldn't be according to Dmitry Fadeyev:

Wouldn't live processing lag? Not really. Two reasons for this. One: Less.js has been written from the ground up for great performance, so even browsers with poor JavaScript implementation should still run it very well (Less.js is about 40 times faster than the Ruby implementation—so suffice to say it's a lot faster.). Two: On modern browsers that support HTML5, Less.js will cache the generated CSS on local storage, making subsequent page loads as fast as pure CSS.

Running on Windows

The command-line compiler lessc is targeted to *nix-based platforms and requires node.js which, the last time I checked, doesn't run on Windows (UPDATE: it runs fine under Cygwin, there's a nice simple standalone version here). But Windows has had the ability to run JavaScript directly since the 1990's using Windows Script Host. So I took the latest distribution copy of less.js from GitHub at https://github.com/cloudhead/less.js/tree/master/dist/ and included it via a *.wsf file and stubbed/faked/implemented a few things like window, document and XMLHttpRequest that less.js assumes will be present (which are presumably provided by node.js?), and added a bit of command-line argument handling.

Download

Browse the code and download from GitHubhttps://github.com/duncansmart/less.js-windows and feel free to fork it and send me pull requests!

Usage

To use it, invoke lessc.wsf via cscript.exe like so:

cscript //nologo lessc.wsf input.less [output.css] [-compress] 

I've also added a less.cmd shim which will simplify it to just:

lessc input.less [output.css] [-compress] 

If you don't specify an output.css the result is written to standard output. The -compress option minifies the output. I'll look into implementing the other command-line arguments supported by lessc in due course.

I've added a couple of test files, so you can see if it's working like so:

C:\code\lessc-wsh>lessc test.less
/* Variables */
#header {
  color: #4d926f;
}
h2 {
  color: #4d926f;
}
/* Mixins */
#header {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px;
}
...

Future

I may look into hooking this into Visual Studio's T4 support so that the *.css files are generated as soon as you save the *.less files in Visual Studio. (UPDATE: See Phil Haack's T4CSS that does this, instead using  the .NET port of LESS. Thanks to David De Sloovere for pointing this out.)

Update

Mark Lagendijk has created a nice little Windows GUI for the script. Check it out at http://winless.org.

Follow @duncansmart on Twitter

0 Comments

As part of our logon script we have a Windows Script Host script that was failing with “Provider: Unspecified error” (mmn, helpful) but only on some Windows XP machines.

The offending line of code looked like this:

rs = con.Execute("<LDAP://DC=example,DC=com>; (sAMAccountName="+ username +"); ADsPath; subTree")

After some trial and error the fix was this (see if you can spot the difference):

rs = con.Execute("<LDAP://DC=example,DC=com>;(sAMAccountName="+ username +");ADsPath;subTree")

Can you see what it is? No spaces after the semicolons!

Alternatively the SQL-like syntax also seems to be a bit more forgiving:

rs = con.Execute("SELECT ADsPath FROM 'LDAP://DC=example,DC=com' WHERE sAMAccountName='"+ username +"'")

It appears that with some later version of ADSI ADsDSOObject (or to give it its full title, the “OLE DB Provider for Microsoft Directory Services”) the query syntax strictness has been relaxed. In any case "Unspecifed error" appears to mean "Syntax error" in this case.

11 Comments
UPDATE: Logan Buesching commented to say that Chrome apparently creates "ChromeHTML" shell\open keys except they're in HKCR (which is why you get a UAC prompt, it's the machine part of the registry), so you can actually just set HKCU\Software\Classes\.htm to ChromeHTML and you're done. Logan also goes into more detail as to what's going on so check out his post. It seems odd that they end up creating a global HKCR key which ulitimately points to an app that's installed in a user's private profile. I'm sure this will cause issues if you have multiple people using your machine. Anyway, I guess this is all a bit moot: this is beta software kids, and I'm sure Google will fix this in due course by release and you may have to undo some of these registry shenanigans for it to work as expected.

Google Chrome was a big hit in the office today. To the extent that many of my colleagues were setting it as their default browser already, even though it's a beta product. Cwazy.

Unfortunately even if you do click the "Make Google Chrome my default browser" button on the Options page, not all applications that launch hyperlinks comply. One of those is the Twitter client Twhirl, which is an Adobe AIR application (I'm assuming this is an issue with AIR itself rather than Twhirl doing something silly). I deduced what AIR was doing when trying to locate the default browser by using Sysinternals' Process Monitor (ProcMon). It was using the current user's ".htm" file association preference: which on my machine was pointing to FirefoxHTML. So I created a new registry key for GoogleChromeHTML that specified the location of chrome.exe as the file opener and pointed the ".htm" setting there, which did the trick.

To make this easier to replicate, and to save having to write tedious explanatory steps detailing exactly what to do - I've created a short JScript Windows Script file.

Save the following with a "*.js" file extension (e.g. ChromeDefaultForAIR.js) and run it:

var wshell = new ActiveXObject('WScript.Shell');
var chromePath = wshell.ExpandEnvironmentStrings('%USERPROFILE%\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe');
wshell.RegWrite('HKCU\\Software\\Classes\\.htm\\',
   'GoogleChromeHTML');
wshell.RegWrite('HKCU\\Software\\Classes\\GoogleChromeHTML\\shell\\open\\command\\',
    '"' + chromePath + '" "%1"');

Normal caveats for editing your registry and downloading and running random scripts from some idiot's blog apply. To revert, set HKCU\Software\Classes.htm back to FirefoxHTML.

If it helps leave a note in the comments!

0 Comments

I've never trusted Exchange Server backup 100% ever since Exchange 2000, following a service pack, refused to restore backups from the non-service packed version (yes honestly).

So I've always had a 2-pronged approach to backup, do the usual monolithic backup using NTBackup, but also have mailboxes individually backed up as plain old PST files, which Outlook can easily mount to make it easier to do partial restores. In the past I've used ExMerge to do this, but it's a becoming a bit neglected and is very clunky to script: it's a Windows app (rather than a console app) that's driven by an INI file.

Anyway, whilst doing some integration work against Exchange for a client I came across Dmitry Streblechenko's superb Redemption Data Objects library. It is a really easy to use COM wrapper around Extended MAPI - a super-charged version of Collaboration Data Objects (CDO).

Honestly I really can't understand why Microsoft didn't ship a library like this (or improve CDO) rather than expecting you to write gnarly C++/COM/MAPI code to do what this library allows you to do easily from .NET code or a script. The Exchange API goalposts move from one release to the next: "Use the M: drive! No, use WebDAV! No, use ExOLEDB!... No, use Web Services!" with the only constant being good old MAPI.

Anyway - here's part of our PST backup script - it relies on the Redemption Data Objects (RDO) COM DLL being registered. The free developer version pops up a prompt once when you RegSvr32 it, the royalty-free redistributable version is a incredibly reasonable $199.99. RDO relies on MAPI being installed, so grab it here if it's not present on your system.

/* BackupPst.js */

// e.g. copyMailboxToPst(
           "EXCH01",
           "FredB",
           "c:\\backups\\fredb.pst",
           "FredB backup")

function copyMailboxToPst(serverName, userName, pstFile, pstName)
{
  var session = new ActiveXObject("Redemption.RDOSession");
  session.LogonExchangeMailbox(userName, serverName);
  WScript.Echo("Logged on to " + session.ExchangeMailboxServerName);

  var mailbox =  session.Stores.DefaultStore;
  var pstStore = session.Stores.AddPSTStore(pstFile, 1, pstName);

  try
  {
    WScript.Echo("Opened " + mailbox.IPMRootFolder.FolderPath);
  }
  catch(err)
  {
    WScript.Echo("Error opening mailbox '" + userName
       + "' (access denied?). " + err.description);
    return;
  }

  foreach(mailbox.IPMRootFolder.Folders, function(folder)
  {
    WScript.Echo(" * " + folder.Name);
    folder.CopyTo(pstStore.IPMRootFolder);
  });

  pstStore.Remove();
}

// Utility to allow enumeration of COM collections
function foreach(collection, fn)
{
  for(var e = new Enumerator(collection); !e.atEnd(); e.moveNext())
  {
    if(fn(e.item()))
      break;
  }
}

This could be further improved for incremental backups by using RDO's newly introduced wrappers to the "Incremental Change Synchronization" API where you can use the same syncing technology that Outlook's cached Exchange mode uses!

31 Comments

After years of great service, our Draytek Vigor 2800 seems to be having problems after running for a few days of allowing HTTPS in to our network, which is only solved by rebooting the router. For the life of me I can't upgrade the firmware using either the TFTP or FTP approach. So giving up, I've adopted the brute force approach of scheduling a reboot every night. To do this I considered scripting the appropriate Telnet commands to the router, but that would involve some custom or third party code. (Telnet, being a duplex protocol means you can't simply pipe commands to telnet.exe from a batch file, you have to use something like expect to script a Telnet session.)

The simplest, lowest-tech approach (and they're always the best, right?) I found was merely to mimic the reboot page on the router web management UI with the following Windows Scripting Host script:

////// RebootRouter.js ////////
// Change these for your environment
var ROUTER_IP = "192.168.0.1";
var ROUTER_USER = "admin";
var ROUTER_PASSWORD = "pA55w0rd";

var http = new ActiveXObject("Microsoft.XMLHTTP");
http.open("POST", "http://" + ROUTER_IP
    + "/cgi-bin/reboot.cgi", false,
    ROUTER_USER, ROUTER_PASSWORD);
http.setRequestHeader("Content-Type",
    "application/x-www-form-urlencoded");
http.send("sReboot=Current&submit=OK");

// For debugging/logging un-comment the following lines:
// WScript.Echo(http.status + " " + http.statusText);
// WScript.Echo(http.responseText);

Save this as RebootRouter.js, update the values accordingly and schedule for a appropriate time using Control Panel > Scheduled Tasks.