From 511c16479ebec791256f0e7e4179a96efa19fa23 Mon Sep 17 00:00:00 2001 From: Raimund Andree Date: Thu, 8 Oct 2026 08:28:27 +0000 Subject: [PATCH] fix: return false from Test-Path2 for a path with invalid characters In Windows PowerShell, .NET rejects a path with a character that Windows doesn't allow in names, such as | or <, and Test-Path2 stopped with the terminating error "Illegal characters in path". Such an item can't exist, so the cmdlet now writes false, as in PowerShell 7 and like Test-Path. Add tests for every -PathType, long paths, relative paths, and the pipeline, and for Get-DiskSpace, which had no tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant --- CHANGELOG.md | 4 + Docs/Cmdlets/Test-Path2.md | 2 +- NTFSSecurity/PathCmdlets/TestPath2.cs | 10 ++ NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml | 2 +- Tests/ItemCmdlets.Tests.ps1 | 127 +++++++++++++++++++ 5 files changed, 143 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2144024..770d467 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -269,6 +269,10 @@ The format is based on - Fix `Get-NTFSEffectiveAccess`, which returned no access when the computer of `-ServerName` couldn't be reached, although it warned that it had calculated the result on this computer; it now returns that result +- Fix `Test-Path2`, which stopped with the terminating error "Illegal + characters in path" in Windows PowerShell for a path with a character + that Windows doesn't allow in names, such as `|`; it now returns `$false` + for such a path, as in PowerShell 7 - Fix the cmdlets that enable the Backup, Restore, Take Ownership, and Security privileges, which left them enabled in the session when a later command, such as `Select-Object -First`, or a terminating error stopped diff --git a/Docs/Cmdlets/Test-Path2.md b/Docs/Cmdlets/Test-Path2.md index 41e14d6..bf28c32 100644 --- a/Docs/Cmdlets/Test-Path2.md +++ b/Docs/Cmdlets/Test-Path2.md @@ -117,7 +117,7 @@ For each path, the cmdlet writes `$true` when the item exists and matches `-Path The cmdlet resolves paths through the AlphaFS library, which is not bound by the 260-character `MAX_PATH` limit of the Windows PowerShell file system provider. Use `Test-Path2` instead of `Test-Path` when a path can be longer than that limit. -A path that does not exist is not an error condition. The cmdlet writes `$false` and continues with the next path. +A path that does not exist is not an error condition. The cmdlet writes `$false` and continues with the next path. This also applies to a path with a character that Windows doesn't allow in names, such as `|` or `<`; before 5.0.0, such a path stopped the cmdlet with the terminating error "Illegal characters in path" in Windows PowerShell. ## RELATED LINKS diff --git a/NTFSSecurity/PathCmdlets/TestPath2.cs b/NTFSSecurity/PathCmdlets/TestPath2.cs index b443d73..b087a2b 100644 --- a/NTFSSecurity/PathCmdlets/TestPath2.cs +++ b/NTFSSecurity/PathCmdlets/TestPath2.cs @@ -68,6 +68,16 @@ namespace NTFSSecurity { WriteError(new ErrorRecord(ex, "PathNotFound", ErrorCategory.ObjectNotFound, path)); } + // In Windows PowerShell, .NET rejects a path with a character that Windows doesn't allow in names. + // Such an item can't exist, so the cmdlet writes $false, as in PowerShell 7 and like Test-Path. + catch (System.ArgumentException) + { + WriteObject(false); + } + catch (System.NotSupportedException) + { + WriteObject(false); + } } } diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index 0f6bb54..1312a20 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -10136,7 +10136,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd The cmdlet resolves paths through the AlphaFS library, which is not bound by the 260-character `MAX_PATH` limit of the Windows PowerShell file system provider. Use `Test-Path2` instead of `Test-Path` when a path can be longer than that limit. - A path that does not exist is not an error condition. The cmdlet writes `$false` and continues with the next path. + A path that does not exist is not an error condition. The cmdlet writes `$false` and continues with the next path. This also applies to a path with a character that Windows doesn't allow in names, such as `|` or `<`; before 5.0.0, such a path stopped the cmdlet with the terminating error "Illegal characters in path" in Windows PowerShell. diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 36fd8e2..cfd91df 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -245,3 +245,130 @@ Describe 'Copy-Item2' { } } } + +Describe 'Test-Path2' { + BeforeAll { + $folder = New-TestSandboxItem -Sandbox $sandbox -Name 'TestPath' -Directory + $file = Join-Path -Path $folder -ChildPath 'File.txt' + Assert-TestSandboxPath -Sandbox $sandbox -Path $file + Set-Content -LiteralPath $file -Value 'File' + $missing = Join-Path -Path $folder -ChildPath 'Missing.txt' + $paths = @{ 'file' = $file; 'folder' = $folder; 'missing item' = $missing } + } + + It 'Should return for a with -PathType ' -ForEach @( + @{ Kind = 'file'; PathType = 'Any'; Expected = $true } + @{ Kind = 'folder'; PathType = 'Any'; Expected = $true } + @{ Kind = 'missing item'; PathType = 'Any'; Expected = $false } + @{ Kind = 'file'; PathType = 'Leaf'; Expected = $true } + @{ Kind = 'folder'; PathType = 'Leaf'; Expected = $false } + @{ Kind = 'missing item'; PathType = 'Leaf'; Expected = $false } + @{ Kind = 'file'; PathType = 'Container'; Expected = $false } + @{ Kind = 'folder'; PathType = 'Container'; Expected = $true } + @{ Kind = 'missing item'; PathType = 'Container'; Expected = $false } + ) { + $result = @(Test-Path2 -Path $paths[$Kind] -PathType $PathType -ErrorAction Stop) + + $result | Should -HaveCount 1 + $result[0] | Should -BeOfType [bool] + $result[0] | Should -Be $Expected + } + + It 'Should write one value per path in the order of the paths' { + $result = @(Test-Path2 -Path $file, $missing, $folder -ErrorAction Stop) + + $result -join ',' | Should -Be 'True,False,True' + } + + It 'Should take the items from the pipeline' { + $result = @(Get-ChildItem -LiteralPath $folder | Test-Path2 -PathType Leaf -ErrorAction Stop) + + $result -join ',' | Should -Be 'True' + } + + It 'Should resolve a relative path against the current location' { + $relative = Join-Path -Path (Split-Path -Path $folder -Leaf) -ChildPath 'File.txt' + + Test-Path2 -Path $relative -ErrorAction Stop | Should -BeTrue + } + + It 'Should find a folder whose path is longer than 260 characters' { + $longRoot = Join-Path -Path $folder -ChildPath 'Long' + Assert-TestSandboxPath -Sandbox $sandbox -Path $longRoot + $long = Join-Path -Path $longRoot -ChildPath (('A' * 100), ('B' * 100), ('C' * 100) -join '\') + Add-Type -Path (Join-Path -Path (Get-Module -Name NTFSSecurity).ModuleBase -ChildPath 'AlphaFS.dll') + [Alphaleonis.Win32.Filesystem.Directory]::CreateDirectory($long) | Out-Null + try { + $long.Length | Should -BeGreaterThan 260 + + Test-Path2 -Path $long -PathType Container -ErrorAction Stop | Should -BeTrue + } finally { + # Remove-TestSandbox can't delete paths longer than 260 characters (#110). + [Alphaleonis.Win32.Filesystem.Directory]::Delete($longRoot, $true) + } + } + + # Before 5.0.0-rc6, a path with a character that Windows doesn't allow in file names stopped the cmdlet with a + # terminating "Illegal characters in path" error in Windows PowerShell. Such an item can't exist, so the cmdlet + # writes $false like for any other missing item, as in PowerShell 7 and like Test-Path. + It 'Should return $false for a path with the character <_> and continue with the next path' -ForEach @('|', '<', '>', '"', '*', '?') { + $invalid = Join-Path -Path $folder -ChildPath ('a{0}b' -f $_) + + $result = @(Test-Path2 -Path $invalid, $file -ErrorVariable testErrors -ErrorAction SilentlyContinue) + + $testErrors | Should -BeNullOrEmpty + $result -join ',' | Should -Be 'False,True' + } +} + +Describe 'Get-DiskSpace' { + BeforeAll { + $systemDrive = New-Object -TypeName 'System.IO.DriveInfo' -ArgumentList $env:SystemDrive + } + + It 'Should return the size of the system drive' { + $result = @(Get-DiskSpace -DriveLetter $env:SystemDrive -ErrorAction Stop) + + $result | Should -HaveCount 1 + $result[0] | Should -BeOfType [Alphaleonis.Win32.Filesystem.DiskSpaceInfo] + $result[0].DriveName | Should -Be ('{0}\' -f $env:SystemDrive) + $result[0].TotalNumberOfBytes | Should -Be $systemDrive.TotalSize + } + + It 'Should report free space and clusters that fit the size' { + $result = Get-DiskSpace -DriveLetter $env:SystemDrive -ErrorAction Stop + + $result.TotalNumberOfFreeBytes | Should -BeLessOrEqual $result.TotalNumberOfBytes + $result.FreeBytesAvailable | Should -BeLessOrEqual $result.TotalNumberOfFreeBytes + $result.ClusterSize | Should -Be ($result.BytesPerSector * $result.SectorsPerCluster) + $result.NumberOfFreeClusters | Should -BeLessOrEqual $result.TotalNumberOfClusters + } + + It 'Should return the volumes with a size greater than zero without -DriveLetter' { + $result = @(Get-DiskSpace -WarningAction SilentlyContinue -ErrorAction Stop) + + $result | Should -Not -BeNullOrEmpty + $result | ForEach-Object -Process { $_.TotalNumberOfBytes | Should -BeGreaterThan 0 } + $result.TotalNumberOfBytes | Should -Contain $systemDrive.TotalSize + } + + It 'Should warn and return nothing for a drive letter without a volume' { + $used = @((Get-PSDrive -PSProvider FileSystem).Name) + @([System.IO.DriveInfo]::GetDrives() | ForEach-Object -Process { $_.Name.Substring(0, 1) }) + $letter = [char[]](68..90) | Where-Object -FilterScript { [string] $_ -notin $used } | Select-Object -Last 1 + if (-not $letter) { + Set-ItResult -Skipped -Because 'every drive letter is in use' + return + } + + $result = @(Get-DiskSpace -DriveLetter "${letter}:" -WarningVariable spaceWarnings -WarningAction SilentlyContinue -ErrorVariable spaceErrors -ErrorAction SilentlyContinue) + + $result | Should -BeNullOrEmpty + $spaceErrors | Should -BeNullOrEmpty + $spaceWarnings.Message | Should -Be "Could not get drive details for '${letter}:'" + } + + It 'Should reject a drive letter without a colon' { + { Get-DiskSpace -DriveLetter 'C' -ErrorAction Stop } | + Should -Throw -ErrorId 'ParameterArgumentValidationError,NTFSSecurity.GetDiskSpace' + } +}