From 03cc896bccbe5bae007e5154b761d6ec19fdd359 Mon Sep 17 00:00:00 2001 From: Raimund Andree Date: Mon, 5 Oct 2026 00:42:30 +0200 Subject: [PATCH] fix: create the destination folder before Copy-Item2 copies a folder Defect 10. AlphaFS 2.2 copies a folder only into an existing destination folder; otherwise DirectoryInfo.CopyTo fails with a DirectoryNotFoundException for the first file, so Copy-Item2 could not copy a folder that contained files. The cmdlet now creates the destination folder first; AlphaFS then copies the files and subfolders. Tests/ItemCmdlets.Tests.ps1: 1 test. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: AI Assistant --- CHANGELOG.md | 2 ++ Docs/Cmdlets/Copy-Item2.md | 2 +- NTFSSecurity/ItemCmdlets/CopyItem2.cs | 3 +++ NTFSSecurity/en-US/NTFSSecurity.dll-Help.xml | 2 +- Tests/ItemCmdlets.Tests.ps1 | 25 ++++++++++++++++++++ 5 files changed, 32 insertions(+), 2 deletions(-) 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 + } + } +}