ファイルの比較（C# ）

#概要
Windows環境でファイルのリリース作業をする際に最新ファイルのみ抽出する比較コマンドを作成したいと思います。

#事前準備
比較する新しいファイルと既存ファイルをAとBのディレクトリにそれぞれコピーして置きます。

#ソース
下記のソースをVSにコピーペーストしましょう。

~~~
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;

namespace filelist
{
    class Program
    {
        static readonly HashAlgorithm hashProvider = new SHA1CryptoServiceProvider();

        /// <summary>
        /// Returns the hash string for the file.
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public static string ComputeFileHash(string filePath)
        {
            var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
            var bs = hashProvider.ComputeHash(fs);
            return BitConverter.ToString(bs).ToLower().Replace("-", "");
        }

        static void Main(string[] args)
        {
            
            try
            {
                if(args.Length>0 && args[0].ToString().ToUpper() == "-D")
                {
                    dcomp(args);
                }
                else
                {
                    fList(args);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        static void dcomp(string[] args)
        {
            var srcList = new List<fileObj>();
            var dstList = new List<fileObj>();
            var resultList = new List<fileObj>();
            //source directory
            if (Directory.Exists(args[1].ToString()))
            {
                var srcfile = Directory.EnumerateFiles(args[1].ToString(), "*.*", System.IO.SearchOption.AllDirectories);
                StreamWriter sw1 = new StreamWriter(@".\srcfile.txt");
                sw1.WriteLine("UpdateTime\tFileName");
                foreach (string f in srcfile)
                {
                    fileObj fobj = new fileObj() { filename=f, filehash=ComputeFileHash(f), updateTime=File.GetLastAccessTime(f) };
                    sw1.WriteLine(fobj.updateTime + "\t" + fobj.filename);
                    srcList.Add(fobj);
                }
                sw1.Close();
            }
            //target directory
            if (Directory.Exists(args[2].ToString()))
            {
                var dstfile = Directory.EnumerateFiles(args[2].ToString(), "*.*", System.IO.SearchOption.AllDirectories);
                StreamWriter sw2 = new StreamWriter(@".\dstfile.txt");
                sw2.WriteLine("UpdateTime\tFileName");
                foreach (string f in dstfile)
                {
                    fileObj fobj = new fileObj() { filename = f, filehash = ComputeFileHash(f), updateTime = File.GetLastAccessTime(f) };
                    sw2.WriteLine(fobj.updateTime + "\t" + fobj.filename);
                    dstList.Add(fobj);
                }
                sw2.Close();
            }                    
            
            //compare
            foreach(fileObj srcobj in srcList)
            {
                string srcfname = Path.GetFileName(srcobj.filename);
                fileObj result = new fileObj();
                
                foreach (fileObj dstobj in dstList)
                {
                    string dstfname = Path.GetFileName(dstobj.filename);
                    if (srcfname == dstfname && 
                       srcobj.updateTime > dstobj.updateTime)
                    {
                        Console.WriteLine("[new]" + "\t" + srcobj.filename);
                        //SRCが最新
                        result.filename = "[new]" +  srcobj.filename;
                        result.filehash = srcobj.filehash;
                        result.updateTime = srcobj.updateTime;
                        resultList.Add(result);
                        break;
                    }
                    if (srcfname == dstfname &&
                        srcobj.updateTime < dstobj.updateTime)
                    {
                        Console.WriteLine("[new]" + dstobj.filename);
                        //SRCが最新
                        result.filename = "[new]" + dstobj.filename;
                        result.filehash = dstobj.filehash;
                        result.updateTime = dstobj.updateTime;
                        resultList.Add(result);
                        break;
                    }
                }

                if(result.filename == null)
                {
                    //DSTにないものを追加
                    Console.WriteLine("[add]" +"\t" + srcobj.filename);
                    result.filename = "[add]"+srcobj.filename;
                    result.filehash = srcobj.filehash;
                    result.updateTime = srcobj.updateTime;
                    resultList.Add(result);
                }
            }

            StreamWriter sw = new StreamWriter(@".\result.txt");

            foreach (fileObj fobj in resultList)
            {
                sw.WriteLine(fobj.updateTime + "\t" + fobj.filename);
            }
            sw.Close();
        }

        static void fList(string[] args)
        {
            string path1 = @".\";

            var filename = Directory.EnumerateFiles(path1, "*.*", System.IO.SearchOption.AllDirectories);

            StreamWriter sw = new StreamWriter(@".\filelist.txt");

            foreach (string f in filename)
            {
                if (args.Length == 1 && args[0].ToString().ToUpper() == "-H")
                {
                    Console.WriteLine(ComputeFileHash(f) + " " + Path.GetFileName(f));
                    sw.WriteLine(ComputeFileHash(f) + " " + Path.GetFileName(f));
                }
                else
                {
                    Console.WriteLine(Path.GetFileName(f));
                    sw.WriteLine(Path.GetFileName(f));
                }
            }

            sw.Close();
        }
    }

    public class fileObj
    {
        public string filename;
        public string filehash;
        public DateTime updateTime;
    }
}
~~~

#コマンドのオプション
-h ：現在ディレクトリの配下にあるファイル一覧とhash値を表示
-h(なし）：現在ディレクトリの配下にあるファイル一覧を表示
-d：二つのディレクトリに存在するファイルを比較し、内容を表示
※-dで表示される[new]が最新、[add]は追加されたファイルです。

#コマンドの実行結果
実行するコマンドと比較するディレクトリを指定します。
SRC→DSTディレクトリを比較することになります。
コマンド例）
filelist.exe -d ./a ./b
※aのディレクトリに入っているファイルとbのディレクトリに入っているファイルを比較し、結果をコンソールに表示およびresult.txtに保存しています。

~~~
#aのファイルをbのファイルと比較。bのファイルが最新でnewと表示されます。
C:\repos\filelist\filelist\bin\Debug>filelist.exe -d ./a ./b
[add]   ./a\App.config
[new]./b\file.txt
[add]   ./a\filelist.csproj
[new]./b\filelist.exe
[new]./b\filelist.exe.config
[new]./b\filelist.pdb
[add]   ./a\packages.config
[add]   ./a\Program.cs
[add]   ./a\obj\Debug\DesignTimeResolveAssemblyReferencesInput.cache
[add]   ./a\obj\Debug\filelist.csproj.CoreCompileInputs.cache
[add]   ./a\obj\Debug\filelist.csproj.FileListAbsolute.txt
[new]./b\filelist.exe
[new]./b\filelist.pdb
[add]   ./a\Properties\AssemblyInfo.cs

#bのファイルをaのファイルと比較。bのファイルに追加されたファイルがあります。
C:\repos\filelist\filelist\bin\Debug>filelist.exe -d ./b ./a
[add]   ./b\ddst.txt
[new]   ./b\file.txt
[new]   ./b\filelist.exe
[new]   ./b\filelist.exe.config
[new]   ./b\filelist.pdb

~~~
実行結果フォルダに生成されたテキストファイル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/a08a6ec0-7d93-3e65-093e-1e223145bb52.png)
srcfile.txtファイルの内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/d203c360-d643-6f24-a407-79f850fd3c4a.png)
dstfile.txtファイルの内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/6ffef167-83e1-def8-36d3-028af41b1fe5.png)
result.txtファイルの内容
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/1dc035ad-93db-7760-3ac7-24c689f4da34.png)

#実行結果（オプションなし）
実行ファイルをこのまま実行すると配下に存在するすべてのファイル一覧が表示されfileslist.txtに保存されます。

~~~
C:\repos\filelist\filelist\bin\Debug>filelist.exe
dstfile.txt
filelist.exe
filelist.exe.config
filelist.pdb
filelist.txt
result.txt
srcfile.txt
a\App.config
a\file.txt
a\filelist.csproj
a\filelist.exe
a\filelist.exe.config
a\filelist.pdb
a\packages.config
a\Program.cs
a\obj\Debug\DesignTimeResolveAssemblyReferencesInput.cache
a\obj\Debug\filelist.csproj.CoreCompileInputs.cache
a\obj\Debug\filelist.csproj.FileListAbsolute.txt
a\obj\Debug\filelist.exe
a\obj\Debug\filelist.pdb
a\Properties\AssemblyInfo.cs
b\ddst.txt
b\file.txt
b\filelist.exe
b\filelist.exe.config
b\filelist.pdb
~~~

保存されたfilelist.txtの内容です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/60ff4c4d-0e20-034b-1c42-c9cc8fb2a4ac.png)

#実行結果（-h）
-hオプションを入れると現在ディレクトリの配下にファイルすべてのファイルのhash値も一緒に表示してfilelist.txtに保存されます。

~~~
C:\repos\filelist\filelist\bin\Debug>filelist.exe -h
c9424476c1f21db28a78037991495f27542f6996 dstfile.txt
1d6917bb159a8209cbd00ce167b42c895c7af40c filelist.exe
2711de49785aba673df043f543686685e3b53e73 filelist.exe.config
3638b6a09b197a11d74d2ac75f022c9220a60cbd filelist.pdb
da39a3ee5e6b4b0d3255bfef95601890afd80709 filelist.txt
d51c8a74bc93a6bcf172a622577cbba94243f0e7 result.txt
aa37458a0f5d24af2bd272357c05e84a4779a54f srcfile.txt
2711de49785aba673df043f543686685e3b53e73 a\App.config
620106eba2a07b032917b5f2576d48ffb2d98503 a\file.txt
438ec6715c8704daa65908e929808ecf945cc70e a\filelist.csproj
7337afd3b69934139e71f0470106c9aadbe595c6 a\filelist.exe
2711de49785aba673df043f543686685e3b53e73 a\filelist.exe.config
4c8c7058cf33e07891bf71329a98c9360ff6eb1a a\filelist.pdb
4dfa86ebeaad9070e2a981004942ba68090ce368 a\packages.config
139b02206cbd12ece9cd62aa50b4be4c18ef3697 a\Program.cs
5ff5492237512ea3dc836a309bcdb3182e97c702 a\obj\Debug\DesignTimeResolveAssemblyReferencesInput.cache
6d5a1db8b80683c8e82601af7b0679bb8338eb7e a\obj\Debug\filelist.csproj.CoreCompileInputs.cache
28afd52d819d8f9d9ab07c96580d659a582caa8e a\obj\Debug\filelist.csproj.FileListAbsolute.txt
7337afd3b69934139e71f0470106c9aadbe595c6 a\obj\Debug\filelist.exe
4c8c7058cf33e07891bf71329a98c9360ff6eb1a a\obj\Debug\filelist.pdb
41525e3927742a96f9f2edca06ecc0481a28f553 a\Properties\AssemblyInfo.cs
da39a3ee5e6b4b0d3255bfef95601890afd80709 b\ddst.txt
620106eba2a07b032917b5f2576d48ffb2d98503 b\file.txt
4dc3645a1daf449cccf3c0dfa6a88d63cb0fd511 b\filelist.exe
2711de49785aba673df043f543686685e3b53e73 b\filelist.exe.config
8673638c7999ba1876adffd361c64cb6dcafbe6f b\filelist.pdb
~~~

保存されたファイルの内容です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162336/6d268c08-8a0c-40cf-65f1-62bbcd608301.png)

#終わりに
ソースの中ではhash値も入れてみましたが、結果的にファイルの更新日時で最新を確認ができるので利用はしていないです。
悪用で更新日時を更新するとhash値は変更されるんですかね。
これを利用してファイルのリリース作業に役に立つとよいですね。