| 1 | param([string]$Root, [string]$CloneHost) |
| 2 | Set-StrictMode -Version Latest |
| 3 | $ErrorActionPreference = 'Stop' |
| 4 | $results = New-Object Collections.Generic.List[object] |
| 5 | $index = 0 |
| 6 | function Record-Phase([string]$Phase) { |
| 7 | @{ name=$file.BaseName; phase=$Phase; utc=[DateTime]::UtcNow.ToString('o') } | |
| 8 | ConvertTo-Json -Compress | Add-Content "$Root\progress.jsonl" -Encoding UTF8 |
| 9 | } |
| 10 | foreach ($file in @(Get-ChildItem "$Root\inputs" -Filter '*.one' | Sort-Object Name)) { |
| 11 | $output = "$Root\results\$($file.BaseName)" |
| 12 | New-Item -ItemType Directory $output -Force | Out-Null |
| 13 | Record-Phase 'cold-reset' |
| 14 | $cold = $false |
| 15 | for ($attempt = 0; $attempt -lt 5; $attempt++) { |
| 16 | $case = "C:\one-tests\runs\probe-$index-$attempt" |
| 17 | New-Item -ItemType Directory "$case\notebook" -Force | Out-Null |
| 18 | Copy-Item $file.FullName "$case\notebook\synthetic.one" |
| 19 | Get-Process ONENOTE -ErrorAction SilentlyContinue | ForEach-Object { |
| 20 | Stop-Process -InputObject $_ -Force -ErrorAction SilentlyContinue |
| 21 | if (-not $_.WaitForExit(10000)) { throw 'OneNote did not exit before the cache reset.' } |
| 22 | } |
| 23 | Start-Sleep -Milliseconds 250 |
| 24 | try { & "$PSScriptRoot\cold-current.ps1" -Root $case -CloneHost $CloneHost } |
| 25 | catch { |
| 26 | if ($_.Exception.Message -ne 'Close OneNote before resetting its test cache.') { throw } |
| 27 | continue |
| 28 | } |
| 29 | if (-not (Get-Process ONENOTE -ErrorAction SilentlyContinue)) { $cold = $true; break } |
| 30 | } |
| 31 | if (-not $cold) { throw 'OneNote kept reopening during the cache reset.' } |
| 32 | Record-Phase 'create-application' |
| 33 | $app = New-Object -ComObject OneNote.Application |
| 34 | $notebook = ''; $section = ''; $hierarchy = ''; $failure = $null; $pages = @() |
| 35 | $started = [DateTime]::UtcNow |
| 36 | try { |
| 37 | Record-Phase 'open-notebook' |
| 38 | $app.OpenHierarchy("$case\notebook", '', [ref]$notebook, 0) |
| 39 | Record-Phase 'open-section' |
| 40 | $app.OpenHierarchy("$case\notebook\synthetic.one", '', [ref]$section, 0) |
| 41 | $deadline = [DateTime]::UtcNow.AddSeconds(30) |
| 42 | do { |
| 43 | Record-Phase 'get-hierarchy' |
| 44 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) |
| 45 | [xml]$tree = $hierarchy |
| 46 | $pages = @($tree.SelectNodes('//*[local-name()="Page"]')) |
| 47 | if ($pages.Count -and $tree.DocumentElement.GetAttribute('areAllPagesAvailable') -ne 'false') { break } |
| 48 | Start-Sleep -Milliseconds 250 |
| 49 | } while ([DateTime]::UtcNow -lt $deadline) |
| 50 | if (-not $pages.Count -or $tree.DocumentElement.GetAttribute('areAllPagesAvailable') -eq 'false') { |
| 51 | throw 'The section did not finish loading its pages.' |
| 52 | } |
| 53 | $n = 0 |
| 54 | foreach ($page in $pages) { |
| 55 | $content = '' |
| 56 | Record-Phase "get-page-$n" |
| 57 | $app.GetPageContent($page.GetAttribute('ID'), [ref]$content, 1, 1) |
| 58 | [IO.File]::WriteAllText("$output\page-$n.xml", $content, [Text.Encoding]::UTF8) |
| 59 | $n++ |
| 60 | } |
| 61 | } catch { $failure = $_ | Out-String } |
| 62 | finally { |
| 63 | [IO.File]::WriteAllText("$output\hierarchy.xml", $hierarchy, [Text.Encoding]::UTF8) |
| 64 | $results.Add(@{ name=$file.BaseName; pages=$pages.Count; error=$failure; |
| 65 | seconds=([DateTime]::UtcNow - $started).TotalSeconds; |
| 66 | source_sha256=(Get-FileHash $file.FullName).Hash.ToLowerInvariant() }) |
| 67 | $results | ConvertTo-Json -Depth 5 | Set-Content "$Root\results.json" -Encoding UTF8 |
| 68 | Record-Phase 'close-notebook' |
| 69 | try { if ($notebook) { $app.CloseNotebook($notebook, $false) } } catch {} |
| 70 | [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) |
| 71 | $app = $null |
| 72 | [GC]::Collect() |
| 73 | [GC]::WaitForPendingFinalizers() |
| 74 | Record-Phase 'complete' |
| 75 | } |
| 76 | $index++ |
| 77 | } |