# make sure that $a exists as a directory.
$a = "C:\tmp\src"
$b = "C:\tmp\dest"
# $a has the following structure
# C:\tmp\src
# |_ foo.txt
# |_ a
# \_ doom.txt
# |_ b
# \_ bar.txt
Get-ChildItem -Path $a | ForEach-Object { Copy-Item -Path $_.FullName -Destination $b -Recurse }
C:\tmp\dest should look like this:
C:\tmp\dest
|_ foo.txt
|_ a
\_ doom.txt
|_ b
\_ bar.txt
It ends up looking like this:
C:\tmp\dest
|_ foo.txt
|_ doom.txt
|_ b
\_ bar.txt
> $PSVersionTable
Name Value
---- -----
PSVersion 6.0.2
PSEdition Core
GitCommitId v6.0.2
OS Microsoft Windows 10.0.14393
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
Other (hopefully) useful info:
This happens in Powershell 5.1 as well.
Found a bug report here from July 2016 which seems to describe the same issue.
Happens with the -Recurse, -Container, and -Force parameters provided as well.
Also happens with the Move-Item command
The SO answer you link to links back to #2934, but the latter is about inconsistent behavior, because the behavior depends on whether the target directory already exists or not.
The question comes down to this: If you do
Copy-Item -Recurse <source-dir-path> <dest-dir-path>
should you end up with (a) <dest-dir-path>/<contents-of-source-dir> or (b) <dest-dir-path>/<source-dir-name>/<contents-of-source-dir>?
Currently, you get (a) if <dest-dir-path> didn't previously exist, and (b) if it did, and that inconsistency is problematic.
It sounds like you're saying that (b) is the behavior you expect - and at this point I'm unclear on what the right answer is:
xcopy /S on Windows seemingly always does (a)cp -R on Unix actually exhibits the same inconsistency as Copy-Item -Recurse My mistake on the SO link, you're correct that isn't relevant to this bug report. I've removed it from the second comment.
My problem is: assuming <dest-dir-path> doesn't already exist; if there are multiple sub directories in <source-dir-path> (lets assume foo and bar) both Copy-Item and Move-Item will take the contents of bar and put them in the root of <dest-dir-path> without copying the bar folder as well.
You should be able to see a "graphical" representation of what I described in the example at the top as well I think. I expect doom.txt to be copied to the folder a but instead it is dropped into the root (C:\tmp\dest) and the folder a is never copied over.
@huffstler:
Yes, but that is the same problem as #2934, just in the context of a larger command:
When subdir. a is being copied, $b doesn't exist yet, so a's content is being copied _directly_ into $b (behavior (a) above).
By the time subdir. b is copied, $b already exists, and that's when the inconsistency kicks in: b's content is copied to $b/b (behavior (b) above).
Therefore, if you run the command again, you do get the desired behavior.
Given the current behavior, you can work around the problem as follows:
Get-ChildItem -Path $a |
ForEach-Object { New-Item -Force -Type Directory $b } {
Copy-Item -Recurse -Path $_.FullName -Destination $b
}
But the larger question is whether this inconsistency should be resolved toward behavior (a) or behavior (b).
You clearly expected (a), but others may have different expectations, based on xcopy.
Frankly, I was baffled to find the same inconsistency as with Copy-Item -Recurse in the Unix world (cp -R).
The only way to currently get predictable behavior is to:
Ensure that the target dir. already exists.
Then, depending on whether you want behavior (a) or (b):
To get behavior (a): Explicitly target the content of the source directory:
Copy-Item -Recurse -Force $a/* $b
Note the need for -Force, which is needed to ensure that _hidden_ items are copied too.
With cp on Unix, you can more simply just $a/., but that doesn't work in PowerShell.
To get behavior (b): No further action needed.
If a change is made, it will be a _breaking_ one.
The alternative is to merely _document_ the behavior and live with it, but I personally find that unsatisfactory.
Unless you disagree with my analysis, I suggest closing this issue as a duplicate of #2934; I'll migrate my findings there.
I experimented a little and found that "Unix" (I'm using cygwin) cp -R achieves almost the same result as Copy-Item.
Example:
$ pwd
~/tmp
# Contents
# a
# a\boo
# a\boo\c.d
# a\doom.txt
# a\foo
# a\foo\a.b
# z is a directory and doesn't exist yet.
cp -R a z
# contents of z
# z
# z\boo
# z\boo\c.d
# z\doom
# z\foo
# z\foo\a.b
If another cp command is given, you will see directory a appear inside of z, but that's not the point of this comment.
In this scenario, you'll see that although the directory did not exist before the copy, the contents of boo and the structure of the directory are persisted. I still agree with your analysis, I only wanted to point out the slight differences between the two scenarios. (That cp is "less-wrong" than Copy-Item)
@huffstler: Good point; yes, there is an additional bug, which I've only a couple of hours ago recorded in #7010 - and which I now realize is a variation of yours.
I encounter the same problem when I want to copy the subfolder structure of a source folder to an existing target folder
I experimented a little and found that "Unix" (I'm using cygwin)
cp -Rachieves _almost_ the same result asCopy-Item.Example:
$ pwd ~/tmp # Contents # a # a\boo # a\boo\c.d # a\doom.txt # a\foo # a\foo\a.b # z is a directory and doesn't exist yet. cp -R a z # contents of z # z # z\boo # z\boo\c.d # z\doom # z\foo # z\foo\a.bIf another
cpcommand is given, you will see directoryaappear inside ofz, but that's not the point of this comment.In this scenario, you'll see that although the directory did not exist before the copy, the contents of
booand the structure of the directory are persisted. I still agree with your analysis, I only wanted to point out the slight differences between the two scenarios. (Thatcpis "less-wrong" thanCopy-Item)
Well, in UNIX world you can always do cp -R a/ z.
Trailing slashes are important.
@army1349:
Unfortunately, it's too late to introduce such semantics to PowerShell, because it would be a breaking change.
Aside from that:
While cp -R a/ b indeed consistently copies the _contents_ of directory a to directory b, whether or not b previously existed:
it is only _BSD_ cp (as also used on macOS) that behaves this way, _not_ the (more common, on all Linux platforms) _GNU_ cp.
Even BSD cp, however, does _not_ apply the same logic to the _destination_ directory argument: cp -R a b/ is effectively the same as cp -R a b, with the ambiguity discussed; that is, you cannot force a copy of a to be placed _inside_ b if b doesn't exist yet.