F# Scripting Zen – Word Interop

Edit: Added a ‘comarg’ function to dramatically clean up the syntax for doing COM-interop, since F# will pass ‘ref types’ as byrefs to COM calls.

In a previous post I talked about how to take advantage of .FSX (F# Script) files to automate tasks for you. In this post I would like to share a script which I’ve found useful.

Let’s say you are working with a lot of Word documents, for example writing a book for O’Reilly on an upcoming .NET language. If you find revising your writing by hand easier than sitting in front of a computer, it would be helpful to have a script which prints out the book’s contents in such a way that doesn’t kill too many trees.

Here’s a simple F# script to automate the task of printing every Word doc in the same folder as the script. Simply copy the .FSX file into the desired folder, and whenever you want to print out all the documents right click and select ‘Run in F# Interactive…’.

Note how this takes advantage of F#’s support for optional parameters for COM-components.


#I @"C:\Program Files\Microsoft Visual Studio 9.0\Visual Studio Tools for Office\PIA\Office12\"
#r "Office.dll"
#r "stdole.dll"
#r "Microsoft.Office.Interop.Word.dll"

open Microsoft.Office.Interop.Word

let private m_word : ApplicationClass option ref = ref None

let OpenWord()        = m_word := Some(new ApplicationClass())
let GetWordInstance() = Option.get !m_word
let CloseWord()       = (GetWordInstance()).Quit()

let comarg x = ref (box x)
let OpenDocument filePath = 
    printfn "Opening %s..." filePath
    word.Documents.Open(comarg filePath)

let PrintDocument (doc : Document) =
    printfn "Printing %s..." doc.Name

        Background  = comarg true, 
        Range       = comarg WdPrintOutRange.wdPrintAllDocument,
        Copies      = comarg 1, 
        PageType    = comarg WdPrintOutPages.wdPrintAllPages,
        PrintToFile = comarg false,
        Collate     = comarg true, 
        ManualDuplexPrint = comarg false,    
        PrintZoomColumn = comarg 2,             // Pages 'across'
        PrintZoomRow    = comarg 2)             // Pages 'up down'

let CloseDocument (doc : Document) =
    printfn "Closing %s..." doc.Name
    doc.Close(SaveChanges = comarg false)

// -------------------------------------------------------------

let currentFolder = __SOURCE_DIRECTORY__

open System
open System.IO


    printfn "Printing all files in [%s]..." currentFolder

    Directory.GetFiles(currentFolder, "*.docx")
    |> Array.iter 
        (fun filePath -> 
            let doc = OpenDocument filePath
            PrintDocument doc
            CloseDocument doc)

printfn "Press any key..."
Console.ReadKey(true) |> ignore

Disclaimer: Note that this post is provided ‘AS IS’ and offers no warranty and confers no rights. If this script file causes you bad mojo, seek a shaman for help and don’t blame me.

Comments (3)

  1. Derek says:

    I recently did something similar with Excel interop. In my case, there was a need to connect to an already running instance of Excel, as it made edits to a sheet alongside user modifications. Not sure if Word uses the same model, but here’s how I accomplished it:

       let application =

           (* connect to existing or start new *)

           let application = Marshal.GetActiveObject("Excel.Application") in

               if application = null then (new ApplicationClass() :> Application)

               else (application :?> Application)

  2. Erik says:

    com interop with F# – that’s just awesome. Thanks for this. 🙂