diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a3ca5..3d38308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -146,5 +146,8 @@ The format is based on source path as the destination, and of `Disable-Privileges`, which said that the privileges were enabled, and the spelling of the privilege in the warning of `Get-NTFSEffectiveAccess` +- Fix `-PassThru` of `Copy-Item2`, `Move-Item2`, and `Remove-Item2`, which + wrote the item also when `-WhatIf` or a declined confirmation skipped the + operation [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 f43b357..0d78423 100644 --- a/Docs/Cmdlets/Copy-Item2.md +++ b/Docs/Cmdlets/Copy-Item2.md @@ -188,6 +188,8 @@ With `-PassThru $true` the cmdlet returns a folder object for each folder that i `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, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + 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 continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. diff --git a/Docs/Cmdlets/Move-Item2.md b/Docs/Cmdlets/Move-Item2.md index 0f49d4d..06d409f 100644 --- a/Docs/Cmdlets/Move-Item2.md +++ b/Docs/Cmdlets/Move-Item2.md @@ -188,6 +188,8 @@ With `-PassThru $true` the cmdlet returns a folder object for each folder that i `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. +Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + 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 continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. diff --git a/Docs/Cmdlets/Remove-Item2.md b/Docs/Cmdlets/Remove-Item2.md index 9b28de8..cda6435 100644 --- a/Docs/Cmdlets/Remove-Item2.md +++ b/Docs/Cmdlets/Remove-Item2.md @@ -181,6 +181,8 @@ With `-PassThru` the cmdlet returns a folder object for each folder that it dele `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. +Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. + 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 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/NTFSSecurity/ItemCmdlets/CopyItem2.cs b/NTFSSecurity/ItemCmdlets/CopyItem2.cs index 1e1dd60..e0434c2 100644 --- a/NTFSSecurity/ItemCmdlets/CopyItem2.cs +++ b/NTFSSecurity/ItemCmdlets/CopyItem2.cs @@ -94,12 +94,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(resolvedPath, "Copy File")) { ((FileInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); WriteVerbose(string.Format("File '{0}' copied to '{1}'", resolvedPath, actualDestination)); + processed = true; } } else @@ -111,10 +114,11 @@ namespace NTFSSecurity Directory.CreateDirectory(actualDestination); ((DirectoryInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); WriteVerbose(string.Format("Directory '{0}' copied to '{1}'", resolvedPath, actualDestination)); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/ItemCmdlets/MoveItem2.cs b/NTFSSecurity/ItemCmdlets/MoveItem2.cs index dfbb22e..4594d2f 100644 --- a/NTFSSecurity/ItemCmdlets/MoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/MoveItem2.cs @@ -94,12 +94,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(resolvedPath, "Move File")) { ((FileInfo)item).MoveTo(actualDestination, force ? MoveOptions.ReplaceExisting : MoveOptions.CopyAllowed, PathFormat.RelativePath); WriteVerbose(string.Format("File '{0}' moved to '{1}'", resolvedPath, actualDestination)); + processed = true; } } else @@ -108,10 +111,11 @@ namespace NTFSSecurity { ((DirectoryInfo)item).MoveTo(actualDestination, force ? MoveOptions.ReplaceExisting : MoveOptions.CopyAllowed, PathFormat.RelativePath); WriteVerbose(string.Format("Directory '{0}' moved to '{1}'", resolvedPath, actualDestination)); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs index 7a20364..b3caa91 100644 --- a/NTFSSecurity/ItemCmdlets/RemoveItem2.cs +++ b/NTFSSecurity/ItemCmdlets/RemoveItem2.cs @@ -71,12 +71,15 @@ namespace NTFSSecurity try { + var processed = false; + if (item is FileInfo) { if (ShouldProcess(item.ToString(), "Remove File")) { ((FileInfo)item).Delete(force); WriteVerbose(string.Format("File '{0}' was removed", item.ToString())); + processed = true; } } else @@ -85,10 +88,11 @@ namespace NTFSSecurity { ((DirectoryInfo)item).Delete(recurse, force); WriteVerbose(string.Format("Directory '{0}' was removed", item.ToString())); + processed = true; } } - if (passThru) + if (passThru && processed) WriteObject(item); } catch (System.IO.IOException ex) diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index d9e5d0f..1646c8a 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -2181,6 +2181,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, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. 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 continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. @@ -6964,6 +6965,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. + Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. 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 continues with the next path. Before 5.0.0, it skipped the remaining paths that were passed in the same call. @@ -7579,6 +7581,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. + Before 5.0.0, `-PassThru` also wrote the item when `-WhatIf` or a declined confirmation skipped the operation. 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 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 e68ad3d..c629536 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -128,6 +128,19 @@ Describe 'Copy-Item2, Move-Item2, and Remove-Item2 with several paths' { $messages.Message | Should -Contain ("File '{0}' {1} to '{2}'" -f $first, $Verb, $target) } + + # Before 5.0.0, -PassThru wrote the item also when -WhatIf skipped the operation. + It '<_> should write nothing with -PassThru and -WhatIf' -ForEach @('Copy-Item2', 'Move-Item2', 'Remove-Item2') { + $parameters = @{ Path = $first; PassThru = $true; WhatIf = $true } + if ($_ -ne 'Remove-Item2') { + $parameters.Destination = $destination + } + + $result = @(& $_ @parameters) + + $result | Should -BeNullOrEmpty + $first | Should -Exist + } } Describe 'Copy-Item2' {