【.NET】FastCopyのコマンドラインを使用する

# はじめに
自社開発のバックアップツールがあり、2012年が初期バージョンで2016年に改善作業が入り自分の方で修正作業を行いました。
今年になり改修して欲しいという依頼がありました。改修依頼とは別に以前からの問合せで気になっていた下記2点について、ついでに修正したい思っていたので調査をしました。

* バックアップ処理が遅い
* ロングパス名に対応していない

# 現行
## 環境
* Visual Basic
* .NET Framework 3.5

## バックアップ処理が遅い
コピー元がネットワーク上ということもあるのですが、お昼休みにバックアップを仕掛けたのにお昼休み中にバックアップが終わっていない。
バックアップ処理自体は、.NETの標準APIを使用しています。フォルダコピーする際には再帰処理しているくらいでごく一般的な方法です。世代管理しており、常時フルコピーしております。

## ロングパス名に対応していない
.NET Framework 3.5の段階では、.NETの標準API(System.IO 名前空間のクラス)はロングパス名に対応していないのです。

### 対応方法 
今回調べたところ MAX_PATHの制限は260文字で、下記の方法ならロングパス名に対応できるようです。

>MAX_PATH 制限は、"\\?\" で始まる UNC パスを使う事で回避できます。
>これで3万2千文字まで許可されます。
>
>ただしこの手法は、System.IO 名前空間のクラスに対しては利用できません。
>「?」を含んだパス指定だと .NET 側のパス検証で弾かれるため、P/Invoke にて
>DeleteFile、CopyFile、RemoveDirectory 等の API (の Unicode バージョン)を
>呼び出す必要があります。
[ロングファイル名パスでのファイル操作](https://dobon.net/vb/bbs/log3-51/30279.html)

.NET Framework 4.6.2以上を使用していた場合、プレフィックス「\\?\」を使用することでSystem.IO 名前空間のクラスでもロングパス名に対応するようになりました。
また、Windows 10またはWindows Server 2016以降では、グループポリシーで「NTFSロングパスを有効にする」を「有効」にすることで、プレフィックス「\\?\」を使用する必要さえなくなります。有効にすればエクスプローラー上でもロングパス名に対応します。残念なのはデフォルトは「無効」なんですよね。

* [長い道のりを選択する（またはMAX_PATHに別れを告げる）](https://geeks-world.github.io/articles/J307186/index.html)
* [Windows 10のExplorerで260文字以上のPATHを取り扱う](https://orebibou.com/2017/05/windows-10-insider-preview%E3%81%A7explorer%E3%81%A7260%E6%96%87%E5%AD%97%E4%BB%A5%E4%B8%8A%E3%81%AEpath%E3%82%92%E5%8F%96%E3%82%8A%E6%89%B1%E3%81%86/)
* [.NET 4.6.2 and long paths on Windows 10](https://docs.microsoft.com/ja-jp/archive/blogs/jeremykuhne/net-4-6-2-and-long-paths-on-windows-10)

# 改善
## 環境
* Visual Basic
* .NET Framework 4.6以上
* FastCopy ver3.87

.NET Frameworkは変更しました。Windows 10なら.NET Framework 4.6以上がプレインストールされています。逆に.NET Framework 3.5を有効化するには、インターネットが繋がる環境なら簡単ですがオフライン環境ではインストールが複雑になってしまいます。これによりWindows XPはサポート対象外としました。.NET Framework 4.0までしか対応していないためです。

* [Windows10にオフライン環境で.NET Framework 1.1 および .NET Framework 3.5をインストールする](https://qiita.com/yaju/items/0cc10ff85b4042fc5ed1)
* [Windows XPや 2000に何故 .Net Framework 4.5が入らないか](http://blog.livedoor.jp/blackwingcat/archives/1707506.html)

## バックアップ処理の速度改善
FastCopyにはコマンドラインモードが用意されています。今回、FastCopyのコマンドラインモードを使用することでバックアップ速度を改善することが出来ました。
[FastCopy 7. コマンドラインモード](https://fastcopy.jp/help/fastcopy.htm#cmdline)

```vb:VB版
''' <summary>
''' FastCopyを使用したコピー処理
''' </summary>
''' <param name="sourcePath">コピー元パス名</param>
''' <param name="destPath">コピー先パス名</param>
''' <returns>0:成功 / -1:失敗</returns>
Private Function FastCopy(ByVal sourcePath As String, ByVal destPath As String) As Integer
    Dim ret As Integer = -1

    Dim psInfo As New ProcessStartInfo()
    Dim sbArg As New StringBuilder()

    psInfo.FileName = "fastcopy.exe"    ' 実行するファイル
    sbArg.Append("/bufsize=512 /cmd=sync /speed=full /auto_close /force_close /no_confirm_del ")
    sbArg.Append("""" + sourcePath + """")
    sbArg.Append(" /To=")
    sbArg.Append("""" + destPath + """")
    psInfo.Arguments = sbArg.ToString() ' コマンドライン引数を指定する
    psInfo.CreateNoWindow = True        ' コンソール・ウィンドウを開かない
    psInfo.UseShellExecute = False      ' シェル機能を使用しない

    Dim p As Process = Process.Start(psInfo)
    p.WaitForExit()

    ' コマンドが成功した場合は 0、失敗した場合は -1
    ret = Integer.Parse(p.ExitCode.ToString())

    Return ret
End Function
```

```csharp:C#版
/// <summary>
/// FastCopyを使用したコピー処理
/// </summary>
/// <param name="sourcePath">コピー元パス名</param>
/// <param name="destPath">コピー先パス名</param>
/// <returns>0:成功 / -1:失敗</returns>
private int FastCopy(string sourcePath, string destPath)
{
    int ret = -1;

    ProcessStartInfo psInfo = new ProcessStartInfo();
    StringBuilder sbArg = new StringBuilder();

    psInfo.FileName = "fastcopy.exe";    // 実行するファイル
    sbArg.Append("/bufsize=512 /cmd=sync /speed=full /auto_close /force_close /no_confirm_del ");
    sbArg.Append("\"" + sourcePath + "\"");
    sbArg.Append(" /To=");
    sbArg.Append("\"" + destPath + "\"");
    psInfo.Arguments = sbArg.ToString(); // コマンドライン引数を指定する
    psInfo.CreateNoWindow = true;        // コンソール・ウィンドウを開かない
    psInfo.UseShellExecute = false;      // シェル機能を使用しない

    Process p = Process.Start(psInfo);
    p.WaitForExit();

    // コマンドが成功した場合は 0、失敗した場合は -1
    ret = int.Parse(p.ExitCode.ToString());

    return ret;
}
```

社内のイントラネット環境と遅いVPN環境でTESTフォルダ(1.02 GB  ファイル数: 12,604、フォルダー数: 216)のバックアップを試したところ、下表の結果となり改善の効果はかなり高いです。

|環境|現行版|改善版|差|
|:--|:--|:--|:--|
|イントラ|2分28秒|1分16秒|1分12秒|
|VPN|37分17秒|28分07秒|9分10秒|

### 世代管理の改善
現行版の世代管理は、常時フルコピーをしていました。ただ世代管理は内容がほぼ同じですよね。
FastCopyは差分コピーがあり、デフォルトではサイズもしくは日付が違うファイルのみコピーするようになっています。
また、FastCopyに限らずローカル間のコピーは、ネットワークのコピーより全然速いです。

一つ前の世代管理があった場合、ローカル上にフルコピーしてから差分コピーしてあげれば速くなると考えて試してみました。
世代管理ありの状態にして、前世代のTESTフォルダの幾つかのファイルを削除(2.5MB、ファイル数: 116)してからバックアップをしたところ、現行版 34分35秒から改善版はたったの1分59秒になりました。劇的な改善の効果です。

|環境|現行版|改善版|差|
|:--|:--|:--|:--|
|VPN|34分35秒|1分59秒|32分34秒|

## ロングパス名に対応
FastCopyを使用することでロングパス名に対応することが出来ました。

ロングパス名が含まれているフォルダの削除の際に、FastCopyにコピー元に空フォルダを指定することでフォルダ削除するようにしています。下記サイトはRobocopyでしたがアイデアを採用しました。
[Windowsで長い名前のフォルダを削除する簡単な方法](https://winfixtut.blogspot.com/2019/09/windows_20.html)

```vb
'長いパス名対応による削除(空フォルダの差分コピーをすることで削除)
FastCopy(emptyPath, dirPath)
```

# 最後に
Windows 7から標準でインストールされている RoboCopyでも良かったんですが FastCopyの方が速かったんですよね。それにRoboCopyはベリファイ機能が付いてないですが、FastCopyはベリファイ機能があります。

これでお昼休み中にバックアップが終わらないってことは避けれるでしょう。