Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy-Item puts contents of 1st directory in root of target folder. #7005

Closed
huffstler opened this issue Jun 5, 2018 · 13 comments
Closed

Copy-Item puts contents of 1st directory in root of target folder. #7005

huffstler opened this issue Jun 5, 2018 · 13 comments
Labels
Area-FileSystem-Provider specific to the FileSystem provider Resolution-No Activity Issue has had no activity for 6 months or more WG-Cmdlets-Management cmdlets in the Microsoft.PowerShell.Management module WG-Engine-Providers built-in PowerShell providers such as FileSystem, Certificates, Registry, etc.

Comments

@huffstler
Copy link

huffstler commented Jun 5, 2018

Steps to reproduce

# 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 }

Expected behavior

C:\tmp\dest should look like this:

C:\tmp\dest
   |_ foo.txt
   |_ a
     \_ doom.txt
   |_ b
     \_ bar.txt

Actual behavior

It ends up looking like this:

C:\tmp\dest
   |_ foo.txt
   |_ doom.txt
   |_ b
     \_ bar.txt

Environment data

> $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
@huffstler
Copy link
Author

huffstler commented Jun 5, 2018

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

@mklement0
Copy link
Contributor

mklement0 commented Jun 6, 2018

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

@iSazonov iSazonov added Issue-Discussion the issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif WG-Cmdlets-Management cmdlets in the Microsoft.PowerShell.Management module labels Jun 6, 2018
@huffstler
Copy link
Author

huffstler commented Jun 6, 2018

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.

@mklement0
Copy link
Contributor

mklement0 commented Jun 6, 2018

@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.

@huffstler
Copy link
Author

huffstler commented Jun 6, 2018

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)

@mklement0
Copy link
Contributor

@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.

@chucklu
Copy link
Contributor

chucklu commented Sep 12, 2018

I encounter the same problem when I want to copy the subfolder structure of a source folder to an existing target folder

@army1349
Copy link

army1349 commented Oct 1, 2019

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)

Well, in UNIX world you can always do cp -R a/ z.
Trailing slashes are important.

@mklement0
Copy link
Contributor

mklement0 commented Oct 4, 2019

@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.

@iSazonov iSazonov added Area-FileSystem-Provider specific to the FileSystem provider WG-Engine-Providers built-in PowerShell providers such as FileSystem, Certificates, Registry, etc. and removed Issue-Discussion the issue may not have a clear classification yet. The issue may generate an RFC or may be reclassif labels Nov 29, 2021
Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

2 similar comments
Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

Copy link
Contributor

This issue has not had any activity in 6 months, if this is a bug please try to reproduce on the latest version of PowerShell and reopen a new issue and reference this issue if this is still a blocker for you.

@microsoft-github-policy-service microsoft-github-policy-service bot added Resolution-No Activity Issue has had no activity for 6 months or more labels Nov 16, 2023
Copy link
Contributor

This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-FileSystem-Provider specific to the FileSystem provider Resolution-No Activity Issue has had no activity for 6 months or more WG-Cmdlets-Management cmdlets in the Microsoft.PowerShell.Management module WG-Engine-Providers built-in PowerShell providers such as FileSystem, Certificates, Registry, etc.
Projects
None yet
Development

No branches or pull requests

5 participants