1param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost,
2 [string]$CaptureOnly = '')
3Set-StrictMode -Version Latest
4$ErrorActionPreference = 'Stop'
5if ($CaptureOnly) {
6 if ($CaptureOnly -notmatch '^[a-z0-9-]+$' -or $CloneHost -notmatch '^ONE-[A-Z0-9-]+$' -or
7 [Environment]::MachineName -ne $CloneHost -or
8 [IO.Path]::GetDirectoryName([IO.Path]::GetFullPath($Root).TrimEnd('\')) -ne 'C:\one-tests\runs') {
9 throw 'Choose a named capture in the owned clone run.'
10 }
11} else {
12 & "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost
13}
14$app = New-Object -ComObject OneNote.Application
15$notebook = ''
16$section = ''
17$pages = [ordered]@{}
18$namespace = 'http://schemas.microsoft.com/office/onenote/2010/onenote'
19
20function Save-Phase([string]$Name) {
21 $destination = Join-Path $Root $Name
22 [void](New-Item -ItemType Directory -Path $destination)
23 $deadline = [DateTime]::UtcNow.AddSeconds(60)
24 $previous = ''
25 $stableSince = [DateTime]::UtcNow
26 do {
27 $hierarchy = ''
28 $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
29 [xml]$tree = $hierarchy
30 $contents = @()
31 foreach ($node in $tree.SelectNodes('//*[local-name()="Page"]')) {
32 $content = ''
33 $app.GetPageContent($node.GetAttribute('ID'), [ref]$content, 1, 1)
34 $contents += $content
35 }
36 $signature = $hierarchy + [String]::Join('|', $contents)
37 if ($signature -ne $previous) {
38 $previous = $signature
39 $stableSince = [DateTime]::UtcNow
40 }
41 if (([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { break }
42 if ([DateTime]::UtcNow -ge $deadline) { throw 'Native page content did not settle.' }
43 Start-Sleep -Milliseconds 250
44 } while ($true)
45 [IO.File]::WriteAllText((Join-Path $destination 'hierarchy.xml'), $hierarchy, [Text.Encoding]::UTF8)
46 for ($index = 0; $index -lt $contents.Count; $index++) {
47 [IO.File]::WriteAllText((Join-Path $destination ('page-{0:d3}.xml' -f $index)), $contents[$index], [Text.Encoding]::UTF8)
48 }
49 $app.SyncHierarchy($notebook)
50 $app.CloseNotebook($notebook, $false)
51 Copy-Item (Join-Path $Root 'notebook') (Join-Path $destination 'notebook') -Recurse
52 $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$script:notebook, 0)
53 $app.OpenHierarchy('Lifecycle.one', $notebook, [ref]$script:section, 0)
54 $beforePages = @($tree.SelectNodes('//*[local-name()="Page"]'))
55 $deadline = [DateTime]::UtcNow.AddSeconds(30)
56 do {
57 $reopened = ''
58 $app.GetHierarchy($section, 4, [ref]$reopened, 1)
59 [xml]$current = $reopened
60 $afterPages = @($current.SelectNodes('//*[local-name()="Page"]'))
61 if ($beforePages.Count -eq $afterPages.Count) { break }
62 Start-Sleep -Milliseconds 200
63 } while ([DateTime]::UtcNow -lt $deadline)
64 [IO.File]::WriteAllText((Join-Path $destination 'reopened-hierarchy.xml'), $reopened, [Text.Encoding]::UTF8)
65 if ($beforePages.Count -ne $afterPages.Count) { throw 'Native reopen changed the page count.' }
66 for ($index = 0; $index -lt $beforePages.Count; $index++) {
67 foreach ($attribute in @('name', 'pageLevel')) {
68 if ($beforePages[$index].GetAttribute($attribute) -ne $afterPages[$index].GetAttribute($attribute)) {
69 throw 'Native reopen changed ordered page metadata.'
70 }
71 }
72 foreach ($key in @($pages.Keys)) {
73 if ($pages[$key] -eq $beforePages[$index].GetAttribute('ID')) {
74 $pages[$key] = $afterPages[$index].GetAttribute('ID')
75 }
76 }
77 }
78 if ($pages.Count) { $pages | ConvertTo-Json | Set-Content (Join-Path $destination 'pages.json') -Encoding UTF8 }
79 Write-Output $Name
80}
81
82function Set-Title([string]$Page, [string]$Title) {
83 Write-Output ('Set title ' + $Page + ' to ' + $Title)
84 $value = [Security.SecurityElement]::Escape($Title)
85 $xml = '<one:Page xmlns:one="' + $namespace + '" ID="' + $Page + '"><one:Title><one:OE><one:T>' + $value + '</one:T></one:OE></one:Title></one:Page>'
86 $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
87}
88
89function Set-Order([string[]]$Names, [int[]]$Levels) {
90 $xml = '<one:Section xmlns:one="' + $namespace + '" ID="' + $section + '">'
91 for ($index = 0; $index -lt $Names.Count; $index++) {
92 $xml += '<one:Page ID="' + $pages[$Names[$index]] + '" pageLevel="' + $Levels[$index] + '"/>'
93 }
94 $xml += '</one:Section>'
95 $app.UpdateHierarchy($xml, 1)
96}
97
98try {
99 $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0)
100 $app.OpenHierarchy('Lifecycle.one', $notebook, [ref]$section, 3)
101 if ($CaptureOnly) {
102 Save-Phase $CaptureOnly
103 return
104 }
105 foreach ($style in 0..2) {
106 $page = ''
107 $app.CreateNewPage($section, [ref]$page, $style)
108 $pages['blank' + $style] = $page
109 }
110 Save-Phase '01-blank'
111 foreach ($name in @('parent', 'child', 'grandchild', 'duplicate', 'trailing', 'automatic')) {
112 $page = ''
113 $app.CreateNewPage($section, [ref]$page, 2)
114 $pages[$name] = $page
115 if ($name -ne 'automatic') {
116 Set-Title $page $(if ($name -in @('parent', 'duplicate')) { 'Same title' } else { $name })
117 }
118 $body = [Security.SecurityElement]::Escape('Body ' + $name + ' ' + [char]::ConvertFromUtf32(0x1F980) + ' e' + [char]0x0301)
119 $xml = '<one:Page xmlns:one="' + $namespace + '" ID="' + $page + '"><one:Outline><one:OEChildren><one:OE><one:T><![CDATA[<b>' + $body + '</b> <i>preserved</i>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>'
120 $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
121 }
122 $pages | ConvertTo-Json | Set-Content (Join-Path $Root 'pages.json') -Encoding UTF8
123 Save-Phase '02-authored'
124 Set-Title $pages['duplicate'] ('Renamed ' + [char]::ConvertFromUtf32(0x1F98B) + ' e' + [char]0x0301)
125 Set-Title $pages['trailing'] ''
126 Save-Phase '03-renamed'
127 Set-Order @('blank0','blank1','blank2','parent','child','grandchild','duplicate','trailing','automatic') @(1,1,1,1,2,3,1,1,1)
128 Save-Phase '04-nested'
129 Set-Order @('blank0','blank1','blank2','duplicate','parent','child','grandchild','trailing','automatic') @(1,1,1,1,1,2,3,1,1)
130 Save-Phase '05-reordered'
131 $app.DeleteHierarchy($pages['parent'], [DateTime]::MinValue, $false)
132 Save-Phase '06-deleted-parent'
133} finally {
134 if ($notebook -and -not $CaptureOnly) { $app.CloseNotebook($notebook, $false) }
135 [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
136 [GC]::Collect()
137 [GC]::WaitForPendingFinalizers()
138}