├── 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 | }
--------------------------------------------------------------------------------