diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ee92b2..d11fc8f 100644 --- a/CHANGELOG.md +++ b/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 diff --git a/Docs/Cmdlets/Copy-Item2.md b/Docs/Cmdlets/Copy-Item2.md index 025dd6d..9bd30c6 100644 --- a/Docs/Cmdlets/Copy-Item2.md +++ b/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 diff --git a/Docs/Cmdlets/Move-Item2.md b/Docs/Cmdlets/Move-Item2.md index addffd2..b4f3eff 100644 --- a/Docs/Cmdlets/Move-Item2.md +++ b/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 diff --git a/Docs/Cmdlets/Remove-Item2.md b/Docs/Cmdlets/Remove-Item2.md index 40d1bad..0669f84 100644 --- a/Docs/Cmdlets/Remove-Item2.md +++ b/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 diff --git a/NTFSSecurity/ItemCmdlets/CopyItem2.cs b/NTFSSecurity/ItemCmdlets/CopyItem2.cs index 8e27a1b..3e391d9 100644 --- a/NTFSSecurity/ItemCmdlets/CopyItem2.cs +++ b/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 diff --git a/NTFSSecurity/ItemCmdlets/MoveItem2.cs b/NTFSSecurity/ItemCmdlets/MoveItem2.cs index 4e93b51..2a360e1 100644 --- a/NTFSSecurity/ItemCmdlets/MoveItem2.cs +++ b/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 diff --git a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs index 0d3a135..de34ca0 100644 --- a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs +++ b/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 diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index 826e823..957a61d 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -2174,7 +2174,7 @@ PS C:\> Set-NTFSSecurityDescriptor -SecurityDescriptor $sd `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. 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. @@ -6945,7 +6945,7 @@ PS C:\Data> Get-NTFSSecurityDescriptor `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. 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. @@ -7551,7 +7551,7 @@ PS C:\Data> Get-NTFSSecurityDescriptor `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. 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. diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 3a7ca2c..31a5f48 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/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 {