From 9b82b662da306a3b6e9bcf8ad7691341b5dad15e Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 12 Oct 2026 18:46:06 +0200 Subject: [PATCH 1/2] Fix: Fix conversion of subpages of pages with titles ending with a period Windows strips trailing periods (and spaces) from file and folder names. A page titled e.g. `Mult. Diff.` had its subpage folder created as `Mult.-Diff` by `New-Item` and pandoc, but on Powershell 5 its subpages were then read via the long path `\?\...\Mult.-Diff.\subpage.md`, which does not strip the period. This failed with `Cannot find path ... because it does not exist`, leaving image references and markdown content unprocessed. Now trailing periods and spaces are removed from file and folder names, including names truncated by `$mdFileNameAndFolderNameMaxLength`. Co-Authored-By: Claude Opus 5.5 --- ConvertOneNote2MarkDown-v2.Tests.ps1 | 61 ++++++++++++++++++++++++++++ ConvertOneNote2MarkDown-v2.ps1 | 4 +- 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/ConvertOneNote2MarkDown-v2.Tests.ps1 b/ConvertOneNote2MarkDown-v2.Tests.ps1 index 72d1ae7..a042646 100644 --- a/ConvertOneNote2MarkDown-v2.Tests.ps1 +++ b/ConvertOneNote2MarkDown-v2.Tests.ps1 @@ -262,6 +262,24 @@ Describe "Remove-InvalidFileNameChars" -Tag 'Unit' { $result | Should -Be $expectedFileName } + It "Should remove trailing periods" { + $fileName = 'foo. bar.' + $expectedFileName = 'foo.-bar' + + $result = Remove-InvalidFileNameChars -Name $fileName + + $result | Should -Be $expectedFileName + } + + It "Should remove trailing periods and spaces if -KeepPathSpaces" { + $fileName = 'foo. bar. .' + $expectedFileName = 'foo. bar' + + $result = Remove-InvalidFileNameChars -Name $fileName -KeepPathSpaces + + $result | Should -Be $expectedFileName + } + } } @@ -313,6 +331,15 @@ Describe "Truncate-PathFileName" -Tag 'Unit' { $path | Truncate-PathFileName -Length $length | Should -Be $expectedPath } + It "Should remove trailing periods and spaces from a truncated filename" { + $drive = "C:$( [io.path]::DirectorySeparatorChar )" # E.g. C:\ + $path = $drive + ("a" * 100) + '. .' + ("b" * 100) + $length = 103 + $expectedPath = $drive + ("a" * 100) + + $path | Truncate-PathFileName -Length $length | Should -Be $expectedPath + } + } } @@ -618,6 +645,22 @@ function Get-FakeOneNoteHierarchyWithDuplicatePageNames { $hierarchy } +function Get-FakeOneNoteHierarchyWithPageNamesEndingWithAPeriod { + # Sample outerXML of a hierarchy object. Here we have one notebook 'test' with a page and its subpage, both with names ending with a period + $hierarchy = @' + + + + + + + + + +'@ -as [xml] + $hierarchy +} + function Get-FakeOneNotePageContent { # Sample outerXML of a page object $page = @' @@ -817,6 +860,24 @@ Describe 'New-SectionGroupConversionConfig' -Tag 'Unit' { } } + It "Should determine file and folder paths without trailing periods for pages with names ending with a period" { + $fakeHierarchy = Get-FakeOneNoteHierarchyWithPageNamesEndingWithAPeriod + $params['SectionGroups'] = $fakeHierarchy.Notebooks.Notebook + $result = @( New-SectionGroupConversionConfig @params 6>$null ) + + $result.Count | Should -Be 2 + + $pageCfg1 = $result[0] # First level page + $pageCfg2 = $result[1] # Second level page preceded by a first level page + + $pageCfg1['filePathRel'] | Should -Be 'p0.0-test' + $pageCfg1['fileName'] | Should -Be 'p0.0-test.md' + $pageCfg2['pagePrefix'] | Should -Be "p0.0-test$( [io.path]::DirectorySeparatorChar )" + $pageCfg2['filePathRel'] | Should -Be "p0.0-test$( [io.path]::DirectorySeparatorChar )p0.1-test" + $pageCfg2['fileName'] | Should -Be 'p0.1-test.md' + $pageCfg2['fileDirectory'] | Should -Match "$( [regex]::Escape("$( [io.path]::DirectorySeparatorChar )p0.0-test") )$" + } + It "Should determine attachment references correctly" { $result = @( New-SectionGroupConversionConfig @params 6>$null ) diff --git a/ConvertOneNote2MarkDown-v2.ps1 b/ConvertOneNote2MarkDown-v2.ps1 index 3668cba..08a84c6 100644 --- a/ConvertOneNote2MarkDown-v2.ps1 +++ b/ConvertOneNote2MarkDown-v2.ps1 @@ -404,7 +404,7 @@ Function Truncate-PathFileName { $name = Split-Path $Path -Leaf if ($name.Length -gt $maxLength) { $parent = Split-Path $Path -Parent - $truncatedName = $name.Substring(0, $maxLength) + $truncatedName = $name.Substring(0, $maxLength).TrimEnd('.', ' ') # Windows does not support file or folder names ending with periods or spaces [io.path]::combine( $parent, $truncatedName ) }else { $Path @@ -433,6 +433,8 @@ Function Remove-InvalidFileNameChars { } else { $newName -replace "\s", "-" } + # Remove trailing periods and spaces. Windows strips them from file and folder names, so a folder 'foo.' would be created as 'foo', and long paths like '\\?\C:\notes\foo.\bar.md' would not be found + $newName = $newName.TrimEnd('.', ' ') return $newName } From a8b3cbf7b1a9efebe605c0f93cd592b6cd190a94 Mon Sep 17 00:00:00 2001 From: Elias Date: Mon, 12 Oct 2026 18:47:57 +0200 Subject: [PATCH 2/2] Enhancement: Include markdown file path in error when markdown content mutation fails Some exceptions do not mention the file path (e.g. when Windows Defender blocks a file: `Operation did not complete successfully because the file contains a virus or potentially unwanted software`), making it impossible to tell which page failed. Co-Authored-By: Claude Opus 5.5 --- ConvertOneNote2MarkDown-v2.Tests.ps1 | 8 ++++++++ ConvertOneNote2MarkDown-v2.ps1 | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/ConvertOneNote2MarkDown-v2.Tests.ps1 b/ConvertOneNote2MarkDown-v2.Tests.ps1 index a042646..ca31fef 100644 --- a/ConvertOneNote2MarkDown-v2.Tests.ps1 +++ b/ConvertOneNote2MarkDown-v2.Tests.ps1 @@ -1853,6 +1853,14 @@ Describe 'Convert-OneNotePage' -Tag 'Unit' { Assert-MockCalled -CommandName Set-ContentNoBom -ParameterFilter { $LiteralPath } -Times 1 -Scope It } + It "Does not halt conversion if mutating markdown content fails, and reports the markdown file path" { + Mock Get-Content -ParameterFilter { $LiteralPath -and ! $Raw } { throw } + + $err = Convert-OneNotePage @params 6>$null 2>&1 + + $err.Exception.Message | Select-Object -First 1 | Should -match "Failed to mutate markdown content of $( [regex]::Escape($params['ConversionConfig']['filePathNormal']) )" + } + It "Does a dry run" { Mock New-Item { 'foo' } Mock Remove-Item { 'foo' } diff --git a/ConvertOneNote2MarkDown-v2.ps1 b/ConvertOneNote2MarkDown-v2.ps1 index 08a84c6..a429c33 100644 --- a/ConvertOneNote2MarkDown-v2.ps1 +++ b/ConvertOneNote2MarkDown-v2.ps1 @@ -1303,7 +1303,7 @@ Function Convert-OneNotePage { Set-ContentNoBom -LiteralPath $pageCfg['filePath'] -Value $content -ErrorAction Stop # Use -LiteralPath so that characters like '(', ')', '[', ']', '`', "'", '"' are supported. Or else we will get an error "Cannot find path 'xxx' because it does not exist" } }catch { - Write-Error "Failed to mutate markdown content: $( $_.Exception.Message )" + Write-Error "Failed to mutate markdown content of $( $pageCfg['filePathNormal'] ). Exception: $( $_.Exception.Message )" } "Markdown file ready: $( $pageCfg['filePathNormal'] )" | Write-Host -ForegroundColor Green