Browse Source

fix: continue with the next path in the *-Item2 cmdlets after an error

Defect 18. Copy-Item2, Move-Item2, and Remove-Item2 left ProcessRecord
with "return" when a path didn't exist and, for copy and move, when the
destination file existed without -Force, so the remaining paths of the
same -Path array were not processed. They now write the error and
continue with the next path.

Tests/ItemCmdlets.Tests.ps1: 5 tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: AI Assistant <ai@example.com>
pull/102/head
Raimund Andree 7 days ago
parent
commit
695d68736e
  1. 3
      CHANGELOG.md
  2. 2
      Docs/Cmdlets/Copy-Item2.md
  3. 2
      Docs/Cmdlets/Move-Item2.md
  4. 2
      Docs/Cmdlets/Remove-Item2.md
  5. 4
      NTFSSecurity/ItemCmdlets/CopyItem2.cs
  6. 4
      NTFSSecurity/ItemCmdlets/MoveItem2.cs
  7. 2
      NTFSSecurity/ItemCmdlets/RemoveItem2.cs
  8. 6
      NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml
  9. 38
      Tests/ItemCmdlets.Tests.ps1

3
CHANGELOG.md

@ -112,5 +112,8 @@ The format is based on
- Restore the `-RemoveSpecific` switch of `Remove-NTFSAccess`, which version
4.1 introduced but later versions lacked, and add it to `Remove-NTFSAudit`:
with it, the cmdlets remove only an entry that matches exactly
- Fix `Copy-Item2`, `Move-Item2`, and `Remove-Item2`, which skipped the
remaining paths of `-Path` after a path that didn't exist or, for copy and
move, a file that already existed at the destination
[Unreleased]: https://github.com/raandree/NTFSSecurity/compare/4.2.6...HEAD

2
Docs/Cmdlets/Copy-Item2.md

@ -186,7 +186,7 @@ By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Al
Before 5.0.0, copying a folder that contained files failed with a `CopyError` that reported a `DirectoryNotFoundException` for the first file in the folder.
If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.
If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call.
## RELATED LINKS

2
Docs/Cmdlets/Move-Item2.md

@ -186,7 +186,7 @@ By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Al
The cmdlet chooses between two mutually exclusive move options. Without `-Force` it moves with `CopyAllowed`, which permits a file to cross volume boundaries because Windows then copies and deletes it. With `-Force` it moves with `ReplaceExisting`, which overwrites the destination but does not request `CopyAllowed`, so a move across volumes can fail when `-Force` is specified.
If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.
If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call.
## RELATED LINKS

2
Docs/Cmdlets/Remove-Item2.md

@ -179,7 +179,7 @@ By default this cmdlet returns nothing. With `-PassThru` it returns an `Alphaleo
The module defines the aliases `rm2` and `del2` for this cmdlet.
A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.
A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet continues with the next path. Before 5.0.0, a path that did not exist made the cmdlet skip the remaining paths that were passed in the same call.
## RELATED LINKS

4
NTFSSecurity/ItemCmdlets/CopyItem2.cs

@ -71,7 +71,7 @@ namespace NTFSSecurity
catch (System.IO.FileNotFoundException ex)
{
WriteError(new ErrorRecord(ex, "FileNotFound", ErrorCategory.ObjectNotFound, resolvedPath));
return;
continue;
}
//destination is a directory
@ -88,7 +88,7 @@ namespace NTFSSecurity
if (!force & File.Exists(actualDestination))
{
WriteError(new ErrorRecord(new AlreadyExistsException(), "DestinationFileAlreadyExists", ErrorCategory.ResourceExists, actualDestination));
return;
continue;
}
try

4
NTFSSecurity/ItemCmdlets/MoveItem2.cs

@ -71,7 +71,7 @@ namespace NTFSSecurity
catch (System.IO.FileNotFoundException ex)
{
WriteError(new ErrorRecord(ex, "FileNotFound", ErrorCategory.ObjectNotFound, resolvedPath));
return;
continue;
}
//destination is a directory
@ -88,7 +88,7 @@ namespace NTFSSecurity
if (!force & File.Exists(actualDestination))
{
WriteError(new ErrorRecord(new AlreadyExistsException(), "DestinationFileAlreadyExists", ErrorCategory.ResourceExists, actualDestination));
return;
continue;
}
try

2
NTFSSecurity/ItemCmdlets/RemoveItem2.cs

@ -66,7 +66,7 @@ namespace NTFSSecurity
catch (System.IO.FileNotFoundException ex)
{
WriteError(new ErrorRecord(ex, "FileNotFound", ErrorCategory.ObjectNotFound, path));
return;
continue;
}
try

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

@ -2174,7 +2174,7 @@ PS C:\&gt; Set-NTFSSecurityDescriptor -SecurityDescriptor $sd</dev:code>
<maml:alert>
<maml:para>`Copy-Item2` copies through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Copy-Item` cmdlet.</maml:para>
<maml:para>Before 5.0.0, copying a folder that contained files failed with a `CopyError` that reported a `DirectoryNotFoundException` for the first file in the folder.</maml:para>
<maml:para>If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.</maml:para>
<maml:para>If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -6945,7 +6945,7 @@ PS C:\Data&gt; Get-NTFSSecurityDescriptor</dev:code>
<maml:alert>
<maml:para>`Move-Item2` moves through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it handles source and destination paths that exceed the 260-character `MAX_PATH` limit of the built-in `Move-Item` cmdlet.</maml:para>
<maml:para>The cmdlet chooses between two mutually exclusive move options. Without `-Force` it moves with `CopyAllowed`, which permits a file to cross volume boundaries because Windows then copies and deletes it. With `-Force` it moves with `ReplaceExisting`, which overwrites the destination but does not request `CopyAllowed`, so a move across volumes can fail when `-Force` is specified.</maml:para>
<maml:para>If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.</maml:para>
<maml:para>If a path in `-Path` does not exist or the destination file exists and `-Force` is missing, the cmdlet writes a non-terminating error and continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>
@ -7551,7 +7551,7 @@ PS C:\Data&gt; Get-NTFSSecurityDescriptor</dev:code>
<maml:alert>
<maml:para>`Remove-Item2` deletes through the AlphaFS library (`Alphaleonis.Win32.Filesystem`), which is why it reaches items whose path exceeds the 260-character `MAX_PATH` limit of the built-in `Remove-Item` cmdlet. Deletion is permanent; the cmdlet does not use the Recycle Bin.</maml:para>
<maml:para>The module defines the aliases `rm2` and `del2` for this cmdlet.</maml:para>
<maml:para>A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet skips the remaining paths that were passed in the same call. Items that arrive one by one through the pipeline are not affected, because each of them is processed separately.</maml:para>
<maml:para>A path that does not exist causes the error `FileNotFound`, and a deletion that the file system rejects causes a `DeleteError`. In both cases the cmdlet continues with the next path. Before 5.0.0, a path that did not exist made the cmdlet skip the remaining paths that were passed in the same call.</maml:para>
</maml:alert>
</maml:alertSet>
<command:examples>

38
Tests/ItemCmdlets.Tests.ps1

@ -80,6 +80,44 @@ Describe 'Get-ChildItem2' {
}
}
Describe 'Copy-Item2, Move-Item2, and Remove-Item2 with several paths' {
BeforeEach {
$folder = New-TestSandboxItem -Sandbox $sandbox -Name 'Several' -Directory
$missing = Join-Path -Path $folder -ChildPath 'Missing.txt'
$first = Join-Path -Path $folder -ChildPath 'First.txt'
$second = Join-Path -Path $folder -ChildPath 'Second.txt'
$destination = Join-Path -Path $folder -ChildPath 'Destination'
Assert-TestSandboxPath -Sandbox $sandbox -Path $missing, $first, $second, $destination
Set-Content -LiteralPath $first -Value 'First'
Set-Content -LiteralPath $second -Value 'Second'
New-Item -ItemType Directory -Path $destination | Out-Null
}
# Before 5.0.0, the cmdlets stopped processing -Path at the first failing path.
It 'Remove-Item2 should continue after a path that does not exist' {
Remove-Item2 -Path $missing, $first -ErrorVariable itemErrors -ErrorAction SilentlyContinue
$itemErrors | Should -HaveCount 1
$first | Should -Not -Exist
}
It '<_> should continue after a path that does not exist' -ForEach @('Copy-Item2', 'Move-Item2') {
& $_ -Path $missing, $first -Destination $destination -ErrorVariable itemErrors -ErrorAction SilentlyContinue
$itemErrors | Should -HaveCount 1
Join-Path -Path $destination -ChildPath 'First.txt' | Should -Exist
}
It '<_> should continue after a file that exists at the destination' -ForEach @('Copy-Item2', 'Move-Item2') {
Set-Content -LiteralPath (Join-Path -Path $destination -ChildPath 'First.txt') -Value 'Existing'
& $_ -Path $first, $second -Destination $destination -ErrorVariable itemErrors -ErrorAction SilentlyContinue
$itemErrors | Should -HaveCount 1
Join-Path -Path $destination -ChildPath 'Second.txt' | Should -Exist
}
}
Describe 'Copy-Item2' {
Context 'When -Path is a folder with files and subfolders' {
BeforeAll {

Loading…
Cancel
Save