過去にめんどくさい業務を自動化したメモです。
何を作ったのか
更新日時が特定日付のソースファイルを自動的に収集、
かつパスワード付きzipファイル化してくれるツール。
↓こんなん。
取得元フォルダ配下から、更新日付が「取得ファイル更新日付」で指定された日付のファイルを特定。
保存先フォルダにディレクトリごとコピー。
指定があればzip化。ということができます。
作成の背景
とある受託プロジェクトにて、ほぼ毎日その日の成果物(ソースファイル)を顧客に送付する業務がありました。
その頃社内のソース管理はもっぱらTFSで、この時は50以上のプロジェクトファイルがまとめられたソリューションを管理していました。
毎日終業前に、
- その日修正なり作成なりしたソースがメンバーからの報告や消化されたチケットと差異がないことを確認
 - 差分の発生したソースファイルのみ取得
 - zip化して送付
という作業をしていたわけなのですが……。 
なんとその作業のすべてが手作業でした。
その業務が自分に回ってきた時に初めてその状況に気づいたわけなのですが、こんなんやってられるかというお気持ちで、せめてソースファイルの収集くらいはと作成した次第です。
ソース(コア部分)
※Windowsフォームで作成(ソース全文はこちら)
対象ファイルをディレクトリごとコピーしてくれるCopyFileWithDirectory関数を再帰的に呼び出しているだけです。
実行時に変なパスを指定されても困るので、システムフォルダやbinなどの特定フォルダは最低限はじいています。
        /// <summary>
        /// ディレクトリ構成を含め、再帰的に対象ファイルをコピーする
        /// </summary>
        /// <param name="originDirectory">取得元フォルダ</param>
        /// <param name="targetDirectory">保存先フォルダ</param>
        /// <param name="targetDateFrom">取得ファイル更新日付(From)</param>
        /// <param name="targetDateTo">取得ファイル更新日付(To)</param>
        private void CopyFileWithDirectory(string originDirectory
                                            , string targetDirectory
                                            , DateTime targetDateFrom
                                            , DateTime targetDateTo)
        {
            // 保存先のディレクトリ名の末尾に"\"をつける
            if (targetDirectory[targetDirectory.Length - 1] != Path.DirectorySeparatorChar)
                targetDirectory = targetDirectory + Path.DirectorySeparatorChar;
            try
            {
                // タスクのキャンセルがされていたら例外を投げる
                _cancellationToken.ThrowIfCancellationRequested();
                // 取得元ディレクトリ配下のファイルを検索し、条件に一致するファイルを取得する
                string[] files = Directory.GetFiles(originDirectory);
                foreach (string file in files)
                {
                    // 処理中のディレクトリを画面に表示
                    Invoke(new Action<string>(SetExecuteMsg), file);
                    if (!CheckTargetFile(file, targetDateFrom, targetDateTo))
                        continue;
                    // 保存先のディレクトリがないときは作る(属性もコピー)
                    if (!Directory.Exists(targetDirectory))
                    {
                        Directory.CreateDirectory(targetDirectory);
                        File.SetAttributes(targetDirectory, File.GetAttributes(originDirectory));
                    }
                    File.Copy(file, targetDirectory + Path.GetFileName(file), true);
                }
                // 取得元ディレクトリ配下のディレクトリについて、再帰的に呼び出す
                string[] dirs = Directory.GetDirectories(originDirectory);
                foreach (string dir in dirs)
                {
                    if (!CheckTargetFolder(dir))
                        continue;
                    CopyFileWithDirectory(dir
                                            , targetDirectory + Path.GetFileName(dir)
                                            , targetDateFrom
                                            , targetDateTo);
                }
            }
            catch (UnauthorizedAccessException ex)
            {
                string[] msg = ex.Message.Split('\'');
                Invoke(new Action<string>(SetErrMsg), "権限エラー:" + msg[1]);
            }
            catch (OperationCanceledException)
            {
                // 処理なし
                // 処理キャンセルのメッセージは呼び出し元でセットする
            }
            catch (Exception ex)
            {
                Invoke(new Action<string>(SetErrMsg), ex.Message);
            }
        }
        /// <summary>
        /// ファイルの取得判定
        /// </summary>
        /// <param name="file"></param>
        /// <param name="targetDateFrom">取得ファイル更新日付(From)</param>
        /// <param name="targetDateTo">取得ファイル更新日付(To)</param>
        /// <returns></returns>
        private static bool CheckTargetFile(string file, DateTime targetDateFrom, DateTime targetDateTo)
        {
            // 存在チェック
            if (!File.Exists(file))
                return false;
            // 属性チェック
            // 非表示、システムファイルは除外
            FileAttributes attributes = File.GetAttributes(file);
            if ((attributes & FileAttributes.System) == FileAttributes.System
                || (attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                return false;
            // 対象日付範囲チェック
            DateTime lastWriteDateTime = File.GetLastWriteTime(file);
            if (lastWriteDateTime.Date < targetDateFrom.Date || targetDateTo.Date < lastWriteDateTime.Date)
                return false;
            return true;
        }
        /// <summary>
        /// フォルダの対象判定
        /// </summary>
        /// <param name="dir">取得元フォルダ</param>
        /// <returns></returns>
        private static bool CheckTargetFolder(string dir)
        {
            string[] arr = dir.Split('\\');
            string strLastDir = arr[arr.Length - 1];
            // 非表示、システムフォルダは除外
            FileAttributes attributes = File.GetAttributes(dir);
            if ((attributes & FileAttributes.System) == FileAttributes.System
                || (attributes & FileAttributes.Hidden) == FileAttributes.Hidden)
                return false;
            // 特定フォルダ名は除外
            if (strLastDir == "bin"
                || strLastDir == "obj"
                || strLastDir == "Program Files"
                || strLastDir == "Program Files (x86)"
                || strLastDir == "Windows")
                return false;
            return true;
        }
最後に
これのおかげで、気を遣いながら対象のファイルを探してコピーしてを繰り返し、最後にまた過不足ないか確認する、という作業からかなり解放されました。
計ったわけではないですが、1回の作業につき20分くらいは削減できたのではないでしょうか。つまり1週間5人日で100分、月に400分≒6~7時間程度。
既存のツールを探してくる、TFSの機能を活用するなど、もっと賢い方法もあったかと思いますが、ツール作成は楽しいのでよしです。
