diff --git a/CHANGELOG.md b/CHANGELOG.md index 814b06c..298c734 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,5 +81,7 @@ The format is based on for every path when a command such as `Select-Object -First 1` stopped the pipeline, and which repeated a failed read instead of reporting the denied access +- Fix `Copy-Item2`, which failed with a `DirectoryNotFoundException` when it + copied a folder that contained files [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 4c93df3..025dd6d 100644 --- a/Docs/Cmdlets/Copy-Item2.md +++ b/Docs/Cmdlets/Copy-Item2.md @@ -184,7 +184,7 @@ By default this cmdlet returns nothing. With `-PassThru $true` it returns an `Al `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. -Copying a folder that contains files currently fails with a `CopyError` that reports a `DirectoryNotFoundException` for the first file in the folder. Copy files individually, for example by piping `Get-ChildItem2 -Recurse -File` into this cmdlet, and create the target folders beforehand. +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. diff --git a/NTFSSecurity/ItemCmdlets/CopyItem2.cs b/NTFSSecurity/ItemCmdlets/CopyItem2.cs index 79d4067..8e27a1b 100644 --- a/NTFSSecurity/ItemCmdlets/CopyItem2.cs +++ b/NTFSSecurity/ItemCmdlets/CopyItem2.cs @@ -105,6 +105,9 @@ namespace NTFSSecurity { if (ShouldProcess(resolvedPath, "Copy Directory")) { + // AlphaFS 2.2 copies into an existing folder only and otherwise fails with a + // DirectoryNotFoundException for the first file. + Directory.CreateDirectory(actualDestination); ((DirectoryInfo)item).CopyTo(actualDestination, force ? CopyOptions.None : CopyOptions.FailIfExists, PathFormat.RelativePath); WriteVerbose(string.Format("Directory '{0}' copied to '{0}'", resolvedPath, destination)); } diff --git a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml index c777abf..4380a03 100644 --- a/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml +++ b/NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml @@ -2173,7 +2173,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. - Copying a folder that contains files currently fails with a `CopyError` that reports a `DirectoryNotFoundException` for the first file in the folder. Copy files individually, for example by piping `Get-ChildItem2 -Recurse -File` into this cmdlet, and create the target folders beforehand. + 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. diff --git a/Tests/ItemCmdlets.Tests.ps1 b/Tests/ItemCmdlets.Tests.ps1 index 75af9ec..5534a0e 100644 --- a/Tests/ItemCmdlets.Tests.ps1 +++ b/Tests/ItemCmdlets.Tests.ps1 @@ -55,3 +55,28 @@ Describe 'Get-ChildItem2' { } } } + +Describe 'Copy-Item2' { + Context 'When -Path is a folder with files and subfolders' { + BeforeAll { + $source = New-TestSandboxItem -Sandbox $sandbox -Name 'Source' -Directory + $sourceFile = Join-Path -Path $source -ChildPath 'File.txt' + $sourceSubfolderFile = Join-Path -Path $source -ChildPath 'Subfolder\Other.txt' + Assert-TestSandboxPath -Sandbox $sandbox -Path $sourceFile, $sourceSubfolderFile + New-Item -ItemType Directory -Path (Split-Path -Path $sourceSubfolderFile -Parent) | Out-Null + Set-Content -LiteralPath $sourceFile -Value 'File' + Set-Content -LiteralPath $sourceSubfolderFile -Value 'Other' + } + + It 'Should copy the folder with its files and subfolders' { + $destination = Join-Path -Path $sandbox -ChildPath ('Copy-{0}' -f [guid]::NewGuid().ToString('N').Substring(0, 8)) + Assert-TestSandboxPath -Sandbox $sandbox -Path $destination + + Copy-Item2 -Path $source -Destination $destination -ErrorVariable copyErrors -ErrorAction SilentlyContinue + + $copyErrors | Should -BeNullOrEmpty + Join-Path -Path $destination -ChildPath 'File.txt' | Should -Exist + Join-Path -Path $destination -ChildPath 'Subfolder\Other.txt' | Should -Exist + } + } +}