:::: MENU ::::

Bulk exporting Word documents to other formats with JavaScript

JavaScript is hot commodity for server and client side web development. But when it comes to command line programming it is unlikely to ditch the likes of Python and Ruby for JavaScript.

I don’t have the luxury of picking my toolset at work. I’m a business consultant with no access to anything beyond the essential enterprise software. Heck, I don’t even have access to PowerShell. All I have left with is JavaScript. Life is fun.

I assume that I’m not the only one facing this challenge, so it may be worth sharing how I do some basic automation at work using JavaScript.

Problem

The team needed to convert a big number (200+) of Word documents saved in different formats into a single format, DOTX to be specific.

Assumptions

You have access to a Windows machine with Microsoft Office 2010 installed.

Solution

  1. Open up a text editor, paste the code in Listing 1, and save the file as bulk_word_exporter.js.
  2. Set the value of sourcePath (line 10) to the folder containing the source files. Use double backslash to separate the folders.
    In my example different file types (DOC, DOCX, DOT, etc.) are stored in different source folders and that’s why extension is added to the sourcePath. Feel free to change this.
  3. Set the value of destPath (line 11) to the folder where you want the exported files to be saved. Create the folder if it doesn’t exist.
  4. open command line and run the script:
    CScript.exe bulk_word_exporter.js

Listing 1

alert = function(s) { WScript.Echo(s) }

var fso,
   folder,
	wordDoc,
	wordApp, 
	extension = 'DOT',
	fileFormat = 1, //see http://msdn.microsoft.com/en-us/library/office/ff839952.aspx
	sourcePath = "\\SOURCE\\PATH\\" + extension + "\\",
	destPath = "\\DESTINATION\\PATH\\";

try {
	fso = new ActiveXObject("Scripting.FileSystemObject");

	wordApp = new ActiveXObject("Word.Application");
	wordApp.Visible = false;

	folder = fso.GetFolder(sourcePath); 
	files = new Enumerator(folder.files);
	
   alert("Preparing ...");
	
   var fileURI, fileName;
	for(var files = new Enumerator(folder.files); !files.atEnd(); files.moveNext()) {
		fileURI = '' + files.item();
		fileName = fileURI.substr(fileURI.lastIndexOf("\\") + 1, fileURI.length - 
            fileURI.lastIndexOf("\\") -  (fileURI.length - fileURI.lastIndexOf(".") + 1));
		
      alert("exporting '" + fileName + "'...");
		
      wordDoc = wordApp.Documents.open(fileURI);
		
      alert("opened '" + fileName + "'...");
		
      wordDoc.SaveAs(destPath + fileName + "." + extension, fileFormat);
		wordDoc.close();
		
      alert("exported '" + fileName + "' to " + extension);
	}

	alert("\n\nDone!\n");

} catch (error) {
	alert(serialize(error));
} finally {
	//close handles and cleanup the memory
	fso = null;
	if (wordDoc) {
		wordDoc.Close(0);
	}
	if (wordApp) {
		wordApp.Quit();
	}
	wordDoc = null;
	wordApp = null;
	fso = null;
}

/* serialize function, thanks to http://blog.stchur.com/2007/04/06/serializing-objects-in-javascript/ */
function serialize(_obj)
{
   // Let Gecko browsers do this the easy way
   if (typeof _obj.toSource !== 'undefined' && typeof _obj.callee === 'undefined')
   {
      return _obj.toSource();
   }

   // Other browsers must do it the hard way
   switch (typeof _obj)
   {
      // numbers, booleans, and functions are trivial:
      // just return the object itself since its default .toString()
      // gives us exactly what we want
      case 'number':
      case 'boolean':
      case 'function':
         return _obj;
         break;

      // for JSON format, strings need to be wrapped in quotes
      case 'string':
         return '\'' + _obj + '\'';
         break;

      case 'object':
         var str;
         if (_obj.constructor === Array || typeof _obj.callee !== 'undefined')
         {
            str = '[';
            var i, len = _obj.length;
            for (i = 0; i < len-1; i++) { str += serialize(_obj[i]) + ','; }
            str += serialize(_obj[i]) + ']';
         }
         else
         {
            str = '{';
            var key;
            for (key in _obj) { str += key + ':' + serialize(_obj[key]) + ','; }
            str = str.replace(/\,$/, '') + '}';
         }
         return str;
         break;

      default:
         return 'UNKNOWN';
         break;
   }
}

This code utilizes Scripting.FileSystemObject library to access the file system:
13. fso = new ActiveXObject("Scripting.FileSystemObject");

It also uses Word.Application for opening and saving Word documents:
15. wordApp = new ActiveXObject("Word.Application");

Note: You can refactor and move the whole extension setting part to the command line arguments. That would be more elegant.

Bonus

Set extension to ‘PDF’ (line 7) and fileFormat (line 8) to 17  and run the code. It will bulk export a batch of Word documents to PDFs. This could be a life saver if you deal with managing information and records.

Other Tools of the Trade

Don’t forget about MS-Excel and MS-Access when you;re working on more serious automation projects. Both applications provide structured data storage and offer programming using VBA. You can utilize many libraries and do really cool things with these tools.

Efficiency as a goal

When my colleagues ask me how long it will take for me to automate job X by writing a script, I always ask them how long will it take if we do it manually?

Writing scripts and automating work is fun and rewarding, but some tasks, no matter how mundane, are done faster manually. The main goal here is to improve efficiency, not to write code. So always consider the faster route.

However, if you are about to repeat the same manual process a few times a month or even a few times a year, it is worth spending some time and automating it.

Another added value of automation is when you are not around, or when you’ve moved on (to a better position), others will still be able to automate mundane work thanking you for the years to come. Trust me, you’ll be thanked for some time.

 


World-class Customer Service

In mid October 2013, Apple started recalling mid-2012 Macbook Airs, due to a SSD problem that was apparently and isolated issue to Toshiba drives only.

I found out that my mid-2012 Macbook Air had a Toshiba SSD, so I booked a meeting at the Genius bar of my local Apple Store in Vancouver. The store is always packed with people fiddling with iPhones and iPads and occasionally Macs. Long story short, the Apple Genius handling my case told me that they don’t have any local stock left and I need to wait a couple of weeks. I agreed and he reserved a new 64GB SSD telling me that I’ll receive a call when the drive arrives.

Two weeks later I received a call from Apple Store that I missed. They wanted to let me know that since it took longer than usual for me to get the SSD replacement, they are giving me a brand new mid-2013 Macbook Air. I didn’t call them back until 4 or 5 days later.

A week later, wondering if my SSD has arrived, I called Apple Store. The nice guy on the other side of the line told me the story and the fact that in the meantime my SSD has arrived too. He gave me the choice of either replacing the 64GB SSD with a new one, or get the brand new Haswell Macbook Air with a 128GB SSD (the new minimum capacity). Guess which one I picked.

There is no doubt that I’m very happy and quite impressed with the way Apple handled my case. That’s not just because I got a sweet deal. They could have just hidden the fact that I could get a new replacement laptop for free. I wouldn’t have known and would have still walked away happy with a SSD replacement. It is safe to say that not everybody with a defected SSD got (or will get) a new laptop, but the point is that as a customer I got more than what I asked for and I won’t forget that.

I compare the service* I received from Apple since I bought my very first Mac in 2007 (when I practically threw my Vista workstation out of the window) to other vendors (who either pissed me off in the process or caused more damage than good), and I’m very happy with Apple’s customer service.

Apple is not perfect in customer service. No company is. No matter how hard you try as a business there will always be unhappy customers. For legitimate reasons or not, there is always somebody to complain. I have been on the other side of the customer service table and I promise you it’s no fun.

At the end of the day, if you stay true to your values and vision and deliver excellent customer service, the joy of seeing happy smiling customers is worth all the effort and pain. One way to guarantee this is to surprise customers with delivering more than what they have asked for. One caveat to this is that setting unrealistic expectations will bite you back in the future.

Looking back at my own side-businesses, I can come up with more than a handful of examples where I applied this strategy and received positive comments, thank-you letters, referrals and even more sales.

To sum up:

  • Deliver more than promised. This doesn’t necessarily mean adding a heap of extra features and free goodies. Sometimes a free month of membership, free expert advise, or just handling the situation well will do the trick. If you demonstrate that you are passionate about what you’re doing  clients will appreciate it, which could result in really good things for your business.
  • Deliver earlier than promised. Don’t over inflate the time requirements of the work, but clients usually appreciate if you finish a few days earlier. Set a realistic deadline and work toward delivering excellent work before that deadline.
  • Give an incentive if you are late. An extra month of support, a free added-value feature, and even a Starbucks card will often cool down the client if you are few days (or weeks depending on the size of the work) behind. This shows the client that you respect their time and money and acknowledge the delay.
    Try offering something that brings instant gratification. Promising something that the client can use in the remote future won’t have the same effect and will only add to your future commitments.

* This is not the first time I’m getting excellent support from Apple. Back in 2010 they’ve replaced the logic-board of my out-of-warranty 2007 Macbook Pro, no questions asked. In 2012 I got a free battery replacement for my wife’s 2008 Macbook Air.

 



Microsoft Surface – A big name for a small device

Back in 2008 when Microsoft revealed Surface in CES, there was much excitement around what this Minority Report age device could bring to the market. Now after 4 years of hopeful waiting, Microsoft comes up with yet another post-PC device, the Surface Tablet. Choosing a second-hand name and renaming the old thing is itself a curious matter.

I have to admit there is much to like about it, but it will need impressive Metro apps to become actually worth buying is what makes the next year or so quite interesting for Microsoft. Remember Blackberry Playbook?

Thinking positively, there are tribes of superstar .NET developers and Microsoft might be able to take back what Apple stole in the enterprise market with the recent iPad fever among executives. Office, Sharepoint, Dynamics, and other Microsoft flagship business software could really help Surface to get momentum. The enterprise deserves a nice and fully functional tablet that offers more than merely glits.

On the other hand, with the radically new Metro UI and having already both users and developers scared enough, we should just wait and see if this whole new “Cool Microsoft” revolution will steal the show or will be another Vista fiasco.

Speaking of fiasco, Microsoft competing with other vendors, could become an itchy situation between old friends who were buying the expensive and mostly underwhelming Windows OS for years.

At end of the day, I think the big dinosaur will survive. Microsoft looks at RIM as a cautionary tale (I hope). You can fail from trying too hard as much as you can fail from not trying at all (read RIM). I really hope that Microsoft pulls this one off successfully and get creative in their marketing department, who I suspect were hired from another galaxy.


How NOT to group your checkboxes

“Global Unsubscribe” shouldn’t be grouped with the others.

Here is to not making it clear for the user if you’re hiding an option on purpose or you’re just being lazy.

Based on the title, by checking “Global Unsubscribe” I actually want to receive “Global Unsubscribe”.


Pages:12345678