You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
89 lines
3.2 KiB
89 lines
3.2 KiB
<#
|
|
.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()
|
|
}
|
|
|