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."
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 LanguageIf 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:
|
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.
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
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
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 |
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.
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.
The following Sample Hacks are available free online.
You can also look at the Table of Contents, the Index, and the full description of the book.
For more information, or to order the book, click here.
Andrew Savikas is the VP of Digital Initiatives at O'Reilly Media, and is the Program Chair for the Tools of Change for Publishing Conference.
Return to the Windows DevCenter.
Copyright © 2009 O'Reilly Media, Inc.