Windows DevCenter    
 Published on Windows DevCenter (http://www.windowsdevcenter.com/)
 See this if you're having trouble printing code examples


Batch-Running Word Macros from the DOS Command Line

by Andrew Savikas, author of Word Hacks
03/22/2005

When processing manuscripts for shiny new O'Reilly books, I often need to run a particular Word macro on a batch of files. While this is certainly possible using VBA directly, it becomes quite challenging when either the name of the specific macro to run (it may be one of dozens of utility macros), or the files to run it on, are constantly changing, as is usually the case.

Spoiled by the large percentage of my day spent on a Unix command line, I started looking for a way to easily run any Word macro, on any number of files, right from the DOS command line. This article shows how to do just that, using three popular, free, and Windows-friendly scripting languages: Perl, Python, and Ruby. You'll need at least one of those installed on your Windows machine to use any of the code in this article. If you don't have one, see the sidebar, "Picking a Scripting Language."

Getting Started

I wanted a script that used the name of the macro to run as its first argument, and the files to run that macro on as the remaining arguments, like this:


> batchmacro MyMacroName *.doc

The Ruby version is the one I actually use, but the Perl and Python versions work just as well. To try out any (or all) of these scripts, put them in the same folder as one or more Word documents. (For now, avoid documents with spaces in their names.) For illustration purposes, you should also create the following macro in your Normal template:


Sub HelloWorld()
  ActiveDocument.Range.InsertBefore "Hello from the command line!" & vbCr
  ActiveDocument.Paragraphs.First.Style = wdStyleHeading1
End Sub

Picking a Scripting Language

If you haven't yet declared allegiance to a particular language (or been forced to after inheriting legacy tools), you've got a choice to make, at least if you want to try out the code in this article. Fortunately, there's no harm in choosing all three, but there are a few important considerations if you'll be scripting in Windows. Here's the skinny on the big three:

  1. Perl. Perl is the darling of system administrators and webmasters the world over. Unmatched in its text-processing skills, its already painful syntax is downright torturous when married with COM. I install Perl on any machine I use, but for COM scripting, it's definitely my last choice. Get Perl for free from ActiveState.

  2. Python. This is a fine choice for COM scripting and is a much easier transition for those already used to the "dot" syntax found in VB and .NET. Get Python for free from ActiveState.

  3. Ruby. If you haven't been converted to the Perl or Python camps (by a "Perl Monk" or a "Pythonista," respectively), you should definitely consider Ruby. It's the newest of the three and may well live up to its billing as "a better Python than Python, and a better Perl than Perl." Using Ruby for COM scripting is a pleasure. Get Ruby for free from RubyForge.

As you may have guessed, this macro inserts a new paragraph at the start of the active document, and then styles that paragraph as Heading 1.

Note: You should also quit Word before running any of these, in order to avoid any conflicts or problems with open documents.

These scripts use Word's Application.Run() method, which takes, as its first argument, the name of a macro to run as a string. (It also accepts optional arguments to pass into the macro, but the code in this article won't use those.) Using Application.Run(), you can run any macro in the document, its template, or any loaded global templates.

In each of the scripts, the filename is expanded to include the full path. Just giving Word the relative filename isn't recommended, because Word's "current" directory isn't necessarily the same as the one you're in when you call it with COM.

Note: The code shown below uses the standard Windows distribution for each language. No additional packages or modules are needed. For simplicity, I've left out any error handling, command-line option processing, and even usage messages.

Batching with Ruby: batchmacro.rb

Only Ruby was able to handle standard DOS wildcards without additional code, so it's the simplest of the three--if you don't count comments or white space, this script is just 11 lines long. I've found scripting Word with COM using Ruby much less stressful than with Perl or Python, though your mileage may vary. Ruby is both concise and readable, an unusual combination in a programming language.

Save this script as batchmacro.rb in the same folder as your sample documents, as described above.


# batchmacro.rb
# Ruby script for batch running Word macros

require 'win32ole'

# Launch new instance of Word
wrd = WIN32OLE.new('Word.Application')

wrd.Visible = 1

# First argument to script is the name of the macro
macro_to_run = ARGV.shift()

# Everything else is a document on which to run the macro
ARGV.each do |file|
  doc = wrd.Documents.Open(File.expand_path(file))
  wrd.Run(macro_to_run)
  doc.Save()
  doc.Close()
end

wrd.Quit()

To run this script, open up a DOS command line, and navigate to the folder where you've put the script and your sample documents, as described above. At the command line, type the following, and then press Enter:


> ruby batchmacro.rb HelloWorld *.doc

Batching with Perl: batchmacro.pl

The Perl version is slightly longer, needing code that explicitly handles DOS wildcards. Each argument after the macro name is expanded using the File::DosGlob module, which is included in the ActivePerl distribution from ActiveState. The File::DosGlob::glob function returns an array of filenames (with only one element if there are no wildcards), and each of these in turn is opened by Word.

Save this script as batchmacro.pl in the same folder as your sample documents, as described above.


# batchmacro.pl
# Perl script for batch running Word macros

use Win32::OLE;
use File::DosGlob;

# Launch new instance of Word
my $wrd = Win32::OLE->new('Word.Application');

$wrd->{'Visible'} = 1;

# First argument to script is the name of the macro
my $macro_to_run = shift @ARGV;

# Everything else is a document on which to run macro
foreach $arg (@ARGV) {

    # Expand any wildcards that might be in the argument
    foreach $file (File::DosGlob::glob($arg)) {

      my $file_full_name = Win32::GetFullPathName($file);
      my $doc = $wrd->{'Documents'}->Open($file_full_name);

      $wrd->Run($macro_to_run);
      $doc->Save();
      $doc->Close();  
  }
}
$wrd->Quit();

To run this script, open up a DOS command line, and then navigate to the folder where you've put the script and your sample documents, as described above. At the command line, type the following, and then press Enter:


> perl batchmacro.pl HelloWorld *.doc

Batching with Python: batchmacro.py

The Python version also needed extra code to explicitly handle DOS wildcards. Each argument after the macro name is expanded using the glob module. The glob.glob function returns a list (Python calls them lists, not arrays) of filenames (with only one element if there are no wildcards), and each of these in turn is opened by Word.

Save this script as batchmacro.py in the same folder as your sample documents, as described above.


# batchmacro.py
# Python script for batch running Word macros

import sys
import os
import glob
from win32com.client import Dispatch

# Launch new instance of Word
wrd = Dispatch('Word.Application')
wrd.Visible = 1

# First argument to script is the name of the macro
macro_to_run = sys.argv[1]

# Everything else is a document on which to run macro
for arg in sys.argv[2:]:
    # Expand any wildcards that might be in the argument
    for file in glob.glob(arg):
      doc = wrd.Documents.Open(os.path.abspath(file))
      wrd.Run(macro_to_run)
      doc.Save()
      doc.Close()
wrd.Quit()

To run this script, open up a DOS command line, and then navigate to the folder where you've put the script and your sample documents, as described above. At the command line, type the following, and then press Enter:


> python batchmacro.py HelloWorld *.doc

Related Reading

Word Hacks
Tips & Tools for Taming Your Text
By Andrew Savikas

Of course, There's More Than One Way to Do It, and I'm not presenting these three examples as the only, or even the best, way to do this particular task. In particular, I've tried to avoid shortcuts that might confuse someone unfamiliar with a particular language.

Making Your Macros Batch-Friendly

Many of the macros I use in Word display some sort of dialog, if only a simple "Done" box for a macro that takes a while to run. But having to dismiss a dialog after each document sort of defeats the purpose of being able to batch a whole set of documents at once.

One solution would be to create two separate macros, one with dialogs, the other meant to run silently. Another option, requiring a lot less code duplication, is to put the main code of your macro in a separate function, and put all the dialogs in a wrapper subroutine. It's the quiet function that you use when batching your files, not the noisy subroutine.

For example, the Word template we use at O'Reilly includes a macro that deletes all the comments in a document. As you might imagine, authors, editors, and technical reviewers make extensive use of comments, so accidentally deleting all of them can be catastrophic (or at least a major annoyance). So the macro starts with a confirmation dialog, just in case. When I get the files, though, all the reviewing is finished, and it's time to blast away any remaining comments, preferably in all the files at once, and without any pesky confirmation dialogs.

Here's how I've split up this particular task. The first listing is the function that deletes all of the comments in a given document (assuming the active document if none is specified), with no dialogs at all. The second listing is a wrapper macro that displays a confirmation dialog, and a message announcing when it's finished running. The production versions of these two also include some error handling, which I've left out for simplicity.


Function DeleteAllCommentsInDoc(Optional doc As Document) As Boolean
Dim iNumberOfComments As Integer
Dim i As Integer

If doc Is Nothing Then Set doc = ActiveDocument

iNumberOfComments = doc.Comments.Count
For i = iNumberOfComments To 1 Step -1
    doc.Comments(i).Delete
Next i
DeleteAllCommentsInDoc = True
End Function

And here's the wrapper macro:


Sub DeleteAllComments()
Dim doc As Document
Dim i As Integer
Dim iNumberOfComments As Integer
Dim lContinue As Long
Set doc = ActiveDocument

iNumberOfComments = doc.Comments.Count

If iNumberOfComments = 0 Then
    MsgBox "There are no comments in this document.", vbInformation
    Exit Sub
End If

If MsgBox("Are you sure you want to delete ALL " & _
          iNumberOfComments & " comments?", _
          vbYesNo) = vbNo Then
        Exit Sub
End If

If DeleteAllCommentsInDoc(doc) = True Then
    MsgBox iNumberOfComments & " Comment(s) Deleted.", vbInformation
End If
End Sub

When someone working within Word wants to delete all the comments, there's plenty of feedback, and an opportunity to cancel. But now I can also delete all the comments in a group of documents at once, using one of the scripts shown above (I'll use the Ruby version here):


> ruby batchmacro.rb DeleteAllCommentsInDoc ch??.doc

By structuring your macros in two parts like this, you retain valuable feedback features, while adding the flexibility of easy scripting without distracting dialogs. In effect, this gives your Word templates an API that's easily accessible from COM scripts, using the language of your choice.


In November 2004, O'Reilly Media, Inc., released Word Hacks.

Andrew Savikas is the CEO at Safari Books Online. He sits on several boards, including the Book Industry Study Group, and is on advisory boards for Bookshare and the University of Michigan Press.

Previously, Andrew led the digital publishing and ebook program and strategy for O'Reilly Media as VP of Digital Initiatives, and was a Program Chair for the Tools of Change for Publishing conference.


Return to the Windows DevCenter.

Copyright © 2009 O'Reilly Media, Inc.