Technically Impossible

Lets look at the weak link in your statement. Anything "Technically Impossible" basically means we haven't figured out how yet.

PowerShellによるOneNote操作 階層構造の取得からMarkdown変換まで

OneNoteにはエクスポート機能が備わっている。ノート、セクション、ページ、それぞれの単位で1ファイルにエクスポートできるのだが、次のような場合のエクスポートには対応していない。

  • 異なる複数のページを、それぞれ個別のファイルにエクスポートする
  • ノート、セクション配下の全ページを、それぞれ個別のファイルにエクスポートする

またサポートしている出力フォーマットも限られており、例えばMarkdownのような未対応のフォーマットの場合、エクスポートしたファイルを変換する必要がある。

いずれの場合においても、自動的かつ効率的に処理を行うためには、プログラムの力を借りたくなる場面だ。

この投稿では、PowerShellOneNoteとの連携方法、エクスポートしてファイルをMarkdownへ変換するまでの手順を紹介する。

事前注意

PowerShellのバージョン

Windows Terminalから動作させるものも含め、PowerShellWindowsに標準搭載される一機能となった。だからこそ気づきにくいのだが、PowerShellには異なる2つの系統が存在している。*1

Desktop Ver 5.1まで .NET Framework上で実行実行される
Core Ver 6.0以降 .NET Core上で実行される

この投稿で紹介するスクリプトはDesktopで動作するが、Coreでは動作しない。もしWindows TerminalでPower Shellを実行している場合、次の機能のいずれかで新しいタブを開くとよい。

OneNoteの種類

OneNoteにも複数の種類が存在している。*2

この投稿で紹介するスクリプトは、Officeの一部として提供されるOneNoteで、インストール・ファイルで配布されているものを対象としている。
Microsoft Storeからインストールされたものや、OneNote for Windows 10は対象外だ。

API

Microsoft 365のように、OfficeもWebサービスとして提供されるようになった。そのため、PowerShellを含むプログラムがOfficeと連携するには、REST APIを使用する前提となっている。特にOneNoteについてMicrosoftが提供しているのが、Microsoft Graphの一部であるOneNote REST APIだ。*3

しかし、この投稿ではCOMオブジェクトを使用する。Microsoft Graphは使用しない。
learn.microsoft.com

階層構造の取得

OneNoteのデータはフォルダとファイルの組み合わせで構成されており、各ページはファイル中のデータとして処理されている*4。COMオブジェクトを介して、この構成情報をXMLとして取得することができる。PowerShellに限らず、プログラムを介してOneNoteの情報を操作する場合、基本的に、このXMLオブジェクトを参照、操作することになる。

GetHierarchyメソッド*5を実行すると、その引数である$HierarchyにXMLのポインタが格納される。

$OneNote = New-Object -ComObject OneNote.Application
Add-Type -assembly Microsoft.Office.Interop.OneNote 

$OneNoteID = ""
[xml]$Hierarchy = ""

$OneNote.GetHierarchy(
  $OneNoteID,
  [Microsoft.Office.InterOp.OneNote.HierarchyScope]::hsPages,
  [ref]$Hierarchy,
  [Microsoft.Office.InterOp.OneNote.XMLSchema]::xsCurrent)

OneNote内のデータには、ノートからセクション、ページに至るまで内部IDが割り当てられている。引数$OneNoteIDに該当するIDを指定することで、そのノード配下の階層構造を取得することができる。このIDについては後述する。
IDを指定すれば、XML中の特定ノードを参照できる。つまり、OneNote内のノートやセクション、ページを参照することができる。OneNote内のノートやセクション、ページを編集するのは、XMLを編集することと同じだ。これをプログラムを介して行う。

IDに何も指定しなければ、OneNoteのルート・ノードが指定されたことになり、全てのノートブックの全階層を取得できる。次のコード(onenote-printall.ps1)を実行すると、それらの情報を出力することができる。

🔎onenote-printall.ps1
gist.github.com

IDの取得、CSVファイルでの出力

特定のノートブックやセクション以下の全ページ、あるいは階層に関わらない特定のページ群などに対してバルク処理を行うとしよう。XMLの階層構造を辿りながら逐次処理するプログラムよりも、対象IDに対するループ処理としてプログラムする方が分かりやすいし、作業効率も良い。
そのためには、全IDを取得し、操作対象となるIDを事前に特定しておく必要がある。

次のコード(onenote-makecsv.ps1)を実行すると、全ノードのIDをCSVファイルとして出力することができる。

🔎onenote-makecsv.ps1
gist.github.com

なおプログラム、並びにCSV中の”depth”は階層構造を表している。ルート・ノードから階層構造を捉えた場合、それぞれの番号が次のように対応している。

1 notebook
2 section group
section
3~ page


OneNoteページのエクスポート、Markdownへの変換

注意:Markdownへの変換

OneNoteはエクスポート機能を有しているが、対応フォーマットは限られている。Markdownのようにサポートされていないフォーマットでエクスポートしたい場合、まずOneNoteがサポートしているフォーマットで文書をエクスポートし、そのファイルを目的のフォーマットへ変換する必要がある。

例えば、Wordフォーマット(docxファイル)でエクスポートし、それをPandoc*6を用いてMarkdownへ変換する、といった具合だ。

注意:実行ユーザーの統一→管理者として実行

ここで紹介する注意事項は、おそらくOneNote操作で最大のハマりどころだ。当たり前の事として暗黙に把握されているのか、Microsoftサイトを含め、これを紹介しているサイトを見たことがない。

  1. スクリプト実行中(特にPublishメソッド*7実行時)、OneNoteを起動しておくこと
  2. Windows Terminal、PowerShellスクリプトOneNoteの実行ユーザーが同一であること

OneNoteオブジェクトのメソッドPublishは、OneNote本体のエクスポート機能に依存しているようだ。これを実行するとき、OneNoteが動作している必要がある。つまり、スクリプト実行前にOneNoteを起動しておく必要がある。

同一のユーザーで操作していても、セキュリティの都合上、システム上のユーザーが細分化され、異なるユーザーで実行されていることがある。共通ユーザーによる構成要素の実行を担保するには、全ての構成要素を管理者として実行するのがよい。

事前にOneNoteを起動していない場合、あるいは実行ユーザーに不整合がある場合、次のエラーが出力される。これで、異常2つのいずれかの原因にたどり着くのは、まず無理だろう。

The remote procedure call failed. (Exception from HRESULT: 0x800706BE)

OneNoteのエクスポート

次の情報を指定してPuslishメソッドを実行することで、指定したIDの情報をエクスポートすることができる。

  • エクスポート対象のID
  • エクスポート・ファイルの絶対パス
  • エスポート・ファイルのフォーマット

もし単一のノートやセクションをエクスポートするかどうかに関わらず、単一のページを1ファイルとしてエクスポートするならば、対象となるページのIDを指定し、それを必要回数だけ実行することになる。
単一のノートやセクションなど、まとまったページを1ファイルとしてエクスポートするならば、そのノートやセクションのIDを指定すればよい。
このようなIDの確認、特定に、先に出力しておいたCSVファイルが役に立つ。

次のサンプル・コード(onenote-exportmakecsv.ps1)は、ある単一ページを一つのファイルとして出力し、それをMarkdownファイルに変換している。
添付画像は、OneNote内の1ページが、Wordファイルとしてエクスポートされ、Markdownに変換された変遷を表している。

🔎onenote-export.ps1
gist.github.com