Search code examples
goms-wordole

Creating MS Word documents with Go OLE binding


I've been playing around and learning how to make Word docs programmatically. I know it can easily be done using pywin32. This simple snippet retrieves the default Visual Basic "code" inside the new Word doc.

import win32com.client
 
word = win32com.client.Dispatch("Word.Application")
word.Visible = True
document = word.Documents.Add()
document.VBProject.Name = "TEST"
wordModule = document.VBProject.VBComponents("ThisDocument") # WORKS

input()

You can then add VB code to wordModule.

I wanted to do the same using Golang. There is a OLE binding for Go, the code is on Github -> https://github.com/go-ole/go-ole

It's a bit less user friendly but I managed to make it work, except that I'm not able to retrieve the default VBComponents.

The default code resides in "ThisDocument" and can be retrieved with the simple python code document.VBProject.VBComponents("ThisDocument") except that, it doesn't work in Go... You can see in the code below that I tried to get "ThisDocument" using multiple ways, without success. Each time, the error message is panic: Unknown name.

// +build windows

package main

import (
    "fmt"

    ole "github.com/go-ole/go-ole"
    "github.com/go-ole/go-ole/oleutil"
)

func main() {
    defer ole.CoUninitialize()

    ole.CoInitialize(0)
    unknown, _ := oleutil.CreateObject("Word.Application")
    word, _ := unknown.QueryInterface(ole.IID_IDispatch)
    oleutil.PutProperty(word, "Visible", true)

    documents := oleutil.MustGetProperty(word, "Documents").ToIDispatch()
    document := oleutil.MustCallMethod(documents, "Add").ToIDispatch()

    vbproject := oleutil.MustGetProperty(document, "VBProject").ToIDispatch()
    oleutil.PutProperty(vbproject, "Name", "TEST")

    // oleutil.MustCallMethod(vbproject, "VBComponents", "ThisDocument").ToIDispatch() --> panic: Unknown name.

    // oleutil.MustGetProperty(vbproject, "VBComponents", "ThisDocument").ToIDispatch() --> panic: Unknown name.

    // vbcomponents := oleutil.MustGetProperty(vbproject, "VBComponents").ToIDispatch()
    // oleutil.MustGetProperty(vbcomponents, "ThisDocument").ToIDispatch() --> panic: Unknown name.

    var input string
    fmt.Scanln(&input)

    oleutil.PutProperty(document, "Saved", true)
    oleutil.CallMethod(documents, "Close", false)
    oleutil.CallMethod(word, "Quit")
    word.Release()
}

Any ideas on why it doesn't work? Thanks a lot.


Solution

  • Turns out "github.com/go-ole/go-ole" has a bug when using ForEach. VBComponets is a Collection, so you have to iterate as stated by Microsoft doc

    Use the VBComponents collection to access, add, or remove components in a project. A component can be a form, module, or class. The VBComponents collection is a standard collection that can be used in a For...Each block.

    This line -> https://github.com/go-ole/go-ole/blob/master/oleutil/oleutil.go#L106 should be replace by

    newEnum, err := disp.CallMethod("_NewEnum")
    

    Now it works as intended.