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.

391 lines
14 KiB

1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
6 months ago
9 months ago
1 year ago
9 months ago
1 year ago
9 months ago
1 year ago
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Drawing;
  5. using System.Globalization;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Net.Http;
  10. using System.Net.Http.Headers;
  11. using System.Security.Policy;
  12. using System.Text.RegularExpressions;
  13. using System.Threading.Tasks;
  14. using System.Windows.Forms;
  15. using System.Xml.Linq;
  16. using System.Xml.Serialization;
  17. using Newtonsoft.Json;
  18. using Newtonsoft.Json.Linq;
  19. using MessageBox = System.Windows.MessageBox;
  20. namespace TrustedUninstaller.Shared
  21. {
  22. public class Playbook
  23. {
  24. public string Name { get; set; }
  25. public string ShortDescription { get; set; }
  26. public string Description { get; set; }
  27. public string Title { get; set; }
  28. public string Username { get; set; }
  29. public string Details { get; set; }
  30. public string Version { get; set; }
  31. [XmlArray]
  32. [XmlArrayItem(ElementName = "CheckboxPage", Type = typeof(CheckboxPage))]
  33. [XmlArrayItem(ElementName = "RadioPage", Type = typeof(RadioPage))]
  34. [XmlArrayItem(ElementName = "RadioImagePage", Type = typeof(RadioImagePage))]
  35. public FeaturePage[] FeaturePages { get; set; }
  36. public string ProgressText { get; set; } = "Deploying the selected Playbook configuration onto the system.";
  37. public int EstimatedMinutes { get; set; } = 25;
  38. #nullable enable
  39. public string[]? SupportedBuilds { get; set; }
  40. public Requirements.Requirement[] Requirements { get; set; } = new Requirements.Requirement[] {};
  41. public string? Git { get; set; }
  42. public string? DonateLink { get; set; }
  43. public string? Website { get; set; }
  44. public string? ProductCode { get; set; }
  45. public string? PasswordReplace { get; set; }
  46. #nullable disable
  47. public bool Overhaul { get; set; } = false;
  48. public string Path { get; set; }
  49. public bool? UseKernelDriver { get; set; } = null;
  50. public List<string> Options { get; set; } = null;
  51. public string Validate()
  52. {
  53. if (FeaturePages == null)
  54. return null;
  55. foreach (var rawPage in FeaturePages)
  56. {
  57. if (rawPage.GetType() == typeof(CheckboxPage))
  58. {
  59. var page = (CheckboxPage)rawPage;
  60. if (page.Options.Length > 2 && page.TopLine != null && page.BottomLine != null)
  61. return @$"CheckboxPage with a TopLine and BottomLine must not have more than 2 options.";
  62. if (page.Options.Length > 3 && (page.TopLine != null || page.BottomLine != null))
  63. return @$"CheckboxPage with a TopLine or BottomLine must not have more than 3 options.";
  64. if (page.Options.Length > 4)
  65. return @$"CheckboxPage must not have more than 4 options.";
  66. }
  67. else if (rawPage.GetType() == typeof(RadioPage))
  68. {
  69. var page = (RadioPage)rawPage;
  70. if (page.Options.Length > 2 && page.TopLine != null && page.BottomLine != null)
  71. return @$"RadioPage with a TopLine and BottomLine must not have more than 2 options.";
  72. if (page.Options.Length > 3 && (page.TopLine != null || page.BottomLine != null))
  73. return @$"RadioPage with a TopLine or BottomLine must not have more than 3 options.";
  74. if (page.Options.Length > 4)
  75. return @$"RadioPage must not have more than 4 options.";
  76. if (page.DefaultOption != null && !page.Options.Any(x => x.Name == page.DefaultOption))
  77. return @$"No option matching DefaultOption {page.DefaultOption} in RadioPage.";
  78. }
  79. else if (rawPage.GetType() == typeof(RadioImagePage))
  80. {
  81. var page = (RadioImagePage)rawPage;
  82. if (page.Options.Length > 4)
  83. return @$"RadioImagePage must not have more than 4 options.";
  84. if (page.DefaultOption != null && !page.Options.Any(x => x.Name == page.DefaultOption))
  85. return @$"No option matching DefaultOption {page.DefaultOption} in RadioImagePage.";
  86. }
  87. }
  88. return null;
  89. }
  90. public static double GetVersionNumber(string toBeParsed)
  91. {
  92. // Examples:
  93. // 0.4
  94. // 0.4 Alpha
  95. // 1.0.5
  96. // 1.0.5 Beta
  97. // Remove characters after first space (and the space itself)
  98. if (toBeParsed.IndexOf(' ') >= 0)
  99. toBeParsed = toBeParsed.Substring(0, toBeParsed.IndexOf(' '));
  100. if (toBeParsed.LastIndexOf('.') != toBeParsed.IndexOf('.'))
  101. {
  102. // Example: 1.0.5
  103. toBeParsed = toBeParsed.Remove(toBeParsed.LastIndexOf('.'), 1);
  104. // Result: 1.05
  105. }
  106. return double.Parse(toBeParsed, CultureInfo.InvariantCulture);
  107. }
  108. public double GetVersionNumber()
  109. {
  110. return GetVersionNumber(Version);
  111. }
  112. public async Task<string> LatestPlaybookVersion()
  113. {
  114. if (!IsValidGit())
  115. {
  116. throw new ArgumentException("Link provided is not a proper Git link.");
  117. }
  118. string gitPlatform = GetPlaybookGitPlatform();
  119. string repo = GetRepository();
  120. using var httpClient = new HttpClient();
  121. httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub
  122. string url = gitPlatform switch
  123. {
  124. "github.com" => $"https://api.github.com/repos/{repo}/releases",
  125. "gitlab.com" => $"https://gitlab.com/api/v4/projects/{Uri.EscapeDataString(repo)}/releases",
  126. _ => $"https://{gitPlatform}/api/v1/repos/{repo}/releases"
  127. };
  128. var response = await httpClient.GetAsync(url);
  129. response.EnsureSuccessStatusCode();
  130. var json = await response.Content.ReadAsStringAsync();
  131. var array = JArray.Parse(json);
  132. return (string) array.FirstOrDefault()?["tag_name"];
  133. }
  134. public async Task<List<string>> GetPlaybookVersions()
  135. {
  136. if (!IsValidGit())
  137. {
  138. throw new ArgumentException("Link provided is not a proper Git link.");
  139. }
  140. string gitPlatform = GetPlaybookGitPlatform();
  141. string repo = GetRepository();
  142. using var httpClient = new HttpClient();
  143. httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub
  144. string url = gitPlatform switch
  145. {
  146. "github.com" => $"https://api.github.com/repos/{repo}/releases",
  147. "gitlab.com" => $"https://gitlab.com/api/v4/projects/{Uri.EscapeDataString(repo)}/releases",
  148. _ => $"https://{gitPlatform}/api/v1/repos/{repo}/releases"
  149. };
  150. var response = await httpClient.GetAsync(url);
  151. response.EnsureSuccessStatusCode();
  152. var json = await response.Content.ReadAsStringAsync();
  153. var array = JArray.Parse(json);
  154. var result = new List<string>();
  155. foreach (var releaseToken in array)
  156. result.Add((string)releaseToken["tag_name"]);
  157. return result;
  158. }
  159. public async Task DownloadLatestPlaybook(BackgroundWorker worker = null)
  160. {
  161. string repo = GetRepository();
  162. string gitPlatform = GetPlaybookGitPlatform();
  163. var httpClient = new WinUtil.HttpProgressClient();
  164. httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub
  165. var downloadUrl = string.Empty;
  166. var downloadDir = System.IO.Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "AME");
  167. var downloadPath = System.IO.Path.Combine(downloadDir, "playbook.apbx");
  168. string baseUrl;
  169. string releasesUrl;
  170. string assetsKey;
  171. string browserDownloadUrlKey;
  172. switch (gitPlatform)
  173. {
  174. case "github.com":
  175. baseUrl = "https://api.github.com";
  176. releasesUrl = $"{baseUrl}/repos/{repo}/releases";
  177. assetsKey = "assets";
  178. browserDownloadUrlKey = "browser_download_url";
  179. break;
  180. case "gitlab.com":
  181. baseUrl = "https://gitlab.com/api/v4";
  182. releasesUrl = $"{baseUrl}/projects/{Uri.EscapeDataString(repo)}/releases";
  183. assetsKey = "assets.links";
  184. browserDownloadUrlKey = "direct_asset_url";
  185. break;
  186. default:
  187. baseUrl = $"https://{gitPlatform}/api/v1";
  188. releasesUrl = $"{baseUrl}/repos/{repo}/releases";
  189. assetsKey = "assets";
  190. browserDownloadUrlKey = "browser_download_url";
  191. break;
  192. }
  193. var releasesResponse = await httpClient.GetAsync(releasesUrl);
  194. releasesResponse.EnsureSuccessStatusCode();
  195. var releasesContent = await releasesResponse.Content.ReadAsStringAsync();
  196. var releases = JArray.Parse(releasesContent);
  197. var release = releases.FirstOrDefault();
  198. long size = 3000000;
  199. if (release?.SelectToken(assetsKey) is JArray assets)
  200. {
  201. var asset = assets.FirstOrDefault(a => a["name"].ToString().EndsWith(".apbx"));
  202. if (asset != null)
  203. {
  204. downloadUrl = asset[browserDownloadUrlKey]?.ToString();
  205. if (asset["size"] != null)
  206. long.TryParse(asset["size"].ToString(), out size);
  207. }
  208. }
  209. if (worker != null)
  210. worker.ReportProgress(10);
  211. // Download the release asset
  212. if (!string.IsNullOrEmpty(downloadUrl))
  213. {
  214. httpClient.Client.DefaultRequestHeaders.Clear();
  215. httpClient.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => {
  216. if (progressPercentage.HasValue && worker != null)
  217. worker.ReportProgress((int)Math.Ceiling(10 + (progressPercentage.Value * 0.7)));
  218. };
  219. await httpClient.StartDownload(downloadUrl, downloadPath, size);
  220. }
  221. httpClient.Dispose();
  222. }
  223. public string GetRepository()
  224. {
  225. if (Git == null)
  226. {
  227. return null;
  228. }
  229. var urlSegments = Git.Replace("https://", "").Replace("http://", "").Split('/');
  230. return urlSegments[1] +"/"+ urlSegments[2];
  231. }
  232. public string GetPlaybookGitPlatform()
  233. {
  234. if (this.Git == null)
  235. {
  236. throw new NullReferenceException("No Git link available.");
  237. }
  238. return new Uri(Git).Host;
  239. }
  240. public bool IsValidGit()
  241. {
  242. if (Git == null)
  243. {
  244. throw new NullReferenceException("No Git link available.");
  245. }
  246. return Regex.IsMatch(Git, "((git|ssh|http(s)?)|(git@[\\w\\.]+))(:(//)?)([\\w\\.@\\:/\\-~]+)(/)?");;
  247. }
  248. public override string ToString()
  249. {
  250. return $"Name: {Name}\nDescription: {Description}\nUsername: {Username}\nDetails: {Details}\nRequirements: {Requirements}.";
  251. }
  252. [XmlType("CheckboxPage")]
  253. public class CheckboxPage : FeaturePage
  254. {
  255. public class CheckboxOption : Option
  256. {
  257. [XmlAttribute]
  258. public bool IsChecked { get; set; } = true;
  259. }
  260. [XmlArray]
  261. [XmlArrayItem(ElementName = "CheckboxOption", Type = typeof(CheckboxOption))]
  262. public Option[] Options { get; set; }
  263. }
  264. public class RadioPage : FeaturePage
  265. {
  266. [XmlAttribute]
  267. public string DefaultOption { get; set; } = null;
  268. public class RadioOption : Option
  269. {
  270. }
  271. [XmlArray]
  272. [XmlArrayItem(ElementName = "RadioOption", Type = typeof(RadioOption))]
  273. public Option[] Options { get; set; }
  274. }
  275. public class RadioImagePage : FeaturePage
  276. {
  277. [XmlAttribute]
  278. public string DefaultOption { get; set; } = null;
  279. public class RadioImageOption : Option
  280. {
  281. public string FileName { get; set; } = null;
  282. public bool Fill { get; set; } = false;
  283. [XmlAttribute]
  284. public bool None { get; set; } = false;
  285. public string GradientTopColor { get; set; } = null;
  286. public string GradientBottomColor { get; set; } = null;
  287. }
  288. [XmlArray]
  289. [XmlArrayItem(ElementName = "RadioImageOption", Type = typeof(RadioImageOption))]
  290. public Option[] Options { get; set; }
  291. [XmlAttribute]
  292. public bool CheckDefaultBrowser { get; set; } = false;
  293. }
  294. public class FeaturePage
  295. {
  296. [XmlAttribute]
  297. public string DependsOn { get; set; } = null;
  298. [XmlAttribute]
  299. public bool IsRequired { get; set; } = false;
  300. public Line TopLine { get; set; } = null;
  301. public Line BottomLine { get; set; } = null;
  302. public class Option
  303. {
  304. public string Name { get; set; } = null;
  305. public virtual string Text { get; set; }
  306. [XmlAttribute]
  307. public string DependsOn { get; set; } = null;
  308. }
  309. public class Line
  310. {
  311. [XmlAttribute("Text")]
  312. public string Text { get; set; }
  313. [XmlAttribute("Link")]
  314. public string Link { get; set; } = null;
  315. }
  316. [XmlAttribute]
  317. public string Description { get; set; }
  318. }
  319. }
  320. }