CLI tool for running Playbooks
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

650 lines
29 KiB

1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. using Newtonsoft.Json.Linq;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Globalization;
  5. using System.IO;
  6. using System.IO.MemoryMappedFiles;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Runtime.InteropServices;
  11. using System.ServiceProcess;
  12. using System.Text;
  13. using System.Threading;
  14. using System.Threading.Tasks;
  15. using System.Windows;
  16. using System.Xml;
  17. using System.Xml.Serialization;
  18. using TrustedUninstaller.Shared.Actions;
  19. using TrustedUninstaller.Shared.Parser;
  20. using TrustedUninstaller.Shared.Tasks;
  21. namespace TrustedUninstaller.Shared
  22. {
  23. public static class AmeliorationUtil
  24. {
  25. private static readonly ConfigParser Parser = new ConfigParser();
  26. private static readonly HttpClient Client = new HttpClient();
  27. public static Playbook Playbook { set; get; }
  28. public static readonly List<string> ErrorDisplayList = new List<string>();
  29. public static int GetProgressMaximum(List<string> options)
  30. {
  31. return Parser.Tasks.Sum(task => task.Actions.Sum(action =>
  32. {
  33. var taskAction = (TaskAction)action;
  34. if (!String.IsNullOrEmpty(taskAction.Option) && (options == null ||
  35. (taskAction.Option.StartsWith("!") && options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase)) ||
  36. (!options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase))))
  37. return 0;
  38. if (!String.IsNullOrEmpty(taskAction.Arch) && (
  39. (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)) ||
  40. (!String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase))))
  41. return 0;
  42. return action.GetProgressWeight();
  43. }));
  44. }
  45. public static bool AddTasks(string configPath, string file)
  46. {
  47. try
  48. {
  49. //This allows for a proper detection of if any error occurred, and if so the CLI will relay an :AME-Fatal Error:
  50. //This is important, as we want the process to stop immediately if a YAML syntax error was detected.
  51. bool hadError = false;
  52. //Adds the config file to the parser's task list
  53. Parser.Add(Path.Combine(configPath, file));
  54. var currentTask = Parser.Tasks[Parser.Tasks.Count - 1];
  55. if (File.Exists("TasksAdded.txt"))
  56. {
  57. var doneTasks = File.ReadAllText("TasksAdded.txt").Split(new[] { "\r\n" }, StringSplitOptions.None);
  58. if (doneTasks.Contains(currentTask.Title))
  59. {
  60. Parser.Tasks.Remove(currentTask);
  61. return true;
  62. }
  63. }
  64. //Get the features of the last added task (the task that was just added from the config file)
  65. var features = currentTask.Features;
  66. //Each feature would reference a directory that has a YAML file, we take those directories and then run the
  67. //AddTasks function again, until we reach a file that doesn't reference any other YAML files, and add them
  68. //all to the parser's tasks list.
  69. if (features == null) return true;
  70. foreach (var feature in features)
  71. {
  72. var subResult = AddTasks(configPath, feature);
  73. // We could return false here, however we want to output ALL detected YAML errors,
  74. // which is why we continue here.
  75. if (!subResult) hadError = true;
  76. }
  77. return hadError ? false : true;
  78. }
  79. catch (Exception e)
  80. {
  81. Console.WriteLine($"Error adding tasks in {configPath + "\\" + file}:\r\n{e.Message}");
  82. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, $"Error adding tasks in {configPath + "\\" + file}.");
  83. return false;
  84. }
  85. }
  86. public static async Task<int> DoActions(UninstallTask task, UninstallTaskPrivilege privilege)
  87. {
  88. try
  89. {
  90. //If the privilege is admin and the program is running as TI, do not do the action.
  91. //if (privilege == UninstallTaskPrivilege.Admin && WinUtil.IsTrustedInstaller())
  92. //{
  93. // return 0;
  94. //}
  95. if (!WinUtil.IsTrustedInstaller())
  96. {
  97. Console.WriteLine("Relaunching as Trusted Installer!");
  98. var mmf = MemoryMappedFile.CreateNew("ImgA", 30000000);
  99. WinUtil.RelaunchAsTrustedInstaller();
  100. if (NativeProcess.Process == null)
  101. {
  102. ErrorLogger.WriteToErrorLog($"Could not launch TrustedInstaller process. Return output was null.",
  103. Environment.StackTrace, "Error while attempting to sync with TrustedInstaller process.");
  104. Console.WriteLine(":AME-Fatal Error: Could not launch TrustedInstaller process.");
  105. Environment.Exit(-1);
  106. }
  107. var delay = 20;
  108. while (!NativeProcess.Process.HasExited)
  109. {
  110. if (delay > 3500)
  111. {
  112. NativeProcess.Process.Kill();
  113. ErrorLogger.WriteToErrorLog($"Could not initialize memory data exchange. Timeframe exceeded.",
  114. Environment.StackTrace, "Error while attempting to sync with TrustedInstaller process.");
  115. Console.WriteLine(":AME-Fatal Error: Could not initialize memory data exchange.");
  116. Environment.Exit(-1);
  117. }
  118. Task.Delay(delay).Wait();
  119. // Kind of inefficient looping this, however it's likely to cause access errors otherwise
  120. using var stream = mmf.CreateViewStream();
  121. using BinaryReader binReader = new BinaryReader(stream);
  122. {
  123. var res = binReader.ReadBytes((int)stream.Length);
  124. var data = Encoding.UTF8.GetString(res);
  125. var end = data.IndexOf('\0');
  126. if (end == 0)
  127. {
  128. delay += 200;
  129. }
  130. else
  131. {
  132. break;
  133. }
  134. }
  135. }
  136. var offset = 0;
  137. var read = false;
  138. using (var stream = mmf.CreateViewStream())
  139. {
  140. while (!NativeProcess.Process.HasExited || read)
  141. {
  142. read = false;
  143. BinaryReader binReader = new BinaryReader(stream);
  144. binReader.BaseStream.Seek(offset, SeekOrigin.Begin);
  145. var res = binReader.ReadBytes((int)stream.Length - offset);
  146. var data = Encoding.UTF8.GetString(res);
  147. var end = data.IndexOf("\0");
  148. var content = data.Substring(0, end);
  149. offset += Encoding.UTF8.GetBytes(content).Length;
  150. var output = content.Split(new [] {Environment.NewLine}, StringSplitOptions.None);
  151. if (output.Length > 0) output = output.Take(output.Length - 1).ToArray();
  152. foreach (var line in output)
  153. {
  154. Console.WriteLine(line);
  155. read = true;
  156. // Introducing ANY delay here makes it lag behind, which isn't ideal
  157. //Task.Delay(5).Wait();
  158. }
  159. Task.Delay(20).Wait();
  160. }
  161. }
  162. mmf.Dispose();
  163. return 0; //Only returns after TI is done
  164. }
  165. //Goes through the list of tasks that are inside the parser class,
  166. //and runs the task using the RunTask method
  167. //Check the Actions folder inside the Shared folder for reference.
  168. foreach (ITaskAction action in task.Actions)
  169. {
  170. var taskAction = (TaskAction)action;
  171. if (!String.IsNullOrEmpty(taskAction.Option) && (Playbook.Options == null ||
  172. (taskAction.Option.StartsWith("!") && Playbook.Options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase)) ||
  173. (!Playbook.Options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase))))
  174. continue;
  175. if (!String.IsNullOrEmpty(taskAction.Arch) && (
  176. (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)) ||
  177. (!String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase))))
  178. continue;
  179. int i = 0;
  180. //var actionType = action.GetType().ToString().Replace("TrustedUninstaller.Shared.Actions.", "");
  181. try
  182. {
  183. do
  184. {
  185. //Console.WriteLine($"Running {actionType}");
  186. Console.WriteLine();
  187. try
  188. {
  189. await action.RunTask();
  190. action.ResetProgress();
  191. }
  192. catch (Exception e)
  193. {
  194. action.ResetProgress();
  195. if (e.InnerException != null)
  196. {
  197. ErrorLogger.WriteToErrorLog(e.InnerException.Message, e.InnerException.StackTrace, e.Message);
  198. }
  199. else
  200. {
  201. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, action.ErrorString());
  202. List<string> ExceptionBreakList = new List<string>() { "System.ArgumentException", "System.SecurityException", "System.UnauthorizedAccessException", "System.UnauthorizedAccessException", "System.TimeoutException" };
  203. if (ExceptionBreakList.Any(x => x.Equals(e.GetType().ToString())))
  204. {
  205. i = 10;
  206. break;
  207. }
  208. }
  209. Thread.Sleep(300);
  210. }
  211. Console.WriteLine($"Status: {action.GetStatus()}");
  212. if (i > 0) Thread.Sleep(50);
  213. i++;
  214. } while (action.GetStatus() != UninstallTaskStatus.Completed && i < 10);
  215. }
  216. catch (Exception e)
  217. {
  218. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Critical error while running action.");
  219. if (!((TaskAction)action).IgnoreErrors)
  220. Console.WriteLine($":AME-ERROR: Critical error while running action: " + e.Message);
  221. }
  222. if (i == 10)
  223. {
  224. var errorString = action.ErrorString();
  225. ErrorLogger.WriteToErrorLog(errorString, Environment.StackTrace, "Action failed to complete.");
  226. // AmeliorationUtil.ErrorDisplayList.Add(errorString) would NOT work here since this
  227. // might be a separate process, and thus has to be forwarded via the console
  228. if (!((TaskAction)action).IgnoreErrors)
  229. Console.WriteLine($":AME-ERROR: {errorString}");
  230. //Environment.Exit(-2);
  231. Console.WriteLine($"Action completed. Weight:{action.GetProgressWeight()}");
  232. continue;
  233. }
  234. Console.WriteLine($"Action completed. Weight:{action.GetProgressWeight()}");
  235. }
  236. Console.WriteLine("Task completed.");
  237. File.AppendAllText("TasksAdded.txt", task.Title + Environment.NewLine);
  238. }
  239. catch (Exception e)
  240. {
  241. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace,
  242. "Encountered an error while doing task actions.");
  243. }
  244. return 0;
  245. }
  246. public static Task<Playbook> DeserializePlaybook(string dir)
  247. {
  248. Playbook pb;
  249. XmlSerializer serializer = new XmlSerializer(typeof(Playbook));
  250. /*serializer.UnknownElement += delegate(object sender, XmlElementEventArgs args)
  251. {
  252. MessageBox.Show(args.Element.Name);
  253. };
  254. serializer.UnknownAttribute += delegate(object sender, XmlAttributeEventArgs args)
  255. {
  256. MessageBox.Show(args.Attr.Name);
  257. };*/
  258. using (XmlReader reader = XmlReader.Create($"{dir}\\playbook.conf"))
  259. {
  260. pb = (Playbook)serializer.Deserialize(reader);
  261. }
  262. var validateResult = pb.Validate();
  263. if (validateResult != null)
  264. throw new XmlException(validateResult);
  265. if (File.Exists($"{dir}\\options.txt"))
  266. {
  267. pb.Options = new List<string>();
  268. using (var reader = new StreamReader($"{dir}\\options.txt"))
  269. {
  270. while (!reader.EndOfStream)
  271. pb.Options.Add(reader.ReadLine());
  272. }
  273. }
  274. pb.Path = dir;
  275. return Task.FromResult(pb);
  276. }
  277. public static async Task<int> StartAmelioration()
  278. {
  279. //Needed after defender removal's reboot, the "current directory" will be set to System32
  280. //After the auto start up.
  281. Directory.SetCurrentDirectory(AppDomain.CurrentDomain.BaseDirectory);
  282. if (File.Exists("TasksAdded.txt") && !WinUtil.IsTrustedInstaller())
  283. {
  284. File.Delete("TasksAdded.txt");
  285. }
  286. if (Directory.Exists("Logs") && !WinUtil.IsTrustedInstaller())
  287. {
  288. if (File.Exists("Logs\\AdminOutput.txt"))
  289. {
  290. File.Delete("Logs\\AdminOutput.txt");
  291. }
  292. if (File.Exists("Logs\\TIOutput.txt"))
  293. {
  294. File.Delete("Logs\\TIOutput.txt");
  295. }
  296. if (File.Exists("Logs\\FileChecklist.txt"))
  297. {
  298. File.Delete("Logs\\FileChecklist.txt");
  299. }
  300. }
  301. //Check if KPH is installed.
  302. ServiceController service = ServiceController.GetDevices()
  303. .FirstOrDefault(s => s.DisplayName == "KProcessHacker2");
  304. if (service == null)
  305. {
  306. //Installs KPH
  307. await WinUtil.RemoveProtectionAsync();
  308. }
  309. var langsFile = Path.Combine($"{Playbook.Path}\\Configuration", "langs.txt");
  310. //Download language packs that were selected by the user
  311. if (!File.Exists(langsFile))
  312. {
  313. File.Create(langsFile);
  314. }
  315. //var langsSelected = File.ReadLines(langsFile);
  316. //await DownloadLanguagesAsync(langsSelected);
  317. //Start adding tasks from the top level configuration folder.
  318. if (!AddTasks($"{Playbook.Path}\\Configuration", "custom.yml"))
  319. {
  320. Console.WriteLine($":AME-Fatal Error: Error adding tasks.");
  321. Environment.Exit(1);
  322. }
  323. if (!Parser.Tasks.Any())
  324. {
  325. Console.Error.WriteLine($"Couldn't find any tasks.");
  326. return -1;
  327. }
  328. //Sort the list based on the priority value.
  329. if (Parser.Tasks.Any(x => x.Priority != Parser.Tasks.First().Priority))
  330. Parser.Tasks.Sort(new TaskComparer());
  331. bool launched = false;
  332. foreach (var task in Parser.Tasks.Where(task => task.Actions.Count != 0))
  333. {
  334. try
  335. {
  336. //if (prevPriv == UninstallTaskPrivilege.TrustedInstaller && task.Privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller())
  337. if (!WinUtil.IsTrustedInstaller() && launched)
  338. {
  339. continue;
  340. }
  341. launched = true;
  342. await DoActions(task, task.Privilege);
  343. //prevPriv = task.Privilege;
  344. }
  345. catch (Exception ex)
  346. {
  347. ErrorLogger.WriteToErrorLog(ex.Message, ex.StackTrace, "Error during DoAction loop.");
  348. }
  349. }
  350. if (WinUtil.IsTrustedInstaller()) return 0;
  351. WinUtil.RegistryManager.UnhookUserHives();
  352. //Check how many files were successfully and unsuccessfully deleted.
  353. var deletedItemsCount = 0;
  354. var failedDeletedItemsCount = 0;
  355. if (File.Exists("Logs\\FileChecklist.txt"))
  356. {
  357. using (var reader = new StreamReader("Logs\\FileChecklist.txt"))
  358. {
  359. var data = reader.ReadToEnd();
  360. var listData = data.Split(new [] { Environment.NewLine }, StringSplitOptions.None).ToList();
  361. deletedItemsCount = listData.FindAll(s => s == "Deleted: True").Count();
  362. failedDeletedItemsCount = listData.FindAll(s => s == "Deleted: False").Count();
  363. }
  364. using (var writer = new StreamWriter("Logs\\FileChecklist.txt", true))
  365. {
  366. writer.WriteLine($"{deletedItemsCount} files were deleted successfully. " +
  367. $"{failedDeletedItemsCount} files couldn't be deleted.");
  368. }
  369. }
  370. Console.WriteLine($"{deletedItemsCount} files were deleted successfully. " +
  371. $"{failedDeletedItemsCount} files couldn't be deleted.");
  372. //Check if the kernel driver is installed.
  373. //service = ServiceController.GetDevices()
  374. //.FirstOrDefault(s => s.DisplayName == "KProcessHacker2");
  375. if (true)
  376. {
  377. //Remove Process Hacker's kernel driver.
  378. await WinUtil.UninstallDriver();
  379. }
  380. await AmeliorationUtil.SafeRunAction(new RegistryKeyAction()
  381. {
  382. KeyName = @"HKLM\SYSTEM\CurrentControlSet\Services\KProcessHacker2",
  383. });
  384. File.Delete("TasksAdded.txt");
  385. Console.WriteLine();
  386. Console.WriteLine("Playbook finished.");
  387. return 0;
  388. }
  389. public static async Task DownloadLanguagesAsync(IEnumerable<string> langsSelected)
  390. {
  391. foreach (var lang in langsSelected)
  392. {
  393. var lowerLang = lang.ToLower();
  394. var arch = RuntimeInformation.OSArchitecture;
  395. var winVersion = Environment.OSVersion.Version.Build;
  396. var convertedArch = "";
  397. switch (arch)
  398. {
  399. case Architecture.X64:
  400. convertedArch = "amd64";
  401. break;
  402. case Architecture.Arm64:
  403. convertedArch = "arm64";
  404. break;
  405. case Architecture.X86:
  406. convertedArch = "x86";
  407. break;
  408. }
  409. var uuidOfWindowsVersion = "";
  410. var uuidResponse =
  411. await Client.GetAsync(
  412. $"https://api.uupdump.net/listid.php?search={winVersion}%20{convertedArch}&sortByDate=1");
  413. switch (uuidResponse.StatusCode)
  414. {
  415. //200 Status code
  416. case HttpStatusCode.OK:
  417. {
  418. var result = uuidResponse.Content.ReadAsStringAsync().Result;
  419. //Gets the UUID of the first build object in the response, we take the first since it's the newest.
  420. uuidOfWindowsVersion = (string)(JToken.Parse(result)["response"]?["builds"]?.Children().First()
  421. .Children().First().Last());
  422. break;
  423. }
  424. //400 Status code
  425. case HttpStatusCode.BadRequest:
  426. {
  427. var result = uuidResponse.Content.ReadAsStringAsync().Result;
  428. dynamic data = JObject.Parse(result);
  429. Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}");
  430. break;
  431. }
  432. //429 Status code
  433. case (HttpStatusCode)429:
  434. {
  435. var result = uuidResponse.Content.ReadAsStringAsync().Result;
  436. dynamic data = JObject.Parse(result);
  437. Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}");
  438. break;
  439. }
  440. //500 Status code
  441. case HttpStatusCode.InternalServerError:
  442. {
  443. var result = uuidResponse.Content.ReadAsStringAsync().Result;
  444. dynamic data = JObject.Parse(result);
  445. Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}");
  446. break;
  447. }
  448. default:
  449. throw new ArgumentOutOfRangeException();
  450. }
  451. var responseString =
  452. await Client.GetAsync(
  453. $"https://api.uupdump.net/get.php?id={uuidOfWindowsVersion}&lang={lowerLang}");
  454. switch (responseString.StatusCode)
  455. {
  456. //200 Status code
  457. case HttpStatusCode.OK:
  458. {
  459. var result = responseString.Content.ReadAsStringAsync().Result;
  460. dynamic data = JObject.Parse(result);
  461. //Add different urls to different packages to a list
  462. var urls = new Dictionary<string, string>
  463. {
  464. {
  465. "basic", (string) data["response"]["files"][
  466. $"microsoft-windows-languagefeatures-basic-{lowerLang}-package-{convertedArch}.cab"]
  467. [
  468. "url"]
  469. },
  470. {
  471. "hw", (string) data["response"]["files"][
  472. $"microsoft-windows-languagefeatures-handwriting-{lowerLang}-package-{convertedArch}.cab"]
  473. [
  474. "url"]
  475. },
  476. {
  477. "ocr", (string) data["response"]["files"][
  478. $"microsoft-windows-languagefeatures-ocr-{lowerLang}-package-{convertedArch}.cab"][
  479. "url"]
  480. },
  481. {
  482. "speech", (string) data["response"]["files"][
  483. $"microsoft-windows-languagefeatures-speech-{lowerLang}-package-{convertedArch}.cab"]
  484. [
  485. "url"]
  486. },
  487. {
  488. "tts", (string) data["response"]["files"][
  489. $"microsoft-windows-languagefeatures-texttospeech-{lowerLang}-package-{convertedArch}.cab"]
  490. [
  491. "url"]
  492. }
  493. };
  494. var amePath = Path.Combine(Path.GetTempPath(), "AME\\");
  495. //Create the directory if it doesn't exist.
  496. var file = new FileInfo(amePath);
  497. file.Directory?.Create(); //Does nothing if the directory already exists
  498. //Final result being "temp\AME\Languages\file.cab"
  499. var downloadPath = Path.Combine(amePath, "Languages\\");
  500. file = new FileInfo(downloadPath);
  501. file.Directory?.Create();
  502. using (var webClient = new WebClient())
  503. {
  504. Console.WriteLine($"Downloading {lowerLang}.cab file, please wait..");
  505. foreach (var url in urls)
  506. {
  507. //Check if the file exists, if it does exist, skip it.
  508. if (File.Exists(Path.Combine(downloadPath, $"{url.Key}_{lowerLang}.cab")))
  509. {
  510. Console.WriteLine($"{url.Key}_{lowerLang} already exists, skipping.");
  511. continue;
  512. }
  513. //Output file format: featureName_languageCode.cab: speech_de-de.cab
  514. webClient.DownloadFile(url.Value, $@"{downloadPath}\{url.Key}_{lowerLang}.cab");
  515. }
  516. }
  517. break;
  518. }
  519. //400 Status code
  520. case HttpStatusCode.BadRequest:
  521. {
  522. var result = responseString.Content.ReadAsStringAsync().Result;
  523. dynamic data = JObject.Parse(result);
  524. Console.WriteLine($"Bad request.\r\nError:{data["response"]["error"]}");
  525. break;
  526. }
  527. //429 Status code
  528. case (HttpStatusCode)429:
  529. {
  530. var result = responseString.Content.ReadAsStringAsync().Result;
  531. dynamic data = JObject.Parse(result);
  532. Console.WriteLine($"Too many requests, try again later.\r\nError:{data["response"]["error"]}");
  533. break;
  534. }
  535. //500 Status code
  536. case HttpStatusCode.InternalServerError:
  537. {
  538. var result = responseString.Content.ReadAsStringAsync().Result;
  539. dynamic data = JObject.Parse(result);
  540. Console.WriteLine($"Internal Server Error.\r\nError:{data["response"]["error"]}");
  541. break;
  542. }
  543. default:
  544. throw new ArgumentOutOfRangeException();
  545. }
  546. }
  547. }
  548. public static async Task<bool> SafeRunAction(ITaskAction action)
  549. {
  550. try
  551. {
  552. return await action.RunTask();
  553. }
  554. catch (Exception e)
  555. {
  556. action.ResetProgress();
  557. if (e.InnerException != null)
  558. {
  559. ErrorLogger.WriteToErrorLog(e.InnerException.Message, e.InnerException.StackTrace, e.Message);
  560. }
  561. else
  562. {
  563. ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, action.ErrorString());
  564. }
  565. }
  566. return false;
  567. }
  568. }
  569. }