diff --git a/README.md b/README.md index 50e2286..554b378 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,6 @@ Coming soon. 4. Build TrustedUninstaller.CLI -If you run into reference issues with the Windows namespace, add `Windows.winmd` as a reference. - ## License This tool has an [MIT license](https://en.wikipedia.org/wiki/MIT_License), which waives any requirements or rules governing the source code’s use, removing politics from the equation. diff --git a/TrustedUninstaller.CLI/App.config b/TrustedUninstaller.CLI/App.config deleted file mode 100644 index d64b1fa..0000000 --- a/TrustedUninstaller.CLI/App.config +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TrustedUninstaller.CLI/CLI.cs b/TrustedUninstaller.CLI/CLI.cs index 3e00569..e6297f0 100644 --- a/TrustedUninstaller.CLI/CLI.cs +++ b/TrustedUninstaller.CLI/CLI.cs @@ -10,6 +10,7 @@ using System.Windows; using Microsoft.Win32; using TrustedUninstaller.Shared; using TrustedUninstaller.Shared.Actions; +using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.CLI { @@ -40,13 +41,12 @@ namespace TrustedUninstaller.CLI if (args.Length < 1 || !Directory.Exists(args[0])) { - Console.WriteLine("No Playbook selected: Use the GUI to select a playbook to run."); + Console.WriteLine("No Playbook selected."); return -1; } AmeliorationUtil.Playbook = await AmeliorationUtil.DeserializePlaybook(args[0]); - AmeliorationUtil.Playbook.Path = args[0]; - + if (!Directory.Exists($"{AmeliorationUtil.Playbook.Path}\\Configuration") || Directory.GetFiles($"{AmeliorationUtil.Playbook.Path}\\Configuration").Length == 0) { Console.WriteLine("Configuration folder is empty, put YAML files in it and restart the application."); @@ -54,6 +54,9 @@ namespace TrustedUninstaller.CLI return -1; } ExtractResourceFolder("resources", Directory.GetCurrentDirectory()); + + + if (!WinUtil.IsTrustedInstaller()) { Console.WriteLine("Checking requirements...\r\n"); @@ -71,20 +74,30 @@ namespace TrustedUninstaller.CLI Console.WriteLine("All 4 windows security toggles must be set to off.\r\nNavigate to Windows Security > Virus & threat detection > manage settings.\r\nPress any key to continue..."); Console.ReadKey(); } - Console.WriteLine("The system must be prepared before continuing Your system will restart after preparation\r\nPress any key to continue..."); + + bool remnantsOnly = Requirements.DefenderDisabled.RemnantsOnly(); + + Console.WriteLine(remnantsOnly ? "The system must be prepared before continuing.\r\nPress any key to continue..." : "The system must be prepared before continuing. Your system will restart after preparation\r\nPress any key to continue..."); Console.ReadKey(); try { - WinUtil.PrepareSystemCLI(); - CmdAction reboot = new CmdAction() + Console.WriteLine("\r\nPreparing system..."); + PrepareSystemCLI(); + Console.WriteLine("Preparation Complete"); + + if (!remnantsOnly) { - Command = "timeout /t 1 & shutdown /r /t 0", - Wait = false - }; + Console.WriteLine("\r\nRestarting system..."); + CmdAction reboot = new CmdAction() + { + Command = "timeout /t 1 & shutdown /r /t 0", + Wait = false + }; - AmeliorationUtil.SafeRunAction(reboot).Wait(); + AmeliorationUtil.SafeRunAction(reboot).Wait(); - Environment.Exit(0); + Environment.Exit(0); + } } catch (Exception e) { Console.WriteLine("Error preparing system: " + e.Message); @@ -96,9 +109,33 @@ namespace TrustedUninstaller.CLI { Console.WriteLine("Internet must be connected to run this Playbook."); } + } + + if (!File.Exists($"{AmeliorationUtil.Playbook.Path}\\options.txt")) + { + List defaultOptions = new List(); + foreach (var page in AmeliorationUtil.Playbook.FeaturePages) + { + if (page.DependsOn != null && !defaultOptions.Contains(page.DependsOn)) + continue; + + if (page.GetType() == typeof(Playbook.CheckboxPage)) + { + foreach (var option in ((Playbook.CheckboxPage)page).Options.Where(x => ((Playbook.CheckboxPage.CheckboxOption)x).IsChecked)) + { + defaultOptions.Add(option.Name); + } + } + + if (page.GetType() == typeof(Playbook.RadioPage)) + defaultOptions.Add(((Playbook.RadioPage)page).DefaultOption); + if (page.GetType() == typeof(Playbook.RadioImagePage)) + defaultOptions.Add(((Playbook.RadioImagePage)page).DefaultOption); + } + AmeliorationUtil.Playbook.Options = defaultOptions; } - + try { if (!Directory.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ame-assassin"))) @@ -227,8 +264,7 @@ namespace TrustedUninstaller.CLI } } } - - + public static async Task> GetDefenderToggles() { var result = new List(); @@ -338,5 +374,113 @@ namespace TrustedUninstaller.CLI }); return result; } + + public static void PrepareSystemCLI() + { + try + { + var defenderStop = new RunAction() + { + RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Exe = $"NSudoLC.exe", + Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"" + + "sc sdset \"WinDefend\" \"D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCLCSWRPLOCRRC;;;BA)(A;;CCLCSWRPLOCRRC;;;BU)(A;;CCLCSWRPLOCRRC;;;IU)(A;;CCLCSWRPLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-80-1913148863-3492339771-4165695881-2087618961-4109116736)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)\"&" + + "sc config WinDefend start=disabled&" + + "net stop WinDefend\"", + CreateWindow = false, + Timeout = 7500, + }; + defenderStop.RunTask().Wait(); + } catch (Exception e) + { + } + + var defenderValues = new RunAction() + { + RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Exe = $"NSudoLC.exe", + Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"ProductAppDataPath\" /f &" + + " reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"InstallLocation\" /f\"", + CreateWindow = false + }; + defenderValues.RunTask().Wait(); + + var defenderKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows Defender"); + if (defenderKey != null && defenderKey.GetValueNames().Contains("InstallLocation")) + { + throw new Exception("Could not remove defender install values."); + } + + var defenderService = new RunAction() + { + RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Exe = $"NSudoLC.exe", + Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg delete \"HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend\" /f", + CreateWindow = false + }; + defenderService.RunTask().Wait(); + + if (Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\WinDefend") != null) + { + var disableDefender = new RunAction() + { + RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Exe = $"NSudoLC.exe", + Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend\" /v \"Start\" /t REG_DWORD /d 4 /f", + CreateWindow = false + }; + disableDefender.RunTask().Wait(); + + if (new RegistryValueAction() {KeyName = @"SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD}.GetStatus() != UninstallTaskStatus.Completed) + throw new Exception("Could not disable WinDefend service."); + } + + // MpOAV.dll normally in use by a lot of processes. This prevents that. + var MpOAVCLSID = new RunAction() + { + RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + Exe = $"NSudoLC.exe", + Arguments = @"-U:T -P:E -M:S -Priority:RealTime -ShowWindowMode:Hide -Wait reg delete ""HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32"" /f", + CreateWindow = false + }; + MpOAVCLSID.RunTask().Wait(); + + if (Registry.ClassesRoot.OpenSubKey(@"CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32") != null) + { + throw new Exception("Could not remove MpOAV mapping."); + } + + // Can cause ProcessHacker driver warnings without this + AmeliorationUtil.SafeRunAction(new RegistryValueAction() + { + KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", + Value = "Enabled", + Data = 0, + }).Wait(); + AmeliorationUtil.SafeRunAction(new RegistryValueAction() + { + KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", + Value = "VulnerableDriverBlocklistEnable", + Data = 0, + }).Wait(); + } + + public static async Task UninstallDriver() + { + CmdAction cmdAction = new CmdAction(); + try + { + Console.WriteLine("Removing driver..."); + cmdAction.Command = Environment.Is64BitOperatingSystem + ? $"ProcessHacker\\x64\\ProcessHacker.exe -s -uninstallkph" + : $"ProcessHacker\\x86\\ProcessHacker.exe -s -uninstallkph"; + await cmdAction.RunTask(); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "ProcessHacker ran into an Error while uninstalling the driver."); + throw; + } + } } } \ No newline at end of file diff --git a/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z b/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z index 53cd97e..ad024ba 100644 Binary files a/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z and b/TrustedUninstaller.CLI/Properties/resources/CLI-Resources.7z differ diff --git a/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj b/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj index 4471c37..1046dfc 100644 --- a/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj +++ b/TrustedUninstaller.CLI/TrustedUninstaller.CLI.csproj @@ -32,6 +32,9 @@ false true true + + *.xml + $([System.IO.Path]::GetFullPath('$(SolutionDir)'))=./ @@ -40,7 +43,7 @@ true bin\x64\Debug\ DEBUG;TRACE - full + embedded x64 7.3 prompt @@ -60,6 +63,13 @@ true en + + bin\x64\Release\ + x64 + true + embedded + SINGLE + B8F0A67800B779C5CEF49BEAB6E5247E373F9452 @@ -81,6 +91,15 @@ app.manifest + + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") + cmd /c "echo Compiled> "$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\Compiled.txt"" + + + PowerShell -NoP -C "Get-Process 'ame-builder' -EA SilentlyContinue | Wait-Process -EA SilentlyContinue; EXIT 0" + PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'CLI' -WindowStyle Hidden" + + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") @@ -104,16 +123,12 @@ - - ..\Windows.winmd - - diff --git a/TrustedUninstaller.Shared/Actions/AppxAction.cs b/TrustedUninstaller.Shared/Actions/AppxAction.cs index 73bfa6e..7951ca2 100644 --- a/TrustedUninstaller.Shared/Actions/AppxAction.cs +++ b/TrustedUninstaller.Shared/Actions/AppxAction.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Threading.Tasks; +//using Windows.ApplicationModel; +//using Windows.Management.Deployment; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; @@ -11,7 +13,8 @@ using System.Threading; namespace TrustedUninstaller.Shared.Actions { - internal class AppxAction : ITaskAction + // Integrate ame-assassin later + internal class AppxAction : TaskAction, ITaskAction { public enum AppxOperation { diff --git a/TrustedUninstaller.Shared/Actions/CmdAction.cs b/TrustedUninstaller.Shared/Actions/CmdAction.cs index b3ffd61..145d6b7 100644 --- a/TrustedUninstaller.Shared/Actions/CmdAction.cs +++ b/TrustedUninstaller.Shared/Actions/CmdAction.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Text; using System.Threading.Tasks; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; @@ -8,7 +9,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - public class CmdAction : ITaskAction + public class CmdAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } @@ -84,8 +85,16 @@ namespace TrustedUninstaller.Shared.Actions process.Dispose(); return true; } - + + var error = new StringBuilder(); process.OutputDataReceived += ProcOutputHandler; + process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args) + { + if (!String.IsNullOrEmpty(args.Data)) + error.AppendLine(args.Data); + else + error.AppendLine(); + }; process.BeginOutputReadLine(); @@ -104,7 +113,7 @@ namespace TrustedUninstaller.Shared.Actions if (process.ExitCode != 0) { - StandardError = process.StandardError.ReadToEnd(); + StandardError = error.ToString(); Console.WriteLine($"cmd instance exited with error code: {process.ExitCode}"); if (!String.IsNullOrEmpty(StandardError)) Console.WriteLine($"Error message: {StandardError}"); diff --git a/TrustedUninstaller.Shared/Actions/FileAction.cs b/TrustedUninstaller.Shared/Actions/FileAction.cs index 7480967..a5dd9be 100644 --- a/TrustedUninstaller.Shared/Actions/FileAction.cs +++ b/TrustedUninstaller.Shared/Actions/FileAction.cs @@ -4,17 +4,20 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Management; +using System.Reflection; +using System.Runtime.InteropServices; using System.Security.AccessControl; using System.ServiceProcess; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Windows; using TrustedUninstaller.Shared.Exceptions; using TrustedUninstaller.Shared.Tasks; using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - public class FileAction : ITaskAction + public class FileAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } @@ -71,6 +74,9 @@ namespace TrustedUninstaller.Shared.Actions return isFile || isDirectory ? UninstallTaskStatus.ToDo : UninstallTaskStatus.Completed; } + [DllImport("Unlocker.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)] + private static extern bool EzUnlockFileW(string path); + private async Task DeleteFile(string file, bool log = false) { if (!TrustedInstaller) @@ -79,15 +85,36 @@ namespace TrustedUninstaller.Shared.Actions if (File.Exists(file)) { + try + { + EzUnlockFileW(file); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog($"Error while unlocking file: " + e.Message, e.StackTrace, + $"FileAction Error", file); + } + + try {await Task.Run(() => File.Delete(file));} catch {} + CmdAction delAction = new CmdAction() { - Command = $"del /q /f {file}" + Command = $"del /q /f \"{file}\"" }; await delAction.RunTask(); } } else if (File.Exists("NSudoLC.exe")) { + try + { + EzUnlockFileW(file); + } + catch (Exception e) + { + ErrorLogger.WriteToErrorLog($"Error while unlocking file: " + e.Message, e.StackTrace, + $"FileAction Error", file); + } RunAction tiDelAction = new RunAction() { Exe = "NSudoLC.exe", @@ -259,15 +286,19 @@ namespace TrustedUninstaller.Shared.Actions Console.WriteLine($"\r\nError: Could not get amount of services locking file.\r\nException: " + e.Message); } } - if (svcCount > 8) Console.WriteLine("Amount of locking services exceeds 8, skipping..."); - - while (processes.Any() && delay <= 800 && svcCount <= 8) + + while (processes.Any() && delay <= 800) { Console.WriteLine("Processes locking the file:"); foreach (var process in processes) { Console.WriteLine(process.ProcessName); } + if (svcCount > 10) + { + Console.WriteLine("Amount of locking services exceeds 10, skipping..."); + break; + } foreach (var process in processes) { diff --git a/TrustedUninstaller.Shared/Actions/LanguageAction.cs b/TrustedUninstaller.Shared/Actions/LanguageAction.cs index 252a9be..034c77a 100644 --- a/TrustedUninstaller.Shared/Actions/LanguageAction.cs +++ b/TrustedUninstaller.Shared/Actions/LanguageAction.cs @@ -6,7 +6,7 @@ using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared.Actions { - class LanguageAction : ITaskAction + class LanguageAction : TaskAction, ITaskAction { public int ProgressWeight { get; set; } = 1; public int GetProgressWeight() => ProgressWeight; diff --git a/TrustedUninstaller.Shared/Actions/LineInFileAction.cs b/TrustedUninstaller.Shared/Actions/LineInFileAction.cs index 954252e..fd4c053 100644 --- a/TrustedUninstaller.Shared/Actions/LineInFileAction.cs +++ b/TrustedUninstaller.Shared/Actions/LineInFileAction.cs @@ -14,7 +14,7 @@ namespace TrustedUninstaller.Shared.Actions Delete = 0, Add = 1 } - internal class LineInFileAction : ITaskAction + internal class LineInFileAction : TaskAction, ITaskAction { [YamlMember(Alias = "path")] public string RawPath { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/PowershellAction.cs b/TrustedUninstaller.Shared/Actions/PowershellAction.cs index 0a17769..4f91867 100644 --- a/TrustedUninstaller.Shared/Actions/PowershellAction.cs +++ b/TrustedUninstaller.Shared/Actions/PowershellAction.cs @@ -2,7 +2,6 @@ using System.Diagnostics; using System.IO; using System.Linq; -using System.Management.Automation; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -13,7 +12,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - public class PowerShellAction : ITaskAction + public class PowerShellAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "command")] public string Command { get; set; } @@ -69,7 +68,7 @@ namespace TrustedUninstaller.Shared.Actions RedirectStandardOutput = true, CreateNoWindow = true }; - if (ExeDir) startInfo.WorkingDirectory = Directory.GetCurrentDirectory() + "\\Executables"; + if (ExeDir) startInfo.WorkingDirectory = AmeliorationUtil.Playbook.Path + "\\Executables"; if (!Wait) { startInfo.RedirectStandardError = false; diff --git a/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs b/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs index 4c7466f..4f49994 100644 --- a/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs +++ b/TrustedUninstaller.Shared/Actions/RegistryKeyAction.cs @@ -19,7 +19,7 @@ namespace TrustedUninstaller.Shared.Actions Delete = 0, Add = 1 } - public class RegistryKeyAction : ITaskAction + public class RegistryKeyAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs b/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs index 41460c4..3aa3ed5 100644 --- a/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs +++ b/TrustedUninstaller.Shared/Actions/RegistryValueAction.cs @@ -36,7 +36,7 @@ namespace TrustedUninstaller.Shared.Actions REG_UNKNOWN = RegistryValueKind.Unknown } - public class RegistryValueAction : ITaskAction + public class RegistryValueAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string KeyName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/RunAction.cs b/TrustedUninstaller.Shared/Actions/RunAction.cs index 1fade55..8b9e630 100644 --- a/TrustedUninstaller.Shared/Actions/RunAction.cs +++ b/TrustedUninstaller.Shared/Actions/RunAction.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - public class RunAction : ITaskAction + public class RunAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } = null; diff --git a/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs b/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs index 193a871..0847619 100644 --- a/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs +++ b/TrustedUninstaller.Shared/Actions/ScheduledTaskAction.cs @@ -19,7 +19,7 @@ namespace TrustedUninstaller.Shared.Actions DeleteFolder = 3 } - internal class ScheduledTaskAction : ITaskAction + internal class ScheduledTaskAction : TaskAction, ITaskAction { [YamlMember(typeof(ScheduledTaskOperation), Alias = "operation")] public ScheduledTaskOperation Operation { get; set; } = ScheduledTaskOperation.Delete; diff --git a/TrustedUninstaller.Shared/Actions/ServiceAction.cs b/TrustedUninstaller.Shared/Actions/ServiceAction.cs index 9219cd1..2ae6c07 100644 --- a/TrustedUninstaller.Shared/Actions/ServiceAction.cs +++ b/TrustedUninstaller.Shared/Actions/ServiceAction.cs @@ -23,7 +23,7 @@ namespace TrustedUninstaller.Shared.Actions Delete, Change } - internal class ServiceAction : ITaskAction + internal class ServiceAction : TaskAction, ITaskAction { [YamlMember(typeof(ServiceOperation), Alias = "operation")] public ServiceOperation Operation { get; set; } = ServiceOperation.Delete; @@ -305,8 +305,8 @@ namespace TrustedUninstaller.Shared.Actions else { cmdAction.Command = Environment.Is64BitOperatingSystem ? - $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {ServiceName} -caction {Operation.ToString().ToLower()}" : - $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {ServiceName} -caction {Operation.ToString().ToLower()}"; + $"ProcessHacker\\x64\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction {Operation.ToString().ToLower()}" : + $"ProcessHacker\\x86\\ProcessHacker.exe -s -elevate -c -ctype service -cobject {service.ServiceName} -caction {Operation.ToString().ToLower()}"; await cmdAction.RunTask(); } diff --git a/TrustedUninstaller.Shared/Actions/ShortcutAction.cs b/TrustedUninstaller.Shared/Actions/ShortcutAction.cs index 07d21b3..7a727b4 100644 --- a/TrustedUninstaller.Shared/Actions/ShortcutAction.cs +++ b/TrustedUninstaller.Shared/Actions/ShortcutAction.cs @@ -8,7 +8,7 @@ using File = System.IO.File; namespace TrustedUninstaller.Shared.Actions { - class ShortcutAction : ITaskAction + class ShortcutAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "path")] public string RawPath { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs b/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs index 756748a..aeaa65c 100644 --- a/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs +++ b/TrustedUninstaller.Shared/Actions/SystemPackageAction.cs @@ -11,7 +11,8 @@ using System.Threading; namespace TrustedUninstaller.Shared.Actions { - internal class SystemPackageAction : ITaskAction + // Integrate ame-assassin later + internal class SystemPackageAction : TaskAction, ITaskAction { public enum Architecture { diff --git a/TrustedUninstaller.Shared/Actions/TaskKillAction.cs b/TrustedUninstaller.Shared/Actions/TaskKillAction.cs index 93382c1..99bd1ea 100644 --- a/TrustedUninstaller.Shared/Actions/TaskKillAction.cs +++ b/TrustedUninstaller.Shared/Actions/TaskKillAction.cs @@ -11,7 +11,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - class TaskKillAction : ITaskAction + class TaskKillAction : TaskAction, ITaskAction { [DllImport("kernel32.dll", SetLastError=true)] [return: MarshalAs(UnmanagedType.Bool)] @@ -85,7 +85,7 @@ namespace TrustedUninstaller.Shared.Actions .Where(process => process.ProcessName.EndsWith(ProcessName.TrimStart('*'), StringComparison.CurrentCultureIgnoreCase)); return Process.GetProcessesByName(ProcessName); - } + } [DllImport("kernel32.dll", SetLastError=true)] static extern bool IsProcessCritical(IntPtr hProcess, ref bool Critical); @@ -252,7 +252,15 @@ namespace TrustedUninstaller.Shared.Actions if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; - IsProcessCritical(process.Handle, ref isCritical); + try + { + IsProcessCritical(process.Handle, ref isCritical); + } + catch (InvalidOperationException e) + { + ErrorLogger.WriteToErrorLog("Could not check if process is critical.", e.StackTrace, "TaskKillAction Error", process.ProcessName); + return false; + } if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); @@ -285,7 +293,15 @@ namespace TrustedUninstaller.Shared.Actions if (!RegexNotCritical.Any(x => Regex.Match(process.ProcessName, x, RegexOptions.IgnoreCase).Success)) { bool isCritical = false; - IsProcessCritical(process.Handle, ref isCritical); + try + { + IsProcessCritical(process.Handle, ref isCritical); + } + catch (InvalidOperationException e) + { + ErrorLogger.WriteToErrorLog("Could not check if process is critical.", e.StackTrace, "TaskKillAction Error", process.ProcessName); + continue; + } if (isCritical) { Console.WriteLine($"{process.ProcessName} is a critical process, skipping..."); diff --git a/TrustedUninstaller.Shared/Actions/UpdateAction.cs b/TrustedUninstaller.Shared/Actions/UpdateAction.cs index b017f8b..ab1edc5 100644 --- a/TrustedUninstaller.Shared/Actions/UpdateAction.cs +++ b/TrustedUninstaller.Shared/Actions/UpdateAction.cs @@ -8,7 +8,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - internal class UpdateAction : ITaskAction + internal class UpdateAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "name")] public string PackageName { get; set; } diff --git a/TrustedUninstaller.Shared/Actions/UserAction.cs b/TrustedUninstaller.Shared/Actions/UserAction.cs index 07be4ff..2a51c86 100644 --- a/TrustedUninstaller.Shared/Actions/UserAction.cs +++ b/TrustedUninstaller.Shared/Actions/UserAction.cs @@ -8,7 +8,7 @@ using System.Security.Principal; namespace TrustedUninstaller.Shared.Actions { - public class UserAction : ITaskAction + public class UserAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "name")] public string Username { get; set; } = ""; diff --git a/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs b/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs index ee5689e..ef166ea 100644 --- a/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs +++ b/TrustedUninstaller.Shared/Actions/WriteStatusAction.cs @@ -5,7 +5,7 @@ using YamlDotNet.Serialization; namespace TrustedUninstaller.Shared.Actions { - public class WriteStatusAction : ITaskAction + public class WriteStatusAction : TaskAction, ITaskAction { [YamlMember(typeof(string), Alias = "status")] public string Status { get; set; } diff --git a/TrustedUninstaller.Shared/AmeliorationUtil.cs b/TrustedUninstaller.Shared/AmeliorationUtil.cs index b8b38da..eb3455a 100644 --- a/TrustedUninstaller.Shared/AmeliorationUtil.cs +++ b/TrustedUninstaller.Shared/AmeliorationUtil.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.IO.MemoryMappedFiles; using System.Linq; @@ -11,6 +12,7 @@ using System.ServiceProcess; using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Windows; using System.Xml; using System.Xml.Serialization; using TrustedUninstaller.Shared.Actions; @@ -30,9 +32,23 @@ namespace TrustedUninstaller.Shared public static readonly List ErrorDisplayList = new List(); - public static int GetProgressMaximum() + public static int GetProgressMaximum(List options) { - return Parser.Tasks.Sum(task => task.Actions.Sum(action => action.GetProgressWeight())); + return Parser.Tasks.Sum(task => task.Actions.Sum(action => + { + var taskAction = (TaskAction)action; + if (!String.IsNullOrEmpty(taskAction.Option) && (options == null || + (taskAction.Option.StartsWith("!") && options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase)) || + (!options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase)))) + return 0; + + if (!String.IsNullOrEmpty(taskAction.Arch) && ( + (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)) || + (!String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)))) + return 0; + + return action.GetProgressWeight(); + })); } public static bool AddTasks(string configPath, string file) @@ -89,16 +105,16 @@ namespace TrustedUninstaller.Shared try { //If the privilege is admin and the program is running as TI, do not do the action. - if (privilege == UninstallTaskPrivilege.Admin && WinUtil.IsTrustedInstaller()) - { - return 0; - } + //if (privilege == UninstallTaskPrivilege.Admin && WinUtil.IsTrustedInstaller()) + //{ + // return 0; + //} - if (privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller()) + if (!WinUtil.IsTrustedInstaller()) { Console.WriteLine("Relaunching as Trusted Installer!"); - var mmf = MemoryMappedFile.CreateNew("ImgA", 5000000); + var mmf = MemoryMappedFile.CreateNew("ImgA", 30000000); WinUtil.RelaunchAsTrustedInstaller(); if (NativeProcess.Process == null) { @@ -185,6 +201,18 @@ namespace TrustedUninstaller.Shared //Check the Actions folder inside the Shared folder for reference. foreach (ITaskAction action in task.Actions) { + var taskAction = (TaskAction)action; + + if (!String.IsNullOrEmpty(taskAction.Option) && (Playbook.Options == null || + (taskAction.Option.StartsWith("!") && Playbook.Options.Contains(taskAction.Option.Substring(1), StringComparer.OrdinalIgnoreCase)) || + (!Playbook.Options.Contains(taskAction.Option, StringComparer.OrdinalIgnoreCase)))) + continue; + + if (!String.IsNullOrEmpty(taskAction.Arch) && ( + (taskAction.Arch.StartsWith("!") && String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)) || + (!String.Equals(taskAction.Arch, RuntimeInformation.ProcessArchitecture.ToString(), StringComparison.OrdinalIgnoreCase)))) + continue; + int i = 0; //var actionType = action.GetType().ToString().Replace("TrustedUninstaller.Shared.Actions.", ""); @@ -217,6 +245,7 @@ namespace TrustedUninstaller.Shared break; } } + Thread.Sleep(300); } Console.WriteLine($"Status: {action.GetStatus()}"); if (i > 0) Thread.Sleep(50); @@ -226,7 +255,8 @@ namespace TrustedUninstaller.Shared catch (Exception e) { ErrorLogger.WriteToErrorLog(e.Message, e.StackTrace, "Critical error while running action."); - Console.WriteLine($":AME-ERROR: Critical error while running action: " + e.Message); + if (!((TaskAction)action).IgnoreErrors) + Console.WriteLine($":AME-ERROR: Critical error while running action: " + e.Message); } if (i == 10) @@ -235,7 +265,8 @@ namespace TrustedUninstaller.Shared ErrorLogger.WriteToErrorLog(errorString, Environment.StackTrace, "Action failed to complete."); // AmeliorationUtil.ErrorDisplayList.Add(errorString) would NOT work here since this // might be a separate process, and thus has to be forwarded via the console - Console.WriteLine($":AME-ERROR: {errorString}"); + if (!((TaskAction)action).IgnoreErrors) + Console.WriteLine($":AME-ERROR: {errorString}"); //Environment.Exit(-2); Console.WriteLine($"Action completed. Weight:{action.GetProgressWeight()}"); continue; @@ -260,12 +291,32 @@ namespace TrustedUninstaller.Shared Playbook pb; XmlSerializer serializer = new XmlSerializer(typeof(Playbook)); + /*serializer.UnknownElement += delegate(object sender, XmlElementEventArgs args) + { + MessageBox.Show(args.Element.Name); + }; + serializer.UnknownAttribute += delegate(object sender, XmlAttributeEventArgs args) + { + MessageBox.Show(args.Attr.Name); + };*/ using (XmlReader reader = XmlReader.Create($"{dir}\\playbook.conf")) { pb = (Playbook)serializer.Deserialize(reader); } - pb.Path = dir; + var validateResult = pb.Validate(); + if (validateResult != null) + throw new XmlException(validateResult); + if (File.Exists($"{dir}\\options.txt")) + { + pb.Options = new List(); + using (var reader = new StreamReader($"{dir}\\options.txt")) + { + while (!reader.EndOfStream) + pb.Options.Add(reader.ReadLine()); + } + } + pb.Path = dir; return Task.FromResult(pb); } @@ -335,17 +386,19 @@ namespace TrustedUninstaller.Shared if (Parser.Tasks.Any(x => x.Priority != Parser.Tasks.First().Priority)) Parser.Tasks.Sort(new TaskComparer()); - UninstallTaskPrivilege prevPriv = UninstallTaskPrivilege.Admin; + bool launched = false; foreach (var task in Parser.Tasks.Where(task => task.Actions.Count != 0)) { try { - if (prevPriv == UninstallTaskPrivilege.TrustedInstaller && task.Privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller()) + //if (prevPriv == UninstallTaskPrivilege.TrustedInstaller && task.Privilege == UninstallTaskPrivilege.TrustedInstaller && !WinUtil.IsTrustedInstaller()) + if (!WinUtil.IsTrustedInstaller() && launched) { continue; } + launched = true; await DoActions(task, task.Privilege); - prevPriv = task.Privilege; + //prevPriv = task.Privilege; } catch (Exception ex) { diff --git a/TrustedUninstaller.Shared/Globals.cs b/TrustedUninstaller.Shared/Globals.cs index 467ba4c..918ac7a 100644 --- a/TrustedUninstaller.Shared/Globals.cs +++ b/TrustedUninstaller.Shared/Globals.cs @@ -9,8 +9,8 @@ namespace TrustedUninstaller.Shared { public class Globals { - public const string CurrentVersion = "0.6.5"; - public const double CurrentVersionNumber = 0.65; + public const string CurrentVersion = "0.7"; + public const double CurrentVersionNumber = 0.7; #if DEBUG public static readonly int WinVer = 19045; #else diff --git a/TrustedUninstaller.Shared/Playbook.cs b/TrustedUninstaller.Shared/Playbook.cs index b168148..2f05939 100644 --- a/TrustedUninstaller.Shared/Playbook.cs +++ b/TrustedUninstaller.Shared/Playbook.cs @@ -1,10 +1,23 @@ using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Globalization; using System.IO; using System.Linq; -using System.Text; +using System.Management.Automation.Runspaces; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Policy; +using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Windows.Forms; +using System.Xml.Linq; using System.Xml.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using MessageBox = System.Windows.MessageBox; namespace TrustedUninstaller.Shared { @@ -18,25 +31,360 @@ namespace TrustedUninstaller.Shared public string Username { get; set; } public string Details { get; set; } public string Version { get; set; } + + [XmlArray] + [XmlArrayItem(ElementName = "CheckboxPage", Type = typeof(CheckboxPage))] + [XmlArrayItem(ElementName = "RadioPage", Type = typeof(RadioPage))] + [XmlArrayItem(ElementName = "RadioImagePage", Type = typeof(RadioImagePage))] + public FeaturePage[] FeaturePages { get; set; } public string ProgressText { get; set; } = "Deploying the selected Playbook configuration onto the system."; public int EstimatedMinutes { get; set; } = 25; #nullable enable public string[]? SupportedBuilds { get; set; } - public Requirements.Requirement[]? Requirements { get; set; } + public Requirements.Requirement[] Requirements { get; set; } = new Requirements.Requirement[] {}; public string? Git { get; set; } public string? DonateLink { get; set; } public string? Website { get; set; } public string? ProductCode { get; set; } public string? PasswordReplace { get; set; } #nullable disable + public bool Overhaul { get; set; } = false; public string Path { get; set; } + + public List Options { get; set; } = null; + + public string Validate() + { + if (FeaturePages == null) + return null; + + foreach (var rawPage in FeaturePages) + { + if (rawPage.GetType() == typeof(CheckboxPage)) + { + var page = (CheckboxPage)rawPage; + + if (page.Options.Length > 2 && page.TopLine != null && page.BottomLine != null) + return @$"CheckboxPage with a TopLine and BottomLine must not have more than 2 options."; + if (page.Options.Length > 3 && (page.TopLine != null || page.BottomLine != null)) + return @$"CheckboxPage with a TopLine or BottomLine must not have more than 3 options."; + if (page.Options.Length > 4) + return @$"CheckboxPage must not have more than 4 options."; + } + else if (rawPage.GetType() == typeof(RadioPage)) + { + var page = (RadioPage)rawPage; + + if (page.Options.Length > 2 && page.TopLine != null && page.BottomLine != null) + return @$"RadioPage with a TopLine and BottomLine must not have more than 2 options."; + if (page.Options.Length > 3 && (page.TopLine != null || page.BottomLine != null)) + return @$"RadioPage with a TopLine or BottomLine must not have more than 3 options."; + if (page.Options.Length > 4) + return @$"RadioPage must not have more than 4 options."; + + if (page.DefaultOption != null && !page.Options.Any(x => x.Name == page.DefaultOption)) + return @$"No option matching DefaultOption {page.DefaultOption} in RadioPage."; + } + else if (rawPage.GetType() == typeof(RadioImagePage)) + { + var page = (RadioImagePage)rawPage; + + if (page.Options.Length > 4) + return @$"RadioImagePage must not have more than 4 options."; + if (page.DefaultOption != null && !page.Options.Any(x => x.Name == page.DefaultOption)) + return @$"No option matching DefaultOption {page.DefaultOption} in RadioImagePage."; + } + } + return null; + } + + public static double GetVersionNumber(string toBeParsed) + { + // Examples: + // 0.4 + // 0.4 Alpha + // 1.0.5 + // 1.0.5 Beta + + + // Remove characters after first space (and the space itself) + if (toBeParsed.IndexOf(' ') >= 0) + toBeParsed = toBeParsed.Substring(0, toBeParsed.IndexOf(' ')); + + if (toBeParsed.LastIndexOf('.') != toBeParsed.IndexOf('.')) + { + // Example: 1.0.5 + toBeParsed = toBeParsed.Remove(toBeParsed.LastIndexOf('.'), 1); + // Result: 1.05 + } + + return double.Parse(toBeParsed, CultureInfo.InvariantCulture); + } + + public double GetVersionNumber() + { + return GetVersionNumber(Version); + } + + public async Task LatestPlaybookVersion() + { + + if (!IsValidGit()) + { + throw new ArgumentException("Link provided is not a proper Git link."); + } + + string gitPlatform = GetPlaybookGitPlatform(); + + string repo = GetRepository(); + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub + + string url = gitPlatform switch + { + "github.com" => $"https://api.github.com/repos/{repo}/releases", + "gitlab.com" => $"https://gitlab.com/api/v4/projects/{Uri.EscapeDataString(repo)}/releases", + _ => $"https://{gitPlatform}/api/v1/repos/{repo}/releases" + }; + + var response = await httpClient.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var array = JArray.Parse(json); + + return (string) array.FirstOrDefault()?["tag_name"]; + } + public async Task> GetPlaybookVersions() + { + + if (!IsValidGit()) + { + throw new ArgumentException("Link provided is not a proper Git link."); + } + + string gitPlatform = GetPlaybookGitPlatform(); + + string repo = GetRepository(); + + using var httpClient = new HttpClient(); + httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub + + string url = gitPlatform switch + { + "github.com" => $"https://api.github.com/repos/{repo}/releases", + "gitlab.com" => $"https://gitlab.com/api/v4/projects/{Uri.EscapeDataString(repo)}/releases", + _ => $"https://{gitPlatform}/api/v1/repos/{repo}/releases" + }; + + var response = await httpClient.GetAsync(url); + + response.EnsureSuccessStatusCode(); + + var json = await response.Content.ReadAsStringAsync(); + var array = JArray.Parse(json); + + var result = new List(); + foreach (var releaseToken in array) + result.Add((string)releaseToken["tag_name"]); + + return result; + } + public async Task DownloadLatestPlaybook(BackgroundWorker worker = null) + { + string repo = GetRepository(); + string gitPlatform = GetPlaybookGitPlatform(); + + var httpClient = new WinUtil.HttpProgressClient(); + httpClient.Client.DefaultRequestHeaders.UserAgent.ParseAdd("curl/7.55.1"); //Required for GitHub + + var downloadUrl = string.Empty; + var downloadDir = System.IO.Path.Combine(Environment.GetEnvironmentVariable("TEMP"), "AME"); + var downloadPath = System.IO.Path.Combine(downloadDir, "playbook.apbx"); + + string baseUrl; + string releasesUrl; + string assetsKey; + string browserDownloadUrlKey; + + switch (gitPlatform) + { + case "github.com": + baseUrl = "https://api.github.com"; + releasesUrl = $"{baseUrl}/repos/{repo}/releases"; + assetsKey = "assets"; + browserDownloadUrlKey = "browser_download_url"; + break; + + case "gitlab.com": + baseUrl = "https://gitlab.com/api/v4"; + releasesUrl = $"{baseUrl}/projects/{Uri.EscapeDataString(repo)}/releases"; + assetsKey = "assets.links"; + browserDownloadUrlKey = "direct_asset_url"; + break; + + default: + baseUrl = $"https://{gitPlatform}/api/v1"; + releasesUrl = $"{baseUrl}/repos/{repo}/releases"; + assetsKey = "assets"; + browserDownloadUrlKey = "browser_download_url"; + break; + } + + var releasesResponse = await httpClient.GetAsync(releasesUrl); + releasesResponse.EnsureSuccessStatusCode(); + + var releasesContent = await releasesResponse.Content.ReadAsStringAsync(); + var releases = JArray.Parse(releasesContent); + var release = releases.FirstOrDefault(); + + long size = 3000000; + + if (release?.SelectToken(assetsKey) is JArray assets) + { + var asset = assets.FirstOrDefault(a => a["name"].ToString().EndsWith(".apbx")); + if (asset != null) + { + downloadUrl = asset[browserDownloadUrlKey]?.ToString(); + + if (asset["size"] != null) + long.TryParse(asset["size"].ToString(), out size); + } + } + + if (worker != null) + worker.ReportProgress(10); + + // Download the release asset + if (!string.IsNullOrEmpty(downloadUrl)) + { + httpClient.Client.DefaultRequestHeaders.Clear(); + + httpClient.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => { + if (progressPercentage.HasValue && worker != null) + worker.ReportProgress((int)Math.Ceiling(10 + (progressPercentage.Value * 0.7))); + }; + + await httpClient.StartDownload(downloadUrl, downloadPath, size); + } + httpClient.Dispose(); + } + + public string GetRepository() + { + if (Git == null) + { + return null; + } + + var urlSegments = Git.Replace("https://", "").Replace("http://", "").Split('/'); + return urlSegments[1] +"/"+ urlSegments[2]; + } + + public string GetPlaybookGitPlatform() + { + if (this.Git == null) + { + throw new NullReferenceException("No Git link available."); + } + + return new Uri(Git).Host; + } + + public bool IsValidGit() + { + if (Git == null) + { + throw new NullReferenceException("No Git link available."); + } + + return Regex.IsMatch(Git, "((git|ssh|http(s)?)|(git@[\\w\\.]+))(:(//)?)([\\w\\.@\\:/\\-~]+)(/)?");; + } public override string ToString() { return $"Name: {Name}\nDescription: {Description}\nUsername: {Username}\nDetails: {Details}\nRequirements: {Requirements}."; } + [XmlType("CheckboxPage")] + public class CheckboxPage : FeaturePage + { + public class CheckboxOption : Option + { + [XmlAttribute] + public bool IsChecked { get; set; } = true; + } + + [XmlArray] + [XmlArrayItem(ElementName = "CheckboxOption", Type = typeof(CheckboxOption))] + public Option[] Options { get; set; } + } + public class RadioPage : FeaturePage + { + [XmlAttribute] + public string DefaultOption { get; set; } = null; + public class RadioOption : Option + { + } + + [XmlArray] + [XmlArrayItem(ElementName = "RadioOption", Type = typeof(RadioOption))] + public Option[] Options { get; set; } + } + public class RadioImagePage : FeaturePage + { + [XmlAttribute] + public string DefaultOption { get; set; } = null; + public class RadioImageOption : Option + { + public string FileName { get; set; } = null; + + public bool Fill { get; set; } = false; + [XmlAttribute] + public bool None { get; set; } = false; + + public string GradientTopColor { get; set; } = null; + public string GradientBottomColor { get; set; } = null; + } + + [XmlArray] + [XmlArrayItem(ElementName = "RadioImageOption", Type = typeof(RadioImageOption))] + public Option[] Options { get; set; } + + [XmlAttribute] + public bool CheckDefaultBrowser { get; set; } = false; + } + + public class FeaturePage + { + [XmlAttribute] + public string DependsOn { get; set; } = null; + [XmlAttribute] + public bool IsRequired { get; set; } = false; + public Line TopLine { get; set; } = null; + public Line BottomLine { get; set; } = null; + + public class Option + { + public string Name { get; set; } = null; + public virtual string Text { get; set; } + + [XmlAttribute] + public string DependsOn { get; set; } = null; + } + public class Line + { + [XmlAttribute("Text")] + public string Text { get; set; } + [XmlAttribute("Link")] + public string Link { get; set; } = null; + } + [XmlAttribute] + public string Description { get; set; } + } } } diff --git a/TrustedUninstaller.Shared/ProviderStatus.cs b/TrustedUninstaller.Shared/ProviderStatus.cs index 2cf09b6..cbd7da0 100644 --- a/TrustedUninstaller.Shared/ProviderStatus.cs +++ b/TrustedUninstaller.Shared/ProviderStatus.cs @@ -39,6 +39,7 @@ namespace TrustedUninstaller.Shared public SignatureStatusFlags SignatureStatus; public AVStatusFlags AVStatus; public ProviderFlags SecurityProvider; + public bool FileExists; public string DisplayName; } } diff --git a/TrustedUninstaller.Shared/Requirements.cs b/TrustedUninstaller.Shared/Requirements.cs index 7f8a776..c9dac47 100644 --- a/TrustedUninstaller.Shared/Requirements.cs +++ b/TrustedUninstaller.Shared/Requirements.cs @@ -14,6 +14,7 @@ using System.Xml.Serialization; using Microsoft.Win32; using TrustedUninstaller.Shared; using TrustedUninstaller.Shared.Actions; +using TrustedUninstaller.Shared.Tasks; namespace TrustedUninstaller.Shared { @@ -36,12 +37,16 @@ namespace TrustedUninstaller.Shared Activation = 5, [XmlEnum("NoAntivirus")] NoAntivirus = 6, + [XmlEnum("LocalAccounts")] + LocalAccounts = 11, [XmlEnum("PasswordSet")] - PasswordSet = 7, + PasswordSet = 11, [XmlEnum("AdministratorPasswordSet")] AdministratorPasswordSet = 8, [XmlEnum("PluggedIn")] PluggedIn = 9, + [XmlEnum("NoTweakware")] + NoTweakware = 10, } public static async Task MetRequirements(this Requirement[] requirements) @@ -59,7 +64,7 @@ namespace TrustedUninstaller.Shared else metRequirements.Add(Requirement.NoInternet); if (requirements.Contains(Requirement.NoAntivirus)) - if (await new NoAntivirus().IsMet()) metRequirements.Add(Requirement.NoAntivirus); + if (true) metRequirements.Add(Requirement.NoAntivirus); if (requirements.Contains(Requirement.NoPendingUpdates)) if (await new NoPendingUpdates().IsMet()) metRequirements.Add(Requirement.NoPendingUpdates); @@ -72,9 +77,9 @@ namespace TrustedUninstaller.Shared if (requirements.Contains(Requirement.DefenderToggled)) if (await new DefenderDisabled().IsMet()) metRequirements.Add(Requirement.DefenderToggled); - - if (requirements.Contains(Requirement.PasswordSet)) - metRequirements.Add(Requirement.PasswordSet); + + if (requirements.Contains(Requirement.LocalAccounts)) + metRequirements.Add(Requirement.LocalAccounts); if (requirements.Contains(Requirement.AdministratorPasswordSet)) metRequirements.Add(Requirement.AdministratorPasswordSet); @@ -210,13 +215,35 @@ namespace TrustedUninstaller.Shared { public async Task IsMet() { - if (Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\WinDefend") != null) + try + { + if (Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\WinDefend") != null && new RegistryValueAction() { KeyName = @"SYSTEM\CurrentControlSet\Services\WinDefend", Value = "Start", Data = 4, Type = RegistryValueType.REG_DWORD }.GetStatus() != UninstallTaskStatus.Completed) + return false; + + if (Registry.ClassesRoot.OpenSubKey(@"CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32") != null) return false; + if (Registry.ClassesRoot.OpenSubKey(@"CLSID\{a463fcb9-6b1c-4e0d-a80b-a2ca7999e25d}\InprocServer32") != null) return false; + var key = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity"); + if (key != null && (int)key.GetValue("Enabled") != 0) + { + return false; + } + } + catch (Exception e) + { + } + return RemnantsOnly(); + } + public static bool RemnantsOnly() + { + if (Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Services\WinDefend") != null) return false; return Process.GetProcessesByName("MsMpEng").Length == 0; } public async Task Meet() { + throw new NotImplementedException(); + OnProgressAdded(30); try { @@ -244,7 +271,7 @@ namespace TrustedUninstaller.Shared }; await defenderService.RunTask(); OnProgressAdded(20); - // MpOAV.dll normally in use by a lot of processes. This prevents that. + // MpOAV.dll normally is in use by a lot of processes. This prevents that. var MpOAVCLSID = new RunAction() { Exe = $"NSudoLC.exe", diff --git a/TrustedUninstaller.Shared/Tasks/TaskAction.cs b/TrustedUninstaller.Shared/Tasks/TaskAction.cs new file mode 100644 index 0000000..e720b40 --- /dev/null +++ b/TrustedUninstaller.Shared/Tasks/TaskAction.cs @@ -0,0 +1,14 @@ +using YamlDotNet.Serialization; + +namespace TrustedUninstaller.Shared.Tasks +{ + public class TaskAction + { + [YamlMember(typeof(bool), Alias = "ignoreErrors")] + public bool IgnoreErrors { get; set; } = false; + [YamlMember(typeof(string), Alias = "option")] + public string Option { get; set; } = null; + [YamlMember(typeof(string), Alias = "cpuArch")] + public string Arch { get; set; } = null; + } +} diff --git a/TrustedUninstaller.Shared/Tasks/UninstallTask.cs b/TrustedUninstaller.Shared/Tasks/UninstallTask.cs index 9137c46..fa2ae20 100644 --- a/TrustedUninstaller.Shared/Tasks/UninstallTask.cs +++ b/TrustedUninstaller.Shared/Tasks/UninstallTask.cs @@ -19,12 +19,20 @@ namespace TrustedUninstaller.Shared.Tasks public int? MaxVersion { get; set; } #nullable disable public UninstallTaskStatus Status { get; set; } = UninstallTaskStatus.ToDo; - public List Actions { get; set; } + + public List Actions { get; set; } = new List(); public int Priority { get; set; } = 1; public UninstallTaskPrivilege Privilege { get; set; } = UninstallTaskPrivilege.Admin; + public List Features { get; set; } = new List(); + public List Tasks + { + set => Features = value; + get => Features; + } + public void Update() { /* diff --git a/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj b/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj index 44241cc..96d9cac 100644 --- a/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj +++ b/TrustedUninstaller.Shared/TrustedUninstaller.Shared.csproj @@ -14,6 +14,9 @@ 512 true + + *.xml + $([System.IO.Path]::GetFullPath('$(SolutionDir)'))=./ @@ -40,6 +43,14 @@ enable true + + bin\x64\Release\ + x64 + true + embedded + true + SINGLE + @@ -61,10 +72,8 @@ - - bin\Debug\Windows.winmd - True - False + + ..\TrustedUninstaller.GUI\bin\x64\Release\YamlDotNet.dll @@ -98,6 +107,7 @@ + @@ -121,7 +131,7 @@ - + @@ -145,6 +155,15 @@ true true + + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") + cmd /c "echo Compiled> "$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\Compiled.txt"" + + + + PowerShell -NoP -C "Get-Process 'ame-builder' -EA SilentlyContinue | Wait-Process -EA SilentlyContinue; EXIT 0" +PowerShell -NoP -C "Start-Process '$(SolutionDir)\TrustedUninstaller.GUI\gui-builder\ame-builder.exe' -ArgumentList 'Shared' -WindowStyle Hidden" + for /f "usebackq delims=" %%A in (`DIR /B /S /A:d "$(SolutionDir)" ^| FINDSTR /R /c:".*\\bin\\.*\\de$" /c:".*\\bin\\.*\\en$" /c:".*\\bin\\.*\\es$" /c:".*\\bin\\.*\\fr$" /c:".*\\bin\\.*\\it$" /c:".*\\bin\\.*\\ja$" /c:".*\\bin\\.*\\ko$" /c:".*\\bin\\.*\\ru$" /c:".*\\bin\\.*\\zh-Hans$" /c:".*\\bin\\.*\\zh-Hant$" /c:".*\\bin\\.*\\pl$" /c:".*\\bin\\.*\\zh-CN$"`) do (RMDIR /Q /S "%%A" & cmd /c "exit /b 0") diff --git a/TrustedUninstaller.Shared/WinUtil.cs b/TrustedUninstaller.Shared/WinUtil.cs index c926f84..4ff3927 100644 --- a/TrustedUninstaller.Shared/WinUtil.cs +++ b/TrustedUninstaller.Shared/WinUtil.cs @@ -173,7 +173,7 @@ namespace TrustedUninstaller.Shared select g).FirstOrDefault(); return msAccount == null ? Environment.UserName : msAccount.Substring(@"MicrosoftAccount\".Length); } - + public static bool IsLocalAccount() { var wi = WindowsIdentity.GetCurrent(); @@ -190,15 +190,14 @@ namespace TrustedUninstaller.Shared public enum SL_GENUINE_STATE { SL_GEN_STATE_IS_GENUINE = 0, - // SL_GEN_STATE_INVALID_LICENSE = 1, // SL_GEN_STATE_TAMPERED = 2, SL_GEN_STATE_LAST = 3 } - + [DllImport("Slwga.dll", EntryPoint = "SLIsGenuineLocal", CharSet = CharSet.None, ExactSpelling = - false, SetLastError = false, PreserveSig = true, CallingConvention = CallingConvention.Winapi, BestFitMapping = - false, ThrowOnUnmappableChar = false)] + false, SetLastError = false, PreserveSig = true, CallingConvention = CallingConvention.Winapi, BestFitMapping = + false, ThrowOnUnmappableChar = false)] [PreserveSigAttribute()] internal static extern uint SLIsGenuineLocal(ref SLID slid, [In, Out] ref SL_GENUINE_STATE genuineState, IntPtr val3); @@ -206,7 +205,7 @@ namespace TrustedUninstaller.Shared { // Microsoft-Windows-Security-SPP GUID // http://technet.microsoft.com/en-us/library/dd772270.aspx - var windowsSlid = new SLID("55c92734-d682-4d71-983e-d6ec3f16059f"); + var windowsSlid = new SLID("55c92734-d682-4d71-983e-d6ec3f16059f"); var genuineState = SL_GENUINE_STATE.SL_GEN_STATE_LAST; var resultInt = SLIsGenuineLocal(ref windowsSlid, ref genuineState, IntPtr.Zero); #if DEBUG @@ -214,20 +213,21 @@ namespace TrustedUninstaller.Shared #else return resultInt == 0 && genuineState == SL_GENUINE_STATE.SL_GEN_STATE_IS_GENUINE; #endif + } private static IEnumerable GetWindowsGroups(WindowsIdentity id) { var irc = id.Groups ?? new IdentityReferenceCollection(); - return irc.Select(ir => (NTAccount)ir.Translate(typeof(NTAccount))).Select(acc => acc.Value).ToList(); + return irc.Select(ir => (NTAccount) ir.Translate(typeof(NTAccount))).Select(acc => acc.Value).ToList(); } public static bool HasWindowsGroup(string groupName) { var appDomain = Thread.GetDomain(); appDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); - var currentPrincipal = (WindowsPrincipal)Thread.CurrentPrincipal; - var groups = GetWindowsGroups((WindowsIdentity)currentPrincipal.Identity); + var currentPrincipal = (WindowsPrincipal) Thread.CurrentPrincipal; + var groups = GetWindowsGroups((WindowsIdentity) currentPrincipal.Identity); return groups.Any(group => group == groupName); } @@ -554,7 +554,8 @@ namespace TrustedUninstaller.Shared DisplayName = result["displayName"].ToString(), AVStatus = enabled ? AVStatusFlags.Enabled : AVStatusFlags.Unknown, SecurityProvider = ProviderFlags.ANTIVIRUS, - SignatureStatus = outdated ? SignatureStatusFlags.OutOfDate : SignatureStatusFlags.UpToDate + SignatureStatus = outdated ? SignatureStatusFlags.OutOfDate : SignatureStatusFlags.UpToDate, + FileExists = File.Exists(result["pathToSignedProductExe"].ToString()) }; avList.Add(av); } @@ -682,87 +683,7 @@ namespace TrustedUninstaller.Shared return false; } } - - public static void PrepareSystemCLI() - { - try - { - var defenderStop = new RunAction() - { - RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - Exe = $"NSudoLC.exe", - Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"" + - "sc sdset \"WinDefend\" \"D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;SY)(A;;CCLCSWRPLOCRRC;;;BA)(A;;CCLCSWRPLOCRRC;;;BU)(A;;CCLCSWRPLOCRRC;;;IU)(A;;CCLCSWRPLOCRRC;;;SU)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-80-1913148863-3492339771-4165695881-2087618961-4109116736)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)\"&" + - "sc config WinDefend start=disabled&" + - "net stop WinDefend\"", - CreateWindow = false, - Timeout = 7500, - }; - defenderStop.RunTask().Wait(); - } catch (Exception e) - { - } - - var defenderValues = new RunAction() - { - RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - Exe = $"NSudoLC.exe", - Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait cmd /c \"reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"ProductAppDataPath\" /f &" + - " reg delete \"HKLM\\SOFTWARE\\Microsoft\\Windows Defender\" /v \"InstallLocation\" /f\"", - CreateWindow = false - }; - defenderValues.RunTask().Wait(); - - var defenderKey = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows Defender"); - if (defenderKey != null && defenderKey.GetValueNames().Contains("InstallLocation")) - { - throw new Exception("Could not remove defender install values."); - } - var defenderService = new RunAction() - { - RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - Exe = $"NSudoLC.exe", - Arguments = "-U:T -P:E -M:S -ShowWindowMode:Hide -Priority:RealTime -Wait reg delete \"HKLM\\SYSTEM\\CurrentControlSet\\Services\\WinDefend\" /f", - CreateWindow = false - }; - defenderService.RunTask().Wait(); - - if (Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Services\\WinDefend") != null) - { - throw new Exception("Could not remove WinDefend service."); - } - - // MpOAV.dll normally in use by a lot of processes. This prevents that. - var MpOAVCLSID = new RunAction() - { - RawPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - Exe = $"NSudoLC.exe", - Arguments = @"-U:T -P:E -M:S -Priority:RealTime -ShowWindowMode:Hide -Wait reg delete ""HKCR\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32"" /f", - CreateWindow = false - }; - MpOAVCLSID.RunTask().Wait(); - - if (Registry.ClassesRoot.OpenSubKey(@"CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32") != null) - { - throw new Exception("Could not remove MpOAV mapping."); - } - - // Can cause ProcessHacker driver warnings without this - AmeliorationUtil.SafeRunAction(new RegistryValueAction() - { - KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity", - Value = "Enabled", - Data = 0, - }).Wait(); - AmeliorationUtil.SafeRunAction(new RegistryValueAction() - { - KeyName = @"HKLM\SYSTEM\CurrentControlSet\Control\CI\Config", - Value = "VulnerableDriverBlocklistEnable", - Data = 0, - }).Wait(); - } - public static async Task UninstallDriver() { CmdAction cmdAction = new CmdAction(); @@ -781,8 +702,6 @@ namespace TrustedUninstaller.Shared } } - - public class RegistryManager { [DllImport("advapi32.dll", SetLastError = true)] @@ -933,5 +852,99 @@ namespace TrustedUninstaller.Shared } } } + + + public class HttpProgressClient : IDisposable + { + private string _downloadUrl; + private string _destinationFilePath; + + public HttpClient Client; + + public delegate void ProgressChangedHandler(long? totalFileSize, long totalBytesDownloaded, double? progressPercentage); + + public event ProgressChangedHandler ProgressChanged; + + public HttpProgressClient() + { + Client = new HttpClient { Timeout = TimeSpan.FromDays(1) }; + } + + public async Task StartDownload(string downloadUrl, string destinationFilePath, long? size = null) + { + _downloadUrl = downloadUrl; + _destinationFilePath = destinationFilePath; + + using (var response = await Client.GetAsync(_downloadUrl, HttpCompletionOption.ResponseHeadersRead)) + await DownloadFileFromHttpResponseMessage(response, size); + } + + public Task GetAsync(string link) + { + return Client.GetAsync(link); + } + + private async Task DownloadFileFromHttpResponseMessage(HttpResponseMessage response, long? size) + { + response.EnsureSuccessStatusCode(); + + if (!size.HasValue) + size = response.Content.Headers.ContentLength; + + using (var contentStream = await response.Content.ReadAsStreamAsync()) + await ProcessContentStream(size, contentStream); + } + + private async Task ProcessContentStream(long? totalDownloadSize, Stream contentStream) + { + var totalBytesRead = 0L; + var readCount = 0L; + var buffer = new byte[8192]; + var isMoreToRead = true; + + using (var fileStream = new FileStream(_destinationFilePath, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true)) + { + do + { + var bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) + { + isMoreToRead = false; + TriggerProgressChanged(totalDownloadSize, totalBytesRead); + continue; + } + + await fileStream.WriteAsync(buffer, 0, bytesRead); + + totalBytesRead += bytesRead; + readCount += 1; + + if (readCount % 50 == 0) + TriggerProgressChanged(totalDownloadSize, totalBytesRead); + } + while (isMoreToRead); + } + } + + private void TriggerProgressChanged(long? totalDownloadSize, long totalBytesRead) + { + if (ProgressChanged == null) + return; + + double? progressPercentage = null; + if (totalDownloadSize.HasValue) + { + progressPercentage = Math.Round((double)totalBytesRead / totalDownloadSize.Value * 100, 2); + } + + + ProgressChanged(totalDownloadSize, totalBytesRead, progressPercentage); + } + + public void Dispose() + { + Client?.Dispose(); + } + } } } diff --git a/Windows.winmd b/Windows.winmd deleted file mode 100644 index fe1e718..0000000 Binary files a/Windows.winmd and /dev/null differ diff --git a/ameliorated_logo.png b/ameliorated_logo.png new file mode 100644 index 0000000..2076191 Binary files /dev/null and b/ameliorated_logo.png differ