Esempio n. 1
0
        private static void Main(string[] args)
        {
            //TODO Live Registry support

            var dumpWarning = false;

            var nlogPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "nlog.config");

            if (File.Exists(nlogPath) == false)
            {
                var config = new LoggingConfiguration();
                var loglevel = LogLevel.Info;

                var layout = @"${message}";

                var consoleTarget = new ColoredConsoleTarget();

                config.AddTarget("console", consoleTarget);

                consoleTarget.Layout = layout;

                var rule1 = new LoggingRule("*", loglevel, consoleTarget);
                config.LoggingRules.Add(rule1);

                LogManager.Configuration = config;
                dumpWarning = true;
            }

            _logger = LogManager.GetCurrentClassLogger();

            if (dumpWarning)
            {
                _logger.Warn("Nlog.config missing! Using default values...");
            }

            if (!CheckForDotnet46())
            {
                _logger.Warn(".net 4.6 not detected. Please install .net 4.6 and try again.");
                return;
            }

            var p = new FluentCommandLineParser<ApplicationArguments>
            {
                IsCaseSensitive = false
            };

//            p.Setup(arg => arg.HiveFile)
//                .As("Hive")
//                .WithDescription("\tHive to search. --Hive or --Dir is required.");

            p.Setup(arg => arg.Directory)
                .As("Dir")
                .WithDescription("\tDirectory to look for hives (recursively)");

//            p.Setup(arg => arg.Literal)
//                .As("Literal")
//                .WithDescription("\tIf present, --sd and --ss search value will not be interpreted as ASCII or Unicode byte strings");

//            p.Setup(arg => arg.RecoverDeleted)
//                .As("Recover")
//                .WithDescription("\tIf present, recover deleted keys/values");

//            p.Setup(arg => arg.DumpKey)
//                .As("DumpKey")
//                .WithDescription("\tDump given key (and all subkeys) and values as json");

//            p.Setup(arg => arg.DumpDir)
//                .As("DumpDir")
//                .WithDescription("\tDirectory to save json output");

//            p.Setup(arg => arg.Recursive)
//                .As("Recursive")
//                .WithDescription("Dump keys/values recursively (ignored if --ValueName used). This option provides FULL details about keys and values");

//            p.Setup(arg => arg.RegEx)
//                .As("RegEx")
//                .WithDescription("\tIf present, treat <string> in --sk, --sv, --sd, and --ss as a regular expression")
//                .SetDefault(false);

//            p.Setup(arg => arg.Sort)
//                .As("Sort")
//                .WithDescription("\tIf present, sort the output").SetDefault(false);

//            p.Setup(arg => arg.SuppressData)
//                .As("SuppressData")
//                .WithDescription("If present, do not show data when using --sd or --ss\r\n").SetDefault(false);

//            p.Setup(arg => arg.KeyName)
//                .As("KeyName")
//                .WithDescription("\tKey name. All values under this key will be dumped");

            // added option by CDI -- begin
            p.Setup(arg => arg.SaveTo)
                .As("SaveTo")
                .WithDescription("\tParsed data will be written to AutoRunParser_Output.csv in this directory");

            p.Setup(arg => arg.NoHeader)
                .As("NoHeader")
                .WithDescription("\tOutput to CSV without header");
            // added option by CDI -- end

//            p.Setup(arg => arg.ValueName)
//                .As("ValueName")
//                .WithDescription("Value name. Only this value will be dumped");

//            p.Setup(arg => arg.SaveToName)
//                .As("SaveToName")
//                .WithDescription("Saves ValueName value data in binary form to file\r\n");

//            p.Setup(arg => arg.StartDate)
//                .As("StartDate")
//                .WithDescription("Start date to look for last write timestamps (UTC). If EndDate is not supplied, last writes AFTER this date will be returned");

//            p.Setup(arg => arg.EndDate)
//                .As("EndDate")
//                .WithDescription("\tEnd date to look for last write timestamps (UTC). If StartDate is not supplied, last writes BEFORE this date will be returned");

//            p.Setup(arg => arg.MinimumSize)
//                .As("MinSize")
//                .WithDescription("\tFind values with data size >= MinSize (specified in bytes)");

//            p.Setup(arg => arg.SimpleSearchKey)
//                .As("sk")
//                .WithDescription("\tSearch for <string> in key names.");

//            p.Setup(arg => arg.SimpleSearchValue)
//                .As("sv")
//                .WithDescription("\tSearch for <string> in value names");

//            p.Setup(arg => arg.SimpleSearchValueData)
//                .As("sd")
//                .WithDescription("\tSearch for <string> in value record's value data");

//            p.Setup(arg => arg.SimpleSearchValueSlack)
//                .As("ss")
//                .WithDescription("\tSearch for <string> in value record's value slack");

            var header =
                $"AutoRunParser version {Assembly.GetExecutingAssembly().GetName().Version}" +
                $" modified by CDI, Inc." +
                "\r\n(Original Author: Eric Zimmerman)" +
                "\r\n\r\nNote: Enclose all strings containing spaces (and all RegEx) with double quotes";

            var footer = @"Example: AutoRunParser.exe --Dir C:\data\ --SaveTo C:\output\";

//            var footer = @"Example: RECmd.exe --Hive ""C:\Temp\UsrClass 1.dat"" --sk URL --Recover" + "\r\n\t " +
//                         @"RECmd.exe --Hive ""D:\temp\UsrClass 1.dat"" --StartDate ""11/13/2014 15:35:01"" " + "\r\n\t " +
//                         @"RECmd.exe --Hive ""D:\temp\UsrClass 1.dat"" --RegEx --sv ""(App|Display)Name"" " + "\r\n\t " +
//                         @"RECmd.exe --Hive ""D:\temp\UsrClass 1.dat"" --StartDate ""05/20/2014 19:00:00"" --EndDate ""05/20/2014 23:59:59"" " + "\r\n\t " +
//                         @"RECmd.exe --Hive ""D:\temp\UsrClass 1.dat"" --StartDate ""05/20/2014 07:00:00 AM"" --EndDate ""05/20/2014 07:59:59 PM"" ";

            p.SetupHelp("?", "help").WithHeader(header).Callback(text => _logger.Info(text + "\r\n" + footer));

            var result = p.Parse(args);

            if (result.HelpCalled)
            {
                return;
            }

            if (result.HasErrors)
            {
                _logger.Error("");
                _logger.Error(result.ErrorText);

                if (result.ErrorText.Contains("--dir"))
                {
                    _logger.Error("Remove the trailing backslash from the --dir argument and try again");
                }              
 
                p.HelpOption.ShowHelp(p.Options);

                return;
            }

            var hivesToProcess = new List<string>();
            var systemHives = new List<string>();
            var softwareHives = new List<string>();
            var ntuserHives = new List<string>();

            if (p.Object.HiveFile?.Length > 0)
            {
                hivesToProcess.Add(p.Object.HiveFile);
            }
            else if (p.Object.Directory?.Length > 0)
            {
                if (Directory.Exists(p.Object.Directory) == false)
                {
                    _logger.Error($"Directory '{p.Object.Directory}' does not exist.");
                    return;
                }

//                var files = Directory.GetFiles(p.Object.Directory, "*", SearchOption.AllDirectories);

                foreach (string fileName in Directory.GetFiles(p.Object.Directory, "*", SearchOption.AllDirectories))
                {
                    Stream st = File.OpenRead(fileName);
                    if (st.Length < 4)
                        continue;

                    BinaryReader br = new BinaryReader(st);
                    if (br.ReadInt32() != 1718052210) // means not "regf"
                        continue;

                    if (Path.GetFileName(fileName).ToUpper().Contains("SYSTEM"))
                    {
                        systemHives.Add(fileName);
                        hivesToProcess.Add(fileName);
                    }
                    else if (Path.GetFileName(fileName).ToUpper().Contains("SOFTWARE"))
                    {
                        softwareHives.Add(fileName);
                        hivesToProcess.Add(fileName);
                    }
                    else if (Path.GetFileName(fileName).ToUpper().Contains("NTUSER.DAT"))
                    {
                        ntuserHives.Add(fileName);
                        hivesToProcess.Add(fileName);
                    }
                }
            }
            else
            {
                p.HelpOption.ShowHelp(p.Options);
                return;
            }

            _logger.Info(header);
            _logger.Info("");

            if (hivesToProcess.Count == 0)
            {
                _logger.Warn("No hives were found. Exiting...");

                return;
            }

            // added by CDI for automation
            if (p.Object.Directory.Length > 0 && p.Object.SaveTo.Length > 0)
            {
                if (Directory.Exists(p.Object.SaveTo) == false)
                {
                    try
                    {
                        Directory.CreateDirectory(p.Object.SaveTo);
                    }
                    catch (Exception ex)
                    {
                        _logger.Error($"Error creating SaveTo '{p.Object.SaveTo}': {ex.Message}. Exiting");
                        return;
                    }
                }

                var outFileBase = string.Empty;
                outFileBase = $"AutoRunParser_Output.csv";
                var outFileName = Path.Combine(p.Object.SaveTo, outFileBase);

                var sw = new StreamWriter(outFileName, true, System.Text.Encoding.Unicode);
                sw.AutoFlush = true;
                sw.WriteLine("FilePath\tKey\tName\tValue\tLastWrittenTimeLocal\tLastWrittenTimeUTC");

                foreach (var systemHive in systemHives)
                {

                    var reg = new RegistryHive(systemHive)
                    {
                        RecoverDeleted = p.Object.RecoverDeleted
                    };

                    reg.ParseHive();
//                    var hive = new RegistryHiveOnDemand(systemHive);
                    var subKey = reg.GetKey("Select");
                    if (subKey == null)
                    {
                        _logger.Warn($"no SYSTEM hive: {systemHive}");
                        continue;
                    }
                    var currentCtlSet = int.Parse(subKey.Values.Single(c => c.ValueName == "Current").ValueData);

                    StreamReader cReader = new StreamReader(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + @"\system.txt", System.Text.Encoding.Default);

                    // 1行ごとに処理
                    while (cReader.Peek() >= 0)
                    {
                        string keyName = cReader.ReadLine();
                        var key = reg.GetKey($@"ControlSet00{currentCtlSet}\{keyName}");

                        if (key == null)
                        {
                            _logger.Warn($"Key not found: {keyName}");
                            continue;
                        }
                        WriteSpecificKeyInfo(key, sw, systemHive);
                    }
                    cReader.Close();
                }

                foreach (var softwareHive in softwareHives)
                {
                    var reg = new RegistryHive(softwareHive)
                    {
                        RecoverDeleted = p.Object.RecoverDeleted
                    };
                    reg.ParseHive();

                    StreamReader cReader = new StreamReader(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + @"\software.txt", System.Text.Encoding.Default);

                    // 1行ごとに処理
                    while (cReader.Peek() >= 0)
                    {
                        string keyName = cReader.ReadLine();
                        var key = reg.GetKey(keyName);

                        if (key == null)
                        {
                            _logger.Warn($"Key not found: {keyName}");
                            continue;
                        }
                        WriteSpecificKeyInfo(key, sw, softwareHive);
                    }
                    cReader.Close();

                }

                foreach (var ntuserHive in ntuserHives)
                {
                    var reg = new RegistryHive(ntuserHive)
                    {
                        RecoverDeleted = p.Object.RecoverDeleted
                    };
                    reg.ParseHive();

                    StreamReader cReader = new StreamReader(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) + @"\ntuser.txt", System.Text.Encoding.Default);

                    // 1行ごとに処理
                    while (cReader.Peek() >= 0)
                    {
                        string keyName = cReader.ReadLine();
                        var key = reg.GetKey(keyName);

                        if (key == null)
                        {
                            _logger.Warn($"Key not found: {keyName}");
                            continue;
                        }
                        WriteSpecificKeyInfo(key, sw, ntuserHive);
                    }
                    cReader.Close();

                }

                _logger.Warn($"Saved to '{outFileName}'");
                sw.Close();
                return;
            }

            var totalHits = 0;
            var hivesWithHits = 0;
            double totalSeconds = 0;

            foreach (var hiveToProcess in hivesToProcess)
            {

                _logger.Info("");
                _logger.Info($"Processing hive '{hiveToProcess}'");
                _logger.Info("");

                if (File.Exists(hiveToProcess) == false)
                {
                    _logger.Warn($"'{hiveToProcess}' does not exist. Skipping");
                    continue;
                }

                try
                {
                    var reg = new RegistryHive(hiveToProcess)
                    {
                        RecoverDeleted = p.Object.RecoverDeleted
                    };

                    _sw = new Stopwatch();
                    _sw.Start();

                    reg.ParseHive();

                    _logger.Info("");

                    if (p.Object.DumpKey.Length > 0 && p.Object.DumpDir.Length > 0)
                    {
                        if (Directory.Exists(p.Object.DumpDir) == false)
                        {
                            try
                            {
                                Directory.CreateDirectory(p.Object.DumpDir);
                            }
                            catch (Exception ex)
                            {
                                _logger.Error($"Error creating DumpDir '{p.Object.DumpDir}': {ex.Message}. Exiting");
                                return;
                            }
                        }

                        var key = reg.GetKey(p.Object.DumpKey);

                        if (key == null)
                        {
                            _logger.Warn($"Key not found: {p.Object.DumpKey}. Exiting");
                            return;
                        }
                        
                        var nout = $"{key.KeyName}_dump.json";
                        var fout = Path.Combine(p.Object.DumpDir, nout);

                        _logger.Info("Found key. Dumping data. Be patient as this can take a while...");

                        var jsons = new JsonSerializer<RegistryKey>();

                        //TODO need a way to get a simple representation of things here, like
                        //name, path, date, etc vs EVERYTHING
                        

                        using (var sw = new StreamWriter(fout))
                        {
                            sw.AutoFlush = true;
                            jsons.SerializeToWriter(key,sw);
                        }
                                              
                        _logger.Warn($"'{p.Object.DumpKey}' saved to '{fout}'");

                    }
                    else if (p.Object.KeyName.Length > 0)
                    {
                        var key = reg.GetKey(p.Object.KeyName);

                        if (key == null)
                        {
                            _logger.Warn($"Key '{p.Object.KeyName}' not found.");
                            DumpStopWatchInfo();
                            continue;
                        }

                        if (p.Object.ValueName.Length > 0)
                        {
                            var val = key.Values.SingleOrDefault(c => c.ValueName == p.Object.ValueName);

                            if (val == null)
                            {
                                _logger.Warn($"Value '{p.Object.ValueName}' not found for key '{p.Object.KeyName}'.");

                                DumpStopWatchInfo();
                                continue;
                            }

                            _sw.Stop();
                            totalSeconds += _sw.Elapsed.TotalSeconds;

                            _logger.Info(val);

                            hivesWithHits += 1;
                            totalHits += 1;

                            if (p.Object.SaveToName.Length > 0)
                            {
                                var baseDir = Path.GetDirectoryName(p.Object.SaveToName);
                                if (Directory.Exists(baseDir) == false)
                                {
                                    Directory.CreateDirectory(baseDir);
                                }

                                _logger.Info($"Saving contents of '{val.ValueName}' to '{p.Object.SaveToName}'");
                                File.WriteAllBytes(p.Object.SaveToName, val.ValueDataRaw);
                            }

                            DumpStopWatchInfo();

                            continue;
                        }

                        hivesWithHits += 1;
                        totalHits += 1;

                        _sw.Stop();
                        totalSeconds += _sw.Elapsed.TotalSeconds;

                        DumpRootKeyName(reg);

                        DumpKey(key, p.Object.Recursive);

                        DumpStopWatchInfo();
                    }
                    else if (p.Object.MinimumSize > 0)
                    {
                        var hits = reg.FindByValueSize(p.Object.MinimumSize).ToList();
                        _sw.Stop();
                        totalSeconds += _sw.Elapsed.TotalSeconds;

                        if (p.Object.Sort)
                        {
                            hits = hits.OrderBy(t => t.Value.ValueDataRaw.Length).ToList();
                        }

                        DumpRootKeyName(reg);

                        hivesWithHits += 1;
                        totalHits += hits.Count;

                        foreach (var valueBySizeInfo in hits)
                        {
                            _logger.Info(
                                $"Key: {Helpers.StripRootKeyNameFromKeyPath(valueBySizeInfo.Key.KeyPath)}, Value: {valueBySizeInfo.Value.ValueName}, Size: {valueBySizeInfo.Value.ValueDataRaw.Length:N0}");
                        }

                        _logger.Info("");

                        var plural = "s";
                        if (hits.Count() == 1)
                        {
                            plural = "";
                        }
                        _logger.Info(
                            $"Found {hits.Count():N0} value{plural} with size greater or equal to {p.Object.MinimumSize:N0} bytes");
                        DumpStopWatchInfo();
                    }
                    else if (p.Object.StartDate != null || p.Object.EndDate != null)
                    {
                        DateTimeOffset start;
                        DateTimeOffset end;
                        var startOk = DateTimeOffset.TryParse(p.Object.StartDate + "-0", out start);
                        var endOk = DateTimeOffset.TryParse(p.Object.EndDate + "-0", out end);

                        DateTimeOffset? startGood = null;
                        DateTimeOffset? endGood = null;
                        var hits = new List<SearchHit>();

                        if (!startOk && p.Object.StartDate != null)
                        {
                            throw new InvalidCastException("'StartDate' is not a valid datetime value");
                        }
                        if (!endOk && p.Object.EndDate != null)
                        {
                            throw new InvalidCastException("'EndDate' is not a valid datetime value");
                        }

                        if (startOk && endOk)
                        {
                            startGood = start;
                            endGood = end;
                            hits = reg.FindByLastWriteTime(startGood, endGood).ToList();
                        }
                        else if (startOk)
                        {
                            startGood = start;

                            hits = reg.FindByLastWriteTime(startGood, null).ToList();
                        }
                        else if (endOk)
                        {
                            endGood = end;
                            hits = reg.FindByLastWriteTime(null, endGood).ToList();
                        }

                        _sw.Stop();
                        totalSeconds += _sw.Elapsed.TotalSeconds;

                        if (p.Object.Sort)
                        {
                            hits = hits.OrderBy(t => t.Key.LastWriteTime ?? new DateTimeOffset()).ToList();
                        }

                        DumpRootKeyName(reg);

                        hivesWithHits += 1;
                        totalHits += hits.Count;

                        foreach (var searchHit in hits)
                        {
                            searchHit.StripRootKeyName = true;
                            _logger.Info($"Last write: {searchHit.Key.LastWriteTime}  Key: {searchHit}");
                        }

                        var suffix = string.Empty;

                        if (startGood != null || endGood != null)
                        {
                            suffix = $"between {startGood} and {endGood}";
                        }
                        if (startGood != null && endGood == null)
                        {
                            suffix = $"after {startGood}";
                        }
                        else if (endGood != null && startGood == null)
                        {
                            suffix = $"before {endGood}";
                        }

                        _logger.Info("");

                        var plural = "s";
                        if (hits.Count() == 1)
                        {
                            plural = "";
                        }
                        _logger.Info($"Found {hits.Count():N0} key{plural} with last write {suffix}");

                        DumpStopWatchInfo();
                    }
                    else if (p.Object.SimpleSearchKey.Length > 0 || p.Object.SimpleSearchValue.Length > 0 ||
                             p.Object.SimpleSearchValueData.Length > 0 || p.Object.SimpleSearchValueSlack.Length > 0)
                    {
                        List<SearchHit> hits = null;

                        if (p.Object.SimpleSearchKey.Length > 0)
                        {
                            hits = reg.FindInKeyName(p.Object.SimpleSearchKey, p.Object.RegEx).ToList();
                            if (p.Object.Sort)
                            {
                                hits = hits.OrderBy(t => t.Key.KeyName).ToList();
                            }
                        }
                        else if (p.Object.SimpleSearchValue.Length > 0)
                        {
                            hits = reg.FindInValueName(p.Object.SimpleSearchValue, p.Object.RegEx).ToList();
                            if (p.Object.Sort)
                            {
                                hits = hits.OrderBy(t => t.Value.ValueName).ToList();
                            }
                        }
                        else if (p.Object.SimpleSearchValueData.Length > 0)
                        {
                            hits =
                                reg.FindInValueData(p.Object.SimpleSearchValueData, p.Object.RegEx, p.Object.Literal)
                                    .ToList();
                            if (p.Object.Sort)
                            {
                                hits = hits.OrderBy(t => t.Value.ValueData).ToList();
                            }
                        }
                        else if (p.Object.SimpleSearchValueSlack.Length > 0)
                        {
                            hits =
                                reg.FindInValueDataSlack(p.Object.SimpleSearchValueSlack, p.Object.RegEx,
                                    p.Object.Literal)
                                    .ToList();
                            if (p.Object.Sort)
                            {
                                hits = hits.OrderBy(t => t.Value.ValueData).ToList();
                            }
                        }

                        if (hits == null)
                        {
                            _logger.Warn("No search results found");
                            DumpStopWatchInfo();
                            continue;
                        }

                        _sw.Stop();
                        totalSeconds += _sw.Elapsed.TotalSeconds;

                        DumpRootKeyName(reg);

                        //set up highlighting
                        var words = new HashSet<string>();
                        foreach (var searchHit in hits)
                        {
                            if (p.Object.SimpleSearchKey.Length > 0)
                            {
                                words.Add(p.Object.SimpleSearchKey);
                            }
                            else if (p.Object.SimpleSearchValue.Length > 0)
                            {
                                words.Add(p.Object.SimpleSearchValue);
                            }
                            else if (p.Object.SimpleSearchValueData.Length > 0)
                            {
                                if (p.Object.RegEx)
                                {
                                    words.Add(p.Object.SimpleSearchValueData);
                                }
                                else
                                {
                                    words.Add(searchHit.HitString);
                                }
                            }
                            else if (p.Object.SimpleSearchValueSlack.Length > 0)
                            {
                                if (p.Object.RegEx)
                                {
                                    words.Add(p.Object.SimpleSearchValueSlack);
                                }
                                else
                                {
                                    words.Add(searchHit.HitString);
                                }
                            }
                        }

                        AddHighlightingRules(words.ToList(), p.Object.RegEx);

                        hivesWithHits += 1;
                        totalHits += hits.Count;

                        foreach (var searchHit in hits)
                        {
                            searchHit.StripRootKeyName = true;

                            if (p.Object.SimpleSearchValueData.Length > 0 || p.Object.SimpleSearchValueSlack.Length > 0)
                            {
                                if (p.Object.SuppressData)
                                {
                                    _logger.Info(
                                        $"Key: {Helpers.StripRootKeyNameFromKeyPath(searchHit.Key.KeyPath)}, Value: {searchHit.Value.ValueName}");
                                }
                                else
                                {
                                    if (p.Object.SimpleSearchValueSlack.Length > 0)
                                    {
                                        _logger.Info(
                                            $"Key: {Helpers.StripRootKeyNameFromKeyPath(searchHit.Key.KeyPath)}, Value: {searchHit.Value.ValueName}, Slack: {searchHit.Value.ValueSlack}");
                                    }
                                    else
                                    {
                                        _logger.Info(
                                            $"Key: {Helpers.StripRootKeyNameFromKeyPath(searchHit.Key.KeyPath)}, Value: {searchHit.Value.ValueName}, Data: {searchHit.Value.ValueData}");
                                    }
                                }
                            }
                            else if (p.Object.SimpleSearchKey.Length > 0)
                            {
                                _logger.Info($"Key: {Helpers.StripRootKeyNameFromKeyPath(searchHit.Key.KeyPath)}");
                            }
                            else if (p.Object.SimpleSearchValue.Length > 0)
                            {
                                _logger.Info(
                                    $"Key: {Helpers.StripRootKeyNameFromKeyPath(searchHit.Key.KeyPath)}, Value: {searchHit.Value.ValueName}");
                            }
                        }

                        var target = (ColoredConsoleTarget) LogManager.Configuration.FindTargetByName("console");
                        target.WordHighlightingRules.Clear();

                        var suffix = string.Empty;
                        var withRegex = string.Empty;

                        var plural = "s";
                        if (hits.Count() == 1)
                        {
                            plural = "";
                        }

                        if (p.Object.SimpleSearchValueData.Length > 0)
                        {
                            suffix = $"value data hit{plural}";
                        }
                        else if (p.Object.SimpleSearchValueSlack.Length > 0)
                        {
                            suffix = $"value slack hit{plural}";
                        }
                        else if (p.Object.SimpleSearchKey.Length > 0)
                        {
                            suffix = $"key{plural}";
                        }
                        else if (p.Object.SimpleSearchValue.Length > 0)
                        {
                            suffix = $"value{plural}";
                        }

                        if (p.Object.RegEx)
                        {
                            withRegex = " (via RegEx)";
                        }

                       _logger.Info("");

                        _logger.Info($"Found {hits.Count():N0} {suffix}{withRegex}");

                        DumpStopWatchInfo();

                        
                    }
                    else
                    {
                        _logger.Warn("Nothing to do! =(");
                    }

                    //TODO search deleted?? should only need to look in reg.UnassociatedRegistryValues
                }
                catch (Exception ex)
                {
                    if (!ex.Message.Contains("bad signature"))
                    {
                        _logger.Error($"There was an error: {ex.Message}");
                    }
                }
            }
            /*
            if (p.Object.Directory?.Length > 0)
            {
                _logger.Info("");

                var suffix2 = totalHits == 1 ? "" : "s";
                var suffix3 = hivesWithHits == 1 ? "" : "s";
                var suffix4 = hivesToProcess.Count == 1 ? "" : "s";

                _logger.Info("---------------------------------------------");
                _logger.Info($"Directory: {p.Object.Directory}");
                _logger.Info(
                  $"Found {totalHits:N0} hit{suffix2} in {hivesWithHits:N0} hive{suffix3} out of {hivesToProcess.Count:N0} file{suffix4}");
                _logger.Info($"Total search time: {totalSeconds:N3} seconds");
                _logger.Info("");
            }
            */
        }