Browse Source

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 <ai@example.com>
ai/release-5.0.0-rc6
Raimund Andree 4 days ago
parent
commit
511c16479e
  1. 4
      CHANGELOG.md
  2. 2
      Docs/Cmdlets/Test-Path2.md
  3. 10
      NTFSSecurity/PathCmdlets/TestPath2.cs
  4. 2
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  5. 127
      Tests/ItemCmdlets.Tests.ps1

4
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

2
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

10
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);
}
}
}

2
NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml

@ -10136,7 +10136,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:alertSet>
<maml:alert>
<maml:para>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.</maml:para>
<maml:para>A path that does not exist is not an error condition. The cmdlet writes `$false` and continues with the next path.</maml:para>
<maml:para>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 `&lt;`; before 5.0.0, such a path stopped the cmdlet with the terminating error "Illegal characters in path" in Windows PowerShell.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>

127
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 <Expected> for a <Kind> with -PathType <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'
}
}

Loading…
Cancel
Save