private static bool SearchLog(string outputFilePath, out RunInfo runInfo, out RunLogger runLogger)
        {
            try
            {
                runInfo = null; runLogger = null;
                string   outputFileHash     = EM_Helpers.GetFileMD5Hash(outputFilePath);
                DateTime outputFileSaveTime = File.GetLastWriteTime(outputFilePath);

                List <FileInfo> logFiles = (new DirectoryInfo(Path.GetDirectoryName(outputFilePath)).GetFiles($"*{EMLOG_FILENAME}")).ToList();
                logFiles.AddRange(new DirectoryInfo(Path.GetDirectoryName(outputFilePath)).GetFiles($"*{PETLOG_FILENAME}"));

                foreach (FileInfo fiLog in
                         from log in logFiles
                         where File.GetLastWriteTime(log.FullName) >= outputFileSaveTime &&                // log is always the last to write, thus includes only run-outputs with earlier save-times
                         File.GetLastWriteTime(log.FullName) < outputFileSaveTime + new TimeSpan(24, 0, 0) // do not include "ancient" logs (more than a day after run-output finish-time)
                         select log)
                {
                    RunLogger rl = new RunLogger(fiLog.FullName, fiLog.Name.Contains(PETLOG_FILENAME));
                    foreach (RunInfo ri in rl.runInfoList)
                    {
                        foreach (string hash in ri.outputHashes)
                        {
                            if (hash == outputFileHash) // if the hash is equal, it was a run with the same settings ...
                            {
                                runLogger = rl;
                                runInfo   = ri;
                                if (Math.Abs((outputFileSaveTime - ri.duration.GetEndTime_dt()).TotalSeconds) <= 2)
                                {
                                    return(true); // ... but the save-time should verify that it was really this run
                                }
                                break;
                            }
                        }
                    }
                }
                return(runInfo != null);
            }
            catch (Exception exception) { throw new Exception($"Error finding log-file for {outputFilePath}:{Environment.NewLine}{exception.Message}"); }
        }
        private void RunLogToFile(out string txtRunLog)
        {
            if (runInfoList == null)
            {
                throw new Exception("Run-info not available");                      // this is a programming-error
            }
            txtRunLog = string.Join("\t", new List <string>()
            {
                RunInfo.LOGTAG_RUNID, RunInfo.LOGTAG_STATUS,
                RunInfo.LOGTAG_SYSTEM, RunInfo.LOGTAG_DATABASE,
                Duration.LOGTAG_START, Duration.LOGTAG_END, Duration.LOGTAG_DURATION,
                RunInfo.LOGTAG_CURRENCY, RunInfo.LOGTAG_EXRATE
            });

            List <string> extensionHeaders = (from ri in runInfoList select ri.extensionSwitches.Keys)
                                             .Aggregate(new List <string>(), (x, y) => x.Concat(y).ToList())
                                             .Distinct(StringComparer.OrdinalIgnoreCase).ToList();

            if (extensionHeaders.Any())
            {
                txtRunLog += "\t" + string.Join("\t", extensionHeaders);
            }

            txtRunLog += "\t" + RunInfo.LOGTAG_NONDEFAULT_OUTPUTPATH;
            for (int o = 0; o < (from r in runInfoList select r.outputFiles.Count()).Max(); ++o)
            {
                txtRunLog += $"\t{RunInfo.LOGTAG_OUTPUTFILE}\t{RunInfo.LOGTAG_OUTPUTHASH}";
            }
            txtRunLog += Environment.NewLine;

            foreach (RunInfo runInfo in runInfoList)
            {
                string        addOnSystemNames = runInfo.addOnSystemNames.Any() ? "+" + string.Join("+", runInfo.addOnSystemNames) : string.Empty;
                List <string> extensionValues  = new List <string>();
                foreach (string eh in extensionHeaders)
                {
                    if (!runInfo.extensionSwitches.CaseInsensitiveContainsKey(eh))
                    {
                        extensionValues.Add("-1");
                    }
                    else if (runInfo.extensionSwitches.CaseInsensitiveGet(eh).Value == DefPar.Value.ON)
                    {
                        extensionValues.Add("1");
                    }
                    else if (runInfo.extensionSwitches.CaseInsensitiveGet(eh).Value == DefPar.Value.OFF)
                    {
                        extensionValues.Add("0");
                    }
                    else
                    {
                        extensionValues.Add(runInfo.extensionSwitches.CaseInsensitiveGet(eh).Value);
                    }
                }
                txtRunLog += runInfo.runId
                             + "\t" + runInfo.finishStatus
                             + "\t" + runInfo.systemName + addOnSystemNames
                             + "\t" + runInfo.databaseName
                             + "\t" + runInfo.duration.GetStartTime_s()
                             + "\t" + runInfo.duration.GetEndTime_s()
                             + "\t" + runInfo.duration.GetDuration()
                             + "\t" + runInfo.currency
                             + "\t" + runInfo.exchangeRate;

                if (extensionValues.Any())
                {
                    txtRunLog += "\t" + string.Join("\t", extensionValues);
                }

                txtRunLog += "\t" + runInfo.nonDefaultOutputPath;
                foreach (string outputFile in runInfo.outputFiles)
                {
                    string hash = DefPar.Value.NA;
                    int    iOut = runInfo.outputFiles.IndexOf(outputFile);
                    if (runInfo.outputHashes.Count > iOut)
                    {
                        hash = runInfo.outputHashes[iOut];
                    }
                    else
                    {
                        try
                        {
                            hash = EM_Helpers.GetFileMD5Hash(Path.Combine(
                                                                 runInfo.nonDefaultOutputPath == "-" ? generalInfo.outputPath : runInfo.nonDefaultOutputPath,
                                                                 $"{outputFile}{(outputFile.EndsWith(".txt") ? string.Empty : ".txt")}"));
                        }
                        catch { }
                    }
                    txtRunLog += $"\t{outputFile}\t{hash}";
                }
                txtRunLog += Environment.NewLine;
            }
            txtRunLog = txtRunLog.TrimEnd();
        }