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'
+ }
+}