Using Excel VBA to Push Content to OneNote
October 01, 2004
In August, Microsoft released version SP1 of OneNote. This is a must-have upgrade. They added many incredible features, include an application programming interface that allows other applications to push data to OneNote.
Microsoft offers several excellent websites that will teach you how to use VB.Net in order to push data into OneNote. But, since this is the MrExcel site, you and I and the other 200 million Office users are most concerned about how to push data to OneNote using Office VBA. I am happy to say that this CAN be done. This page will walk you through all that you need to get it done.
I will assume that you are moderately familiar with VBA. If you are not, I highly recommend VBA & Macros for Microsoft Excel, the book designed to take someone up the VBA learning curve.
Overview
You can send data to OneNote by formatting the data as XML data. XML is a fairly new concept. It is sort of like HTML. Think of it as a CSV file on steroids. You can read my Introduction to XML.
Basically, your VBA program needs to write out an XML file, then pass the contents of the XML file to OneNote using the .Import method. The XML file needs to contain these elements:
- An EnsurePage element for each page that you want to write to. If the page does not exist, OneNote will create the page for you. In theory, you are supposed to have control and place the page after a specific existing page. However, in practice, this does not appear to work.
- A PlaceObject element for each item that you want to add to the page. You specify the X & Y location for the item and the source of the item. An item can either be an image, an Ink object, or text in HTML format. You would think that since OneNote reads from HTML, you could actually pass a table with TR and TD tags, but this does not work. You are limited to passing text with BR and P tags to add linefeeds. UL & LI tags to appear to work. Font tags do work.
The Gotcha
In order to update an existing page, you must know the Globally Unique Identifier (GUID) for that page. There does not appear to be a way to find the GUID for an existing page in OneNote. This means that you can only update or delete items on an existing page if you programmatically created the page and have stored the GUID used to create that page in your workbook. The example below uses an out-of-the way place on the worksheet to save the GUID for the page, the data table and the chart.
GUIDs
Every new page in OneNote needs a GUID. Every new object placed on a page needs a GUID. While it is easy to generate GUIDs from VB.Net, finding a way to generate GUID's from VBA has been elusive. All 200 million Office VBA users need to give a tip of the cap to Michael Kaplan of Trigeminal Software. Michael seems to be the only guy in the world to break the code on how to generate a GUID from VBA. He has graciously shared this code with the world. Check out the complete code at his website. With Michael's permission, I've copied just the functions needed to generate a new GUID in VBA here. Insert a module in your project and include the following code in that module.
'------------------------------------------
' basGuid from http://www.trigeminal.com/code/guids.bas
' You may use this code in your applications, just make
' sure you keep the (c) notice and don't publish it anywhere
' as your own
' Copyright (c) 1999 Trigeminal Software, Inc. All Rights Reserved
'------------------------------------------
Option Compare Binary
' Note that although Variants now have
' a VT_GUID type, this type is unsupported in VBA,
' so we must define our own here that will have the same
' binary layout as all GUIDs are expected by COM to
' have.
Public Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public Declare Function StringFromGUID2 Lib "ole32.dll" _
(rclsid As GUID, ByVal lpsz As Long, ByVal cbMax As Long) As Long
Public Declare Function CoCreateGuid Lib "ole32.dll" _
(rclsid As GUID) As Long
'------------------------------------------------------------
' StGuidGen
'
' Generates a new GUID, returning it in canonical
' (string) format
'------------------------------------------------------------
Public Function StGuidGen() As String
Dim rclsid As GUID
If CoCreateGuid(rclsid) = 0 Then
StGuidGen = StGuidFromGuid(rclsid)
End If
End Function
'------------------------------------------------------------
' StGuidFromGuid
'
' Converts a binary GUID to a canonical (string) GUID.
'------------------------------------------------------------
Public Function StGuidFromGuid(rclsid As GUID) As String
Dim rc As Long
Dim stGuid As String
' 39 chars for the GUID plus room for the Null char
stGuid = String$(40, vbNullChar)
rc = StringFromGUID2(rclsid, StrPtr(stGuid), Len(stGuid) - 1)
StGuidFromGuid = Left$(stGuid, rc - 1)
End Function
Adding a Reference
In VBA, use Tools - References to add a reference to the OneNote 1.1 Object Library. This will allow you to declare a new CSimpleImporter object and then use the .Import and .NavigateToPage methods on the object.
Case Study
This Excel workbook contains a daily reporting system. There is one worksheet for each store in a local chain of stores. Each page contains a table showing daily sales and a chart showing progress towards the monthly goal.
The VBA code will add a new section called DailySales. One new page will be added for each store. The Chart from the worksheet is exported as a GIF file and imported to OneNote. The data from the worksheet is added to OneNote as an HTML column.
The following code is used in Excel.
Sub CreateUpdateOneNoteReport()
' Requires basGuid module from above
Dim Cht As Chart
fname = "C:\OneNoteImport.xml"
On Error Resume Next
Kill (fname)
On Error GoTo 0
' Do we need new GUID's?
For Each ws In ThisWorkbook.Worksheets
If Not ws.Range("J22").Value > "" Then
ws.Range("J22").Value = StGuidGen()
End If
If Not ws.Range("J23").Value > "" Then
ws.Range("J23").Value = StGuidGen()
End If
If Not ws.Range("J24").Value > "" Then
ws.Range("J24").Value = StGuidGen()
End If
Next ws
' Build a temporary XML file
fname = "C:\OneNoteImport.xml"
On Error Resume Next
Kill (fname)
On Error GoTo 0
Open fname For Output As #1
Print #1, "<!--?xml version=""1.0""?--> "
Print #1, "<import xmlns="" http:="" schemas.microsoft.com="" office="" onenote="" 2004="" import""=""> "
' Make sure that for each page, we have a page
FirstPage = True
DateStr = Format(Date - 1, "yyyy-mm-dd") & "T21:00:00-06:00"
For Each ws In ThisWorkbook.Worksheets
ThisTitle = ws.Name
ThisGuid = ws.Range("J22").Value
Print #1, " <ensurepage path="" dailyreport.one"""="" print="" #1,="" "="" guid="" &="" thisguid="" """="" date="" datestr="" if="" not="" firstpage="" then="" insertafter="" lastguid="" else="" firstguid="ThisGuid" end="" title="" thistitle=""></ensurepage>"
FirstPage = False
LastGuid = ThisGuid
Next ws
For Each ws In ThisWorkbook.Worksheets
ThisTitle = ws.Name
ThisImage = "C:\" & ThisTitle & ".gif"
ThisGuid = ws.Range("J22").Value
ChartGuid = ws.Range("J24").Value
TableGuid = ws.Range("J23").Value
' Export the Chart
Set Cht = ws.ChartObjects(1).Chart
Cht.Export Filename:=ThisImage, FilterName:="GIF"
' Place the Chart on the top, right side
Print #1, ""
Print #1, " <placeobjects pagepath="" dailyreport.one""="" "="" print="" #1,="" pageguid="" &="" thisguid="" """="">"
Print #1, ""
Print #1, " <object guid="" "="" &="" chartguid="" """="">"
Print #1, " <position x="" 258""="" y="" 36""=""></position>"
Print #1, " <img backgroundimage="" false""="">"
Print #1, " <file path="" "="" &="" thisimage="" """=""></file>"
Print #1, " "
Print #1, " </object>"
Print #1, ""
' Place table of sales on left side
' Place the text in the first column
Print #1, " <object guid="" "="" &="" tableguid="" """="">"
Print #1, " <position x="" 36""="" y=""></position>"
Print #1, " <outline width="" 210""="">"
Print #1, " "
Print #1, " <data>"
Print #1, " <!--[CDATA["
'Build HTML String
HTMLStr = "<html--><h1>Daily Sales</h1>"
For i = 2 To 32
If ws.Cells(i, 2).Value > 0 Then
HTMLStr = HTMLStr & ws.Cells(i, 1).Value _
& " - $" & ws.Cells(i, 2).Value & "<br>"
End If
Next i
HTMLStr = HTMLStr & ""
Print #1, HTMLStr
Print #1, " ]]>"
Print #1, " </data>"
Print #1, " "
Print #1, " </outline>"
Print #1, " </object>"
Print #1, ""
Print #1, " </placeobjects>"
Next ws
Print #1, ""
Print #1, "</import>"
Close #1
' Load file into a string variable
XMLStr = ""
Open fname For Input As #1
Do
Line Input #1, strData
XMLStr = XMLStr & strData
Loop While EOF(1) = False
Close #1
Dim CSI As New CSimpleImporter
' Import the string
CSI.Import XMLStr
'navigate to the page
CSI.NavigateToPage bstrPath:="DailyReport.one", bStrGuid:=FirstGuid
End Sub
The resulting OneNote notebook looks like this.
Apparent Bugs
In the book, I mentioned an apparent bug with "insertafter". I forgot that XML is case sensitive. If you use "insertAfter", then everything works fine. Thanks to Donovan Lange at Microsoft for pointing this out.
I am guessing that the next issue is not a bug - the code is probably working like Microsoft intended, but they missed an opportunity to do something the right way. You are allowed to specify a date and time in the EnsurePage section of the XML. This date and time is only used if the page does not exist. Given that Microsoft later allows us to update the page by remembering the GUID, they really should have allowed us to update the date and time on the page. In the example here, we are pushing new data each day, yet the date is always going to show that it is as of the first time that the program was run. This is disappointing.