Using a script to create a text file is fairly easy because the Windows environment in general and the Windows Script Host (WSH) environment in particular provide ad hoc tools for working with text files. However, although text files are ideal for storing simple, unstructured information, they're inadequate when your requirements are more sophisticated. If you need to present hierarchical information, you can rely on XML and create tagged text with or without referencing a schema. Frequently, though, what you'd like to be able to do is create documents that present informative content in a complex layout. HTML files are one alternative for marrying content and sophisticated layout. However, HTML documents are plain text and, as such, are subject to tampering. Furthermore, HTML documents are rarely self-contained, often linking to external resources such as images, ActiveX controls, and applets.
In contrast, Microsoft Word is a de facto standard for real-world document exchange. Creating such documents interactively is easy, but can you automate that process by creating and programmatically using template-based documents? The answer is certainly yes, as I explain. I don't go into detail about the Word end of the process; rather, I concentrate on creating and manipulating Word documents programmatically and assume that you know the basics of Word templates and bookmarks.
Using Word Templates
Sometimes (although not as often as I'd like), I need to prepare invoices, then print them for mailing or distribute them through email. Typically, I open Word, select my invoice template, enter the necessary information, and save the file as a new document. Then, I send the invoice to the client either through snail mail or as an electronic document attached to an email message.
In most cases, invoices and other forms are simply documents that share the same template but contain different information, such as date, invoice number, item description, and billing amount. The template on which the document is based typically contains one or more blank fields in which users can insert data specific to that particular form.
In Word, you can create forms for standard documents such as invoices by defining a .dot template. When you want to prepare a new document based on a template, you click File, New. After you select the template you want from the New window and click OK, Word creates and displays a new form based on that template. On the blank form, you enter variable information where necessary.
When you use Word interactively, you can see where you need to provide information. But how can you make a WSH script enter variable information in the correct fields? Before I answer that question, let's review the Word object model—that is, the COM-based infrastructure that lets you programmatically control Word's behavior.
The Word Object Model
Word's object model includes a rich and powerful hierarchy of objects. This object model exposes many of Word's interactive functions programmatically to COM-aware clients (i.e., WSH and Visual Basic—VB—scripts). The root element in Word's object model is the Word.Application object. To create an instance of this object, use the following code:
Set word = CreateObject _
Word's object model is implemented within the Word executable (winword.exe), not within a separate DLL. Thus, each instance of the Word.Application object is equivalent to having an instance of the Word application up and running. However, those instances are invisible. Because you can't see them, you run the risk of proliferating instances of Word that exist without doing anything, thus wasting system resources. To avoid proliferating useless instances of Word, your scripts should always quit the object when they're done with it by using the Application object's Quit method:
Although newly created instances of Word are invisible by default, you can make them visible by setting the Visible property to True:
When an instance is visible, you can also close the application manually. Setting the Visible property to True can be very useful when you're debugging scripted Word applications.
Working with members of Word's object model is the same as working with the application interactively. After you initialize the application, you create a new document or open an existing one. To create a new, empty document based on a specific template, you call the Add method on Word's Documents collection and specify the .dot file for the template you want to use. The following code creates a document that contains the features built into a template called Invoice:
To open an existing document, you use the Documents collection, which contains Document objects. The code in Listing 1 opens existing documents named invoice.doc and expenses.doc. This code snippet opens invoice.doc first, then opens expenses.doc. At this point, the status of the programmatically created document is exactly the same as that of a template document that you've opened interactively by clicking File, Open from within Word. If you made the instance visible, you would see document windows for invoice.doc and expenses.doc.
The doc1 and doc2 variables in the listing reference objects that represent the newly opened Word documents. You can also use an index to the Documents collection to obtain references to open documents. For example, the following code returns a reference to the first document that's been opened:
This bit of code is equivalent to setting the doc1 variable with the outcome of the Documents.Add method, as the second line of code in Listing 1 does.
The Document object's programming interface features all the functions that are available in Word's File and Edit menus. To print a document, you use the Document object's PrintOut method; to save a document to a different location, use the object's SaveAs method. When you use Word interactively, both of these methods present a dialog box that lets you choose the printer and the path. When you use the PrintOut or the SaveAs method, you can specify the printer or the file path as an argument, as the following code shows:
When you specify the path as an argument, no dialog box appears.
The SaveAs method also lets you specify an optional argument to indicate the output format (e.g., Word Document, Rich Text Format—RTF—Word 6.0, Text Only). The following code snippet shows how to save a document as an RTF file:
For more information about the constants that map to various file formats, refer to the Word documentation.
To close an open document, you use the Close method:
When you close a document, all subsequently opened documents automatically move up one position in the collection. So, if you use the code
Set doc2 = word.Documents(1)
and later close the doc1 reference, doc2's position in the array becomes Documents(0).
When you work with Word documents programmatically, you can assign formats and styles to paragraphs, words, and characters in the document as you can when you use Word interactively. In addition, you can add comments and bookmarks to document elements. When you create template-based Word documents programmatically, you use bookmarks to insert user-entered data in the proper place in the Word document.
Using Document Bookmarks
A bookmark is an association between a name and a specific point in a Word document. Bookmarks are a feature of Word that you can use to mark selected text, graphics, tables, or any other element of a document. Word stores bookmarks in the document. To add a bookmark interactively, you select the text or item that you want to bookmark, then select Bookmark from the Insert menu. In the Bookmark window, you name the bookmark that you're creating.
For example, suppose you have a Word template (a .dot file) and want it to show the current date in a certain position. You open the template and select the area reserved for the date. To facilitate future editing of the template, you can put descriptive placeholder text (e.g., "Put invoice date here") in that area. After typing the placeholder text, select the text; click Insert, Bookmark; type a name for the bookmark (e.g., InvoiceDate); then save the template.
After you programmatically create a new document based on a template, you use the document's Bookmarks collection to identify and customize bookmarked text. For example, to enter a date at the InvoiceDate bookmark within an invoice document, you'd use code such as
dateBookMark.Range.Text = _
As this code snippet shows, you access a bookmark by its name. The bookmark name points to the document's Bookmarks collection to get a bookmark object. The bookmark object's Range property is an instance of the TextRange type. This property individuates the bookmark's area of text. The Text property of the TextRange object lets you read and write the bookmark's current value. The preceding code snippet sets the date of the invoice to 7/1/2002.
Creating an Invoice
After you programmatically create a template-based document, you use bookmarks to populate the invoice with fresh data, then you save the document to a new file. At that point, you can print the document, email it to someone, or do anything else with it that you can do with a Word document.
Listing 2 shows CreateInvoice.vbs, which collects some information and prepares an invoice. The code opens the TestInvoice.dot template that's located in the same folder as the script, then creates a new document based on the template (you can download TestInvoice.dot from http://www.winscriptingsolutions.com, InstantDoc ID 26972). Web Figure 1 (http://www.winscriptingsolutions.com, InstantDoc ID 26972) shows the TestInvoice.dot template. (You can also download this template from the Code Library on the Windows Scripting Solutions Web site.)
CreateInvoice.vbs prompts the user for the invoice's number, date, and recipient. The code at callout A in Listing 2 uses bookmarks to merge this information with the underlying document. The code at callout B then uses the SaveAs method to save the document in the current folder. CreateInvoice.vbs creates a name for the completed document by concatenating the invoice number with the "inv_" prefix. The naming convention and the destination folder are arbitrary; you can easily change them to suit your needs. Finally, CreateInvoice.vbs closes the document and quits the application.
Emailing a Document
The Word object model includes a method on the Document object that lets you send the document through email:
As you can see, the SendMail method takes no arguments. The method calls the registered Messaging API (MAPI) client—typically Microsoft Outlook or Outlook Express. An email-application message window eventually appears with the Word document appended as an attachment. The user simply fills the To field and clicks Send. The mail program's Sent Items folder automatically traces the message. If the user doesn't have a full-time connection, the mail program puts the message into the Outbox folder to be sent the next time the user connects.
When you use the SendMail method, you need to be sure that your script doesn't close the Word application before the email window appears. The SendMail method operates asynchronously with respect to the main script, so the script has no way of determining that the SendMail method has finished sending the message. If your script attempts to close Word before the SendMail method has finished, the system generates a message saying that you can't close Word until the command is finished. If this message box appears, the user must close it manually. To avoid the system-generated message box, you can add a call to Sleep, as I do within callout B in Listing 2, to temporarily stop the script and give the SendMail method time to complete. Alternatively, you can add a custom confirmation message box to your script so that the user can confirm that the email has been sent. The following code displays a confirmation message box called Done just before the script closes Word:
Run with It
Most forms today are simple Word documents. After you interactively create a template in Word, you can easily write a script such as the one I provide here to populate the form with custom data, then let WSH and the Word object model do the rest.
After you see how simple and effective this approach is, you'll be tempted to use it for other frequently used forms, such as expense reports and subscription requests. You can also add functionality to your scripts. For example, you might want to integrate automatic invoice creation with database access so that your script can automatically retrieve the next available invoice number. Or you might want your script to add a record to a database to track the invoice. To do so, you can extend CreateInvoice.vbs with ADO-based code that queries the database for the latest invoice number and executes an INSERT statement to add a new record. To learn more about using ADO for database access, see "Rem: Obtaining Data from a SQL Server Database," August 2002, http://www.winscriptingsolutions.com, InstantDoc ID 25628.