【PowerShell】特定のファイルをディレクトリ構造を維持したままコピーする（Robocopyを利用して）

# やりたいこと

- ディレクトリ構造を維持した状態で、指定ファイルを別ディレクトリに移動させる

# TL,DR

- ~~コピー元にファイルを指定したとき、ディレクトリ構造を維持するのに`Copy-Item`は使いにくさを感じた。~~
- `Robocopy.exe`を使う。
- ファイルは第三引数に指定する。
- ディレクトリ構造を維持するには、コピー対象のファイルに対するコピー元とコピー先の親ディレクトリを合わせる必要がある。


## :sunny: ファイルのみをコピーする際に`Copy-Item`ではディレクトリ構造を維持するのが困難（主原因：自分のぐぐり力）

見出しの通りです。
こういうスクリプトを書いたりしてました。（失敗）


```shell:copyItem.ps1
PS> Copy-Item .\test1\level1\index.html .\test2 -Recurse -Container
```

## :sunny: `Robocopy.exe`を利用する

今回の目的を達成するためにぐぐってると`Robocopy`を使うという記事がいくつかヒットしました。
（**"copy-item ファイル ディレクトリ構造 維持"**とか**"copy-item file directory structure"**とかでググりました。）

そもそも、ファイルではなくディレクトリごをそのままコピーする場合なら先述の`Copy-Item`のオプション`-Recurse`を付ければ実現可能です。

しかしながら今回は、どうしてもファイルのみをコピーしたかったので...。
`Robocopy`に関してはググればたくさん記事が出ます。

こういうスクリプトを書きました。
~~`/E`でサブディレクトリのコピー~~
**2020.05.29 追記：コピー対象がファイルのみの場合、このオプションは不要です。似たようなオプションに、`/S`がありますがそれも同様です。**

`/R:n`でコピー実行時、エラーが起きた場合の再実行回数を指定（今回は`1`を指定）

```powershell
Robocopy.exe $fromDirectoryPath $destinationDirectoryPath $fileName /R:1
```

## :sunny: ファイルは第3引数に指定する

コピー先やコピー元にファイル名を入れるとうまく行きません。
（実行結果はちなみにこんな感じになります。）

```shell
PS> Robocopy.exe .\test1\level1\index.html .\test2 /E 

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : 2019年6月29日 16:37:51
   Source : C:\Users\username\test1\level1\index.html\
     Dest : C:\Users\username\test2\

    Files : *.*

  Options : *.* /S /E /DCOPY:DA /COPY:DAT /R:1000000 /W:30 

------------------------------------------------------------------------------

2019/06/29 16:37:51 ERROR 123 (0x0000007B) Accessing Source Directory

C:\Users\username\test1\level1\index.html\
The filename, directory name, or volume label syntax is incorrect.
```

下から2行目とかを見ていただくとわかると思いますが、ファイル名のあとにバックスラッシュが入ってます。
**第一引数、第二引数はともにディレクトリを指定しましょう**。
（※第三引数には、ファイル名のみ指定します。）


## :sunny: コピー対象のファイルに対する、コピー元・先の親ディレクトリを合わせる

コピー後の理想を下記とした場合
`.\test2\level1\index.html`

以下の書き方ではうまく行きません。

```powershell
PS C:\Users\username> Robocopy.exe .\test1\level1 .\test2 index.html /E
```

実行します。

```shell
PS C:\Users\username> Robocopy.exe .\test1\level1 .\test2 index.html /E               

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : 2019年6月29日 16:40:58
   Source : C:\Users\username\test1\level1\
     Dest : C:\Users\username\test2\

    Files : index.html

  Options : /S /E /DCOPY:DA /COPY:DAT /R:1000000 /W:30 

------------------------------------------------------------------------------

                           1    C:\Users\username\test1\level1\

------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         1         0         1         0         0         0
   Files :         1         0         1         0         0         0
   Bytes :       248         0       248         0         0         0
   Times :   0:00:00   0:00:00                       0:00:00   0:00:00
   Ended : 2019年6月29日 16:40:59
```

コピー自体は成功です。

肝心のコピー先のディレクトリを見てみますと、、

```shell
PS C:\Users\username> ls .\test2

    ディレクトリ: C:\Users\username\test2

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019/06/29     15:28            248 index.html
```

ディレクトリ構造が維持されていません。


なので、先ほどのスクリプトを修正します。

```shell
PS C:\Users\username> Robocopy.exe .\test1\level1 .\test2\level1 index.html /E

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : 2019年6月29日 16:54:03
   Source : C:\Users\username\test1\level1\
     Dest : C:\Users\username\test2\level1\

    Files : index.html

  Options : /S /E /DCOPY:DA /COPY:DAT /R:1000000 /W:30

------------------------------------------------------------------------------

          New Dir          1    C:\Users\username\test1\level1\
100%        New File                 248        index.html

------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         1         1         0         0         0         0
   Files :         1         1         0         0         0         0
   Bytes :       248       248         0         0         0         0
   Times :   0:00:00   0:00:00                       0:00:00   0:00:00


   Speed :               49600 Bytes/sec.
   Speed :               2.838 MegaBytes/min.
   Ended : 2019年6月29日 16:54:03
```

結果は以下のとおりです。

```shell
PS C:\Users\username> ls .\test2 -Recurse

    ディレクトリ: C:\Users\username\test2

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2019/06/30      0:06                level1

    ディレクトリ: C:\Users\username\test2\level1

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019/06/30      0:06              0 index.html
```

うまくいきました。


# スクリプト

※Gistのページに飛びます。

- [copyFilesAndKeepFolderStructure.ps1](https://gist.github.com/itsumoonazicode/15f4b28389b6f043ee929fcf8698e696)

# 参考

- [PowerShellメモ　パス関連操作](https://qiita.com/Kosen-amai/items/c7669e711d6b5743a840)
- [robocopy コマンドについて調べてみた](http://blog.serverworks.co.jp/tech/2017/01/12/robocopy/)

# 雑記

サイトの保守作業で、作業後に、修正を加えたファイルのみを別ディレクトリに置くためにこういうスクリプトを書きたいなと思った次第なのでした。