PoshCode Logo PowerShell Code Repository

Findup by James Gentile 28 months ago (modification of post by James Gentile view diff)
diff | embed code: <script type="text/javascript" src="http://PoshCode.org/embed/3377"></script>download | new post

Findup – Find duplicates C# version. Compares files sizes and SHA512 hashes to identify duplicates. New regex Include/Exclude feature. Should be compiled with Visual Studio 11 (beta as of now), as older Visual Studio C# compilers seem to have a bug that causes crashes on long file names.

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.Security.Cryptography;
  6. using System.Runtime.InteropServices;
  7. using Microsoft.Win32;
  8. using System.IO;
  9. using System.Text.RegularExpressions;
  10.  
  11.  
  12.  
  13. namespace Findup
  14. {
  15.     public class FileLengthComparer : IComparer<FileInfo>
  16.     {
  17.         public int Compare(FileInfo x, FileInfo y)
  18.         {
  19.             return (x.Length.CompareTo(y.Length));
  20.         }
  21.     }
  22.    
  23.     class Findup
  24.     {
  25.         public static Dictionary<string, List<string>> optionspaths = new Dictionary<string, List<string>>
  26.             { {"/x", new List<string>()},{"/i",new List<string>()},{"/xf",new List<string>()},{"/if",new List<string>()},
  27.             {"/xd",new List<string>()},{"/id",new List<string>()},{"/paths",new List<string>()} };
  28.         public static Dictionary<string, List<Regex>> optionsregex = new Dictionary<string, List<Regex>>
  29.             { {"/xr", new List<Regex>()},{"/ir",new List<Regex>()},{"/xfr",new List<Regex>()},{"/ifr",new List<Regex>()},
  30.             {"/xdr",new List<Regex>()},{"/idr",new List<Regex>()} };        
  31.         public static Dictionary<string, Boolean> optionsbools = new Dictionary<string, bool> { { "/recurse", false }, { "/noerr", false }, {"/delete",false}, {"/xj", false}};
  32.         public static long numOfDupes, dupeBytes, bytesrecovered, deletedfiles = 0;  // number of duplicate files found, bytes in duplicates, bytes recovered from deleting dupes, number of deleted dupes.
  33.         public static long errors = 0;
  34.         public static string delconfirm;        
  35.  
  36.         public static void Main(string[] args)
  37.         {
  38.             DateTime startTime = DateTime.Now;
  39.             Console.WriteLine("Findup.exe v2.0 - By James Gentile - JamesRaymondGentile@gmail.com - 2012.");
  40.             Console.WriteLine("Findup.exe matches sizes, then SHA512 hashes to identify duplicate files.");
  41.             Console.WriteLine(" ");                        
  42.             string optionskey = "/paths";        
  43.             List<FileInfo> files = new List<FileInfo>();            
  44.             int i = 0;            
  45.  
  46.             for (i = 0; i < args.Length; i++)
  47.             {
  48.                 string argitem = args[i].ToLower();
  49.  
  50.                 if ((System.String.Compare(argitem, "/help", true) == 0) || (System.String.Compare(argitem, "/h", true) == 0))
  51.                 {
  52.                     Console.WriteLine("Usage:    findup.exe <file/directory #1..#N> [/recurse] [/noerr] [/x /i /xd /id /xf /if + [r]] <files/directories/regex> [/delete]");
  53.                     Console.WriteLine(" ");
  54.                     Console.WriteLine("Options:  /help     - displays this help message.");
  55.                     Console.WriteLine("          /recurse  - recurses through subdirectories when directories or file specifications (e.g. *.txt) are specified.");                    
  56.                     Console.WriteLine("          /noerr    - discards error messages.");
  57.                     Console.WriteLine("          /delete   - delete each duplicate file with confirmation.");                    
  58.                     Console.WriteLine("          /x        - eXcludes if full file path starts with (or RegEx matches if /xr) one of the items following this switch until another switch is used.");
  59.                     Console.WriteLine("          /i        - include if full file path starts with (or Regex matches if /ir) one of the items following this switch until another switch is used.");
  60.                     Console.WriteLine("          /xd       - eXcludes all directories - minus drive/files - (using RegEx if /xdr) including subdirs following this switch until another switch is used.");
  61.                     Console.WriteLine("          /id       - Include only directories - minus drive/files - (using RegEx if /idr) including subdirs following this switch until another switch is used.");
  62.                     Console.WriteLine("          /xf       - eXcludes all files - minus drive/directories - (using RegEx if /xfr) following this switch until another switch is used.");
  63.                     Console.WriteLine("          /if       - Include only files - minus drive/directories - (using RegEx if /ifr) following this switch until another switch is used.");
  64.                     Console.WriteLine("          [r]       - Use regex for include/exclude by appending an 'r' to the option, e.g. -ir, -ifr, -idr, -xr, -xfr, -xdr.");
  65.                     Console.WriteLine("          /paths    - not needed unless you want to specify files/dirs after an include/exclude without using another non-exclude/non-include option.");
  66.                     Console.WriteLine("          /xj       - Exclude File and Directory Junctions.");
  67.                     Console.WriteLine(" ");
  68.                     Console.WriteLine("Examples: findup.exe c:\\finances /recurse /noerr /delete");
  69.                     Console.WriteLine("                     - Find dupes in c:\\finance.");
  70.                     Console.WriteLine("                     - recurse all subdirs of c:\\finance.");
  71.                     Console.WriteLine("                     - suppress error messages.");
  72.                     Console.WriteLine("                     - deletes duplicates after consent is given.");                    
  73.                     Console.WriteLine("          findup.exe c:\\users\\alice\\plan.txt d:\\data /recurse /x d:\\data\\webpics");
  74.                     Console.WriteLine("                     - Find dupes in c:\\users\\alice\\plan.txt, d:\\data");
  75.                     Console.WriteLine("                     - recurse subdirs in d:\\data.");
  76.                     Console.WriteLine("                     - exclude any files in d:\\data\\webpics and subdirs.");
  77.                     Console.WriteLine("          findup.exe c:\\data *.txt c:\\reports\\quarter.doc /xfr \"(jim)\"");
  78.                     Console.WriteLine("                     - Find dupes in c:\\data, *.txt in current directory and c:\\reports\\quarter.doc");
  79.                     Console.WriteLine("                     - exclude any file with 'jim' in the name as specified by the Regex item \"(jim)\"");
  80.                     Console.WriteLine("          findup.exe c:\\data *.txt c:\\reports\\*quarter.doc /xr \"[bf]\" /ir \"(smith)\"");
  81.                     Console.WriteLine("                     - Find dupes in c:\\data, *.txt in current directory and c:\\reports\\*quarter.doc");
  82.                     Console.WriteLine("                     - Include only files with 'smith' and exclude any file with letters b or f in the path name as specified by the Regex items \"[bf]\",\"(smith)\"");
  83.                     Console.WriteLine("Note:     Exclude takes precedence over Include.");
  84.                     return;
  85.                 }
  86.                 if (optionsbools.ContainsKey(argitem))
  87.                 {
  88.                     optionsbools[argitem] = true;
  89.                     optionskey = "/paths";
  90.                     continue;
  91.                 }                
  92.                 if (optionspaths.ContainsKey(argitem) || optionsregex.ContainsKey(argitem))
  93.                 {
  94.                     optionskey = argitem;
  95.                     continue;
  96.                 }                
  97.                 if (optionspaths.ContainsKey(optionskey))                
  98.                     optionspaths[optionskey].Add(args[i]);                                    
  99.                 else
  100.                 {
  101.                     try {
  102.                         Regex rgx = new Regex(args[i], RegexOptions.Compiled);
  103.                         optionsregex[optionskey].Add(rgx);
  104.                     }
  105.                     catch (Exception e) {WriteErr("Regex compilation failed: " + e.Message);}
  106.                 }
  107.             }
  108.             if (optionspaths["/paths"].Count == 0)
  109.             {
  110.                 WriteErr("No files, file specifications, or directories specified. Try findup.exe /help. Assuming current directory.");
  111.                 optionspaths["/paths"].Add(".");
  112.             }
  113.             Console.Write("Getting file info and sorting file list...");
  114.             getFiles(optionspaths["/paths"], "*.*", files, optionsbools["/recurse"], optionsbools["/xj"]);
  115.                          
  116.             if (files.Count < 2)
  117.             {
  118.                 WriteErr("\nFindup.exe needs at least 2 files to compare. Try findup.exe /help");
  119.                 Console.WriteLine("\n");
  120.                 return;
  121.             }
  122.  
  123.             files.Sort(new FileLengthComparer());
  124.             Console.WriteLine("Completed!");
  125.            
  126.             Console.WriteLine("Building dictionary of file sizes, SHA512 hashes and full paths...");
  127.            
  128.             for (i = 0; i < (files.Count - 1); i++)
  129.             {
  130.                 if (files[i].Length != files[i + 1].Length) continue;
  131.  
  132.                 var breakout = false;
  133.                 var HashFile = new Dictionary<string, List<FileInfo>>();
  134.                 long filesize = files[i].Length;
  135.  
  136.                 while (true)
  137.                 {
  138.  
  139.                     try
  140.                     {
  141.                         var _SHA512 = SHA512.Create();
  142.                         using (var fstream = File.OpenRead(files[i].FullName))
  143.                         {
  144.                             _SHA512.ComputeHash(fstream);
  145.                         }
  146.  
  147.                         string SHA512string = Hash2String(_SHA512.Hash);
  148.  
  149.                         if (!HashFile.ContainsKey(SHA512string))
  150.                             HashFile[SHA512string] = new List<FileInfo>() { };
  151.                         HashFile[SHA512string].Add(files[i]);
  152.                     }
  153.                     catch (Exception e) { WriteErr("Hash error: " + e.Message); }
  154.  
  155.                     if (breakout == true) { break; }
  156.                     i++;
  157.                     if (i == (files.Count - 1)) { breakout = true; continue; }
  158.                     breakout = (files[i].Length != files[i + 1].Length);
  159.                 }
  160.  
  161.  
  162.                 foreach (var HG in HashFile)
  163.                 {
  164.                     if (HG.Value.Count > 1)     // display filenames if number of files in SHA512 Hash Group > 1
  165.                     {
  166.                         Console.WriteLine("{0:N0} Duplicate files. {1:N0} Bytes each. {2:N0} Bytes total : ", HG.Value.Count, filesize, filesize * HG.Value.Count);
  167.                         foreach (var finfo in HG.Value)
  168.                         {
  169.                             Console.WriteLine(finfo.FullName);
  170.                             numOfDupes++;
  171.                             dupeBytes += filesize;
  172.                             if (optionsbools["/delete"])
  173.                                 if (DeleteDupe(finfo)) { bytesrecovered += filesize; deletedfiles++; }
  174.                         }
  175.                     }
  176.                 }
  177.             }
  178.                      
  179.  
  180.             Console.WriteLine("\n ");
  181.             Console.WriteLine("Files checked      : {0:N0}", files.Count);              // display statistics and return to OS.
  182.             Console.WriteLine("Duplicate files    : {0:N0}", numOfDupes);
  183.             Console.WriteLine("Duplicate bytes    : {0:N0}", dupeBytes);
  184.             Console.WriteLine("Deleted duplicates : {0:N0}", deletedfiles);
  185.             Console.WriteLine("Bytes recovered    : {0:N0}", bytesrecovered);
  186.             Console.WriteLine("Errors             : {0:N0}", errors);
  187.             Console.WriteLine("Execution time     : " + (DateTime.Now - startTime));
  188.         }              
  189.  
  190.         private static void WriteErr(string Str)
  191.         {
  192.             errors++;
  193.             if (!optionsbools["/noerr"])
  194.                 Console.WriteLine(Str);
  195.         }
  196.         private static string Hash2String(Byte[] hasharray)
  197.         {
  198.             string SHA512string = "";
  199.             foreach (var c in hasharray)
  200.             {
  201.                 SHA512string += String.Format("{0:x2}", c);
  202.             }
  203.             return SHA512string;
  204.         }
  205.  
  206.         private static Boolean DeleteDupe(FileInfo Filenfo)
  207.         {
  208.             Console.Write("Delete this file <y/N> <ENTER>?");
  209.             delconfirm = Console.ReadLine();
  210.             if ((delconfirm[0] == 'Y') || (delconfirm[0] == 'y'))
  211.             {
  212.                 try
  213.                 {                    
  214.                     Filenfo.Delete();
  215.                     Console.WriteLine("File Successfully deleted.");                                        
  216.                     return true;
  217.                 }
  218.                 catch (Exception e) { Console.WriteLine("File could not be deleted: " + e.Message); }
  219.             }
  220.             return false;
  221.         }
  222.  
  223.  
  224.         private static Boolean CheckNames(string fullname)
  225.         {
  226.             var filePart = Path.GetFileName(fullname);                                                              // get file name only (e.g. "d:\temp\data.txt" -> "data.txt")
  227.             var dirPart = Path.GetDirectoryName(fullname).Substring(fullname.IndexOf(Path.VolumeSeparatorChar)+2);  // remove drive & file  (e.g. "d:\temp\data.txt" -> "temp")
  228.  
  229.             if (CheckNamesWorker(fullname, "/x", "/xr", true))
  230.                 return false;
  231.             if (CheckNamesWorker(filePart, "/xf", "/xfr", true))          
  232.                 return false;
  233.             if (CheckNamesWorker(dirPart, "/xd", "/xdr", true))            
  234.                 return false;            
  235.             if (CheckNamesWorker(fullname, "/i", "/ir", false))
  236.                 return false;
  237.             if (CheckNamesWorker(filePart, "/if", "/ifr", false))
  238.                 return false;
  239.             if (CheckNamesWorker(dirPart, "/id", "/idr", false))
  240.                 return false;
  241.             return true;
  242.         }
  243.        
  244.         private static Boolean CheckNamesWorker(string filestr, string pathskey, string rgxkey, Boolean CheckType)
  245.         {            
  246.             foreach (var filepath in optionspaths[pathskey])
  247.             {
  248.                 if (filestr.ToLower().StartsWith(filepath.ToLower()) == CheckType)
  249.                     return true;                    
  250.             }          
  251.             foreach (var rgx in optionsregex[rgxkey])
  252.             {
  253.                 if (rgx.IsMatch(filestr) == CheckType)
  254.                     return true;
  255.             }            
  256.             return false;
  257.         }        
  258.                
  259.         private static void getFiles(List<string> pathRec, string searchPattern, List<FileInfo> returnList, Boolean recursiveFlag = true, Boolean xj = true)
  260.         {
  261.             foreach (string d in pathRec) { getFiles(d, searchPattern, returnList, recursiveFlag, xj); }        
  262.         }
  263.  
  264.         private static void getFiles(string[] pathRec, string searchPattern, List<FileInfo> returnList, Boolean recursiveFlag = true, Boolean xj = true)
  265.         {
  266.             foreach (string d in pathRec) { getFiles(d, searchPattern, returnList, recursiveFlag, xj); }            
  267.         }
  268.  
  269.         private static void getFiles(string pathRec, string searchPattern, List<FileInfo> returnList, Boolean recursiveFlag = true, Boolean xj = true)
  270.         {
  271.  
  272.             string dirPart;
  273.             string filePart;
  274.  
  275.             if (File.Exists(pathRec))
  276.             {
  277.                 try
  278.                 {
  279.                     FileInfo addf = (new FileInfo(pathRec));
  280.                     if (((addf.Attributes & FileAttributes.ReparsePoint) == 0) || !xj)
  281.                         if (CheckNames(addf.FullName))                        
  282.                             returnList.Add(addf);
  283.                 }
  284.                 catch (Exception e) { WriteErr("Add file error: " + e.Message); }                
  285.             }
  286.             else if (Directory.Exists(pathRec))
  287.             {
  288.                 try
  289.                 {
  290.                     DirectoryInfo Dir = new DirectoryInfo(pathRec);
  291.                     if (((Dir.Attributes & FileAttributes.ReparsePoint) == 0) || !xj)
  292.                         foreach (FileInfo addf in (Dir.GetFiles(searchPattern)))
  293.                         {                        
  294.                             if (((addf.Attributes & FileAttributes.ReparsePoint) == 0) || !xj)
  295.                                 if (CheckNames(addf.FullName))                        
  296.                                     returnList.Add(addf);
  297.                         }
  298.                 }
  299.                 catch (Exception e) { WriteErr("Add files from Directory error: " + e.Message); }
  300.  
  301.                 if (recursiveFlag)
  302.                 {
  303.                     try { getFiles((Directory.GetDirectories(pathRec)), searchPattern, returnList, recursiveFlag, xj); }
  304.                     catch (Exception e) { WriteErr("Add Directory error: " + e.Message); }
  305.                 }                
  306.             }
  307.             else
  308.             {
  309.                 try
  310.                 {
  311.                     filePart = Path.GetFileName(pathRec);
  312.                     dirPart = Path.GetDirectoryName(pathRec);
  313.                 }
  314.                 catch (Exception e)
  315.                 {
  316.                     WriteErr("Parse error on: " + pathRec);
  317.                     WriteErr(@"Make sure you don't end a directory with a \ when using quotes. The console arg parser doesn't accept that.");
  318.                     WriteErr("Exception: " + e.Message);
  319.                     return;
  320.                 }
  321.  
  322.                 if (filePart.IndexOfAny(new char[] {'?','*'}) >= 0)
  323.                 {
  324.                     if ((dirPart == null) || (dirPart == ""))
  325.                         dirPart = Directory.GetCurrentDirectory();
  326.                     if (Directory.Exists(dirPart))
  327.                     {
  328.                         getFiles(dirPart, filePart, returnList, recursiveFlag, xj);
  329.                         return;
  330.                     }
  331.                 }
  332.                 WriteErr("Invalid file path, directory path, file specification, or program option specified: " + pathRec);                                                        
  333.             }            
  334.         }
  335.     }
  336. }

Submit a correction or amendment below (
click here to make a fresh posting)
After submitting an amendment, you'll be able to view the differences between the old and new posts easily.

Syntax highlighting:


Remember me