<# .SYNOPSIS Dumps the Daniel/Ahmed 1:1 notebook tracker (.docx) as plain text via Word COM. .DESCRIPTION Word re-applies the document's MIP sensitivity label on every save, which wraps the .docx in an encrypted OLE container. That means the file cannot be read by unzipping it once it has been written once. Always read it through this script. Output is one line per paragraph, indented by list level, with a "---" rule before each dated entry. .PARAMETER DocPath Full path to the tracker .docx. .PARAMETER Last Emit only the N most recent dated entries, newest first, skipping the Template and Example blocks. Omit to dump the whole document. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$DocPath, [int]$Last = 0 ) $ErrorActionPreference = 'Stop' if (-not (Test-Path -LiteralPath $DocPath)) { throw "Tracker not found: $DocPath" } $word = $null $doc = $null $openedByUs = $false try { try { $word = [Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application') } catch { $word = New-Object -ComObject Word.Application; $word.Visible = $false } $full = (Resolve-Path -LiteralPath $DocPath).Path foreach ($d in $word.Documents) { if ($d.FullName -eq $full) { $doc = $d; break } } if (-not $doc) { $doc = $word.Documents.Open($full, [ref]$false, [ref]$true); $openedByUs = $true } $rows = @() $paras = $doc.Paragraphs for ($i = 1; $i -le $paras.Count; $i++) { $p = $paras.Item($i) $text = ($p.Range.Text -replace "[\r\a]", '').TrimEnd() $lvl = $p.Range.ListFormat.ListLevelNumber $isDate = $text.Trim() -match '^\d{1,2}/\d{1,2}/\d{4}$' $prefix = if ($lvl -ge 2) { ' - ' } elseif ($lvl -eq 1 -and $text -and -not $isDate) { ' ' } else { '' } $rows += [pscustomobject]@{ IsDate = $isDate; Raw = $text.Trim(); Text = "$prefix$text" } } if ($Last -le 0) { foreach ($r in $rows) { if ($r.IsDate) { Write-Output '---' } if ($r.Text.Trim()) { Write-Output $r.Text } } return } # Group into dated blocks. The first dated block belongs to the "Example:" section. $blocks = @() $current = $null for ($i = 0; $i -lt $rows.Count; $i++) { if ($rows[$i].IsDate) { if ($current) { $blocks += $current } $parsed = [datetime]::MinValue [void][datetime]::TryParse($rows[$i].Raw, [ref]$parsed) $current = [pscustomobject]@{ Date = $parsed; Label = $rows[$i].Raw; Lines = @() } } elseif ($current -and $rows[$i].Text.Trim()) { $current.Lines += $rows[$i].Text } } if ($current) { $blocks += $current } if ($blocks.Count -le 1) { Write-Output '(no entries yet - only the Example block is present)'; return } $entries = @($blocks[1..($blocks.Count - 1)] | Sort-Object -Property Date -Descending) foreach ($e in ($entries | Select-Object -First $Last)) { Write-Output '---' Write-Output $e.Label foreach ($l in $e.Lines) { Write-Output $l } } } finally { if ($doc -and $openedByUs) { $doc.Close([ref]$false) | Out-Null } if ($word -and $openedByUs -and $word.Documents.Count -eq 0) { $word.Quit() | Out-Null } [System.GC]::Collect() }