PowerShell 第4回 Copy-Itemのフォルダ内ファイルのコピー動作

#はじめに
PowerShellでまたしても、はまってしまいました。
Copy-Itemの挙動が変なんです。解決はしましたが。
ということで、それを備忘としてまとめておきます。

#今回実施する内容
Copy-Itemを使用して、フォルダ(c:\temp\source)に存在するファイル(テキストファイル)を別のフォルダ(c:\temp\target)にコピーします。

```
c:
+--Temp
   +--source
       +--test.txt
       +--test2.txt
       +--test.rtf

↓下へコピー
c:
+--Temp
   +--target
```


#ソースコード(Git Hub)
[PowerShell_04_Copy-Item](https://github.com/Build-Mount/PowerShell_04_Copy-Item)

#環境
OS: Windows 10 JP (64bit)
PowerShell version: 5.1.19041.1

#参考
[Copy-itemを使ったディレクトリコピーはしないほうが無難なのでは、という話](https://mk-55.hatenablog.com/entry/2016/09/19/155051)
今回のコピー動作について、記載された記事です。解決方法の記載もあります。

[Copy-Item -Recurse の振舞](https://www.vwnet.jp/Windows/PowerShell/copy/Copy-Recurse.htm)
今回のコピー動作について、記載された記事です。

[MicrosoftのCopy-Itemのリファレンス](https://docs.microsoft.com/ja-jp/powershell/module/microsoft.powershell.management/copy-item?view=powershell-7.1)
今回の事象について説明があります。

#用語
なし

#Copy-Itemの変な挙動
Copy-Itemを使用してコピー元のフォルダ内(source)に存在するファイルをコピー先のフォルダにコピーしたいのですが、コピー先のフォルダ(target)配下にコピー元のフォルダ名のフォルダが作成されて、そこにファイルがコピーされます。

```
c:
+--Temp
   +--source
       +--test.txt
       +--test2.txt
       +--test.rtf

↓targetの下にsourceフォルダができてそこにコピーされる。
c:
+--Temp
   +--target
      +--source
         +test.txt
         +test2.txt
```

##ソースコード
```Powershell:Copy-Item_fail.ps1
try {
    $source = "C:\Temp\source"
    $target = "C:\Temp\target"
    Copy-Item -Filter "*.txt" -Path $source -Destination $target -Recurse
} catch {
    Write-Host $Error[0]
}
```

##変な挙動の説明
単に、Copy-Itemを使用して、拡張子が`txt`のファイルをコピーするだけです。
期待は、「targetフォルダにテキストファイルがコピーされる」なんですが。
少し条件があって、targetフォルダが存在する場合、この動作になります。存在しない場合は、targetフォルダが作成されてそこにファイルがコピーされ、コピーされる場所としては期待通りになります。

|$targetフォルダの有無|事象|
|:--|:--|
|あり|targetフォルダ配下にsourceフォルダが作成されてファイルコピー。|
|なし|targetフォルダを作成しファイルコピー。|

コピーを実行しようとする場合、コピー先にフォルダがあるかないかで動作が変わるのって困ります。
既存フォルダにコピーしたいかもしれないし。既存フォルダに置きたいのに、sourceフォルダができてしまうのって、やはり利便性に欠けます。

##MicrosoftのReference
このことは調べてみると、きちんと記載がありました。

>###Example 2: Copy directory contents to an existing directory
>This example copies the contents of the C:\Logfiles directory into the existing C:\Drawings directory. The Logfiles directory isn't copied.
>
>If the Logfiles directory contains files in subdirectories, those subdirectories are copied with their file trees intact. By default, the Container parameter is set to True, which preserves the directory structure.
>
>```PowerShell
>Copy-Item -Path "C:\Logfiles\*" -Destination "C:\Drawings" -Recurse
>```
> 注意
>
>If you need to include the Logfiles directory in the copy, remove the \* from the Path. For example:
>
>Copy-Item -Path "C:\Logfiles" -Destination "C:\Drawings" -Recurse
>
>###Example 3: Copy directory and contents to a new directory
>This example copies the contents of the C:\Logfiles source directory and creates a new destination directory. The new destination directory, \Logs is created in C:\Drawings.
>
>To include the source directory's name, copy to an existing destination directory as shown in Example 2. Or, name the new destination directory with the same as the source directory.
>
>```PowerShell
>Copy-Item -Path "C:\Logfiles" -Destination "C:\Drawings\Logs" -Recurse
>```
> 注意
>
>If the Path includes \*, all the directory's file contents, including the subdirectory trees, are copied to the new destination directory. For example:
>
>Copy-Item -Path "C:\Logfiles\\*" -Destination "C:\Drawings\Logs" -Recurse

Example 2は既存フォルダにコピーする場合、ディレクトリの中身をコピーする方法を記載しており、-Pathのフォルダ設定について、`\*`を付与すると、targetのディレクトリにそのディレクトリ配下のファイルがコピーされるとしています。
なるほど、-Pathをディレクトリとすると、ディレクトリをコピーする動作になるんですね。なので、`\*`をつけることで、コピー対象をディレクトリ配下のファイルに絞っていると理解しました。

Example 3は新しいフォルダにコピーする場合、-Pathはディレクトリを指定すると、targetのディレクトリを作ってその配下にファイルがコピーされるとしています。
コピーの対象はディレクトリであるものの、コピー先はフォルダがないため、targetのディレクトリの名前で生成しているということですね。
だったら、その下にさらにコピー対象のディレクトリができてもよさそうだけど、そうではないらしい。

ディレクトリをコピーしているのだからディレクトリごとコピーされる、ファイルをしたいならは`\*`でディレクトリ配下のファイルを対象にすればファイルコピーできるということのようです。
そういわれるとそうだなあと納得してしまいました。
-Filterで拡張子を設定していたから、ファイルが対象と思い込んでいたということです。

なお、ディレクトリ＝フォルダとしています。MicrosoftのReferenceがディレクトリと記載されていたため、ここではディレクトリと記載しています。

#ディレクトリ配下のファイルコピー（成功例）
では記載の通りにソースコードを直してみます。
要するに$sourceの最後に`\*`をつけて対象をファイルにします。
これで期待通り、ファイルだけコピーされて余分なフォルダは作成されませんでした。

##ソースコード

```Powershell:CopyItem_success.ps1
try {
    $source = "C:\Temp\source\*"
    $target = "C:\Temp\target"
    Copy-Item -Filter "*.txt" -Path $source -Destination $target -Recurse
} catch {
    Write-Host $Error[0]
}
```


#おわりに
すっごく簡単なことなのに、2時間ほど悩んでしまいました。なさけない。