1param(
2 [Parameter(Mandatory=$true)][string]$Root,
3 [int]$ExpectedPages = -1,
4 [switch]$UseCurrentCache,
5 [string]$CloneHost = ''
6)
7Set-StrictMode -Version Latest
8$ErrorActionPreference = 'Stop'
9$root = [IO.Path]::GetFullPath($Root).TrimEnd('\')
10if ([IO.Path]::GetDirectoryName($root) -ne 'C:\one-tests\runs') {
11 throw 'Choose a run directly below C:\one-tests\runs.'
12}
13$notebook = Join-Path $root 'notebook'
14$output = Join-Path $root 'read'
15if (Test-Path $output) { throw 'Choose a new read destination.' }
16if ($UseCurrentCache) {
17 if ((Get-ItemProperty 'HKCU:\Software\Microsoft\Office\14.0\OneNote\Options\Paths').UnfiledNotesSection -ne 'C:\one-tests\Loose.one') {
18 throw 'Park the personal OneNote profile before reading test notebooks.'
19 }
20} else {
21 & "$PSScriptRoot\cold-current.ps1" -Root $root -CloneHost $CloneHost
22}
23New-Item -ItemType Directory -Path $output | Out-Null
24$app = New-Object -ComObject OneNote.Application
25$notebookId = ''
26try {
27 $app.OpenHierarchy($notebook, '', [ref]$notebookId, 0)
28 $process = Get-Process ONENOTE
29 @{ hostname = [Environment]::MachineName; onenote = $process.MainModule.FileVersionInfo.FileVersion;
30 powershell = $PSVersionTable.PSVersion.ToString(); schema = 'xs2010'; cold = (-not $UseCurrentCache.IsPresent) } |
31 ConvertTo-Json | Set-Content (Join-Path $output 'environment.json') -Encoding UTF8
32 $sections = @()
33 foreach ($file in @(Get-ChildItem $notebook -Recurse | Where-Object { $_.Extension -eq '.one' })) {
34 $id = ''
35 $app.OpenHierarchy($file.FullName, '', [ref]$id, 0)
36 $sections += $id
37 }
38 $deadline = [DateTime]::UtcNow.AddSeconds(45)
39 $previous = ''
40 $stableSince = [DateTime]::UtcNow
41 $settled = $false
42 do {
43 $pages = @{}
44 foreach ($section in $sections) {
45 $hierarchy = ''
46 $app.GetHierarchy($section, 4, [ref]$hierarchy, 1)
47 [xml]$xml = $hierarchy
48 foreach ($node in $xml.SelectNodes('//*[@path]')) {
49 if (-not $node.GetAttribute('path').StartsWith("$notebook\", [StringComparison]::OrdinalIgnoreCase)) {
50 throw 'OneNote opened a section outside the copied notebook.'
51 }
52 }
53 foreach ($node in $xml.SelectNodes('//*[local-name()="Page"]')) {
54 $id = $node.GetAttribute('ID')
55 $content = ''
56 $app.GetPageContent($id, [ref]$content, 3, 1)
57 $pages[$id] = $content
58 }
59 }
60 $signature = [String]::Join('|', @($pages.Keys | Sort-Object | ForEach-Object { $_ + $pages[$_] }))
61 if ($signature -ne $previous) {
62 $previous = $signature
63 $stableSince = [DateTime]::UtcNow
64 }
65 if ((($ExpectedPages -ge 0 -and $pages.Count -eq $ExpectedPages) -or
66 ($ExpectedPages -lt 0 -and $pages.Count -gt 0)) -and
67 ([DateTime]::UtcNow - $stableSince).TotalSeconds -ge 2) { $settled = $true; break }
68 Start-Sleep -Milliseconds 250
69 } while ([DateTime]::UtcNow -lt $deadline)
70 if (-not $settled -or ($ExpectedPages -ge 0 -and $pages.Count -ne $ExpectedPages) -or ($ExpectedPages -lt 0 -and $pages.Count -eq 0)) {
71 throw "Expected $ExpectedPages pages; OneNote returned $($pages.Count)."
72 }
73 $index = 0
74 foreach ($id in @($pages.Keys | Sort-Object)) {
75 [IO.File]::WriteAllText((Join-Path $output ('page-{0:d3}.xml' -f $index)), $pages[$id], [Text.Encoding]::UTF8)
76 [xml]$page = $pages[$id]
77 foreach ($file in $page.SelectNodes('//*[local-name()="InsertedFile"]')) {
78 $bytes = [IO.File]::ReadAllBytes($file.GetAttribute('pathCache'))
79 $hash = [BitConverter]::ToString([Security.Cryptography.SHA256]::Create().ComputeHash($bytes)).Replace('-', '').ToLowerInvariant()
80 [IO.File]::WriteAllBytes((Join-Path $output ($hash + '.attachment')), $bytes)
81 }
82 $index++
83 }
84 $all = ''
85 $app.GetHierarchy($notebookId, 4, [ref]$all, 1)
86 [xml]$finalTree = $all
87 $finalIds = @($finalTree.SelectNodes('//*[local-name()="Page"]') | ForEach-Object { $_.GetAttribute('ID') } | Sort-Object -Unique)
88 if ($finalIds.Count -ne $pages.Count -or @($finalIds | Where-Object { -not $pages.ContainsKey($_) }).Count -ne 0) {
89 throw 'The notebook hierarchy changed while collecting page evidence; repeat the cold read.'
90 }
91 [IO.File]::WriteAllText((Join-Path $output 'hierarchy.xml'), $all, [Text.Encoding]::UTF8)
92 Write-Output "Read $($sections.Count) sections and $($pages.Count) pages."
93} finally {
94 try {
95 if ($notebookId -and -not $UseCurrentCache) { $app.CloseNotebook($notebookId, $false) }
96 } finally {
97 [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
98 }
99 $app = $null
100 [GC]::Collect()
101 [GC]::WaitForPendingFinalizers()
102 if (-not $UseCurrentCache -and -not $CloneHost) {
103 Get-Process ONENOTE -ErrorAction SilentlyContinue | Wait-Process -Timeout 10
104 }
105}