├── README.md ├── .gitignore ├── SharpDirLister.sln ├── Interop.cs ├── Properties └── AssemblyInfo.cs ├── SharpDirLister.csproj ├── Log.cs └── SharpDirLister.cs /README.md: -------------------------------------------------------------------------------- 1 | # SharpDirLister 2 | 3 | A .NET 4.0 application that uses an optimized file search algorithm that will output a full directory / file listing of a drive in a matter of seconds and at the end it will compress it to a .gz 4 | 5 | ``` 6 | Usage: SharpDirLister target output 7 | 8 | Examples: 9 | SharpDirLister C:\path\to\directory C:\ 10 | SharpDirLister \\path\to\share c:\users\USER\Appdata\ 11 | ``` 12 | 13 | 14 | ## Credits 15 | This app is based heavily on code from https://stackoverflow.com/q/26321366 and https://stackoverflow.com/a/17259945 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | .vs/ 12 | Makefile.in 13 | *.DS_Store 14 | *.sln.cache 15 | *.suo 16 | *.cache 17 | *.pidb 18 | *.userprefs 19 | *.usertasks 20 | config.log 21 | config.make 22 | config.status 23 | aclocal.m4 24 | install-sh 25 | autom4te.cache/ 26 | *.user 27 | *.tar.gz 28 | tarballs/ 29 | test-results/ 30 | Thumbs.db 31 | 32 | # Mac bundle stuff 33 | *.dmg 34 | *.app 35 | 36 | # resharper 37 | *_Resharper.* 38 | *.Resharper 39 | 40 | # dotCover 41 | *.dotCover 42 | 43 | -------------------------------------------------------------------------------- /SharpDirLister.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.1169 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharpDirLister", "SharpDirLister.csproj", "{308AF7AF-4553-4F76-9D7B-6C33F227FF91}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {308AF7AF-4553-4F76-9D7B-6C33F227FF91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {308AF7AF-4553-4F76-9D7B-6C33F227FF91}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {308AF7AF-4553-4F76-9D7B-6C33F227FF91}.Release|Any CPU.ActiveCfg = Debug|Any CPU 17 | {308AF7AF-4553-4F76-9D7B-6C33F227FF91}.Release|Any CPU.Build.0 = Debug|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {A29A5CC2-EE59-456B-A643-C3DE68329AC4} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /Interop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace SharpDirLister 6 | { 7 | public class Interop 8 | { 9 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 10 | public struct WIN32_FIND_DATAW 11 | { 12 | public FileAttributes dwFileAttributes; 13 | internal System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime; 14 | internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime; 15 | internal System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime; 16 | public int nFileSizeHigh; 17 | public int nFileSizeLow; 18 | public int dwReserved0; 19 | public int dwReserved1; 20 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] 21 | public string cFileName; 22 | [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] 23 | public string cAlternateFileName; 24 | } 25 | 26 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 27 | public static extern IntPtr FindFirstFileW(string lpFileName, out WIN32_FIND_DATAW lpFindFileData); 28 | 29 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] 30 | public static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATAW lpFindFileData); 31 | 32 | [DllImport("kernel32.dll")] 33 | public static extern bool FindClose(IntPtr hFindFile); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("308af7af-4553-4f76-9d7b-6c33f227ff91")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("0.0.0.0")] 35 | [assembly: AssemblyFileVersion("0.0.0.0")] 36 | -------------------------------------------------------------------------------- /SharpDirLister.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {308AF7AF-4553-4F76-9D7B-6C33F227FF91} 8 | Exe 9 | SharpDirLister 10 | SharpDirLister 11 | v4.0 12 | 512 13 | true 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | 31 | 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | 6 | //Logger from https://stackoverflow.com/a/17259945 7 | namespace Log 8 | { 9 | public interface ILogger 10 | { 11 | void WriteLine(string msg); 12 | } 13 | 14 | internal class Param 15 | { 16 | internal enum LogType { Info, Warning, Error, SimpleError }; 17 | 18 | internal LogType Ltype { get; set; } // Type of log 19 | internal string Msg { get; set; } // Message 20 | internal string Action { get; set; } // Action when error or warning occurs (optional) 21 | internal string Obj { get; set; } // Object that was processed whend error or warning occurs (optional) 22 | 23 | internal Param() 24 | { 25 | Ltype = LogType.Info; 26 | Msg = ""; 27 | } 28 | internal Param(LogType logType, string logMsg) 29 | { 30 | Ltype = logType; 31 | Msg = logMsg; 32 | } 33 | internal Param(LogType logType, string logMsg, string logAction, string logObj) 34 | { 35 | Ltype = logType; 36 | Msg = logMsg; 37 | Action = logAction; 38 | Obj = logObj; 39 | } 40 | } 41 | 42 | // Reentrant Logger written with Producer/Consumer pattern. 43 | // It creates a thread that receives write commands through a Queue (a BlockingCollection). 44 | // The user of this log has just to call Logger.WriteLine() and the log is transparently written asynchronously. 45 | 46 | public class Logger : ILogger 47 | { 48 | private string file; 49 | private TextWriter filewritter; 50 | BlockingCollection bc = new BlockingCollection(); 51 | 52 | // Constructor create the thread that wait for work on .GetConsumingEnumerable() 53 | public Logger(string filename) 54 | { 55 | file = filename; 56 | filewritter = new StreamWriter(file); 57 | Console.WriteLine("Results will be saved at {0}", file); 58 | 59 | Task.Factory.StartNew(() => 60 | { 61 | foreach (Param p in bc.GetConsumingEnumerable()) 62 | { 63 | filewritter.WriteLine(p.Msg); 64 | } 65 | }); 66 | } 67 | 68 | ~Logger() 69 | { 70 | // Free the writing thread 71 | bc.CompleteAdding(); 72 | 73 | } 74 | 75 | public void Close() 76 | { 77 | filewritter.Close(); 78 | } 79 | 80 | // Just call this method to log something (it will return quickly because it just queue the work with bc.Add(p)) 81 | public void WriteLine(string msg) 82 | { 83 | Param p = new Param(Param.LogType.Info, msg); 84 | bc.Add(p); 85 | } 86 | 87 | internal void Write(string v) 88 | { 89 | throw new NotImplementedException(); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /SharpDirLister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.IO.Compression; 6 | using System.Collections.Generic; 7 | using static SharpDirLister.Interop; 8 | 9 | namespace SharpDirLister 10 | { 11 | public static class FILETIMEExtensions 12 | { 13 | public static DateTime ToDateTime(this System.Runtime.InteropServices.ComTypes.FILETIME time) 14 | { 15 | ulong high = (ulong)time.dwHighDateTime; 16 | ulong low = (ulong)time.dwLowDateTime; 17 | long fileTime = (long)((high << 32) + low); 18 | 19 | return DateTime.FromFileTimeUtc(fileTime); 20 | } 21 | } 22 | 23 | public class FileInformation 24 | { 25 | public string FullPath; 26 | public DateTime LastWriteTime; 27 | public long Size; 28 | public static string type = "F"; 29 | 30 | public override string ToString() 31 | { 32 | return string.Format("{0} | {1} | {2} | {3}", FullPath, LastWriteTime, Size, type); 33 | } 34 | } 35 | 36 | public class DirectoryInformation 37 | { 38 | public string FullPath; 39 | public DateTime LastWriteTime; 40 | public static string type = "D"; 41 | 42 | public override string ToString() 43 | { 44 | return string.Format("{0} | {1} | {2}", FullPath, LastWriteTime, type); 45 | } 46 | } 47 | 48 | class List 49 | { 50 | static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); 51 | 52 | //Code based heavily on https://stackoverflow.com/q/47471744 53 | static bool FindNextFilePInvokeRecursive(string path, out List files, out List directories, Log.Logger logger) 54 | { 55 | List fileList = new List(); 56 | List directoryList = new List(); 57 | IntPtr findHandle = INVALID_HANDLE_VALUE; 58 | List> info = new List>(); 59 | 60 | try 61 | { 62 | findHandle = FindFirstFileW(path + @"\*", out WIN32_FIND_DATAW findData); 63 | 64 | if (findHandle != INVALID_HANDLE_VALUE) 65 | { 66 | do 67 | { 68 | if (findData.cFileName != "." && findData.cFileName != "..") 69 | { 70 | string fullPath = path + @"\" + findData.cFileName; 71 | 72 | if (findData.dwFileAttributes.HasFlag(FileAttributes.Directory) && !findData.dwFileAttributes.HasFlag(FileAttributes.ReparsePoint)) 73 | { 74 | var dirdata = new DirectoryInformation { FullPath = fullPath, LastWriteTime = findData.ftLastWriteTime.ToDateTime() }; 75 | directoryList.Add(dirdata); 76 | List subDirectoryFileList = new List(); 77 | List subDirectoryDirectoryList = new List(); 78 | 79 | if (FindNextFilePInvokeRecursive(fullPath, out subDirectoryFileList, out subDirectoryDirectoryList, logger)) 80 | { 81 | fileList.AddRange(subDirectoryFileList); 82 | directoryList.AddRange(subDirectoryDirectoryList); 83 | } 84 | } 85 | 86 | else if (!findData.dwFileAttributes.HasFlag(FileAttributes.Directory)) 87 | { 88 | var filedata = new FileInformation { FullPath = fullPath, LastWriteTime = findData.ftLastWriteTime.ToDateTime(), Size = (long)findData.nFileSizeLow + (long)findData.nFileSizeHigh * 4294967296 }; 89 | fileList.Add(filedata); 90 | } 91 | } 92 | } while (FindNextFile(findHandle, out findData)); 93 | } 94 | } 95 | 96 | catch (Exception exception) 97 | { 98 | Console.WriteLine(exception.ToString()); 99 | 100 | if (findHandle != INVALID_HANDLE_VALUE) 101 | { 102 | FindClose(findHandle); 103 | } 104 | 105 | files = null; 106 | directories = null; 107 | return false; 108 | } 109 | 110 | if (findHandle != INVALID_HANDLE_VALUE) 111 | { 112 | FindClose(findHandle); 113 | } 114 | 115 | files = fileList; 116 | directories = directoryList; 117 | return true; 118 | } 119 | 120 | static bool FindNextFilePInvokeRecursiveParalleled(string path, out List files, out List directories, Log.Logger logger) 121 | { 122 | List fileList = new List(); 123 | object fileListLock = new object(); 124 | List directoryList = new List(); 125 | object directoryListLock = new object(); 126 | IntPtr findHandle = INVALID_HANDLE_VALUE; 127 | List> info = new List>(); 128 | 129 | try 130 | { 131 | path = path.EndsWith(@"\") ? path : path + @"\"; 132 | findHandle = FindFirstFileW(path + @"*", out WIN32_FIND_DATAW findData); 133 | 134 | if (findHandle != INVALID_HANDLE_VALUE) 135 | { 136 | do 137 | { 138 | if (findData.cFileName != "." && findData.cFileName != "..") 139 | { 140 | string fullPath = path + findData.cFileName; 141 | 142 | if (findData.dwFileAttributes.HasFlag(FileAttributes.Directory) && !findData.dwFileAttributes.HasFlag(FileAttributes.ReparsePoint)) 143 | { 144 | var dirdata = new DirectoryInformation { FullPath = fullPath, LastWriteTime = findData.ftLastWriteTime.ToDateTime() }; 145 | directoryList.Add(dirdata); 146 | } 147 | 148 | else if (!findData.dwFileAttributes.HasFlag(FileAttributes.Directory)) 149 | { 150 | var filedata = new FileInformation { FullPath = fullPath, LastWriteTime = findData.ftLastWriteTime.ToDateTime() }; 151 | fileList.Add(filedata); 152 | } 153 | } 154 | } while (FindNextFile(findHandle, out findData)); 155 | 156 | directoryList.AsParallel().ForAll(x => 157 | { 158 | List subDirectoryFileList = new List(); 159 | List subDirectoryDirectoryList = new List(); 160 | 161 | if (FindNextFilePInvokeRecursive(x.FullPath, out subDirectoryFileList, out subDirectoryDirectoryList, logger)) 162 | { 163 | lock (fileListLock) 164 | { 165 | fileList.AddRange(subDirectoryFileList); 166 | } 167 | 168 | lock (directoryListLock) 169 | { 170 | directoryList.AddRange(subDirectoryDirectoryList); 171 | } 172 | } 173 | }); 174 | } 175 | } 176 | 177 | catch (Exception exception) 178 | { 179 | Console.WriteLine(exception.ToString()); 180 | 181 | if (findHandle != INVALID_HANDLE_VALUE) 182 | { 183 | FindClose(findHandle); 184 | } 185 | 186 | files = null; 187 | directories = null; 188 | return false; 189 | } 190 | 191 | if (findHandle != INVALID_HANDLE_VALUE) 192 | { 193 | FindClose(findHandle); 194 | } 195 | 196 | files = fileList; 197 | directories = directoryList; 198 | return true; 199 | } 200 | 201 | public static void CompressFile(string path) 202 | { 203 | FileStream sourceFile = File.OpenRead(path); 204 | FileStream destinationFile = File.Create(path + ".gz"); 205 | byte[] buffer = new byte[sourceFile.Length]; 206 | sourceFile.Read(buffer, 0, buffer.Length); 207 | 208 | using (GZipStream output = new GZipStream(destinationFile, CompressionMode.Compress)) 209 | { 210 | Console.WriteLine("Compressing to {0}", destinationFile.Name); 211 | output.Write(buffer, 0, buffer.Length); 212 | } 213 | 214 | sourceFile.Close(); 215 | destinationFile.Close(); 216 | } 217 | 218 | public static void Usage() 219 | { 220 | Console.WriteLine("Usage: SharpDirLister.exe target outputfolder\n" + 221 | "Examples:\n" + 222 | "SharpDirLister.exe c:\\path\\to\\directory c:\\outputfolder\n" + 223 | "SharpDirLister.exe \\\\path\\to\\share c:\\outputfolder\n" + 224 | "Will output two files with the format MMddTHHmmss_listing.{.txt,.txt.gz} in the output folder.\n"+ 225 | "Remember to clean up after yourself.\n"); 226 | Environment.Exit(1); //Be careful if running without fork & run 227 | } 228 | 229 | static void Main(string[] args) 230 | { 231 | try 232 | { 233 | string filename = DateTime.Now.ToString("MMddTHHmmss") + "_listing.txt"; 234 | List files1 = new List(); 235 | List directories1 = new List(); 236 | 237 | if (args.Length != 2) 238 | { 239 | Usage(); 240 | } 241 | 242 | else 243 | { 244 | if (!Directory.Exists(args[0])) { 245 | Console.WriteLine("Error: Directory \"{0}\" not found.\n", args[0]); 246 | Usage(); 247 | } 248 | 249 | if (!Directory.Exists(args[1])) 250 | { 251 | Console.WriteLine("Error: Output directory \"{0}\" not found.\n", args[1]); 252 | Usage(); 253 | } 254 | 255 | Log.Logger logger = new Log.Logger(Path.Combine(args[1], filename)); 256 | while (!FindNextFilePInvokeRecursiveParalleled(args[0], out files1, out directories1, logger)) 257 | { 258 | Thread.Sleep(1000); 259 | } 260 | 261 | 262 | files1.Sort((a, b) => string.Compare(a.FullPath, b.FullPath)); 263 | directories1.Sort((a, b) => string.Compare(a.FullPath, b.FullPath)); 264 | foreach (var filedata in files1) 265 | { 266 | logger.WriteLine(filedata.ToString()); 267 | } 268 | foreach (var filedata in directories1) 269 | { 270 | logger.WriteLine(filedata.ToString()); 271 | } 272 | logger.Close(); 273 | CompressFile(args[1] + "\\" + filename); 274 | Console.WriteLine("Done!"); 275 | } 276 | } 277 | catch (Exception exception) 278 | { 279 | //TODO: If I crash I will not output the listing 280 | Console.WriteLine(exception.Message); 281 | Console.WriteLine(exception.StackTrace); 282 | } 283 | } 284 | } 285 | } --------------------------------------------------------------------------------