From 672457ef0c0c18b9bc11a1a3f33fa93531f0f3c2 Mon Sep 17 00:00:00 2001 From: klangme1ster <126453713+klangme1ster@users.noreply.github.com> Date: Thu, 24 Sep 2026 15:05:29 +0200 Subject: [PATCH] Enhancement (configuration): Add option overwrite to remove a notebook's previous output When converting into an existing notes folder, files of the same name are overwritten, but notes, folders and media that were renamed or deleted in OneNote since the last run are left behind. With overwrite = 2, a notebook's previous output is removed before converting it. The notebook's docx folder is kept, so usedocx = 2 can still reuse it. Only the notebook's own folder under notesdestpath is touched. Read-only files (e.g. inserted attachments) and paths beyond 260 characters on Windows Powershell 5 are handled. The default (1) keeps the current behavior. Co-Authored-By: Claude Opus 5.5 --- ConvertOneNote2MarkDown-v2.Tests.ps1 | 38 ++++++++++++++++++++ ConvertOneNote2MarkDown-v2.ps1 | 53 ++++++++++++++++++++++++++++ README.md | 1 + 3 files changed, 92 insertions(+) diff --git a/ConvertOneNote2MarkDown-v2.Tests.ps1 b/ConvertOneNote2MarkDown-v2.Tests.ps1 index 72d1ae7..ae96aef 100644 --- a/ConvertOneNote2MarkDown-v2.Tests.ps1 +++ b/ConvertOneNote2MarkDown-v2.Tests.ps1 @@ -1812,6 +1812,44 @@ Describe 'Convert-OneNotePage' -Tag 'Unit' { } +Describe "Remove-PreviousNotebookOutput" -Tag 'Unit' { + + Context 'Behavior' { + + BeforeEach { + $notebookDirectory = Join-Path $TestDrive 'my-notebook' + if (Test-Path -LiteralPath $notebookDirectory) { + Remove-Item -LiteralPath $notebookDirectory -Recurse -Force # Left over from a previous test (e.g. the dry run), incl. the read-only file + } + New-Item -ItemType Directory -Path (Join-Path $notebookDirectory 'docx') -Force > $null + New-Item -ItemType Directory -Path (Join-Path $notebookDirectory 'section\media') -Force > $null + 'docx' | Set-Content -LiteralPath (Join-Path $notebookDirectory 'docx\page.docx') + 'note' | Set-Content -LiteralPath (Join-Path $notebookDirectory 'section\stale-page.md') + 'pdf' | Set-Content -LiteralPath (Join-Path $notebookDirectory 'section\media\attachment.pdf') + (Get-Item -LiteralPath (Join-Path $notebookDirectory 'section\media\attachment.pdf')).IsReadOnly = $true # OneNote's inserted files are read-only + } + + It "Should remove everything in the notebook's directory except the docx folder" { + Remove-PreviousNotebookOutput -NotesDestination $TestDrive -NotebookName 'my notebook' + + Test-Path -LiteralPath (Join-Path $notebookDirectory 'section') | Should -Be $false + Test-Path -LiteralPath (Join-Path $notebookDirectory 'docx\page.docx') | Should -Be $true + } + + It "Should not remove anything on a dry run" { + Remove-PreviousNotebookOutput -NotesDestination $TestDrive -NotebookName 'my notebook' -DryRun + + Test-Path -LiteralPath (Join-Path $notebookDirectory 'section\stale-page.md') | Should -Be $true + } + + It "Should do nothing if the notebook's directory does not exist" { + { Remove-PreviousNotebookOutput -NotesDestination $TestDrive -NotebookName 'other notebook' } | Should -Not -Throw + } + + } + +} + Describe "Print-ConversionErrors" -Tag 'Unit' { Context 'Behavior' { diff --git a/ConvertOneNote2MarkDown-v2.ps1 b/ConvertOneNote2MarkDown-v2.ps1 index 3668cba..0b0b3ff 100644 --- a/ConvertOneNote2MarkDown-v2.ps1 +++ b/ConvertOneNote2MarkDown-v2.ps1 @@ -186,6 +186,16 @@ Whether to use Line Feed (LF) or Carriage Return + Line Feed (CRLF) for new line Whether to include a PDF export alongside the markdown file 1: Don't include PDF - Default 2: Include PDF +'@ + default = 1 + value = 1 + validateRange = 1,2 + } + overwrite = @{ + description = @' +Whether to remove a notebook's previous output before converting it. Without this, files of the same name are overwritten, but notes, folders and media that were renamed or deleted in OneNote since the last run are left behind. The notebook's docx folder is always kept +1: Keep previous output - Default +2: Remove previous output (except the docx folder) '@ default = 1 value = 1 @@ -528,6 +538,45 @@ Function Set-ContentNoBom { } } +Function Remove-PreviousNotebookOutput { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $NotesDestination + , + [Parameter(Mandatory)] + [ValidateNotNullOrEmpty()] + [string] + $NotebookName + , + [Parameter()] + [switch] + $DryRun + ) + + # The same directory New-SectionGroupConversionConfig converts this notebook into + $nameCompat = $NotebookName | Remove-InvalidFileNameChars + if (! $nameCompat.Trim('-. ')) { + throw "Refusing to remove previous output of notebook '$NotebookName': its directory name would be empty" + } + $notebookDirectory = [io.path]::combine( $NotesDestination.TrimEnd('/').TrimEnd('\').Replace('\', [io.path]::DirectorySeparatorChar), $nameCompat ) + if (! (Test-Path -LiteralPath $notebookDirectory -PathType Container)) { + return + } + + # Keep the docx folder, so that 'usedocx = 2' can still reuse its files + foreach ($item in @( Get-ChildItem -LiteralPath $notebookDirectory -Force | Where-Object { $_.Name -ne 'docx' } )) { + "Removing previous output: $( $item.FullName )" | Write-Verbose + if (! $DryRun) { + # A non-Win32 path, so that Windows Powershell <= 5 can also remove files beyond the 260-character path limit + $literalPath = if ($PSVersionTable.PSVersion.Major -le 5 -and $env:OS -match 'windows') { "\\?\$( $item.FullName )" } else { $item.FullName } + Remove-Item -LiteralPath $literalPath -Recurse -Force -ErrorAction Stop + } + } +} + Function New-OneNoteConnection { [CmdletBinding()] param () @@ -1432,6 +1481,10 @@ Function Convert-OneNote2MarkDown { $pageConversionConfigsAll = @() foreach ($notebook in $notebooks) { "`nConverting notebook '$( $notebook.name )'... (Ignoring deleted notes)" | Write-Host -ForegroundColor Cyan + if ($config['overwrite']['value'] -eq 2) { + "Removing previous output of notebook '$( $notebook.name )' (keeping its docx folder)" | Write-Host -ForegroundColor Cyan + Remove-PreviousNotebookOutput -NotesDestination $config['notesdestpath']['value'] -NotebookName $notebook.name -DryRun:([bool]$config['dryRun']['value']) + } New-SectionGroupConversionConfig -OneNoteConnection $OneNote -NotesDestination $config['notesdestpath']['value'] -Config $config -SectionGroups $notebook -LevelsFromRoot 0 -ErrorVariable +totalerr | Tee-Object -Variable pageConversionConfigs | Convert-OneNotePage -OneNoteConnection $OneNote -Config $config -ErrorVariable +totalerr "`nDone converting notebook '$( $notebook.name )' with $( ($pageConversionConfigs | Measure-object).Count ) notes." | Write-Host -ForegroundColor Cyan $pageConversionConfigsAll += $pageConversionConfigs diff --git a/README.md b/README.md index c23006f..857296d 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ The powershell script `ConvertOneNote2MarkDown-v2.ps1` will utilize the OneNote * Choose whether to remove `\` escape symbol that are created when converting with Pandoc * Choose whether to use Line Feed (`LF`) or Carriage Return + Line Feed (`CRLF`) for new lines * Choose whether to include a `.pdf` export alongside the `.md` file. `.md` does not preserve `InkDrawing` (i.e. overlayed drawings, highlights, pen marks) absolute positions within a page, but a `.pdf` export is a complete page snapshot that preserves `InkDrawing` absolute positions within a page. +* Choose whether to **remove a notebook's previous output** before converting it (`$overwrite = 2`), so notes, folders and media that were renamed or deleted in OneNote since the last run don't linger. The notebook's `docx` folder is always kept, so existing `.docx` files can still be reused. * Detailed logs. Run the script with `-Verbose` to see detailed logs of each page's conversion. ## Known Issues