Backing up an Exchange Mailbox to a PST file

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!

Moving on from SVN_ASP_DOT_NET_HACK

I noticed that I was running TortoiseSVN in SVN_ASP_DOT_NET_HACK mode (where Subversion clients use ‘_svn’ instead of ‘.svn’ directories) unnecessarily as I don’t have silly old Visual Studio 2003 installed anymore which caused this mess in the first place.

The _svn directories work just as well as .svn, but nevertheless (due to mild OCD?), I created a batch script that ripped through my project directory renaming all ‘_svn’ directories to ‘.svn’, so I could remove the SVN_ASP_DOT_NET_HACK mode. It uses the wonderfully flexible FOR command drive the whole process. We don’t need no stinkin’ Powershell round these parts…

Save this script as something like “SvnRenameDirs.cmd” in the root of your projects folder:

:: Make script directory current
pushd "%~dp0"

:: Unhide, rename and re-hide svn dirs
for /r /d %%D in (*) do @if exist "%%D\_svn" (
   attrib -H "%%D\_svn"
   ren "%%D\_svn" ".svn"
   attrib +H "%%D\.svn"
)
popd

rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl,0,3

At the end click the Environment Variables button in the System Properties dialog and remove the SVN_ASP_DOT_NET_HACK environment variable and then log out and back in again (or restart explorer.exe and TSVNCache.exe).

System.Net.Mail: The specified string is not in the form required for a subject

Having your ASP.NET error handling routine, which sends you emails when an error occurs on your site, itself fail is annoying. Especially when you think you’ve made the code robust enough. Anyway the error handler for one site I work on was failing with “ArgumentException: The specified string is not in the form required for a subject“.

So what is exactly “the form required for a subject”? Googling for this error message returns a lot of junk and misinformed forum posts. It turns out that setting the Subject on a System.Net.Mail.Message internally calls MailBnfHelper.HasCROrLF (thank you Reflector) which does exactly what it says on the tin. Therefore one forum poster’s solution of subject.Replace("\r\n", " ") isn’t going to work when your have either a carriage return or line feed in there.

So, obviously, the solution is:

message.Subject = subject.Replace('\r', ' ').Replace('\n', ' ');

Personally, I think that the MailMessage should to this for you or at least Microsoft should document what actually constitutes a “form required for a subject” in MSDN or, even better, in the actual error message itself!

C# Extension Methods and IsNullOrEmpty

As Brad Wilson points out one of the nice features of C# 3.0′s extension methods is that they work on null instances. Indeed, why shouldn’t they? They’re just static methods that are invoked in a slightly different way. But this allows a subtle but very pleasing (to me this morning anyway) bit of syntactic sugar for the very commonly used String.IsNullOrEmpty() method.

So instead of:

if (string.IsNullOrEmpty(myString)) ...

you can have the more-readable:

if (myString.IsNullOrEmpty()) ...

by using:

public static class StringExtensions
{
   public static bool IsNullOrEmpty(this string str)
   {
      return string.IsNullOrEmpty(str);
   }
}

It’s a very small thing but I like it.

Draytek Vigor 2800 reboot script

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.

CTRL+TAB switching between document windows in Visual Studio

Visual Studio 2008 introduced a document switcher window that pops up when you do a CTRL+TAB. Nice idea, inspired by ALT+TAB application switching  but I find that the little previews it shows you are pretty useless. One code window looks like pretty much any other at that resolution. I prefer the previous behaviour of just immediately switching windows.

Sara Ford’s excellent series of Visual Studio tips prompted me to restore CTRL+TAB’s previous behaviour. You can do this by using the Keyboard binding page in Tools Options. In Tools > Options > Keyboard locate Window.NextDocumentWindowNav and remove the CTRL+TAB binding, then go to Window.NextDocumentWindow and assign CTRL+TAB there instead. Same drill for CTRL+SHIFT+TAB for Window.PreviousDocumentWindowNav and Window.PreviousDocumentWindow.